From 936b4d375716a0e68c623c1dfa4b0634371471a1 Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 27 May 2026 16:06:53 +0200 Subject: [PATCH 1/2] fix(headings): skip headings in details in table of contents Fixes: #8582 Signed-off-by: Jonas --- src/plugins/extractHeadings.ts | 4 ++++ src/tests/plugins/extractHeadings.spec.js | 19 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/plugins/extractHeadings.ts b/src/plugins/extractHeadings.ts index 6da1e5df8d7..887562ce851 100644 --- a/src/plugins/extractHeadings.ts +++ b/src/plugins/extractHeadings.ts @@ -31,6 +31,10 @@ export default function extractHeadings(doc: Node) { } doc.descendants((node, offset) => { + // Don't descent into detials blocks - their headings are hidden + if (node.type.name === 'details') { + return false + } if (node.type.name !== 'heading') { return } diff --git a/src/tests/plugins/extractHeadings.spec.js b/src/tests/plugins/extractHeadings.spec.js index 5adaf5cbb74..b925e7a9818 100644 --- a/src/tests/plugins/extractHeadings.spec.js +++ b/src/tests/plugins/extractHeadings.spec.js @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +import Details from '../../nodes/Details.js' import Heading from '../../nodes/Heading.js' import extractHeadings from '../../plugins/extractHeadings.ts' import createCustomEditor from '../testHelpers/createCustomEditor.ts' @@ -35,6 +36,20 @@ describe('extractHeadings', () => { expect(headings).toEqual([]) }) + it('ignores headings inside a details block', () => { + const content = ` +

Visible heading

+
+ Details summary +

Hidden heading

+
+ ` + const doc = prepareDoc(content, [Details]) + const headings = extractHeadings(doc) + expect(headings).toHaveLength(1) + expect(headings[0].text).toBe('Visible heading') + }) + it('creates unique ids with a counter', () => { const content = `

Level 1 heading

@@ -46,8 +61,8 @@ describe('extractHeadings', () => { }) }) -const prepareDoc = (content) => { - const editor = createCustomEditor(content, [Heading]) +const prepareDoc = (content, extraExtensions = []) => { + const editor = createCustomEditor(content, [Heading, ...extraExtensions]) const doc = editor.state.doc editor.destroy() return doc From c0db4ffaff133af07a8d9de761c24f9fbaaffb31 Mon Sep 17 00:00:00 2001 From: Jonas Date: Thu, 28 May 2026 11:41:26 +0200 Subject: [PATCH 2/2] fix(headings): skip headings in blockquotes in table of contents Signed-off-by: Jonas --- src/plugins/extractHeadings.ts | 4 ++-- src/tests/plugins/extractHeadings.spec.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/plugins/extractHeadings.ts b/src/plugins/extractHeadings.ts index 887562ce851..526653f7a8f 100644 --- a/src/plugins/extractHeadings.ts +++ b/src/plugins/extractHeadings.ts @@ -31,8 +31,8 @@ export default function extractHeadings(doc: Node) { } doc.descendants((node, offset) => { - // Don't descent into detials blocks - their headings are hidden - if (node.type.name === 'details') { + // Don't descent into details/blockquote nodes - their headings are hidden + if (node.type.name === 'details' || node.type.name === 'blockquote') { return false } if (node.type.name !== 'heading') { diff --git a/src/tests/plugins/extractHeadings.spec.js b/src/tests/plugins/extractHeadings.spec.js index b925e7a9818..75e67ce6f84 100644 --- a/src/tests/plugins/extractHeadings.spec.js +++ b/src/tests/plugins/extractHeadings.spec.js @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +import { Blockquote } from '@tiptap/extension-blockquote' import Details from '../../nodes/Details.js' import Heading from '../../nodes/Heading.js' import extractHeadings from '../../plugins/extractHeadings.ts' @@ -50,6 +51,19 @@ describe('extractHeadings', () => { expect(headings[0].text).toBe('Visible heading') }) + it('ignores headings inside a block quote block', () => { + const content = ` +

Visible heading

+
+

Quoted heading

+
+ ` + const doc = prepareDoc(content, [Blockquote]) + const headings = extractHeadings(doc) + expect(headings).toHaveLength(1) + expect(headings[0].text).toBe('Visible heading') + }) + it('creates unique ids with a counter', () => { const content = `

Level 1 heading