@@ -213,10 +213,44 @@ function withRightAlign(config: TocSwitchConfig, rightAlignPageNumbers: boolean
213213 return { ...config , display : { ...config . display , rightAlignPageNumbers } } ;
214214}
215215
216- function materializeTocContent ( doc : ProseMirrorNode , config : TocSwitchConfig ) : EntryParagraphJson [ ] {
216+ /**
217+ * Removes tocPageNumber marks when the active schema doesn't define that mark.
218+ * Some headless/test schemas omit TOC-specific marks, and nodeFromJSON fails if
219+ * unknown marks are present in generated TOC paragraph content.
220+ */
221+ function sanitizeTocContentForSchema ( content : EntryParagraphJson [ ] , editor : Editor ) : EntryParagraphJson [ ] {
222+ if ( editor . state . schema ?. marks ?. tocPageNumber ) return content ;
223+
224+ return content . map ( ( paragraph ) => {
225+ const paragraphContent = paragraph . content ;
226+ if ( ! Array . isArray ( paragraphContent ) ) return paragraph ;
227+
228+ let changed = false ;
229+ const sanitizedContent = paragraphContent . map ( ( node ) => {
230+ if ( ! node || typeof node !== 'object' ) return node ;
231+ const typedNode = node as { marks ?: Array < { type ?: string } > } ;
232+ const marks = typedNode . marks ;
233+ if ( ! Array . isArray ( marks ) ) return node ;
234+ const filteredMarks = marks . filter ( ( mark ) => mark ?. type !== 'tocPageNumber' ) ;
235+ if ( filteredMarks . length === marks . length ) return node ;
236+
237+ changed = true ;
238+ if ( filteredMarks . length === 0 ) {
239+ const { marks : _removed , ...rest } = typedNode ;
240+ return rest as typeof node ;
241+ }
242+ return { ...typedNode , marks : filteredMarks } as typeof node ;
243+ } ) ;
244+
245+ return changed ? ( { ...paragraph , content : sanitizedContent } as EntryParagraphJson ) : paragraph ;
246+ } ) ;
247+ }
248+
249+ function materializeTocContent ( doc : ProseMirrorNode , config : TocSwitchConfig , editor : Editor ) : EntryParagraphJson [ ] {
217250 const sources = collectTocSources ( doc , config ) ;
218251 const entryParagraphs = buildTocEntryParagraphs ( sources , config ) ;
219- return entryParagraphs . length > 0 ? entryParagraphs : NO_ENTRIES_PLACEHOLDER ;
252+ const content = entryParagraphs . length > 0 ? entryParagraphs : NO_ENTRIES_PLACEHOLDER ;
253+ return sanitizeTocContentForSchema ( content , editor ) ;
220254}
221255
222256// ---------------------------------------------------------------------------
@@ -244,7 +278,7 @@ export function tocConfigureWrapper(
244278 // Patch value takes priority; fall back to existing node attr.
245279 const effectiveRightAlign =
246280 input . patch . rightAlignPageNumbers ?? ( resolved . node . attrs ?. rightAlignPageNumbers as boolean | undefined ) ;
247- const nextContent = materializeTocContent ( editor . state . doc , withRightAlign ( patched , effectiveRightAlign ) ) ;
281+ const nextContent = materializeTocContent ( editor . state . doc , withRightAlign ( patched , effectiveRightAlign ) , editor ) ;
248282
249283 if ( areTocConfigsEqual ( currentConfig , patched ) && ! rightAlignChanged ) {
250284 return tocFailure ( 'NO_OP' , 'Configuration patch produced no change.' ) ;
@@ -327,7 +361,7 @@ function tocUpdateAll(editor: Editor, input: TocUpdateInput, options?: MutationO
327361 const resolved = resolveTocTarget ( editor . state . doc , input . target ) ;
328362 const config = parseTocInstruction ( resolved . node . attrs ?. instruction ?? '' ) ;
329363 const rightAlign = resolved . node . attrs ?. rightAlignPageNumbers as boolean | undefined ;
330- const content = materializeTocContent ( editor . state . doc , withRightAlign ( config , rightAlign ) ) ;
364+ const content = materializeTocContent ( editor . state . doc , withRightAlign ( config , rightAlign ) , editor ) ;
331365
332366 // NO_OP detection: compare new content against existing before executing.
333367 // The PM command returns "found" (not "content changed"), so receipt-based
@@ -608,7 +642,11 @@ export function createTableOfContentsWrapper(
608642 // Build instruction from config patch or use defaults
609643 const config = input . config ? applyTocPatchTyped ( DEFAULT_TOC_CONFIG , input . config ) : DEFAULT_TOC_CONFIG ;
610644 const instruction = serializeTocInstruction ( config ) ;
611- const content = materializeTocContent ( editor . state . doc , withRightAlign ( config , input . config ?. rightAlignPageNumbers ) ) ;
645+ const content = materializeTocContent (
646+ editor . state . doc ,
647+ withRightAlign ( config , input . config ?. rightAlignPageNumbers ) ,
648+ editor ,
649+ ) ;
612650
613651 const sdBlockId = uuidv4 ( ) ;
614652
0 commit comments