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.
- package/dist/internal/frontmatter.d.ts +1 -0
- package/dist/internal/frontmatter.js +4 -2
- package/dist/internal/parse/auto-close/index.js +69 -31
- package/dist/internal/parse/auto-close/table.js +12 -9
- package/dist/internal/parse/auto-unwrap.js +6 -10
- package/dist/internal/parse/html/html_block_rule.js +10 -16
- package/dist/internal/parse/html/html_inline_rule.js +3 -7
- package/dist/internal/parse/html/html_re.js +1 -1
- package/dist/internal/parse/html/index.d.ts +1 -0
- package/dist/internal/parse/html/index.js +15 -3
- package/dist/internal/parse/syntax/block-params.d.ts +9 -0
- package/dist/internal/parse/syntax/block-params.js +48 -0
- package/dist/internal/parse/syntax/brackets.d.ts +8 -0
- package/dist/internal/parse/syntax/brackets.js +20 -0
- package/dist/internal/parse/syntax/props.d.ts +5 -0
- package/dist/internal/parse/syntax/props.js +119 -0
- package/dist/internal/parse/token-processor.js +89 -50
- package/dist/internal/props-validation.js +4 -9
- package/dist/internal/stringify/attributes.d.ts +7 -0
- package/dist/internal/stringify/attributes.js +56 -1
- package/dist/internal/stringify/handlers/a.js +1 -3
- package/dist/internal/stringify/handlers/blockquote.js +19 -4
- package/dist/internal/stringify/handlers/code.js +1 -3
- package/dist/internal/stringify/handlers/emphesis.js +1 -3
- package/dist/internal/stringify/handlers/heading.js +6 -1
- package/dist/internal/stringify/handlers/html.js +34 -18
- package/dist/internal/stringify/handlers/img.js +1 -3
- package/dist/internal/stringify/handlers/li.js +18 -9
- package/dist/internal/stringify/handlers/mdc.js +3 -4
- package/dist/internal/stringify/handlers/ol.js +12 -2
- package/dist/internal/stringify/handlers/p.d.ts +1 -1
- package/dist/internal/stringify/handlers/p.js +8 -1
- package/dist/internal/stringify/handlers/pre.js +20 -14
- package/dist/internal/stringify/handlers/strong.js +1 -3
- package/dist/internal/stringify/handlers/table.js +14 -5
- package/dist/internal/stringify/handlers/template.js +5 -2
- package/dist/internal/stringify/handlers/ul.js +12 -2
- package/dist/internal/stringify/state.js +1 -1
- package/dist/internal/yaml.js +1 -1
- package/dist/parse.d.ts +4 -4
- package/dist/parse.js +20 -10
- package/dist/plugins/alert.d.ts +1 -1
- package/dist/plugins/alert.js +1 -1
- package/dist/plugins/binding.d.ts +1 -1
- package/dist/plugins/binding.js +1 -3
- package/dist/plugins/breaks.d.ts +1 -1
- package/dist/plugins/breaks.js +1 -1
- package/dist/plugins/emoji.d.ts +1 -1
- package/dist/plugins/emoji.js +8 -8
- package/dist/plugins/footnotes.d.ts +1 -1
- package/dist/plugins/footnotes.js +19 -13
- package/dist/plugins/headings.d.ts +19 -8
- package/dist/plugins/headings.js +27 -19
- package/dist/plugins/highlight.d.ts +2 -12
- package/dist/plugins/highlight.js +201 -103
- package/dist/plugins/json-render.d.ts +1 -1
- package/dist/plugins/json-render.js +5 -9
- package/dist/plugins/math.d.ts +1 -1
- package/dist/plugins/math.js +4 -6
- package/dist/plugins/mermaid.d.ts +1 -1
- package/dist/plugins/mermaid.js +6 -20
- package/dist/plugins/punctuation.d.ts +1 -1
- package/dist/plugins/punctuation.js +5 -6
- package/dist/plugins/security.d.ts +1 -1
- package/dist/plugins/security.js +2 -2
- package/dist/plugins/summary.d.ts +4 -1
- package/dist/plugins/syntax.d.ts +49 -0
- package/dist/plugins/syntax.js +558 -0
- package/dist/plugins/task-list.d.ts +2 -2
- package/dist/plugins/task-list.js +11 -8
- package/dist/plugins/toc.d.ts +3 -1
- package/dist/plugins/toc.js +1 -1
- package/dist/types.d.ts +57 -12
- package/dist/utils/comark.tmLanguage.d.ts +335 -0
- package/dist/utils/comark.tmLanguage.js +597 -0
- package/dist/utils/helpers.d.ts +16 -4
- package/dist/utils/helpers.js +16 -6
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +25 -3
- package/package.json +40 -40
- package/skills/comark/references/rendering-svelte.md +51 -7
- package/dist/internal/stringify/indent.d.ts +0 -5
- package/dist/internal/stringify/indent.js +0 -9
- package/dist/vite.d.ts +0 -1
- package/dist/vite.js +0 -1
- package/skills/skills/comark/AGENTS.md +0 -261
- package/skills/skills/comark/SKILL.md +0 -489
- package/skills/skills/comark/references/markdown-syntax.md +0 -599
- package/skills/skills/comark/references/parsing-ast.md +0 -378
- package/skills/skills/comark/references/rendering-react.md +0 -445
- package/skills/skills/comark/references/rendering-svelte.md +0 -453
- package/skills/skills/comark/references/rendering-vue.md +0 -462
- /package/skills/{skills/migrate-mdc-to-comark → migrate-mdc-to-comark}/SKILL.md +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { indent } from "
|
|
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
|
|
5
5
|
const INLINE_HTML_ELEMENTS = new Set(['a', 'strong', 'em', 'span']);
|
|
6
6
|
export async function mdc(node, state, parent) {
|
|
7
7
|
const [tag, attr, ...children] = node;
|
|
8
|
-
const {
|
|
8
|
+
const { $: _, ...attributes } = attr;
|
|
9
9
|
if (tag === 'table') {
|
|
10
10
|
return html(node, state);
|
|
11
11
|
}
|
|
@@ -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,5 @@
|
|
|
1
|
-
import { indent } from "
|
|
1
|
+
import { indent } from "../../../utils/index.js";
|
|
2
|
+
import { comarkAttributes, userBlockAttrs } from "../attributes.js";
|
|
2
3
|
export async function ol(node, state) {
|
|
3
4
|
const children = node.slice(2);
|
|
4
5
|
const revert = state.applyContext({ list: true, order: 1, listIndent: 3 });
|
|
@@ -7,12 +8,21 @@ export async function ol(node, state) {
|
|
|
7
8
|
result += await state.one(child, state);
|
|
8
9
|
}
|
|
9
10
|
result = result.trim();
|
|
11
|
+
state.applyContext(revert);
|
|
12
|
+
// ol with user attrs round-trips via `::ol{attrs}\n1. …\n::` — the native
|
|
13
|
+
// markdown list syntax has no slot for list-level attrs.
|
|
14
|
+
const attrs = comarkAttributes(userBlockAttrs('ol', node[1]));
|
|
15
|
+
if (attrs) {
|
|
16
|
+
if (revert.list) {
|
|
17
|
+
return '\n' + indent(`::ol${attrs}\n${result}\n::`, { width: revert.listIndent || 2 });
|
|
18
|
+
}
|
|
19
|
+
return `::ol${attrs}\n${result}\n::` + state.context.blockSeparator;
|
|
20
|
+
}
|
|
10
21
|
if (revert.list) {
|
|
11
22
|
result = '\n' + indent(result, { width: revert.listIndent || 2 });
|
|
12
23
|
}
|
|
13
24
|
else {
|
|
14
25
|
result = result + state.context.blockSeparator;
|
|
15
26
|
}
|
|
16
|
-
state.applyContext(revert);
|
|
17
27
|
return result;
|
|
18
28
|
}
|
|
@@ -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,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { comarkAttributes } from "../attributes.js";
|
|
2
|
+
export async function p(node, state, parent) {
|
|
2
3
|
const children = node.slice(2);
|
|
3
4
|
let result = '';
|
|
4
5
|
for (const child of children) {
|
|
5
6
|
result += await state.one(child, state, node);
|
|
6
7
|
}
|
|
8
|
+
const attrs = comarkAttributes(node[1]);
|
|
9
|
+
if (attrs)
|
|
10
|
+
result = `${result.replace(/[ \t]+$/, '')} ${attrs}`;
|
|
11
|
+
if (parent?.[0] === 'li') {
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
7
14
|
return result + state.context.blockSeparator;
|
|
8
15
|
}
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import { textContent } from "../../../utils/index.js";
|
|
2
|
+
import { comarkAttributes, userBlockAttrs } from "../attributes.js";
|
|
2
3
|
export function pre(node, state) {
|
|
3
4
|
const [_, attributes, ...children] = node;
|
|
4
5
|
const codeClasses = children[0]?.[1]?.class;
|
|
5
|
-
const language =
|
|
6
|
+
const language = attributes.language ||
|
|
7
|
+
codeClasses
|
|
8
|
+
?.split(' ')
|
|
9
|
+
.find((cls) => cls.startsWith('language-'))
|
|
10
|
+
?.slice(9) ||
|
|
11
|
+
'';
|
|
6
12
|
// Escape ] in filename
|
|
7
|
-
const filename = attributes.filename
|
|
8
|
-
|
|
9
|
-
: '';
|
|
10
|
-
const highlights = attributes.highlights
|
|
11
|
-
? ' {' + formatHighlights(attributes.highlights) + '}'
|
|
12
|
-
: '';
|
|
13
|
+
const filename = attributes.filename ? ' [' + String(attributes.filename).split(']').join('\\\\]') + ']' : '';
|
|
14
|
+
const highlights = attributes.highlights ? ' {' + formatHighlights(attributes.highlights) + '}' : '';
|
|
13
15
|
// Meta always has a leading space
|
|
14
|
-
const meta = attributes.meta
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const meta = attributes.meta ? ' ' + attributes.meta : '';
|
|
17
|
+
const code = String(node[1]?.code || textContent(node)).trim();
|
|
18
|
+
const fence = code.includes('```') ? '~~~' : '```';
|
|
19
|
+
const fenceBlock = fence + language + filename + highlights + meta + '\n' + code + '\n' + fence;
|
|
20
|
+
// Extra user attrs that can't ride on the fence info string round-trip via
|
|
21
|
+
// `::pre{attrs}\n```…```\n::` — mirrors the wrapper form for ul/ol/table/blockquote.
|
|
22
|
+
const attrs = comarkAttributes(userBlockAttrs('pre', attributes));
|
|
23
|
+
if (attrs) {
|
|
24
|
+
return `::pre${attrs}\n${fenceBlock}\n::` + state.context.blockSeparator;
|
|
25
|
+
}
|
|
26
|
+
return fenceBlock + state.context.blockSeparator;
|
|
21
27
|
}
|
|
22
28
|
function formatHighlights(highlights) {
|
|
23
29
|
if (highlights.length === 0)
|
|
@@ -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
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { comarkAttributes, userBlockAttrs } from "../attributes.js";
|
|
1
2
|
// Helper function to extract alignment from style attribute
|
|
2
3
|
function getAlignment(attributes) {
|
|
3
4
|
const style = attributes.style;
|
|
@@ -52,14 +53,14 @@ function getRows(element) {
|
|
|
52
53
|
}
|
|
53
54
|
// If it's thead/tbody, extract tr elements
|
|
54
55
|
if (tag === 'thead' || tag === 'tbody') {
|
|
55
|
-
return children.filter(child => typeof child !== 'string' && child[0] === 'tr');
|
|
56
|
+
return children.filter((child) => typeof child !== 'string' && child[0] === 'tr');
|
|
56
57
|
}
|
|
57
58
|
return [];
|
|
58
59
|
}
|
|
59
60
|
// Helper function to get cells from a row
|
|
60
61
|
function getCells(row) {
|
|
61
62
|
const [, , ...children] = row;
|
|
62
|
-
return children.filter(child => typeof child !== 'string' && (child[0] === 'th' || child[0] === 'td'));
|
|
63
|
+
return children.filter((child) => typeof child !== 'string' && (child[0] === 'th' || child[0] === 'td'));
|
|
63
64
|
}
|
|
64
65
|
export async function table(node, state) {
|
|
65
66
|
const [, , ...children] = node;
|
|
@@ -110,7 +111,7 @@ export async function table(node, state) {
|
|
|
110
111
|
return getAlignment(attributes);
|
|
111
112
|
});
|
|
112
113
|
// Calculate column widths (minimum 3 characters per column)
|
|
113
|
-
const columnWidths = headerContent.map(content => Math.max(3, content.length));
|
|
114
|
+
const columnWidths = headerContent.map((content) => Math.max(3, content.length));
|
|
114
115
|
// Update column widths based on body content
|
|
115
116
|
for (const row of bodyRows) {
|
|
116
117
|
const cells = getCells(row);
|
|
@@ -127,7 +128,8 @@ export async function table(node, state) {
|
|
|
127
128
|
result += ' |\n';
|
|
128
129
|
// Add separator row with alignment
|
|
129
130
|
result += '| ';
|
|
130
|
-
result += columnWidths
|
|
131
|
+
result += columnWidths
|
|
132
|
+
.map((width, i) => {
|
|
131
133
|
const alignment = alignments[i];
|
|
132
134
|
if (alignment === 'left') {
|
|
133
135
|
return ':' + '-'.repeat(width - 1);
|
|
@@ -139,7 +141,8 @@ export async function table(node, state) {
|
|
|
139
141
|
return '-'.repeat(width - 1) + ':';
|
|
140
142
|
}
|
|
141
143
|
return '-'.repeat(width);
|
|
142
|
-
})
|
|
144
|
+
})
|
|
145
|
+
.join(' | ');
|
|
143
146
|
result += ' |\n';
|
|
144
147
|
// Add body rows
|
|
145
148
|
for (const row of bodyRows) {
|
|
@@ -156,6 +159,12 @@ export async function table(node, state) {
|
|
|
156
159
|
result += '| ' + cellContents.join(' | ') + ' |\n';
|
|
157
160
|
}
|
|
158
161
|
// result already ends with \n, so we only need to add one more \n
|
|
162
|
+
// table with user attrs round-trips via `::table{attrs}\n<table>\n::` —
|
|
163
|
+
// GFM table syntax has no slot for table-level attrs.
|
|
164
|
+
const attrs = comarkAttributes(userBlockAttrs('table', node[1]));
|
|
165
|
+
if (attrs) {
|
|
166
|
+
return `::table${attrs}\n${result.trimEnd()}\n::\n\n`;
|
|
167
|
+
}
|
|
159
168
|
return result + '\n';
|
|
160
169
|
}
|
|
161
170
|
export function thead(_node, _state) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { comarkAttributes } from "../attributes.js";
|
|
1
2
|
// slot template
|
|
2
3
|
export async function template(node, state, parent) {
|
|
3
4
|
const [_, attrs] = node;
|
|
@@ -5,10 +6,12 @@ export async function template(node, state, parent) {
|
|
|
5
6
|
// Omit #default marker if this is the only slot
|
|
6
7
|
if (attrs.name === 'default') {
|
|
7
8
|
const siblings = parent ? parent.slice(2) : [];
|
|
8
|
-
const templateCount = siblings.filter(child => Array.isArray(child) && child[0] === 'template').length;
|
|
9
|
+
const templateCount = siblings.filter((child) => Array.isArray(child) && child[0] === 'template').length;
|
|
9
10
|
if (templateCount === 1) {
|
|
10
11
|
return content + state.context.blockSeparator;
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
const { name: _name, $: _$, ...rest } = attrs;
|
|
15
|
+
const extraAttrs = comarkAttributes(rest);
|
|
16
|
+
return `#${attrs.name}${extraAttrs}\n${content}` + state.context.blockSeparator;
|
|
14
17
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { indent } from "
|
|
1
|
+
import { indent } from "../../../utils/index.js";
|
|
2
|
+
import { comarkAttributes, userBlockAttrs } from "../attributes.js";
|
|
2
3
|
export async function ul(node, state) {
|
|
3
4
|
const children = node.slice(2);
|
|
4
5
|
const revert = state.applyContext({ list: true, order: false, listIndent: 2 });
|
|
@@ -7,12 +8,21 @@ export async function ul(node, state) {
|
|
|
7
8
|
result += await state.one(child, state);
|
|
8
9
|
}
|
|
9
10
|
result = result.trim();
|
|
11
|
+
state.applyContext(revert);
|
|
12
|
+
// ul with user attrs round-trips via `::ul{attrs}\n- …\n::` — the native
|
|
13
|
+
// markdown list syntax has no slot for list-level attrs.
|
|
14
|
+
const attrs = comarkAttributes(userBlockAttrs('ul', node[1]));
|
|
15
|
+
if (attrs) {
|
|
16
|
+
if (revert.list) {
|
|
17
|
+
return '\n' + indent(`::ul${attrs}\n${result}\n::`, { width: revert.listIndent || 2 });
|
|
18
|
+
}
|
|
19
|
+
return `::ul${attrs}\n${result}\n::` + state.context.blockSeparator;
|
|
20
|
+
}
|
|
10
21
|
if (revert.list) {
|
|
11
22
|
result = '\n' + indent(result, { width: revert.listIndent || 2 });
|
|
12
23
|
}
|
|
13
24
|
else {
|
|
14
25
|
result = result + state.context.blockSeparator;
|
|
15
26
|
}
|
|
16
|
-
state.applyContext(revert);
|
|
17
27
|
return result;
|
|
18
28
|
}
|
package/dist/internal/yaml.js
CHANGED
|
@@ -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] === '
|
|
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.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComarkParseFn, ParseOptions, ComarkTree } from './types.ts';
|
|
1
|
+
import type { ComarkParseFn, ComarkPlugin, MergePluginFrontmatter, MergePluginMeta, ParseOptions, ResolvedFrontmatter, ResolvedMeta, ComarkTree } from './types.ts';
|
|
2
2
|
export { parseFrontmatter } from './internal/frontmatter.ts';
|
|
3
3
|
export { defineComarkPlugin } from './utils/helpers.ts';
|
|
4
4
|
/**
|
|
@@ -30,7 +30,7 @@ export { defineComarkPlugin } from './utils/helpers.ts';
|
|
|
30
30
|
* const parseNoHtml = createParse({ html: false })
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
|
-
export declare function createParse(options?: ParseOptions): ComarkParseFn
|
|
33
|
+
export declare function createParse<const TPlugins extends readonly ComarkPlugin<any, any>[] = []>(options?: ParseOptions<TPlugins>): ComarkParseFn<ResolvedMeta<MergePluginMeta<TPlugins>>, ResolvedFrontmatter<MergePluginFrontmatter<TPlugins>>>;
|
|
34
34
|
/**
|
|
35
35
|
* Parse Comark content from a string
|
|
36
36
|
*
|
|
@@ -64,7 +64,7 @@ export declare function createParse(options?: ParseOptions): ComarkParseFn;
|
|
|
64
64
|
* const tree2 = await parse(content, { autoUnwrap: false })
|
|
65
65
|
* ```
|
|
66
66
|
*/
|
|
67
|
-
export declare function parse(markdown: string, options?: ParseOptions): Promise<ComarkTree
|
|
67
|
+
export declare function parse<const TPlugins extends readonly ComarkPlugin<any, any>[] = []>(markdown: string, options?: ParseOptions<TPlugins>): Promise<ComarkTree<ResolvedMeta<MergePluginMeta<TPlugins>>, ResolvedFrontmatter<MergePluginFrontmatter<TPlugins>>>>;
|
|
68
68
|
/**
|
|
69
69
|
* Creates a serialized parser function for Comark content.
|
|
70
70
|
* This is useful for parsing large files in a streaming manner.
|
|
@@ -80,4 +80,4 @@ export declare function parse(markdown: string, options?: ParseOptions): Promise
|
|
|
80
80
|
* const tree = await parse(content)
|
|
81
81
|
* console.log(tree.nodes)
|
|
82
82
|
*/
|
|
83
|
-
export declare function createSerializedParse(options?: ParseOptions): ComarkParseFn
|
|
83
|
+
export declare function createSerializedParse<const TPlugins extends readonly ComarkPlugin<any, any>[] = []>(options?: ParseOptions<TPlugins>): ComarkParseFn<ResolvedMeta<MergePluginMeta<TPlugins>>, ResolvedFrontmatter<MergePluginFrontmatter<TPlugins>>>;
|
package/dist/parse.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import MarkdownExit from 'markdown-exit';
|
|
2
|
-
import
|
|
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";
|
|
@@ -44,15 +44,17 @@ export { defineComarkPlugin } from "./utils/helpers.js";
|
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
46
|
export function createParse(options = {}) {
|
|
47
|
-
const { autoUnwrap = true, autoClose = true
|
|
47
|
+
const { autoUnwrap = true, autoClose = true } = options;
|
|
48
|
+
// Make a mutable working copy so the inferred (possibly readonly) user tuple
|
|
49
|
+
// isn't mutated by the unshift calls below.
|
|
50
|
+
const plugins = options.plugins ? [...options.plugins] : [];
|
|
51
|
+
plugins.unshift(syntax());
|
|
48
52
|
plugins.unshift(taskList());
|
|
49
53
|
plugins.unshift(alert());
|
|
50
54
|
const parser = new MarkdownExit({
|
|
51
55
|
html: false,
|
|
52
56
|
linkify: true,
|
|
53
|
-
})
|
|
54
|
-
.enable(['table', 'strikethrough'])
|
|
55
|
-
.use(pluginMdc);
|
|
57
|
+
}).enable(['table', 'strikethrough']);
|
|
56
58
|
if (options.html !== false) {
|
|
57
59
|
parser.inline.ruler.before('text', 'comark_html_inline', html_inline);
|
|
58
60
|
parser.block.ruler.before('html_block', 'comark_html_block', html_block, {
|
|
@@ -60,13 +62,13 @@ export function createParse(options = {}) {
|
|
|
60
62
|
});
|
|
61
63
|
}
|
|
62
64
|
for (const plugin of plugins) {
|
|
63
|
-
for (const markdownItPlugin of
|
|
65
|
+
for (const markdownItPlugin of plugin.markdownItPlugins || []) {
|
|
64
66
|
parser.use(markdownItPlugin);
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
let lastOutput = null;
|
|
68
70
|
let lastInput = null;
|
|
69
|
-
|
|
71
|
+
const parseFn = async (markdown, opts = {}) => {
|
|
70
72
|
const state = {
|
|
71
73
|
options,
|
|
72
74
|
tokens: [],
|
|
@@ -76,7 +78,8 @@ export function createParse(options = {}) {
|
|
|
76
78
|
reusableNodes: [],
|
|
77
79
|
};
|
|
78
80
|
const prevOutput = lastOutput;
|
|
79
|
-
|
|
81
|
+
const isStartsWithLastInput = markdown.startsWith(lastInput ?? '');
|
|
82
|
+
if (opts.streaming && prevOutput && isStartsWithLastInput) {
|
|
80
83
|
const { remainingMarkdownStartLine, reusedNodes, remainingMarkdown } = extractReusableNodes(markdown, prevOutput);
|
|
81
84
|
// If there is no remaining markdown, return the previous output
|
|
82
85
|
if (!remainingMarkdown)
|
|
@@ -91,7 +94,13 @@ export function createParse(options = {}) {
|
|
|
91
94
|
for (const plugin of options.plugins || []) {
|
|
92
95
|
await plugin.pre?.(state);
|
|
93
96
|
}
|
|
94
|
-
const { content, data } = parseFrontmatter(state.markdown);
|
|
97
|
+
const { content, data, frontmatterText } = parseFrontmatter(state.markdown);
|
|
98
|
+
// Count frontmatter lines for line number tracking
|
|
99
|
+
if (content && frontmatterText) {
|
|
100
|
+
state.parsedLines +=
|
|
101
|
+
frontmatterText.split('\n').length + // Number of lines in frontmatter
|
|
102
|
+
1; // Separator line
|
|
103
|
+
}
|
|
95
104
|
try {
|
|
96
105
|
state.tokens = parser.parse(content, {});
|
|
97
106
|
}
|
|
@@ -114,7 +123,7 @@ export function createParse(options = {}) {
|
|
|
114
123
|
}
|
|
115
124
|
if (opts.streaming) {
|
|
116
125
|
state.tree = {
|
|
117
|
-
frontmatter:
|
|
126
|
+
frontmatter: frontmatterText ? data : (prevOutput?.frontmatter ?? data),
|
|
118
127
|
meta: {},
|
|
119
128
|
nodes: [...state.reusableNodes, ...nodes],
|
|
120
129
|
};
|
|
@@ -137,6 +146,7 @@ export function createParse(options = {}) {
|
|
|
137
146
|
}
|
|
138
147
|
return state.tree;
|
|
139
148
|
};
|
|
149
|
+
return parseFn;
|
|
140
150
|
}
|
|
141
151
|
/**
|
|
142
152
|
* Parse Comark content from a string
|
package/dist/plugins/alert.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("comark").ComarkPluginFactory<unknown>;
|
|
1
|
+
declare const _default: import("comark").ComarkPluginFactory<unknown, {}, {}>;
|
|
2
2
|
export default _default;
|
package/dist/plugins/alert.js
CHANGED
|
@@ -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();
|
|
@@ -7,7 +7,7 @@ export interface MdcInlineBindingOptions {
|
|
|
7
7
|
*/
|
|
8
8
|
tag?: string;
|
|
9
9
|
}
|
|
10
|
-
declare const _default: import("../types").ComarkPluginFactory<MdcInlineBindingOptions>;
|
|
10
|
+
declare const _default: import("../types").ComarkPluginFactory<MdcInlineBindingOptions, {}, {}>;
|
|
11
11
|
export default _default;
|
|
12
12
|
/**
|
|
13
13
|
* Markdown-format handler that renders a `binding` node back to the
|
package/dist/plugins/binding.js
CHANGED
|
@@ -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
|
/**
|
package/dist/plugins/breaks.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("../types.ts").ComarkPluginFactory<unknown>;
|
|
1
|
+
declare const _default: import("../types.ts").ComarkPluginFactory<unknown, {}, {}>;
|
|
2
2
|
export default _default;
|
package/dist/plugins/breaks.js
CHANGED
|
@@ -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;
|
package/dist/plugins/emoji.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { MarkdownItPlugin } from 'comark';
|
|
2
2
|
export declare const markdownItEmoji: MarkdownItPlugin;
|
|
3
|
-
declare const _default: import("comark").ComarkPluginFactory<unknown>;
|
|
3
|
+
declare const _default: import("comark").ComarkPluginFactory<unknown, {}, {}>;
|
|
4
4
|
export default _default;
|
package/dist/plugins/emoji.js
CHANGED
|
@@ -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) !==
|
|
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 ===
|
|
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 <=
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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;
|
|
@@ -35,7 +35,7 @@ export interface FootnotesConfig {
|
|
|
35
35
|
* })
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
38
|
-
declare const _default: import("comark").ComarkPluginFactory<FootnotesConfig>;
|
|
38
|
+
declare const _default: import("comark").ComarkPluginFactory<FootnotesConfig, {}, {}>;
|
|
39
39
|
export default _default;
|
|
40
40
|
/**
|
|
41
41
|
* Conditional stringify handler for footnotes.
|
|
@@ -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) !==
|
|
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 ===
|
|
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 = '↩'
|
|
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 [
|
|
92
|
-
|
|
88
|
+
return [
|
|
89
|
+
'sup',
|
|
90
|
+
{ class: 'footnote-ref' },
|
|
91
|
+
[
|
|
92
|
+
'a',
|
|
93
|
+
{
|
|
93
94
|
href: `#fn-${refLabel}`,
|
|
94
95
|
id: `fnref-${refLabel}`,
|
|
95
|
-
},
|
|
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([
|
|
125
|
-
|
|
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 = '';
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
export interface HeadingsOptions {
|
|
2
2
|
/**
|
|
3
3
|
* Tag to extract as title and set to `tree.meta.title`.
|
|
4
|
+
* Set to `false` to disable title extraction.
|
|
4
5
|
* @default 'h1'
|
|
5
6
|
*/
|
|
6
|
-
titleTag?: string;
|
|
7
|
+
titleTag?: string | false;
|
|
7
8
|
/**
|
|
8
9
|
* Tag to extract as description and set to `tree.meta.description`.
|
|
9
10
|
* Useful alternatives: `'blockquote'`
|
|
11
|
+
* Set to `false` to disable description extraction.
|
|
10
12
|
* @default 'p'
|
|
11
13
|
*/
|
|
12
|
-
descriptionTag?: string;
|
|
14
|
+
descriptionTag?: string | false;
|
|
13
15
|
/**
|
|
14
16
|
* Whether to remove the extracted nodes from the tree.
|
|
15
17
|
* @default false
|
|
@@ -29,20 +31,29 @@ export interface HeadingsOptions {
|
|
|
29
31
|
* content is written to `tree.meta.description`. When no title was found,
|
|
30
32
|
* this check starts from the very first content node.
|
|
31
33
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
+
* By default the extracted nodes are kept in the tree. Set `remove: true`
|
|
35
|
+
* to strip them so they are not rendered twice.
|
|
34
36
|
*
|
|
35
37
|
* @example
|
|
36
38
|
* ```ts
|
|
37
|
-
* // Default — h1 as title, first paragraph as description
|
|
39
|
+
* // Default — h1 as title, first paragraph as description, nodes kept in tree
|
|
38
40
|
* headings()
|
|
39
41
|
*
|
|
40
42
|
* // Use a blockquote as the description instead of a paragraph
|
|
41
43
|
* headings({ descriptionTag: 'blockquote' })
|
|
42
44
|
*
|
|
43
|
-
* // Extract metadata
|
|
44
|
-
* headings({ remove:
|
|
45
|
+
* // Extract metadata and remove the matched nodes from the tree
|
|
46
|
+
* headings({ remove: true })
|
|
47
|
+
*
|
|
48
|
+
* // Disable title extraction, only extract description
|
|
49
|
+
* headings({ titleTag: false })
|
|
50
|
+
*
|
|
51
|
+
* // Disable description extraction, only extract title
|
|
52
|
+
* headings({ descriptionTag: false })
|
|
45
53
|
* ```
|
|
46
54
|
*/
|
|
47
|
-
declare const _default: import("comark").ComarkPluginFactory<HeadingsOptions
|
|
55
|
+
declare const _default: import("comark").ComarkPluginFactory<HeadingsOptions, {
|
|
56
|
+
title?: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
}, {}>;
|
|
48
59
|
export default _default;
|