diff options
author | HiDeoo | 2025-03-04 00:24:54 +0100 |
---|---|---|
committer | GitHub | 2025-03-04 00:24:54 +0100 |
commit | 10b93b336cf4e3500e3003635b5afc430284d1a7 (patch) | |
tree | f4938bc0fad89741e6929b81d738e8592ccbc725 | |
parent | f2123a17482c1d4020a062de974fd254d3324199 (diff) | |
download | IT.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.md | 28 | ||||
-rw-r--r-- | docs/src/content/docs/guides/authoring-content.mdx | 142 | ||||
-rw-r--r-- | packages/markdoc/__tests__/markdoc.test-d.ts | 212 | ||||
-rw-r--r-- | packages/markdoc/index.mjs | 31 |
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 */ }, }, |