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,48 @@
1
+ import type { ComarkPlugin } from 'comark';
2
+ export interface HeadingsOptions {
3
+ /**
4
+ * Tag to extract as title and set to `tree.meta.title`.
5
+ * @default 'h1'
6
+ */
7
+ titleTag?: string;
8
+ /**
9
+ * Tag to extract as description and set to `tree.meta.description`.
10
+ * Useful alternatives: `'blockquote'`
11
+ * @default 'p'
12
+ */
13
+ descriptionTag?: string;
14
+ /**
15
+ * Whether to remove the extracted nodes from the tree.
16
+ * @default true
17
+ */
18
+ remove?: boolean;
19
+ }
20
+ /**
21
+ * Extracts the title and description from the top of the document and sets
22
+ * them on `tree.meta.title` and `tree.meta.description`.
23
+ *
24
+ * The plugin scans the top-level nodes (ignoring `<hr>` and bare text nodes)
25
+ * and checks the first two content nodes in order:
26
+ *
27
+ * 1. If the first node matches `titleTag` (default `h1`), its text content is
28
+ * written to `tree.meta.title`.
29
+ * 2. If the next content node matches `descriptionTag` (default `p`), its text
30
+ * content is written to `tree.meta.description`. When no title was found,
31
+ * this check starts from the very first content node.
32
+ *
33
+ * Both nodes are removed from the tree by default so they are not rendered
34
+ * twice. Set `remove: false` to keep them in place.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * // Default — h1 as title, first paragraph as description
39
+ * headings()
40
+ *
41
+ * // Use a blockquote as the description instead of a paragraph
42
+ * headings({ descriptionTag: 'blockquote' })
43
+ *
44
+ * // Extract metadata without removing the nodes from the tree
45
+ * headings({ remove: false })
46
+ * ```
47
+ */
48
+ export default function headings(options?: HeadingsOptions): ComarkPlugin;
@@ -0,0 +1,85 @@
1
+ function getTag(node) {
2
+ if (Array.isArray(node) && node.length >= 1) {
3
+ return node[0];
4
+ }
5
+ return null;
6
+ }
7
+ function getChildren(node) {
8
+ if (Array.isArray(node) && node.length > 2) {
9
+ return node.slice(2);
10
+ }
11
+ return [];
12
+ }
13
+ function flattenNodeText(node) {
14
+ if (typeof node === 'string') {
15
+ return node;
16
+ }
17
+ if (Array.isArray(node)) {
18
+ return getChildren(node).reduce((text, child) => {
19
+ return text + flattenNodeText(child);
20
+ }, '');
21
+ }
22
+ return '';
23
+ }
24
+ /**
25
+ * Extracts the title and description from the top of the document and sets
26
+ * them on `tree.meta.title` and `tree.meta.description`.
27
+ *
28
+ * The plugin scans the top-level nodes (ignoring `<hr>` and bare text nodes)
29
+ * and checks the first two content nodes in order:
30
+ *
31
+ * 1. If the first node matches `titleTag` (default `h1`), its text content is
32
+ * written to `tree.meta.title`.
33
+ * 2. If the next content node matches `descriptionTag` (default `p`), its text
34
+ * content is written to `tree.meta.description`. When no title was found,
35
+ * this check starts from the very first content node.
36
+ *
37
+ * Both nodes are removed from the tree by default so they are not rendered
38
+ * twice. Set `remove: false` to keep them in place.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * // Default — h1 as title, first paragraph as description
43
+ * headings()
44
+ *
45
+ * // Use a blockquote as the description instead of a paragraph
46
+ * headings({ descriptionTag: 'blockquote' })
47
+ *
48
+ * // Extract metadata without removing the nodes from the tree
49
+ * headings({ remove: false })
50
+ * ```
51
+ */
52
+ export default function headings(options = {}) {
53
+ const { titleTag = 'h1', descriptionTag = 'p', remove = true } = options;
54
+ return {
55
+ name: 'headings',
56
+ post(state) {
57
+ const nodes = state.tree.nodes;
58
+ // Top-level content nodes — skip raw text nodes and <hr>
59
+ const contentNodes = nodes.filter(node => Array.isArray(node) && getTag(node) !== 'hr');
60
+ let titleNodeIndex = -1;
61
+ let descriptionNodeIndex = -1;
62
+ const first = contentNodes[0];
63
+ if (first && getTag(first) === titleTag) {
64
+ titleNodeIndex = nodes.indexOf(first);
65
+ state.tree.meta.title = flattenNodeText(first);
66
+ }
67
+ // Description is the first content node after the (optional) title
68
+ const afterTitle = titleNodeIndex !== -1 ? contentNodes.slice(1) : contentNodes;
69
+ const second = afterTitle[0];
70
+ if (second && getTag(second) === descriptionTag) {
71
+ descriptionNodeIndex = nodes.indexOf(second);
72
+ state.tree.meta.description = flattenNodeText(second);
73
+ }
74
+ if (remove) {
75
+ // Remove in reverse order to preserve indices
76
+ const toRemove = [titleNodeIndex, descriptionNodeIndex]
77
+ .filter(i => i !== -1)
78
+ .sort((a, b) => b - a);
79
+ for (const i of toRemove) {
80
+ nodes.splice(i, 1);
81
+ }
82
+ }
83
+ },
84
+ };
85
+ }
@@ -0,0 +1,71 @@
1
+ import type { LanguageRegistration } from 'shiki';
2
+ import type { ComarkNode, ComarkTree, ComarkPlugin } from 'comark';
3
+ import type { ShikiPrimitive, ThemeRegistration } from '@shikijs/primitive';
4
+ import type { ShikiTransformer } from '@shikijs/types';
5
+ export interface HighlightOptions {
6
+ /**
7
+ * Whether to use the default language definitions
8
+ * @default true
9
+ */
10
+ registerDefaultLanguages?: boolean;
11
+ /**
12
+ * Whether to use the default theme definitions
13
+ * @default true
14
+ */
15
+ registerDefaultThemes?: boolean;
16
+ /**
17
+ * Languages to preload. If not specified, languages will be loaded on demand.
18
+ * @default undefined (load on demand)
19
+ */
20
+ languages?: Array<LanguageRegistration | LanguageRegistration[]>;
21
+ /**
22
+ * Additional themes to preload
23
+ * @default { light: 'material-theme-lighter', dark: 'material-theme-palenight' }
24
+ */
25
+ themes?: {
26
+ light?: ThemeRegistration;
27
+ dark?: ThemeRegistration;
28
+ };
29
+ /**
30
+ * Transformers to apply to the code blocks
31
+ * @default undefined
32
+ */
33
+ transformers?: ShikiTransformer[];
34
+ /**
35
+ * Whether to add pre styles to the code blocks
36
+ * @default false
37
+ */
38
+ preStyles?: boolean;
39
+ }
40
+ export interface CodeBlockAttributes {
41
+ language?: string;
42
+ class?: string;
43
+ highlights?: number[];
44
+ meta?: string;
45
+ }
46
+ /**
47
+ * Get or create the Shiki highlighter instance
48
+ * Uses a singleton pattern to avoid creating multiple highlighters
49
+ */
50
+ export declare function getHighlighter(options?: HighlightOptions): Promise<ShikiPrimitive>;
51
+ /**
52
+ * Highlight code using Shiki with codeToTokens
53
+ * Returns comark nodes built from hast
54
+ */
55
+ export declare function highlightCode(code: string, attrs: CodeBlockAttributes, options?: HighlightOptions): Promise<{
56
+ nodes: ComarkNode[];
57
+ language: string;
58
+ bgColor?: string;
59
+ fgColor?: string;
60
+ }>;
61
+ /**
62
+ * Apply syntax highlighting to all code blocks in a Comark tree
63
+ * Uses codeToTokens API with batched async operations
64
+ */
65
+ export declare function highlightCodeBlocks(tree: ComarkTree, options?: HighlightOptions): Promise<ComarkTree>;
66
+ /**
67
+ * Reset the highlighter instance
68
+ * Useful for testing or when you want to reconfigure
69
+ */
70
+ export declare function resetHighlighter(): void;
71
+ export default function highlight(options?: HighlightOptions): ComarkPlugin;
@@ -0,0 +1,234 @@
1
+ import { createShikiPrimitive } from '@shikijs/primitive';
2
+ import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
3
+ import { codeToHast } from 'shiki/core';
4
+ let highlighter = null;
5
+ let highlighterPromise = null;
6
+ const loadedThemes = new Set();
7
+ const loadedLanguages = new Set();
8
+ /**
9
+ * Get or create the Shiki highlighter instance
10
+ * Uses a singleton pattern to avoid creating multiple highlighters
11
+ */
12
+ export async function getHighlighter(options = {}) {
13
+ // If highlighter exists, load any new themes that aren't loaded yet
14
+ if (highlighter) {
15
+ const { themes, languages } = await registerDefaults(options);
16
+ await Promise.all(themes.map(theme => loadTheme(highlighter, theme)));
17
+ await Promise.all(languages.map(language => loadLanguage(highlighter, language)));
18
+ return highlighter;
19
+ }
20
+ if (highlighterPromise) {
21
+ return highlighterPromise;
22
+ }
23
+ try {
24
+ highlighterPromise = (async () => {
25
+ const { themes, languages } = await registerDefaults(options);
26
+ const hl = createShikiPrimitive({
27
+ themes: themes,
28
+ langs: languages,
29
+ langAlias: {
30
+ md: 'mdc',
31
+ markdown: 'mdc',
32
+ comark: 'mdc',
33
+ },
34
+ engine: createJavaScriptRegexEngine({ forgiving: true }),
35
+ });
36
+ await Promise.all(themes.map(theme => loadTheme(hl, theme)));
37
+ await Promise.all(languages.map(language => loadLanguage(hl, language)));
38
+ return hl;
39
+ })();
40
+ highlighter = await highlighterPromise;
41
+ highlighterPromise = null;
42
+ return highlighter;
43
+ }
44
+ catch (error) {
45
+ console.error('Failed to create highlighter: make sure `shiki` is installed', error);
46
+ throw error;
47
+ }
48
+ }
49
+ async function registerDefaults(options) {
50
+ const themes = Object.values(options.themes || {});
51
+ const languages = options.languages || [];
52
+ const promises = [];
53
+ if (options.registerDefaultThemes !== false) {
54
+ promises.push(import('@shikijs/themes/material-theme-lighter').then(m => ({ type: 'theme', value: m.default })), import('@shikijs/themes/material-theme-palenight').then(m => ({ type: 'theme', value: m.default })));
55
+ }
56
+ if (options.registerDefaultLanguages !== false) {
57
+ promises.push(import('@shikijs/langs/vue').then(m => ({ type: 'lang', value: m.default })), import('@shikijs/langs/tsx').then(m => ({ type: 'lang', value: m.default })), import('@shikijs/langs/svelte').then(m => ({ type: 'lang', value: m.default })), import('@shikijs/langs/typescript').then(m => ({ type: 'lang', value: m.default })), import('@shikijs/langs/javascript').then(m => ({ type: 'lang', value: m.default })), import('@shikijs/langs/mdc').then(m => ({ type: 'lang', value: m.default })), import('@shikijs/langs/bash').then(m => ({ type: 'lang', value: m.default })), import('@shikijs/langs/json').then(m => ({ type: 'lang', value: m.default })), import('@shikijs/langs/yaml').then(m => ({ type: 'lang', value: m.default })), import('@shikijs/langs/astro').then(m => ({ type: 'lang', value: m.default })));
58
+ }
59
+ const results = await Promise.all(promises);
60
+ for (const result of results) {
61
+ if (result.type === 'theme')
62
+ themes.push(result.value);
63
+ else
64
+ languages.push(result.value);
65
+ }
66
+ return { themes, languages };
67
+ }
68
+ async function loadTheme(hl, theme) {
69
+ if (loadedThemes.has(theme.name || '')) {
70
+ return;
71
+ }
72
+ await hl.loadTheme(theme);
73
+ loadedThemes.add(theme.name || '');
74
+ }
75
+ async function loadLanguage(hl, language) {
76
+ if (loadedLanguages.has(Array.isArray(language) ? language.map(l => l.name || '').join(',') : language.name || '')) {
77
+ return;
78
+ }
79
+ await hl.loadLanguage(language);
80
+ loadedLanguages.add(Array.isArray(language) ? language.map(l => l.name || '').join(',') : language.name || '');
81
+ }
82
+ /**
83
+ * Highlight code using Shiki with codeToTokens
84
+ * Returns comark nodes built from hast
85
+ */
86
+ export async function highlightCode(code, attrs, options = {}) {
87
+ // Extract language from attributes
88
+ const language = attrs?.language;
89
+ try {
90
+ const hl = await getHighlighter(options);
91
+ const { themes = { light: 'material-theme-lighter', dark: 'material-theme-palenight' } } = options;
92
+ const lightTheme = themes.light || themes.dark || 'material-theme-lighter';
93
+ const darkTheme = themes.dark || themes.light || 'material-theme-palenight';
94
+ // Use codeToTokens to get raw tokens
95
+ const result = await codeToHast(hl, code, {
96
+ lang: language,
97
+ transformers: options.transformers,
98
+ themes: {
99
+ light: lightTheme,
100
+ dark: lightTheme !== darkTheme ? darkTheme : undefined,
101
+ },
102
+ meta: {
103
+ __raw: attrs.meta,
104
+ },
105
+ });
106
+ const allTokens = result.children.map(hastToMinimarkNode);
107
+ return {
108
+ nodes: allTokens,
109
+ language,
110
+ };
111
+ }
112
+ catch (error) {
113
+ // If highlighting fails, return the original code
114
+ console.error('Shiki highlighting error:', error);
115
+ return {
116
+ nodes: [code],
117
+ language,
118
+ };
119
+ }
120
+ function hastToMinimarkNode(input) {
121
+ const props = input.properties || {};
122
+ if (input.type === 'comment')
123
+ return [null, {}, input.value];
124
+ if (input.type === 'text')
125
+ return input.value;
126
+ if (input.tag === 'code' && props?.className && props.className.length === 0)
127
+ delete props.className;
128
+ return [
129
+ input.tagName,
130
+ props,
131
+ ...(input.children || []).map(hastToMinimarkNode),
132
+ ];
133
+ }
134
+ }
135
+ /**
136
+ * Apply syntax highlighting to all code blocks in a Comark tree
137
+ * Uses codeToTokens API with batched async operations
138
+ */
139
+ export async function highlightCodeBlocks(tree, options = {}) {
140
+ const codeBlocks = [];
141
+ const findCodeBlocks = (nodes, path) => {
142
+ for (let i = 0; i < nodes.length; i++) {
143
+ const node = nodes[i];
144
+ if (typeof node === 'string')
145
+ continue;
146
+ if (!Array.isArray(node) || node.length < 3)
147
+ continue;
148
+ if (node[0] === 'pre' && Array.isArray(node[2]) && node[2][0] === 'code') {
149
+ const codeContent = node[2][2];
150
+ if (typeof codeContent === 'string') {
151
+ codeBlocks.push({ node, path: [...path, i] });
152
+ }
153
+ }
154
+ findCodeBlocks(node.slice(2), [...path, i]);
155
+ }
156
+ };
157
+ findCodeBlocks(tree.nodes, []);
158
+ if (codeBlocks.length === 0)
159
+ return tree;
160
+ const highlightedResults = await Promise.all(codeBlocks.map(({ node }) => {
161
+ return highlightCode(node[2][2], node[1], options);
162
+ }));
163
+ const newNodes = JSON.parse(JSON.stringify(tree.nodes));
164
+ for (let i = 0; i < codeBlocks.length; i++) {
165
+ const { node, path } = codeBlocks[i];
166
+ const result = highlightedResults[i];
167
+ const preNode = result.nodes[0];
168
+ const preNodeClasses = typeof preNode === 'string'
169
+ ? ['shiki', options.themes?.light?.name]
170
+ : (Array.isArray(preNode[1].class)
171
+ ? preNode[1].class
172
+ : String(preNode[1].class).split(' '));
173
+ const codeChildren = preNode[2].slice(2);
174
+ const children = typeof preNode === 'string' ? preNode : codeChildren.filter(element => element !== '\n');
175
+ const preAttrs = node[1];
176
+ const newPreAttrs = {
177
+ ...preAttrs,
178
+ class: [...preNodeClasses, options.themes?.dark?.name ? `dark:${options.themes?.dark?.name}` : ''].filter(Boolean).join(' '),
179
+ tabindex: '0',
180
+ };
181
+ if (options.preStyles) {
182
+ const lightTheme = options.themes?.light;
183
+ const darkTheme = options.themes?.dark;
184
+ const styles = [];
185
+ if (lightTheme?.colors?.['editor.background']) {
186
+ styles.push(`background-color:${lightTheme.colors['editor.background']}`);
187
+ }
188
+ if (lightTheme?.colors?.['editor.foreground']) {
189
+ styles.push(`color:${lightTheme.colors['editor.foreground']}`);
190
+ }
191
+ if (lightTheme?.name !== darkTheme?.name) {
192
+ if (darkTheme?.colors?.['editor.background']) {
193
+ styles.push(`--shiki-dark-bg:${darkTheme.colors['editor.background']}`);
194
+ }
195
+ if (darkTheme?.colors?.['editor.foreground']) {
196
+ styles.push(`--shiki-dark:${darkTheme.colors['editor.foreground']}`);
197
+ }
198
+ }
199
+ newPreAttrs.style = styles.join(';');
200
+ }
201
+ const codeEl = node[2];
202
+ const codeAttrs = codeEl[1] || {};
203
+ const newPreNode = ['pre', newPreAttrs, ['code', codeAttrs, ...children]];
204
+ if (path.length === 1) {
205
+ newNodes[path[0]] = newPreNode;
206
+ }
207
+ else {
208
+ let current = newNodes[path[0]];
209
+ for (let j = 1; j < path.length - 1; j++) {
210
+ current = current[path[j] + 2];
211
+ }
212
+ const childSlot = path[path.length - 1] + 2;
213
+ current[childSlot] = newPreNode;
214
+ }
215
+ }
216
+ return { ...tree, nodes: newNodes };
217
+ }
218
+ /**
219
+ * Reset the highlighter instance
220
+ * Useful for testing or when you want to reconfigure
221
+ */
222
+ export function resetHighlighter() {
223
+ highlighter = null;
224
+ highlighterPromise = null;
225
+ loadedThemes.clear();
226
+ }
227
+ export default function highlight(options = {}) {
228
+ return {
229
+ name: 'highlight',
230
+ async post(state) {
231
+ state.tree = await highlightCodeBlocks(state.tree, options);
232
+ },
233
+ };
234
+ }
@@ -0,0 +1,59 @@
1
+ import type { ComarkPlugin } from 'comark';
2
+ export interface MathConfig {
3
+ /**
4
+ * Throw on parse errors or return error message
5
+ * @default false
6
+ */
7
+ throwOnError?: boolean;
8
+ /**
9
+ * Additional katex options
10
+ */
11
+ options?: Record<string, unknown>;
12
+ }
13
+ /**
14
+ * Render LaTeX math expression to HTML using KaTeX
15
+ *
16
+ * @param code LaTeX math expression
17
+ * @param displayMode Whether to render in display mode
18
+ * @param config KaTeX configuration
19
+ * @returns HTML string
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const html = renderMath('E = mc^2', false)
24
+ * const display = renderMath('x^2', true)
25
+ * ```
26
+ */
27
+ export declare function renderMath(code: string, displayMode: boolean, config?: MathConfig): string;
28
+ /**
29
+ * Check if code is valid LaTeX math syntax
30
+ *
31
+ * @param code LaTeX math expression
32
+ * @returns true if valid, false otherwise
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * validateMath('E = mc^2') // true
37
+ * validateMath('\\invalid') // false
38
+ * ```
39
+ */
40
+ export declare function validateMath(code: string): boolean;
41
+ /**
42
+ * Create math plugin for comark
43
+ *
44
+ * This plugin processes markdown-it tokens and converts math expressions
45
+ * with $ and $$ delimiters to HTML using KaTeX.
46
+ *
47
+ * @param config Math rendering configuration
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * import { parse } from 'comark'
52
+ * import math from 'comark/plugins/math'
53
+ *
54
+ * const result = await parse('Inline $x^2$ and display $$E = mc^2$$', {
55
+ * plugins: [math({ throwOnError: false })]
56
+ * })
57
+ * ```
58
+ */
59
+ export default function math(config?: MathConfig): ComarkPlugin;