comark 0.3.1 → 0.3.2

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 (80) hide show
  1. package/dist/devtools/index.d.ts +1 -0
  2. package/dist/devtools/index.js +1 -0
  3. package/dist/devtools/register.d.ts +1 -0
  4. package/dist/devtools/register.js +1 -0
  5. package/dist/devtools/registry.d.ts +1 -0
  6. package/dist/devtools/registry.js +1 -0
  7. package/dist/devtools/vite.d.ts +1 -0
  8. package/dist/devtools/vite.js +1 -0
  9. package/dist/internal/frontmatter.d.ts +1 -0
  10. package/dist/internal/frontmatter.js +4 -2
  11. package/dist/internal/parse/auto-close/index.js +25 -13
  12. package/dist/internal/parse/auto-close/table.js +12 -9
  13. package/dist/internal/parse/auto-unwrap.js +2 -10
  14. package/dist/internal/parse/html/html_block_rule.js +1 -1
  15. package/dist/internal/parse/html/html_inline_rule.js +3 -7
  16. package/dist/internal/parse/html/html_re.js +1 -1
  17. package/dist/internal/parse/html/index.js +14 -2
  18. package/dist/internal/parse/syntax/block-params.d.ts +9 -0
  19. package/dist/internal/parse/syntax/block-params.js +48 -0
  20. package/dist/internal/parse/syntax/brackets.d.ts +8 -0
  21. package/dist/internal/parse/syntax/brackets.js +20 -0
  22. package/dist/internal/parse/syntax/props.d.ts +5 -0
  23. package/dist/internal/parse/syntax/props.js +119 -0
  24. package/dist/internal/parse/token-processor.js +25 -24
  25. package/dist/internal/props-validation.js +4 -9
  26. package/dist/internal/stringify/attributes.js +4 -1
  27. package/dist/internal/stringify/handlers/a.js +1 -3
  28. package/dist/internal/stringify/handlers/blockquote.js +2 -4
  29. package/dist/internal/stringify/handlers/code.js +1 -3
  30. package/dist/internal/stringify/handlers/emphesis.js +1 -3
  31. package/dist/internal/stringify/handlers/html.js +26 -16
  32. package/dist/internal/stringify/handlers/img.js +1 -3
  33. package/dist/internal/stringify/handlers/li.js +14 -8
  34. package/dist/internal/stringify/handlers/mdc.js +2 -3
  35. package/dist/internal/stringify/handlers/ol.js +1 -1
  36. package/dist/internal/stringify/handlers/p.d.ts +1 -1
  37. package/dist/internal/stringify/handlers/p.js +4 -1
  38. package/dist/internal/stringify/handlers/pre.js +10 -13
  39. package/dist/internal/stringify/handlers/strong.js +1 -3
  40. package/dist/internal/stringify/handlers/table.js +7 -5
  41. package/dist/internal/stringify/handlers/template.js +1 -1
  42. package/dist/internal/stringify/handlers/ul.js +1 -1
  43. package/dist/internal/stringify/indent.d.ts +1 -5
  44. package/dist/internal/stringify/indent.js +1 -9
  45. package/dist/internal/stringify/state.js +1 -1
  46. package/dist/internal/yaml.js +1 -1
  47. package/dist/parse.js +14 -8
  48. package/dist/plugins/alert.js +1 -1
  49. package/dist/plugins/binding.js +1 -3
  50. package/dist/plugins/breaks.js +1 -1
  51. package/dist/plugins/emoji.js +8 -8
  52. package/dist/plugins/footnotes.js +19 -13
  53. package/dist/plugins/headings.js +2 -4
  54. package/dist/plugins/highlight.d.ts +1 -11
  55. package/dist/plugins/highlight.js +198 -103
  56. package/dist/plugins/json-render.js +5 -9
  57. package/dist/plugins/math.js +4 -6
  58. package/dist/plugins/mermaid.js +6 -20
  59. package/dist/plugins/punctuation.js +5 -6
  60. package/dist/plugins/security.js +2 -2
  61. package/dist/plugins/syntax.d.ts +49 -0
  62. package/dist/plugins/syntax.js +522 -0
  63. package/dist/plugins/task-list.d.ts +1 -1
  64. package/dist/plugins/task-list.js +11 -8
  65. package/dist/plugins/toc.js +1 -1
  66. package/dist/types.d.ts +1 -0
  67. package/dist/utils/comark.tmLanguage.d.ts +335 -0
  68. package/dist/utils/comark.tmLanguage.js +597 -0
  69. package/dist/utils/helpers.js +1 -3
  70. package/dist/utils/index.d.ts +5 -0
  71. package/dist/utils/index.js +25 -3
  72. package/package.json +39 -40
  73. package/skills/skills/comark/AGENTS.md +0 -261
  74. package/skills/skills/comark/SKILL.md +0 -489
  75. package/skills/skills/comark/references/markdown-syntax.md +0 -599
  76. package/skills/skills/comark/references/parsing-ast.md +0 -378
  77. package/skills/skills/comark/references/rendering-react.md +0 -445
  78. package/skills/skills/comark/references/rendering-svelte.md +0 -453
  79. package/skills/skills/comark/references/rendering-vue.md +0 -462
  80. /package/skills/{skills/migrate-mdc-to-comark → migrate-mdc-to-comark}/SKILL.md +0 -0
@@ -0,0 +1 @@
1
+ export * from '../../src/devtools/index'
@@ -0,0 +1 @@
1
+ export * from '../../src/devtools/index.ts'
@@ -0,0 +1 @@
1
+ export * from '../../src/devtools/register'
@@ -0,0 +1 @@
1
+ export * from '../../src/devtools/register.ts'
@@ -0,0 +1 @@
1
+ export * from '../../src/devtools/registry'
@@ -0,0 +1 @@
1
+ export * from '../../src/devtools/registry.ts'
@@ -0,0 +1 @@
1
+ export * from '../../src/devtools/vite'
@@ -0,0 +1 @@
1
+ export * from '../../src/devtools/vite.ts'
@@ -7,6 +7,7 @@ import type { DumpOptions } from 'js-yaml';
7
7
  export declare function parseFrontmatter(content: string): {
8
8
  content: string;
9
9
  data: Record<string, any>;
10
+ frontmatterText: string;
10
11
  };
11
12
  /**
12
13
  * Render frontmatter to content
@@ -9,11 +9,12 @@ const CR = '\r';
9
9
  */
10
10
  export function parseFrontmatter(content) {
11
11
  let data = {};
12
+ let frontmatter = '';
12
13
  if (content.startsWith(FRONTMATTER_DELIMITER_DEFAULT)) {
13
14
  const idx = content.indexOf(LF + FRONTMATTER_DELIMITER_DEFAULT);
14
15
  if (idx !== -1) {
15
16
  const hasCarriageReturn = content[idx - 1] === CR;
16
- const frontmatter = content.slice(4, idx - (hasCarriageReturn ? 1 : 0));
17
+ frontmatter = content.slice(4, idx - (hasCarriageReturn ? 1 : 0));
17
18
  if (frontmatter) {
18
19
  data = parseYaml(frontmatter);
19
20
  content = content.slice(idx + 4 + (hasCarriageReturn ? 1 : 0));
@@ -23,6 +24,7 @@ export function parseFrontmatter(content) {
23
24
  return {
24
25
  content,
25
26
  data,
27
+ frontmatterText: frontmatter,
26
28
  };
27
29
  }
28
30
  /**
@@ -33,7 +35,7 @@ export function parseFrontmatter(content) {
33
35
  */
34
36
  export function renderFrontmatter(data, content, yamlOptions) {
35
37
  if (!data || Object.keys(data).length === 0) {
36
- return (content?.trim() || '');
38
+ return content?.trim() || '';
37
39
  }
38
40
  const fm = stringifyYaml(data, yamlOptions).trim();
39
41
  if (content) {
@@ -77,11 +77,22 @@ export function autoCloseMarkdown(markdown) {
77
77
  let nameEnd = colonCount;
78
78
  while (nameEnd < trimmed.length) {
79
79
  const c = trimmed[nameEnd];
80
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c === '$' || c === '.' || c === '-' || c === '_'))
80
+ if (!((c >= 'a' && c <= 'z') ||
81
+ (c >= 'A' && c <= 'Z') ||
82
+ (c >= '0' && c <= '9') ||
83
+ c === '$' ||
84
+ c === '.' ||
85
+ c === '-' ||
86
+ c === '_'))
81
87
  break;
82
88
  nameEnd++;
83
89
  }
84
- componentStack.push({ depth: colonCount, name: trimmed.slice(colonCount, nameEnd), indent, hasYamlProps: false });
90
+ componentStack.push({
91
+ depth: colonCount,
92
+ name: trimmed.slice(colonCount, nameEnd),
93
+ indent,
94
+ hasYamlProps: false,
95
+ });
85
96
  }
86
97
  else if (colonCount === trimmed.length && componentStack.length > 0) {
87
98
  const top = componentStack[componentStack.length - 1];
@@ -136,14 +147,14 @@ export function autoCloseMarkdown(markdown) {
136
147
  for (let i = 0; i < propsContent.length; i++) {
137
148
  if (propsContent[i] === '"')
138
149
  dq++;
139
- if (propsContent[i] === '\'')
150
+ if (propsContent[i] === "'")
140
151
  sq++;
141
152
  }
142
153
  let braceClose = '';
143
154
  if (dq % 2 === 1)
144
155
  braceClose += '"';
145
156
  if (sq % 2 === 1)
146
- braceClose += '\'';
157
+ braceClose += "'";
147
158
  result += braceClose + '}';
148
159
  }
149
160
  if (componentStack.length > 0) {
@@ -299,8 +310,11 @@ function closeInlineMarkersLinear(line) {
299
310
  // Check if line starts with more than 3 asterisks (e.g., ****)
300
311
  if (!(line[3] === '*')) {
301
312
  // Check if marker at end with no content
302
- if (!(contentEnd >= 3 && line[contentEnd - 1] === '*' && line[contentEnd - 2] === '*'
303
- && line[contentEnd - 3] === '*' && (contentEnd === 3 || line[contentEnd - 4] === ' '))) {
313
+ if (!(contentEnd >= 3 &&
314
+ line[contentEnd - 1] === '*' &&
315
+ line[contentEnd - 2] === '*' &&
316
+ line[contentEnd - 3] === '*' &&
317
+ (contentEnd === 3 || line[contentEnd - 4] === ' '))) {
304
318
  closingSuffix = '***';
305
319
  }
306
320
  }
@@ -333,9 +347,9 @@ function closeInlineMarkersLinear(line) {
333
347
  if (!allPaired) {
334
348
  // Check if line ends with word (not just a closing marker)
335
349
  const lastChar = line[contentEnd - 1];
336
- const endsWithWord = (lastChar >= 'a' && lastChar <= 'z')
337
- || (lastChar >= 'A' && lastChar <= 'Z')
338
- || (lastChar >= '0' && lastChar <= '9');
350
+ const endsWithWord = (lastChar >= 'a' && lastChar <= 'z') ||
351
+ (lastChar >= 'A' && lastChar <= 'Z') ||
352
+ (lastChar >= '0' && lastChar <= '9');
339
353
  if (!hasCompleteBoldPair || endsWithWord) {
340
354
  closingSuffix = '**';
341
355
  if (hasTrailingSpace && !endsWithMarker) {
@@ -375,8 +389,7 @@ function closeInlineMarkersLinear(line) {
375
389
  // Check marker at end with no content
376
390
  // Only skip if it's truly isolated (e.g., "input *")
377
391
  // Don't skip if there are complete pairs before it (e.g., "input **bold** *")
378
- const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '*'
379
- && (contentEnd === 1 || line[contentEnd - 2] === ' ');
392
+ const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '*' && (contentEnd === 1 || line[contentEnd - 2] === ' ');
380
393
  if (!markerAtEnd || asteriskCount > 1) {
381
394
  closingSuffix = '*';
382
395
  const endsWithMarker = line[contentEnd - 1] === '*';
@@ -427,8 +440,7 @@ function closeInlineMarkersLinear(line) {
427
440
  }
428
441
  if (validItalic) {
429
442
  // Check marker at end with no content
430
- const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '_'
431
- && (contentEnd === 1 || line[contentEnd - 2] === ' ');
443
+ const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '_' && (contentEnd === 1 || line[contentEnd - 2] === ' ');
432
444
  if (!markerAtEnd) {
433
445
  closingSuffix = '_';
434
446
  const endsWithMarker = line[contentEnd - 1] === '_';
@@ -87,12 +87,10 @@ export function closeTables(markdown) {
87
87
  const generateSeparator = () => '| ' + Array(columnCount).fill('---').join(' | ') + ' |';
88
88
  // Check if separator exists (including incomplete ones with just :)
89
89
  const secondLine = end - start >= 1 ? lines[start + 1].trim() : '';
90
- const hasSeparator = secondLine.startsWith('|')
91
- && (secondLine.includes('-') || secondLine.includes(':'));
90
+ const hasSeparator = secondLine.startsWith('|') && (secondLine.includes('-') || secondLine.includes(':'));
92
91
  // Handle last line
93
92
  const lastLine = lines[end].trim();
94
- const isSeparator = lastLine.startsWith('|')
95
- && (lastLine.includes('-') || lastLine.includes(':'));
93
+ const isSeparator = lastLine.startsWith('|') && (lastLine.includes('-') || lastLine.includes(':'));
96
94
  if (isSeparator) {
97
95
  // Parse and complete separator cells
98
96
  const sepCells = parseCells(lastLine);
@@ -147,11 +145,16 @@ export function closeTables(markdown) {
147
145
  const refWidths = parseCellWidths(refRow);
148
146
  const cells = parseCells(lastLine);
149
147
  // Rebuild with padding
150
- lines[end] = '| ' + cells.map((cell, i) => {
151
- const targetWidth = refWidths[i] || cell.length + 2;
152
- const padding = ' '.repeat(Math.max(0, targetWidth - cell.length - 2));
153
- return cell + padding;
154
- }).join(' | ') + ' |';
148
+ lines[end] =
149
+ '| ' +
150
+ cells
151
+ .map((cell, i) => {
152
+ const targetWidth = refWidths[i] || cell.length + 2;
153
+ const padding = ' '.repeat(Math.max(0, targetWidth - cell.length - 2));
154
+ return cell + padding;
155
+ })
156
+ .join(' | ') +
157
+ ' |';
155
158
  }
156
159
  // Add separator if missing
157
160
  if (!hasSeparator) {
@@ -28,15 +28,7 @@ export function applyAutoUnwrap(node) {
28
28
  }
29
29
  // Check if we have exactly one paragraph child (and possibly empty text nodes)
30
30
  if (nonEmptyChildren.length > 1 || typeof nonEmptyChildren[0] === 'string' || nonEmptyChildren[0][0] !== 'p') {
31
- return [
32
- tag,
33
- props,
34
- ...children.map((child) => applyAutoUnwrap(child)),
35
- ];
31
+ return [tag, props, ...children.map((child) => applyAutoUnwrap(child))];
36
32
  }
37
- return [
38
- tag,
39
- props,
40
- ...nonEmptyChildren[0].slice(2),
41
- ];
33
+ return [tag, props, ...nonEmptyChildren[0].slice(2)];
42
34
  }
@@ -20,7 +20,7 @@ export default function html_block(state, startLine, endLine, silent) {
20
20
  // if it's indented more than 3 spaces, it should be a code block
21
21
  if (state.sCount[startLine] - state.blkIndent >= 4)
22
22
  return false;
23
- if (state.src.charCodeAt(pos) !== 0x3C /* < */)
23
+ if (state.src.charCodeAt(pos) !== 0x3c /* < */)
24
24
  return false;
25
25
  let lineText = state.src.slice(pos, max);
26
26
  let i = 0;
@@ -9,22 +9,18 @@ function isLinkClose(str) {
9
9
  function isLetter(ch) {
10
10
  /* eslint no-bitwise:0 */
11
11
  const lc = ch | 0x20; // to lower case
12
- return (lc >= 0x61 /* a */) && (lc <= 0x7A /* z */);
12
+ return lc >= 0x61 /* a */ && lc <= 0x7a; /* z */
13
13
  }
14
14
  export default function html_inline(state, silent) {
15
15
  // Check start
16
16
  const max = state.posMax;
17
17
  const pos = state.pos;
18
- if (state.src.charCodeAt(pos) !== 0x3C
19
- || /* < */ pos + 2 >= max) {
18
+ if (state.src.charCodeAt(pos) !== 0x3c || /* < */ pos + 2 >= max) {
20
19
  return false;
21
20
  }
22
21
  // Quick fail on second char
23
22
  const ch = state.src.charCodeAt(pos + 1);
24
- if (ch !== 0x21
25
- && /* ! */ ch !== 0x3F
26
- && /* ? */ ch !== 0x2F
27
- && /* / */ !isLetter(ch)) {
23
+ if (ch !== 0x21 && /* ! */ ch !== 0x3f && /* ? */ ch !== 0x2f && /* / */ !isLetter(ch)) {
28
24
  return false;
29
25
  }
30
26
  const match = state.src.slice(pos).match(HTML_TAG_RE);
@@ -1,7 +1,7 @@
1
1
  // Regexps to match html elements
2
2
  const attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*';
3
3
  const unquoted = '[^"\'=<>`\\x00-\\x20]+';
4
- const single_quoted = '\'[^\']*\'';
4
+ const single_quoted = "'[^']*'";
5
5
  const double_quoted = '"[^"]*"';
6
6
  const attr_value = `(?:${unquoted}|${single_quoted}|${double_quoted})`;
7
7
  const attribute = `(?:\\s+${attr_name}(?:\\s*=\\s*${attr_value})?)`;
@@ -1,7 +1,19 @@
1
1
  import { Parser } from 'htmlparser2';
2
2
  const VOID_ELEMENTS = new Set([
3
- 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
4
- 'link', 'meta', 'param', 'source', 'track', 'wbr',
3
+ 'area',
4
+ 'base',
5
+ 'br',
6
+ 'col',
7
+ 'embed',
8
+ 'hr',
9
+ 'img',
10
+ 'input',
11
+ 'link',
12
+ 'meta',
13
+ 'param',
14
+ 'source',
15
+ 'track',
16
+ 'wbr',
5
17
  ]);
6
18
  function attribsToComarkAttrs(attribs, isInline = false) {
7
19
  const attrs = {
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Parse `component-name [content] {.params}` from the trailing portion of a block fence line.
3
+ */
4
+ export declare function parseBlockParams(str: string): {
5
+ name: string;
6
+ content?: string;
7
+ props?: [string, string][];
8
+ remaining?: string;
9
+ };
@@ -0,0 +1,48 @@
1
+ import { kebabCase } from "../../../utils/index.js";
2
+ import { parseBracketContent } from "./brackets.js";
3
+ import { searchProps } from "./props.js";
4
+ const RE_BLOCK_NAME = /^[a-z$][$\w.-]*/i;
5
+ /**
6
+ * Parse `component-name [content] {.params}` from the trailing portion of a block fence line.
7
+ */
8
+ export function parseBlockParams(str) {
9
+ str = str.trim();
10
+ if (!str)
11
+ return { name: '' };
12
+ const name = str.match(RE_BLOCK_NAME)?.[0];
13
+ if (!name)
14
+ throw new Error(`Invalid block params: ${str}`);
15
+ let remaining = str.slice(name.length).trim();
16
+ let content;
17
+ let props;
18
+ let unparsedRemaining;
19
+ if (remaining.startsWith('[')) {
20
+ const result = parseBracketContent(remaining, 0);
21
+ if (result) {
22
+ content = result.content;
23
+ remaining = remaining.slice(result.endIndex).trim();
24
+ }
25
+ }
26
+ if (remaining.startsWith('{')) {
27
+ const propsResult = searchProps(remaining, 0);
28
+ if (propsResult) {
29
+ props = propsResult.props;
30
+ const afterProps = remaining.slice(propsResult.index).trim();
31
+ if (afterProps)
32
+ unparsedRemaining = afterProps;
33
+ }
34
+ }
35
+ else if (remaining) {
36
+ unparsedRemaining = remaining;
37
+ }
38
+ const result = {
39
+ name: kebabCase(name),
40
+ };
41
+ if (content !== undefined)
42
+ result.content = content;
43
+ if (props !== undefined)
44
+ result.props = props;
45
+ if (unparsedRemaining)
46
+ result.remaining = unparsedRemaining;
47
+ return result;
48
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Parse content within square brackets `[content]`.
3
+ * Returns the content (without the brackets) and the index just past the closing `]`.
4
+ */
5
+ export declare function parseBracketContent(str: string, startIndex: number): {
6
+ content: string;
7
+ endIndex: number;
8
+ } | null;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Parse content within square brackets `[content]`.
3
+ * Returns the content (without the brackets) and the index just past the closing `]`.
4
+ */
5
+ export function parseBracketContent(str, startIndex) {
6
+ if (str[startIndex] !== '[')
7
+ return null;
8
+ let index = startIndex + 1;
9
+ while (index < str.length) {
10
+ if (str[index] === '\\' && index + 1 < str.length) {
11
+ index += 2;
12
+ continue;
13
+ }
14
+ if (str[index] === ']') {
15
+ return { content: str.slice(startIndex + 1, index), endIndex: index + 1 };
16
+ }
17
+ index += 1;
18
+ }
19
+ return null;
20
+ }
@@ -0,0 +1,5 @@
1
+ export declare function parseProps(content: string): [string, string][] | undefined;
2
+ export declare function searchProps(content: string, index?: number): {
3
+ props: [string, string][];
4
+ index: number;
5
+ } | undefined;
@@ -0,0 +1,119 @@
1
+ const bracketPairs = {
2
+ '[': ']',
3
+ '{': '}',
4
+ '(': ')',
5
+ };
6
+ const quotePairs = {
7
+ "'": "'",
8
+ '"': '"',
9
+ '`': '`',
10
+ };
11
+ export function parseProps(content) {
12
+ content = content.trim();
13
+ if (!content)
14
+ return undefined;
15
+ const props = searchProps(content);
16
+ if (!props)
17
+ throw new Error(`Invalid props: \`${content}\``);
18
+ if (props.index !== content.length)
19
+ throw new Error(`Invalid props: \`${content}\`, expected end \`}\` but got \`${content.slice(props.index)}\``);
20
+ return props.props;
21
+ }
22
+ export function searchProps(content, index = 0) {
23
+ if (content[index] !== '{')
24
+ throw new Error(`Invalid props, expected \`{\` but got '${content[index]}'`);
25
+ const props = [];
26
+ // Skip Vue mustache `{{ }}` syntax
27
+ if (content[index + 1] === '{')
28
+ return undefined;
29
+ index += 1;
30
+ while (index < content.length) {
31
+ if (content[index] === '\\') {
32
+ index += 2;
33
+ }
34
+ else if (content[index] === '}') {
35
+ index += 1;
36
+ break;
37
+ }
38
+ else if (content[index] === ' ') {
39
+ index += 1;
40
+ }
41
+ else if (content[index] === '.') {
42
+ index += 1;
43
+ props.push(['class', searchUntil(' #.}')]);
44
+ }
45
+ else if (content[index] === '#') {
46
+ index += 1;
47
+ props.push(['id', searchUntil(' #.}')]);
48
+ }
49
+ else {
50
+ const start = index;
51
+ while (index < content.length) {
52
+ index += 1;
53
+ if (' }='.includes(content[index]))
54
+ break;
55
+ }
56
+ const char = content[index];
57
+ if (start !== index) {
58
+ const key = content.slice(start, index).trim();
59
+ if (char === '=') {
60
+ index += 1;
61
+ props.push([key, searchValue()]);
62
+ }
63
+ else {
64
+ props.push([key, 'true']);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ function searchUntil(str) {
70
+ const start = index;
71
+ while (index < content.length) {
72
+ index += 1;
73
+ if (content[index] === '\\')
74
+ index += 2;
75
+ if (str.includes(content[index]))
76
+ break;
77
+ }
78
+ return content.slice(start, index);
79
+ }
80
+ function searchValue() {
81
+ const start = index;
82
+ if (content[index] in bracketPairs) {
83
+ searchBracket(bracketPairs[content[index]]);
84
+ index += 1;
85
+ return content.slice(start, index);
86
+ }
87
+ else if (content[index] in quotePairs) {
88
+ searchString(quotePairs[content[index]]);
89
+ index += 1;
90
+ return content.slice(start, index);
91
+ }
92
+ else {
93
+ return searchUntil(' }');
94
+ }
95
+ }
96
+ function searchBracket(end) {
97
+ while (index < content.length) {
98
+ index++;
99
+ if (content[index] in quotePairs)
100
+ searchString(quotePairs[content[index]]);
101
+ else if (content[index] in bracketPairs)
102
+ searchBracket(bracketPairs[content[index]]);
103
+ else if (content[index] === end)
104
+ return;
105
+ }
106
+ }
107
+ function searchString(end) {
108
+ return searchUntil(end);
109
+ }
110
+ // Strip surrounding quotes from values
111
+ props.forEach((v) => {
112
+ if (/^(['"`]).*\1$/.test(v[1]))
113
+ v[1] = v[1].slice(1, -1);
114
+ });
115
+ return {
116
+ props,
117
+ index,
118
+ };
119
+ }
@@ -38,12 +38,11 @@ export function marmdownItTokensToComarkTree(tokens, options = { startLine: 0, p
38
38
  if (options.preservePositions) {
39
39
  for (let j = i; j < result.nextIndex; j++) {
40
40
  if (tokens[j].map && tokens[j].map[1]) {
41
- endLine = tokens[j].map[1]
42
- + options.startLine
43
- + (tokens[j].type?.endsWith('_close') ? 1 : 0);
41
+ endLine = tokens[j].map[1] + options.startLine + (tokens[j].type?.endsWith('_close') ? 1 : 0);
44
42
  }
45
43
  }
46
44
  if (!result.node[1].$) {
45
+ ;
47
46
  result.node[1].$ = {};
48
47
  }
49
48
  ;
@@ -73,7 +72,11 @@ function processAttributes(attrsArray, options = {}) {
73
72
  continue;
74
73
  }
75
74
  // Handle boolean attributes: {bool} -> {":bool": "true"}
76
- if (handleBoolean && !key.startsWith(':') && !key.startsWith('#') && !key.startsWith('.') && (!value || value === 'true' || value === '')) {
75
+ if (handleBoolean &&
76
+ !key.startsWith(':') &&
77
+ !key.startsWith('#') &&
78
+ !key.startsWith('.') &&
79
+ (!value || value === 'true' || value === '')) {
77
80
  attrs[`:${key}`] = 'true';
78
81
  continue;
79
82
  }
@@ -140,7 +143,7 @@ function parseCodeblockInfo(info) {
140
143
  const trimmed = part.trim();
141
144
  if (trimmed.includes('-')) {
142
145
  // Range like "1-3"
143
- const [start, end] = trimmed.split('-').map(s => Number.parseInt(s.trim(), 10));
146
+ const [start, end] = trimmed.split('-').map((s) => Number.parseInt(s.trim(), 10));
144
147
  if (!Number.isNaN(start) && !Number.isNaN(end)) {
145
148
  for (let i = start; i <= end; i++) {
146
149
  highlights.push(i);
@@ -271,7 +274,10 @@ function processBlockToken(tokens, startIndex, insideNestedContext = false, stat
271
274
  return { node: [componentName, attrs, ...children], nextIndex: nextIndex };
272
275
  }
273
276
  if (token.type === 'math_block') {
274
- return { node: ['math', { class: 'math block', content: token.content }, token.content], nextIndex: startIndex + 1 };
277
+ return {
278
+ node: ['math', { class: 'math block', content: token.content }, token.content],
279
+ nextIndex: startIndex + 1,
280
+ };
275
281
  }
276
282
  if (token.type === 'fence' || token.type === 'fenced_code_block' || token.type === 'code_block') {
277
283
  const content = token.content || '';
@@ -312,7 +318,10 @@ function processBlockToken(tokens, startIndex, insideNestedContext = false, stat
312
318
  const textContent = extractTextContent(children.nodes);
313
319
  const headingId = uniqueSlug(slugify(textContent), level, state);
314
320
  // Always attach ID to the heading element itself
315
- return { node: [headingTag, { id: headingId }, ...children.nodes], nextIndex: children.nextIndex + 1 };
321
+ return {
322
+ node: [headingTag, { id: headingId }, ...children.nodes],
323
+ nextIndex: children.nextIndex + 1,
324
+ };
316
325
  }
317
326
  return { node: null, nextIndex: children.nextIndex + 1 };
318
327
  }
@@ -320,19 +329,8 @@ function processBlockToken(tokens, startIndex, insideNestedContext = false, stat
320
329
  if (token.type === 'list_item_open') {
321
330
  const attrs = processAttributes(token.attrs, { handleBoolean: false, handleJSON: false });
322
331
  const children = processBlockChildren(tokens, startIndex + 1, 'list_item_close', false, false, true, state);
323
- // Unwrap paragraphs in list items
324
- const unwrapped = [];
325
- for (const child of children.nodes) {
326
- if (Array.isArray(child) && child[0] === 'p') {
327
- // Unwrap paragraph, add its children directly
328
- unwrapped.push(...child.slice(2));
329
- }
330
- else {
331
- unwrapped.push(child);
332
- }
333
- }
334
- if (unwrapped.length > 0) {
335
- return { node: ['li', attrs, ...unwrapped], nextIndex: children.nextIndex + 1 };
332
+ if (children.nodes.length > 0) {
333
+ return { node: ['li', attrs, ...children.nodes], nextIndex: children.nextIndex + 1 };
336
334
  }
337
335
  return { node: null, nextIndex: children.nextIndex + 1 };
338
336
  }
@@ -615,7 +613,7 @@ function processInlineToken(tokens, startIndex, inHeading = false) {
615
613
  return { node, nextIndex: j };
616
614
  }
617
615
  // Handle Comark inline span (e.g., [text]{attr})
618
- // @comark/markdown-it creates mdc_inline_span tokens, and props appear AFTER the close token
616
+ // The syntax plugin emits mdc_inline_span tokens, and props appear AFTER the close token
619
617
  if (token.type === 'mdc_inline_span' && token.nesting === 1) {
620
618
  const attrs = {};
621
619
  let i = startIndex + 1;
@@ -698,11 +696,11 @@ function processInlineToken(tokens, startIndex, inHeading = false) {
698
696
  else {
699
697
  // Self-closing component (nesting === 0)
700
698
  const attrs = {};
701
- // @comark/markdown-it stores attributes in a separate mdc_inline_props token
699
+ // The syntax plugin stores attributes in a separate mdc_inline_props token
702
700
  // that appears right after the component token
703
701
  const { attrs: componentAttrs, nextIndex: propsNextIndex } = extractAttributes(tokens, startIndex + 1, false);
704
702
  Object.assign(attrs, componentAttrs);
705
- // Extract attributes from token.attrs (fallback, though @comark/markdown-it uses mdc_inline_props)
703
+ // Extract attributes from token.attrs (fallback, though the syntax plugin uses mdc_inline_props)
706
704
  const fallbackAttrs = processAttributes(token.attrs, { handleBoolean: false });
707
705
  Object.assign(attrs, fallbackAttrs);
708
706
  // Return the component without any text children
@@ -734,7 +732,10 @@ function processInlineToken(tokens, startIndex, inHeading = false) {
734
732
  return { node: null, nextIndex };
735
733
  }
736
734
  if (token.type === 'math_inline') {
737
- return { node: ['math', { class: 'math inline', content: token.content }, token.content], nextIndex: startIndex + 1 };
735
+ return {
736
+ node: ['math', { class: 'math inline', content: token.content }, token.content],
737
+ nextIndex: startIndex + 1,
738
+ };
738
739
  }
739
740
  // Handle generic inline open/close pairs
740
741
  const tagName = INLINE_TAG_MAP[token.type];
@@ -1,10 +1,5 @@
1
- export const unsafeTags = [
2
- 'object',
3
- ];
4
- export const unsafeAttributes = [
5
- 'srcdoc',
6
- 'formaction',
7
- ];
1
+ export const unsafeTags = ['object'];
2
+ export const unsafeAttributes = ['srcdoc', 'formaction'];
8
3
  export const unsafeLinkPrefix = [
9
4
  'javascript:',
10
5
  'data:text/html',
@@ -44,7 +39,7 @@ function validateUrl(value, mode, options) {
44
39
  return value;
45
40
  }
46
41
  // Block known-unsafe protocols — hard floor, not overrideable by options
47
- if (unsafeLinkPrefix.some(prefix => url.href.toLowerCase().startsWith(prefix))) {
42
+ if (unsafeLinkPrefix.some((prefix) => url.href.toLowerCase().startsWith(prefix))) {
48
43
  return false;
49
44
  }
50
45
  // Block data: images when allowDataImages is false
@@ -62,7 +57,7 @@ function validateUrl(value, mode, options) {
62
57
  const allowedPrefixes = mode === 'link' ? allowedLinkPrefixes : allowedImagePrefixes;
63
58
  if (!allowedPrefixes.includes('*')) {
64
59
  const href = url.href.toLowerCase();
65
- const matchesPrefix = allowedPrefixes.some(prefix => href.startsWith(prefix.toLowerCase()));
60
+ const matchesPrefix = allowedPrefixes.some((prefix) => href.startsWith(prefix.toLowerCase()));
66
61
  if (!matchesPrefix) {
67
62
  if (defaultOrigin) {
68
63
  return rewriteToDefaultOrigin(urlSanitized, defaultOrigin);