From 814f0a3d43f535b05ea541aba31b5fa13129809a Mon Sep 17 00:00:00 2001 From: HiDeoo <494699+HiDeoo@users.noreply.github.com> Date: Mon, 12 Feb 2024 16:00:43 +0100 Subject: [PATCH] fix: tranform back unhandled markdown directives --- .changeset/many-knives-type.md | 5 ++ .../__tests__/remark-rehype/asides.test.ts | 19 +++++++ packages/starlight/integrations/asides.ts | 49 ++++++++++++++++++- packages/starlight/package.json | 1 + pnpm-lock.yaml | 3 ++ 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 .changeset/many-knives-type.md diff --git a/.changeset/many-knives-type.md b/.changeset/many-knives-type.md new file mode 100644 index 00000000000..0a006cdb358 --- /dev/null +++ b/.changeset/many-knives-type.md @@ -0,0 +1,5 @@ +--- +'@astrojs/starlight': patch +--- + +Fixes a potential text rendering issue with text containing colons. diff --git a/packages/starlight/__tests__/remark-rehype/asides.test.ts b/packages/starlight/__tests__/remark-rehype/asides.test.ts index 6b055def611..ba3548c9531 100644 --- a/packages/starlight/__tests__/remark-rehype/asides.test.ts +++ b/packages/starlight/__tests__/remark-rehype/asides.test.ts @@ -143,3 +143,22 @@ test('runs without locales config', async () => { const res = await processor.render(':::note\nTest\n::'); expect(res.code.includes('aria-label=Note"')); }); + +test('tranforms back unhandled text directives', async () => { + const res = await processor.render( + `This is a:test of a sentence with a text:name[content]{key=val} directive.` + ); + expect(res.code).toMatchInlineSnapshot(` + "
This is a:test + of a sentence with a text:name[content]{key="val"} + directive.
" + `); +}); + +test('tranforms back unhandled leaf directives', async () => { + const res = await processor.render(`::video[Title]{v=xxxxxxxxxxx}`); + expect(res.code).toMatchInlineSnapshot(` + "::video[Title]{v="xxxxxxxxxxx"} +
" + `); +}); diff --git a/packages/starlight/integrations/asides.ts b/packages/starlight/integrations/asides.ts index 0b4f9c0c5bf..9e83496865a 100644 --- a/packages/starlight/integrations/asides.ts +++ b/packages/starlight/integrations/asides.ts @@ -2,7 +2,14 @@ import type { AstroConfig, AstroUserConfig } from 'astro'; import { h as _h, s as _s, type Properties } from 'hastscript'; -import type { Paragraph as P, Root } from 'mdast'; +import type { Node, Paragraph as P, Parent, Root } from 'mdast'; +import { + type Directives, + directiveToMarkdown, + type TextDirective, + type LeafDirective, +} from 'mdast-util-directive'; +import { toMarkdown } from 'mdast-util-to-markdown'; import remarkDirective from 'remark-directive'; import type { Plugin, Transformer } from 'unified'; import { remove } from 'unist-util-remove'; @@ -37,6 +44,40 @@ function s(el: string, attrs: Properties = {}, children: any[] = []): P { }; } +/** Checks if a node is a directive. */ +function isNodeDirective(node: Node): node is Directives { + return ( + node.type === 'textDirective' || + node.type === 'leafDirective' || + node.type === 'containerDirective' + ); +} + +/** + * Transforms back directives not handled by Starlight to avoid breaking user content. + * For example, a user might write `x:y` in the middle of a sentence, where `:y` would be + * identified as a text directive, which are not used by Starlight, and we definitely want that + * text to be rendered verbatim in the output. + */ +function transformUnhandledDirective( + node: TextDirective | LeafDirective, + index: number, + parent: Parent +) { + const textNode = { + type: 'text', + value: toMarkdown(node, { extensions: [directiveToMarkdown()] }), + } as const; + if (node.type === 'textDirective') { + parent.children[index] = textNode; + } else { + parent.children[index] = { + type: 'paragraph', + children: [textNode], + }; + } +} + /** * remark plugin that converts blocks delimited with `:::` into styled * asides (a.k.a. “callouts”, “admonitions”, etc.). Depends on the @@ -102,7 +143,11 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> { const locale = pathToLocale(file.history[0], options); const t = options.useTranslations(locale); visit(tree, (node, index, parent) => { - if (!parent || index === undefined || node.type !== 'containerDirective') { + if (!parent || index === undefined || !isNodeDirective(node)) { + return; + } + if (node.type === 'textDirective' || node.type === 'leafDirective') { + transformUnhandledDirective(node, index, parent); return; } const variant = node.name; diff --git a/packages/starlight/package.json b/packages/starlight/package.json index 385e5a3aa13..0905f3ea9c5 100644 --- a/packages/starlight/package.json +++ b/packages/starlight/package.json @@ -187,6 +187,7 @@ "hast-util-select": "^6.0.2", "hastscript": "^8.0.0", "mdast-util-directive": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", "pagefind": "^1.0.3", "rehype": "^13.0.1", "remark-directive": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fde73e13a3f..9aacfa7814c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -159,6 +159,9 @@ importers: mdast-util-directive: specifier: ^3.0.0 version: 3.0.0 + mdast-util-to-markdown: + specifier: ^2.1.0 + version: 2.1.0 pagefind: specifier: ^1.0.3 version: 1.0.3