Skip to content

Commit

Permalink
Let remark plugins injected by Starlight plugins handle Markdown text…
Browse files Browse the repository at this point in the history
… and leaf directives (#2056)
  • Loading branch information
HiDeoo committed Jun 27, 2024
1 parent 42aebfd commit 87e9ad0
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-dryers-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': patch
---

Fixes an issue preventing remark plugins injected by Starlight plugins to handle Markdown text and leaf directives.
49 changes: 46 additions & 3 deletions packages/starlight/__tests__/remark-rehype/asides.test.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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,
],
});

Expand Down Expand Up @@ -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.`
);
Expand All @@ -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>"
`);
});
6 changes: 5 additions & 1 deletion packages/starlight/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down
43 changes: 41 additions & 2 deletions packages/starlight/integrations/asides.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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],
},
});
},
},
};
}

0 comments on commit 87e9ad0

Please sign in to comment.