comark 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/internal/frontmatter.d.ts +2 -1
- package/dist/internal/frontmatter.js +2 -2
- package/dist/internal/parse/auto-close/index.js +58 -23
- package/dist/internal/parse/token-processor.js +18 -3
- package/dist/internal/stringify/attributes.d.ts +37 -1
- package/dist/internal/stringify/attributes.js +96 -12
- package/dist/internal/stringify/handlers/a.js +3 -0
- package/dist/internal/stringify/handlers/code.js +1 -1
- package/dist/internal/stringify/handlers/del.js +1 -1
- package/dist/internal/stringify/handlers/hr.d.ts +1 -1
- package/dist/internal/stringify/handlers/hr.js +4 -1
- package/dist/internal/stringify/handlers/html.js +13 -2
- package/dist/internal/stringify/handlers/img.js +1 -1
- package/dist/internal/stringify/handlers/li.js +14 -1
- package/dist/internal/stringify/handlers/math.js +1 -1
- package/dist/internal/stringify/handlers/mdc.js +3 -2
- package/dist/internal/stringify/handlers/ol.js +2 -2
- package/dist/internal/stringify/handlers/pre.js +1 -1
- package/dist/internal/stringify/handlers/template.js +1 -1
- package/dist/internal/stringify/handlers/ul.js +2 -2
- package/dist/internal/stringify/indent.d.ts +2 -1
- package/dist/internal/stringify/indent.js +3 -2
- package/dist/internal/stringify/state.d.ts +3 -3
- package/dist/internal/stringify/state.js +71 -15
- package/dist/internal/yaml.d.ts +2 -1
- package/dist/internal/yaml.js +3 -1
- package/dist/parse.js +13 -2
- package/dist/plugins/alert.js +1 -1
- package/dist/plugins/binding.d.ts +20 -0
- package/dist/plugins/binding.js +61 -0
- package/dist/plugins/breaks.d.ts +2 -0
- package/dist/plugins/breaks.js +34 -0
- package/dist/plugins/footnotes.d.ts +61 -0
- package/dist/plugins/footnotes.js +187 -0
- package/dist/plugins/highlight.d.ts +2 -2
- package/dist/plugins/highlight.js +7 -5
- package/dist/plugins/json-render.d.ts +53 -0
- package/dist/plugins/json-render.js +99 -0
- package/dist/plugins/punctuation.d.ts +67 -0
- package/dist/plugins/punctuation.js +236 -0
- package/dist/plugins/security.js +1 -1
- package/dist/render.d.ts +2 -1
- package/dist/render.js +3 -1
- package/dist/types.d.ts +71 -16
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.js +24 -0
- package/dist/vite.d.ts +1 -0
- package/dist/vite.js +1 -0
- package/package.json +29 -23
- package/skills/comark/AGENTS.md +261 -0
- package/skills/comark/SKILL.md +489 -0
- package/skills/comark/references/markdown-syntax.md +599 -0
- package/skills/comark/references/parsing-ast.md +378 -0
- package/skills/comark/references/rendering-react.md +445 -0
- package/skills/comark/references/rendering-svelte.md +453 -0
- package/skills/comark/references/rendering-vue.md +462 -0
- package/skills/skills/comark/AGENTS.md +261 -0
- package/skills/skills/comark/SKILL.md +489 -0
- package/skills/skills/comark/references/markdown-syntax.md +599 -0
- package/skills/skills/comark/references/parsing-ast.md +378 -0
- package/skills/skills/comark/references/rendering-react.md +445 -0
- package/skills/skills/comark/references/rendering-svelte.md +453 -0
- package/skills/skills/comark/references/rendering-vue.md +462 -0
- package/skills/skills/migrate-mdc-to-comark/SKILL.md +191 -0
- package/dist/utils/serialized-task.d.ts +0 -1
- package/dist/utils/serialized-task.js +0 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { defineComarkPlugin } from "../parse.js";
|
|
2
|
+
import { textContent, visit } from "../utils/index.js";
|
|
3
|
+
import { parseYaml } from "../internal/yaml.js";
|
|
4
|
+
function jsonRenderToAst(jrt) {
|
|
5
|
+
if (!jrt.root) {
|
|
6
|
+
jrt = {
|
|
7
|
+
root: 'template',
|
|
8
|
+
elements: { template: jrt },
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const tree = jrt;
|
|
12
|
+
const root = tree.elements[tree.root];
|
|
13
|
+
return jsonRenderElementToAst(root, tree.elements);
|
|
14
|
+
}
|
|
15
|
+
function jsonRenderElementToAst(element, elements) {
|
|
16
|
+
if (element.type === 'Text') {
|
|
17
|
+
return String(element.props.content);
|
|
18
|
+
}
|
|
19
|
+
const children = element.children?.map(childName => elements[childName])
|
|
20
|
+
.filter(Boolean) || [];
|
|
21
|
+
return [
|
|
22
|
+
element.type,
|
|
23
|
+
element.props,
|
|
24
|
+
...children.map(child => jsonRenderElementToAst(child, elements)),
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Plugin for rendering [JSON Render](https://json-render.dev/) specs as UI components.
|
|
29
|
+
*
|
|
30
|
+
* Transforms `json-render` fenced code blocks into Comark AST nodes at parse time.
|
|
31
|
+
* Supports both full specs (with `root` and `elements`) and single-element shorthand.
|
|
32
|
+
*
|
|
33
|
+
* @param config - Plugin configuration options
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { parse } from 'comark'
|
|
38
|
+
* import jsonRender from 'comark/plugins/json-render'
|
|
39
|
+
*
|
|
40
|
+
* const result = await parse(`
|
|
41
|
+
* \`\`\`json-render
|
|
42
|
+
* {
|
|
43
|
+
* "root": "card",
|
|
44
|
+
* "elements": {
|
|
45
|
+
* "card": {
|
|
46
|
+
* "type": "Card",
|
|
47
|
+
* "props": { "title": "Hello" },
|
|
48
|
+
* "children": ["text"]
|
|
49
|
+
* },
|
|
50
|
+
* "text": {
|
|
51
|
+
* "type": "Text",
|
|
52
|
+
* "props": { "content": "World" }
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* }
|
|
56
|
+
* \`\`\`
|
|
57
|
+
* `, {
|
|
58
|
+
* plugins: [jsonRender()]
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```vue
|
|
64
|
+
* <script setup>
|
|
65
|
+
* import { Comark } from '@comark/vue'
|
|
66
|
+
* import jsonRender from '@comark/vue/plugins/json-render'
|
|
67
|
+
* </script>
|
|
68
|
+
*
|
|
69
|
+
* <template>
|
|
70
|
+
* <Suspense>
|
|
71
|
+
* <Comark :plugins="[jsonRender()]">{{ content }}</Comark>
|
|
72
|
+
* </Suspense>
|
|
73
|
+
* </template>
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export default defineComarkPlugin((_config = {}) => ({
|
|
77
|
+
name: 'json-render',
|
|
78
|
+
post: async (state) => {
|
|
79
|
+
visit(state.tree, node => node[0] === 'pre' && (node[1].language === 'json-render'
|
|
80
|
+
|| node[1].language === 'yaml-render'), (preNode) => {
|
|
81
|
+
const language = preNode[1].language;
|
|
82
|
+
try {
|
|
83
|
+
let spec = undefined;
|
|
84
|
+
if (language === 'json-render') {
|
|
85
|
+
spec = JSON.parse(textContent(preNode));
|
|
86
|
+
}
|
|
87
|
+
else if (language === 'yaml-render') {
|
|
88
|
+
spec = parseYaml(textContent(preNode));
|
|
89
|
+
}
|
|
90
|
+
if (spec) {
|
|
91
|
+
return jsonRenderToAst(spec);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// nothing to do
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
}));
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export interface PunctuationOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Convert straight quotes to smart (curly) quotes
|
|
4
|
+
* @default true
|
|
5
|
+
*/
|
|
6
|
+
quotes?: boolean | string | [string, string, string, string];
|
|
7
|
+
/**
|
|
8
|
+
* Convert -- to en-dash and --- to em-dash
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
dashes?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Convert ... to ellipsis character and normalize trailing dots after ? and !
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
ellipsis?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Convert (c), (r), (tm), +- to ©, ®, ™, ±
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
symbols?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Normalize repeated punctuation: ???? → ???, !!!! → !!!, ,, → ,
|
|
24
|
+
* @default true
|
|
25
|
+
*/
|
|
26
|
+
normalize?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Punctuation plugin for comark
|
|
30
|
+
*
|
|
31
|
+
* Transforms common punctuation patterns into their typographically correct Unicode characters:
|
|
32
|
+
* - Smart (curly) quotes: "text" → \u201Ctext\u201D, 'text' → \u2018text\u2019
|
|
33
|
+
* - Dashes: -- → \u2013 (en-dash), --- → \u2014 (em-dash)
|
|
34
|
+
* - Ellipsis: ... → \u2026, ?.... → ?.., !.... → !..
|
|
35
|
+
* - Symbols: (c) → \u00A9, (r) → \u00AE, (tm) → \u2122, +- → \u00B1
|
|
36
|
+
* - Normalize: ???? → ???, !!!! → !!!, ,, → ,
|
|
37
|
+
*
|
|
38
|
+
* Does not transform text inside code, pre, math, kbd, script, or style elements.
|
|
39
|
+
*
|
|
40
|
+
* Supports locale-aware quote characters via the `quotes` option:
|
|
41
|
+
* - String of 4 characters: `'«»„"'` (open double, close double, open single, close single)
|
|
42
|
+
* - Array of 4 strings: `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (with nbsp)
|
|
43
|
+
*
|
|
44
|
+
* @param options Punctuation configuration
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { parse } from 'comark'
|
|
49
|
+
* import punctuation from 'comark/plugins/punctuation'
|
|
50
|
+
*
|
|
51
|
+
* const result = await parse('"Hello" -- world...', {
|
|
52
|
+
* plugins: [punctuation()]
|
|
53
|
+
* })
|
|
54
|
+
*
|
|
55
|
+
* // Locale-aware quotes (Russian)
|
|
56
|
+
* const result2 = await parse('"Hello"', {
|
|
57
|
+
* plugins: [punctuation({ quotes: '«»„"' })]
|
|
58
|
+
* })
|
|
59
|
+
*
|
|
60
|
+
* // French quotes with non-breaking spaces
|
|
61
|
+
* const result3 = await parse('"Hello"', {
|
|
62
|
+
* plugins: [punctuation({ quotes: ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] })]
|
|
63
|
+
* })
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare const _default: import("comark").ComarkPluginFactory<PunctuationOptions>;
|
|
67
|
+
export default _default;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { defineComarkPlugin } from "../utils/helpers.js";
|
|
2
|
+
const DEFAULT_QUOTES = '\u201C\u201D\u2018\u2019'; // ""''
|
|
3
|
+
/** Tags whose text content should not be transformed */
|
|
4
|
+
const SKIP_TAGS = new Set(['code', 'pre', 'math', 'script', 'style', 'kbd']);
|
|
5
|
+
function isWhitespaceOrOpener(ch) {
|
|
6
|
+
return ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r'
|
|
7
|
+
|| ch === '(' || ch === '[' || ch === '{';
|
|
8
|
+
}
|
|
9
|
+
function isLetter(ch) {
|
|
10
|
+
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Single-pass O(n) text transformation — replaces punctuation patterns
|
|
14
|
+
* (ellipsis, dashes, symbols, smart quotes, normalization) in one scan with no regex.
|
|
15
|
+
* Uses slice-based string building to minimize allocations.
|
|
16
|
+
*/
|
|
17
|
+
function applyPunctuation(text, enableQuotes, dashes, ellipsis, symbols, normalize, openDouble, closeDouble, openSingle, closeSingle) {
|
|
18
|
+
const len = text.length;
|
|
19
|
+
let result = '';
|
|
20
|
+
let last = 0;
|
|
21
|
+
for (let i = 0; i < len; i++) {
|
|
22
|
+
const ch = text[i];
|
|
23
|
+
// Ellipsis: two or more dots → … with special handling for ?... and !...
|
|
24
|
+
if (ellipsis && ch === '.') {
|
|
25
|
+
if (i + 1 < len && text[i + 1] === '.') {
|
|
26
|
+
// Count consecutive dots
|
|
27
|
+
let end = i + 2;
|
|
28
|
+
while (end < len && text[end] === '.')
|
|
29
|
+
end++;
|
|
30
|
+
// Check if preceded by ? or ! → collapse to ?.. or !..
|
|
31
|
+
const prev = i > 0 ? text[i - 1] : '';
|
|
32
|
+
if (prev === '?' || prev === '!') {
|
|
33
|
+
result += text.slice(last, i) + '..';
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
result += text.slice(last, i) + '\u2026';
|
|
37
|
+
}
|
|
38
|
+
i = end - 1;
|
|
39
|
+
last = i + 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Dashes: --- (em-dash) or -- (en-dash)
|
|
44
|
+
if (dashes && ch === '-' && i + 1 < len && text[i + 1] === '-') {
|
|
45
|
+
if (i + 2 < len && text[i + 2] === '-') {
|
|
46
|
+
result += text.slice(last, i) + '\u2014';
|
|
47
|
+
i += 2;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
result += text.slice(last, i) + '\u2013';
|
|
51
|
+
i += 1;
|
|
52
|
+
}
|
|
53
|
+
last = i + 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// Symbols: (c), (C), (r), (R), (tm), (TM), +-
|
|
57
|
+
if (symbols && ch === '(') {
|
|
58
|
+
const remaining = len - i;
|
|
59
|
+
if (remaining >= 3 && text[i + 2] === ')') {
|
|
60
|
+
const inner = text[i + 1];
|
|
61
|
+
if (inner === 'c' || inner === 'C') {
|
|
62
|
+
result += text.slice(last, i) + '\u00A9';
|
|
63
|
+
i += 2;
|
|
64
|
+
last = i + 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (inner === 'r' || inner === 'R') {
|
|
68
|
+
result += text.slice(last, i) + '\u00AE';
|
|
69
|
+
i += 2;
|
|
70
|
+
last = i + 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (remaining >= 4 && text[i + 3] === ')') {
|
|
75
|
+
const c1 = text[i + 1];
|
|
76
|
+
const c2 = text[i + 2];
|
|
77
|
+
if ((c1 === 't' || c1 === 'T') && (c2 === 'm' || c2 === 'M')) {
|
|
78
|
+
result += text.slice(last, i) + '\u2122';
|
|
79
|
+
i += 3;
|
|
80
|
+
last = i + 1;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (symbols && ch === '+' && i + 1 < len && text[i + 1] === '-') {
|
|
86
|
+
result += text.slice(last, i) + '\u00B1';
|
|
87
|
+
i += 1;
|
|
88
|
+
last = i + 1;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
// Normalize repeated punctuation: ???? → ???, !!!! → !!!, ,, → ,
|
|
92
|
+
if (normalize) {
|
|
93
|
+
if (ch === '?' || ch === '!') {
|
|
94
|
+
let end = i + 1;
|
|
95
|
+
while (end < len && text[end] === ch)
|
|
96
|
+
end++;
|
|
97
|
+
const count = end - i;
|
|
98
|
+
if (count >= 4) {
|
|
99
|
+
result += text.slice(last, i) + ch + ch + ch;
|
|
100
|
+
i = end - 1;
|
|
101
|
+
last = i + 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (ch === ',' && i + 1 < len && text[i + 1] === ',') {
|
|
106
|
+
// Collapse ,, (or more) to ,
|
|
107
|
+
let end = i + 2;
|
|
108
|
+
while (end < len && text[end] === ',')
|
|
109
|
+
end++;
|
|
110
|
+
result += text.slice(last, i) + ',';
|
|
111
|
+
i = end - 1;
|
|
112
|
+
last = i + 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Smart quotes
|
|
117
|
+
if (enableQuotes) {
|
|
118
|
+
if (ch === '"') {
|
|
119
|
+
const prev = i > 0 ? text[i - 1] : ' ';
|
|
120
|
+
result += text.slice(last, i) + ((isWhitespaceOrOpener(prev) || i === 0) ? openDouble : closeDouble);
|
|
121
|
+
last = i + 1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (ch === '\'') {
|
|
125
|
+
const prev = i > 0 ? text[i - 1] : ' ';
|
|
126
|
+
const next = i + 1 < len ? text[i + 1] : '';
|
|
127
|
+
result += text.slice(last, i);
|
|
128
|
+
// Apostrophe in contractions: letter before AND letter after
|
|
129
|
+
if (isLetter(prev) && isLetter(next)) {
|
|
130
|
+
result += closeSingle;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
result += (isWhitespaceOrOpener(prev) || i === 0) ? openSingle : closeSingle;
|
|
134
|
+
}
|
|
135
|
+
last = i + 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Fast path: no substitutions were made
|
|
141
|
+
if (last === 0)
|
|
142
|
+
return text;
|
|
143
|
+
return result + text.slice(last);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Punctuation plugin for comark
|
|
147
|
+
*
|
|
148
|
+
* Transforms common punctuation patterns into their typographically correct Unicode characters:
|
|
149
|
+
* - Smart (curly) quotes: "text" → \u201Ctext\u201D, 'text' → \u2018text\u2019
|
|
150
|
+
* - Dashes: -- → \u2013 (en-dash), --- → \u2014 (em-dash)
|
|
151
|
+
* - Ellipsis: ... → \u2026, ?.... → ?.., !.... → !..
|
|
152
|
+
* - Symbols: (c) → \u00A9, (r) → \u00AE, (tm) → \u2122, +- → \u00B1
|
|
153
|
+
* - Normalize: ???? → ???, !!!! → !!!, ,, → ,
|
|
154
|
+
*
|
|
155
|
+
* Does not transform text inside code, pre, math, kbd, script, or style elements.
|
|
156
|
+
*
|
|
157
|
+
* Supports locale-aware quote characters via the `quotes` option:
|
|
158
|
+
* - String of 4 characters: `'«»„"'` (open double, close double, open single, close single)
|
|
159
|
+
* - Array of 4 strings: `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (with nbsp)
|
|
160
|
+
*
|
|
161
|
+
* @param options Punctuation configuration
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* import { parse } from 'comark'
|
|
166
|
+
* import punctuation from 'comark/plugins/punctuation'
|
|
167
|
+
*
|
|
168
|
+
* const result = await parse('"Hello" -- world...', {
|
|
169
|
+
* plugins: [punctuation()]
|
|
170
|
+
* })
|
|
171
|
+
*
|
|
172
|
+
* // Locale-aware quotes (Russian)
|
|
173
|
+
* const result2 = await parse('"Hello"', {
|
|
174
|
+
* plugins: [punctuation({ quotes: '«»„"' })]
|
|
175
|
+
* })
|
|
176
|
+
*
|
|
177
|
+
* // French quotes with non-breaking spaces
|
|
178
|
+
* const result3 = await parse('"Hello"', {
|
|
179
|
+
* plugins: [punctuation({ quotes: ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] })]
|
|
180
|
+
* })
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export default defineComarkPlugin((options = {}) => {
|
|
184
|
+
const { quotes = true, dashes = true, ellipsis = true, symbols = true, normalize = true, } = options;
|
|
185
|
+
// Resolve quote characters
|
|
186
|
+
let enableQuotes;
|
|
187
|
+
let openDouble;
|
|
188
|
+
let closeDouble;
|
|
189
|
+
let openSingle;
|
|
190
|
+
let closeSingle;
|
|
191
|
+
if (quotes === false) {
|
|
192
|
+
enableQuotes = false;
|
|
193
|
+
openDouble = closeDouble = openSingle = closeSingle = '';
|
|
194
|
+
}
|
|
195
|
+
else if (Array.isArray(quotes)) {
|
|
196
|
+
enableQuotes = true;
|
|
197
|
+
openDouble = quotes[0];
|
|
198
|
+
closeDouble = quotes[1];
|
|
199
|
+
openSingle = quotes[2];
|
|
200
|
+
closeSingle = quotes[3];
|
|
201
|
+
}
|
|
202
|
+
else if (typeof quotes === 'string') {
|
|
203
|
+
enableQuotes = true;
|
|
204
|
+
openDouble = quotes[0];
|
|
205
|
+
closeDouble = quotes[1];
|
|
206
|
+
openSingle = quotes[2];
|
|
207
|
+
closeSingle = quotes[3];
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
enableQuotes = true;
|
|
211
|
+
openDouble = DEFAULT_QUOTES[0];
|
|
212
|
+
closeDouble = DEFAULT_QUOTES[1];
|
|
213
|
+
openSingle = DEFAULT_QUOTES[2];
|
|
214
|
+
closeSingle = DEFAULT_QUOTES[3];
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
name: 'punctuation',
|
|
218
|
+
post(state) {
|
|
219
|
+
function walkNodes(nodes, startIndex, skip) {
|
|
220
|
+
for (let i = startIndex; i < nodes.length; i++) {
|
|
221
|
+
const node = nodes[i];
|
|
222
|
+
if (typeof node === 'string') {
|
|
223
|
+
if (!skip) {
|
|
224
|
+
nodes[i] = applyPunctuation(node, enableQuotes, dashes, ellipsis, symbols, normalize, openDouble, closeDouble, openSingle, closeSingle);
|
|
225
|
+
}
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (Array.isArray(node) && node[0] != null) {
|
|
229
|
+
walkNodes(node, 2, skip || SKIP_TAGS.has(node[0]));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
walkNodes(state.tree.nodes, 0, false);
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
});
|
package/dist/plugins/security.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComarkPlugin } from "../utils/helpers.js";
|
|
2
|
-
import { visit } from
|
|
2
|
+
import { visit } from "../utils/index.js";
|
|
3
3
|
import { validateProps } from "../internal/props-validation.js";
|
|
4
4
|
export default defineComarkPlugin((options = {}) => {
|
|
5
5
|
const { blockedTags = [], allowedLinkPrefixes, allowedImagePrefixes, allowedProtocols, defaultOrigin, allowDataImages, } = options;
|
package/dist/render.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ComarkTree, RenderOptions, RenderMarkdownOptions } from 'comark';
|
|
2
|
-
export type { NodeHandler, State, Context, RenderOptions, RenderMarkdownOptions } from './types.ts';
|
|
2
|
+
export type { NodeHandler, State, Context, RenderOptions, RenderMarkdownOptions, NodeRenderData } from './types.ts';
|
|
3
3
|
export { renderFrontmatter } from './internal/frontmatter.ts';
|
|
4
|
+
export { resolveAttributes, resolveAttribute } from './internal/stringify/attributes.ts';
|
|
4
5
|
/**
|
|
5
6
|
* Generate a string from a Comark tree
|
|
6
7
|
* @param tree - The Comark tree to render
|
package/dist/render.js
CHANGED
|
@@ -2,6 +2,8 @@ import { renderFrontmatter } from "./internal/frontmatter.js";
|
|
|
2
2
|
import { createState, one } from "./internal/stringify/state.js";
|
|
3
3
|
// Re-export frontmatter renderer
|
|
4
4
|
export { renderFrontmatter } from "./internal/frontmatter.js";
|
|
5
|
+
// Re-export attribute resolvers for custom handlers that want to honor `:prefix` bindings
|
|
6
|
+
export { resolveAttributes, resolveAttribute } from "./internal/stringify/attributes.js";
|
|
5
7
|
/**
|
|
6
8
|
* Generate a string from a Comark tree
|
|
7
9
|
* @param tree - The Comark tree to render
|
|
@@ -25,5 +27,5 @@ export async function render(tree, context = {}) {
|
|
|
25
27
|
*/
|
|
26
28
|
export async function renderMarkdown(tree, options) {
|
|
27
29
|
const content = await render(tree, { format: 'markdown/comark', ...options });
|
|
28
|
-
return renderFrontmatter(tree.frontmatter, content);
|
|
30
|
+
return renderFrontmatter(tree.frontmatter, content, options?.frontmatterOptions);
|
|
29
31
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DumpOptions } from 'js-yaml';
|
|
1
2
|
import type MarkdownExit from 'markdown-exit';
|
|
2
3
|
import type MarkdownIt from 'markdown-it';
|
|
3
4
|
/**
|
|
@@ -48,7 +49,19 @@ export interface ComarkTree {
|
|
|
48
49
|
frontmatter: Record<string, any>;
|
|
49
50
|
meta: Record<string, any>;
|
|
50
51
|
}
|
|
51
|
-
interface
|
|
52
|
+
export interface ContextBase {
|
|
53
|
+
/**
|
|
54
|
+
* true if node is inside html scope
|
|
55
|
+
*/
|
|
56
|
+
html?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* true if node is inside a list
|
|
59
|
+
*/
|
|
60
|
+
list?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* number if node is inside an ordered list
|
|
63
|
+
*/
|
|
64
|
+
order?: number;
|
|
52
65
|
/**
|
|
53
66
|
* @default '\n\n'
|
|
54
67
|
*/
|
|
@@ -57,10 +70,6 @@ interface StringifyOptions {
|
|
|
57
70
|
* @default 'markdown/comark'
|
|
58
71
|
*/
|
|
59
72
|
format: 'markdown/comark' | 'markdown/html' | 'text/html' | 'text';
|
|
60
|
-
/**
|
|
61
|
-
* user defined node handlers
|
|
62
|
-
*/
|
|
63
|
-
handlers: Record<string, NodeHandler>;
|
|
64
73
|
/**
|
|
65
74
|
* @default true
|
|
66
75
|
*/
|
|
@@ -72,24 +81,29 @@ interface StringifyOptions {
|
|
|
72
81
|
*/
|
|
73
82
|
maxInlineAttributes?: number;
|
|
74
83
|
/**
|
|
75
|
-
*
|
|
84
|
+
* Default syntax for block attributes when attributes exceed `maxInlineAttributes`.
|
|
85
|
+
* - `'codeblock'` — wraps attributes in a fenced YAML code block with `[props]` label
|
|
86
|
+
* - `'frontmatter'` — wraps attributes in `---` delimiters (frontmatter style)
|
|
87
|
+
* @default 'codeblock'
|
|
76
88
|
*/
|
|
89
|
+
blockAttributesStyle?: 'frontmatter' | 'codeblock';
|
|
77
90
|
[key: string]: unknown;
|
|
78
91
|
}
|
|
79
|
-
export interface
|
|
92
|
+
export interface CreateContext extends ContextBase {
|
|
80
93
|
/**
|
|
81
|
-
*
|
|
94
|
+
* user defined node handlers
|
|
82
95
|
*/
|
|
83
|
-
|
|
96
|
+
handlers: Record<string, NodeHandler | ConditionalNodeHandler>;
|
|
97
|
+
}
|
|
98
|
+
export interface Context extends ContextBase {
|
|
84
99
|
/**
|
|
85
|
-
*
|
|
100
|
+
* user defined node handlers
|
|
86
101
|
*/
|
|
87
|
-
|
|
102
|
+
handlers: Record<string, NodeHandler>;
|
|
88
103
|
/**
|
|
89
|
-
*
|
|
104
|
+
* The conditional handlers of the renderer
|
|
90
105
|
*/
|
|
91
|
-
|
|
92
|
-
[key: string]: unknown;
|
|
106
|
+
conditionalHandlers: ConditionalNodeHandler[];
|
|
93
107
|
}
|
|
94
108
|
/**
|
|
95
109
|
* The NodeHandler function
|
|
@@ -99,6 +113,14 @@ export interface Context extends StringifyOptions {
|
|
|
99
113
|
* @returns The rendered node
|
|
100
114
|
*/
|
|
101
115
|
export type NodeHandler = (node: ComarkElement, state: State, parent?: ComarkElement) => string | Promise<string>;
|
|
116
|
+
/**
|
|
117
|
+
* A node handler rule that pairs a match predicate with a handler function.
|
|
118
|
+
* When `match` returns true for a node, the associated `handler` is used to render it.
|
|
119
|
+
*/
|
|
120
|
+
export type ConditionalNodeHandler = {
|
|
121
|
+
match: (node: ComarkElement) => boolean;
|
|
122
|
+
handler: NodeHandler;
|
|
123
|
+
};
|
|
102
124
|
/**
|
|
103
125
|
* The State of the renderer
|
|
104
126
|
* @param handlers - The handlers of the renderer
|
|
@@ -113,6 +135,13 @@ export type State = {
|
|
|
113
135
|
* Additional data to pass to the renderer nodes, can be used to pass pre-fetched data to the renderer nodes
|
|
114
136
|
*/
|
|
115
137
|
data: Record<string, any>;
|
|
138
|
+
/**
|
|
139
|
+
* Render context — `{ frontmatter, meta, data, props }` — used to
|
|
140
|
+
* resolve `:prefixed` attributes that reference dot-paths in markdown.
|
|
141
|
+
* `props` is scoped to the nearest enclosing element as it's mutated during
|
|
142
|
+
* recursion.
|
|
143
|
+
*/
|
|
144
|
+
renderData: NodeRenderData;
|
|
116
145
|
/**
|
|
117
146
|
* The context of the renderer
|
|
118
147
|
*/
|
|
@@ -152,7 +181,7 @@ export interface RenderOptions {
|
|
|
152
181
|
/**
|
|
153
182
|
* Additional node handlers to pass to the renderer
|
|
154
183
|
*/
|
|
155
|
-
components?: Record<string, NodeHandler>;
|
|
184
|
+
components?: Record<string, NodeHandler | ConditionalNodeHandler>;
|
|
156
185
|
/**
|
|
157
186
|
* Additional data to pass to the renderer nodes, can be used to pass pre-fetched data to the renderer nodes
|
|
158
187
|
*/
|
|
@@ -169,6 +198,33 @@ export interface RenderMarkdownOptions extends RenderOptions {
|
|
|
169
198
|
* @default 3
|
|
170
199
|
*/
|
|
171
200
|
maxInlineAttributes?: number;
|
|
201
|
+
/**
|
|
202
|
+
* Default syntax for block attributes when attributes exceed `maxInlineAttributes`.
|
|
203
|
+
* - `'codeblock'` — wraps attributes in a fenced YAML code block with `[props]` label
|
|
204
|
+
* - `'frontmatter'` — wraps attributes in `---` delimiters (frontmatter style)
|
|
205
|
+
* @default 'codeblock'
|
|
206
|
+
*/
|
|
207
|
+
blockAttributesStyle?: 'frontmatter' | 'codeblock';
|
|
208
|
+
/**
|
|
209
|
+
* Options for YAML serialization of frontmatter (js-yaml DumpOptions).
|
|
210
|
+
* Defaults: indent=2, lineWidth=-1.
|
|
211
|
+
*/
|
|
212
|
+
frontmatterOptions?: DumpOptions;
|
|
213
|
+
}
|
|
214
|
+
export interface NodeRenderData {
|
|
215
|
+
frontmatter: Record<string, unknown>;
|
|
216
|
+
/**
|
|
217
|
+
* Meta information from Comark Tree
|
|
218
|
+
*/
|
|
219
|
+
meta: Record<string, unknown>;
|
|
220
|
+
/**
|
|
221
|
+
* Additional data paased to rendere
|
|
222
|
+
*/
|
|
223
|
+
data: Record<string, unknown>;
|
|
224
|
+
/**
|
|
225
|
+
* Props from parent node
|
|
226
|
+
*/
|
|
227
|
+
props: Record<string, unknown>;
|
|
172
228
|
}
|
|
173
229
|
export type MarkdownExitPlugin = (md: MarkdownExit) => void;
|
|
174
230
|
export type MarkdownItPlugin = (md: MarkdownIt) => void;
|
|
@@ -256,4 +312,3 @@ export type ComarkParseFnOptions = {
|
|
|
256
312
|
* Accepts a markdown string and optional parsing options, and returns a Promise of ComarkTree.
|
|
257
313
|
*/
|
|
258
314
|
export type ComarkParseFn = (markdown: string, opts?: ComarkParseFnOptions) => Promise<ComarkTree>;
|
|
259
|
-
export {};
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -36,3 +36,12 @@ export declare function kebabCase(str: string): string;
|
|
|
36
36
|
* @returns The camel case string
|
|
37
37
|
*/
|
|
38
38
|
export declare function camelCase(str: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Retrieves a value from a nested object using a dot-separated key path.
|
|
41
|
+
* @param data - The object to retrieve the value from.
|
|
42
|
+
* @param key - The dot-separated key path to the value.
|
|
43
|
+
* @returns The value at the specified key path, or `undefined` if the key path does not exist.
|
|
44
|
+
*/
|
|
45
|
+
export declare function get(data: unknown, key: string): unknown;
|
|
46
|
+
export { resolveAttributes, resolveAttribute } from '../internal/stringify/attributes.ts';
|
|
47
|
+
export type { ResolveAttributesOptions } from '../internal/stringify/attributes.ts';
|
package/dist/utils/index.js
CHANGED
|
@@ -147,3 +147,27 @@ function splitByCase(str) {
|
|
|
147
147
|
return parts;
|
|
148
148
|
}
|
|
149
149
|
// #endregion
|
|
150
|
+
// #region Object Utils
|
|
151
|
+
/**
|
|
152
|
+
* Retrieves a value from a nested object using a dot-separated key path.
|
|
153
|
+
* @param data - The object to retrieve the value from.
|
|
154
|
+
* @param key - The dot-separated key path to the value.
|
|
155
|
+
* @returns The value at the specified key path, or `undefined` if the key path does not exist.
|
|
156
|
+
*/
|
|
157
|
+
export function get(data, key) {
|
|
158
|
+
const keys = key.split('.');
|
|
159
|
+
let value = data;
|
|
160
|
+
for (const k of keys) {
|
|
161
|
+
if (value && typeof value === 'object' && k in value) {
|
|
162
|
+
value = value[k];
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
// #endregion
|
|
171
|
+
// Re-export the shared attribute resolvers so framework renderers can apply the
|
|
172
|
+
// same `:prefix` semantics as the HTML/ANSI handlers without duplicating logic.
|
|
173
|
+
export { resolveAttributes, resolveAttribute } from "../internal/stringify/attributes.js";
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../src/vite'
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../src/vite.ts'
|