comark 0.3.0 → 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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/dist/devtools/index.d.ts +1 -0
  3. package/dist/devtools/index.js +1 -0
  4. package/dist/devtools/register.d.ts +1 -0
  5. package/dist/devtools/register.js +1 -0
  6. package/dist/devtools/registry.d.ts +1 -0
  7. package/dist/devtools/registry.js +1 -0
  8. package/dist/devtools/vite.d.ts +1 -0
  9. package/dist/devtools/vite.js +1 -0
  10. package/dist/internal/frontmatter.d.ts +1 -0
  11. package/dist/internal/frontmatter.js +4 -2
  12. package/dist/internal/parse/auto-close/index.js +25 -13
  13. package/dist/internal/parse/auto-close/table.js +12 -9
  14. package/dist/internal/parse/auto-unwrap.js +2 -10
  15. package/dist/internal/parse/html/html_block_rule.js +1 -1
  16. package/dist/internal/parse/html/html_inline_rule.js +3 -7
  17. package/dist/internal/parse/html/html_re.js +1 -1
  18. package/dist/internal/parse/html/index.js +14 -2
  19. package/dist/internal/parse/syntax/block-params.d.ts +9 -0
  20. package/dist/internal/parse/syntax/block-params.js +48 -0
  21. package/dist/internal/parse/syntax/brackets.d.ts +8 -0
  22. package/dist/internal/parse/syntax/brackets.js +20 -0
  23. package/dist/internal/parse/syntax/props.d.ts +5 -0
  24. package/dist/internal/parse/syntax/props.js +119 -0
  25. package/dist/internal/parse/token-processor.js +25 -24
  26. package/dist/internal/props-validation.js +4 -9
  27. package/dist/internal/stringify/attributes.js +4 -1
  28. package/dist/internal/stringify/handlers/a.js +1 -3
  29. package/dist/internal/stringify/handlers/blockquote.js +2 -4
  30. package/dist/internal/stringify/handlers/code.js +1 -3
  31. package/dist/internal/stringify/handlers/emphesis.js +1 -3
  32. package/dist/internal/stringify/handlers/html.js +26 -16
  33. package/dist/internal/stringify/handlers/img.js +1 -3
  34. package/dist/internal/stringify/handlers/li.js +14 -8
  35. package/dist/internal/stringify/handlers/mdc.js +2 -3
  36. package/dist/internal/stringify/handlers/ol.js +1 -1
  37. package/dist/internal/stringify/handlers/p.d.ts +1 -1
  38. package/dist/internal/stringify/handlers/p.js +4 -1
  39. package/dist/internal/stringify/handlers/pre.js +10 -13
  40. package/dist/internal/stringify/handlers/strong.js +1 -3
  41. package/dist/internal/stringify/handlers/table.js +7 -5
  42. package/dist/internal/stringify/handlers/template.js +1 -1
  43. package/dist/internal/stringify/handlers/ul.js +1 -1
  44. package/dist/internal/stringify/indent.d.ts +1 -5
  45. package/dist/internal/stringify/indent.js +1 -9
  46. package/dist/internal/stringify/state.js +1 -1
  47. package/dist/internal/yaml.js +1 -1
  48. package/dist/parse.js +14 -8
  49. package/dist/plugins/alert.js +1 -1
  50. package/dist/plugins/binding.js +1 -3
  51. package/dist/plugins/breaks.js +1 -1
  52. package/dist/plugins/emoji.js +8 -8
  53. package/dist/plugins/footnotes.js +19 -13
  54. package/dist/plugins/headings.js +2 -4
  55. package/dist/plugins/highlight.d.ts +1 -11
  56. package/dist/plugins/highlight.js +198 -103
  57. package/dist/plugins/json-render.js +5 -9
  58. package/dist/plugins/math.js +4 -6
  59. package/dist/plugins/mermaid.js +6 -20
  60. package/dist/plugins/punctuation.js +5 -6
  61. package/dist/plugins/security.js +2 -2
  62. package/dist/plugins/syntax.d.ts +49 -0
  63. package/dist/plugins/syntax.js +522 -0
  64. package/dist/plugins/task-list.d.ts +1 -1
  65. package/dist/plugins/task-list.js +11 -8
  66. package/dist/plugins/toc.js +1 -1
  67. package/dist/types.d.ts +1 -0
  68. package/dist/utils/comark.tmLanguage.d.ts +335 -0
  69. package/dist/utils/comark.tmLanguage.js +597 -0
  70. package/dist/utils/helpers.js +1 -3
  71. package/dist/utils/index.d.ts +5 -0
  72. package/dist/utils/index.js +25 -3
  73. package/package.json +49 -51
  74. package/skills/skills/comark/AGENTS.md +0 -261
  75. package/skills/skills/comark/SKILL.md +0 -489
  76. package/skills/skills/comark/references/markdown-syntax.md +0 -599
  77. package/skills/skills/comark/references/parsing-ast.md +0 -378
  78. package/skills/skills/comark/references/rendering-react.md +0 -445
  79. package/skills/skills/comark/references/rendering-svelte.md +0 -453
  80. package/skills/skills/comark/references/rendering-vue.md +0 -462
  81. /package/skills/{skills/migrate-mdc-to-comark → migrate-mdc-to-comark}/SKILL.md +0 -0
@@ -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);
@@ -83,7 +83,10 @@ export function comarkAttributes(attributes) {
83
83
  return `#${value}`;
84
84
  }
85
85
  if (key === 'class') {
86
- return value.split(' ').map(c => `.${c}`).join('');
86
+ return value
87
+ .split(' ')
88
+ .map((c) => `.${c}`)
89
+ .join('');
87
90
  }
88
91
  if (typeof value === 'object') {
89
92
  return `${key}="${JSON.stringify(value).replace(/"/g, '\\"')}"`;
@@ -3,9 +3,7 @@ import { comarkAttributes } from "../attributes.js";
3
3
  export async function a(node, state) {
4
4
  const [_, attrs] = node;
5
5
  const { href, ...rest } = attrs;
6
- const attrsString = Object.keys(rest).length > 0
7
- ? comarkAttributes(rest)
8
- : '';
6
+ const attrsString = Object.keys(rest).length > 0 ? comarkAttributes(rest) : '';
9
7
  const content = await state.flow(node, state);
10
8
  if (content === href && !attrsString) {
11
9
  return `<${href}>`;
@@ -7,12 +7,10 @@ export async function blockquote(node, state) {
7
7
  const content = childResult
8
8
  .trim()
9
9
  .split('\n')
10
- .map(line => line ? `> ${line}` : '>')
10
+ .map((line) => (line ? `> ${line}` : '>'))
11
11
  .join('\n');
12
12
  if (node[1].as) {
13
- return `> [!${String(node[1].as).toUpperCase()}]\n`
14
- + content
15
- + state.context.blockSeparator;
13
+ return `> [!${String(node[1].as).toUpperCase()}]\n` + content + state.context.blockSeparator;
16
14
  }
17
15
  return content + state.context.blockSeparator;
18
16
  }
@@ -2,9 +2,7 @@ import { comarkAttributes } from "../attributes.js";
2
2
  import { textContent } from "../../../utils/index.js";
3
3
  export function code(node, _state) {
4
4
  const [_, attrs] = node;
5
- const attrsString = Object.keys(attrs).length > 0
6
- ? comarkAttributes(attrs)
7
- : '';
5
+ const attrsString = Object.keys(attrs).length > 0 ? comarkAttributes(attrs) : '';
8
6
  const content = textContent(node);
9
7
  const fence = content.includes('`') ? '``' : '`';
10
8
  return `${fence}${content}${fence}${attrsString}`;
@@ -6,8 +6,6 @@ export async function emphesis(node, state) {
6
6
  content += await state.one(child, state, node);
7
7
  }
8
8
  content = content.trim();
9
- const attrsString = Object.keys(attrs).length > 0
10
- ? comarkAttributes(attrs)
11
- : '';
9
+ const attrsString = Object.keys(attrs).length > 0 ? comarkAttributes(attrs) : '';
12
10
  return `*${content}*${attrsString}`;
13
11
  }
@@ -1,9 +1,25 @@
1
1
  import { htmlAttributes } from "../attributes.js";
2
- import { indent } from "../indent.js";
2
+ import { indent } from "../../../utils/index.js";
3
3
  const textBlocks = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'td', 'th']);
4
4
  const selfCloseTags = new Set(['br', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr']);
5
5
  const inlineTags = new Set(['strong', 'em', 'code', 'a', 'br', 'span', 'img']);
6
- const blockTags = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'ul', 'ol', 'blockquote', 'hr', 'table', 'td', 'th']);
6
+ const blockTags = new Set([
7
+ 'p',
8
+ 'h1',
9
+ 'h2',
10
+ 'h3',
11
+ 'h4',
12
+ 'h5',
13
+ 'h6',
14
+ 'li',
15
+ 'ul',
16
+ 'ol',
17
+ 'blockquote',
18
+ 'hr',
19
+ 'table',
20
+ 'td',
21
+ 'th',
22
+ ]);
7
23
  export async function html(node, state, parent) {
8
24
  const [tag, attr, ...children] = node;
9
25
  const { $ = {}, ...rawAttributes } = attr;
@@ -15,11 +31,9 @@ export async function html(node, state, parent) {
15
31
  // back to the raw (empty) attrs to avoid leaking parent props onto native
16
32
  // wrappers like `<p>` or `<ul>`.
17
33
  const rawHasAttrs = Object.keys(rawAttributes).length > 0;
18
- const attributes = state.context.html
19
- ? (rawHasAttrs ? state.renderData.props : rawAttributes)
20
- : rawAttributes;
21
- const hasOnlyTextChildren = children.every(child => typeof child === 'string' || inlineTags.has(String(child?.[0])));
22
- const hasTextSibling = children.some(child => typeof child === 'string');
34
+ const attributes = state.context.html ? (rawHasAttrs ? state.renderData.props : rawAttributes) : rawAttributes;
35
+ const hasOnlyTextChildren = children.every((child) => typeof child === 'string' || inlineTags.has(String(child?.[0])));
36
+ const hasTextSibling = children.some((child) => typeof child === 'string');
23
37
  const isBlock = textBlocks.has(String(tag));
24
38
  const isInline = inlineTags.has(String(tag)) && $.block === 0;
25
39
  let oneLiner = isBlock && hasOnlyTextChildren;
@@ -48,7 +62,8 @@ export async function html(node, state, parent) {
48
62
  for (let i = 0; i < children.length; i++) {
49
63
  const childContent = childrenContent[i];
50
64
  const child = children[i];
51
- const isBlock = typeof child !== 'string' && (blockTags.has(String(child?.[0])) || (!inlineTags.has(String(child?.[0])) && !hasTextSibling));
65
+ const isBlock = typeof child !== 'string' &&
66
+ (blockTags.has(String(child?.[0])) || (!inlineTags.has(String(child?.[0])) && !hasTextSibling));
52
67
  if (i > 0 && !isPrevBlock && isBlock) {
53
68
  content += state.context.blockSeparator;
54
69
  }
@@ -62,23 +77,18 @@ export async function html(node, state, parent) {
62
77
  if (revert) {
63
78
  state.applyContext(revert);
64
79
  }
65
- const attrs = Object.keys(attributes).length > 0
66
- ? ` ${htmlAttributes(attributes)}`
67
- : '';
80
+ const attrs = Object.keys(attributes).length > 0 ? ` ${htmlAttributes(attributes)}` : '';
68
81
  if (isSelfClose) {
69
82
  return `<${tag}${attrs} />` + (!parent && !isInline ? state.context.blockSeparator : '');
70
83
  }
71
84
  if (!oneLiner && content) {
72
85
  content = '\n' + paddNoneHtmlContent(content, state).trimEnd() + '\n';
73
86
  }
74
- return `<${tag}${attrs}>${content}</${tag}>`
75
- + (!parent && !isInline ? state.context.blockSeparator : '');
87
+ return `<${tag}${attrs}>${content}</${tag}>` + (!parent && !isInline ? state.context.blockSeparator : '');
76
88
  }
77
89
  function paddNoneHtmlContent(content, state) {
78
90
  if (state.context.html) {
79
91
  return indent(content);
80
92
  }
81
- return ((content.trim().startsWith('<') ? '' : '')
82
- + content
83
- + (content.trim().endsWith('>') ? '' : ''));
93
+ return (content.trim().startsWith('<') ? '' : '') + content + (content.trim().endsWith('>') ? '' : '');
84
94
  }
@@ -2,8 +2,6 @@ import { comarkAttributes } from "../attributes.js";
2
2
  export function img(node, _state) {
3
3
  const [_, attrs] = node;
4
4
  const { title, src, alt = '', ...rest } = attrs;
5
- const attrsString = Object.keys(rest).length > 0
6
- ? comarkAttributes(rest)
7
- : '';
5
+ const attrsString = Object.keys(rest).length > 0 ? comarkAttributes(rest) : '';
8
6
  return title ? `![${alt}](${src} "${title}")` : `![${alt}](${src})${attrsString}`;
9
7
  }
@@ -1,4 +1,4 @@
1
- import { indent } from "../indent.js";
1
+ import { indent } from "../../../utils/index.js";
2
2
  // Block elements that need explicit indentation in list items.
3
3
  // Note: ol/ul are handled by their own handlers which manage indentation via listIndent context.
4
4
  const blockElements = new Set(['pre', 'blockquote', 'table']);
@@ -18,14 +18,20 @@ export async function li(node, state) {
18
18
  let result = '';
19
19
  for (const child of children) {
20
20
  const rendered = await state.one(child, state, node);
21
- if (Array.isArray(child) && blockElements.has(child[0])) {
22
- // Block-level child: put on its own line and indent to align with list prefix
23
- const indented = indent(rendered, { width: prefixWidth });
24
- result = result.trimEnd() + '\n' + indented.trimEnd() + '\n';
25
- }
26
- else {
27
- result += rendered;
21
+ if (result && Array.isArray(child)) {
22
+ if (blockElements.has(child[0])) {
23
+ // Block-level child: put on its own line and indent to align with list prefix
24
+ const indented = indent(rendered, { width: prefixWidth });
25
+ result = result.trimEnd() + '\n' + indented.trimEnd() + '\n';
26
+ continue;
27
+ }
28
+ if (child[0] === 'p') {
29
+ const indented = indent(rendered, { width: prefixWidth });
30
+ result = result.trimEnd() + '\n\n' + indented.trimEnd() + '\n';
31
+ continue;
32
+ }
28
33
  }
34
+ result += rendered;
29
35
  }
30
36
  result = result.trim();
31
37
  if (!order) {
@@ -1,4 +1,4 @@
1
- import { indent } from "../indent.js";
1
+ import { indent } from "../../../utils/index.js";
2
2
  import { comarkAttributes, comarkYamlAttributes } from "../attributes.js";
3
3
  import { html } from "./html.js";
4
4
  // HTML elements that always create an inline context for their children
@@ -28,8 +28,7 @@ export async function mdc(node, state, parent) {
28
28
  content = content.trimEnd();
29
29
  const attrs = attributeEntries.length > 0 ? comarkAttributes(attributes) : '';
30
30
  if (tag === 'span') {
31
- return `[${content}]${attrs}`
32
- + (inline ? '' : state.context.blockSeparator);
31
+ return `[${content}]${attrs}` + (inline ? '' : state.context.blockSeparator);
33
32
  }
34
33
  const fence = ':'.repeat((state.nodeDepthInTree || 0) + 2);
35
34
  let result = `:${tag}${content && `[${content}]`}${attrs}` + (!parent ? state.context.blockSeparator : '');
@@ -1,4 +1,4 @@
1
- import { indent } from "../indent.js";
1
+ import { indent } from "../../../utils/index.js";
2
2
  export async function ol(node, state) {
3
3
  const children = node.slice(2);
4
4
  const revert = state.applyContext({ list: true, order: 1, listIndent: 3 });
@@ -1,3 +1,3 @@
1
1
  import type { State } from 'comark/render';
2
2
  import type { ComarkElement } from 'comark';
3
- export declare function p(node: ComarkElement, state: State): Promise<string>;
3
+ export declare function p(node: ComarkElement, state: State, parent?: ComarkElement): Promise<string>;
@@ -1,8 +1,11 @@
1
- export async function p(node, state) {
1
+ export async function p(node, state, parent) {
2
2
  const children = node.slice(2);
3
3
  let result = '';
4
4
  for (const child of children) {
5
5
  result += await state.one(child, state, node);
6
6
  }
7
+ if (parent?.[0] === 'li') {
8
+ return result;
9
+ }
7
10
  return result + state.context.blockSeparator;
8
11
  }
@@ -2,21 +2,18 @@ import { textContent } from "../../../utils/index.js";
2
2
  export function pre(node, state) {
3
3
  const [_, attributes, ...children] = node;
4
4
  const codeClasses = children[0]?.[1]?.class;
5
- const language = (attributes.language || (codeClasses?.split(' ').find(cls => cls.startsWith('language-')))?.slice(9)) || '';
5
+ const language = attributes.language ||
6
+ codeClasses
7
+ ?.split(' ')
8
+ .find((cls) => cls.startsWith('language-'))
9
+ ?.slice(9) ||
10
+ '';
6
11
  // Escape ] in filename
7
- const filename = attributes.filename
8
- ? ' [' + String(attributes.filename).split(']').join('\\\\]') + ']'
9
- : '';
10
- const highlights = attributes.highlights
11
- ? ' {' + formatHighlights(attributes.highlights) + '}'
12
- : '';
12
+ const filename = attributes.filename ? ' [' + String(attributes.filename).split(']').join('\\\\]') + ']' : '';
13
+ const highlights = attributes.highlights ? ' {' + formatHighlights(attributes.highlights) + '}' : '';
13
14
  // Meta always has a leading space
14
- const meta = attributes.meta
15
- ? ' ' + attributes.meta
16
- : '';
17
- const result = '```' + language + filename + highlights + meta + '\n'
18
- + String(node[1]?.code || textContent(node)).trim()
19
- + '\n```';
15
+ const meta = attributes.meta ? ' ' + attributes.meta : '';
16
+ const result = '```' + language + filename + highlights + meta + '\n' + String(node[1]?.code || textContent(node)).trim() + '\n```';
20
17
  return result + state.context.blockSeparator;
21
18
  }
22
19
  function formatHighlights(highlights) {
@@ -6,8 +6,6 @@ export async function strong(node, state) {
6
6
  content += await state.one(child, state, node);
7
7
  }
8
8
  content = content.trim();
9
- const attrsString = Object.keys(attrs).length > 0
10
- ? comarkAttributes(attrs)
11
- : '';
9
+ const attrsString = Object.keys(attrs).length > 0 ? comarkAttributes(attrs) : '';
12
10
  return `**${content}**${attrsString}`;
13
11
  }
@@ -52,14 +52,14 @@ function getRows(element) {
52
52
  }
53
53
  // If it's thead/tbody, extract tr elements
54
54
  if (tag === 'thead' || tag === 'tbody') {
55
- return children.filter(child => typeof child !== 'string' && child[0] === 'tr');
55
+ return children.filter((child) => typeof child !== 'string' && child[0] === 'tr');
56
56
  }
57
57
  return [];
58
58
  }
59
59
  // Helper function to get cells from a row
60
60
  function getCells(row) {
61
61
  const [, , ...children] = row;
62
- return children.filter(child => typeof child !== 'string' && (child[0] === 'th' || child[0] === 'td'));
62
+ return children.filter((child) => typeof child !== 'string' && (child[0] === 'th' || child[0] === 'td'));
63
63
  }
64
64
  export async function table(node, state) {
65
65
  const [, , ...children] = node;
@@ -110,7 +110,7 @@ export async function table(node, state) {
110
110
  return getAlignment(attributes);
111
111
  });
112
112
  // Calculate column widths (minimum 3 characters per column)
113
- const columnWidths = headerContent.map(content => Math.max(3, content.length));
113
+ const columnWidths = headerContent.map((content) => Math.max(3, content.length));
114
114
  // Update column widths based on body content
115
115
  for (const row of bodyRows) {
116
116
  const cells = getCells(row);
@@ -127,7 +127,8 @@ export async function table(node, state) {
127
127
  result += ' |\n';
128
128
  // Add separator row with alignment
129
129
  result += '| ';
130
- result += columnWidths.map((width, i) => {
130
+ result += columnWidths
131
+ .map((width, i) => {
131
132
  const alignment = alignments[i];
132
133
  if (alignment === 'left') {
133
134
  return ':' + '-'.repeat(width - 1);
@@ -139,7 +140,8 @@ export async function table(node, state) {
139
140
  return '-'.repeat(width - 1) + ':';
140
141
  }
141
142
  return '-'.repeat(width);
142
- }).join(' | ');
143
+ })
144
+ .join(' | ');
143
145
  result += ' |\n';
144
146
  // Add body rows
145
147
  for (const row of bodyRows) {
@@ -5,7 +5,7 @@ export async function template(node, state, parent) {
5
5
  // Omit #default marker if this is the only slot
6
6
  if (attrs.name === 'default') {
7
7
  const siblings = parent ? parent.slice(2) : [];
8
- const templateCount = siblings.filter(child => Array.isArray(child) && child[0] === 'template').length;
8
+ const templateCount = siblings.filter((child) => Array.isArray(child) && child[0] === 'template').length;
9
9
  if (templateCount === 1) {
10
10
  return content + state.context.blockSeparator;
11
11
  }
@@ -1,4 +1,4 @@
1
- import { indent } from "../indent.js";
1
+ import { indent } from "../../../utils/index.js";
2
2
  export async function ul(node, state) {
3
3
  const children = node.slice(2);
4
4
  const revert = state.applyContext({ list: true, order: false, listIndent: 2 });
@@ -1,5 +1 @@
1
- export declare function indent(text: string, { ignoreFirstLine, level, width }?: {
2
- ignoreFirstLine?: boolean;
3
- level?: number;
4
- width?: number;
5
- }): string;
1
+ export * from '../../../src/internal/stringify/indent'
@@ -1,9 +1 @@
1
- export function indent(text, { ignoreFirstLine = false, level = 1, width } = {}) {
2
- const pad = width ? ' '.repeat(width) : ' '.repeat(level);
3
- return text.split('\n').map((line, index) => {
4
- if (ignoreFirstLine && index === 0) {
5
- return line;
6
- }
7
- return line ? pad + line : line;
8
- }).join('\n');
9
- }
1
+ export * from '../../../src/internal/stringify/indent.ts'
@@ -173,5 +173,5 @@ function escapeHtml(text) {
173
173
  '>': '&gt;',
174
174
  '&amp;': '&',
175
175
  };
176
- return text.replace(/[<>]/g, char => map[char]);
176
+ return text.replace(/[<>]/g, (char) => map[char]);
177
177
  }
@@ -36,7 +36,7 @@ export function stringifyYaml(data, options) {
36
36
  const line = lines[i];
37
37
  const trimmed = line.trimStart();
38
38
  // Check if line starts with a quote followed by colon
39
- if (trimmed[0] === '\'' || trimmed[0] === '"') {
39
+ if (trimmed[0] === "'" || trimmed[0] === '"') {
40
40
  const quote = trimmed[0];
41
41
  if (trimmed[1] === ':') {
42
42
  // Find the closing quote
package/dist/parse.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import MarkdownExit from 'markdown-exit';
2
- import pluginMdc from '@comark/markdown-it';
2
+ import syntax from "./plugins/syntax.js";
3
3
  import taskList from "./plugins/task-list.js";
4
4
  import alert from "./plugins/alert.js";
5
5
  import { applyAutoUnwrap } from "./internal/parse/auto-unwrap.js";
@@ -45,14 +45,13 @@ export { defineComarkPlugin } from "./utils/helpers.js";
45
45
  */
46
46
  export function createParse(options = {}) {
47
47
  const { autoUnwrap = true, autoClose = true, plugins = [] } = options;
48
+ plugins.unshift(syntax());
48
49
  plugins.unshift(taskList());
49
50
  plugins.unshift(alert());
50
51
  const parser = new MarkdownExit({
51
52
  html: false,
52
53
  linkify: true,
53
- })
54
- .enable(['table', 'strikethrough'])
55
- .use(pluginMdc);
54
+ }).enable(['table', 'strikethrough']);
56
55
  if (options.html !== false) {
57
56
  parser.inline.ruler.before('text', 'comark_html_inline', html_inline);
58
57
  parser.block.ruler.before('html_block', 'comark_html_block', html_block, {
@@ -60,7 +59,7 @@ export function createParse(options = {}) {
60
59
  });
61
60
  }
62
61
  for (const plugin of plugins) {
63
- for (const markdownItPlugin of (plugin.markdownItPlugins || [])) {
62
+ for (const markdownItPlugin of plugin.markdownItPlugins || []) {
64
63
  parser.use(markdownItPlugin);
65
64
  }
66
65
  }
@@ -76,7 +75,8 @@ export function createParse(options = {}) {
76
75
  reusableNodes: [],
77
76
  };
78
77
  const prevOutput = lastOutput;
79
- if (opts.streaming && prevOutput && markdown.startsWith(lastInput ?? '')) {
78
+ const isStartsWithLastInput = markdown.startsWith(lastInput ?? '');
79
+ if (opts.streaming && prevOutput && isStartsWithLastInput) {
80
80
  const { remainingMarkdownStartLine, reusedNodes, remainingMarkdown } = extractReusableNodes(markdown, prevOutput);
81
81
  // If there is no remaining markdown, return the previous output
82
82
  if (!remainingMarkdown)
@@ -91,7 +91,13 @@ export function createParse(options = {}) {
91
91
  for (const plugin of options.plugins || []) {
92
92
  await plugin.pre?.(state);
93
93
  }
94
- const { content, data } = parseFrontmatter(state.markdown);
94
+ const { content, data, frontmatterText } = parseFrontmatter(state.markdown);
95
+ // Count frontmatter lines for line number tracking
96
+ if (content && frontmatterText) {
97
+ state.parsedLines +=
98
+ frontmatterText.split('\n').length + // Number of lines in frontmatter
99
+ 1; // Separator line
100
+ }
95
101
  try {
96
102
  state.tokens = parser.parse(content, {});
97
103
  }
@@ -114,7 +120,7 @@ export function createParse(options = {}) {
114
120
  }
115
121
  if (opts.streaming) {
116
122
  state.tree = {
117
- frontmatter: state.parsedLines > 0 ? (prevOutput?.frontmatter ?? data) : data,
123
+ frontmatter: frontmatterText ? data : (prevOutput?.frontmatter ?? data),
118
124
  meta: {},
119
125
  nodes: [...state.reusableNodes, ...nodes],
120
126
  };
@@ -30,7 +30,7 @@ const markers = {
30
30
  export default defineComarkPlugin(() => ({
31
31
  name: 'alert',
32
32
  post(state) {
33
- visit(state.tree, node => Array.isArray(node) && node[0] === 'blockquote', (node) => {
33
+ visit(state.tree, (node) => Array.isArray(node) && node[0] === 'blockquote', (node) => {
34
34
  const element = node;
35
35
  if (node[2]?.[0] === 'span') {
36
36
  const content = String(node[2][2]).toUpperCase();
@@ -36,9 +36,7 @@ const markdownItInlineBinding = (md, options = {}) => {
36
36
  export default defineComarkPlugin((opts = {}) => {
37
37
  return {
38
38
  name: 'binding',
39
- markdownItPlugins: [
40
- ((md) => markdownItInlineBinding(md, opts)),
41
- ],
39
+ markdownItPlugins: [((md) => markdownItInlineBinding(md, opts))],
42
40
  };
43
41
  });
44
42
  /**
@@ -3,7 +3,7 @@ import { visit } from "../utils/index.js";
3
3
  export default defineComarkPlugin(() => ({
4
4
  name: 'breaks',
5
5
  post(state) {
6
- visit(state.tree, node => Array.isArray(node) && node.length > 2, (node) => {
6
+ visit(state.tree, (node) => Array.isArray(node) && node.length > 2, (node) => {
7
7
  const parent = node;
8
8
  const newParent = [parent[0], parent[1]];
9
9
  let hasModified = false;
@@ -386,7 +386,7 @@ const emojiRule = (state, silent) => {
386
386
  const max = state.posMax;
387
387
  const start = state.pos;
388
388
  // Quick check: must start with ':'
389
- if (state.src.charCodeAt(start) !== 0x3A /* : */) {
389
+ if (state.src.charCodeAt(start) !== 0x3a /* : */) {
390
390
  return false;
391
391
  }
392
392
  // Find the closing ':'
@@ -394,7 +394,7 @@ const emojiRule = (state, silent) => {
394
394
  while (pos < max) {
395
395
  const code = state.src.charCodeAt(pos);
396
396
  // Found closing ':'
397
- if (code === 0x3A /* : */) {
397
+ if (code === 0x3a /* : */) {
398
398
  const emojiName = state.src.slice(start + 1, pos);
399
399
  // Check if this is a valid emoji
400
400
  const emojiChar = EMOJI_MAP.get(emojiName);
@@ -412,12 +412,12 @@ const emojiRule = (state, silent) => {
412
412
  }
413
413
  // Only allow word characters, digits, underscores, hyphens, and plus
414
414
  // This matches the pattern of valid emoji names
415
- if ((code >= 0x61 && code <= 0x7A) // a-z
416
- || (code >= 0x41 && code <= 0x5A) // A-Z
417
- || (code >= 0x30 && code <= 0x39) // 0-9
418
- || code === 0x5F // _
419
- || code === 0x2D // -
420
- || code === 0x2B // +
415
+ if ((code >= 0x61 && code <= 0x7a) || // a-z
416
+ (code >= 0x41 && code <= 0x5a) || // A-Z
417
+ (code >= 0x30 && code <= 0x39) || // 0-9
418
+ code === 0x5f || // _
419
+ code === 0x2d || // -
420
+ code === 0x2b // +
421
421
  ) {
422
422
  pos++;
423
423
  continue;
@@ -9,10 +9,7 @@ const FOOTNOTE_DEF_RE = /^\[\^([^\s\]]+)\]:[ \t]?(.*)$/gm;
9
9
  * on every node in the tree.
10
10
  */
11
11
  function maybeFootnoteRef(node) {
12
- return Array.isArray(node)
13
- && node[0] === 'span'
14
- && node.length === 3
15
- && typeof node[2] === 'string';
12
+ return Array.isArray(node) && node[0] === 'span' && node.length === 3 && typeof node[2] === 'string';
16
13
  }
17
14
  /**
18
15
  * Check if a node is a footnote reference: ['span', {}, '^label']
@@ -29,12 +26,12 @@ function isFootnoteRef(node) {
29
26
  }
30
27
  const child = node[2];
31
28
  // Must start with '^' and have at least one label char
32
- if (child.charCodeAt(0) !== 0x5E /* ^ */ || child.length < 2)
29
+ if (child.charCodeAt(0) !== 0x5e /* ^ */ || child.length < 2)
33
30
  return null;
34
31
  // Check for whitespace using charCode scanning (avoid regex)
35
32
  for (let i = 1; i < child.length; i++) {
36
33
  const c = child.charCodeAt(i);
37
- if (c === 0x20 || c === 0x09 || c === 0x0A || c === 0x0D)
34
+ if (c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d)
38
35
  return null;
39
36
  }
40
37
  return child.slice(1);
@@ -59,7 +56,7 @@ function isFootnoteRef(node) {
59
56
  * ```
60
57
  */
61
58
  export default defineComarkPlugin((config = {}) => {
62
- const { label = 'Footnotes', hr = true, backRef = '↩', } = config;
59
+ const { label = 'Footnotes', hr = true, backRef = '↩' } = config;
63
60
  return {
64
61
  name: 'footnotes',
65
62
  // extract footnote definitions from markdown before MDC parsing
@@ -88,11 +85,17 @@ export default defineComarkPlugin((config = {}) => {
88
85
  refIndexMap.set(refLabel, refIndexMap.size + 1);
89
86
  }
90
87
  const refIndex = refIndexMap.get(refLabel);
91
- return ['sup', { class: 'footnote-ref' },
92
- ['a', {
88
+ return [
89
+ 'sup',
90
+ { class: 'footnote-ref' },
91
+ [
92
+ 'a',
93
+ {
93
94
  href: `#fn-${refLabel}`,
94
95
  id: `fnref-${refLabel}`,
95
- }, `[${refIndex}]`],
96
+ },
97
+ `[${refIndex}]`,
98
+ ],
96
99
  ];
97
100
  });
98
101
  let nodes = state.tree.nodes;
@@ -121,8 +124,11 @@ export default defineComarkPlugin((config = {}) => {
121
124
  const footnoteItems = [];
122
125
  for (const [refLabel] of refIndexMap) {
123
126
  const content = definitions.get(refLabel);
124
- footnoteItems.push(['li', { id: `fn-${refLabel}` },
125
- content, ' ',
127
+ footnoteItems.push([
128
+ 'li',
129
+ { id: `fn-${refLabel}` },
130
+ content,
131
+ ' ',
126
132
  ['a', { href: `#fnref-${refLabel}`, class: 'footnote-backref' }, backRef],
127
133
  ]);
128
134
  }
@@ -166,7 +172,7 @@ export const Footnote = {
166
172
  },
167
173
  handler: (node) => {
168
174
  if (node[1].class === 'footnotes') {
169
- const ol = node.find(n => Array.isArray(n) && n[0] === 'ol');
175
+ const ol = node.find((n) => Array.isArray(n) && n[0] === 'ol');
170
176
  if (!ol)
171
177
  return '';
172
178
  let result = '';