diff --git a/src/lib/editor/extensions.ts b/src/lib/editor/extensions.ts index 746d85c..06b5f50 100644 --- a/src/lib/editor/extensions.ts +++ b/src/lib/editor/extensions.ts @@ -79,7 +79,12 @@ import { Insertion, Deletion, TrackChanges } from './extensions/trackChanges'; export const extensions = [ // textBox and columns have their own groups so only the document (not // cells/lists/boxes) admits them; the note section is last or nowhere. - Document.extend({ content: '(block | textBox | columns)+ noteSection?' }), + // Spelled as an alternation, not `… + noteSection?`: with the trailing optional the + // match state after the first block offers noteSection first, and that is the type + // TipTap's clearNodes converts a block into (`contentMatchAt(i).defaultType`). + Document.extend({ + content: '((block | textBox | columns)+ noteSection) | (block | textBox | columns)+', + }), Paragraph, Text, Bold, diff --git a/tests/unit/doc-default-type.test.ts b/tests/unit/doc-default-type.test.ts new file mode 100644 index 0000000..59fca03 --- /dev/null +++ b/tests/unit/doc-default-type.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { getSchema } from '@tiptap/core'; +import { extensions } from '../../src/lib/editor/extensions'; + +// TipTap's clearNodes rewrites a block into its parent's `defaultType`, so the +// document's must stay a paragraph at every index — noteSection there made every +// style command on a block past the first throw (Invalid content for noteSection). +describe('document content expression', () => { + it('offers a paragraph as the default type at every index', () => { + const schema = getSchema(extensions); + const p = schema.nodes.paragraph.createAndFill()!; + const doc = schema.nodes.doc.create(null, [p, p, p]); + for (let i = 0; i <= doc.childCount; i++) { + expect(doc.contentMatchAt(i).defaultType?.name).toBe('paragraph'); + } + }); + + it('still admits a note section, last only', () => { + const schema = getSchema(extensions); + const p = schema.nodes.paragraph.createAndFill()!; + const note = schema.nodes.note.create(null, p); + const section = schema.nodes.noteSection.create(null, note); + expect(() => schema.nodes.doc.createChecked(null, [p, section])).not.toThrow(); + expect(() => schema.nodes.doc.createChecked(null, [section, p])).toThrow(); + }); +});