fix: paragraph styles apply to every block, not just the first

The document's content ended in an optional noteSection, so the content
match after the first child offered noteSection as its defaultType — and
that is what TipTap's clearNodes converts a block into. Every style command
on a top-level block past the first threw "Invalid content for node type
noteSection" and applied nothing. Spelling the same language as an
alternation puts a paragraph first again.
This commit is contained in:
Steffen Becker committed 2026-08-16 02:18:55 +02:00
1 parent dc1f5bb7b1
commit 8b1bef9706
2 files changed
+32 -1

No files matched your search

+6 -1
View File
@@ -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,
+26
View File
@@ -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();
});
});