comark 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/internal/frontmatter.d.ts +2 -1
- package/dist/internal/frontmatter.js +2 -2
- package/dist/internal/parse/auto-close/index.js +58 -23
- package/dist/internal/parse/token-processor.js +18 -3
- package/dist/internal/stringify/attributes.d.ts +37 -1
- package/dist/internal/stringify/attributes.js +96 -12
- package/dist/internal/stringify/handlers/a.js +3 -0
- package/dist/internal/stringify/handlers/code.js +1 -1
- package/dist/internal/stringify/handlers/del.js +1 -1
- package/dist/internal/stringify/handlers/html.js +12 -1
- package/dist/internal/stringify/handlers/img.js +1 -1
- package/dist/internal/stringify/handlers/li.js +14 -1
- package/dist/internal/stringify/handlers/math.js +1 -1
- package/dist/internal/stringify/handlers/mdc.js +1 -1
- package/dist/internal/stringify/handlers/ol.js +2 -2
- package/dist/internal/stringify/handlers/pre.js +1 -1
- package/dist/internal/stringify/handlers/template.js +1 -1
- package/dist/internal/stringify/handlers/ul.js +2 -2
- package/dist/internal/stringify/indent.d.ts +2 -1
- package/dist/internal/stringify/indent.js +3 -2
- package/dist/internal/stringify/state.d.ts +3 -3
- package/dist/internal/stringify/state.js +71 -15
- package/dist/internal/yaml.d.ts +2 -1
- package/dist/internal/yaml.js +3 -1
- package/dist/parse.js +13 -2
- package/dist/plugins/alert.js +1 -1
- package/dist/plugins/binding.d.ts +20 -0
- package/dist/plugins/binding.js +61 -0
- package/dist/plugins/breaks.d.ts +2 -0
- package/dist/plugins/breaks.js +34 -0
- package/dist/plugins/footnotes.d.ts +61 -0
- package/dist/plugins/footnotes.js +187 -0
- package/dist/plugins/highlight.js +6 -4
- package/dist/plugins/json-render.d.ts +1 -1
- package/dist/plugins/json-render.js +3 -3
- package/dist/plugins/punctuation.d.ts +67 -0
- package/dist/plugins/punctuation.js +236 -0
- package/dist/plugins/security.js +1 -1
- package/dist/render.d.ts +2 -1
- package/dist/render.js +3 -1
- package/dist/types.d.ts +71 -16
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.js +24 -0
- package/dist/vite.d.ts +1 -0
- package/dist/vite.js +1 -0
- package/package.json +29 -24
- package/skills/comark/AGENTS.md +261 -0
- package/skills/comark/SKILL.md +489 -0
- package/skills/comark/references/markdown-syntax.md +599 -0
- package/skills/comark/references/parsing-ast.md +378 -0
- package/skills/comark/references/rendering-react.md +445 -0
- package/skills/comark/references/rendering-svelte.md +453 -0
- package/skills/comark/references/rendering-vue.md +462 -0
- package/skills/skills/comark/AGENTS.md +261 -0
- package/skills/skills/comark/SKILL.md +489 -0
- package/skills/skills/comark/references/markdown-syntax.md +599 -0
- package/skills/skills/comark/references/parsing-ast.md +378 -0
- package/skills/skills/comark/references/rendering-react.md +445 -0
- package/skills/skills/comark/references/rendering-svelte.md +453 -0
- package/skills/skills/comark/references/rendering-vue.md +462 -0
- package/skills/skills/migrate-mdc-to-comark/SKILL.md +191 -0
- package/dist/utils/serialized-task.d.ts +0 -1
- package/dist/utils/serialized-task.js +0 -1
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
# Vue Rendering Guide
|
|
2
|
+
|
|
3
|
+
Complete guide for rendering Comark AST in Vue applications.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Basic Usage](#basic-usage)
|
|
8
|
+
- [Custom Components](#custom-components)
|
|
9
|
+
- [Dynamic Component Resolution](#dynamic-component-resolution)
|
|
10
|
+
- [Slots Support](#slots-support)
|
|
11
|
+
- [Streaming Mode](#streaming-mode)
|
|
12
|
+
- [Prose Components](#prose-components)
|
|
13
|
+
- [Error Handling](#error-handling)
|
|
14
|
+
- [Props Access](#props-access)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
Use the `Comark` component to render markdown:
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<template>
|
|
24
|
+
<Comark>{{ content }}</Comark>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { Comark } from '@comark/vue'
|
|
29
|
+
|
|
30
|
+
const content = `
|
|
31
|
+
# Hello World
|
|
32
|
+
|
|
33
|
+
This is **markdown** content.
|
|
34
|
+
|
|
35
|
+
::alert{type="info"}
|
|
36
|
+
Important message
|
|
37
|
+
::
|
|
38
|
+
`
|
|
39
|
+
</script>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Custom Components
|
|
45
|
+
|
|
46
|
+
Map custom Vue components to Comark elements:
|
|
47
|
+
|
|
48
|
+
```vue
|
|
49
|
+
<template>
|
|
50
|
+
<Comark :components="customComponents">{{ content }}</Comark>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<script setup lang="ts">
|
|
54
|
+
import { Comark } from '@comark/vue'
|
|
55
|
+
import CustomHeading from './CustomHeading.vue'
|
|
56
|
+
import CustomAlert from './CustomAlert.vue'
|
|
57
|
+
import CustomCard from './CustomCard.vue'
|
|
58
|
+
|
|
59
|
+
const customComponents = {
|
|
60
|
+
h1: CustomHeading,
|
|
61
|
+
h2: CustomHeading,
|
|
62
|
+
alert: CustomAlert,
|
|
63
|
+
card: CustomCard,
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Custom Component Example
|
|
69
|
+
|
|
70
|
+
```vue
|
|
71
|
+
<!-- CustomHeading.vue -->
|
|
72
|
+
<template>
|
|
73
|
+
<component :is="tag" :id="id" class="custom-heading">
|
|
74
|
+
<slot />
|
|
75
|
+
</component>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<script setup lang="ts">
|
|
79
|
+
import { computed } from 'vue'
|
|
80
|
+
|
|
81
|
+
const props = defineProps<{
|
|
82
|
+
__node: any // Comark node
|
|
83
|
+
}>()
|
|
84
|
+
|
|
85
|
+
const tag = computed(() => props.__node[0])
|
|
86
|
+
const id = computed(() => props.__node[1]?.id)
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<style scoped>
|
|
90
|
+
.custom-heading {
|
|
91
|
+
font-family: 'Inter', sans-serif;
|
|
92
|
+
font-weight: 700;
|
|
93
|
+
margin-bottom: 1rem;
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Alert Component Example
|
|
99
|
+
|
|
100
|
+
```vue
|
|
101
|
+
<!-- CustomAlert.vue -->
|
|
102
|
+
<template>
|
|
103
|
+
<div :class="`alert alert-${type}`" role="alert">
|
|
104
|
+
<div class="alert-icon">
|
|
105
|
+
<Icon :name="iconName" />
|
|
106
|
+
</div>
|
|
107
|
+
<div class="alert-content">
|
|
108
|
+
<slot />
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</template>
|
|
112
|
+
|
|
113
|
+
<script setup lang="ts">
|
|
114
|
+
import { computed } from 'vue'
|
|
115
|
+
|
|
116
|
+
const props = defineProps<{
|
|
117
|
+
type?: 'info' | 'warning' | 'error' | 'success'
|
|
118
|
+
__node?: any
|
|
119
|
+
}>()
|
|
120
|
+
|
|
121
|
+
const iconName = computed(() => {
|
|
122
|
+
switch (props.type) {
|
|
123
|
+
case 'info': return 'info-circle'
|
|
124
|
+
case 'warning': return 'exclamation-triangle'
|
|
125
|
+
case 'error': return 'times-circle'
|
|
126
|
+
case 'success': return 'check-circle'
|
|
127
|
+
default: return 'info-circle'
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<style scoped>
|
|
133
|
+
.alert {
|
|
134
|
+
display: flex;
|
|
135
|
+
padding: 1rem;
|
|
136
|
+
border-radius: 0.5rem;
|
|
137
|
+
margin-bottom: 1rem;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.alert-info {
|
|
141
|
+
background-color: #e3f2fd;
|
|
142
|
+
color: #1976d2;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.alert-warning {
|
|
146
|
+
background-color: #fff3e0;
|
|
147
|
+
color: #f57c00;
|
|
148
|
+
}
|
|
149
|
+
</style>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Dynamic Component Resolution
|
|
155
|
+
|
|
156
|
+
Load components dynamically using `componentsManifest`:
|
|
157
|
+
|
|
158
|
+
```vue
|
|
159
|
+
<template>
|
|
160
|
+
<Comark
|
|
161
|
+
:components-manifest="loadComponent"
|
|
162
|
+
>{{ content }}</Comark>
|
|
163
|
+
</template>
|
|
164
|
+
|
|
165
|
+
<script setup lang="ts">
|
|
166
|
+
import { Comark } from '@comark/vue'
|
|
167
|
+
|
|
168
|
+
const componentMap = {
|
|
169
|
+
'alert': () => import('./Alert.vue'),
|
|
170
|
+
'card': () => import('./Card.vue'),
|
|
171
|
+
'button': () => import('./Button.vue'),
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function loadComponent(name: string) {
|
|
175
|
+
if (componentMap[name]) {
|
|
176
|
+
return componentMap[name]()
|
|
177
|
+
}
|
|
178
|
+
throw new Error(`Component ${name} not found`)
|
|
179
|
+
}
|
|
180
|
+
</script>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Slots Support
|
|
186
|
+
|
|
187
|
+
Comark components with slots work seamlessly in Vue:
|
|
188
|
+
|
|
189
|
+
### Markdown with Slots
|
|
190
|
+
|
|
191
|
+
```markdown
|
|
192
|
+
::card
|
|
193
|
+
#header
|
|
194
|
+
## Card Title
|
|
195
|
+
|
|
196
|
+
#content
|
|
197
|
+
Main content here with **markdown** support
|
|
198
|
+
|
|
199
|
+
#footer
|
|
200
|
+
Footer text
|
|
201
|
+
::
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Custom Component with Slots
|
|
205
|
+
|
|
206
|
+
```vue
|
|
207
|
+
<!-- Card.vue -->
|
|
208
|
+
<template>
|
|
209
|
+
<div class="card">
|
|
210
|
+
<div v-if="$slots.header" class="card-header">
|
|
211
|
+
<slot name="header" />
|
|
212
|
+
</div>
|
|
213
|
+
<div class="card-content">
|
|
214
|
+
<slot name="content" />
|
|
215
|
+
<!-- Default slot as fallback -->
|
|
216
|
+
<slot />
|
|
217
|
+
</div>
|
|
218
|
+
<div v-if="$slots.footer" class="card-footer">
|
|
219
|
+
<slot name="footer" />
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</template>
|
|
223
|
+
|
|
224
|
+
<style scoped>
|
|
225
|
+
.card {
|
|
226
|
+
border: 1px solid #e5e7eb;
|
|
227
|
+
border-radius: 0.5rem;
|
|
228
|
+
overflow: hidden;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.card-header {
|
|
232
|
+
background-color: #f9fafb;
|
|
233
|
+
padding: 1rem;
|
|
234
|
+
border-bottom: 1px solid #e5e7eb;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.card-content {
|
|
238
|
+
padding: 1rem;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.card-footer {
|
|
242
|
+
background-color: #f9fafb;
|
|
243
|
+
padding: 1rem;
|
|
244
|
+
border-top: 1px solid #e5e7eb;
|
|
245
|
+
}
|
|
246
|
+
</style>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Nested Slots
|
|
250
|
+
|
|
251
|
+
```markdown
|
|
252
|
+
::tabs
|
|
253
|
+
#tab1
|
|
254
|
+
### First Tab
|
|
255
|
+
Content for tab 1
|
|
256
|
+
|
|
257
|
+
#tab2
|
|
258
|
+
### Second Tab
|
|
259
|
+
Content for tab 2
|
|
260
|
+
::
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
```vue
|
|
264
|
+
<!-- Tabs.vue -->
|
|
265
|
+
<template>
|
|
266
|
+
<div class="tabs">
|
|
267
|
+
<div class="tab-headers">
|
|
268
|
+
<button
|
|
269
|
+
v-for="(slot, name) in $slots"
|
|
270
|
+
:key="name"
|
|
271
|
+
:class="{ active: activeTab === name }"
|
|
272
|
+
@click="activeTab = name"
|
|
273
|
+
>
|
|
274
|
+
{{ name }}
|
|
275
|
+
</button>
|
|
276
|
+
</div>
|
|
277
|
+
<div class="tab-content">
|
|
278
|
+
<component :is="() => $slots[activeTab]?.()" />
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</template>
|
|
282
|
+
|
|
283
|
+
<script setup lang="ts">
|
|
284
|
+
import { ref } from 'vue'
|
|
285
|
+
|
|
286
|
+
const activeTab = ref('tab1')
|
|
287
|
+
</script>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Streaming Mode
|
|
293
|
+
|
|
294
|
+
The `Comark` component can be used with reactive content for streaming scenarios:
|
|
295
|
+
|
|
296
|
+
```vue
|
|
297
|
+
<template>
|
|
298
|
+
<div>
|
|
299
|
+
<Comark>{{ content }}</Comark>
|
|
300
|
+
<div v-if="isLoading">Loading...</div>
|
|
301
|
+
</div>
|
|
302
|
+
</template>
|
|
303
|
+
|
|
304
|
+
<script setup lang="ts">
|
|
305
|
+
import { ref } from 'vue'
|
|
306
|
+
import { Comark } from '@comark/vue'
|
|
307
|
+
|
|
308
|
+
const content = ref('')
|
|
309
|
+
const isLoading = ref(true)
|
|
310
|
+
|
|
311
|
+
async function loadContent() {
|
|
312
|
+
const response = await fetch('/api/content.md')
|
|
313
|
+
const reader = response.body!.getReader()
|
|
314
|
+
const decoder = new TextDecoder()
|
|
315
|
+
|
|
316
|
+
while (true) {
|
|
317
|
+
const { done, value } = await reader.read()
|
|
318
|
+
if (done) break
|
|
319
|
+
content.value += decoder.decode(value)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
isLoading.value = false
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
loadContent()
|
|
326
|
+
</script>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Prose Components
|
|
334
|
+
|
|
335
|
+
The `Comark` component uses built-in prose styling automatically. You can override with custom components:
|
|
336
|
+
|
|
337
|
+
```vue
|
|
338
|
+
<template>
|
|
339
|
+
<Comark :components="components">{{ content }}</Comark>
|
|
340
|
+
</template>
|
|
341
|
+
|
|
342
|
+
<script setup lang="ts">
|
|
343
|
+
import { Comark } from '@comark/vue'
|
|
344
|
+
import CustomAlert from './CustomAlert.vue'
|
|
345
|
+
|
|
346
|
+
const components = {
|
|
347
|
+
alert: CustomAlert, // Override or add custom components
|
|
348
|
+
}
|
|
349
|
+
</script>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Error Handling
|
|
355
|
+
|
|
356
|
+
The `ComarkRenderer` component has built-in error capture via Vue's `onErrorCaptured` hook. Component rendering errors are caught automatically without crashing the application. You can also use Vue's native `onErrorCaptured` in a parent component to handle errors:
|
|
357
|
+
|
|
358
|
+
```vue
|
|
359
|
+
<template>
|
|
360
|
+
<Comark :markdown="content" />
|
|
361
|
+
</template>
|
|
362
|
+
|
|
363
|
+
<script setup lang="ts">
|
|
364
|
+
import { onErrorCaptured } from 'vue'
|
|
365
|
+
import { Comark } from '@comark/vue'
|
|
366
|
+
|
|
367
|
+
onErrorCaptured((error) => {
|
|
368
|
+
console.error('Component error:', error)
|
|
369
|
+
return false // prevent propagation
|
|
370
|
+
})
|
|
371
|
+
</script>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Props Access
|
|
377
|
+
|
|
378
|
+
Custom components receive the original Comark node and parsed props:
|
|
379
|
+
|
|
380
|
+
```vue
|
|
381
|
+
<!-- CustomAlert.vue -->
|
|
382
|
+
<template>
|
|
383
|
+
<div :class="alertClasses" role="alert">
|
|
384
|
+
<slot />
|
|
385
|
+
</div>
|
|
386
|
+
</template>
|
|
387
|
+
|
|
388
|
+
<script setup lang="ts">
|
|
389
|
+
import { computed } from 'vue'
|
|
390
|
+
|
|
391
|
+
const props = defineProps<{
|
|
392
|
+
type?: string // From {type="info"}
|
|
393
|
+
bool?: boolean // From {bool} → :bool="true"
|
|
394
|
+
count?: number // From {:count="5"}
|
|
395
|
+
data?: object // From {:data='{"key":"val"}'}
|
|
396
|
+
__node?: any // Original Comark node
|
|
397
|
+
}>()
|
|
398
|
+
|
|
399
|
+
const alertClasses = computed(() => [
|
|
400
|
+
'alert',
|
|
401
|
+
`alert-${props.type || 'info'}`,
|
|
402
|
+
{ 'alert-important': props.bool }
|
|
403
|
+
])
|
|
404
|
+
</script>
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Property Parsing Rules
|
|
408
|
+
|
|
409
|
+
- Props starting with `:` are parsed as booleans/JSON
|
|
410
|
+
- Standard HTML attributes work normally
|
|
411
|
+
- `__node` provides access to the raw AST node
|
|
412
|
+
|
|
413
|
+
### Accessing Node Structure
|
|
414
|
+
|
|
415
|
+
```vue
|
|
416
|
+
<script setup lang="ts">
|
|
417
|
+
const props = defineProps<{ __node?: any }>()
|
|
418
|
+
|
|
419
|
+
// Node structure: [tag, props, ...children]
|
|
420
|
+
const tag = computed(() => props.__node?.[0])
|
|
421
|
+
const nodeProps = computed(() => props.__node?.[1] || {})
|
|
422
|
+
const children = computed(() => props.__node?.slice(2) || [])
|
|
423
|
+
</script>
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Working with Complex Props
|
|
427
|
+
|
|
428
|
+
```vue
|
|
429
|
+
<!-- DataTable.vue -->
|
|
430
|
+
<template>
|
|
431
|
+
<table>
|
|
432
|
+
<thead>
|
|
433
|
+
<tr>
|
|
434
|
+
<th v-for="col in columns" :key="col">{{ col }}</th>
|
|
435
|
+
</tr>
|
|
436
|
+
</thead>
|
|
437
|
+
<tbody>
|
|
438
|
+
<slot />
|
|
439
|
+
</tbody>
|
|
440
|
+
</table>
|
|
441
|
+
</template>
|
|
442
|
+
|
|
443
|
+
<script setup lang="ts">
|
|
444
|
+
const props = defineProps<{
|
|
445
|
+
columns?: string[] // From {:columns='["Name","Age"]'}
|
|
446
|
+
sortable?: boolean // From {sortable}
|
|
447
|
+
__node?: any
|
|
448
|
+
}>()
|
|
449
|
+
</script>
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Usage in Markdown:**
|
|
453
|
+
|
|
454
|
+
```markdown
|
|
455
|
+
::data-table{:columns='["Name", "Age", "Email"]' sortable}
|
|
456
|
+
Table content here
|
|
457
|
+
::
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
[← Back to Main Skills Guide](../SKILL.md)
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migrate-mdc-to-comark
|
|
3
|
+
description: >-
|
|
4
|
+
Migrate a Nuxt project from @nuxtjs/mdc to Comark. Covers package changes, parse/render API mapping, AST format, Nuxt module config, components, slots, plugins, and Nuxt UI integration.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Migrate from @nuxtjs/mdc to Comark
|
|
8
|
+
|
|
9
|
+
`@nuxtjs/mdc` was Nuxt-only. Comark is its successor — the markdown syntax is fully compatible, your `.md` files need no changes. What changes is the JavaScript API.
|
|
10
|
+
|
|
11
|
+
The migration has two parts: **Core Package** (programmatic API) and **Nuxt Module** (components, slots, config).
|
|
12
|
+
|
|
13
|
+
## Quick Overview
|
|
14
|
+
|
|
15
|
+
- **Package**: `@nuxtjs/mdc` → `comark` (core only) or `@comark/nuxt` (Nuxt module)
|
|
16
|
+
- **Parse**: `parseMarkdown()` → `parse()` · factory: `createParse()` (sync, no await)
|
|
17
|
+
- **Render**: `stringifyMarkdown()` → `renderMarkdown()` from `comark/render`
|
|
18
|
+
- **AST**: object tree → compact tuples `['tag', props, ...children]`
|
|
19
|
+
- **Result**: `result.body` / `result.data` → `tree.nodes` / `tree.frontmatter`
|
|
20
|
+
- **Renderer**: `<MDCRenderer :body :data>` → `<ComarkRenderer :tree>`
|
|
21
|
+
- **All-in-one**: `<MDC :value>` → `<Comark :markdown>`
|
|
22
|
+
- **Slots**: `<MDCSlot />` → native `<slot />`
|
|
23
|
+
- **Plugins**: global `nuxt.config` → per-component `defineComarkComponent({ plugins })`
|
|
24
|
+
- **Markdown files**: no changes needed
|
|
25
|
+
|
|
26
|
+
## Core Package
|
|
27
|
+
|
|
28
|
+
### API Mapping
|
|
29
|
+
|
|
30
|
+
| `@nuxtjs/mdc` | `comark` |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `parseMarkdown(md, opts)` | `parse(md, opts)` from `comark` |
|
|
33
|
+
| `createMarkdownParser(opts)` (async) | `createParse(opts)` (sync — no await) |
|
|
34
|
+
| `stringifyMarkdown(body, data)` | `renderMarkdown(tree)` from `comark/render` |
|
|
35
|
+
| `result.body` (`MDCRoot`) | `tree.nodes` (`ComarkNode[]`) |
|
|
36
|
+
| `result.data` | `tree.frontmatter` |
|
|
37
|
+
| `result.data.title` | `tree.frontmatter.title` |
|
|
38
|
+
| `result.toc` | `tree.meta.toc` (requires `toc` plugin) |
|
|
39
|
+
| `result.excerpt` | `tree.meta.summary` (requires `summary` plugin) |
|
|
40
|
+
|
|
41
|
+
### AST Format
|
|
42
|
+
|
|
43
|
+
| `@nuxtjs/mdc` | `comark` |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `{ type: 'root', children: MDCNode[] }` | `{ nodes: ComarkNode[], frontmatter: {}, meta: {} }` |
|
|
46
|
+
| `{ type: 'element', tag: 'p', props: {}, children: [] }` | `['p', {}, ...children]` |
|
|
47
|
+
| `{ type: 'text', value: 'hello' }` | `'hello'` (plain string) |
|
|
48
|
+
|
|
49
|
+
### Parse Options
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// Before — MDCParseOptions
|
|
53
|
+
{
|
|
54
|
+
remark: { plugins: { /* record */ } },
|
|
55
|
+
rehype: { options: {...}, plugins: { /* record */ } },
|
|
56
|
+
highlight: { theme: '...', langs: [...] } | false,
|
|
57
|
+
toc: { depth: 3, searchDepth: 2 } | false,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// After — ParseOptions
|
|
61
|
+
{
|
|
62
|
+
plugins: ComarkPlugin[], // ordered array, not a record
|
|
63
|
+
autoUnwrap: true, // removes <p> from single-paragraph containers
|
|
64
|
+
autoClose: true, // completes incomplete syntax (useful for streaming)
|
|
65
|
+
html: true, // parse embedded HTML tags
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Plugins
|
|
70
|
+
|
|
71
|
+
The `unified`/`remark`/`rehype` pipeline is replaced by Comark's own lighter plugin interface.
|
|
72
|
+
|
|
73
|
+
| Feature | Before | After |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| Syntax highlighting | `rehypeHighlight` via `createMarkdownParser` | `highlight()` from `comark/plugins/highlight` |
|
|
76
|
+
| Table of Contents | `parseMarkdown(md, { toc: { depth: 3 } })` | `toc({ depth: 3 })` plugin |
|
|
77
|
+
| Excerpt / Summary | `result.excerpt` (built-in) | `summary()` plugin → `tree.meta.summary` |
|
|
78
|
+
| Emoji | `remark-emoji` (enabled by default) | `emoji()` plugin (opt-in) |
|
|
79
|
+
|
|
80
|
+
Available plugins: `comark/plugins/toc`, `comark/plugins/highlight`, `comark/plugins/emoji`, `comark/plugins/task-list`, `comark/plugins/summary`, `comark/plugins/security`, `comark/plugins/alert`, `comark/plugins/math`, `comark/plugins/mermaid`, `comark/plugins/punctuation`
|
|
81
|
+
|
|
82
|
+
## Nuxt Module
|
|
83
|
+
|
|
84
|
+
### Configuration
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Before
|
|
88
|
+
export default defineNuxtConfig({
|
|
89
|
+
modules: ['@nuxtjs/mdc'],
|
|
90
|
+
mdc: { highlight: { ... }, remarkPlugins: { ... } },
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// After
|
|
94
|
+
export default defineNuxtConfig({
|
|
95
|
+
modules: ['@comark/nuxt'],
|
|
96
|
+
// No plugin config here — plugins are defined per-component
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`@comark/nuxt` auto-imports: `Comark`, `ComarkRenderer`, `defineComarkComponent`, `defineComarkRendererComponent`.
|
|
101
|
+
|
|
102
|
+
### Components
|
|
103
|
+
|
|
104
|
+
| `@nuxtjs/mdc` | `@comark/nuxt` |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `<MDCRenderer :body :data :components>` | `<ComarkRenderer :tree :components>` |
|
|
107
|
+
| `<MDC :value :parser-options>` | `<Comark :markdown :options>` or `<Comark>{{ md }}</Comark>` |
|
|
108
|
+
| `<MDCSlot />` | `<slot />` |
|
|
109
|
+
| `<MDCSlot unwrap="p" />` | `<slot unwrap="p" />` |
|
|
110
|
+
| `<slot mdc-unwrap="p" />` | `<slot unwrap="p" />` |
|
|
111
|
+
|
|
112
|
+
For a pre-parsed tree, use `<ComarkRenderer>` directly instead of `<Comark>`.
|
|
113
|
+
|
|
114
|
+
#### `<ComarkRenderer>` props changes
|
|
115
|
+
|
|
116
|
+
| MDC `<MDCRenderer>` | Comark `<ComarkRenderer>` | Notes |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| `body` (`MDCRoot`) | `tree` (`ComarkTree`) | Different AST shape |
|
|
119
|
+
| `data` | — | Frontmatter is in `tree.frontmatter` |
|
|
120
|
+
| `tag` | — | Wrapper is always `<div class="comark-content">` |
|
|
121
|
+
| `prose` | — | `Prose*` resolution is automatic |
|
|
122
|
+
| `unwrap` | — | Use `autoUnwrap` in parse options |
|
|
123
|
+
| `components` | `components` | Same purpose |
|
|
124
|
+
| — | `componentsManifest` | New: dynamic async component resolver |
|
|
125
|
+
| — | `streaming` | New: streaming mode |
|
|
126
|
+
| — | `caret` | New: animated caret for streaming |
|
|
127
|
+
|
|
128
|
+
#### Summary rendering
|
|
129
|
+
|
|
130
|
+
```vue
|
|
131
|
+
<!-- Before -->
|
|
132
|
+
<MDCRenderer :body="result.excerpt ?? result.body" :data="result.data" />
|
|
133
|
+
|
|
134
|
+
<!-- After -->
|
|
135
|
+
<Comark summary>{{ markdown }}</Comark>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `defineComarkComponent`
|
|
139
|
+
|
|
140
|
+
Replaces global `mdc: { ... }` config. Define reusable components with their own plugins and component mappings:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { defineComarkComponent } from '@comark/vue'
|
|
144
|
+
import highlight from 'comark/plugins/highlight'
|
|
145
|
+
import toc from 'comark/plugins/toc'
|
|
146
|
+
|
|
147
|
+
export const ArticleComark = defineComarkComponent({
|
|
148
|
+
name: 'ArticleComark',
|
|
149
|
+
plugins: [highlight({ themes: { light: githubLight, dark: githubDark } }), toc()],
|
|
150
|
+
components: { alert: CustomAlert },
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Slots
|
|
155
|
+
|
|
156
|
+
`<MDCSlot />` → native `<slot />`. Named slots work the same (`#slotName` in markdown, `<slot name="slotName">` in component). The `unwrap` attribute (`<slot unwrap="p">`) strips wrapper tags from children.
|
|
157
|
+
|
|
158
|
+
### Prose Components
|
|
159
|
+
|
|
160
|
+
Same `Prose*.vue` naming convention in `components/prose/`. Resolution changed from kebab-case (`prose-p`) to PascalCase (`ProseP`) internally — no file changes needed.
|
|
161
|
+
|
|
162
|
+
### Nuxt UI Integration
|
|
163
|
+
|
|
164
|
+
When using Nuxt UI, `@comark/nuxt` registers Nuxt UI prose components automatically. Shorthand callout components are available:
|
|
165
|
+
|
|
166
|
+
```mdc
|
|
167
|
+
::note — informational
|
|
168
|
+
::tip — helpful suggestion
|
|
169
|
+
::warning — something to watch out for
|
|
170
|
+
::caution — critical warning
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
These are **only available with Nuxt UI**. Without it, use `::callout{icon="..." color="..."}`.
|
|
174
|
+
|
|
175
|
+
## Common Pitfalls
|
|
176
|
+
|
|
177
|
+
1. **`createParse` is sync** — no `await` needed (unlike `createMarkdownParser`)
|
|
178
|
+
2. **Attribute naming**: Comark uses `attrs.lang`, not `attrs.language`
|
|
179
|
+
3. **Frontmatter**: stored in `tree.frontmatter`, not passed separately
|
|
180
|
+
4. **`renderMarkdown` includes frontmatter** — reads `tree.frontmatter` automatically
|
|
181
|
+
5. **No `unified` pipeline** — `mdc.config.ts` hooks (`pre`, `remark`, `rehype`, `post`) have no equivalent, use `ComarkPlugin` interface instead
|
|
182
|
+
6. **Emoji is opt-in** — not enabled by default like in MDC's `remark-emoji`
|
|
183
|
+
|
|
184
|
+
## Component Syntax
|
|
185
|
+
|
|
186
|
+
The MDC block and inline component syntax is identical — no changes needed in `.md` files.
|
|
187
|
+
|
|
188
|
+
## Unsupported Features
|
|
189
|
+
|
|
190
|
+
- **Binding syntax** (`{{ variable }}`) — not supported, rendered as plain text
|
|
191
|
+
- **Props binding / data passing** — no equivalent for `parseMarkdown(md, { data: { ... } })`
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from '../../src/utils/serialized-task'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from '../../src/utils/serialized-task.ts'
|