From 10b93b336cf4e3500e3003635b5afc430284d1a7 Mon Sep 17 00:00:00 2001
From: HiDeoo
Date: Tue, 4 Mar 2025 00:24:54 +0100
Subject: Support for `title`, `frame`, and `meta` Markdoc fence attributes
(#2931)
---
.changeset/healthy-rivers-divide.md | 28 +++
docs/src/content/docs/guides/authoring-content.mdx | 142 ++++++++++++++
packages/markdoc/__tests__/markdoc.test-d.ts | 212 +++++++++++++--------
packages/markdoc/index.mjs | 31 ++-
4 files changed, 326 insertions(+), 87 deletions(-)
create mode 100644 .changeset/healthy-rivers-divide.md
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:
}
```
+
+
+
+
````md
```js {2-3}
function demo() {
@@ -255,6 +259,23 @@ Some of the most common examples are shown below:
```
````
+
+
+
+
+ ````markdoc
+ ```js {% meta="{2-3}" %}
+ function demo() {
+ // This line (#2) and the next one are highlighted
+ return 'This is line #3 of this snippet';
+ }
+ ```
+ ````
+
+
+
+
+
- [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:
}
```
+
+
+
+
````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:
```
````
+
+
+
+
+ ````markdoc
+ ```js {% meta="'Individual terms' /Even.*supported/" %}
+ // Individual terms can be highlighted, too
+ function demo() {
+ return 'Even regular expressions are supported';
+ }
+ ```
+ ````
+
+
+
+
+
- [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:
}
```
+
+
+
+
````md
```js "return true;" ins="inserted" del="deleted"
function demo() {
@@ -293,6 +339,24 @@ Some of the most common examples are shown below:
```
````
+
+
+
+
+ ````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;
+ }
+ ```
+ ````
+
+
+
+
+
- [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:
}
```
+
+
+
+
````md
```diff lang="js"
function thisIsJavaScript() {
@@ -315,6 +383,25 @@ Some of the most common examples are shown below:
```
````
+
+
+
+
+ ````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!')
+ }
+ ```
+ ````
+
+
+
+
+
#### 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!');
```
+
+
+
+
+ ````md
+ ```js
+ // my-test-file.js
+ console.log('Hello World!');
+ ```
+ ````
+
+
+
+
+
````md
```js
// my-test-file.js
@@ -337,30 +439,70 @@ A code block’s optional title can be set either with a `title="..."` attribute
```
````
+
+
+
+
- [Add a title to a Terminal window](https://expressive-code.com/key-features/frames/#terminal-frames)
```bash title="Installing dependencies…"
npm install
```
+
+
+
+
````md
```bash title="Installing dependencies…"
npm install
```
````
+
+
+
+
+ ````markdoc
+ ```bash {% title="Installing dependencies…" %}
+ npm install
+ ```
+ ````
+
+
+
+
+
- [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"
```
+
+
+
+
````md
```bash frame="none"
echo "This is not rendered as a terminal despite using the bash language"
```
````
+
+
+
+
+ ````markdoc
+ ```bash {% frame="none" %}
+ echo "This is not rendered as a terminal despite using the bash language"
+ ```
+ ````
+
+
+
+
+
## 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 any> = keyof RemoveIndexSignatu
>;
type MarkdocPreset = typeof import('../index.mjs').StarlightMarkdocPreset;
+type MarkdocNodes = keyof MarkdocPreset['nodes'];
+type MarkdocNodeAttributes = keyof MarkdocPreset['nodes'][T]['attributes'];
type MarkdocTags = keyof MarkdocPreset['tags'];
type MarkdocTagAttributes = keyof MarkdocPreset['tags'][T]['attributes'];
-test('defines a tag for each user components', () => {
- expectTypeOf().toEqualTypeOf>();
+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
+ >().toEqualTypeOf();
+
+ type UnsupportedCodeProps =
+ /** The `code` and `lang` attributes mapping is tested above. */
+ | 'code'
+ | 'lang'
+ /** Not all `` 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 `` component props are mapped.
+ expectTypeOf>().toEqualTypeOf<
+ Exclude, UnsupportedCodeProps>
+ >();
+ });
});
-test('defines all `