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,15 @@
1
+ import type { ComarkPlugin, ComarkTree } from 'comark';
2
+ export interface TocLink {
3
+ id: string;
4
+ text: string;
5
+ depth: number;
6
+ children?: TocLink[];
7
+ }
8
+ export interface Toc {
9
+ title: string;
10
+ depth: number;
11
+ searchDepth: number;
12
+ links: TocLink[];
13
+ }
14
+ export declare function generateFlatToc(body: ComarkTree, options: Toc): Toc;
15
+ export default function toc(options?: Partial<Toc>): ComarkPlugin;
@@ -0,0 +1,118 @@
1
+ const TOC_TAGS = ['h2', 'h3', 'h4', 'h5', 'h6'];
2
+ const TOC_TAGS_DEPTH = TOC_TAGS.reduce((tags, tag) => {
3
+ tags[tag] = Number(tag.charAt(tag.length - 1));
4
+ return tags;
5
+ }, {});
6
+ function getTag(node) {
7
+ if (Array.isArray(node) && node.length >= 1) {
8
+ return node[0];
9
+ }
10
+ return null;
11
+ }
12
+ function getProps(node) {
13
+ if (Array.isArray(node) && node.length >= 2 && typeof node[1] === 'object' && !Array.isArray(node[1])) {
14
+ return node[1];
15
+ }
16
+ return {};
17
+ }
18
+ function getChildren(node) {
19
+ if (Array.isArray(node) && node.length > 2) {
20
+ return node.slice(2);
21
+ }
22
+ return [];
23
+ }
24
+ function getHeaderDepth(node) {
25
+ const tag = getTag(node);
26
+ return tag ? TOC_TAGS_DEPTH[tag] || 0 : 0;
27
+ }
28
+ function getTocTags(depth) {
29
+ if (depth < 1 || depth > 5) {
30
+ console.warn(`\`toc.depth\` is set to ${depth}. It should be a number between 1 and 5. `);
31
+ depth = 1;
32
+ }
33
+ return TOC_TAGS.slice(0, depth);
34
+ }
35
+ function flattenNodeText(node) {
36
+ if (typeof node === 'string') {
37
+ return node;
38
+ }
39
+ if (Array.isArray(node)) {
40
+ return getChildren(node).reduce((text, child) => {
41
+ return text + flattenNodeText(child);
42
+ }, '');
43
+ }
44
+ return '';
45
+ }
46
+ function flattenNodes(nodes, maxDepth, currentDepth = 0) {
47
+ if (currentDepth >= maxDepth) {
48
+ return nodes;
49
+ }
50
+ const result = [];
51
+ for (const node of nodes) {
52
+ result.push(node);
53
+ if (Array.isArray(node)) {
54
+ const children = getChildren(node);
55
+ if (children.length > 0) {
56
+ result.push(...flattenNodes(children, maxDepth, currentDepth + 1));
57
+ }
58
+ }
59
+ }
60
+ return result;
61
+ }
62
+ function nestHeaders(headers) {
63
+ if (headers.length <= 1) {
64
+ return headers;
65
+ }
66
+ const toc = [];
67
+ let parent;
68
+ headers.forEach((header) => {
69
+ if (!parent || header.depth <= parent.depth) {
70
+ header.children = [];
71
+ parent = header;
72
+ toc.push(header);
73
+ }
74
+ else {
75
+ parent.children.push(header);
76
+ }
77
+ });
78
+ toc.forEach((header) => {
79
+ if (header.children?.length) {
80
+ header.children = nestHeaders(header.children);
81
+ }
82
+ else {
83
+ delete header.children;
84
+ }
85
+ });
86
+ return toc;
87
+ }
88
+ export function generateFlatToc(body, options) {
89
+ const { searchDepth, depth, title = '' } = options;
90
+ const tags = getTocTags(depth);
91
+ const allNodes = flattenNodes(body.nodes, searchDepth);
92
+ const headers = allNodes.filter((node) => {
93
+ const tag = getTag(node);
94
+ return tag !== null && tags.includes(tag);
95
+ });
96
+ const links = headers.map(node => ({
97
+ id: getProps(node).id || '',
98
+ depth: getHeaderDepth(node),
99
+ text: flattenNodeText(node),
100
+ }));
101
+ return {
102
+ title,
103
+ searchDepth,
104
+ depth,
105
+ links,
106
+ };
107
+ }
108
+ export default function toc(options = {}) {
109
+ const { title = '', depth = 2, searchDepth = 2, links = [] } = options;
110
+ return {
111
+ name: 'toc',
112
+ post(state) {
113
+ const toc = generateFlatToc(state.tree, { title, depth, searchDepth, links });
114
+ toc.links = nestHeaders(toc.links);
115
+ state.tree.meta.toc = toc;
116
+ },
117
+ };
118
+ }
@@ -0,0 +1,18 @@
1
+ import type { ComarkTree, RenderOptions, RenderMarkdownOptions } from 'comark';
2
+ export type { NodeHandler, State, Context, RenderOptions, RenderMarkdownOptions } from './types.ts';
3
+ export { renderFrontmatter } from './internal/frontmatter.ts';
4
+ /**
5
+ * Generate a string from a Comark tree
6
+ * @param tree - The Comark tree to render
7
+ * @param context - The context of the renderer
8
+ * @returns The string representation of the Comark tree
9
+ */
10
+ export declare function render(tree: ComarkTree, context?: RenderOptions): Promise<string>;
11
+ /**
12
+ * Render Comark tree to markdown
13
+ *
14
+ * @param tree - The Comark tree to render
15
+ * @param options - Optional rendering options
16
+ * @returns The markdown string with optional frontmatter
17
+ */
18
+ export declare function renderMarkdown(tree: ComarkTree, options?: RenderMarkdownOptions): Promise<string>;
package/dist/render.js ADDED
@@ -0,0 +1,29 @@
1
+ import { renderFrontmatter } from "./internal/frontmatter.js";
2
+ import { createState, one } from "./internal/stringify/state.js";
3
+ // Re-export frontmatter renderer
4
+ export { renderFrontmatter } from "./internal/frontmatter.js";
5
+ /**
6
+ * Generate a string from a Comark tree
7
+ * @param tree - The Comark tree to render
8
+ * @param context - The context of the renderer
9
+ * @returns The string representation of the Comark tree
10
+ */
11
+ export async function render(tree, context = {}) {
12
+ const state = createState({ ...context, tree, handlers: context.components });
13
+ let result = '';
14
+ for (const child of tree.nodes) {
15
+ result += await one(child, state);
16
+ }
17
+ return result.trim() + '\n';
18
+ }
19
+ /**
20
+ * Render Comark tree to markdown
21
+ *
22
+ * @param tree - The Comark tree to render
23
+ * @param options - Optional rendering options
24
+ * @returns The markdown string with optional frontmatter
25
+ */
26
+ export async function renderMarkdown(tree, options) {
27
+ const content = await render(tree, { format: 'markdown/comark', ...options });
28
+ return renderFrontmatter(tree.frontmatter, content);
29
+ }
@@ -0,0 +1,258 @@
1
+ import type MarkdownExit from 'markdown-exit';
2
+ import type MarkdownIt from 'markdown-it';
3
+ /**
4
+ * The Comark text
5
+ * @param string - The text content
6
+ */
7
+ export type ComarkText = string;
8
+ /**
9
+ * The Comark comment
10
+ * @param null - The null node
11
+ * @param {} - The attributes of the comment
12
+ * @param string - The content of the comment
13
+ */
14
+ export type ComarkComment = [null, {}, string];
15
+ /**
16
+ * The Comark element attributes
17
+ * @param [key: string]: unknown - The attributes of the element
18
+ */
19
+ export type ComarkElementAttributes = {
20
+ [key: string]: unknown;
21
+ $?: {
22
+ line?: number;
23
+ html?: 0 | 1;
24
+ block?: 0 | 1;
25
+ };
26
+ };
27
+ /**
28
+ * The Comark element
29
+ * @param string - The tag of the element
30
+ * @param ComarkElementAttributes - The attributes of the element
31
+ * @param ...ComarkNode[] - The children of the element
32
+ */
33
+ export type ComarkElement = [string, ComarkElementAttributes, ...ComarkNode[]];
34
+ /**
35
+ * The Comark node
36
+ *
37
+ * `ComarkElement` | `ComarkText` | `ComarkComment` - The node can be an element, text or comment
38
+ */
39
+ export type ComarkNode = ComarkElement | ComarkText | ComarkComment;
40
+ /**
41
+ * The Comark tree
42
+ * @param nodes - The nodes of the tree
43
+ * @param frontmatter - The frontmatter data which is the data at the top of the file
44
+ * @param meta - The meta data of tree, it can be used to store additional data for the tree
45
+ */
46
+ export interface ComarkTree {
47
+ nodes: ComarkNode[];
48
+ frontmatter: Record<string, any>;
49
+ meta: Record<string, any>;
50
+ }
51
+ interface StringifyOptions {
52
+ /**
53
+ * @default '\n\n'
54
+ */
55
+ blockSeparator: string;
56
+ /**
57
+ * @default 'markdown/comark'
58
+ */
59
+ format: 'markdown/comark' | 'markdown/html' | 'text/html' | 'text';
60
+ /**
61
+ * user defined node handlers
62
+ */
63
+ handlers: Record<string, NodeHandler>;
64
+ /**
65
+ * @default true
66
+ */
67
+ removeLastStyle?: boolean;
68
+ /**
69
+ * Maximum number of inline attributes before switching to YAML block syntax.
70
+ * Set to 0 to always use YAML block syntax.
71
+ * @default 3
72
+ */
73
+ maxInlineAttributes?: number;
74
+ /**
75
+ * Additional options
76
+ */
77
+ [key: string]: unknown;
78
+ }
79
+ export interface Context extends StringifyOptions {
80
+ /**
81
+ * true if node is inside html scope
82
+ */
83
+ html?: boolean;
84
+ /**
85
+ * true if node is inside a list
86
+ */
87
+ list?: boolean;
88
+ /**
89
+ * number if node is inside an ordered list
90
+ */
91
+ order?: number;
92
+ [key: string]: unknown;
93
+ }
94
+ /**
95
+ * The NodeHandler function
96
+ * @param node - The node to render
97
+ * @param state - The state of the renderer
98
+ * @param parent - The parent node
99
+ * @returns The rendered node
100
+ */
101
+ export type NodeHandler = (node: ComarkElement, state: State, parent?: ComarkElement) => string | Promise<string>;
102
+ /**
103
+ * The State of the renderer
104
+ * @param handlers - The handlers of the renderer
105
+ * @param context - The context of the renderer
106
+ * @param flow - Render children of the node
107
+ * @param one - Render a single node
108
+ * @param applyContext - The applyContext of the renderer
109
+ * @returns The state of the renderer
110
+ */
111
+ export type State = {
112
+ /**
113
+ * Additional data to pass to the renderer nodes, can be used to pass pre-fetched data to the renderer nodes
114
+ */
115
+ data: Record<string, any>;
116
+ /**
117
+ * The context of the renderer
118
+ */
119
+ context: Context;
120
+ /**
121
+ * The handlers of the renderer
122
+ */
123
+ handlers: Record<string, NodeHandler>;
124
+ /**
125
+ * Render children of the node
126
+ */
127
+ flow: (node: ComarkElement, state: State, parent?: ComarkElement) => Promise<string>;
128
+ /**
129
+ * Render a single node
130
+ */
131
+ one: (node: ComarkNode, state: State, parent?: ComarkElement) => Promise<string>;
132
+ /**
133
+ * Render the input
134
+ */
135
+ render: (input: ComarkNode[] | ComarkElement) => Promise<string>;
136
+ /**
137
+ * Apply the context
138
+ * @param edit - The edit to apply to the context
139
+ * @returns The revert of the edit
140
+ */
141
+ applyContext: (edit: Record<string, unknown>) => Record<string, unknown>;
142
+ /**
143
+ * The depth of the node in the tree
144
+ */
145
+ nodeDepthInTree?: number;
146
+ [key: string]: unknown;
147
+ };
148
+ /**
149
+ * The context of the renderer
150
+ */
151
+ export interface RenderOptions {
152
+ /**
153
+ * Additional node handlers to pass to the renderer
154
+ */
155
+ components?: Record<string, NodeHandler>;
156
+ /**
157
+ * Additional data to pass to the renderer nodes, can be used to pass pre-fetched data to the renderer nodes
158
+ */
159
+ data?: Record<string, any>;
160
+ [key: string]: unknown;
161
+ }
162
+ /**
163
+ * The options for rendering markdown
164
+ */
165
+ export interface RenderMarkdownOptions extends RenderOptions {
166
+ /**
167
+ * Maximum number of inline attributes before switching to YAML block syntax.
168
+ * Set to 0 to always use YAML block syntax.
169
+ * @default 3
170
+ */
171
+ maxInlineAttributes?: number;
172
+ }
173
+ export type MarkdownExitPlugin = (md: MarkdownExit) => void;
174
+ export type MarkdownItPlugin = (md: MarkdownIt) => void;
175
+ export type ComarkParsePreState = {
176
+ markdown: string;
177
+ options: ParseOptions;
178
+ [key: string]: any;
179
+ };
180
+ export type ComarkParsePostState = {
181
+ markdown: string;
182
+ tree: ComarkTree;
183
+ options: ParseOptions;
184
+ tokens: unknown[];
185
+ [key: string]: any;
186
+ };
187
+ export type ComarkPlugin = {
188
+ name: string;
189
+ markdownItPlugins?: MarkdownItPlugin[];
190
+ pre?: (state: ComarkParsePreState) => Promise<void> | void;
191
+ post?: (state: ComarkParsePostState) => Promise<void> | void;
192
+ };
193
+ export type ComponentManifest = (name: string) => Promise<unknown> | undefined | null;
194
+ export interface ComarkContextProvider {
195
+ components: Record<string, any>;
196
+ componentManifest: ComponentManifest;
197
+ }
198
+ export interface ParseOptions {
199
+ /**
200
+ * Whether to automatically unwrap single paragraphs in container components.
201
+ * When enabled, if a container component (alert, card, callout, note, warning, tip, info)
202
+ * has only a single paragraph child, the paragraph wrapper is removed and its children
203
+ * become direct children of the container. This creates cleaner HTML output.
204
+ *
205
+ * @default true
206
+ * @example
207
+ * // With autoUnwrap: true (default)
208
+ * // <alert><strong>Text</strong></alert>
209
+ *
210
+ * // With autoUnwrap: false
211
+ * // <alert><p><strong>Text</strong></p></alert>
212
+ */
213
+ autoUnwrap?: boolean;
214
+ /**
215
+ * Whether to automatically close unclosed markdown and Comark components.
216
+ * @default true
217
+ */
218
+ autoClose?: boolean;
219
+ /**
220
+ * Whether to parse HTML tags embedded in Comark/markdown content.
221
+ * When enabled, HTML block and inline elements are parsed into AST nodes and can be
222
+ * mixed freely with Comark components and markdown syntax.
223
+ *
224
+ * @default true
225
+ * @example
226
+ * // With html: true (default) — HTML is parsed into AST nodes
227
+ * // Input: `<strong class="bold">text</strong>`
228
+ * // AST: ['strong', { class: 'bold' }, 'text']
229
+ *
230
+ * // HTML can be mixed with Comark components:
231
+ * // Input:
232
+ * // <div>
233
+ * // ::alert
234
+ * // Hello <em>world</em>
235
+ * // ::
236
+ * // </div>
237
+ *
238
+ * // With html: false — HTML tags are left as raw text / ignored
239
+ */
240
+ html?: boolean;
241
+ /**
242
+ * Additional plugins to use
243
+ * @default []
244
+ */
245
+ plugins?: ComarkPlugin[];
246
+ }
247
+ /**
248
+ * Type signature for the options object passed to the Comark parser function returned by createParse().
249
+ */
250
+ export type ComarkParseFnOptions = {
251
+ streaming?: boolean;
252
+ };
253
+ /**
254
+ * Type signature for the async Comark parser function returned by createParse().
255
+ * Accepts a markdown string and optional parsing options, and returns a Promise of ComarkTree.
256
+ */
257
+ export type ComarkParseFn = (markdown: string, opts?: ComarkParseFnOptions) => Promise<ComarkTree>;
258
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { ComarkElement } from '../';
2
+ interface CaretOptions {
3
+ class?: string;
4
+ }
5
+ export declare function getCaret(options: boolean | CaretOptions): ComarkElement | null;
6
+ export declare function findLastTextNodeAndAppendNode(parent: ComarkElement, nodeToAppend: ComarkElement): boolean;
7
+ export {};
@@ -0,0 +1,36 @@
1
+ const CARET_TEXT = ' '; // thin space is used to avoid wide spaces between text and caret
2
+ const CARET_STYLE = 'background-color: currentColor; display: inline-block; margin-left: 0.25rem; margin-right: 0.25rem; animation: pulse 0.75s cubic-bezier(0.4,0,0.6,1) infinite;';
3
+ export function getCaret(options) {
4
+ if (options === true) {
5
+ return ['span', { key: 'stream-caret', style: CARET_STYLE }, CARET_TEXT];
6
+ }
7
+ if (typeof options === 'object') {
8
+ return [
9
+ 'span',
10
+ {
11
+ key: 'stream-caret',
12
+ ...(options.class ? { class: options.class } : { style: CARET_STYLE }),
13
+ },
14
+ CARET_TEXT,
15
+ ];
16
+ }
17
+ return null;
18
+ }
19
+ export function findLastTextNodeAndAppendNode(parent, nodeToAppend) {
20
+ // Traverse nodes backwards to find the last text node
21
+ for (let i = parent.length - 1; i >= 2; i--) {
22
+ const node = parent[i];
23
+ if (typeof node === 'string' && parent[1]?.key !== 'stream-caret') {
24
+ // Found a text node - insert stream indicator after it
25
+ parent.push(nodeToAppend);
26
+ return true;
27
+ }
28
+ if (Array.isArray(node)) {
29
+ // This is an element node - recursively check its children
30
+ if (findLastTextNodeAndAppendNode(node, nodeToAppend)) {
31
+ return true;
32
+ }
33
+ }
34
+ }
35
+ return false;
36
+ }
@@ -0,0 +1,38 @@
1
+ import type { ComarkNode, ComarkTree } from 'comark';
2
+ /**
3
+ * Get the text content of a Comark node
4
+ *
5
+ * @param node - The Comark node
6
+ * @param options - The options
7
+ * @param options.decodeUnicodeEntities - Whether to decode Unicode entities
8
+ * @returns The text content
9
+ */
10
+ export declare function textContent(node: ComarkNode, options?: {
11
+ decodeUnicodeEntities?: boolean;
12
+ }): string;
13
+ /**
14
+ * Visit a Comark tree and apply a visitor function to each node
15
+ *
16
+ * @param tree - The Comark tree
17
+ * @param checker - A function that checks if a node should be visited
18
+ * @param visitor - A function that visits a node
19
+ */
20
+ export declare function visit(tree: ComarkTree, checker: (node: ComarkNode) => boolean, visitor: (node: ComarkNode) => ComarkNode | false | undefined | void): void;
21
+ /**
22
+ * Convert a string to pascal case
23
+ * @param str - The string to convert
24
+ * @returns The pascal case string
25
+ */
26
+ export declare function pascalCase(str: string): string;
27
+ /**
28
+ * Convert a string to kebab case
29
+ * @param str - The string to convert
30
+ * @returns The kebab case string
31
+ */
32
+ export declare function kebabCase(str: string): string;
33
+ /**
34
+ * Convert a string to camel case
35
+ * @param str - The string to convert
36
+ * @returns The camel case string
37
+ */
38
+ export declare function camelCase(str: string): string;
@@ -0,0 +1,149 @@
1
+ // #region Tree Utils
2
+ import { decodeHTML } from 'entities';
3
+ /**
4
+ * Get the text content of a Comark node
5
+ *
6
+ * @param node - The Comark node
7
+ * @param options - The options
8
+ * @param options.decodeUnicodeEntities - Whether to decode Unicode entities
9
+ * @returns The text content
10
+ */
11
+ export function textContent(node, options = {}) {
12
+ if (typeof node === 'string') {
13
+ if (options.decodeUnicodeEntities) {
14
+ return decodeHTML(node);
15
+ }
16
+ return node;
17
+ }
18
+ let out = '';
19
+ const len = node.length;
20
+ for (let i = 2; i < len; i++) {
21
+ out += textContent(node[i], options);
22
+ }
23
+ return out;
24
+ }
25
+ /**
26
+ * Visit a Comark tree and apply a visitor function to each node
27
+ *
28
+ * @param tree - The Comark tree
29
+ * @param checker - A function that checks if a node should be visited
30
+ * @param visitor - A function that visits a node
31
+ */
32
+ export function visit(tree, checker,
33
+ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
34
+ visitor) {
35
+ function walk(node, parent, index) {
36
+ let currentNode = node;
37
+ if (checker(node)) {
38
+ const res = visitor(node);
39
+ if (res === false) {
40
+ // remove the node from the parent
41
+ parent.splice(index, 1);
42
+ return true; // signal that node was removed
43
+ }
44
+ if (res !== undefined) {
45
+ parent[index] = res;
46
+ currentNode = res;
47
+ }
48
+ }
49
+ if (Array.isArray(currentNode) && currentNode.length > 2) {
50
+ // Use a while loop to handle removals correctly - don't increment if node was removed
51
+ let i = 2;
52
+ while (i < currentNode.length) {
53
+ const childRemoved = walk(currentNode[i], currentNode, i);
54
+ if (childRemoved) {
55
+ // If removed, i stays the same (next node is now at this index)
56
+ continue;
57
+ }
58
+ i += 1;
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+ // Use a while loop to handle removals correctly - don't increment if node was removed
64
+ let i = 0;
65
+ while (i < tree.nodes.length) {
66
+ const removed = walk(tree.nodes[i], tree.nodes, i);
67
+ if (removed) {
68
+ // If removed, i stays the same (next node is now at this index)
69
+ continue;
70
+ }
71
+ i += 1;
72
+ }
73
+ }
74
+ // #region String Utils
75
+ /**
76
+ * Convert a string to pascal case
77
+ * @param str - The string to convert
78
+ * @returns The pascal case string
79
+ */
80
+ export function pascalCase(str) {
81
+ return str ? splitByCase(str).map(p => p[0].toUpperCase() + p.slice(1)).join('') : '';
82
+ }
83
+ /**
84
+ * Convert a string to kebab case
85
+ * @param str - The string to convert
86
+ * @returns The kebab case string
87
+ */
88
+ export function kebabCase(str) {
89
+ return str ? splitByCase(str).map(p => p.toLowerCase()).join('-') : '';
90
+ }
91
+ /**
92
+ * Convert a string to camel case
93
+ * @param str - The string to convert
94
+ * @returns The camel case string
95
+ */
96
+ export function camelCase(str) {
97
+ if (!str) {
98
+ return '';
99
+ }
100
+ str = pascalCase(str);
101
+ return str.charAt(0).toLowerCase() + str.slice(1);
102
+ }
103
+ // split a string by case
104
+ function splitByCase(str) {
105
+ const parts = [];
106
+ if (!str) {
107
+ return parts;
108
+ }
109
+ let buff = '';
110
+ let previousUpper;
111
+ let previousSplitter;
112
+ for (let i = 0; i < str.length; i++) {
113
+ const char = str[i];
114
+ // Fast splitter check using direct character comparisons
115
+ const isSplitter = char === '-' || char === '_' || char === '/' || char === '.';
116
+ if (isSplitter === true) {
117
+ parts.push(buff);
118
+ buff = '';
119
+ previousUpper = void 0;
120
+ continue;
121
+ }
122
+ // Fast number check using character codes
123
+ const charCode = char.charCodeAt(0);
124
+ const isNumber = charCode >= 48 && charCode <= 57; // '0' to '9'
125
+ // Fast uppercase check using character codes
126
+ const isUpper = isNumber ? void 0 : (charCode >= 65 && charCode <= 90); // 'A' to 'Z'
127
+ if (previousSplitter === false) {
128
+ if (previousUpper === false && isUpper === true) {
129
+ parts.push(buff);
130
+ buff = char;
131
+ previousUpper = isUpper;
132
+ continue;
133
+ }
134
+ if (previousUpper === true && isUpper === false && buff.length > 1) {
135
+ const lastChar = buff[buff.length - 1];
136
+ parts.push(buff.slice(0, buff.length - 1));
137
+ buff = lastChar + char;
138
+ previousUpper = isUpper;
139
+ continue;
140
+ }
141
+ }
142
+ buff += char;
143
+ previousUpper = isUpper;
144
+ previousSplitter = isSplitter;
145
+ }
146
+ parts.push(buff);
147
+ return parts;
148
+ }
149
+ // #endregion