comark 0.0.1 → 0.1.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 +104 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/internal/frontmatter.d.ts +16 -0
- package/dist/internal/frontmatter.js +43 -0
- package/dist/internal/parse/auto-close/index.d.ts +12 -0
- package/dist/internal/parse/auto-close/index.js +457 -0
- package/dist/internal/parse/auto-close/table.d.ts +4 -0
- package/dist/internal/parse/auto-close/table.js +161 -0
- package/dist/internal/parse/auto-unwrap.d.ts +20 -0
- package/dist/internal/parse/auto-unwrap.js +42 -0
- package/dist/internal/parse/html/html_block_rule.d.ts +2 -0
- package/dist/internal/parse/html/html_block_rule.js +60 -0
- package/dist/internal/parse/html/html_blocks.d.ts +2 -0
- package/dist/internal/parse/html/html_blocks.js +66 -0
- package/dist/internal/parse/html/html_inline_rule.d.ts +2 -0
- package/dist/internal/parse/html/html_inline_rule.js +43 -0
- package/dist/internal/parse/html/html_re.d.ts +3 -0
- package/dist/internal/parse/html/html_re.js +18 -0
- package/dist/internal/parse/html/index.d.ts +18 -0
- package/dist/internal/parse/html/index.js +122 -0
- package/dist/internal/parse/incremental.d.ts +12 -0
- package/dist/internal/parse/incremental.js +39 -0
- package/dist/internal/parse/token-processor.d.ts +9 -0
- package/dist/internal/parse/token-processor.js +803 -0
- package/dist/internal/props-validation.d.ts +12 -0
- package/dist/internal/props-validation.js +112 -0
- package/dist/internal/stringify/attributes.d.ts +21 -0
- package/dist/internal/stringify/attributes.js +67 -0
- package/dist/internal/stringify/handlers/a.d.ts +3 -0
- package/dist/internal/stringify/handlers/a.js +11 -0
- package/dist/internal/stringify/handlers/blockquote.d.ts +3 -0
- package/dist/internal/stringify/handlers/blockquote.js +18 -0
- package/dist/internal/stringify/handlers/br.d.ts +3 -0
- package/dist/internal/stringify/handlers/br.js +3 -0
- package/dist/internal/stringify/handlers/code.d.ts +3 -0
- package/dist/internal/stringify/handlers/code.js +11 -0
- package/dist/internal/stringify/handlers/comment.d.ts +3 -0
- package/dist/internal/stringify/handlers/comment.js +6 -0
- package/dist/internal/stringify/handlers/del.d.ts +3 -0
- package/dist/internal/stringify/handlers/del.js +4 -0
- package/dist/internal/stringify/handlers/emphesis.d.ts +3 -0
- package/dist/internal/stringify/handlers/emphesis.js +13 -0
- package/dist/internal/stringify/handlers/heading.d.ts +3 -0
- package/dist/internal/stringify/handlers/heading.js +7 -0
- package/dist/internal/stringify/handlers/hr.d.ts +3 -0
- package/dist/internal/stringify/handlers/hr.js +3 -0
- package/dist/internal/stringify/handlers/html.d.ts +3 -0
- package/dist/internal/stringify/handlers/html.js +73 -0
- package/dist/internal/stringify/handlers/img.d.ts +3 -0
- package/dist/internal/stringify/handlers/img.js +9 -0
- package/dist/internal/stringify/handlers/index.d.ts +2 -0
- package/dist/internal/stringify/handlers/index.js +56 -0
- package/dist/internal/stringify/handlers/li.d.ts +3 -0
- package/dist/internal/stringify/handlers/li.js +43 -0
- package/dist/internal/stringify/handlers/math.d.ts +3 -0
- package/dist/internal/stringify/handlers/math.js +8 -0
- package/dist/internal/stringify/handlers/mdc.d.ts +3 -0
- package/dist/internal/stringify/handlers/mdc.js +47 -0
- package/dist/internal/stringify/handlers/mermaid.d.ts +3 -0
- package/dist/internal/stringify/handlers/mermaid.js +8 -0
- package/dist/internal/stringify/handlers/ol.d.ts +3 -0
- package/dist/internal/stringify/handlers/ol.js +18 -0
- package/dist/internal/stringify/handlers/p.d.ts +3 -0
- package/dist/internal/stringify/handlers/p.js +8 -0
- package/dist/internal/stringify/handlers/pre.d.ts +3 -0
- package/dist/internal/stringify/handlers/pre.js +60 -0
- package/dist/internal/stringify/handlers/strong.d.ts +3 -0
- package/dist/internal/stringify/handlers/strong.js +13 -0
- package/dist/internal/stringify/handlers/table.d.ts +8 -0
- package/dist/internal/stringify/handlers/table.js +180 -0
- package/dist/internal/stringify/handlers/template.d.ts +3 -0
- package/dist/internal/stringify/handlers/template.js +14 -0
- package/dist/internal/stringify/handlers/ul.d.ts +3 -0
- package/dist/internal/stringify/handlers/ul.js +18 -0
- package/dist/internal/stringify/indent.d.ts +4 -0
- package/dist/internal/stringify/indent.js +8 -0
- package/dist/internal/stringify/state.d.ts +13 -0
- package/dist/internal/stringify/state.js +121 -0
- package/dist/internal/yaml.d.ts +12 -0
- package/dist/internal/yaml.js +51 -0
- package/dist/parse.d.ts +66 -0
- package/dist/parse.js +163 -0
- package/dist/plugins/alert.d.ts +2 -0
- package/dist/plugins/alert.js +66 -0
- package/dist/plugins/emoji.d.ts +3 -0
- package/dist/plugins/emoji.js +438 -0
- package/dist/plugins/headings.d.ts +48 -0
- package/dist/plugins/headings.js +85 -0
- package/dist/plugins/highlight.d.ts +63 -0
- package/dist/plugins/highlight.js +235 -0
- package/dist/plugins/math.d.ts +59 -0
- package/dist/plugins/math.js +263 -0
- package/dist/plugins/mermaid.d.ts +38 -0
- package/dist/plugins/mermaid.js +185 -0
- package/dist/plugins/security.d.ts +11 -0
- package/dist/plugins/security.js +32 -0
- package/dist/plugins/summary.d.ts +2 -0
- package/dist/plugins/summary.js +22 -0
- package/dist/plugins/task-list.d.ts +8 -0
- package/dist/plugins/task-list.js +117 -0
- package/dist/plugins/toc.d.ts +15 -0
- package/dist/plugins/toc.js +118 -0
- package/dist/render.d.ts +18 -0
- package/dist/render.js +29 -0
- package/dist/types.d.ts +258 -0
- package/dist/types.js +1 -0
- package/dist/utils/caret.d.ts +7 -0
- package/dist/utils/caret.js +36 -0
- package/dist/utils/index.d.ts +38 -0
- package/dist/utils/index.js +149 -0
- package/package.json +73 -9
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { indent } from '../indent';
|
|
2
|
+
export async function ol(node, state) {
|
|
3
|
+
const children = node.slice(2);
|
|
4
|
+
const revert = state.applyContext({ list: true, order: 1 });
|
|
5
|
+
let result = '';
|
|
6
|
+
for (const child of children) {
|
|
7
|
+
result += await state.one(child, state);
|
|
8
|
+
}
|
|
9
|
+
result = result.trim();
|
|
10
|
+
if (revert.list) {
|
|
11
|
+
result = '\n' + indent(result);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
result = result + state.context.blockSeparator;
|
|
15
|
+
}
|
|
16
|
+
state.applyContext(revert);
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { textContent } from 'comark/utils';
|
|
2
|
+
export function pre(node, state) {
|
|
3
|
+
const [_, attributes, ...children] = node;
|
|
4
|
+
const codeClasses = children[0]?.[1]?.class;
|
|
5
|
+
const language = (attributes.language || (codeClasses?.split(' ').find(cls => cls.startsWith('language-')))?.slice(9)) || '';
|
|
6
|
+
// Escape ] in filename
|
|
7
|
+
const filename = attributes.filename
|
|
8
|
+
? ' [' + String(attributes.filename).split(']').join('\\\\]') + ']'
|
|
9
|
+
: '';
|
|
10
|
+
const highlights = attributes.highlights
|
|
11
|
+
? ' {' + formatHighlights(attributes.highlights) + '}'
|
|
12
|
+
: '';
|
|
13
|
+
// Meta always has a leading space
|
|
14
|
+
const meta = attributes.meta
|
|
15
|
+
? ' ' + attributes.meta
|
|
16
|
+
: '';
|
|
17
|
+
const result = '```' + language + filename + highlights + meta + '\n'
|
|
18
|
+
+ String(node[1]?.code || extractCode(node)).trim()
|
|
19
|
+
+ '\n```';
|
|
20
|
+
return result + state.context.blockSeparator;
|
|
21
|
+
}
|
|
22
|
+
function extractCode(node) {
|
|
23
|
+
const codeNode = node[2];
|
|
24
|
+
if (Array.isArray(codeNode) && codeNode[0] === 'code') {
|
|
25
|
+
const spans = codeNode.slice(2);
|
|
26
|
+
const lineSpans = spans.filter(s => Array.isArray(s) && String(s[1]?.class ?? '').includes('line'));
|
|
27
|
+
if (lineSpans.length > 0) {
|
|
28
|
+
return lineSpans.map(span => textContent(span)).join('\n');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return textContent(node);
|
|
32
|
+
}
|
|
33
|
+
function formatHighlights(highlights) {
|
|
34
|
+
if (highlights.length === 0)
|
|
35
|
+
return '';
|
|
36
|
+
const sorted = [...highlights].sort((a, b) => a - b);
|
|
37
|
+
const ranges = [];
|
|
38
|
+
let start = sorted[0];
|
|
39
|
+
let end = sorted[0];
|
|
40
|
+
for (let i = 1; i <= sorted.length; i++) {
|
|
41
|
+
if (i < sorted.length && sorted[i] === end + 1) {
|
|
42
|
+
end = sorted[i];
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Add the current range
|
|
46
|
+
if (start === end) {
|
|
47
|
+
ranges.push(String(start));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
ranges.push(start + '-' + end);
|
|
51
|
+
}
|
|
52
|
+
// Start a new range
|
|
53
|
+
if (i < sorted.length) {
|
|
54
|
+
start = sorted[i];
|
|
55
|
+
end = sorted[i];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return ranges.join(',');
|
|
60
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { comarkAttributes } from '../attributes';
|
|
2
|
+
export async function strong(node, state) {
|
|
3
|
+
const [_, attrs, ...children] = node;
|
|
4
|
+
let content = '';
|
|
5
|
+
for (const child of children) {
|
|
6
|
+
content += await state.one(child, state, node);
|
|
7
|
+
}
|
|
8
|
+
content = content.trim();
|
|
9
|
+
const attrsString = Object.keys(attrs).length > 0
|
|
10
|
+
? comarkAttributes(attrs)
|
|
11
|
+
: '';
|
|
12
|
+
return `**${content}**${attrsString}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { State } from 'comark/render';
|
|
2
|
+
import type { ComarkElement } from 'comark';
|
|
3
|
+
export declare function table(node: ComarkElement, state: State): Promise<string>;
|
|
4
|
+
export declare function thead(_node: ComarkElement, _state: State): string;
|
|
5
|
+
export declare function tbody(_node: ComarkElement, _state: State): string;
|
|
6
|
+
export declare function tr(_node: ComarkElement, _state: State): string;
|
|
7
|
+
export declare function th(_node: ComarkElement, _state: State): string;
|
|
8
|
+
export declare function td(_node: ComarkElement, _state: State): string;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// Helper function to extract alignment from style attribute
|
|
2
|
+
function getAlignment(attributes) {
|
|
3
|
+
const style = attributes.style;
|
|
4
|
+
if (typeof style !== 'string') {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
// Parse text-align from style attribute - normalize and check
|
|
8
|
+
const normalized = style.toLowerCase().split(' ').join('').split('\t').join('');
|
|
9
|
+
if (normalized.includes('text-align:left')) {
|
|
10
|
+
return 'left';
|
|
11
|
+
}
|
|
12
|
+
if (normalized.includes('text-align:center')) {
|
|
13
|
+
return 'center';
|
|
14
|
+
}
|
|
15
|
+
if (normalized.includes('text-align:right')) {
|
|
16
|
+
return 'right';
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
// Helper function to extract text content from a cell
|
|
21
|
+
async function getCellContent(cell, state) {
|
|
22
|
+
if (typeof cell === 'string') {
|
|
23
|
+
return escapePipes(cell);
|
|
24
|
+
}
|
|
25
|
+
const [, , ...children] = cell;
|
|
26
|
+
let content = '';
|
|
27
|
+
for (const child of children) {
|
|
28
|
+
if (typeof child === 'string') {
|
|
29
|
+
content += child;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
content += await state.one(child, state, cell);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return escapePipes(content.trim());
|
|
36
|
+
}
|
|
37
|
+
// Helper function to escape pipe characters and handle newlines
|
|
38
|
+
function escapePipes(text) {
|
|
39
|
+
// Replace newlines with spaces (tables don't support multi-line cells)
|
|
40
|
+
// Then escape pipe characters
|
|
41
|
+
return text.split('\n').join(' ').split('|').join('\\|');
|
|
42
|
+
}
|
|
43
|
+
// Helper function to get all rows from thead/tbody
|
|
44
|
+
function getRows(element) {
|
|
45
|
+
if (typeof element === 'string') {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
const [tag, , ...children] = element;
|
|
49
|
+
// If it's a tr, return it directly
|
|
50
|
+
if (tag === 'tr') {
|
|
51
|
+
return [element];
|
|
52
|
+
}
|
|
53
|
+
// If it's thead/tbody, extract tr elements
|
|
54
|
+
if (tag === 'thead' || tag === 'tbody') {
|
|
55
|
+
return children.filter(child => typeof child !== 'string' && child[0] === 'tr');
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
// Helper function to get cells from a row
|
|
60
|
+
function getCells(row) {
|
|
61
|
+
const [, , ...children] = row;
|
|
62
|
+
return children.filter(child => typeof child !== 'string' && (child[0] === 'th' || child[0] === 'td'));
|
|
63
|
+
}
|
|
64
|
+
export async function table(node, state) {
|
|
65
|
+
const [, , ...children] = node;
|
|
66
|
+
// Extract thead and tbody
|
|
67
|
+
let headerRows = [];
|
|
68
|
+
let bodyRows = [];
|
|
69
|
+
for (const child of children) {
|
|
70
|
+
if (typeof child === 'string')
|
|
71
|
+
continue;
|
|
72
|
+
const [tag] = child;
|
|
73
|
+
if (tag === 'thead') {
|
|
74
|
+
headerRows = getRows(child);
|
|
75
|
+
}
|
|
76
|
+
else if (tag === 'tbody') {
|
|
77
|
+
bodyRows = getRows(child);
|
|
78
|
+
}
|
|
79
|
+
else if (tag === 'tr') {
|
|
80
|
+
// Direct tr children (no thead/tbody wrapper)
|
|
81
|
+
const cells = getCells(child);
|
|
82
|
+
if (cells.length > 0 && cells[0][0] === 'th') {
|
|
83
|
+
headerRows.push(child);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
bodyRows.push(child);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// If no header rows, create a default header based on the first body row
|
|
91
|
+
if (headerRows.length === 0 && bodyRows.length > 0) {
|
|
92
|
+
const firstRow = bodyRows[0];
|
|
93
|
+
const cells = getCells(firstRow);
|
|
94
|
+
const headerCells = cells.map((_, i) => ['th', {}, `Column ${i + 1}`]);
|
|
95
|
+
headerRows = [['tr', {}, ...headerCells]];
|
|
96
|
+
}
|
|
97
|
+
if (headerRows.length === 0) {
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
// Process header row (use the first header row)
|
|
101
|
+
const headerRow = headerRows[0];
|
|
102
|
+
const headerCells = getCells(headerRow);
|
|
103
|
+
const headerContent = [];
|
|
104
|
+
for (const cell of headerCells) {
|
|
105
|
+
headerContent.push(await getCellContent(cell, state));
|
|
106
|
+
}
|
|
107
|
+
// Extract alignment from header cells
|
|
108
|
+
const alignments = headerCells.map((cell) => {
|
|
109
|
+
const [, attributes] = cell;
|
|
110
|
+
return getAlignment(attributes);
|
|
111
|
+
});
|
|
112
|
+
// Calculate column widths (minimum 3 characters per column)
|
|
113
|
+
const columnWidths = headerContent.map(content => Math.max(3, content.length));
|
|
114
|
+
// Update column widths based on body content
|
|
115
|
+
for (const row of bodyRows) {
|
|
116
|
+
const cells = getCells(row);
|
|
117
|
+
for (let i = 0; i < cells.length; i++) {
|
|
118
|
+
if (i < columnWidths.length) {
|
|
119
|
+
const content = await getCellContent(cells[i], state);
|
|
120
|
+
columnWidths[i] = Math.max(columnWidths[i], content.length);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Build the markdown table
|
|
125
|
+
let result = '| ';
|
|
126
|
+
result += headerContent.map((content, i) => content.padEnd(columnWidths[i])).join(' | ');
|
|
127
|
+
result += ' |\n';
|
|
128
|
+
// Add separator row with alignment
|
|
129
|
+
result += '| ';
|
|
130
|
+
result += columnWidths.map((width, i) => {
|
|
131
|
+
const alignment = alignments[i];
|
|
132
|
+
if (alignment === 'left') {
|
|
133
|
+
return ':' + '-'.repeat(width - 1);
|
|
134
|
+
}
|
|
135
|
+
else if (alignment === 'center') {
|
|
136
|
+
return ':' + '-'.repeat(width - 2) + ':';
|
|
137
|
+
}
|
|
138
|
+
else if (alignment === 'right') {
|
|
139
|
+
return '-'.repeat(width - 1) + ':';
|
|
140
|
+
}
|
|
141
|
+
return '-'.repeat(width);
|
|
142
|
+
}).join(' | ');
|
|
143
|
+
result += ' |\n';
|
|
144
|
+
// Add body rows
|
|
145
|
+
for (const row of bodyRows) {
|
|
146
|
+
const cells = getCells(row);
|
|
147
|
+
const cellContents = [];
|
|
148
|
+
for (let i = 0; i < cells.length; i++) {
|
|
149
|
+
const content = await getCellContent(cells[i], state);
|
|
150
|
+
cellContents.push(content.padEnd(columnWidths[i] || 0));
|
|
151
|
+
}
|
|
152
|
+
// Fill missing columns with empty cells
|
|
153
|
+
while (cellContents.length < columnWidths.length) {
|
|
154
|
+
cellContents.push(''.padEnd(columnWidths[cellContents.length]));
|
|
155
|
+
}
|
|
156
|
+
result += '| ' + cellContents.join(' | ') + ' |\n';
|
|
157
|
+
}
|
|
158
|
+
// result already ends with \n, so we only need to add one more \n
|
|
159
|
+
return result + '\n';
|
|
160
|
+
}
|
|
161
|
+
export function thead(_node, _state) {
|
|
162
|
+
// thead is handled by the table handler
|
|
163
|
+
return '';
|
|
164
|
+
}
|
|
165
|
+
export function tbody(_node, _state) {
|
|
166
|
+
// tbody is handled by the table handler
|
|
167
|
+
return '';
|
|
168
|
+
}
|
|
169
|
+
export function tr(_node, _state) {
|
|
170
|
+
// tr is handled by the table handler
|
|
171
|
+
return '';
|
|
172
|
+
}
|
|
173
|
+
export function th(_node, _state) {
|
|
174
|
+
// th is handled by the table handler
|
|
175
|
+
return '';
|
|
176
|
+
}
|
|
177
|
+
export function td(_node, _state) {
|
|
178
|
+
// td is handled by the table handler
|
|
179
|
+
return '';
|
|
180
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// slot template
|
|
2
|
+
export async function template(node, state, parent) {
|
|
3
|
+
const [_, attrs] = node;
|
|
4
|
+
const content = (await state.flow(node, state)).trim();
|
|
5
|
+
// Omit #default marker if this is the only slot
|
|
6
|
+
if (attrs.name === 'default') {
|
|
7
|
+
const siblings = parent ? parent.slice(2) : [];
|
|
8
|
+
const templateCount = siblings.filter(child => Array.isArray(child) && child[0] === 'template').length;
|
|
9
|
+
if (templateCount === 1) {
|
|
10
|
+
return content + state.context.blockSeparator;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return `#${attrs.name}\n${content}` + state.context.blockSeparator;
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { indent } from '../indent';
|
|
2
|
+
export async function ul(node, state) {
|
|
3
|
+
const children = node.slice(2);
|
|
4
|
+
const revert = state.applyContext({ list: true, order: false });
|
|
5
|
+
let result = '';
|
|
6
|
+
for (const child of children) {
|
|
7
|
+
result += await state.one(child, state);
|
|
8
|
+
}
|
|
9
|
+
result = result.trim();
|
|
10
|
+
if (revert.list) {
|
|
11
|
+
result = '\n' + indent(result);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
result = result + state.context.blockSeparator;
|
|
15
|
+
}
|
|
16
|
+
state.applyContext(revert);
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { State, Context } from 'comark/render';
|
|
2
|
+
import type { ComarkElement, ComarkNode } from 'comark';
|
|
3
|
+
/**
|
|
4
|
+
* Render a single node
|
|
5
|
+
* @param node - The node to render
|
|
6
|
+
* @param state - The state of the renderer
|
|
7
|
+
* @param parent - The parent node
|
|
8
|
+
* @returns The rendered node
|
|
9
|
+
*/
|
|
10
|
+
export declare function one(node: ComarkNode, state: State, parent?: ComarkElement): Promise<string>;
|
|
11
|
+
export declare function flow(node: ComarkElement, state: State, parent?: ComarkElement): Promise<string>;
|
|
12
|
+
export declare function createState(ctx?: Partial<Context>): State;
|
|
13
|
+
export declare const state: State;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { handlers } from "./handlers/index.js";
|
|
2
|
+
import { pascalCase } from "../../utils/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Render a single node
|
|
5
|
+
* @param node - The node to render
|
|
6
|
+
* @param state - The state of the renderer
|
|
7
|
+
* @param parent - The parent node
|
|
8
|
+
* @returns The rendered node
|
|
9
|
+
*/
|
|
10
|
+
export async function one(node, state, parent) {
|
|
11
|
+
if (typeof node === 'string') {
|
|
12
|
+
if (state.context.html) {
|
|
13
|
+
return escapeHtml(node);
|
|
14
|
+
}
|
|
15
|
+
return node;
|
|
16
|
+
}
|
|
17
|
+
if (node[0] === null) {
|
|
18
|
+
return await state.handlers.comment(node, state);
|
|
19
|
+
}
|
|
20
|
+
const userHandler = state.context.handlers[node[0]] || state.context.handlers[pascalCase(node[0])];
|
|
21
|
+
if (userHandler) {
|
|
22
|
+
return await userHandler(node, state, parent);
|
|
23
|
+
}
|
|
24
|
+
if (state.context.html || node[1].$?.html === 1) {
|
|
25
|
+
return await state.handlers.html(node, state, parent);
|
|
26
|
+
}
|
|
27
|
+
const nodeHandler = state.handlers[node[0]];
|
|
28
|
+
if (nodeHandler) {
|
|
29
|
+
return await nodeHandler(node, state, parent);
|
|
30
|
+
}
|
|
31
|
+
return state.context.format === 'markdown/comark'
|
|
32
|
+
? await state.handlers.mdc(node, state, parent)
|
|
33
|
+
: await state.handlers.html(node, state, parent);
|
|
34
|
+
}
|
|
35
|
+
export async function flow(node, state, parent) {
|
|
36
|
+
const children = node.slice(2);
|
|
37
|
+
let result = '';
|
|
38
|
+
for (const child of children) {
|
|
39
|
+
result += await one(child, state, parent || node);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
export function createState(ctx = {}) {
|
|
44
|
+
const context = {
|
|
45
|
+
...ctx,
|
|
46
|
+
blockSeparator: ctx.blockSeparator || '\n\n',
|
|
47
|
+
format: ctx.format || 'markdown/comark',
|
|
48
|
+
handlers: ctx.handlers || {}, // user defined node handlers
|
|
49
|
+
// Enable html mode for text/html format
|
|
50
|
+
html: ctx.format === 'text/html',
|
|
51
|
+
};
|
|
52
|
+
const state = {
|
|
53
|
+
handlers,
|
|
54
|
+
context,
|
|
55
|
+
one,
|
|
56
|
+
flow,
|
|
57
|
+
data: ctx.data || {},
|
|
58
|
+
render: async (input) => {
|
|
59
|
+
if (Array.isArray(input) && typeof input[0] === 'string' && input.length > 1) {
|
|
60
|
+
return state.one(input, state);
|
|
61
|
+
}
|
|
62
|
+
let result = '';
|
|
63
|
+
for (const child of input) {
|
|
64
|
+
result += await state.one(child, state);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
},
|
|
68
|
+
applyContext: (edit) => {
|
|
69
|
+
const revert = {};
|
|
70
|
+
for (const [key, value] of Object.entries(edit)) {
|
|
71
|
+
revert[key] = context[key];
|
|
72
|
+
context[key] = value;
|
|
73
|
+
}
|
|
74
|
+
return revert;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
return state;
|
|
78
|
+
}
|
|
79
|
+
export const state = {
|
|
80
|
+
handlers,
|
|
81
|
+
data: {},
|
|
82
|
+
context: {
|
|
83
|
+
blockSeparator: '\n\n',
|
|
84
|
+
format: 'markdown/comark',
|
|
85
|
+
handlers: {}, // user defined node handlers
|
|
86
|
+
},
|
|
87
|
+
flow,
|
|
88
|
+
one,
|
|
89
|
+
render: async (input) => {
|
|
90
|
+
if (typeof input === 'string') {
|
|
91
|
+
return input;
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(input) && typeof input[0] === 'string') {
|
|
94
|
+
return one(input, state);
|
|
95
|
+
}
|
|
96
|
+
let result = '';
|
|
97
|
+
for (const child of input) {
|
|
98
|
+
result += await one(child, state);
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
},
|
|
102
|
+
applyContext: (edit) => {
|
|
103
|
+
const revert = {};
|
|
104
|
+
for (const [key, value] of Object.entries(edit)) {
|
|
105
|
+
revert[key] = state.context[key];
|
|
106
|
+
state.context[key] = value;
|
|
107
|
+
}
|
|
108
|
+
return revert;
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Escape HTML special characters
|
|
113
|
+
*/
|
|
114
|
+
function escapeHtml(text) {
|
|
115
|
+
const map = {
|
|
116
|
+
'<': '<',
|
|
117
|
+
'>': '>',
|
|
118
|
+
'&': '&',
|
|
119
|
+
};
|
|
120
|
+
return text.replace(/[<>]/g, char => map[char]);
|
|
121
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse YAML content
|
|
3
|
+
* @param content - The content to parse
|
|
4
|
+
* @returns The parsed data
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseYaml(content: string): Record<string, unknown>;
|
|
7
|
+
/**
|
|
8
|
+
* Stringify YAML data
|
|
9
|
+
* @param data - The data to stringify
|
|
10
|
+
* @returns The stringified data
|
|
11
|
+
*/
|
|
12
|
+
export declare function stringifyYaml(data: Record<string, unknown>): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { dump, JSON_SCHEMA, load } from 'js-yaml';
|
|
2
|
+
/**
|
|
3
|
+
* Parse YAML content
|
|
4
|
+
* @param content - The content to parse
|
|
5
|
+
* @returns The parsed data
|
|
6
|
+
*/
|
|
7
|
+
export function parseYaml(content) {
|
|
8
|
+
return load(content, { schema: JSON_SCHEMA });
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Stringify YAML data
|
|
12
|
+
* @param data - The data to stringify
|
|
13
|
+
* @returns The stringified data
|
|
14
|
+
*/
|
|
15
|
+
export function stringifyYaml(data) {
|
|
16
|
+
const yamlOutput = dump(data, {
|
|
17
|
+
indent: 2,
|
|
18
|
+
replacer: (_key, value) => {
|
|
19
|
+
if (value === 'true')
|
|
20
|
+
return true;
|
|
21
|
+
if (value === 'false')
|
|
22
|
+
return false;
|
|
23
|
+
return value;
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* js-yaml wraps keys with quotes if they start with a colon. This function removes the quotes.
|
|
28
|
+
* `':test': true` becomes `:test: true`
|
|
29
|
+
*
|
|
30
|
+
* Using js-yaml and this function is faster than using other libraries like yaml.
|
|
31
|
+
*/
|
|
32
|
+
const lines = yamlOutput.split('\n');
|
|
33
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34
|
+
const line = lines[i];
|
|
35
|
+
const trimmed = line.trimStart();
|
|
36
|
+
// Check if line starts with a quote followed by colon
|
|
37
|
+
if (trimmed[0] === '\'' || trimmed[0] === '"') {
|
|
38
|
+
const quote = trimmed[0];
|
|
39
|
+
if (trimmed[1] === ':') {
|
|
40
|
+
// Find the closing quote
|
|
41
|
+
const quoteEnd = trimmed.indexOf(quote, 1);
|
|
42
|
+
if (quoteEnd > 1 && trimmed[quoteEnd + 1] === ':') {
|
|
43
|
+
// Remove quotes: keep indentation + unquoted key + rest
|
|
44
|
+
const indent = line.length - trimmed.length;
|
|
45
|
+
lines[i] = ' '.repeat(indent) + trimmed.slice(1, quoteEnd) + trimmed.slice(quoteEnd + 1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return lines.join('\n');
|
|
51
|
+
}
|
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ComarkParseFn, ParseOptions, ComarkTree } from './types.ts';
|
|
2
|
+
export { parseFrontmatter } from './internal/frontmatter.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a parser function for Comark content.
|
|
5
|
+
*
|
|
6
|
+
* Returns an async function that takes a markdown string and returns a Promise resolving to a ComarkTree AST.
|
|
7
|
+
* The returned parser applies frontmatter extraction, Comark syntax parsing, token-to-AST conversion,
|
|
8
|
+
* auto-closing of incomplete markdown, optional AST transformations and plugin hooks.
|
|
9
|
+
*
|
|
10
|
+
* @param options - Parser options controlling parsing behavior.
|
|
11
|
+
* @returns An async parser function: (markdown) => Promise<ComarkTree>
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { createParse } from 'comark'
|
|
16
|
+
*
|
|
17
|
+
* const parse = createParse({ autoUnwrap: false })
|
|
18
|
+
* const tree = await parse('# Hello **World**\n::alert\nhi\n::')
|
|
19
|
+
* console.log(tree.nodes)
|
|
20
|
+
* // → [ ['h1', { id: 'hello-world' }, 'Hello ', ['strong', {}, 'World'] ], ['alert', {}, 'hi'] ]
|
|
21
|
+
*
|
|
22
|
+
* // Enable HTML parsing (on by default) — HTML tags are included in the AST
|
|
23
|
+
* const parseWithHtml = createParse({ html: true })
|
|
24
|
+
* const tree2 = await parseWithHtml('<strong class="bold">Hello</strong> _world_')
|
|
25
|
+
* console.log(tree2.nodes)
|
|
26
|
+
* // → [ ['strong', { class: 'bold' }, 'Hello'], ' ', ['em', {}, 'world'] ]
|
|
27
|
+
*
|
|
28
|
+
* // Disable HTML parsing — HTML tags are treated as plain text
|
|
29
|
+
* const parseNoHtml = createParse({ html: false })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function createParse(options?: ParseOptions): ComarkParseFn;
|
|
33
|
+
/**
|
|
34
|
+
* Parse Comark content from a string
|
|
35
|
+
*
|
|
36
|
+
* @param markdown - The markdown/Comark content as a string
|
|
37
|
+
* @param options - Parser options
|
|
38
|
+
* @returns ComarkTree - The parsed AST tree
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* import { parse } from 'comark'
|
|
43
|
+
*
|
|
44
|
+
* const content = `---
|
|
45
|
+
* title: Hello World
|
|
46
|
+
* ---
|
|
47
|
+
*
|
|
48
|
+
* # Hello World
|
|
49
|
+
*
|
|
50
|
+
* This is a **markdown** document with *Comark* components.
|
|
51
|
+
*
|
|
52
|
+
* ::alert{type="info"}
|
|
53
|
+
* This is an alert component
|
|
54
|
+
* ::
|
|
55
|
+
* `
|
|
56
|
+
*
|
|
57
|
+
* const tree = await parse(content)
|
|
58
|
+
* console.log(tree.nodes) // Array of AST nodes
|
|
59
|
+
* console.log(tree.frontmatter) // { title: 'Hello World' }
|
|
60
|
+
* console.log(tree.meta) // Additional metadata
|
|
61
|
+
*
|
|
62
|
+
* // Disable auto-unwrap
|
|
63
|
+
* const tree2 = await parse(content, { autoUnwrap: false })
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function parse(markdown: string, options?: ParseOptions): Promise<ComarkTree>;
|