diff --git a/packages/docusaurus-mdx-loader/src/__tests__/processor.test.ts b/packages/docusaurus-mdx-loader/src/__tests__/processor.test.ts index 46286ad41502..f187b54e5692 100644 --- a/packages/docusaurus-mdx-loader/src/__tests__/processor.test.ts +++ b/packages/docusaurus-mdx-loader/src/__tests__/processor.test.ts @@ -290,6 +290,22 @@ Body expect(result.content).toMatchSnapshot(); }); + it('extracts explicit heading ID from HTML comment with mdx1Compat.comments', async () => { + const options = createOptions({ + markdownConfig: { + format: 'md', + mdx1Compat: {comments: true}, + }, + }); + const result = await processContent( + '## Hello World ', + options, + ); + expect(result.content).toContain('id: "my-explicit-id"'); + expect(result.content).toContain('"value": "Hello World"'); + expect(result.content).not.toContain('hello-world'); + }); + it('preserves legacy heading IDs', async () => { const options = createOptions(); const result = await processContent('## Heading {#custom-id}', options); diff --git a/packages/docusaurus-mdx-loader/src/processor.ts b/packages/docusaurus-mdx-loader/src/processor.ts index aa1497600367..f7257d4105cd 100644 --- a/packages/docusaurus-mdx-loader/src/processor.ts +++ b/packages/docusaurus-mdx-loader/src/processor.ts @@ -169,7 +169,9 @@ function createProcessorUncached({ } satisfies TransformLinksOptions, ], gfm, - options.markdownConfig.mdx1Compat.comments ? remarkComment : null, + options.markdownConfig.mdx1Compat.comments + ? [remarkComment, {ast: true}] + : null, ...(options.remarkPlugins ?? []), [ unusedDirectives, diff --git a/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts index 883d40021c3b..3412c0dd0a75 100644 --- a/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts +++ b/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts @@ -10,6 +10,7 @@ import {describe, expect, it} from 'vitest'; import {remark} from 'remark'; import mdx from 'remark-mdx'; +import remarkComment from '@slorber/remark-comment'; import {u} from 'unist-builder'; import {visit} from 'unist-util-visit'; import {escapeMarkdownHeadingIds} from '@docusaurus/utils'; @@ -439,6 +440,30 @@ describe('headings remark plugin', () => { const id = await headingIdFor('# Heading {/* #my-id */}', 'md'); expect(id).not.toEqual('my-id'); }); + + it('extracts id from HTML comment when remarkComment is enabled', async () => { + const result = await process( + '## Heading ', + [[remarkComment, {ast: true}]], + {}, + 'md', + ); + const heading = result.children[0] as Heading; + expect((heading.data as {id: string}).id).toEqual('my-id'); + expect(heading).toEqual(h('Heading', 2, 'my-id')); + }); + + it('removes the remark-comment node when it is the only heading content', async () => { + const result = await process( + '## ', + [[remarkComment, {ast: true}]], + {}, + 'md', + ); + const heading = result.children[0] as Heading; + expect((heading.data as {id: string}).id).toEqual('id-only'); + expect(heading).toEqual(h(null, 2, 'id-only')); + }); }); describe('works for format MDX', () => { diff --git a/packages/docusaurus-mdx-loader/src/remark/headings/index.ts b/packages/docusaurus-mdx-loader/src/remark/headings/index.ts index 531a1a599b65..56963514fe47 100644 --- a/packages/docusaurus-mdx-loader/src/remark/headings/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/headings/index.ts @@ -61,6 +61,15 @@ function getCommentHeadingId(heading: Heading): string | undefined { } } + // remark-comment AST node: or + if (lastChild?.type === ('comment' as unknown)) { + const commentContent = (lastChild as unknown as {commentValue?: string}) + .commentValue; + if (commentContent) { + return getCommentContentHeadingId(commentContent); + } + } + return undefined; } @@ -114,7 +123,7 @@ const plugin: Plugin = function plugin({ return (root) => { function getHeadingText(heading: Heading) { const headingTextNodes = heading.children.filter( - ({type}) => !['html', 'jsx'].includes(type), + ({type}) => !['html', 'jsx', 'comment'].includes(type), ); return toString(headingTextNodes.length > 0 ? headingTextNodes : heading); }