summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiDeoo2024-06-27 20:38:58 +0200
committerGitHub2024-06-27 20:38:58 +0200
commit87e9ad029c9730fca8df66e35828b57cd0872a61 (patch)
tree9f877d104d0240545adacb5db561b1e6e9f1b272
parent42aebfd31a800a2f4b9a7697f1c31da9e8a0cfaa (diff)
downloadIT.starlight-87e9ad029c9730fca8df66e35828b57cd0872a61.tar.gz
IT.starlight-87e9ad029c9730fca8df66e35828b57cd0872a61.tar.bz2
IT.starlight-87e9ad029c9730fca8df66e35828b57cd0872a61.zip
Let remark plugins injected by Starlight plugins handle Markdown text and leaf directives (#2056)
-rw-r--r--.changeset/giant-dryers-fetch.md5
-rw-r--r--packages/starlight/__tests__/remark-rehype/asides.test.ts49
-rw-r--r--packages/starlight/index.ts6
-rw-r--r--packages/starlight/integrations/asides.ts43
4 files changed, 97 insertions, 6 deletions
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(`
"<p>::video[Title]{v="xxxxxxxxxxx"}
</p>"
`);
});
+
+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(`
+ "<p>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.</p>"
+ `);
+});
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 @@
/// <reference types="mdast-util-directive" />
-import type { AstroConfig, AstroUserConfig } from 'astro';
+import type { AstroConfig, AstroIntegration, AstroUserConfig } from 'astro';
import { h as _h, s as _s, type Properties } from 'hastscript';
import type { Node, Paragraph as P, Parent, Root } from 'mdast';
import {
@@ -146,7 +146,6 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
return;
}
if (node.type === 'textDirective' || node.type === 'leafDirective') {
- transformUnhandledDirective(node, index, parent);
return;
}
const variant = node.name;
@@ -210,3 +209,43 @@ type RemarkPlugins = NonNullable<NonNullable<AstroUserConfig['markdown']>['remar
export function starlightAsides(options: AsidesOptions): RemarkPlugins {
return [remarkDirective, remarkAsides(options)];
}
+
+export function remarkDirectivesRestoration() {
+ return function transformer(tree: Root) {
+ visit(tree, (node, index, parent) => {
+ if (
+ index !== undefined &&
+ parent &&
+ (node.type === 'textDirective' || node.type === 'leafDirective')
+ ) {
+ transformUnhandledDirective(node, index, parent);
+ return;
+ }
+ });
+ };
+}
+
+/**
+ * Directives not handled by Starlight are transformed back to their original form to avoid
+ * breaking user content.
+ * To allow remark plugins injected by Starlight plugins through Astro integrations to handle
+ * such directives, we need to restore unhandled text and leaf directives back to their original
+ * form only after all these other plugins have run.
+ * To do so, we run a remark plugin restoring these directives back to their original form from
+ * another Astro integration that runs after all the ones that may have been injected by Starlight
+ * plugins.
+ */
+export function starlightDirectivesRestorationIntegration(): AstroIntegration {
+ return {
+ name: 'starlight-directives-restoration',
+ hooks: {
+ 'astro:config:setup': ({ updateConfig }) => {
+ updateConfig({
+ markdown: {
+ remarkPlugins: [remarkDirectivesRestoration],
+ },
+ });
+ },
+ },
+ };
+}