From 48b3f7d20684fa1add82168379006abcce12d96b Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Fri, 30 Oct 2026 16:15:05 +0100 Subject: [PATCH 1/7] fix(stringify): keep task item text in loose lists In a loose list the checkbox opens the item's first paragraph; the handler dropped that whole paragraph. Also measure continuation indent before the checkbox, so nested blocks get 2 spaces instead of 6. Refs #525 --- packages/comark/SPEC/GFM/task-list-loose.md | 108 ++++++++++++++++ .../SPEC/GFM/task-list-nested-blocks.md | 116 ++++++++++++++++++ .../src/internal/stringify/handlers/li.ts | 40 +++++- 3 files changed, 258 insertions(+), 6 deletions(-) create mode 100644 packages/comark/SPEC/GFM/task-list-loose.md create mode 100644 packages/comark/SPEC/GFM/task-list-nested-blocks.md diff --git a/packages/comark/SPEC/GFM/task-list-loose.md b/packages/comark/SPEC/GFM/task-list-loose.md new file mode 100644 index 000000000..24ea7977d --- /dev/null +++ b/packages/comark/SPEC/GFM/task-list-loose.md @@ -0,0 +1,108 @@ +## Input + +```md +- [ ] Option one + + > Why this is wrong. + +- [x] Option two + + More about it. +``` + +## AST + +```json +{ + "frontmatter": {}, + "meta": {}, + "nodes": [ + [ + "ul", + { + "class": "contains-task-list" + }, + [ + "li", + { + "class": "task-list-item" + }, + [ + "p", + {}, + [ + "input", + { + "class": "task-list-item-checkbox", + "type": "checkbox", + ":disabled": "true" + } + ], + " Option one" + ], + [ + "blockquote", + {}, + "Why this is wrong." + ] + ], + [ + "li", + { + "class": "task-list-item" + }, + [ + "p", + {}, + [ + "input", + { + "class": "task-list-item-checkbox", + "type": "checkbox", + ":disabled": "true", + ":checked": "true" + } + ], + " Option two" + ], + [ + "p", + {}, + "More about it." + ] + ] + ] + ] +} +``` + +## HTML + +```html + +``` + +## Markdown + +```md +- [ ] Option one + > Why this is wrong. +- [x] Option two + + More about it. +``` diff --git a/packages/comark/SPEC/GFM/task-list-nested-blocks.md b/packages/comark/SPEC/GFM/task-list-nested-blocks.md new file mode 100644 index 000000000..0ab676b87 --- /dev/null +++ b/packages/comark/SPEC/GFM/task-list-nested-blocks.md @@ -0,0 +1,116 @@ +## Input + +```md +- [ ] Option one + > Why this is wrong. +- [x] Option two + ```js + x + ``` +``` + +## AST + +```json +{ + "frontmatter": {}, + "meta": {}, + "nodes": [ + [ + "ul", + { + "class": "contains-task-list" + }, + [ + "li", + { + "class": "task-list-item" + }, + [ + "p", + {}, + [ + "input", + { + "class": "task-list-item-checkbox", + "type": "checkbox", + ":disabled": "true" + } + ], + " Option one" + ], + [ + "blockquote", + {}, + "Why this is wrong." + ] + ], + [ + "li", + { + "class": "task-list-item" + }, + [ + "p", + {}, + [ + "input", + { + "class": "task-list-item-checkbox", + "type": "checkbox", + ":disabled": "true", + ":checked": "true" + } + ], + " Option two" + ], + [ + "pre", + { + "language": "js" + }, + [ + "code", + { + "class": "language-js" + }, + "x" + ] + ] + ] + ] + ] +} +``` + +## HTML + +```html + +``` + +## Markdown + +```md +- [ ] Option one + > Why this is wrong. +- [x] Option two + ```js + x + ``` +``` diff --git a/packages/comark/src/internal/stringify/handlers/li.ts b/packages/comark/src/internal/stringify/handlers/li.ts index 70aae9979..90651b729 100644 --- a/packages/comark/src/internal/stringify/handlers/li.ts +++ b/packages/comark/src/internal/stringify/handlers/li.ts @@ -18,15 +18,17 @@ export async function li(node: ElementNode, state: State) { ? node[1].className.join(' ') : String(node[1].className || node[1].class) - const taskList = className.includes('task-list-item') + // Continuation lines align with the item's content, which starts after the + // list marker: the `[ ] ` checkbox is part of that content, not the marker. + const prefixWidth = prefix.length - if (taskList) { - const input = children.shift() as ElementNode - prefix += input[1].checked || input[1][':checked'] ? '[x] ' : '[ ] ' + if (className.includes('task-list-item')) { + const input = takeCheckbox(children) + if (input) { + prefix += input[1].checked || input[1][':checked'] ? '[x] ' : '[ ] ' + } } - const prefixWidth = prefix.length - // Direct text children render sibling components inline. const hasInlineContent = children.some((child) => typeof child === 'string') @@ -68,3 +70,29 @@ export async function li(node: ElementNode, state: State) { return `${prefix}${result}${suffix}\n` } + +function isCheckbox(node: Node | undefined): node is ElementNode { + return Array.isArray(node) && node[0] === 'input' +} + +/** + * Remove the task checkbox from `children` and return it. In a tight list the + * checkbox is the item's first child; in a loose list it opens the item's + * first paragraph, which is replaced by a copy without it so the node passed + * in is left untouched. + */ +function takeCheckbox(children: Node[]): ElementNode | undefined { + const first = children[0] + if (isCheckbox(first)) { + children.shift() + return first + } + if (Array.isArray(first) && first[0] === 'p') { + const [tag, attrs, checkbox, ...rest] = first as ElementNode + if (isCheckbox(checkbox)) { + children[0] = [tag, attrs, ...rest] + return checkbox + } + } + return undefined +} From d02a3542fe500b3495ee9551a0f19f3dd6742819 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Fri, 30 Oct 2026 16:15:05 +0100 Subject: [PATCH 2/7] fix(stringify): keep trailing blank lines and spaces in code blocks The parser drops only the newline before the closing fence, so the writer must not trim the rest. Refs #525 --- .../COMARK/codeblock-trailing-blank-lines.md | 48 +++++++++++++++++++ .../COMARK/codeblock-trailing-whitespace.md | 48 +++++++++++++++++++ .../SPEC/COMARK/shiki-codeblock-twoslash.md | 1 + .../src/internal/stringify/handlers/pre.ts | 4 +- 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 packages/comark/SPEC/COMARK/codeblock-trailing-blank-lines.md create mode 100644 packages/comark/SPEC/COMARK/codeblock-trailing-whitespace.md diff --git a/packages/comark/SPEC/COMARK/codeblock-trailing-blank-lines.md b/packages/comark/SPEC/COMARK/codeblock-trailing-blank-lines.md new file mode 100644 index 000000000..e2951491f --- /dev/null +++ b/packages/comark/SPEC/COMARK/codeblock-trailing-blank-lines.md @@ -0,0 +1,48 @@ +## Input + +```md +```txt +line + +``` +``` + +## AST + +```json +{ + "frontmatter": {}, + "meta": {}, + "nodes": [ + [ + "pre", + { + "language": "txt" + }, + [ + "code", + { + "class": "language-txt" + }, + "line\n" + ] + ] + ] +} +``` + +## HTML + +```html +
line
+
+``` + +## Markdown + +```md +```txt +line + +``` +``` diff --git a/packages/comark/SPEC/COMARK/codeblock-trailing-whitespace.md b/packages/comark/SPEC/COMARK/codeblock-trailing-whitespace.md new file mode 100644 index 000000000..3947463b5 --- /dev/null +++ b/packages/comark/SPEC/COMARK/codeblock-trailing-whitespace.md @@ -0,0 +1,48 @@ +## Input + +```md +```txt +line + +``` +``` + +## AST + +```json +{ + "frontmatter": {}, + "meta": {}, + "nodes": [ + [ + "pre", + { + "language": "txt" + }, + [ + "code", + { + "class": "language-txt" + }, + "line \n " + ] + ] + ] +} +``` + +## HTML + +```html +
line  
+  
+``` + +## Markdown + +```md +```txt +line + +``` +``` diff --git a/packages/comark/SPEC/COMARK/shiki-codeblock-twoslash.md b/packages/comark/SPEC/COMARK/shiki-codeblock-twoslash.md index 8eecfc2c8..29e1fa6d0 100644 --- a/packages/comark/SPEC/COMARK/shiki-codeblock-twoslash.md +++ b/packages/comark/SPEC/COMARK/shiki-codeblock-twoslash.md @@ -160,5 +160,6 @@ const message = "Hello from twoslash" ```md ```ts twoslash const const message: "Hello from twoslash"message = "Hello from twoslash" + ``` ``` diff --git a/packages/comark/src/internal/stringify/handlers/pre.ts b/packages/comark/src/internal/stringify/handlers/pre.ts index 6b3d5c80e..dce8e3ec6 100644 --- a/packages/comark/src/internal/stringify/handlers/pre.ts +++ b/packages/comark/src/internal/stringify/handlers/pre.ts @@ -25,7 +25,9 @@ export function pre(node: ElementNode, state: State) { // Meta always has a leading space const meta = attributes.meta ? ' ' + attributes.meta : '' - const code = String(node[1]?.code || textContent(node)).trimEnd() + // The parser drops only the newline before the closing fence, so trailing + // blank lines and spaces are part of the code and must be written back. + const code = String(node[1]?.code || textContent(node)) const fence = pickFence(code) const fenceBlock = fence + language + filename + highlights + meta + '\n' + code + '\n' + fence From 68937a143a994b6012b9690db0871a2df0d7c787 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Fri, 30 Oct 2026 16:14:21 +0100 Subject: [PATCH 3/7] fix(stringify): write a component named table as a component A table without thead/tbody/tr rows is a ::table component, not an empty GFM table. Also drop the mdc special case that wrote every table as HTML. Refs #525 --- .../SPEC/COMARK/component-name-table.md | 56 +++++++++++++++++++ .../internal/stringify/handlers/component.ts | 17 ++++++ .../src/internal/stringify/handlers/mdc.ts | 5 -- .../src/internal/stringify/handlers/table.ts | 10 +++- 4 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 packages/comark/SPEC/COMARK/component-name-table.md create mode 100644 packages/comark/src/internal/stringify/handlers/component.ts diff --git a/packages/comark/SPEC/COMARK/component-name-table.md b/packages/comark/SPEC/COMARK/component-name-table.md new file mode 100644 index 000000000..5b392145c --- /dev/null +++ b/packages/comark/SPEC/COMARK/component-name-table.md @@ -0,0 +1,56 @@ +## Input + +```md +::table{src="data.csv"} +:: + +Some :table[x] text. +``` + +## AST + +```json +{ + "frontmatter": {}, + "meta": {}, + "nodes": [ + [ + "table", + { + "src": "data.csv" + } + ], + [ + "p", + {}, + "Some ", + [ + "table", + {}, + "x" + ], + " text." + ] + ] +} +``` + +## HTML + +```html +
+

+ Some + x
+ text. +

+``` + +## Markdown + +```md +::table{src="data.csv"} +:: + +Some :table[x] text. +``` diff --git a/packages/comark/src/internal/stringify/handlers/component.ts b/packages/comark/src/internal/stringify/handlers/component.ts new file mode 100644 index 000000000..d6246aeea --- /dev/null +++ b/packages/comark/src/internal/stringify/handlers/component.ts @@ -0,0 +1,17 @@ +import type { State } from 'comark/render' +import type { ElementNode } from 'comark' + +/** + * Render a node the way a tag with no handler is rendered: as a Comark + * component (`:name[…]` / `::name`), or as HTML in other formats. + * + * Built-in handlers whose tag can also be a component name (`table`, `math`, + * `template`, …) call this when the node does not have the shape the parser + * gives their own syntax, so `::table{src="data.csv"}` is not written as an + * empty Markdown table. + */ +export function component(node: ElementNode, state: State, parent?: ElementNode): string | Promise { + return state.context.format === 'markdown/comark' + ? state.handlers.mdc(node, state, parent) + : state.handlers.html(node, state, parent) +} diff --git a/packages/comark/src/internal/stringify/handlers/mdc.ts b/packages/comark/src/internal/stringify/handlers/mdc.ts index 63622fb00..faacea673 100644 --- a/packages/comark/src/internal/stringify/handlers/mdc.ts +++ b/packages/comark/src/internal/stringify/handlers/mdc.ts @@ -2,7 +2,6 @@ import type { State } from 'comark/render' import type { ElementNode, Node } from 'comark' import { indent } from '../../../utils/index.ts' import { comarkAttributes, comarkYamlAttributes } from '../attributes.ts' -import { html } from './html.ts' // HTML elements that always create an inline context for their children const INLINE_HTML_ELEMENTS = new Set(['a', 'strong', 'em', 'span']) @@ -11,10 +10,6 @@ export async function mdc(node: ElementNode, state: State, parent?: ElementNode) const [tag, attr, ...children] = node const { $: _, ...attributes } = attr - if (tag === 'table') { - return html(node, state) - } - const attributeEntries = Object.entries(attributes) const hasObjectAttributes = attributeEntries.some(([, value]) => typeof value === 'object') diff --git a/packages/comark/src/internal/stringify/handlers/table.ts b/packages/comark/src/internal/stringify/handlers/table.ts index 9c6d54171..e5e991554 100644 --- a/packages/comark/src/internal/stringify/handlers/table.ts +++ b/packages/comark/src/internal/stringify/handlers/table.ts @@ -1,9 +1,12 @@ import type { State } from 'comark/render' import type { ElementNode, Node } from 'comark' import { comarkAttributes, userBlockAttrs } from '../attributes.ts' +import { component } from './component.ts' type Alignment = 'left' | 'center' | 'right' | null +const TABLE_SECTIONS = new Set(['thead', 'tbody', 'tr']) + // Helper function to extract alignment from style attribute function getAlignment(attributes: Record): Alignment { const style = attributes.style @@ -82,9 +85,14 @@ function getCells(row: ElementNode): ElementNode[] { ) as ElementNode[] } -export async function table(node: ElementNode, state: State) { +export async function table(node: ElementNode, state: State, parent?: ElementNode) { const [, , ...children] = node + // A parsed table always has rows; without them this is a `::table` component. + if (!children.some((child) => Array.isArray(child) && TABLE_SECTIONS.has(child[0] as string))) { + return component(node, state, parent) + } + // Extract thead and tbody let headerRows: ElementNode[] = [] let bodyRows: ElementNode[] = [] From 3d21d9c362f1cb65c7c57767fa45d2fdc6059de1 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Fri, 30 Oct 2026 16:14:21 +0100 Subject: [PATCH 4/7] fix(stringify): write a component named comment as a component The comment handler wrote only null-tag nodes and dropped elements named comment. Refs #525 --- .../SPEC/COMARK/component-name-comment.md | 59 +++++++++++++++++++ .../internal/stringify/handlers/comment.ts | 11 +++- 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 packages/comark/SPEC/COMARK/component-name-comment.md diff --git a/packages/comark/SPEC/COMARK/component-name-comment.md b/packages/comark/SPEC/COMARK/component-name-comment.md new file mode 100644 index 000000000..43fd79e59 --- /dev/null +++ b/packages/comark/SPEC/COMARK/component-name-comment.md @@ -0,0 +1,59 @@ +## Input + +```md +Some :comment[a note]{author="maxi"} text. + +::comment +body +:: +``` + +## AST + +```json +{ + "frontmatter": {}, + "meta": {}, + "nodes": [ + [ + "p", + {}, + "Some ", + [ + "comment", + { + "author": "maxi" + }, + "a note" + ], + " text." + ], + [ + "comment", + {}, + "body" + ] + ] +} +``` + +## HTML + +```html +

+ Some a note text. +

+ + body + +``` + +## Markdown + +```md +Some :comment[a note]{author="maxi"} text. + +::comment +body +:: +``` diff --git a/packages/comark/src/internal/stringify/handlers/comment.ts b/packages/comark/src/internal/stringify/handlers/comment.ts index 8177dae87..a59e3e3c9 100644 --- a/packages/comark/src/internal/stringify/handlers/comment.ts +++ b/packages/comark/src/internal/stringify/handlers/comment.ts @@ -1,10 +1,15 @@ import type { State } from 'comark/render' import type { ElementNode } from 'comark' -export function comment(node: ElementNode, _state: State) { +export function comment(node: ElementNode, state: State, parent?: ElementNode) { if (node[0] === null) { - return `` + _state.context.blockSeparator + return `` + state.context.blockSeparator } - return '' + // Comments have a `null` tag; an element named `comment` is a component. + // (Not via `./component.ts`: `comark/render/html` imports this file and must + // stay free of the other handlers. It never reaches here with an element.) + return state.context.format === 'markdown/comark' + ? state.handlers.mdc(node, state, parent) + : state.handlers.html(node, state, parent) } From 141c6bb330a6157ea23a8d6724e1d6d48b1e1d2b Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Fri, 30 Oct 2026 16:14:21 +0100 Subject: [PATCH 5/7] fix(stringify): write a component named math as a component A math node without the math class or content attribute is a :math component, not math syntax. Refs #525 --- .../comark/SPEC/COMARK/component-name-math.md | 59 +++++++++++++++++++ .../src/internal/stringify/handlers/math.ts | 11 +++- packages/comark/test/math.test.ts | 9 ++- 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 packages/comark/SPEC/COMARK/component-name-math.md diff --git a/packages/comark/SPEC/COMARK/component-name-math.md b/packages/comark/SPEC/COMARK/component-name-math.md new file mode 100644 index 000000000..cebcc0677 --- /dev/null +++ b/packages/comark/SPEC/COMARK/component-name-math.md @@ -0,0 +1,59 @@ +## Input + +```md +Some :math[x] text. + +::math{k="v"} +body +:: +``` + +## AST + +```json +{ + "frontmatter": {}, + "meta": {}, + "nodes": [ + [ + "p", + {}, + "Some ", + [ + "math", + {}, + "x" + ], + " text." + ], + [ + "math", + { + "k": "v" + }, + "body" + ] + ] +} +``` + +## HTML + +```html +

+ Some x text. +

+ + body + +``` + +## Markdown + +```md +Some :math[x] text. + +::math{k="v"} +body +:: +``` diff --git a/packages/comark/src/internal/stringify/handlers/math.ts b/packages/comark/src/internal/stringify/handlers/math.ts index 36f126e23..2c5aa6d00 100644 --- a/packages/comark/src/internal/stringify/handlers/math.ts +++ b/packages/comark/src/internal/stringify/handlers/math.ts @@ -1,11 +1,18 @@ import type { State } from 'comark/render' import type { ElementNode } from 'comark' import { textContent } from '../../../utils/index.ts' +import { component } from './component.ts' -export function math(node: ElementNode, state: State, parent?: ElementNode): string { - const content = textContent(node) +export function math(node: ElementNode, state: State, parent?: ElementNode): string | Promise { const className = node[1].class const classes = typeof className === 'string' ? className.split(' ') : [] + // The math plugin always sets `class="math …"` and `content`, which the Math + // components also need. With neither, this is a `:math` component. + if (!classes.includes('math') && typeof node[1].content !== 'string') { + return component(node, state, parent) + } + + const content = textContent(node) const hasInlineClass = classes.includes('inline') const hasBlockClass = classes.includes('block') const hasInlineSiblings = parent?.some((child, index) => index > 1 && typeof child === 'string') ?? false diff --git a/packages/comark/test/math.test.ts b/packages/comark/test/math.test.ts index 04d2e1c4c..73bf8ad5e 100644 --- a/packages/comark/test/math.test.ts +++ b/packages/comark/test/math.test.ts @@ -26,12 +26,19 @@ Inline $E = mc^2$ end.` const document: MarkdownDocument = { frontmatter: {}, meta: {}, - nodes: [['p', {}, 'Before ', ['math', {}, 'x^2'], ' after']], + nodes: [['p', {}, 'Before ', ['math', { content: 'x^2' }, 'x^2'], ' after']], } expect(await renderMarkdown(document)).toBe('Before $x^2$ after') }) + it('writes a `math` node without math class or content as a component', async () => { + const source = 'Some :math[x] text.\n\n::math{k="v"}\nbody\n::' + const document = await parseMarkdown(source) + + expect(await renderMarkdown(document)).toBe(source) + }) + it('keeps explicitly block math as a block inside a parent', async () => { const source = '> $$\n> x^2\n> $$' const document = await parseMarkdown(source, { plugins: [math()] }) From 3e00a397ad5453297a1a3a2fcfc0e0d5b9cc6627 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Fri, 30 Oct 2026 16:14:22 +0100 Subject: [PATCH 6/7] fix(stringify): write a component named template as a component A template node without a slot name is a :template component, not a #undefined slot. Refs #525 --- .../SPEC/COMARK/component-name-template.md | 59 +++++++++++++++++++ .../internal/stringify/handlers/template.ts | 6 ++ 2 files changed, 65 insertions(+) create mode 100644 packages/comark/SPEC/COMARK/component-name-template.md diff --git a/packages/comark/SPEC/COMARK/component-name-template.md b/packages/comark/SPEC/COMARK/component-name-template.md new file mode 100644 index 000000000..e312e30aa --- /dev/null +++ b/packages/comark/SPEC/COMARK/component-name-template.md @@ -0,0 +1,59 @@ +## Input + +```md +Some :template[x] text. + +::template{k="v"} +body +:: +``` + +## AST + +```json +{ + "frontmatter": {}, + "meta": {}, + "nodes": [ + [ + "p", + {}, + "Some ", + [ + "template", + {}, + "x" + ], + " text." + ], + [ + "template", + { + "k": "v" + }, + "body" + ] + ] +} +``` + +## HTML + +```html +

+ Some text. +

+ +``` + +## Markdown + +```md +Some :template[x] text. + +::template{k="v"} +body +:: +``` diff --git a/packages/comark/src/internal/stringify/handlers/template.ts b/packages/comark/src/internal/stringify/handlers/template.ts index 924a72947..dc5833f6d 100644 --- a/packages/comark/src/internal/stringify/handlers/template.ts +++ b/packages/comark/src/internal/stringify/handlers/template.ts @@ -1,11 +1,17 @@ import type { State } from 'comark/render' import type { ElementNode, Node } from 'comark' import { comarkAttributes } from '../attributes.ts' +import { component } from './component.ts' // slot template export async function template(node: ElementNode, state: State, parent?: ElementNode) { const [_, attrs] = node + // Parsed slots always carry a `name`; without one this is a `:template` component. + if (typeof attrs.name !== 'string') { + return component(node, state, parent) + } + const content = (await state.flow(node, state)).trimEnd() // Omit #default marker if this is the only slot From 1f426139dd7b30776815fcd977a2e6aaed740da5 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Fri, 30 Oct 2026 16:14:22 +0100 Subject: [PATCH 7/7] fix(stringify): write a component named mermaid as a component A mermaid node without a content attribute is a :mermaid component, not an empty mermaid fence. Refs #525 --- .../SPEC/COMARK/component-name-mermaid.md | 57 +++++++++++++++++++ .../internal/stringify/handlers/mermaid.ts | 8 ++- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 packages/comark/SPEC/COMARK/component-name-mermaid.md diff --git a/packages/comark/SPEC/COMARK/component-name-mermaid.md b/packages/comark/SPEC/COMARK/component-name-mermaid.md new file mode 100644 index 000000000..69c852ca6 --- /dev/null +++ b/packages/comark/SPEC/COMARK/component-name-mermaid.md @@ -0,0 +1,57 @@ +## Input + +```md +Some :mermaid[x] text. + +::mermaid +body +:: +``` + +## AST + +```json +{ + "frontmatter": {}, + "meta": {}, + "nodes": [ + [ + "p", + {}, + "Some ", + [ + "mermaid", + {}, + "x" + ], + " text." + ], + [ + "mermaid", + {}, + "body" + ] + ] +} +``` + +## HTML + +```html +

+ Some x text. +

+ + body + +``` + +## Markdown + +```md +Some :mermaid[x] text. + +::mermaid +body +:: +``` diff --git a/packages/comark/src/internal/stringify/handlers/mermaid.ts b/packages/comark/src/internal/stringify/handlers/mermaid.ts index c93ff4580..b430c971f 100644 --- a/packages/comark/src/internal/stringify/handlers/mermaid.ts +++ b/packages/comark/src/internal/stringify/handlers/mermaid.ts @@ -2,10 +2,16 @@ import type { State } from 'comark/render' import type { ElementNode } from 'comark' import { comarkAttributes } from '../attributes.ts' import { pickFence } from '../fence.ts' +import { component } from './component.ts' -export function mermaid(node: ElementNode, state: State) { +export function mermaid(node: ElementNode, state: State, parent?: ElementNode): string | Promise { const [_, attributes] = node + // The mermaid plugin always sets `content`; without it this is a `:mermaid` component. + if (typeof attributes.content !== 'string') { + return component(node, state, parent) + } + const { content, ...rest } = attributes const attrs = comarkAttributes(rest)