From 87e9ad029c9730fca8df66e35828b57cd0872a61 Mon Sep 17 00:00:00 2001 From: HiDeoo Date: Thu, 27 Jun 2024 20:38:58 +0200 Subject: Let remark plugins injected by Starlight plugins handle Markdown text and leaf directives (#2056) --- .changeset/giant-dryers-fetch.md | 5 +++ .../__tests__/remark-rehype/asides.test.ts | 49 ++++++++++++++++++++-- packages/starlight/index.ts | 6 ++- packages/starlight/integrations/asides.ts | 43 ++++++++++++++++++- 4 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 .changeset/giant-dryers-fetch.md diff --git a/.changeset/giant-dryers-fetch.md b/.changeset/giant-dryers-fetch.md new file mode 100644 index 00000000..1d630146 --- /dev/null +++ b/.changeset/giant-dryers-fetch.md @@ -0,0 +1,5 @@ +--- +'@astrojs/starlight': patch +--- + +Fixes an issue preventing remark plugins injected by Starlight plugins to handle Markdown text and leaf directives. diff --git a/packages/starlight/__tests__/remark-rehype/asides.test.ts b/packages/starlight/__tests__/remark-rehype/asides.test.ts index cb145e12..a46f8e0d 100644 --- a/packages/starlight/__tests__/remark-rehype/asides.test.ts +++ b/packages/starlight/__tests__/remark-rehype/asides.test.ts @@ -1,6 +1,8 @@ import { createMarkdownProcessor } from '@astrojs/markdown-remark'; +import type { Root } from 'mdast'; +import { visit } from 'unist-util-visit'; import { describe, expect, test } from 'vitest'; -import { starlightAsides } from '../../integrations/asides'; +import { starlightAsides, remarkDirectivesRestoration } from '../../integrations/asides'; import { createTranslationSystemFromFs } from '../../utils/translations-fs'; import { StarlightConfigSchema, type StarlightUserConfig } from '../../utils/user-config'; @@ -23,6 +25,9 @@ const processor = await createMarkdownProcessor({ astroConfig: { root: new URL(import.meta.url), srcDir: new URL('./_src/', import.meta.url) }, useTranslations, }), + // The restoration plugin is run after the asides and any other plugin that may have been + // injected by Starlight plugins. + remarkDirectivesRestoration, ], }); @@ -167,13 +172,14 @@ test('runs without locales config', async () => { }, useTranslations, }), + remarkDirectivesRestoration, ], }); const res = await processor.render(':::note\nTest\n::'); expect(res.code.includes('aria-label=Note"')); }); -test('tranforms back unhandled text directives', async () => { +test('transforms 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.` ); @@ -184,10 +190,47 @@ test('tranforms back unhandled text directives', async () => { `); }); -test('tranforms back unhandled leaf directives', async () => { +test('transforms back unhandled leaf directives', async () => { const res = await processor.render(`::video[Title]{v=xxxxxxxxxxx}`); expect(res.code).toMatchInlineSnapshot(` "
::video[Title]{v="xxxxxxxxxxx"}
" `); }); + +test('lets remark plugin injected by Starlight plugins handle text and leaf directives', async () => { + const processor = await createMarkdownProcessor({ + remarkPlugins: [ + ...starlightAsides({ + starlightConfig, + astroConfig: { + root: new URL(import.meta.url), + srcDir: new URL('./_src/', import.meta.url), + }, + useTranslations, + }), + // A custom remark plugin injected by a Starlight plugin through an Astro integration would + // run before the restoration plugin. + function customRemarkPlugin() { + return function transformer(tree: Root) { + visit(tree, (node, index, parent) => { + if (node.type !== 'textDirective' || typeof index !== 'number' || !parent) return; + if (node.name === 'abbr') { + parent.children.splice(index, 1, { type: 'text', value: 'TEXT FROM REMARK PLUGIN' }); + } + }); + }; + }, + remarkDirectivesRestoration, + ], + }); + + const res = await processor.render( + `This is a:test of a sentence with a :abbr[SL]{name="Starlight"} directive handled by another remark plugin and some other text:name[content]{key=val} directives not handled by any plugin.` + ); + expect(res.code).toMatchInlineSnapshot(` + "This is a:test + of a sentence with a TEXT FROM REMARK PLUGIN directive handled by another remark plugin and some other text:name[content]{key="val"} + directives not handled by any plugin.
" + `); +}); diff --git a/packages/starlight/index.ts b/packages/starlight/index.ts index a7876ab8..c13c03bc 100644 --- a/packages/starlight/index.ts +++ b/packages/starlight/index.ts @@ -3,7 +3,7 @@ import type { AstroIntegration } from 'astro'; import { spawn } from 'node:child_process'; import { dirname, relative } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { starlightAsides } from './integrations/asides'; +import { starlightAsides, starlightDirectivesRestorationIntegration } from './integrations/asides'; import { starlightExpressiveCode } from './integrations/expressive-code/index'; import { starlightSitemap } from './integrations/sitemap'; import { vitePluginStarlightUserConfig } from './integrations/virtual-user-config'; @@ -73,6 +73,10 @@ export default function StarlightIntegration({ if (!allIntegrations.find(({ name }) => name === '@astrojs/mdx')) { integrations.push(mdx({ optimize: true })); } + // Add Starlight directives restoration integration at the end of the list so that remark + // plugins injected by Starlight plugins through Astro integrations can handle text and + // leaf directives before they are transformed back to their original form. + integrations.push(starlightDirectivesRestorationIntegration()); // Add integrations immediately after Starlight in the config array. // e.g. if a user has `integrations: [starlight(), tailwind()]`, then the order will be diff --git a/packages/starlight/integrations/asides.ts b/packages/starlight/integrations/asides.ts index 2b03b372..3218e509 100644 --- a/packages/starlight/integrations/asides.ts +++ b/packages/starlight/integrations/asides.ts @@ -1,6 +1,6 @@ ///