diff --git a/docs/content/2.syntax/3.attributes.md b/docs/content/2.syntax/3.attributes.md index 6654e833..a80a0c50 100644 --- a/docs/content/2.syntax/3.attributes.md +++ b/docs/content/2.syntax/3.attributes.md @@ -351,7 +351,7 @@ const variable = "value"
Paragraph 1
Paragraph 2
-const variable = "value"
+const variable = "value"
```
::
diff --git a/packages/comark-angular/src/components/markdown-node.component.ts b/packages/comark-angular/src/components/markdown-node.component.ts
index 9869759f..9acf28d8 100644
--- a/packages/comark-angular/src/components/markdown-node.component.ts
+++ b/packages/comark-angular/src/components/markdown-node.component.ts
@@ -17,7 +17,7 @@ import {
} from '@angular/core'
import type { ElementNode, Node as MarkdownAstNode, NodeRenderData } from 'comark'
import { resolveIfWrapper, selectIfBranch, shouldRenderIf, type IfProps } from 'comark/plugins/binding'
-import { pascalCase, resolveAttributes } from 'comark/utils'
+import { pascalCase, resolveAttributes, toNativeAttributes } from 'comark/utils'
interface StructuralComponent extends Type`, `
'
+ )
+ })
+
+ it('should pass code block props unchanged to a custom pre component', async () => {
+ const tree = await parseMarkdown(markdown)
+ function ProsePre({ language, filename, highlights, meta }: any) {
+ return
+ }
+ const html = renderToString(
+
+ )
+ expect(html).toContain(
+ '{"language":"ruby","filename":"app.rb","highlights":[1],"meta":"foo=bar"}'
+ )
+ })
+
+ it('should keep authored language on a raw HTML pre', () => {
+ const tree = {
+ nodes: [['pre', { $: { html: 1, block: 1 }, language: 'ruby' }, 'puts 1']],
+ frontmatter: {},
+ meta: {},
+ } as any
+ const html = renderToString( )
+ expect(html).toContain('')
+ expect(html).not.toContain('data-language')
+ })
+
+ it('should keep code block props in the binding scope of native pre children', () => {
+ const tree = {
+ nodes: [['pre', { language: 'ruby' }, ['code', { ':data-lang': 'props.language' }, 'x']]],
+ frontmatter: {},
+ meta: {},
+ } as any
+ const html = renderToString( )
+ expect(html).toContain('')
+ })
+})
diff --git a/packages/comark-svelte/src/components/MarkdownNode.svelte b/packages/comark-svelte/src/components/MarkdownNode.svelte
index dc9db091..23cf98ba 100644
--- a/packages/comark-svelte/src/components/MarkdownNode.svelte
+++ b/packages/comark-svelte/src/components/MarkdownNode.svelte
@@ -72,7 +72,7 @@ naturally appears inline after the deepest trailing text node.
import MarkdownNode from './MarkdownNode.svelte'
import ComarkComponent from './ComarkComponent.svelte'
import Resolve from './Resolve.svelte'
- import { resolveAttributes } from 'comark/utils'
+ import { resolveAttributes, toNativeAttributes } from 'comark/utils'
const EMPTY_RENDER_DATA: NodeRenderData = { frontmatter: {}, meta: {}, data: {}, props: {} }
@@ -197,6 +197,8 @@ naturally appears inline after the deepest trailing text node.
// Only shadow the parent's `props` scope when the current element has its
// own attributes. Bare wrappers (``, `
`, `- `, …) must keep the
// parent's scope so bindings like `{{ props.x }}` reach across them.
+ let nativeProps = $derived(tag ? toNativeAttributes(tag, node, mappedProps) : mappedProps)
+
let childrenRenderData = $derived
(
Object.keys(mappedProps).length > 0
? { ...renderData, props: mappedProps }
@@ -283,9 +285,9 @@ naturally appears inline after the deepest trailing text node.
{@render renderChildren()}
{:else if isVoid}
-
+
{:else if tag}
-
+
{@render renderChildren()}
{/if}
diff --git a/packages/comark-svelte/test/MarkdownNode.test.ts b/packages/comark-svelte/test/MarkdownNode.test.ts
index fa3f8d63..1901c2ad 100644
--- a/packages/comark-svelte/test/MarkdownNode.test.ts
+++ b/packages/comark-svelte/test/MarkdownNode.test.ts
@@ -384,3 +384,26 @@ Footer slot content.
expect(output).toContain('content')
})
})
+
+describe('MarkdownNode code blocks', () => {
+ it('renders code block props as data attributes on native pre', async () => {
+ const tree = await parseMarkdown('```ruby {1} [app.rb] foo=bar\nputs 1\n```')
+ const { body } = render(MarkdownNode, { props: { node: tree.nodes[0] } })
+ expect(html(body)).toContain(
+ ''
+ )
+ })
+
+ it('keeps authored language on a raw HTML pre', () => {
+ const node = ['pre', { $: { html: 1, block: 1 }, language: 'ruby' }, 'puts 1']
+ const { body } = render(MarkdownNode, { props: { node } })
+ expect(html(body)).toContain('')
+ expect(html(body)).not.toContain('data-language')
+ })
+
+ it('keeps code block props in the binding scope of native pre children', () => {
+ const node = ['pre', { language: 'ruby' }, ['code', { ':data-lang': 'props.language' }, 'x']]
+ const { body } = render(MarkdownNode, { props: { node } })
+ expect(html(body)).toContain('')
+ })
+})
diff --git a/packages/comark-vue/src/components/MarkdownDocument.ts b/packages/comark-vue/src/components/MarkdownDocument.ts
index 530870c3..1201fdd8 100644
--- a/packages/comark-vue/src/components/MarkdownDocument.ts
+++ b/packages/comark-vue/src/components/MarkdownDocument.ts
@@ -21,7 +21,7 @@ import {
toRaw,
} from 'vue'
import { findLastTextNodeAndAppendNode, getCaret } from '../utils/caret.ts'
-import { pascalCase, resolveAttributes } from 'comark/utils'
+import { pascalCase, resolveAttributes, toNativeAttributes } from 'comark/utils'
// Cache for dynamically resolved components
const asyncComponentCache = new Map()
@@ -142,7 +142,7 @@ function renderNode(
// Resolve `:prefix` bindings and let Vue-specific attribute mapping run
// on top (e.g. `className` → `class`).
const resolved = resolveAttributes(nodeProps, renderData, { parseJson: true })
- const props: Record = {}
+ let props: Record = {}
for (const k in resolved) {
if (k === 'className') {
props.class = resolved[k]
@@ -150,6 +150,10 @@ function renderNode(
props[k] = resolved[k]
}
}
+ const scopeProps = props
+ if (!customComponent) {
+ props = toNativeAttributes(tag, node, props)
+ }
// @ts-expect-error - component might be a Vue component
if (component?.props?.__node || component?.__asyncResolved?.props?.__node) {
@@ -169,7 +173,7 @@ function renderNode(
// own attributes. Bare wrappers (``, `
`, `- `, …) must keep the
// parent's scope so bindings like `{{ props.x }}` reach across them.
const hasOwnAttrs = Object.keys(resolved).length > 0
- const childrenRenderData = hasOwnAttrs ? { ...renderData, props } : renderData
+ const childrenRenderData = hasOwnAttrs ? { ...renderData, props: scopeProps } : renderData
// Separate template elements (slots) from regular children
const slots: Record
(VNode | string)[]> = {}
const regularChildren: (VNode | string)[] = []
diff --git a/packages/comark-vue/test/code-block.test.ts b/packages/comark-vue/test/code-block.test.ts
new file mode 100644
index 00000000..157f57c8
--- /dev/null
+++ b/packages/comark-vue/test/code-block.test.ts
@@ -0,0 +1,67 @@
+import { describe, expect, it } from 'vitest'
+import { createSSRApp, defineComponent, h } from 'vue'
+import { renderToString } from '@vue/server-renderer'
+import { parseMarkdown } from 'comark'
+import { MarkdownDocument } from '../src/components/MarkdownDocument'
+
+const markdown = '```ruby {1} [app.rb] foo=bar\nputs 1\n```'
+
+async function renderTree(components: Record = {}) {
+ const tree = await parseMarkdown(markdown)
+ return renderToString(createSSRApp({ render: () => h(MarkdownDocument, { value: tree, components }) }))
+}
+
+describe('code blocks', () => {
+ it('should render code block props as data attributes on native pre', async () => {
+ const html = await renderTree()
+ expect(html).toContain(
+ ''
+ )
+ })
+
+ it('should pass code block props unchanged to a custom pre component', async () => {
+ const ProsePre = defineComponent({
+ props: ['language', 'filename', 'highlights', 'meta'],
+ setup(props, { slots }) {
+ return () =>
+ h(
+ 'div',
+ {
+ 'data-props': JSON.stringify({
+ language: props.language,
+ filename: props.filename,
+ highlights: props.highlights,
+ meta: props.meta,
+ }),
+ },
+ slots.default?.()
+ )
+ },
+ })
+ const html = await renderTree({ ProsePre })
+ expect(html).toContain(
+ '{"language":"ruby","filename":"app.rb","highlights":[1],"meta":"foo=bar"}'
+ )
+ })
+
+ it('should keep authored language on a raw HTML pre', async () => {
+ const tree = {
+ nodes: [['pre', { $: { html: 1, block: 1 }, language: 'ruby' }, 'puts 1']],
+ frontmatter: {},
+ meta: {},
+ } as any
+ const html = await renderToString(createSSRApp({ render: () => h(MarkdownDocument, { value: tree }) }))
+ expect(html).toContain('')
+ expect(html).not.toContain('data-language')
+ })
+
+ it('should keep code block props in the binding scope of native pre children', async () => {
+ const tree = {
+ nodes: [['pre', { language: 'ruby' }, ['code', { ':data-lang': 'props.language' }, 'x']]],
+ frontmatter: {},
+ meta: {},
+ } as any
+ const html = await renderToString(createSSRApp({ render: () => h(MarkdownDocument, { value: tree }) }))
+ expect(html).toContain('')
+ })
+})
diff --git a/packages/comark/SPEC/COMARK/attributes/wrapped-pre-class.md b/packages/comark/SPEC/COMARK/attributes/wrapped-pre-class.md
index a30f2a16..82c7b87d 100644
--- a/packages/comark/SPEC/COMARK/attributes/wrapped-pre-class.md
+++ b/packages/comark/SPEC/COMARK/attributes/wrapped-pre-class.md
@@ -36,7 +36,7 @@ const variable = "value"
## HTML
```html
-const variable = "value"
+const variable = "value"
```
## Markdown
diff --git a/packages/comark/SPEC/COMARK/attributes/wrapped-pre-highlighted.md b/packages/comark/SPEC/COMARK/attributes/wrapped-pre-highlighted.md
index 4327a366..4e813c65 100644
--- a/packages/comark/SPEC/COMARK/attributes/wrapped-pre-highlighted.md
+++ b/packages/comark/SPEC/COMARK/attributes/wrapped-pre-highlighted.md
@@ -95,7 +95,7 @@ const variable = "value"
## HTML
```html
-const variable = "value"
+const variable = "value"
```
## Markdown
diff --git a/packages/comark/SPEC/COMARK/attributes/wrapped-pre.md b/packages/comark/SPEC/COMARK/attributes/wrapped-pre.md
index 5135cf2f..a042e03f 100644
--- a/packages/comark/SPEC/COMARK/attributes/wrapped-pre.md
+++ b/packages/comark/SPEC/COMARK/attributes/wrapped-pre.md
@@ -36,7 +36,7 @@ const variable = "value"
## HTML
```html
-const variable = "value"
+const variable = "value"
```
## Markdown
diff --git a/packages/comark/SPEC/COMARK/codeblock-diff-syntax.md b/packages/comark/SPEC/COMARK/codeblock-diff-syntax.md
index 529d74d2..cb4d20ee 100644
--- a/packages/comark/SPEC/COMARK/codeblock-diff-syntax.md
+++ b/packages/comark/SPEC/COMARK/codeblock-diff-syntax.md
@@ -35,7 +35,7 @@
## HTML
```html
-- const oldValue = 42;
+- const oldValue = 42;
+ const newValue = 100;
const unchanged = "same";
```
diff --git a/packages/comark/SPEC/COMARK/codeblock-empty.md b/packages/comark/SPEC/COMARK/codeblock-empty.md
index 8341bc52..40d1acc7 100644
--- a/packages/comark/SPEC/COMARK/codeblock-empty.md
+++ b/packages/comark/SPEC/COMARK/codeblock-empty.md
@@ -32,7 +32,7 @@
## HTML
```html
-
+
```
## Markdown
diff --git a/packages/comark/SPEC/COMARK/codeblock-filename-highlight-lines.md b/packages/comark/SPEC/COMARK/codeblock-filename-highlight-lines.md
index b9067992..990f1da7 100644
--- a/packages/comark/SPEC/COMARK/codeblock-filename-highlight-lines.md
+++ b/packages/comark/SPEC/COMARK/codeblock-filename-highlight-lines.md
@@ -42,7 +42,7 @@ function hello() {
## HTML
```html
-function hello() {
+function hello() {
console.log("Hello, World!");
}
```
diff --git a/packages/comark/SPEC/COMARK/codeblock-filename-highlight-lines2.md b/packages/comark/SPEC/COMARK/codeblock-filename-highlight-lines2.md
index 99bd42a7..b6aa7b5f 100644
--- a/packages/comark/SPEC/COMARK/codeblock-filename-highlight-lines2.md
+++ b/packages/comark/SPEC/COMARK/codeblock-filename-highlight-lines2.md
@@ -46,7 +46,7 @@ function hello() {
## HTML
```html
-function hello() {
+function hello() {
console.log("Hello, World!");
}
```
diff --git a/packages/comark/SPEC/COMARK/codeblock-filename.md b/packages/comark/SPEC/COMARK/codeblock-filename.md
index 54aff39f..c76c53ff 100644
--- a/packages/comark/SPEC/COMARK/codeblock-filename.md
+++ b/packages/comark/SPEC/COMARK/codeblock-filename.md
@@ -36,7 +36,7 @@ function hello() {
## HTML
```html
-function hello() {
+function hello() {
console.log("Hello, World!");
}
```
diff --git a/packages/comark/SPEC/COMARK/codeblock-highlight-complex.md b/packages/comark/SPEC/COMARK/codeblock-highlight-complex.md
index 2ae4a928..31daec06 100644
--- a/packages/comark/SPEC/COMARK/codeblock-highlight-complex.md
+++ b/packages/comark/SPEC/COMARK/codeblock-highlight-complex.md
@@ -51,7 +51,7 @@ func main() {
## HTML
```html
-package main
+package main
import "fmt"
diff --git a/packages/comark/SPEC/COMARK/codeblock-indented-content.md b/packages/comark/SPEC/COMARK/codeblock-indented-content.md
index da267f2c..cd2642e8 100644
--- a/packages/comark/SPEC/COMARK/codeblock-indented-content.md
+++ b/packages/comark/SPEC/COMARK/codeblock-indented-content.md
@@ -41,7 +41,7 @@
```html
- ::accordion
+ ::accordion
::
```
diff --git a/packages/comark/SPEC/COMARK/codeblock-language-only.md b/packages/comark/SPEC/COMARK/codeblock-language-only.md
index 158b5cfe..614735eb 100644
--- a/packages/comark/SPEC/COMARK/codeblock-language-only.md
+++ b/packages/comark/SPEC/COMARK/codeblock-language-only.md
@@ -34,7 +34,7 @@ console.log(greeting)
## HTML
```html
-const greeting: string = "Hello, World!"
+const greeting: string = "Hello, World!"
console.log(greeting)
```
diff --git a/packages/comark/SPEC/COMARK/codeblock-multiple-meta.md b/packages/comark/SPEC/COMARK/codeblock-multiple-meta.md
index 4860adbf..5e07782b 100644
--- a/packages/comark/SPEC/COMARK/codeblock-multiple-meta.md
+++ b/packages/comark/SPEC/COMARK/codeblock-multiple-meta.md
@@ -35,7 +35,7 @@ def greet(name):
## HTML
```html
-def greet(name):
+def greet(name):
return f"Hello, {name}!"
```
diff --git a/packages/comark/SPEC/COMARK/codeblock-nested-4backtick.md b/packages/comark/SPEC/COMARK/codeblock-nested-4backtick.md
index a4e634e1..389dcc57 100644
--- a/packages/comark/SPEC/COMARK/codeblock-nested-4backtick.md
+++ b/packages/comark/SPEC/COMARK/codeblock-nested-4backtick.md
@@ -40,7 +40,7 @@
## HTML
```html
-::code-preview
+::code-preview
`inline code`
#code
diff --git a/packages/comark/SPEC/COMARK/codeblock-nested-tilde.md b/packages/comark/SPEC/COMARK/codeblock-nested-tilde.md
index 850b2d1c..7552fc9a 100644
--- a/packages/comark/SPEC/COMARK/codeblock-nested-tilde.md
+++ b/packages/comark/SPEC/COMARK/codeblock-nested-tilde.md
@@ -40,7 +40,7 @@
## HTML
```html
-::code-preview
+::code-preview
`inline code`
#code
diff --git a/packages/comark/SPEC/COMARK/codeblock-pnpm-add.md b/packages/comark/SPEC/COMARK/codeblock-pnpm-add.md
index 0a98ce1b..b08a6db0 100644
--- a/packages/comark/SPEC/COMARK/codeblock-pnpm-add.md
+++ b/packages/comark/SPEC/COMARK/codeblock-pnpm-add.md
@@ -33,7 +33,7 @@ pnpm add comark
## HTML
```html
-pnpm add comark
+pnpm add comark
```
## Markdown
diff --git a/packages/comark/SPEC/COMARK/codeblock-rust-example.md b/packages/comark/SPEC/COMARK/codeblock-rust-example.md
index 3e0076a5..edd2e890 100644
--- a/packages/comark/SPEC/COMARK/codeblock-rust-example.md
+++ b/packages/comark/SPEC/COMARK/codeblock-rust-example.md
@@ -44,7 +44,7 @@ fn main() {
## HTML
```html
-fn main() {
+fn main() {
let x = 5;
let y = 10;
println!("Sum: {}", x + y);
diff --git a/packages/comark/SPEC/COMARK/codeblock-special-chars.md b/packages/comark/SPEC/COMARK/codeblock-special-chars.md
index 5e161034..275763a2 100644
--- a/packages/comark/SPEC/COMARK/codeblock-special-chars.md
+++ b/packages/comark/SPEC/COMARK/codeblock-special-chars.md
@@ -38,7 +38,7 @@
## HTML
```html
-<div class="container">
+<div class="container">
<h1>Title & Subtitle</h1>
<p>Text with "quotes" and 'apostrophes'</p>
<script>alert('XSS < > test');</script>
diff --git a/packages/comark/SPEC/COMARK/component-nested-codeblock-indented.md b/packages/comark/SPEC/COMARK/component-nested-codeblock-indented.md
index 3de36f54..e2089e85 100644
--- a/packages/comark/SPEC/COMARK/component-nested-codeblock-indented.md
+++ b/packages/comark/SPEC/COMARK/component-nested-codeblock-indented.md
@@ -50,7 +50,7 @@
```html
- ::accordion
+ ::accordion
::
diff --git a/packages/comark/SPEC/COMARK/component-nested-codeblock-outdented.md b/packages/comark/SPEC/COMARK/component-nested-codeblock-outdented.md
index f827062a..bbecf01e 100644
--- a/packages/comark/SPEC/COMARK/component-nested-codeblock-outdented.md
+++ b/packages/comark/SPEC/COMARK/component-nested-codeblock-outdented.md
@@ -50,7 +50,7 @@
```html
- ::accordion
+ ::accordion
::
diff --git a/packages/comark/SPEC/COMARK/component-nested-codeblock.md b/packages/comark/SPEC/COMARK/component-nested-codeblock.md
index ee3222fb..79f073e4 100644
--- a/packages/comark/SPEC/COMARK/component-nested-codeblock.md
+++ b/packages/comark/SPEC/COMARK/component-nested-codeblock.md
@@ -42,7 +42,7 @@ hello
```html
- ::alert
+ ::alert
hello
::
diff --git a/packages/comark/SPEC/COMARK/component-slot-nested-codeblock.md b/packages/comark/SPEC/COMARK/component-slot-nested-codeblock.md
index 1262bc37..d567db4b 100644
--- a/packages/comark/SPEC/COMARK/component-slot-nested-codeblock.md
+++ b/packages/comark/SPEC/COMARK/component-slot-nested-codeblock.md
@@ -50,7 +50,7 @@ hello
```html
- ::alert
+ ::alert
hello
::
diff --git a/packages/comark/SPEC/COMARK/component-with-codeblock.md b/packages/comark/SPEC/COMARK/component-with-codeblock.md
index 28bb212e..52498641 100644
--- a/packages/comark/SPEC/COMARK/component-with-codeblock.md
+++ b/packages/comark/SPEC/COMARK/component-with-codeblock.md
@@ -62,7 +62,7 @@ options:
```html
- ---
+ ---
title: The Mountains Website
description: A website about the most iconic mountains in the world.
---
diff --git a/packages/comark/SPEC/COMARK/shiki-codeblock-dual-theme.md b/packages/comark/SPEC/COMARK/shiki-codeblock-dual-theme.md
index b36dc44a..f31f9ff1 100644
--- a/packages/comark/SPEC/COMARK/shiki-codeblock-dual-theme.md
+++ b/packages/comark/SPEC/COMARK/shiki-codeblock-dual-theme.md
@@ -161,7 +161,7 @@ console.log(greeting)
## HTML
```html
-const greeting: string = "Hello, World!"
+const greeting: string = "Hello, World!"
console.log(greeting)
```
diff --git a/packages/comark/SPEC/COMARK/shiki-codeblock-highlight-complex.md b/packages/comark/SPEC/COMARK/shiki-codeblock-highlight-complex.md
index e0de7ee6..63b220d2 100644
--- a/packages/comark/SPEC/COMARK/shiki-codeblock-highlight-complex.md
+++ b/packages/comark/SPEC/COMARK/shiki-codeblock-highlight-complex.md
@@ -396,7 +396,7 @@ func main() {
## HTML
```html
-package main
+package main
import "fmt"
diff --git a/packages/comark/SPEC/COMARK/shiki-codeblock-language-meta.md b/packages/comark/SPEC/COMARK/shiki-codeblock-language-meta.md
index d9eceaea..355170c2 100644
--- a/packages/comark/SPEC/COMARK/shiki-codeblock-language-meta.md
+++ b/packages/comark/SPEC/COMARK/shiki-codeblock-language-meta.md
@@ -131,7 +131,7 @@ console.log(greeting)
## HTML
```html
-const greeting: string = "Hello, World!"
+const greeting: string = "Hello, World!"
console.log(greeting)
```
diff --git a/packages/comark/SPEC/COMARK/shiki-codeblock-language-only.md b/packages/comark/SPEC/COMARK/shiki-codeblock-language-only.md
index a15c100f..152b0ef3 100644
--- a/packages/comark/SPEC/COMARK/shiki-codeblock-language-only.md
+++ b/packages/comark/SPEC/COMARK/shiki-codeblock-language-only.md
@@ -123,7 +123,7 @@ console.log(greeting)
## HTML
```html
-const greeting: string = "Hello, World!"
+const greeting: string = "Hello, World!"
console.log(greeting)
```
diff --git a/packages/comark/SPEC/COMARK/shiki-codeblock-rust-example.md b/packages/comark/SPEC/COMARK/shiki-codeblock-rust-example.md
index 812bbba6..78f35900 100644
--- a/packages/comark/SPEC/COMARK/shiki-codeblock-rust-example.md
+++ b/packages/comark/SPEC/COMARK/shiki-codeblock-rust-example.md
@@ -236,7 +236,7 @@ fn main() {
## HTML
```html
-fn main() {
+fn main() {
let x = 5;
let y = 10;
println!("Sum: {}", x + y);
diff --git a/packages/comark/SPEC/COMARK/shiki-codeblock-special-chars.md b/packages/comark/SPEC/COMARK/shiki-codeblock-special-chars.md
index 90c0f2d1..2b394fbf 100644
--- a/packages/comark/SPEC/COMARK/shiki-codeblock-special-chars.md
+++ b/packages/comark/SPEC/COMARK/shiki-codeblock-special-chars.md
@@ -286,7 +286,7 @@ options:
## HTML
```html
-<div class="container">
+<div class="container">
<h1>Title & Subtitle</h1>
<p>Text with "quotes" and 'apostrophes'</p>
<script>alert('XSS < > test');</script>
diff --git a/packages/comark/SPEC/COMARK/shiki-codeblock-twoslash.md b/packages/comark/SPEC/COMARK/shiki-codeblock-twoslash.md
index 5e21947e..8eecfc2c 100644
--- a/packages/comark/SPEC/COMARK/shiki-codeblock-twoslash.md
+++ b/packages/comark/SPEC/COMARK/shiki-codeblock-twoslash.md
@@ -150,7 +150,7 @@ const message = "Hello from twoslash"
## HTML
```html
-const
+const
const message: "Hello from twoslash"message = "Hello from twoslash"
```
diff --git a/packages/comark/SPEC/COMARK/shiki-codeblock-with-empty-lines.md b/packages/comark/SPEC/COMARK/shiki-codeblock-with-empty-lines.md
index b54295fd..51ab7fc4 100644
--- a/packages/comark/SPEC/COMARK/shiki-codeblock-with-empty-lines.md
+++ b/packages/comark/SPEC/COMARK/shiki-codeblock-with-empty-lines.md
@@ -136,7 +136,7 @@ More content here.
## HTML
```html
-# My Page Title
+# My Page Title
This is the opening paragraph used as the description.
diff --git a/packages/comark/SPEC/COMARK/shiki-ordered-list-nested-codeblock.md b/packages/comark/SPEC/COMARK/shiki-ordered-list-nested-codeblock.md
index 8257ae0c..e9f6998f 100644
--- a/packages/comark/SPEC/COMARK/shiki-ordered-list-nested-codeblock.md
+++ b/packages/comark/SPEC/COMARK/shiki-ordered-list-nested-codeblock.md
@@ -104,7 +104,7 @@ options:
-
Setup:
- let x = 1;
+ let x = 1;
```
diff --git a/packages/comark/SPEC/common-mark/codeblock-with-empty-lines.md b/packages/comark/SPEC/common-mark/codeblock-with-empty-lines.md
index 44808550..11f58ec2 100644
--- a/packages/comark/SPEC/common-mark/codeblock-with-empty-lines.md
+++ b/packages/comark/SPEC/common-mark/codeblock-with-empty-lines.md
@@ -49,7 +49,7 @@ More content here.
## HTML
```html
-# My Page Title
+# My Page Title
This is the opening paragraph used as the description.
diff --git a/packages/comark/SPEC/common-mark/ordered-list-mixed-blocks-nested.md b/packages/comark/SPEC/common-mark/ordered-list-mixed-blocks-nested.md
index c5c320bf..adc72f19 100644
--- a/packages/comark/SPEC/common-mark/ordered-list-mixed-blocks-nested.md
+++ b/packages/comark/SPEC/common-mark/ordered-list-mixed-blocks-nested.md
@@ -117,7 +117,7 @@
-
Complex item:
- code()
+ code()
A note
diff --git a/packages/comark/SPEC/common-mark/ordered-list-multiple-blocks.md b/packages/comark/SPEC/common-mark/ordered-list-multiple-blocks.md
index 9ab16b4b..e0554ebb 100644
--- a/packages/comark/SPEC/common-mark/ordered-list-multiple-blocks.md
+++ b/packages/comark/SPEC/common-mark/ordered-list-multiple-blocks.md
@@ -68,7 +68,7 @@
A quote here
- code()
+ code()
- Second point
diff --git a/packages/comark/SPEC/common-mark/ordered-list-nested-codeblock.md b/packages/comark/SPEC/common-mark/ordered-list-nested-codeblock.md
index 0b06ad07..72f7cea4 100644
--- a/packages/comark/SPEC/common-mark/ordered-list-nested-codeblock.md
+++ b/packages/comark/SPEC/common-mark/ordered-list-nested-codeblock.md
@@ -68,7 +68,7 @@
-
Second level with code:
- let x = 42;
+ let x = 42;
diff --git a/packages/comark/SPEC/common-mark/unordered-list-nested-deep-codeblock.md b/packages/comark/SPEC/common-mark/unordered-list-nested-deep-codeblock.md
index 5c1bf82b..bdda3174 100644
--- a/packages/comark/SPEC/common-mark/unordered-list-nested-deep-codeblock.md
+++ b/packages/comark/SPEC/common-mark/unordered-list-nested-deep-codeblock.md
@@ -85,7 +85,7 @@
-
Level 3 with code:
- print("deep")
+ print("deep")
diff --git a/packages/comark/src/internal/stringify/handlers/html.ts b/packages/comark/src/internal/stringify/handlers/html.ts
index 4cced776..af5feed6 100644
--- a/packages/comark/src/internal/stringify/handlers/html.ts
+++ b/packages/comark/src/internal/stringify/handlers/html.ts
@@ -1,7 +1,7 @@
import type { State } from 'comark/render'
import type { ElementNode } from 'comark'
import { htmlAttributes } from '../attributes.ts'
-import { indent } from '../../../utils/index.ts'
+import { indent, toNativeAttributes } from '../../../utils/index.ts'
const textBlocks = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'td', 'th'])
const selfCloseTags = new Set(['br', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr'])
@@ -99,7 +99,8 @@ export async function html(node: ElementNode, state: State, parent?: ElementNode
state.applyContext(revert)
}
- const attrs = Object.keys(attributes).length > 0 ? ` ${htmlAttributes(attributes)}` : ''
+ const nativeAttributes = toNativeAttributes(String(tag), node, attributes)
+ const attrs = Object.keys(nativeAttributes).length > 0 ? ` ${htmlAttributes(nativeAttributes)}` : ''
if (isSelfClose) {
return `<${tag}${attrs}>` + (!parent && !isInline ? state.context.blockSeparator : '')
diff --git a/packages/comark/src/utils/index.ts b/packages/comark/src/utils/index.ts
index 76790343..34332f3c 100644
--- a/packages/comark/src/utils/index.ts
+++ b/packages/comark/src/utils/index.ts
@@ -121,6 +121,31 @@ export async function visitAsync(
}
}
+const CODE_BLOCK_PROPS = new Set(['language', 'filename', 'highlights', 'meta'])
+
+/**
+ * Maps code block props on a native `
` to `data-*` attributes.
+ *
+ * Raw HTML (`node[1].$.html`) is left unchanged so authored attributes round-trip.
+ * The attribute bag does not include `$` — `resolveAttributes` strips it — so the source
+ * node is required.
+ */
+export function toNativeAttributes>(tag: string, node: Node, attributes: T): T {
+ if (tag !== 'pre' || !Array.isArray(node) || node[1]?.$?.html === 1) return attributes
+ let changed = false
+ const result: Record = {}
+ for (const key in attributes) {
+ if (CODE_BLOCK_PROPS.has(key)) {
+ changed = true
+ const value = attributes[key]
+ result[`data-${key}`] = value !== null && typeof value === 'object' ? JSON.stringify(value) : value
+ } else {
+ result[key] = attributes[key]
+ }
+ }
+ return changed ? (result as T) : attributes
+}
+
// #region String Utils
const HTML_ESCAPE_RE = /[&<>"]/g
diff --git a/packages/comark/test/html-escape.test.ts b/packages/comark/test/html-escape.test.ts
index 6551678d..81bc7a35 100644
--- a/packages/comark/test/html-escape.test.ts
+++ b/packages/comark/test/html-escape.test.ts
@@ -11,6 +11,16 @@ const renderNodes = async (nodes: Node[]) =>
render({ nodes, frontmatter: {}, meta: {} }, { format: 'text/html', blockSeparator: '\n' })
describe('HTML attribute escaping', () => {
+ it('keeps authored language on a raw pre and remaps fence language', async () => {
+ const raw = await renderHtml('\nputs 1\n
')
+ expect(raw).toContain('')
+ expect(raw).not.toContain('data-language')
+
+ const fence = await renderHtml('```ruby\nputs 1\n```')
+ expect(fence).toContain('')
+ expect(fence).not.toContain(' language="ruby"')
+ })
+
it('escapes double quotes in raw-HTML attribute values', async () => {
const html = await renderHtml(`hi`)
expect(html).toContain('title="a" onmouseover=alert(1)"')
diff --git a/packages/comark/test/html-roundtrip.test.ts b/packages/comark/test/html-roundtrip.test.ts
index 87f1a423..47dae02c 100644
--- a/packages/comark/test/html-roundtrip.test.ts
+++ b/packages/comark/test/html-roundtrip.test.ts
@@ -28,6 +28,7 @@ describe('raw HTML round-trip (parse → renderMarkdown → parse)', () => {
'nested divs': '\none\ntwo\n',
'single-child video': '',
'mixed text and element children': '\nsome text\ncap \n',
+ 'pre with authored language attribute': '\nputs 1\n
',
}
for (const [name, md] of Object.entries(cases)) {
diff --git a/test/bundle.test.ts b/test/bundle.test.ts
index 7d61a1e7..4ce4d51e 100644
--- a/test/bundle.test.ts
+++ b/test/bundle.test.ts
@@ -64,10 +64,10 @@ describe('package bundle size', { timeout: 60_000 }, () => {
"@comark/ansi": "37.3k (98 files)",
"@comark/html": "16.5k (58 files)",
"@comark/nuxt": "11.8k (58 files)",
- "@comark/react": "37.7k (76 files)",
- "@comark/svelte": "45.7k (84 files)",
- "@comark/vue": "56.0k (80 files)",
- "comark": "374k (160 files)",
+ "@comark/react": "37.9k (76 files)",
+ "@comark/svelte": "45.8k (84 files)",
+ "@comark/vue": "56.1k (80 files)",
+ "comark": "375k (160 files)",
}
`)
})