From dc3583567d63249364c0d801ac2effa05f251650 Mon Sep 17 00:00:00 2001 From: Seth Falco Date: Sun, 2 Aug 2026 01:38:17 +0100 Subject: [PATCH 1/2] fix(metadata): preserve MDX special symbols in inline code syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Markdown/MDX, any special characters between inline code should be displayed literally. The problem at hand was HTML tags, but this would apply to underscores, colons, etc. as well. The description was being mangled before, for example: "… of `` tags for headings." → "… of `` tags for headings." Now we correctly set the description: "… of tags for headings." → "… of tags for headings." It might've be better if we parsed the content character by character, and just skipped the pointer to after the inline code when found, but that'd be a drastic change for little benefit. Instead, I took the marker approach proposed by Sébastien Lorber—using U+FFFE and U+FFFF. I settled on them since they are "not characters" so we shouldn't encounter them organically. See: https://en.wikipedia.org/wiki/Specials_(Unicode_block) --- .../src/__tests__/markdownUtils.test.ts | 20 +++++++++++++++++++ .../docusaurus-utils/src/markdownUtils.ts | 18 ++++++++++++++--- .../doc-with-complex-description.mdx | 3 +++ website/_dogfooding/docs-tests-sidebars.js | 1 + 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 website/_dogfooding/_docs tests/doc-with-complex-description.mdx diff --git a/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts b/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts index a81e8cfff103..e81622499d8b 100644 --- a/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts @@ -197,6 +197,26 @@ describe('createExcerpt', () => { `), ).toBe('Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); }); + + it('creates excerpt with XML tag inside inline code', () => { + expect( + createExcerpt(dedent` + # Markdown Regular Title + + This paragraph includes a link to the \`\` documentation. + `), + ).toBe('This paragraph includes a link to the documentation.'); + }); + + it('creates excerpt with XML tag inside inline code with hyperlink', () => { + expect( + createExcerpt(dedent` + # Markdown Regular Title + + This paragraph includes a link to the [\`\`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/metadata) documentation. + `), + ).toBe('This paragraph includes a link to the documentation.'); + }); }); describe('parseMarkdownContentTitle', () => { diff --git a/packages/docusaurus-utils/src/markdownUtils.ts b/packages/docusaurus-utils/src/markdownUtils.ts index 216d0122e7ad..9626e14ec4d1 100644 --- a/packages/docusaurus-utils/src/markdownUtils.ts +++ b/packages/docusaurus-utils/src/markdownUtils.ts @@ -16,6 +16,12 @@ import type { // server-side when we infer metadata like `title` and `description` from the // content. Most parsing is still done in MDX through the mdx-loader. +/** + * Characters that have markup behavior for the Markdown/MDX renderer to + * interpret. + */ +const MARKUP_CHARS = ['_', ':', '*', '<', '>', '~', '!', '[', ']']; + /** * Hacky temporary escape hatch for Crowdin bad MDX support * See https://docusaurus.io/docs/i18n/crowdin#mdx @@ -143,6 +149,12 @@ export function createExcerpt(fileString: string): string | undefined { } const cleanedLine = fileLine + // Unwrap inline code. + .replace(/`(?.+?)`/g, (_, text) => { + return MARKUP_CHARS.reduce((acc, val) => { + return acc.replaceAll(val, `\u{FFFE}${val.codePointAt(0)}\u{FFFF}`); + }, text); + }) // Remove HTML tags. .replace(/<[^>]*>/g, '') // Remove Title headers @@ -159,8 +171,6 @@ export function createExcerpt(fileString: string): string | undefined { .replace(/\[\^.+?\](?:: .*$)?/g, '') // Remove inline links. .replace(/\[(?.*?)\][[(].*?[\])]/g, '$1') - // Remove inline code. - .replace(/`(?.+?)`/g, '$1') // Remove blockquotes. .replace(/^\s{0,3}>\s?/g, '') // Remove admonition definition. @@ -172,7 +182,9 @@ export function createExcerpt(fileString: string): string | undefined { .trim(); if (cleanedLine) { - return cleanedLine; + return MARKUP_CHARS.reduce((acc, val) => { + return acc.replaceAll(`\u{FFFE}${val.codePointAt(0)}\u{FFFF}`, val); + }, cleanedLine); } } diff --git a/website/_dogfooding/_docs tests/doc-with-complex-description.mdx b/website/_dogfooding/_docs tests/doc-with-complex-description.mdx new file mode 100644 index 000000000000..ebb5e46e85c6 --- /dev/null +++ b/website/_dogfooding/_docs tests/doc-with-complex-description.mdx @@ -0,0 +1,3 @@ +# Doc with Complex Description + +One Two _Three_ `` `:Five:` Six ![Seven](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII) diff --git a/website/_dogfooding/docs-tests-sidebars.js b/website/_dogfooding/docs-tests-sidebars.js index d8dac0c10e50..da20f4e6a9b7 100644 --- a/website/_dogfooding/docs-tests-sidebars.js +++ b/website/_dogfooding/docs-tests-sidebars.js @@ -22,6 +22,7 @@ const sidebars = { }, 'doc-without-sidebar', 'doc-with-another-sidebar', + 'doc-with-complex-description', 'doc-with-last-update', { type: 'category', From 8259e1b51625079636ac4c6ac88b11e050689501 Mon Sep 17 00:00:00 2001 From: Seth Falco Date: Sun, 2 Aug 2026 02:02:56 +0100 Subject: [PATCH 2/2] chore(spellcheck): ignore Unicode character syntax in JavaScript --- .cspell.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cspell.json b/.cspell.json index c7bea3bd6aa7..3e9dd8c3257a 100644 --- a/.cspell.json +++ b/.cspell.json @@ -53,6 +53,6 @@ "*.min.*", "jest/vendor" ], - "ignoreRegExpList": ["Email", "Urls", "#[\\w-]*"], + "ignoreRegExpList": ["Email", "Urls", "#[\\w-]*", "\\u\\{[0-9A-F]{4}\\}"], "enableFiletypes": ["mdx"] }