comark 0.3.1 → 0.4.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.
Files changed (93) hide show
  1. package/dist/internal/frontmatter.d.ts +1 -0
  2. package/dist/internal/frontmatter.js +4 -2
  3. package/dist/internal/parse/auto-close/index.js +69 -31
  4. package/dist/internal/parse/auto-close/table.js +12 -9
  5. package/dist/internal/parse/auto-unwrap.js +6 -10
  6. package/dist/internal/parse/html/html_block_rule.js +10 -16
  7. package/dist/internal/parse/html/html_inline_rule.js +3 -7
  8. package/dist/internal/parse/html/html_re.js +1 -1
  9. package/dist/internal/parse/html/index.d.ts +1 -0
  10. package/dist/internal/parse/html/index.js +15 -3
  11. package/dist/internal/parse/syntax/block-params.d.ts +9 -0
  12. package/dist/internal/parse/syntax/block-params.js +48 -0
  13. package/dist/internal/parse/syntax/brackets.d.ts +8 -0
  14. package/dist/internal/parse/syntax/brackets.js +20 -0
  15. package/dist/internal/parse/syntax/props.d.ts +5 -0
  16. package/dist/internal/parse/syntax/props.js +119 -0
  17. package/dist/internal/parse/token-processor.js +89 -50
  18. package/dist/internal/props-validation.js +4 -9
  19. package/dist/internal/stringify/attributes.d.ts +7 -0
  20. package/dist/internal/stringify/attributes.js +56 -1
  21. package/dist/internal/stringify/handlers/a.js +1 -3
  22. package/dist/internal/stringify/handlers/blockquote.js +19 -4
  23. package/dist/internal/stringify/handlers/code.js +1 -3
  24. package/dist/internal/stringify/handlers/emphesis.js +1 -3
  25. package/dist/internal/stringify/handlers/heading.js +6 -1
  26. package/dist/internal/stringify/handlers/html.js +34 -18
  27. package/dist/internal/stringify/handlers/img.js +1 -3
  28. package/dist/internal/stringify/handlers/li.js +18 -9
  29. package/dist/internal/stringify/handlers/mdc.js +3 -4
  30. package/dist/internal/stringify/handlers/ol.js +12 -2
  31. package/dist/internal/stringify/handlers/p.d.ts +1 -1
  32. package/dist/internal/stringify/handlers/p.js +8 -1
  33. package/dist/internal/stringify/handlers/pre.js +20 -14
  34. package/dist/internal/stringify/handlers/strong.js +1 -3
  35. package/dist/internal/stringify/handlers/table.js +14 -5
  36. package/dist/internal/stringify/handlers/template.js +5 -2
  37. package/dist/internal/stringify/handlers/ul.js +12 -2
  38. package/dist/internal/stringify/state.js +1 -1
  39. package/dist/internal/yaml.js +1 -1
  40. package/dist/parse.d.ts +4 -4
  41. package/dist/parse.js +20 -10
  42. package/dist/plugins/alert.d.ts +1 -1
  43. package/dist/plugins/alert.js +1 -1
  44. package/dist/plugins/binding.d.ts +1 -1
  45. package/dist/plugins/binding.js +1 -3
  46. package/dist/plugins/breaks.d.ts +1 -1
  47. package/dist/plugins/breaks.js +1 -1
  48. package/dist/plugins/emoji.d.ts +1 -1
  49. package/dist/plugins/emoji.js +8 -8
  50. package/dist/plugins/footnotes.d.ts +1 -1
  51. package/dist/plugins/footnotes.js +19 -13
  52. package/dist/plugins/headings.d.ts +19 -8
  53. package/dist/plugins/headings.js +27 -19
  54. package/dist/plugins/highlight.d.ts +2 -12
  55. package/dist/plugins/highlight.js +201 -103
  56. package/dist/plugins/json-render.d.ts +1 -1
  57. package/dist/plugins/json-render.js +5 -9
  58. package/dist/plugins/math.d.ts +1 -1
  59. package/dist/plugins/math.js +4 -6
  60. package/dist/plugins/mermaid.d.ts +1 -1
  61. package/dist/plugins/mermaid.js +6 -20
  62. package/dist/plugins/punctuation.d.ts +1 -1
  63. package/dist/plugins/punctuation.js +5 -6
  64. package/dist/plugins/security.d.ts +1 -1
  65. package/dist/plugins/security.js +2 -2
  66. package/dist/plugins/summary.d.ts +4 -1
  67. package/dist/plugins/syntax.d.ts +49 -0
  68. package/dist/plugins/syntax.js +558 -0
  69. package/dist/plugins/task-list.d.ts +2 -2
  70. package/dist/plugins/task-list.js +11 -8
  71. package/dist/plugins/toc.d.ts +3 -1
  72. package/dist/plugins/toc.js +1 -1
  73. package/dist/types.d.ts +57 -12
  74. package/dist/utils/comark.tmLanguage.d.ts +335 -0
  75. package/dist/utils/comark.tmLanguage.js +597 -0
  76. package/dist/utils/helpers.d.ts +16 -4
  77. package/dist/utils/helpers.js +16 -6
  78. package/dist/utils/index.d.ts +5 -0
  79. package/dist/utils/index.js +25 -3
  80. package/package.json +40 -40
  81. package/skills/comark/references/rendering-svelte.md +51 -7
  82. package/dist/internal/stringify/indent.d.ts +0 -5
  83. package/dist/internal/stringify/indent.js +0 -9
  84. package/dist/vite.d.ts +0 -1
  85. package/dist/vite.js +0 -1
  86. package/skills/skills/comark/AGENTS.md +0 -261
  87. package/skills/skills/comark/SKILL.md +0 -489
  88. package/skills/skills/comark/references/markdown-syntax.md +0 -599
  89. package/skills/skills/comark/references/parsing-ast.md +0 -378
  90. package/skills/skills/comark/references/rendering-react.md +0 -445
  91. package/skills/skills/comark/references/rendering-svelte.md +0 -453
  92. package/skills/skills/comark/references/rendering-vue.md +0 -462
  93. /package/skills/{skills/migrate-mdc-to-comark → migrate-mdc-to-comark}/SKILL.md +0 -0
@@ -0,0 +1,558 @@
1
+ import { Token } from 'markdown-exit';
2
+ import { defineComarkPlugin } from "../utils/helpers.js";
3
+ import { parseBracketContent } from "../internal/parse/syntax/brackets.js";
4
+ import { searchProps } from "../internal/parse/syntax/props.js";
5
+ import { parseBlockParams } from "../internal/parse/syntax/block-params.js";
6
+ import { parseYaml } from "../internal/yaml.js";
7
+ // #region Block component plugin (`::name` and `::name ... ::`)
8
+ const blockYamlLines = {
9
+ '---': '---',
10
+ '```yaml [props]': '```',
11
+ '~~~yaml [props]': '~~~',
12
+ '```yml [props]': '```',
13
+ '~~~yml [props]': '~~~',
14
+ };
15
+ const markdownItComarkBlock = (md) => {
16
+ const min_markers = 2;
17
+ const marker_str = ':';
18
+ const marker_char = marker_str.charCodeAt(0);
19
+ md.block.ruler.before('fence', 'comark_block_shorthand', function comark_block_shorthand(state, startLine, _endLine, silent) {
20
+ const line = state.src.slice(state.bMarks[startLine] + state.tShift[startLine], state.eMarks[startLine]);
21
+ if (!/^:\w/.test(line))
22
+ return false;
23
+ const { name, content, props, remaining } = parseBlockParams(line.slice(1));
24
+ // If there's unparsed remaining content, treat it as inline component in a paragraph
25
+ if (remaining)
26
+ return false;
27
+ state.lineMax = startLine + 1;
28
+ if (!silent) {
29
+ if (content !== undefined) {
30
+ const tokenOpen = state.push('mdc_block_shorthand', name, 1);
31
+ props?.forEach(([key, value]) => {
32
+ if (key === 'class')
33
+ tokenOpen.attrJoin(key, value);
34
+ else
35
+ tokenOpen.attrSet(key, value);
36
+ });
37
+ tokenOpen.map = [startLine, startLine + 1];
38
+ const inline = state.push('inline', '', 0);
39
+ inline.content = content;
40
+ inline.children = [];
41
+ const tokenClose = state.push('mdc_block_shorthand', name, -1);
42
+ tokenClose.map = [startLine, startLine + 1];
43
+ }
44
+ else {
45
+ const token = state.push('mdc_block_shorthand', name, 0);
46
+ token.map = [startLine, startLine + 1];
47
+ props?.forEach(([key, value]) => {
48
+ if (key === 'class')
49
+ token.attrJoin(key, value);
50
+ else
51
+ token.attrSet(key, value);
52
+ });
53
+ }
54
+ }
55
+ state.line = startLine + 1;
56
+ return true;
57
+ });
58
+ md.block.ruler.before('fence', 'comark_block', function comark_block(state, startLine, endLine, silent) {
59
+ let pos;
60
+ let nextLine;
61
+ let auto_closed = false;
62
+ let start = state.bMarks[startLine] + state.tShift[startLine];
63
+ let max = state.eMarks[startLine];
64
+ const indent = state.sCount[startLine];
65
+ // Track code fences (``` or ~~~) so we don't match closing :: inside them
66
+ let inCodeFence = false;
67
+ let codeFenceCharCode = 0;
68
+ let codeFenceCount = 0;
69
+ // Track nesting depth for blocks with the same marker count
70
+ let nestingDepth = 0;
71
+ if (state.src[start] !== ':')
72
+ return false;
73
+ for (pos = start + 1; pos <= max; pos++) {
74
+ if (marker_str !== state.src[pos])
75
+ break;
76
+ }
77
+ const marker_count = Math.floor(pos - start);
78
+ if (marker_count < min_markers)
79
+ return false;
80
+ const markup = state.src.slice(start, pos);
81
+ const params = parseBlockParams(state.src.slice(pos, max));
82
+ if (!params.name)
83
+ return false;
84
+ if (silent)
85
+ return true;
86
+ nextLine = startLine;
87
+ for (;;) {
88
+ nextLine++;
89
+ if (nextLine >= endLine)
90
+ break;
91
+ start = state.bMarks[nextLine] + state.tShift[nextLine];
92
+ max = state.eMarks[nextLine];
93
+ if (start < max && state.sCount[nextLine] < state.blkIndent)
94
+ break;
95
+ const lineCharCode = state.src.charCodeAt(start);
96
+ // Detect closing code fence (``` or ~~~)
97
+ if (inCodeFence) {
98
+ if (lineCharCode === codeFenceCharCode) {
99
+ let fencePos = start + 1;
100
+ while (fencePos < max && state.src.charCodeAt(fencePos) === codeFenceCharCode)
101
+ fencePos++;
102
+ if (fencePos - start >= codeFenceCount) {
103
+ const afterFence = state.skipSpaces(fencePos);
104
+ if (afterFence >= max)
105
+ inCodeFence = false;
106
+ }
107
+ }
108
+ continue;
109
+ }
110
+ // Detect opening code fence (``` or ~~~)
111
+ if (lineCharCode === 0x60 /* ` */ || lineCharCode === 0x7e /* ~ */) {
112
+ let fencePos = start + 1;
113
+ while (fencePos < max && state.src.charCodeAt(fencePos) === lineCharCode)
114
+ fencePos++;
115
+ if (fencePos - start >= 3) {
116
+ inCodeFence = true;
117
+ codeFenceCharCode = lineCharCode;
118
+ codeFenceCount = fencePos - start;
119
+ continue;
120
+ }
121
+ }
122
+ if (marker_char !== lineCharCode)
123
+ continue;
124
+ for (pos = start + 1; pos <= max; pos++) {
125
+ if (marker_str !== state.src[pos])
126
+ break;
127
+ }
128
+ // Closing fence must match the opening fence length
129
+ if (pos - start !== marker_count)
130
+ continue;
131
+ pos = state.skipSpaces(pos);
132
+ if (pos < max) {
133
+ // A new nested block opens with same marker count
134
+ nestingDepth++;
135
+ continue;
136
+ }
137
+ if (nestingDepth > 0) {
138
+ nestingDepth--;
139
+ continue;
140
+ }
141
+ auto_closed = true;
142
+ break;
143
+ }
144
+ const old_parent = state.parentType;
145
+ const old_line_max = state.lineMax;
146
+ state.parentType = 'comark_block';
147
+ // Prevent lazy continuations from going past our end marker
148
+ state.lineMax = nextLine;
149
+ const tokenOpen = state.push('mdc_block_open', params.name, 1);
150
+ tokenOpen.markup = markup;
151
+ tokenOpen.block = true;
152
+ tokenOpen.info = params.name;
153
+ tokenOpen.map = [startLine, nextLine];
154
+ params.props?.forEach(([key, value]) => {
155
+ if (key === 'class')
156
+ tokenOpen.attrJoin(key, value);
157
+ else
158
+ tokenOpen.attrSet(key, value);
159
+ });
160
+ // Render bracket content as the first paragraph: `::block[Content]\n::`
161
+ if (params.content !== undefined) {
162
+ const pOpen = state.push('paragraph_open', 'p', 1);
163
+ pOpen.map = [startLine, startLine + 1];
164
+ const inline = state.push('inline', '', 0);
165
+ inline.content = params.content;
166
+ inline.children = [];
167
+ state.push('paragraph_close', 'p', -1);
168
+ }
169
+ const blkIndent = state.blkIndent;
170
+ state.blkIndent = indent;
171
+ state.env.comarkBlockTokens ||= [];
172
+ state.env.comarkBlockTokens.unshift(tokenOpen);
173
+ state.md.block.tokenize(state, startLine + 1, nextLine);
174
+ state.blkIndent = blkIndent;
175
+ state.env.comarkBlockTokens.shift();
176
+ const tokenClose = state.push('mdc_block_close', params.name, -1);
177
+ tokenClose.map = [startLine, nextLine];
178
+ tokenClose.markup = state.src.slice(start, pos);
179
+ tokenClose.block = true;
180
+ // Hide the wrapper paragraph for single-paragraph blocks
181
+ state.tokens
182
+ .slice(state.tokens.indexOf(tokenOpen) + 1, state.tokens.indexOf(tokenClose))
183
+ .filter((i) => i.level === tokenOpen.level + 1)
184
+ .forEach((i, _, arr) => {
185
+ if (arr.length <= 2 && i.tag === 'p')
186
+ i.hidden = true;
187
+ });
188
+ state.parentType = old_parent;
189
+ state.lineMax = old_line_max;
190
+ state.line = nextLine + (auto_closed ? 1 : 0);
191
+ return true;
192
+ }, {
193
+ alt: ['paragraph', 'reference', 'blockquote', 'list'],
194
+ });
195
+ md.block.ruler.after('code', 'comark_block_yaml', function comark_block_yaml(state, startLine, endLine, silent) {
196
+ if (!state.env.comarkBlockTokens?.length)
197
+ return false;
198
+ const start = state.bMarks[startLine] + state.tShift[startLine];
199
+ const end = state.eMarks[startLine];
200
+ const line = state.src.slice(start, end);
201
+ const blockAttributesClosingFence = blockYamlLines[line] || '';
202
+ if (!blockAttributesClosingFence)
203
+ return false;
204
+ // The `---` fence is only valid on the line immediately after the component opener. Any other `---` is a thematic break.
205
+ if (line === '---') {
206
+ const parentOpenLine = state.env.comarkBlockTokens[0].map?.[0];
207
+ if (parentOpenLine === undefined || startLine !== parentOpenLine + 1)
208
+ return false;
209
+ }
210
+ let lineEnd = startLine + 1;
211
+ let found = false;
212
+ while (lineEnd < endLine) {
213
+ const inner = state.src.slice(state.bMarks[lineEnd] + state.tShift[startLine], state.eMarks[lineEnd]);
214
+ if (inner === blockAttributesClosingFence) {
215
+ found = true;
216
+ break;
217
+ }
218
+ lineEnd += 1;
219
+ }
220
+ if (!found)
221
+ return false;
222
+ if (!silent) {
223
+ const yaml = state.src.slice(state.bMarks[startLine + 1], state.eMarks[lineEnd - 1]);
224
+ const data = parseYaml(yaml);
225
+ const token = state.env.comarkBlockTokens[0];
226
+ Object.entries(data || {}).forEach(([key, value]) => {
227
+ if (key === 'class')
228
+ token.attrJoin(key, value);
229
+ else
230
+ token.attrSet(key, typeof value === 'string' ? value : JSON.stringify(value));
231
+ });
232
+ }
233
+ state.line = lineEnd + 1;
234
+ state.lineMax = lineEnd + 1;
235
+ return true;
236
+ });
237
+ md.block.ruler.after('code', 'comark_block_slots', function comark_block_slots(state, startLine, endLine, silent) {
238
+ if (!state.env.comarkBlockTokens?.length)
239
+ return false;
240
+ const start = state.bMarks[startLine] + state.tShift[startLine];
241
+ if (!(state.src[start] === '#' && state.src[start + 1] !== ' ' && state.src[start + 1] !== '#'))
242
+ return false;
243
+ const line = state.src.slice(start, state.eMarks[startLine]);
244
+ const { name, props } = parseBlockParams(line.slice(1));
245
+ let lineEnd = startLine + 1;
246
+ let inCodeFence = false;
247
+ let codeFenceChar = '';
248
+ let codeFenceCount = 0;
249
+ while (lineEnd < endLine) {
250
+ const inner = state.src.slice(state.bMarks[lineEnd] + state.tShift[startLine], state.eMarks[lineEnd]);
251
+ if (inCodeFence) {
252
+ // Look for matching closing fence (same char, >= opening count, nothing but spaces after)
253
+ if (inner[0] === codeFenceChar) {
254
+ let fencePos = 1;
255
+ while (fencePos < inner.length && inner[fencePos] === codeFenceChar)
256
+ fencePos++;
257
+ if (fencePos >= codeFenceCount && inner.slice(fencePos).trim() === '') {
258
+ inCodeFence = false;
259
+ }
260
+ }
261
+ lineEnd += 1;
262
+ continue;
263
+ }
264
+ // Detect opening code fence (``` or ~~~, length >= 3)
265
+ if (inner[0] === '`' || inner[0] === '~') {
266
+ const ch = inner[0];
267
+ let fencePos = 1;
268
+ while (fencePos < inner.length && inner[fencePos] === ch)
269
+ fencePos++;
270
+ if (fencePos >= 3) {
271
+ inCodeFence = true;
272
+ codeFenceChar = ch;
273
+ codeFenceCount = fencePos;
274
+ lineEnd += 1;
275
+ continue;
276
+ }
277
+ }
278
+ if (/^#\w+/.test(inner) || inner.startsWith('::'))
279
+ break;
280
+ lineEnd += 1;
281
+ }
282
+ if (silent) {
283
+ state.line = lineEnd;
284
+ state.lineMax = lineEnd;
285
+ return true;
286
+ }
287
+ state.lineMax = startLine + 1;
288
+ const slot = state.push('mdc_block_slot', 'template', 1);
289
+ slot.attrSet(`#${name}`, '');
290
+ props?.forEach(([key, value]) => {
291
+ if (key === 'class')
292
+ slot.attrJoin(key, value);
293
+ else
294
+ slot.attrSet(key, value);
295
+ });
296
+ state.line = startLine + 1;
297
+ state.lineMax = lineEnd;
298
+ state.md.block.tokenize(state, startLine + 1, lineEnd);
299
+ state.push('mdc_block_slot', 'template', -1);
300
+ state.line = lineEnd;
301
+ state.lineMax = lineEnd;
302
+ return true;
303
+ });
304
+ };
305
+ // #endregion
306
+ // #region Inline span plugin (`[text]`)
307
+ const markdownItInlineSpan = (md) => {
308
+ md.inline.ruler.before('link', 'comark_inline_span', (state, silent) => {
309
+ const start = state.pos;
310
+ if (state.src[start] !== '[')
311
+ return false;
312
+ let index = start + 1;
313
+ let depth = 0;
314
+ while (index < state.src.length) {
315
+ if (state.src[index] === '\\') {
316
+ index += 2;
317
+ continue;
318
+ }
319
+ if (state.src[index] === '[') {
320
+ depth++;
321
+ }
322
+ else if (state.src[index] === ']') {
323
+ if (depth === 0)
324
+ break;
325
+ depth--;
326
+ }
327
+ index += 1;
328
+ }
329
+ if (index === start)
330
+ return false;
331
+ // Don't match `[text](url)` or `[text][ref]` — let the link parser handle those
332
+ const nextChar = state.src[index + 1];
333
+ if (nextChar === '(' || nextChar === '[')
334
+ return false;
335
+ if (silent)
336
+ return true;
337
+ state.push('mdc_inline_span', 'span', 1);
338
+ const oldPos = state.pos;
339
+ const oldPosMax = state.posMax;
340
+ state.pos = start + 1;
341
+ state.posMax = index;
342
+ state.md.inline.tokenize(state);
343
+ state.pos = oldPos;
344
+ state.posMax = oldPosMax;
345
+ state.push('mdc_inline_span', 'span', -1);
346
+ state.pos = index + 1;
347
+ return true;
348
+ });
349
+ };
350
+ // #endregion
351
+ // #region Inline component plugin (`:name[content]{props}`)
352
+ const ALLOWED_PREV_CHARS = new Set([' ', '\t', '\n', '*', '_', '[']);
353
+ const markdownItInlineComponent = (md) => {
354
+ md.inline.ruler.after('entity', 'comark_inline_component', (state, silent) => {
355
+ const start = state.pos;
356
+ if (state.src[start] !== ':')
357
+ return false;
358
+ const prevChar = state.src[start - 1];
359
+ if (start > 0 && !ALLOWED_PREV_CHARS.has(prevChar))
360
+ return false;
361
+ let index = start + 1;
362
+ let nameEnd = -1;
363
+ let contentStart = -1;
364
+ let contentEnd = -1;
365
+ while (index < state.src.length) {
366
+ const char = state.src[index];
367
+ if (char === '[') {
368
+ nameEnd = index;
369
+ const result = parseBracketContent(state.src, index);
370
+ if (result) {
371
+ contentStart = index + 1;
372
+ contentEnd = result.endIndex - 1;
373
+ index = result.endIndex;
374
+ }
375
+ break;
376
+ }
377
+ if (!/[\w$-]/.test(char))
378
+ break;
379
+ index += 1;
380
+ }
381
+ if (nameEnd === -1)
382
+ nameEnd = index;
383
+ // Empty name
384
+ if (nameEnd <= start + 1)
385
+ return false;
386
+ state.pos = index;
387
+ if (silent)
388
+ return true;
389
+ const name = state.src.slice(start + 1, nameEnd);
390
+ if (contentStart !== -1) {
391
+ state.push('mdc_inline_component', name, 1);
392
+ const oldPos = state.pos;
393
+ const oldPosMax = state.posMax;
394
+ state.pos = contentStart;
395
+ state.posMax = contentEnd;
396
+ state.md.inline.tokenize(state);
397
+ state.pos = oldPos;
398
+ state.posMax = oldPosMax;
399
+ state.push('mdc_inline_component', name, -1);
400
+ }
401
+ else {
402
+ state.push('mdc_inline_component', name, 0);
403
+ }
404
+ return true;
405
+ });
406
+ };
407
+ // #endregion
408
+ // #region Inline props plugin (`{class="foo"}` after a token)
409
+ const markdownItInlineProps = (md) => {
410
+ md.inline.ruler.after('entity', 'comark_inline_props', (state, silent) => {
411
+ const start = state.pos;
412
+ if (state.src[start] !== '{')
413
+ return false;
414
+ // Skip Vue mustache `{{ }}` and template `${ }` syntax
415
+ if (state.src[start + 1] === '{' || state.src[start - 1] === '{' || state.src[start - 1] === '$')
416
+ return false;
417
+ const search = searchProps(state.src, start);
418
+ if (!search)
419
+ return false;
420
+ const { props, index: end } = search;
421
+ if (end === start)
422
+ return false;
423
+ state.pos = end;
424
+ if (silent)
425
+ return true;
426
+ // Hidden token holding the props; later applied to the previous token
427
+ const token = state.push('mdc_inline_props', 'span', 0);
428
+ token.attrs = props;
429
+ token.hidden = true;
430
+ return true;
431
+ });
432
+ md.renderer.rules.mdc_inline_props = () => '';
433
+ const _parse = md.parse;
434
+ md.parse = function (src, env) {
435
+ const tokens = _parse.call(this, src, env);
436
+ // When the trailing inline child is a props token directly after a text
437
+ // node, lift the props onto the surrounding heading/paragraph/list_item.
438
+ // (If the props follow a closing tag, they belong to that inline tag, not
439
+ // the parent — leave them alone.)
440
+ tokens.forEach((token, index) => {
441
+ const prev = tokens[index - 1];
442
+ const next = tokens[index + 1];
443
+ if (!prev || !['heading_open', 'paragraph_open', 'list_item_open'].includes(prev.type) || prev.hidden)
444
+ return;
445
+ // Tight-list paragraph: the inline lives one slot ahead
446
+ if (token.hidden && next?.type === 'inline')
447
+ token = next;
448
+ if (token.type !== 'inline' || !token.children?.length)
449
+ return;
450
+ const last = token.children[token.children.length - 1];
451
+ if (last.type !== 'mdc_inline_props')
452
+ return;
453
+ // Find the previous non-empty child. Markdown-it's emphasis tokenizer
454
+ // can leave an empty text token between the closing delimiter and the
455
+ // props — skipping it lets us distinguish "props on the parent" from
456
+ // "props on the preceding inline tag".
457
+ let beforeIdx = token.children.length - 2;
458
+ while (beforeIdx >= 0) {
459
+ const child = token.children[beforeIdx];
460
+ if (child.type === 'text' && !child.content) {
461
+ beforeIdx--;
462
+ continue;
463
+ }
464
+ break;
465
+ }
466
+ const beforeProps = beforeIdx >= 0 ? token.children[beforeIdx] : undefined;
467
+ if (!beforeProps || beforeProps.type !== 'text')
468
+ return;
469
+ // Strip the trailing space the text picked up before the `{...}` token.
470
+ if (typeof beforeProps.content === 'string') {
471
+ beforeProps.content = beforeProps.content.replace(/[ \t]+$/, '');
472
+ }
473
+ const props = last.attrs;
474
+ // Drop the props token (last) plus any empty text tokens it left behind.
475
+ token.children.length = beforeProps.content ? beforeIdx + 1 : beforeIdx;
476
+ props?.forEach(([key, value]) => {
477
+ if (key === 'class')
478
+ prev.attrJoin('class', value);
479
+ else
480
+ prev.attrSet(key, value);
481
+ });
482
+ });
483
+ return tokens;
484
+ };
485
+ md.renderer.renderInline = wrapRenderInline(md.renderer.renderInline);
486
+ // Support markdown-exit's async inline renderer
487
+ if ('renderInlineAsync' in md.renderer) {
488
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mirrors the sync wrapper for the async overload
489
+ ;
490
+ md.renderer.renderInlineAsync = wrapRenderInline(md.renderer.renderInlineAsync);
491
+ }
492
+ };
493
+ function wrapRenderInline(renderInline) {
494
+ return function (tokens, options, env) {
495
+ tokens = [...tokens];
496
+ tokens.forEach((token, index) => {
497
+ if (token.type !== 'mdc_inline_props')
498
+ return;
499
+ let prevIndex = index - 1;
500
+ let prev = tokens[prevIndex];
501
+ // Skip whitespace-only text tokens
502
+ while (prevIndex >= 0) {
503
+ if (prev.type === 'text' && !prev.content.trim()) {
504
+ prevIndex--;
505
+ prev = tokens[prevIndex];
506
+ }
507
+ else {
508
+ break;
509
+ }
510
+ }
511
+ // Wrap a bare text token in a span so we can attach attrs to it
512
+ if (!prev.tag && prev.type === 'text') {
513
+ prev = new Token('mdc_inline_span', 'span', 1);
514
+ tokens.splice(index - 1, 0, prev);
515
+ const close = new Token('mdc_inline_span', 'span', -1);
516
+ tokens.splice(index + 2, 0, close);
517
+ }
518
+ else if (prev.nesting === -1) {
519
+ // Resolve a closing tag back to its matching opening tag
520
+ let searchIndex = index - 1;
521
+ while (searchIndex >= 0) {
522
+ const searchToken = tokens[searchIndex];
523
+ if (searchToken.nesting === 1 && searchToken.tag === prev.tag && searchToken.level === prev.level) {
524
+ prev = searchToken;
525
+ break;
526
+ }
527
+ searchIndex--;
528
+ }
529
+ }
530
+ if (prev.nesting === -1)
531
+ throw new Error(`No matching opening tag found for ${JSON.stringify(prev)}`);
532
+ token.attrs?.forEach(([key, value]) => {
533
+ if (key === 'class')
534
+ prev.attrJoin('class', value);
535
+ else
536
+ prev.attrSet(key, value);
537
+ });
538
+ });
539
+ return renderInline.call(this, tokens, options, env);
540
+ };
541
+ }
542
+ // #endregion
543
+ function applySyntax(md, options = {}) {
544
+ const { blockComponent = true, inlineProps = true, inlineSpan = true, inlineComponent = true } = options;
545
+ if (blockComponent)
546
+ md.use(markdownItComarkBlock);
547
+ if (inlineProps)
548
+ md.use(markdownItInlineProps);
549
+ if (inlineSpan)
550
+ md.use(markdownItInlineSpan);
551
+ if (inlineComponent)
552
+ md.use(markdownItInlineComponent);
553
+ }
554
+ export default defineComarkPlugin((options = {}) => ({
555
+ name: 'syntax',
556
+ markdownItPlugins: [((md) => applySyntax(md, options))],
557
+ }));
558
+ export const markdownItComark = applySyntax;
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Custom task list plugin for markdown-it that works with @comark/markdown-it
2
+ * Custom task list plugin for markdown-it that integrates with the Comark syntax plugin.
3
3
  *
4
4
  * This plugin runs before inline parsing to prevent Comark from interpreting
5
5
  * task list markers [X] and [ ] as Comark inline span syntax.
6
6
  */
7
- declare const _default: import("../types.ts").ComarkPluginFactory<unknown>;
7
+ declare const _default: import("../types.ts").ComarkPluginFactory<unknown, {}, {}>;
8
8
  export default _default;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Custom task list plugin for markdown-it that works with @comark/markdown-it
2
+ * Custom task list plugin for markdown-it that integrates with the Comark syntax plugin.
3
3
  *
4
4
  * This plugin runs before inline parsing to prevent Comark from interpreting
5
5
  * task list markers [X] and [ ] as Comark inline span syntax.
@@ -35,8 +35,8 @@ function findParentList(tokens, listItemIndex) {
35
35
  const targetLevel = tokens[listItemIndex].level - 1;
36
36
  // Look backwards for the list (ul/ol) that contains this list item
37
37
  for (let i = listItemIndex - 1; i >= 0; i--) {
38
- if (tokens[i].level === targetLevel
39
- && (tokens[i].type === 'bullet_list_open' || tokens[i].type === 'ordered_list_open')) {
38
+ if (tokens[i].level === targetLevel &&
39
+ (tokens[i].type === 'bullet_list_open' || tokens[i].type === 'ordered_list_open')) {
40
40
  return i;
41
41
  }
42
42
  }
@@ -67,7 +67,7 @@ function markdownItTaskList(md, options) {
67
67
  // Replace the task marker with a placeholder that won't be processed by Comark
68
68
  // We use a special format that we can detect later
69
69
  // Keep one space after the placeholder to match expected output
70
- const checkboxPlaceholder = `{{TASK_CHECKBOX_${isChecked ? 'CHECKED' : 'UNCHECKED'}}} `;
70
+ const checkboxPlaceholder = `TASK_CHECKBOX_${isChecked ? 'CHECKED' : 'UNCHECKED'} `;
71
71
  token.content = token.content.replace(/^\[[ x]\]\s+/i, checkboxPlaceholder);
72
72
  }
73
73
  }
@@ -85,13 +85,16 @@ function markdownItTaskList(md, options) {
85
85
  const child = token.children[j];
86
86
  if (child.type === 'text' && child.content) {
87
87
  // Check for our checkbox placeholder
88
- const checkedMatch = child.content.match(/^\{\{TASK_CHECKBOX_CHECKED\}\}/);
89
- const uncheckedMatch = child.content.match(/^\{\{TASK_CHECKBOX_UNCHECKED\}\}/);
88
+ const checkedMatch = child.content.match(/^TASK_CHECKBOX_CHECKED/);
89
+ const uncheckedMatch = child.content.match(/^TASK_CHECKBOX_UNCHECKED/);
90
90
  if (checkedMatch || uncheckedMatch) {
91
91
  const isChecked = !!checkedMatch;
92
92
  // Create checkbox token
93
93
  const checkbox = new state.Token('mdc_inline_component', 'input', 0);
94
- checkbox.attrs = [['class', 'task-list-item-checkbox'], ['type', 'checkbox']];
94
+ checkbox.attrs = [
95
+ ['class', 'task-list-item-checkbox'],
96
+ ['type', 'checkbox'],
97
+ ];
95
98
  if (disableCheckboxes) {
96
99
  checkbox.attrs.push([':disabled', 'true']);
97
100
  }
@@ -99,7 +102,7 @@ function markdownItTaskList(md, options) {
99
102
  checkbox.attrs.push([':checked', 'true']);
100
103
  }
101
104
  // Remove placeholder from text
102
- child.content = child.content.replace(/^\{\{TASK_CHECKBOX_(CHECKED|UNCHECKED)\}\}/, '');
105
+ child.content = child.content.replace(/^TASK_CHECKBOX_(CHECKED|UNCHECKED)/, '');
103
106
  // Insert checkbox before the text
104
107
  token.children.splice(j, 0, checkbox);
105
108
  j++; // Skip the newly inserted checkbox
@@ -12,5 +12,7 @@ export interface Toc {
12
12
  links: TocLink[];
13
13
  }
14
14
  export declare function generateFlatToc(body: ComarkTree, options: Toc): Toc;
15
- declare const _default: import("comark").ComarkPluginFactory<Partial<Toc>>;
15
+ declare const _default: import("comark").ComarkPluginFactory<Partial<Toc>, {
16
+ toc: Toc;
17
+ }, {}>;
16
18
  export default _default;
@@ -94,7 +94,7 @@ export function generateFlatToc(body, options) {
94
94
  const tag = getTag(node);
95
95
  return tag !== null && tags.includes(tag);
96
96
  });
97
- const links = headers.map(node => ({
97
+ const links = headers.map((node) => ({
98
98
  id: getProps(node).id || '',
99
99
  depth: getHeaderDepth(node),
100
100
  text: flattenNodeText(node),