comark 0.0.1 → 0.1.1

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 (112) hide show
  1. package/README.md +104 -0
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.js +6 -0
  4. package/dist/internal/frontmatter.d.ts +16 -0
  5. package/dist/internal/frontmatter.js +43 -0
  6. package/dist/internal/parse/auto-close/index.d.ts +12 -0
  7. package/dist/internal/parse/auto-close/index.js +457 -0
  8. package/dist/internal/parse/auto-close/table.d.ts +4 -0
  9. package/dist/internal/parse/auto-close/table.js +161 -0
  10. package/dist/internal/parse/auto-unwrap.d.ts +20 -0
  11. package/dist/internal/parse/auto-unwrap.js +42 -0
  12. package/dist/internal/parse/html/html_block_rule.d.ts +2 -0
  13. package/dist/internal/parse/html/html_block_rule.js +60 -0
  14. package/dist/internal/parse/html/html_blocks.d.ts +2 -0
  15. package/dist/internal/parse/html/html_blocks.js +66 -0
  16. package/dist/internal/parse/html/html_inline_rule.d.ts +2 -0
  17. package/dist/internal/parse/html/html_inline_rule.js +43 -0
  18. package/dist/internal/parse/html/html_re.d.ts +3 -0
  19. package/dist/internal/parse/html/html_re.js +18 -0
  20. package/dist/internal/parse/html/index.d.ts +18 -0
  21. package/dist/internal/parse/html/index.js +122 -0
  22. package/dist/internal/parse/incremental.d.ts +12 -0
  23. package/dist/internal/parse/incremental.js +39 -0
  24. package/dist/internal/parse/token-processor.d.ts +9 -0
  25. package/dist/internal/parse/token-processor.js +803 -0
  26. package/dist/internal/props-validation.d.ts +12 -0
  27. package/dist/internal/props-validation.js +112 -0
  28. package/dist/internal/stringify/attributes.d.ts +21 -0
  29. package/dist/internal/stringify/attributes.js +67 -0
  30. package/dist/internal/stringify/handlers/a.d.ts +3 -0
  31. package/dist/internal/stringify/handlers/a.js +11 -0
  32. package/dist/internal/stringify/handlers/blockquote.d.ts +3 -0
  33. package/dist/internal/stringify/handlers/blockquote.js +18 -0
  34. package/dist/internal/stringify/handlers/br.d.ts +3 -0
  35. package/dist/internal/stringify/handlers/br.js +3 -0
  36. package/dist/internal/stringify/handlers/code.d.ts +3 -0
  37. package/dist/internal/stringify/handlers/code.js +11 -0
  38. package/dist/internal/stringify/handlers/comment.d.ts +3 -0
  39. package/dist/internal/stringify/handlers/comment.js +6 -0
  40. package/dist/internal/stringify/handlers/del.d.ts +3 -0
  41. package/dist/internal/stringify/handlers/del.js +4 -0
  42. package/dist/internal/stringify/handlers/emphesis.d.ts +3 -0
  43. package/dist/internal/stringify/handlers/emphesis.js +13 -0
  44. package/dist/internal/stringify/handlers/heading.d.ts +3 -0
  45. package/dist/internal/stringify/handlers/heading.js +7 -0
  46. package/dist/internal/stringify/handlers/hr.d.ts +3 -0
  47. package/dist/internal/stringify/handlers/hr.js +3 -0
  48. package/dist/internal/stringify/handlers/html.d.ts +3 -0
  49. package/dist/internal/stringify/handlers/html.js +73 -0
  50. package/dist/internal/stringify/handlers/img.d.ts +3 -0
  51. package/dist/internal/stringify/handlers/img.js +9 -0
  52. package/dist/internal/stringify/handlers/index.d.ts +2 -0
  53. package/dist/internal/stringify/handlers/index.js +56 -0
  54. package/dist/internal/stringify/handlers/li.d.ts +3 -0
  55. package/dist/internal/stringify/handlers/li.js +43 -0
  56. package/dist/internal/stringify/handlers/math.d.ts +3 -0
  57. package/dist/internal/stringify/handlers/math.js +8 -0
  58. package/dist/internal/stringify/handlers/mdc.d.ts +3 -0
  59. package/dist/internal/stringify/handlers/mdc.js +47 -0
  60. package/dist/internal/stringify/handlers/mermaid.d.ts +3 -0
  61. package/dist/internal/stringify/handlers/mermaid.js +8 -0
  62. package/dist/internal/stringify/handlers/ol.d.ts +3 -0
  63. package/dist/internal/stringify/handlers/ol.js +18 -0
  64. package/dist/internal/stringify/handlers/p.d.ts +3 -0
  65. package/dist/internal/stringify/handlers/p.js +8 -0
  66. package/dist/internal/stringify/handlers/pre.d.ts +3 -0
  67. package/dist/internal/stringify/handlers/pre.js +60 -0
  68. package/dist/internal/stringify/handlers/strong.d.ts +3 -0
  69. package/dist/internal/stringify/handlers/strong.js +13 -0
  70. package/dist/internal/stringify/handlers/table.d.ts +8 -0
  71. package/dist/internal/stringify/handlers/table.js +180 -0
  72. package/dist/internal/stringify/handlers/template.d.ts +3 -0
  73. package/dist/internal/stringify/handlers/template.js +14 -0
  74. package/dist/internal/stringify/handlers/ul.d.ts +3 -0
  75. package/dist/internal/stringify/handlers/ul.js +18 -0
  76. package/dist/internal/stringify/indent.d.ts +4 -0
  77. package/dist/internal/stringify/indent.js +8 -0
  78. package/dist/internal/stringify/state.d.ts +13 -0
  79. package/dist/internal/stringify/state.js +121 -0
  80. package/dist/internal/yaml.d.ts +12 -0
  81. package/dist/internal/yaml.js +51 -0
  82. package/dist/parse.d.ts +66 -0
  83. package/dist/parse.js +163 -0
  84. package/dist/plugins/alert.d.ts +2 -0
  85. package/dist/plugins/alert.js +66 -0
  86. package/dist/plugins/emoji.d.ts +3 -0
  87. package/dist/plugins/emoji.js +438 -0
  88. package/dist/plugins/headings.d.ts +48 -0
  89. package/dist/plugins/headings.js +85 -0
  90. package/dist/plugins/highlight.d.ts +71 -0
  91. package/dist/plugins/highlight.js +234 -0
  92. package/dist/plugins/math.d.ts +59 -0
  93. package/dist/plugins/math.js +263 -0
  94. package/dist/plugins/mermaid.d.ts +38 -0
  95. package/dist/plugins/mermaid.js +185 -0
  96. package/dist/plugins/security.d.ts +11 -0
  97. package/dist/plugins/security.js +32 -0
  98. package/dist/plugins/summary.d.ts +2 -0
  99. package/dist/plugins/summary.js +22 -0
  100. package/dist/plugins/task-list.d.ts +8 -0
  101. package/dist/plugins/task-list.js +117 -0
  102. package/dist/plugins/toc.d.ts +15 -0
  103. package/dist/plugins/toc.js +118 -0
  104. package/dist/render.d.ts +18 -0
  105. package/dist/render.js +29 -0
  106. package/dist/types.d.ts +258 -0
  107. package/dist/types.js +1 -0
  108. package/dist/utils/caret.d.ts +7 -0
  109. package/dist/utils/caret.js +36 -0
  110. package/dist/utils/index.d.ts +38 -0
  111. package/dist/utils/index.js +149 -0
  112. package/package.json +75 -9
@@ -0,0 +1,263 @@
1
+ import katex from 'katex';
2
+ /**
3
+ * Render LaTeX math expression to HTML using KaTeX
4
+ *
5
+ * @param code LaTeX math expression
6
+ * @param displayMode Whether to render in display mode
7
+ * @param config KaTeX configuration
8
+ * @returns HTML string
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const html = renderMath('E = mc^2', false)
13
+ * const display = renderMath('x^2', true)
14
+ * ```
15
+ */
16
+ export function renderMath(code, displayMode, config = {}) {
17
+ try {
18
+ const options = {
19
+ displayMode,
20
+ throwOnError: config.throwOnError ?? false,
21
+ ...config.options,
22
+ };
23
+ return katex.renderToString(code, options);
24
+ }
25
+ catch (error) {
26
+ console.error('Math rendering error:', error);
27
+ if (config.throwOnError) {
28
+ throw error;
29
+ }
30
+ return `<span class="math-error">${error instanceof Error ? error.message : 'Failed to render math'}</span>`;
31
+ }
32
+ }
33
+ /**
34
+ * Check if code is valid LaTeX math syntax
35
+ *
36
+ * @param code LaTeX math expression
37
+ * @returns true if valid, false otherwise
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * validateMath('E = mc^2') // true
42
+ * validateMath('\\invalid') // false
43
+ * ```
44
+ */
45
+ export function validateMath(code) {
46
+ try {
47
+ katex.renderToString(code, { throwOnError: true });
48
+ return true;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ /**
55
+ * markdown-it plugin for inline display math ($$...$$)
56
+ * This handles $$...$$ within a paragraph (same line)
57
+ */
58
+ function mathInlineDisplayRule(state, silent, _config) {
59
+ const start = state.pos;
60
+ const max = state.posMax;
61
+ // Check if we start with $$
62
+ if (start + 1 >= max)
63
+ return false;
64
+ if (state.src.charCodeAt(start) !== 0x24 /* $ */)
65
+ return false;
66
+ if (state.src.charCodeAt(start + 1) !== 0x24 /* $ */)
67
+ return false;
68
+ // Find closing $$
69
+ let pos = start + 2;
70
+ while (pos + 1 < max) {
71
+ // Stop at newline
72
+ if (state.src.charCodeAt(pos) === 0x0A /* \n */) {
73
+ return false;
74
+ }
75
+ // Check for $$
76
+ if (state.src.charCodeAt(pos) === 0x24 && state.src.charCodeAt(pos + 1) === 0x24) {
77
+ // Found closing $$
78
+ const content = state.src.slice(start + 2, pos);
79
+ if (!silent) {
80
+ const token = state.push('math_inline', 'math', 0);
81
+ token.content = content;
82
+ token.markup = '$$';
83
+ token.meta = { display: true }; // Mark as display mode
84
+ }
85
+ state.pos = pos + 2;
86
+ return true;
87
+ }
88
+ pos++;
89
+ }
90
+ return false;
91
+ }
92
+ /**
93
+ * markdown-it plugin for inline math ($...$)
94
+ */
95
+ function mathInlineRule(state, silent, _config) {
96
+ const start = state.pos;
97
+ const max = state.posMax;
98
+ // Check if we start with $
99
+ if (state.src.charCodeAt(start) !== 0x24 /* $ */) {
100
+ return false;
101
+ }
102
+ // Don't match $$ (that's display math)
103
+ if (start + 1 < max && state.src.charCodeAt(start + 1) === 0x24) {
104
+ return false;
105
+ }
106
+ // Find closing $
107
+ let pos = start + 1;
108
+ let foundClose = false;
109
+ while (pos < max) {
110
+ const char = state.src.charCodeAt(pos);
111
+ // Stop at newline - $ must close on same line
112
+ if (char === 0x0A /* \n */) {
113
+ return false;
114
+ }
115
+ if (char === 0x24 /* $ */) {
116
+ // Found potential closing $
117
+ // Make sure it's not escaped, we have content (at least 1 char),
118
+ // it's not preceded by another $ (which would make it $$),
119
+ // and it's not followed by another $ (which would make it $$)
120
+ const hasContent = pos > start + 1;
121
+ const notEscaped = pos === start + 1 || state.src.charCodeAt(pos - 1) !== 0x5C; /* \ */
122
+ const notPrecededByDollar = pos === start + 1 || state.src.charCodeAt(pos - 1) !== 0x24;
123
+ const notFollowedByDollar = pos + 1 >= max || state.src.charCodeAt(pos + 1) !== 0x24;
124
+ if (hasContent && notEscaped && notPrecededByDollar && notFollowedByDollar) {
125
+ foundClose = true;
126
+ break;
127
+ }
128
+ }
129
+ pos++;
130
+ }
131
+ if (!foundClose) {
132
+ return false;
133
+ }
134
+ // Extract math content
135
+ const content = state.src.slice(start + 1, pos);
136
+ // Don't match if content looks like a price or number (starts with digits and contains "or" or ends at end of sentence)
137
+ if (/^\d/.test(content)) {
138
+ return false;
139
+ }
140
+ if (!silent) {
141
+ const token = state.push('math_inline', 'math', 0);
142
+ token.content = content;
143
+ token.markup = '$';
144
+ }
145
+ state.pos = pos + 1;
146
+ return true;
147
+ }
148
+ /**
149
+ * markdown-it plugin for display math ($$...$$)
150
+ */
151
+ function mathBlockRule(state, startLine, endLine, silent) {
152
+ const start = state.bMarks[startLine] + state.tShift[startLine];
153
+ const max = state.eMarks[startLine];
154
+ // Check if line starts with $$
155
+ if (start + 2 > max)
156
+ return false;
157
+ if (state.src.charCodeAt(start) !== 0x24 /* $ */)
158
+ return false;
159
+ if (state.src.charCodeAt(start + 1) !== 0x24 /* $ */)
160
+ return false;
161
+ const marker = state.src.slice(start, start + 2);
162
+ const pos = start + 2;
163
+ // Check if it's inline $$ (closing on same line)
164
+ const firstLine = state.src.slice(pos, max);
165
+ const closePos = firstLine.indexOf('$$');
166
+ if (closePos !== -1) {
167
+ // Inline display math on single line
168
+ if (silent)
169
+ return true;
170
+ const content = firstLine.slice(0, closePos);
171
+ const token = state.push('math_block', 'div', 0);
172
+ token.content = content;
173
+ token.markup = marker;
174
+ token.block = true;
175
+ token.map = [startLine, startLine + 1];
176
+ state.line = startLine + 1;
177
+ return true;
178
+ }
179
+ // Multi-line display math
180
+ let nextLine = startLine;
181
+ let autoClose = false;
182
+ // Search for closing $$
183
+ while (nextLine < endLine) {
184
+ nextLine++;
185
+ if (nextLine >= endLine)
186
+ break;
187
+ const lineStart = state.bMarks[nextLine] + state.tShift[nextLine];
188
+ const lineMax = state.eMarks[nextLine];
189
+ if (lineStart < lineMax && state.sCount[nextLine] < state.blkIndent) {
190
+ // Non-empty line with less indent - might close the block
191
+ break;
192
+ }
193
+ const lineText = state.src.slice(lineStart, lineMax);
194
+ if (lineText.trim() === '$$') {
195
+ autoClose = true;
196
+ break;
197
+ }
198
+ // Check if line contains $$
199
+ if (lineText.includes('$$')) {
200
+ autoClose = true;
201
+ break;
202
+ }
203
+ }
204
+ if (!autoClose) {
205
+ return false;
206
+ }
207
+ if (silent)
208
+ return true;
209
+ // Extract content between $$ markers
210
+ const contentLines = [];
211
+ for (let i = startLine + 1; i < nextLine; i++) {
212
+ const lineStart = state.bMarks[i] + state.tShift[i];
213
+ const lineMax = state.eMarks[i];
214
+ contentLines.push(state.src.slice(lineStart, lineMax));
215
+ }
216
+ const content = contentLines.join('\n');
217
+ const token = state.push('math_block', 'div', 0);
218
+ token.content = content;
219
+ token.markup = marker;
220
+ token.block = true;
221
+ token.map = [startLine, nextLine + 1];
222
+ state.line = nextLine + 1;
223
+ return true;
224
+ }
225
+ /**
226
+ * Create markdown-it plugin for math support
227
+ */
228
+ function markdownItMath(md, config = {}) {
229
+ // Add inline display math rule ($$...$$) - must come before inline math ($...$)
230
+ md.inline.ruler.before('escape', 'math_inline_display', (state, silent) => mathInlineDisplayRule(state, silent, config));
231
+ // Add inline math rule ($...$)
232
+ md.inline.ruler.before('escape', 'math_inline', (state, silent) => mathInlineRule(state, silent, config));
233
+ // Add block math rule ($$...$$)
234
+ md.block.ruler.before('fence', 'math_block', mathBlockRule, {
235
+ alt: ['paragraph', 'reference', 'blockquote', 'list'],
236
+ });
237
+ }
238
+ /**
239
+ * Create math plugin for comark
240
+ *
241
+ * This plugin processes markdown-it tokens and converts math expressions
242
+ * with $ and $$ delimiters to HTML using KaTeX.
243
+ *
244
+ * @param config Math rendering configuration
245
+ *
246
+ * @example
247
+ * ```ts
248
+ * import { parse } from 'comark'
249
+ * import math from 'comark/plugins/math'
250
+ *
251
+ * const result = await parse('Inline $x^2$ and display $$E = mc^2$$', {
252
+ * plugins: [math({ throwOnError: false })]
253
+ * })
254
+ * ```
255
+ */
256
+ export default function math(config) {
257
+ return {
258
+ name: 'math',
259
+ markdownItPlugins: [
260
+ ((md) => markdownItMath(md, config ?? {})),
261
+ ],
262
+ };
263
+ }
@@ -0,0 +1,38 @@
1
+ import type { ComarkPlugin } from 'comark';
2
+ export type ThemeNames = 'zinc-light' | 'zinc-dark' | 'tokyo-night' | 'tokyo-night-storm' | 'tokyo-night-light' | 'catppuccin-mocha' | 'catppuccin-latte' | 'nord' | 'nord-light' | 'dracula' | 'github-light' | 'github-dark' | 'solarized-light' | 'solarized-dark' | 'one-dark';
3
+ export interface MermaidConfig {
4
+ /**
5
+ * Theme for mermaid diagrams
6
+ * @default undefined
7
+ */
8
+ theme?: ThemeNames;
9
+ /**
10
+ * Theme for mermaid diagrams in dark mode
11
+ * @default undefined
12
+ */
13
+ themeDark?: ThemeNames;
14
+ }
15
+ export declare function searchProps(content: string, index?: number): {
16
+ props: [string, string][];
17
+ index: number;
18
+ } | undefined;
19
+ /**
20
+ * Create mermaid plugin for comark
21
+ *
22
+ * This plugin processes markdown-it tokens and identifies mermaid code blocks
23
+ * for rendering by Vue/React components.
24
+ *
25
+ * @param config Mermaid rendering configuration
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { parse } from 'comark'
30
+ * import { createMermaidPlugin } from 'comark/plugins/mermaid'
31
+ *
32
+ * const mermaid = createMermaidPlugin({ theme: 'dark' })
33
+ * const result = await parse('```mermaid\ngraph TD; A-->B;\n```', {
34
+ * plugins: [mermaid()]
35
+ * })
36
+ * ```
37
+ */
38
+ export default function comarkMermaid(config?: MermaidConfig): ComarkPlugin;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * markdown-it plugin for mermaid diagrams
3
+ * This handles ```mermaid code blocks
4
+ */
5
+ function markdownItMermaid(md, config) {
6
+ md.core.ruler.after('block', 'replace-pre', (state) => {
7
+ for (const token of state.tokens) {
8
+ if (token.type === 'fence' && token.info?.startsWith('mermaid')) {
9
+ let info = token.info.substring(7).trim();
10
+ let props = [];
11
+ // extract props from info
12
+ const curlyBraceIndex = info.indexOf('{');
13
+ if (curlyBraceIndex !== -1) {
14
+ const result = searchProps(info.substring(curlyBraceIndex));
15
+ if (result) {
16
+ props = result.props;
17
+ info = info.substring(result.index);
18
+ }
19
+ }
20
+ // set type, tag, props and content
21
+ token.type = 'mermaid';
22
+ token.tag = 'mermaid';
23
+ for (const prop of props) {
24
+ token.attrJoin(prop[0], prop[1]);
25
+ }
26
+ if (config?.theme) {
27
+ token.attrSet('theme', config.theme);
28
+ }
29
+ if (config?.themeDark) {
30
+ token.attrSet('theme-dark', config.themeDark);
31
+ }
32
+ token.info = info;
33
+ token.attrSet('content', token.content);
34
+ }
35
+ }
36
+ });
37
+ }
38
+ export function searchProps(content, index = 0) {
39
+ const bracketPairs = {
40
+ '[': ']',
41
+ '{': '}',
42
+ '(': ')',
43
+ };
44
+ const quotePairs = {
45
+ '\'': '\'',
46
+ '"': '"',
47
+ '`': '`',
48
+ };
49
+ if (content[index] !== '{')
50
+ throw new Error(`Invalid props, expected \`{\` but got '${content[index]}'`);
51
+ const props = [];
52
+ // Vue's mustache {{ }} syntax
53
+ if (content[index + 1] === '{')
54
+ return undefined;
55
+ index += 1;
56
+ while (index < content.length) {
57
+ if (content[index] === '\\') {
58
+ index += 2;
59
+ }
60
+ else if (content[index] === '}') {
61
+ index += 1;
62
+ break;
63
+ }
64
+ else if (content[index] === ' ') {
65
+ index += 1;
66
+ }
67
+ else if (content[index] === '.') {
68
+ index += 1;
69
+ props.push([
70
+ 'class',
71
+ searchUntil(' #.}'),
72
+ ]);
73
+ }
74
+ else if (content[index] === '#') {
75
+ index += 1;
76
+ props.push([
77
+ 'id',
78
+ searchUntil(' #.}'),
79
+ ]);
80
+ }
81
+ else {
82
+ const start = index;
83
+ while (index < content.length) {
84
+ index += 1;
85
+ if (' }='.includes(content[index]))
86
+ break;
87
+ }
88
+ const char = content[index];
89
+ if (start !== index) {
90
+ const key = content.slice(start, index).trim();
91
+ if (char === '=') {
92
+ index += 1;
93
+ props.push([
94
+ key,
95
+ searchValue(),
96
+ ]);
97
+ }
98
+ else {
99
+ props.push([
100
+ key,
101
+ 'true',
102
+ ]);
103
+ }
104
+ }
105
+ }
106
+ }
107
+ function searchUntil(str) {
108
+ const start = index;
109
+ while (index < content.length) {
110
+ index += 1;
111
+ if (content[index] === '\\')
112
+ index += 2;
113
+ if (str.includes(content[index]))
114
+ break;
115
+ }
116
+ return content.slice(start, index);
117
+ }
118
+ function searchValue() {
119
+ const start = index;
120
+ if (content[index] in bracketPairs) {
121
+ searchBracket(bracketPairs[content[index]]);
122
+ index += 1;
123
+ return content.slice(start, index);
124
+ }
125
+ else if (content[index] in quotePairs) {
126
+ searchString(quotePairs[content[index]]);
127
+ index += 1;
128
+ return content.slice(start, index);
129
+ }
130
+ else {
131
+ // unquoted value
132
+ return searchUntil(' }');
133
+ }
134
+ }
135
+ function searchBracket(end) {
136
+ while (index < content.length) {
137
+ index++;
138
+ if (content[index] in quotePairs)
139
+ searchString(quotePairs[content[index]]);
140
+ else if (content[index] in bracketPairs)
141
+ searchBracket(bracketPairs[content[index]]);
142
+ else if (content[index] === end)
143
+ return;
144
+ }
145
+ }
146
+ function searchString(end) {
147
+ return searchUntil(end);
148
+ }
149
+ // Escale quotes
150
+ props.forEach((v) => {
151
+ if (v[1].match(/^(['"`]).*\1$/))
152
+ v[1] = v[1].slice(1, -1);
153
+ });
154
+ return {
155
+ props,
156
+ index,
157
+ };
158
+ }
159
+ /**
160
+ * Create mermaid plugin for comark
161
+ *
162
+ * This plugin processes markdown-it tokens and identifies mermaid code blocks
163
+ * for rendering by Vue/React components.
164
+ *
165
+ * @param config Mermaid rendering configuration
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * import { parse } from 'comark'
170
+ * import { createMermaidPlugin } from 'comark/plugins/mermaid'
171
+ *
172
+ * const mermaid = createMermaidPlugin({ theme: 'dark' })
173
+ * const result = await parse('```mermaid\ngraph TD; A-->B;\n```', {
174
+ * plugins: [mermaid()]
175
+ * })
176
+ * ```
177
+ */
178
+ export default function comarkMermaid(config) {
179
+ return {
180
+ name: 'mermaid',
181
+ markdownItPlugins: [
182
+ ((md) => markdownItMermaid(md, config)),
183
+ ],
184
+ };
185
+ }
@@ -0,0 +1,11 @@
1
+ import type { ComarkPlugin } from 'comark';
2
+ import type { PropsValidationOptions } from '../internal/props-validation';
3
+ interface SecurityOptions extends PropsValidationOptions {
4
+ /**
5
+ * Tags to remove entirely from the output tree.
6
+ * @default []
7
+ */
8
+ blockedTags?: string[];
9
+ }
10
+ export default function security(options?: SecurityOptions): ComarkPlugin;
11
+ export {};
@@ -0,0 +1,32 @@
1
+ import { visit } from 'comark/utils';
2
+ import { validateProps } from '../internal/props-validation';
3
+ export default function security(options = {}) {
4
+ const { blockedTags = [], allowedLinkPrefixes, allowedImagePrefixes, allowedProtocols, defaultOrigin, allowDataImages, } = options;
5
+ const dropSet = new Set(blockedTags.map(t => t.toLowerCase()));
6
+ const propsOptions = {
7
+ allowedLinkPrefixes,
8
+ allowedImagePrefixes,
9
+ allowedProtocols,
10
+ defaultOrigin,
11
+ allowDataImages,
12
+ };
13
+ return {
14
+ name: 'security',
15
+ post(state) {
16
+ visit(state.tree, node => typeof node !== 'string' && node[0] !== null, (node) => {
17
+ const element = node;
18
+ // return false to remove the node from the tree
19
+ if (dropSet.has(element[0].toLowerCase())) {
20
+ return false;
21
+ }
22
+ const keys = Object.keys(element[1]);
23
+ /**
24
+ * If the element has any props, validate them
25
+ */
26
+ if (keys.length) {
27
+ element[1] = validateProps(element[0], element[1], propsOptions);
28
+ }
29
+ });
30
+ },
31
+ };
32
+ }
@@ -0,0 +1,2 @@
1
+ import type { ComarkPlugin } from '../types';
2
+ export default function summary(delimiter?: string): ComarkPlugin;
@@ -0,0 +1,22 @@
1
+ import { applyAutoUnwrap } from '../internal/parse/auto-unwrap';
2
+ import { marmdownItTokensToComarkTree } from '../internal/parse/token-processor';
3
+ export default function summary(delimiter = '<!-- more -->') {
4
+ return {
5
+ name: 'summary',
6
+ post(state) {
7
+ let summary;
8
+ const delimiterIndex = state.tokens.findIndex((token) => token.type === 'html_block' && token.content?.includes(delimiter));
9
+ if (delimiterIndex !== -1) {
10
+ const summaryTokens = state.tokens.slice(0, delimiterIndex);
11
+ summary = marmdownItTokensToComarkTree(summaryTokens);
12
+ // Apply auto-unwrap to summary as well
13
+ if (state.options.autoUnwrap) {
14
+ summary = summary?.map((child) => applyAutoUnwrap(child));
15
+ }
16
+ if (summary) {
17
+ state.tree.meta.summary = summary;
18
+ }
19
+ }
20
+ },
21
+ };
22
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Custom task list plugin for markdown-it that works with @comark/markdown-it
3
+ *
4
+ * This plugin runs before inline parsing to prevent Comark from interpreting
5
+ * task list markers [X] and [ ] as Comark inline span syntax.
6
+ */
7
+ import type { ComarkPlugin } from 'comark';
8
+ export default function comarkTaskList(): ComarkPlugin;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Custom task list plugin for markdown-it that works with @comark/markdown-it
3
+ *
4
+ * This plugin runs before inline parsing to prevent Comark from interpreting
5
+ * task list markers [X] and [ ] as Comark inline span syntax.
6
+ */
7
+ function attrSet(token, name, value) {
8
+ const index = token.attrIndex(name);
9
+ const attr = [name, value];
10
+ if (index < 0) {
11
+ if (!token.attrs) {
12
+ token.attrs = [];
13
+ }
14
+ token.attrs.push(attr);
15
+ }
16
+ else {
17
+ token.attrs[index] = attr;
18
+ }
19
+ }
20
+ function findParentListItem(tokens, index) {
21
+ // Look backwards for list_item_open
22
+ for (let i = index - 1; i >= 0; i--) {
23
+ if (tokens[i].type === 'list_item_open') {
24
+ return i;
25
+ }
26
+ if (tokens[i].type === 'list_item_close') {
27
+ // We've gone past the current list item
28
+ return -1;
29
+ }
30
+ }
31
+ return -1;
32
+ }
33
+ function findParentList(tokens, listItemIndex) {
34
+ const targetLevel = tokens[listItemIndex].level - 1;
35
+ // Look backwards for the list (ul/ol) that contains this list item
36
+ for (let i = listItemIndex - 1; i >= 0; i--) {
37
+ if (tokens[i].level === targetLevel
38
+ && (tokens[i].type === 'bullet_list_open' || tokens[i].type === 'ordered_list_open')) {
39
+ return i;
40
+ }
41
+ }
42
+ return -1;
43
+ }
44
+ function markdownItTaskList(md, options) {
45
+ const disableCheckboxes = !(options?.enabled ?? false);
46
+ // Run BEFORE inline parsing to prevent Comark from processing task list markers
47
+ md.core.ruler.before('inline', 'task-lists-mdc', (state) => {
48
+ const tokens = state.tokens;
49
+ for (let i = 0; i < tokens.length; i++) {
50
+ const token = tokens[i];
51
+ // Look for list items that might contain task lists
52
+ if (token.type === 'inline' && token.content) {
53
+ const parentIdx = findParentListItem(tokens, i);
54
+ if (parentIdx >= 0) {
55
+ // Check if content starts with task list marker
56
+ const match = token.content.match(/^(\[[ x]\])\s+/i);
57
+ if (match) {
58
+ const isChecked = match[1].toLowerCase() === '[x]';
59
+ // Mark the list item with task-list-item class
60
+ attrSet(tokens[parentIdx], 'class', 'task-list-item');
61
+ // Mark the parent list with contains-task-list class
62
+ const listIdx = findParentList(tokens, parentIdx);
63
+ if (listIdx >= 0) {
64
+ attrSet(tokens[listIdx], 'class', 'contains-task-list');
65
+ }
66
+ // Replace the task marker with a placeholder that won't be processed by Comark
67
+ // We use a special format that we can detect later
68
+ // Keep one space after the placeholder to match expected output
69
+ const checkboxPlaceholder = `{{TASK_CHECKBOX_${isChecked ? 'CHECKED' : 'UNCHECKED'}}} `;
70
+ token.content = token.content.replace(/^\[[ x]\]\s+/i, checkboxPlaceholder);
71
+ }
72
+ }
73
+ }
74
+ }
75
+ });
76
+ // Run AFTER inline parsing to replace placeholders with actual checkbox HTML
77
+ // @ts-expect-error - after is not a valid method on the ruler types
78
+ md.core.ruler.after('inline', 'task-lists-mdc-post', (state) => {
79
+ const tokens = state.tokens;
80
+ for (let i = 0; i < tokens.length; i++) {
81
+ const token = tokens[i];
82
+ if (token.type === 'inline' && token.children) {
83
+ for (let j = 0; j < token.children.length; j++) {
84
+ const child = token.children[j];
85
+ if (child.type === 'text' && child.content) {
86
+ // Check for our checkbox placeholder
87
+ const checkedMatch = child.content.match(/^\{\{TASK_CHECKBOX_CHECKED\}\}/);
88
+ const uncheckedMatch = child.content.match(/^\{\{TASK_CHECKBOX_UNCHECKED\}\}/);
89
+ if (checkedMatch || uncheckedMatch) {
90
+ const isChecked = !!checkedMatch;
91
+ // Create checkbox token
92
+ const checkbox = new state.Token('mdc_inline_component', 'input', 0);
93
+ checkbox.attrs = [['class', 'task-list-item-checkbox'], ['type', 'checkbox']];
94
+ if (disableCheckboxes) {
95
+ checkbox.attrs.push([':disabled', 'true']);
96
+ }
97
+ if (isChecked) {
98
+ checkbox.attrs.push([':checked', 'true']);
99
+ }
100
+ // Remove placeholder from text
101
+ child.content = child.content.replace(/^\{\{TASK_CHECKBOX_(CHECKED|UNCHECKED)\}\}/, '');
102
+ // Insert checkbox before the text
103
+ token.children.splice(j, 0, checkbox);
104
+ j++; // Skip the newly inserted checkbox
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ });
111
+ }
112
+ export default function comarkTaskList() {
113
+ return {
114
+ name: 'task-list',
115
+ markdownItPlugins: [markdownItTaskList],
116
+ };
117
+ }