summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiDeoo2025-03-04 00:24:54 +0100
committerGitHub2025-03-04 00:24:54 +0100
commit10b93b336cf4e3500e3003635b5afc430284d1a7 (patch)
treef4938bc0fad89741e6929b81d738e8592ccbc725
parentf2123a17482c1d4020a062de974fd254d3324199 (diff)
downloadIT.starlight-10b93b336cf4e3500e3003635b5afc430284d1a7.tar.gz
IT.starlight-10b93b336cf4e3500e3003635b5afc430284d1a7.tar.bz2
IT.starlight-10b93b336cf4e3500e3003635b5afc430284d1a7.zip
Support for `title`, `frame`, and `meta` Markdoc fence attributes (#2931)
-rw-r--r--.changeset/healthy-rivers-divide.md28
-rw-r--r--docs/src/content/docs/guides/authoring-content.mdx142
-rw-r--r--packages/markdoc/__tests__/markdoc.test-d.ts212
-rw-r--r--packages/markdoc/index.mjs31
4 files changed, 326 insertions, 87 deletions
diff --git a/.changeset/healthy-rivers-divide.md b/.changeset/healthy-rivers-divide.md
new file mode 100644
index 00000000..98988154
--- /dev/null
+++ b/.changeset/healthy-rivers-divide.md
@@ -0,0 +1,28 @@
+---
+'@astrojs/starlight-markdoc': minor
+---
+
+Adds support for the `title`, `frame`, and `meta` fence attributes to code blocks.
+
+These new optional attributes add support for Expressive Code [text & line markers](https://expressive-code.com/key-features/text-markers/). The following example renders a code block using a [terminal frame](https://expressive-code.com/key-features/frames/#terminal-frames) with a [title](https://expressive-code.com/key-features/frames/#code-editor-frames):
+
+````mdoc
+```js {% title="editor.exe" frame="terminal" %}
+console.log('Hello, world!');
+```
+````
+
+Any other text or line markers should be specified using the `meta` fence attribute. For example, the following code block renders a code block using the `diff` syntax combined with the `js` language syntax highlighting and the `markers` text highlighted:
+
+````mdoc
+```diff {% meta="lang=js 'markers'" %}
+ function thisIsJavaScript() {
+ // This entire block gets highlighted as JavaScript,
+ // and we can still add diff markers to it!
+- console.log('Old code to be removed')
++ console.log('New and shiny code!')
+ }
+```
+````
+
+To learn more about all the available options, check out the [Expressive Code documentation](https://expressive-code.com/key-features/text-markers/#usage-in-markdown--mdx).
diff --git a/docs/src/content/docs/guides/authoring-content.mdx b/docs/src/content/docs/guides/authoring-content.mdx
index f59d59fd..6598ceb9 100644
--- a/docs/src/content/docs/guides/authoring-content.mdx
+++ b/docs/src/content/docs/guides/authoring-content.mdx
@@ -246,6 +246,10 @@ Some of the most common examples are shown below:
}
```
+ <Tabs syncKey="content-type">
+
+ <TabItem label="MDX">
+
````md
```js {2-3}
function demo() {
@@ -255,6 +259,23 @@ Some of the most common examples are shown below:
```
````
+ </TabItem>
+
+ <TabItem label="Markdoc">
+
+ ````markdoc
+ ```js {% meta="{2-3}" %}
+ function demo() {
+ // This line (#2) and the next one are highlighted
+ return 'This is line #3 of this snippet';
+ }
+ ```
+ ````
+
+ </TabItem>
+
+ </Tabs>
+
- [Mark selections of text using the `" "` marker or regular expressions](https://expressive-code.com/key-features/text-markers/#marking-individual-text-inside-lines):
```js "Individual terms" /Even.*supported/
@@ -264,6 +285,10 @@ Some of the most common examples are shown below:
}
```
+ <Tabs syncKey="content-type">
+
+ <TabItem label="MDX">
+
````md
```js "Individual terms" /Even.*supported/
// Individual terms can be highlighted, too
@@ -273,6 +298,23 @@ Some of the most common examples are shown below:
```
````
+ </TabItem>
+
+ <TabItem label="Markdoc">
+
+ ````markdoc
+ ```js {% meta="'Individual terms' /Even.*supported/" %}
+ // Individual terms can be highlighted, too
+ function demo() {
+ return 'Even regular expressions are supported';
+ }
+ ```
+ ````
+
+ </TabItem>
+
+ </Tabs>
+
- [Mark text or lines as inserted or deleted with `ins` or `del`](https://expressive-code.com/key-features/text-markers/#selecting-inline-marker-types-mark-ins-del):
```js "return true;" ins="inserted" del="deleted"
@@ -283,6 +325,10 @@ Some of the most common examples are shown below:
}
```
+ <Tabs syncKey="content-type">
+
+ <TabItem label="MDX">
+
````md
```js "return true;" ins="inserted" del="deleted"
function demo() {
@@ -293,6 +339,24 @@ Some of the most common examples are shown below:
```
````
+ </TabItem>
+
+ <TabItem label="Markdoc">
+
+ ````markdoc
+ ```js {% meta="'return true;' ins='inserted' del='deleted'" %}
+ function demo() {
+ console.log('These are inserted and deleted marker types');
+ // The return statement uses the default marker type
+ return true;
+ }
+ ```
+ ````
+
+ </TabItem>
+
+ </Tabs>
+
- [Combine syntax highlighting with `diff`-like syntax](https://expressive-code.com/key-features/text-markers/#combining-syntax-highlighting-with-diff-like-syntax):
```diff lang="js"
@@ -304,6 +368,10 @@ Some of the most common examples are shown below:
}
```
+ <Tabs syncKey="content-type">
+
+ <TabItem label="MDX">
+
````md
```diff lang="js"
function thisIsJavaScript() {
@@ -315,6 +383,25 @@ Some of the most common examples are shown below:
```
````
+ </TabItem>
+
+ <TabItem label="Markdoc">
+
+ ````markdoc
+ ```diff {% meta="lang='js'" %}
+ function thisIsJavaScript() {
+ // This entire block gets highlighted as JavaScript,
+ // and we can still add diff markers to it!
+ - console.log('Old code to be removed')
+ + console.log('New and shiny code!')
+ }
+ ```
+ ````
+
+ </TabItem>
+
+ </Tabs>
+
#### Frames and titles
Code blocks can be rendered inside a window-like frame.
@@ -330,6 +417,21 @@ A code block’s optional title can be set either with a `title="..."` attribute
console.log('Hello World!');
```
+ <Tabs syncKey="content-type">
+
+ <TabItem label="MDX">
+
+ ````md
+ ```js
+ // my-test-file.js
+ console.log('Hello World!');
+ ```
+ ````
+
+ </TabItem>
+
+ <TabItem label="Markdoc">
+
````md
```js
// my-test-file.js
@@ -337,30 +439,70 @@ A code block’s optional title can be set either with a `title="..."` attribute
```
````
+ </TabItem>
+
+ </Tabs>
+
- [Add a title to a Terminal window](https://expressive-code.com/key-features/frames/#terminal-frames)
```bash title="Installing dependencies…"
npm install
```
+ <Tabs syncKey="content-type">
+
+ <TabItem label="MDX">
+
````md
```bash title="Installing dependencies…"
npm install
```
````
+ </TabItem>
+
+ <TabItem label="Markdoc">
+
+ ````markdoc
+ ```bash {% title="Installing dependencies…" %}
+ npm install
+ ```
+ ````
+
+ </TabItem>
+
+ </Tabs>
+
- [Disable window frames with `frame="none"`](https://expressive-code.com/key-features/frames/#overriding-frame-types)
```bash frame="none"
echo "This is not rendered as a terminal despite using the bash language"
```
+ <Tabs syncKey="content-type">
+
+ <TabItem label="MDX">
+
````md
```bash frame="none"
echo "This is not rendered as a terminal despite using the bash language"
```
````
+ </TabItem>
+
+ <TabItem label="Markdoc">
+
+ ````markdoc
+ ```bash {% frame="none" %}
+ echo "This is not rendered as a terminal despite using the bash language"
+ ```
+ ````
+
+ </TabItem>
+
+ </Tabs>
+
## Details
Details (also known as “disclosures” or “accordions”) are useful to hide content that is not immediately relevant.
diff --git a/packages/markdoc/__tests__/markdoc.test-d.ts b/packages/markdoc/__tests__/markdoc.test-d.ts
index 868f2c97..d0cc0067 100644
--- a/packages/markdoc/__tests__/markdoc.test-d.ts
+++ b/packages/markdoc/__tests__/markdoc.test-d.ts
@@ -1,5 +1,5 @@
import type { ComponentProps, HTMLAttributes } from 'astro/types';
-import { expectTypeOf, test } from 'vitest';
+import { describe, expectTypeOf, test } from 'vitest';
import {
Aside,
@@ -22,94 +22,138 @@ type UserComponentProps<T extends (args: any) => any> = keyof RemoveIndexSignatu
>;
type MarkdocPreset = typeof import('../index.mjs').StarlightMarkdocPreset;
+type MarkdocNodes = keyof MarkdocPreset['nodes'];
+type MarkdocNodeAttributes<T extends MarkdocNodes> = keyof MarkdocPreset['nodes'][T]['attributes'];
type MarkdocTags = keyof MarkdocPreset['tags'];
type MarkdocTagAttributes<T extends MarkdocTags> = keyof MarkdocPreset['tags'][T]['attributes'];
-test('defines a tag for each user components', () => {
- expectTypeOf<MarkdocTags>().toEqualTypeOf<Lowercase<UserComponents>>();
+describe('nodes', () => {
+ test('defines attributes for fenced code blocks with support for some text markers', () => {
+ type FenceAttributes = MarkdocNodeAttributes<'fence'>;
+
+ // Markdoc default fence attributes are `content` and `language`.
+ type MarkdocFenceAttributes = 'content' | 'language';
+
+ // Ensure Markdoc default fence attributes are always mapped.
+ expectTypeOf<
+ Extract<FenceAttributes, MarkdocFenceAttributes>
+ >().toEqualTypeOf<MarkdocFenceAttributes>();
+
+ type UnsupportedCodeProps =
+ /** The `code` and `lang` attributes mapping is tested above. */
+ | 'code'
+ | 'lang'
+ /** Not all `<Code>` component props are supported in code fences. */
+ | 'class'
+ | 'locale'
+ | 'preserveIndent'
+ | 'useDiffSyntax'
+ | 'wrap'
+ /**
+ * Some props cannot be described using Markdoc attribute validation syntax.
+ * @see {@link file://./../index.mjs}
+ */
+ | 'mark'
+ | 'ins'
+ | 'del';
+
+ // Ensure all non-unsupported `<Code>` component props are mapped.
+ expectTypeOf<Exclude<FenceAttributes, MarkdocFenceAttributes>>().toEqualTypeOf<
+ Exclude<UserComponentProps<typeof Code>, UnsupportedCodeProps>
+ >();
+ });
});
-test('defines all `<Aside>` component attributes', () => {
- expectTypeOf<MarkdocTagAttributes<'aside'>>().toEqualTypeOf<UserComponentProps<typeof Aside>>();
-});
-
-test('defines all `<Badge>` component attributes', () => {
- /**
- * Only supports a list of well-known `<span>` attributes.
- * @see {@link file://./../html.mjs}
- */
- type UnsupportedBadgeProps = Exclude<keyof HTMLAttributes<'span'>, WellKnownElementAttributes>;
-
- expectTypeOf<MarkdocTagAttributes<'badge'>>().toEqualTypeOf<
- Exclude<UserComponentProps<typeof Badge>, UnsupportedBadgeProps>
- >();
-});
-
-test('defines all `<Card>` component attributes', () => {
- expectTypeOf<MarkdocTagAttributes<'card'>>().toEqualTypeOf<UserComponentProps<typeof Card>>();
-});
-
-test('defines all `<CardGrid>` component attributes', () => {
- expectTypeOf<MarkdocTagAttributes<'cardgrid'>>().toEqualTypeOf<
- UserComponentProps<typeof CardGrid>
- >();
-});
-
-test('defines all `<Code>` component attributes', () => {
- /** @see {@link file://./../index.mjs} */
- type UnsupportedCodeProps = 'mark' | 'ins' | 'del';
-
- expectTypeOf<MarkdocTagAttributes<'code'>>().toEqualTypeOf<
- Exclude<UserComponentProps<typeof Code>, UnsupportedCodeProps>
- >();
-});
-
-test('defines all `<FileTree>` component attributes', () => {
- expectTypeOf<MarkdocTagAttributes<'filetree'>>().toEqualTypeOf<
- UserComponentProps<typeof FileTree>
- >();
-});
-
-test('defines all `<Icon>` component attributes', () => {
- expectTypeOf<MarkdocTagAttributes<'icon'>>().toEqualTypeOf<UserComponentProps<typeof Icon>>();
-});
-
-test('defines all `<LinkButton>` component attributes', () => {
- /**
- * Only supports a list of well-known `<a>` attributes.
- * @see {@link file://./../html.mjs}
- */
- type UnsupportedLinkButtonProps = Exclude<keyof HTMLAttributes<'a'>, WellKnownAnchorAttributes>;
-
- expectTypeOf<MarkdocTagAttributes<'linkbutton'>>().toEqualTypeOf<
- Exclude<UserComponentProps<typeof LinkButton>, UnsupportedLinkButtonProps>
- >();
-});
-
-test('defines all `<LinkCard>` component attributes', () => {
- /**
- * Only supports a list of well-known `<a>` attributes.
- * @see {@link file://./../html.mjs}
- */
- type UnsupportedLinkCardProps = Exclude<keyof HTMLAttributes<'a'>, WellKnownAnchorAttributes>;
-
- expectTypeOf<MarkdocTagAttributes<'linkcard'>>().toEqualTypeOf<
- Exclude<UserComponentProps<typeof LinkCard>, UnsupportedLinkCardProps>
- >();
-});
-
-test('defines all `<Steps>` component attributes', () => {
- expectTypeOf<MarkdocTagAttributes<'steps'>>().toEqualTypeOf<UserComponentProps<typeof Steps>>();
-});
-
-test('defines all `<TabItem>` component attributes', () => {
- expectTypeOf<MarkdocTagAttributes<'tabitem'>>().toEqualTypeOf<
- UserComponentProps<typeof TabItem>
- >();
-});
-
-test('defines all `<Tabs>` component attributes', () => {
- expectTypeOf<MarkdocTagAttributes<'tabs'>>().toEqualTypeOf<UserComponentProps<typeof Tabs>>();
+describe('tags', () => {
+ test('defines a tag for each user components', () => {
+ expectTypeOf<MarkdocTags>().toEqualTypeOf<Lowercase<UserComponents>>();
+ });
+
+ test('defines all `<Aside>` component attributes', () => {
+ expectTypeOf<MarkdocTagAttributes<'aside'>>().toEqualTypeOf<UserComponentProps<typeof Aside>>();
+ });
+
+ test('defines all `<Badge>` component attributes', () => {
+ /**
+ * Only supports a list of well-known `<span>` attributes.
+ * @see {@link file://./../html.mjs}
+ */
+ type UnsupportedBadgeProps = Exclude<keyof HTMLAttributes<'span'>, WellKnownElementAttributes>;
+
+ expectTypeOf<MarkdocTagAttributes<'badge'>>().toEqualTypeOf<
+ Exclude<UserComponentProps<typeof Badge>, UnsupportedBadgeProps>
+ >();
+ });
+
+ test('defines all `<Card>` component attributes', () => {
+ expectTypeOf<MarkdocTagAttributes<'card'>>().toEqualTypeOf<UserComponentProps<typeof Card>>();
+ });
+
+ test('defines all `<CardGrid>` component attributes', () => {
+ expectTypeOf<MarkdocTagAttributes<'cardgrid'>>().toEqualTypeOf<
+ UserComponentProps<typeof CardGrid>
+ >();
+ });
+
+ test('defines all `<Code>` component attributes', () => {
+ /**
+ * Some props cannot be described using Markdoc attribute validation syntax.
+ * @see {@link file://./../index.mjs}
+ */
+ type UnsupportedCodeProps = 'mark' | 'ins' | 'del';
+
+ expectTypeOf<MarkdocTagAttributes<'code'>>().toEqualTypeOf<
+ Exclude<UserComponentProps<typeof Code>, UnsupportedCodeProps>
+ >();
+ });
+
+ test('defines all `<FileTree>` component attributes', () => {
+ expectTypeOf<MarkdocTagAttributes<'filetree'>>().toEqualTypeOf<
+ UserComponentProps<typeof FileTree>
+ >();
+ });
+
+ test('defines all `<Icon>` component attributes', () => {
+ expectTypeOf<MarkdocTagAttributes<'icon'>>().toEqualTypeOf<UserComponentProps<typeof Icon>>();
+ });
+
+ test('defines all `<LinkButton>` component attributes', () => {
+ /**
+ * Only supports a list of well-known `<a>` attributes.
+ * @see {@link file://./../html.mjs}
+ */
+ type UnsupportedLinkButtonProps = Exclude<keyof HTMLAttributes<'a'>, WellKnownAnchorAttributes>;
+
+ expectTypeOf<MarkdocTagAttributes<'linkbutton'>>().toEqualTypeOf<
+ Exclude<UserComponentProps<typeof LinkButton>, UnsupportedLinkButtonProps>
+ >();
+ });
+
+ test('defines all `<LinkCard>` component attributes', () => {
+ /**
+ * Only supports a list of well-known `<a>` attributes.
+ * @see {@link file://./../html.mjs}
+ */
+ type UnsupportedLinkCardProps = Exclude<keyof HTMLAttributes<'a'>, WellKnownAnchorAttributes>;
+
+ expectTypeOf<MarkdocTagAttributes<'linkcard'>>().toEqualTypeOf<
+ Exclude<UserComponentProps<typeof LinkCard>, UnsupportedLinkCardProps>
+ >();
+ });
+
+ test('defines all `<Steps>` component attributes', () => {
+ expectTypeOf<MarkdocTagAttributes<'steps'>>().toEqualTypeOf<UserComponentProps<typeof Steps>>();
+ });
+
+ test('defines all `<TabItem>` component attributes', () => {
+ expectTypeOf<MarkdocTagAttributes<'tabitem'>>().toEqualTypeOf<
+ UserComponentProps<typeof TabItem>
+ >();
+ });
+
+ test('defines all `<Tabs>` component attributes', () => {
+ expectTypeOf<MarkdocTagAttributes<'tabs'>>().toEqualTypeOf<UserComponentProps<typeof Tabs>>();
+ });
});
type WellKnownElementAttributes = keyof typeof import('../html.mjs').WellKnownElementAttributes;
diff --git a/packages/markdoc/index.mjs b/packages/markdoc/index.mjs
index 8c959b5e..21260cf5 100644
--- a/packages/markdoc/index.mjs
+++ b/packages/markdoc/index.mjs
@@ -32,9 +32,33 @@ export const StarlightMarkdocPreset = {
* Markdoc ignores meta attributes (markers) after a fence block (e.g.
* ```js title="example.js" del={2} ins={3-4} {6} ).
* This means that Expressive Code markers defined after the fence block are ignored and
- * users would need to use the `code` tag instead.
+ * users would need to either use the Markdoc syntax for fence attributes or the `code` tag
+ * instead.
*
* @see https://github.com/withastro/astro/blob/9f943c1344671b569a0d1ddba683b3cca0068adc/packages/integrations/markdoc/src/extensions/shiki.ts#L15-L17
+ * @see https://github.com/markdoc/markdoc/discussions/318#discussioncomment-4821979
+ */
+ frame: {
+ type: String,
+ required: false,
+ default: 'auto',
+ matches: ['auto', 'code', 'terminal', 'none'],
+ },
+ meta: {
+ type: String,
+ required: false,
+ },
+ title: {
+ type: String,
+ required: false,
+ },
+ /**
+ * `mark`, `ins`, `del`, and the label syntax are not supported as the Markdoc attribute
+ * validation syntax does not allow to describe properly all the possible values.
+ * Users should use the `meta` attribute instead.
+ *
+ * @see https://expressive-code.com/key-features/code-component/#mark--ins--del
+ * @see https://expressive-code.com/key-features/code-component/#meta
*/
},
},
@@ -148,11 +172,12 @@ export const StarlightMarkdocPreset = {
default: false,
},
/**
- * `mark`, `ins`, and `del` are not supported as the Markdoc attribute validation syntax
- * does not allow to describe properly all the possible values.
+ * `mark`, `ins`, `del`, and the label syntax are not supported as the Markdoc attribute
+ * validation syntax does not allow to describe properly all the possible values.
* Users should use the `meta` attribute instead.
*
* @see https://expressive-code.com/key-features/code-component/#mark--ins--del
+ * @see https://expressive-code.com/key-features/code-component/#meta
*/
},
},