quikdown 1.1.0 → 1.2.2
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 +46 -6
- package/dist/quikdown.cjs +5 -5
- package/dist/quikdown.dark.css +1 -1
- package/dist/quikdown.esm.js +5 -5
- package/dist/quikdown.esm.min.js +2 -2
- package/dist/quikdown.esm.min.js.map +1 -1
- package/dist/quikdown.light.css +1 -1
- package/dist/quikdown.umd.js +5 -5
- package/dist/quikdown.umd.min.js +2 -2
- package/dist/quikdown.umd.min.js.map +1 -1
- package/dist/quikdown_ast.cjs +513 -0
- package/dist/quikdown_ast.d.ts +227 -0
- package/dist/quikdown_ast.esm.js +511 -0
- package/dist/quikdown_ast.esm.min.js +8 -0
- package/dist/quikdown_ast.esm.min.js.map +1 -0
- package/dist/quikdown_ast.umd.js +519 -0
- package/dist/quikdown_ast.umd.min.js +8 -0
- package/dist/quikdown_ast.umd.min.js.map +1 -0
- package/dist/quikdown_ast_html.cjs +1058 -0
- package/dist/quikdown_ast_html.d.ts +68 -0
- package/dist/quikdown_ast_html.esm.js +1056 -0
- package/dist/quikdown_ast_html.esm.min.js +8 -0
- package/dist/quikdown_ast_html.esm.min.js.map +1 -0
- package/dist/quikdown_ast_html.umd.js +1064 -0
- package/dist/quikdown_ast_html.umd.min.js +8 -0
- package/dist/quikdown_ast_html.umd.min.js.map +1 -0
- package/dist/quikdown_bd.cjs +12 -12
- package/dist/quikdown_bd.esm.js +12 -12
- package/dist/quikdown_bd.esm.min.js +2 -2
- package/dist/quikdown_bd.esm.min.js.map +1 -1
- package/dist/quikdown_bd.umd.js +12 -12
- package/dist/quikdown_bd.umd.min.js +2 -2
- package/dist/quikdown_bd.umd.min.js.map +1 -1
- package/dist/quikdown_edit.cjs +2297 -136
- package/dist/quikdown_edit.d.ts +110 -132
- package/dist/quikdown_edit.esm.js +2297 -136
- package/dist/quikdown_edit.esm.min.js +3 -4
- package/dist/quikdown_edit.esm.min.js.map +1 -1
- package/dist/quikdown_edit.umd.js +2298 -137
- package/dist/quikdown_edit.umd.min.js +3 -4
- package/dist/quikdown_edit.umd.min.js.map +1 -1
- package/dist/quikdown_json.cjs +556 -0
- package/dist/quikdown_json.d.ts +48 -0
- package/dist/quikdown_json.esm.js +554 -0
- package/dist/quikdown_json.esm.min.js +8 -0
- package/dist/quikdown_json.esm.min.js.map +1 -0
- package/dist/quikdown_json.umd.js +562 -0
- package/dist/quikdown_json.umd.min.js +8 -0
- package/dist/quikdown_json.umd.min.js.map +1 -0
- package/dist/quikdown_yaml.cjs +717 -0
- package/dist/quikdown_yaml.d.ts +51 -0
- package/dist/quikdown_yaml.esm.js +715 -0
- package/dist/quikdown_yaml.esm.min.js +8 -0
- package/dist/quikdown_yaml.esm.min.js.map +1 -0
- package/dist/quikdown_yaml.umd.js +723 -0
- package/dist/quikdown_yaml.umd.min.js +8 -0
- package/dist/quikdown_yaml.umd.min.js.map +1 -0
- package/package.json +92 -39
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quikdown_ast - AST Markdown Parser
|
|
3
|
+
* @version 1.2.2
|
|
4
|
+
* @license BSD-2-Clause
|
|
5
|
+
* @copyright DeftIO 2025
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* quikdown_ast - Forgiving markdown to AST parser
|
|
9
|
+
* Converts markdown to a structured Abstract Syntax Tree
|
|
10
|
+
* @param {string} markdown - The markdown source text
|
|
11
|
+
* @param {Object} options - Optional configuration object
|
|
12
|
+
* @returns {Object} - The AST object
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Version will be injected at build time
|
|
16
|
+
const quikdownVersion = '1.2.2';
|
|
17
|
+
|
|
18
|
+
// Safety limit to prevent infinite loops in list parsing
|
|
19
|
+
const MAX_LOOP_ITERATIONS = 1000;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse markdown into an AST
|
|
23
|
+
* @param {string} markdown - The markdown source text
|
|
24
|
+
* @param {Object} options - Optional configuration object
|
|
25
|
+
* @returns {Object} - The AST object
|
|
26
|
+
*/
|
|
27
|
+
function quikdown_ast(markdown, options = {}) {
|
|
28
|
+
if (!markdown || typeof markdown !== 'string') {
|
|
29
|
+
return { type: 'document', children: [] };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Normalize line endings (handle CRLF, CR, LF uniformly)
|
|
33
|
+
const text = markdown.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
34
|
+
|
|
35
|
+
const children = parseBlocks(text);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
type: 'document',
|
|
39
|
+
children
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse block-level elements
|
|
45
|
+
*/
|
|
46
|
+
function parseBlocks(text, options) {
|
|
47
|
+
const blocks = [];
|
|
48
|
+
const lines = text.split('\n');
|
|
49
|
+
let i = 0;
|
|
50
|
+
|
|
51
|
+
while (i < lines.length) {
|
|
52
|
+
const line = lines[i];
|
|
53
|
+
|
|
54
|
+
// Empty line - skip
|
|
55
|
+
if (line.trim() === '') {
|
|
56
|
+
i++;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Fenced code block (``` or ~~~)
|
|
61
|
+
const fenceMatch = line.match(/^(```|~~~)(.*)$/);
|
|
62
|
+
if (fenceMatch) {
|
|
63
|
+
const [, openFence, langPart] = fenceMatch;
|
|
64
|
+
const lang = langPart.trim();
|
|
65
|
+
const codeLines = [];
|
|
66
|
+
i++;
|
|
67
|
+
|
|
68
|
+
// Find closing fence (forgiving: accept mismatched fences or EOF)
|
|
69
|
+
while (i < lines.length) {
|
|
70
|
+
const closingMatch = lines[i].match(/^(```|~~~)\s*$/);
|
|
71
|
+
if (closingMatch) {
|
|
72
|
+
i++;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
codeLines.push(lines[i]);
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
blocks.push({
|
|
80
|
+
type: 'code_block',
|
|
81
|
+
lang: lang || null,
|
|
82
|
+
content: codeLines.join('\n'),
|
|
83
|
+
fence: openFence
|
|
84
|
+
});
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Horizontal rule
|
|
89
|
+
if (/^---+\s*$/.test(line) || /^\*\*\*+\s*$/.test(line) || /^___+\s*$/.test(line)) {
|
|
90
|
+
blocks.push({ type: 'hr' });
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Heading (forgiving: accept #heading without space)
|
|
96
|
+
const headingMatch = line.match(/^(#{1,6})\s*(.+?)\s*#*$/);
|
|
97
|
+
if (headingMatch) {
|
|
98
|
+
const [, hashes, content] = headingMatch;
|
|
99
|
+
blocks.push({
|
|
100
|
+
type: 'heading',
|
|
101
|
+
level: hashes.length,
|
|
102
|
+
children: parseInline(content)
|
|
103
|
+
});
|
|
104
|
+
i++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Table (look for separator line)
|
|
109
|
+
if (line.includes('|')) {
|
|
110
|
+
const tableResult = tryParseTable(lines, i);
|
|
111
|
+
if (tableResult) {
|
|
112
|
+
blocks.push(tableResult.node);
|
|
113
|
+
i = tableResult.nextIndex;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Blockquote
|
|
119
|
+
if (line.match(/^>\s*/)) {
|
|
120
|
+
const quoteLines = [];
|
|
121
|
+
while (i < lines.length && lines[i].match(/^>\s*/)) {
|
|
122
|
+
quoteLines.push(lines[i].replace(/^>\s*/, ''));
|
|
123
|
+
i++;
|
|
124
|
+
}
|
|
125
|
+
blocks.push({
|
|
126
|
+
type: 'blockquote',
|
|
127
|
+
children: parseBlocks(quoteLines.join('\n'))
|
|
128
|
+
});
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// List (ordered or unordered)
|
|
133
|
+
const listMatch = line.match(/^(\s*)([*\-+]|\d+\.)\s+(.*)$/);
|
|
134
|
+
if (listMatch) {
|
|
135
|
+
const listResult = parseList(lines, i);
|
|
136
|
+
blocks.push(listResult.node);
|
|
137
|
+
i = listResult.nextIndex;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Paragraph - collect lines until empty line or block element
|
|
142
|
+
const paragraphLines = [];
|
|
143
|
+
while (i < lines.length) {
|
|
144
|
+
const pLine = lines[i];
|
|
145
|
+
|
|
146
|
+
// Stop on empty line
|
|
147
|
+
if (pLine.trim() === '') break;
|
|
148
|
+
|
|
149
|
+
// Stop on block elements
|
|
150
|
+
if (/^(```|~~~)/.test(pLine)) break;
|
|
151
|
+
if (/^#{1,6}\s/.test(pLine)) break;
|
|
152
|
+
if (/^---+\s*$/.test(pLine) || /^\*\*\*+\s*$/.test(pLine) || /^___+\s*$/.test(pLine)) break;
|
|
153
|
+
if (/^>\s*/.test(pLine)) break;
|
|
154
|
+
if (/^(\s*)([*\-+]|\d+\.)\s+/.test(pLine)) break;
|
|
155
|
+
if (pLine.includes('|') && i + 1 < lines.length && /^\|?[\s\-:|]+\|?$/.test(lines[i + 1])) break;
|
|
156
|
+
|
|
157
|
+
paragraphLines.push(pLine);
|
|
158
|
+
i++;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (paragraphLines.length > 0) {
|
|
162
|
+
blocks.push({
|
|
163
|
+
type: 'paragraph',
|
|
164
|
+
children: parseInline(paragraphLines.join('\n'))
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return blocks;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Try to parse a table starting at the given line
|
|
174
|
+
*/
|
|
175
|
+
function tryParseTable(lines, startIndex, options) {
|
|
176
|
+
// Need at least 2 lines (header + separator)
|
|
177
|
+
if (startIndex + 1 >= lines.length) return null;
|
|
178
|
+
|
|
179
|
+
const headerLine = lines[startIndex];
|
|
180
|
+
const separatorLine = lines[startIndex + 1];
|
|
181
|
+
|
|
182
|
+
// Check if separator line is valid
|
|
183
|
+
if (!/^\|?[\s\-:|]+\|?$/.test(separatorLine) || !separatorLine.includes('-')) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Parse header
|
|
188
|
+
const headerCells = parseTableRow(headerLine);
|
|
189
|
+
if (headerCells.length === 0) return null;
|
|
190
|
+
|
|
191
|
+
// Parse alignments from separator
|
|
192
|
+
const separatorCells = parseTableRow(separatorLine);
|
|
193
|
+
const alignments = separatorCells.map(cell => {
|
|
194
|
+
const trimmed = cell.trim();
|
|
195
|
+
if (trimmed.startsWith(':') && trimmed.endsWith(':')) return 'center';
|
|
196
|
+
if (trimmed.endsWith(':')) return 'right';
|
|
197
|
+
return 'left';
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Parse headers with inline formatting
|
|
201
|
+
const headers = headerCells.map(cell => parseInline(cell.trim()));
|
|
202
|
+
|
|
203
|
+
// Parse body rows
|
|
204
|
+
const rows = [];
|
|
205
|
+
let i = startIndex + 2;
|
|
206
|
+
while (i < lines.length) {
|
|
207
|
+
const rowLine = lines[i];
|
|
208
|
+
if (!rowLine.includes('|') || rowLine.trim() === '') break;
|
|
209
|
+
|
|
210
|
+
const cells = parseTableRow(rowLine);
|
|
211
|
+
rows.push(cells.map(cell => parseInline(cell.trim())));
|
|
212
|
+
i++;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
node: {
|
|
217
|
+
type: 'table',
|
|
218
|
+
headers,
|
|
219
|
+
rows,
|
|
220
|
+
alignments
|
|
221
|
+
},
|
|
222
|
+
nextIndex: i
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Parse a table row into cells
|
|
228
|
+
*/
|
|
229
|
+
function parseTableRow(line) {
|
|
230
|
+
// Handle pipes at start/end or not
|
|
231
|
+
let trimmed = line.trim();
|
|
232
|
+
if (trimmed.startsWith('|')) trimmed = trimmed.slice(1);
|
|
233
|
+
if (trimmed.endsWith('|')) trimmed = trimmed.slice(0, -1);
|
|
234
|
+
return trimmed.split('|');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Parse a list starting at the given line
|
|
239
|
+
*/
|
|
240
|
+
function parseList(lines, startIndex, options) {
|
|
241
|
+
const items = [];
|
|
242
|
+
let i = startIndex;
|
|
243
|
+
let loopCount = 0;
|
|
244
|
+
|
|
245
|
+
// Determine initial list type
|
|
246
|
+
const firstMatch = lines[i].match(/^(\s*)([*\-+]|\d+\.)\s+(.*)$/);
|
|
247
|
+
const isOrdered = /^\d+\./.test(firstMatch[2]);
|
|
248
|
+
const baseIndent = firstMatch[1].length;
|
|
249
|
+
|
|
250
|
+
while (i < lines.length && loopCount < MAX_LOOP_ITERATIONS) {
|
|
251
|
+
loopCount++;
|
|
252
|
+
const line = lines[i];
|
|
253
|
+
const match = line.match(/^(\s*)([*\-+]|\d+\.)\s+(.*)$/);
|
|
254
|
+
|
|
255
|
+
if (!match) break;
|
|
256
|
+
|
|
257
|
+
const [, indent, marker, content] = match;
|
|
258
|
+
const indentLevel = indent.length;
|
|
259
|
+
|
|
260
|
+
// If less indented than base, stop
|
|
261
|
+
if (indentLevel < baseIndent) break;
|
|
262
|
+
|
|
263
|
+
// If same indentation but different list type, stop
|
|
264
|
+
const itemIsOrdered = /^\d+\./.test(marker);
|
|
265
|
+
if (indentLevel === baseIndent && itemIsOrdered !== isOrdered) break;
|
|
266
|
+
|
|
267
|
+
// If more indented, it's a nested list - handle by collecting sub-lines
|
|
268
|
+
if (indentLevel > baseIndent) {
|
|
269
|
+
// This is a nested list item, collect and parse as sublist
|
|
270
|
+
const subLines = [];
|
|
271
|
+
let subLoopCount = 0;
|
|
272
|
+
while (i < lines.length && subLoopCount < MAX_LOOP_ITERATIONS) {
|
|
273
|
+
subLoopCount++;
|
|
274
|
+
const subLine = lines[i];
|
|
275
|
+
const subMatch = subLine.match(/^(\s*)([*\-+]|\d+\.)\s+/);
|
|
276
|
+
if (!subMatch) break;
|
|
277
|
+
if (subMatch[1].length < baseIndent) break;
|
|
278
|
+
if (subMatch[1].length === baseIndent) break;
|
|
279
|
+
subLines.push(subLine);
|
|
280
|
+
i++;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (subLines.length > 0 && items.length > 0) {
|
|
284
|
+
// Add nested list to last item
|
|
285
|
+
const nestedResult = parseList(subLines, 0);
|
|
286
|
+
const lastItem = items[items.length - 1];
|
|
287
|
+
if (!lastItem.children) {
|
|
288
|
+
lastItem.children = [];
|
|
289
|
+
} else if (!Array.isArray(lastItem.children)) {
|
|
290
|
+
lastItem.children = [{ type: 'paragraph', children: lastItem.children }];
|
|
291
|
+
}
|
|
292
|
+
lastItem.children.push(nestedResult.node);
|
|
293
|
+
}
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Parse list item
|
|
298
|
+
const itemNode = {
|
|
299
|
+
type: 'list_item',
|
|
300
|
+
checked: null,
|
|
301
|
+
children: null
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// Check for task list syntax
|
|
305
|
+
const taskMatch = content.match(/^\[([x ])\]\s*(.*)$/i);
|
|
306
|
+
if (taskMatch && !isOrdered) {
|
|
307
|
+
itemNode.checked = taskMatch[1].toLowerCase() === 'x';
|
|
308
|
+
itemNode.children = parseInline(taskMatch[2]);
|
|
309
|
+
} else {
|
|
310
|
+
itemNode.children = parseInline(content);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
items.push(itemNode);
|
|
314
|
+
i++;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
node: {
|
|
319
|
+
type: 'list',
|
|
320
|
+
ordered: isOrdered,
|
|
321
|
+
items
|
|
322
|
+
},
|
|
323
|
+
nextIndex: i
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Parse inline elements
|
|
329
|
+
*/
|
|
330
|
+
function parseInline(text, options) {
|
|
331
|
+
if (!text) return [];
|
|
332
|
+
|
|
333
|
+
const nodes = [];
|
|
334
|
+
let remaining = text;
|
|
335
|
+
|
|
336
|
+
while (remaining.length > 0) {
|
|
337
|
+
let matched = false;
|
|
338
|
+
|
|
339
|
+
// Line break (1+ trailing spaces or explicit \n after processing)
|
|
340
|
+
// Handle inline line breaks (two spaces at end of line or backslash before newline)
|
|
341
|
+
const brMatch = remaining.match(/^(.+?)(?: {2}|\\\n|\n)/);
|
|
342
|
+
if (brMatch && remaining.includes('\n')) {
|
|
343
|
+
const beforeBr = remaining.indexOf('\n');
|
|
344
|
+
const beforeText = remaining.slice(0, beforeBr);
|
|
345
|
+
const afterText = remaining.slice(beforeBr + 1);
|
|
346
|
+
|
|
347
|
+
// Check if line break is significant (2+ trailing spaces or backslash)
|
|
348
|
+
if (beforeText.endsWith(' ') || beforeText.endsWith('\\')) {
|
|
349
|
+
const cleanText = beforeText.replace(/\\$/, '').replace(/ +$/, '');
|
|
350
|
+
if (cleanText) {
|
|
351
|
+
nodes.push(...parseInlineContent(cleanText));
|
|
352
|
+
}
|
|
353
|
+
nodes.push({ type: 'br' });
|
|
354
|
+
remaining = afterText;
|
|
355
|
+
matched = true;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Images: 
|
|
361
|
+
const imgMatch = remaining.match(/^!\[([^\]]*)\]\(\s*([^)\s]+)\s*\)/);
|
|
362
|
+
if (imgMatch) {
|
|
363
|
+
nodes.push({
|
|
364
|
+
type: 'image',
|
|
365
|
+
alt: imgMatch[1],
|
|
366
|
+
url: imgMatch[2].trim() // Forgiving: trim whitespace in URL
|
|
367
|
+
});
|
|
368
|
+
remaining = remaining.slice(imgMatch[0].length);
|
|
369
|
+
matched = true;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Links: [text](url)
|
|
374
|
+
const linkMatch = remaining.match(/^\[([^\]]+)\]\(\s*([^)\s]+)\s*\)/);
|
|
375
|
+
if (linkMatch) {
|
|
376
|
+
nodes.push({
|
|
377
|
+
type: 'link',
|
|
378
|
+
url: linkMatch[2].trim(), // Forgiving: trim whitespace in URL
|
|
379
|
+
children: parseInlineContent(linkMatch[1])
|
|
380
|
+
});
|
|
381
|
+
remaining = remaining.slice(linkMatch[0].length);
|
|
382
|
+
matched = true;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Inline code: `code`
|
|
387
|
+
const codeMatch = remaining.match(/^`([^`]+)`/);
|
|
388
|
+
if (codeMatch) {
|
|
389
|
+
nodes.push({
|
|
390
|
+
type: 'code',
|
|
391
|
+
value: codeMatch[1]
|
|
392
|
+
});
|
|
393
|
+
remaining = remaining.slice(codeMatch[0].length);
|
|
394
|
+
matched = true;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Bold: **text** or __text__
|
|
399
|
+
const boldMatch = remaining.match(/^(\*\*|__)(.+?)\1/);
|
|
400
|
+
if (boldMatch) {
|
|
401
|
+
nodes.push({
|
|
402
|
+
type: 'strong',
|
|
403
|
+
children: parseInlineContent(boldMatch[2])
|
|
404
|
+
});
|
|
405
|
+
remaining = remaining.slice(boldMatch[0].length);
|
|
406
|
+
matched = true;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Strikethrough: ~~text~~
|
|
411
|
+
const strikeMatch = remaining.match(/^~~(.+?)~~/);
|
|
412
|
+
if (strikeMatch) {
|
|
413
|
+
nodes.push({
|
|
414
|
+
type: 'del',
|
|
415
|
+
children: parseInlineContent(strikeMatch[1])
|
|
416
|
+
});
|
|
417
|
+
remaining = remaining.slice(strikeMatch[0].length);
|
|
418
|
+
matched = true;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Italic: *text* or _text_ (not at word boundary for underscores)
|
|
423
|
+
const emMatch = remaining.match(/^(\*|_)(?!\1)(.+?)(?<!\1)\1(?!\1)/);
|
|
424
|
+
if (emMatch) {
|
|
425
|
+
nodes.push({
|
|
426
|
+
type: 'em',
|
|
427
|
+
children: parseInlineContent(emMatch[2])
|
|
428
|
+
});
|
|
429
|
+
remaining = remaining.slice(emMatch[0].length);
|
|
430
|
+
matched = true;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Autolinks: URLs starting with http:// or https://
|
|
435
|
+
const urlMatch = remaining.match(/^(https?:\/\/[^\s<>[\]]+)/);
|
|
436
|
+
if (urlMatch) {
|
|
437
|
+
nodes.push({
|
|
438
|
+
type: 'link',
|
|
439
|
+
url: urlMatch[1],
|
|
440
|
+
children: [{ type: 'text', value: urlMatch[1] }]
|
|
441
|
+
});
|
|
442
|
+
remaining = remaining.slice(urlMatch[0].length);
|
|
443
|
+
matched = true;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Plain text - consume until next potential inline element or end
|
|
448
|
+
if (!matched) {
|
|
449
|
+
// Find next potential inline marker
|
|
450
|
+
const nextMarker = remaining.search(/[`*_~![\\n]|https?:\/\//);
|
|
451
|
+
if (nextMarker === -1) {
|
|
452
|
+
// No more markers, consume rest as text
|
|
453
|
+
nodes.push({ type: 'text', value: remaining });
|
|
454
|
+
break;
|
|
455
|
+
} else if (nextMarker === 0) {
|
|
456
|
+
// Current char is a marker but didn't match - consume it as text
|
|
457
|
+
nodes.push({ type: 'text', value: remaining[0] });
|
|
458
|
+
remaining = remaining.slice(1);
|
|
459
|
+
} else {
|
|
460
|
+
// Consume text up to next marker
|
|
461
|
+
nodes.push({ type: 'text', value: remaining.slice(0, nextMarker) });
|
|
462
|
+
remaining = remaining.slice(nextMarker);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Merge adjacent text nodes
|
|
468
|
+
return mergeTextNodes(nodes);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Parse inline content (recursive helper for nested inline elements)
|
|
473
|
+
*/
|
|
474
|
+
function parseInlineContent(text, options) {
|
|
475
|
+
// For simple nested content, use parseInline
|
|
476
|
+
// But handle newlines as spaces for inline content
|
|
477
|
+
const normalized = text.replace(/\n/g, ' ');
|
|
478
|
+
return parseInline(normalized);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Merge adjacent text nodes
|
|
483
|
+
*/
|
|
484
|
+
function mergeTextNodes(nodes) {
|
|
485
|
+
const merged = [];
|
|
486
|
+
for (const node of nodes) {
|
|
487
|
+
if (node.type === 'text' && merged.length > 0 && merged[merged.length - 1].type === 'text') {
|
|
488
|
+
merged[merged.length - 1].value += node.value;
|
|
489
|
+
} else {
|
|
490
|
+
merged.push(node);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return merged;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Attach version
|
|
497
|
+
quikdown_ast.version = quikdownVersion;
|
|
498
|
+
|
|
499
|
+
// Export for both CommonJS and ES6
|
|
500
|
+
/* istanbul ignore next */
|
|
501
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
502
|
+
module.exports = quikdown_ast;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// For browser global
|
|
506
|
+
/* istanbul ignore next */
|
|
507
|
+
if (typeof window !== 'undefined') {
|
|
508
|
+
window.quikdown_ast = quikdown_ast;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export { quikdown_ast as default };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quikdown_ast - AST Markdown Parser
|
|
3
|
+
* @version 1.2.2
|
|
4
|
+
* @license BSD-2-Clause
|
|
5
|
+
* @copyright DeftIO 2025
|
|
6
|
+
*/
|
|
7
|
+
function e(e,n={}){if(!e||"string"!=typeof e)return{type:"document",children:[]};return{type:"document",children:t(e.replace(/\r\n/g,"\n").replace(/\r/g,"\n"))}}function t(e,s){const l=[],r=e.split("\n");let h=0;for(;h<r.length;){const e=r[h];if(""===e.trim()){h++;continue}const s=e.match(/^(```|~~~)(.*)$/);if(s){const[,e,t]=s,n=t.trim(),i=[];for(h++;h<r.length;){if(r[h].match(/^(```|~~~)\s*$/)){h++;break}i.push(r[h]),h++}l.push({type:"code_block",lang:n||null,content:i.join("\n"),fence:e});continue}if(/^---+\s*$/.test(e)||/^\*\*\*+\s*$/.test(e)||/^___+\s*$/.test(e)){l.push({type:"hr"}),h++;continue}const o=e.match(/^(#{1,6})\s*(.+?)\s*#*$/);if(o){const[,e,t]=o;l.push({type:"heading",level:e.length,children:c(t)}),h++;continue}if(e.includes("|")){const e=n(r,h);if(e){l.push(e.node),h=e.nextIndex;continue}}if(e.match(/^>\s*/)){const e=[];for(;h<r.length&&r[h].match(/^>\s*/);)e.push(r[h].replace(/^>\s*/,"")),h++;l.push({type:"blockquote",children:t(e.join("\n"))});continue}if(e.match(/^(\s*)([*\-+]|\d+\.)\s+(.*)$/)){const e=i(r,h);l.push(e.node),h=e.nextIndex;continue}const u=[];for(;h<r.length;){const e=r[h];if(""===e.trim())break;if(/^(```|~~~)/.test(e))break;if(/^#{1,6}\s/.test(e))break;if(/^---+\s*$/.test(e)||/^\*\*\*+\s*$/.test(e)||/^___+\s*$/.test(e))break;if(/^>\s*/.test(e))break;if(/^(\s*)([*\-+]|\d+\.)\s+/.test(e))break;if(e.includes("|")&&h+1<r.length&&/^\|?[\s\-:|]+\|?$/.test(r[h+1]))break;u.push(e),h++}u.length>0&&l.push({type:"paragraph",children:c(u.join("\n"))})}return l}function n(e,t,n){if(t+1>=e.length)return null;const i=e[t],l=e[t+1];if(!/^\|?[\s\-:|]+\|?$/.test(l)||!l.includes("-"))return null;const r=s(i);if(0===r.length)return null;const h=s(l).map(e=>{const t=e.trim();return t.startsWith(":")&&t.endsWith(":")?"center":t.endsWith(":")?"right":"left"}),o=r.map(e=>c(e.trim())),u=[];let p=t+2;for(;p<e.length;){const t=e[p];if(!t.includes("|")||""===t.trim())break;const n=s(t);u.push(n.map(e=>c(e.trim()))),p++}return{node:{type:"table",headers:o,rows:u,alignments:h},nextIndex:p}}function s(e){let t=e.trim();return t.startsWith("|")&&(t=t.slice(1)),t.endsWith("|")&&(t=t.slice(0,-1)),t.split("|")}function i(e,t,n){const s=[];let l=t,r=0;const h=e[l].match(/^(\s*)([*\-+]|\d+\.)\s+(.*)$/),o=/^\d+\./.test(h[2]),u=h[1].length;for(;l<e.length&&r<1e3;){r++;const t=e[l].match(/^(\s*)([*\-+]|\d+\.)\s+(.*)$/);if(!t)break;const[,n,h,p]=t,a=n.length;if(a<u)break;const d=/^\d+\./.test(h);if(a===u&&d!==o)break;if(a>u){const t=[];let n=0;for(;l<e.length&&n<1e3;){n++;const s=e[l],i=s.match(/^(\s*)([*\-+]|\d+\.)\s+/);if(!i)break;if(i[1].length<u)break;if(i[1].length===u)break;t.push(s),l++}if(t.length>0&&s.length>0){const e=i(t,0),n=s[s.length-1];n.children?Array.isArray(n.children)||(n.children=[{type:"paragraph",children:n.children}]):n.children=[],n.children.push(e.node)}continue}const f={type:"list_item",checked:null,children:null},g=p.match(/^\[([x ])\]\s*(.*)$/i);g&&!o?(f.checked="x"===g[1].toLowerCase(),f.children=c(g[2])):f.children=c(p),s.push(f),l++}return{node:{type:"list",ordered:o,items:s},nextIndex:l}}function c(e,t){if(!e)return[];const n=[];let s=e;for(;s.length>0;){let e=!1;if(s.match(/^(.+?)(?: {2}|\\\n|\n)/)&&s.includes("\n")){const t=s.indexOf("\n"),i=s.slice(0,t),c=s.slice(t+1);if(i.endsWith(" ")||i.endsWith("\\")){const t=i.replace(/\\$/,"").replace(/ +$/,"");t&&n.push(...l(t)),n.push({type:"br"}),s=c,e=!0;continue}}const t=s.match(/^!\[([^\]]*)\]\(\s*([^)\s]+)\s*\)/);if(t){n.push({type:"image",alt:t[1],url:t[2].trim()}),s=s.slice(t[0].length),e=!0;continue}const i=s.match(/^\[([^\]]+)\]\(\s*([^)\s]+)\s*\)/);if(i){n.push({type:"link",url:i[2].trim(),children:l(i[1])}),s=s.slice(i[0].length),e=!0;continue}const c=s.match(/^`([^`]+)`/);if(c){n.push({type:"code",value:c[1]}),s=s.slice(c[0].length),e=!0;continue}const r=s.match(/^(\*\*|__)(.+?)\1/);if(r){n.push({type:"strong",children:l(r[2])}),s=s.slice(r[0].length),e=!0;continue}const h=s.match(/^~~(.+?)~~/);if(h){n.push({type:"del",children:l(h[1])}),s=s.slice(h[0].length),e=!0;continue}const o=s.match(/^(\*|_)(?!\1)(.+?)(?<!\1)\1(?!\1)/);if(o){n.push({type:"em",children:l(o[2])}),s=s.slice(o[0].length),e=!0;continue}const u=s.match(/^(https?:\/\/[^\s<>[\]]+)/);if(u)n.push({type:"link",url:u[1],children:[{type:"text",value:u[1]}]}),s=s.slice(u[0].length),e=!0;else if(!e){const e=s.search(/[`*_~![\\n]|https?:\/\//);if(-1===e){n.push({type:"text",value:s});break}0===e?(n.push({type:"text",value:s[0]}),s=s.slice(1)):(n.push({type:"text",value:s.slice(0,e)}),s=s.slice(e))}}return function(e){const t=[];for(const n of e)"text"===n.type&&t.length>0&&"text"===t[t.length-1].type?t[t.length-1].value+=n.value:t.push(n);return t}(n)}function l(e,t){return c(e.replace(/\n/g," "))}e.version="1.2.2","undefined"!=typeof module&&module.exports&&(module.exports=e),"undefined"!=typeof window&&(window.quikdown_ast=e);export{e as default};
|
|
8
|
+
//# sourceMappingURL=quikdown_ast.esm.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quikdown_ast.esm.min.js","sources":["../src/quikdown_ast.js"],"sourcesContent":["/**\n * quikdown_ast - Forgiving markdown to AST parser\n * Converts markdown to a structured Abstract Syntax Tree\n * @param {string} markdown - The markdown source text\n * @param {Object} options - Optional configuration object\n * @returns {Object} - The AST object\n */\n\n// Version will be injected at build time\nconst quikdownVersion = '__QUIKDOWN_VERSION__';\n\n// Safety limit to prevent infinite loops in list parsing\nconst MAX_LOOP_ITERATIONS = 1000;\n\n/**\n * Parse markdown into an AST\n * @param {string} markdown - The markdown source text\n * @param {Object} options - Optional configuration object\n * @returns {Object} - The AST object\n */\nfunction quikdown_ast(markdown, options = {}) {\n if (!markdown || typeof markdown !== 'string') {\n return { type: 'document', children: [] };\n }\n\n // Normalize line endings (handle CRLF, CR, LF uniformly)\n const text = markdown.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n\n const children = parseBlocks(text, options);\n\n return {\n type: 'document',\n children\n };\n}\n\n/**\n * Parse block-level elements\n */\nfunction parseBlocks(text, options) {\n const blocks = [];\n const lines = text.split('\\n');\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n // Empty line - skip\n if (line.trim() === '') {\n i++;\n continue;\n }\n\n // Fenced code block (``` or ~~~)\n const fenceMatch = line.match(/^(```|~~~)(.*)$/);\n if (fenceMatch) {\n const [, openFence, langPart] = fenceMatch;\n const lang = langPart.trim();\n const codeLines = [];\n i++;\n\n // Find closing fence (forgiving: accept mismatched fences or EOF)\n while (i < lines.length) {\n const closingMatch = lines[i].match(/^(```|~~~)\\s*$/);\n if (closingMatch) {\n i++;\n break;\n }\n codeLines.push(lines[i]);\n i++;\n }\n\n blocks.push({\n type: 'code_block',\n lang: lang || null,\n content: codeLines.join('\\n'),\n fence: openFence\n });\n continue;\n }\n\n // Horizontal rule\n if (/^---+\\s*$/.test(line) || /^\\*\\*\\*+\\s*$/.test(line) || /^___+\\s*$/.test(line)) {\n blocks.push({ type: 'hr' });\n i++;\n continue;\n }\n\n // Heading (forgiving: accept #heading without space)\n const headingMatch = line.match(/^(#{1,6})\\s*(.+?)\\s*#*$/);\n if (headingMatch) {\n const [, hashes, content] = headingMatch;\n blocks.push({\n type: 'heading',\n level: hashes.length,\n children: parseInline(content, options)\n });\n i++;\n continue;\n }\n\n // Table (look for separator line)\n if (line.includes('|')) {\n const tableResult = tryParseTable(lines, i, options);\n if (tableResult) {\n blocks.push(tableResult.node);\n i = tableResult.nextIndex;\n continue;\n }\n }\n\n // Blockquote\n if (line.match(/^>\\s*/)) {\n const quoteLines = [];\n while (i < lines.length && lines[i].match(/^>\\s*/)) {\n quoteLines.push(lines[i].replace(/^>\\s*/, ''));\n i++;\n }\n blocks.push({\n type: 'blockquote',\n children: parseBlocks(quoteLines.join('\\n'), options)\n });\n continue;\n }\n\n // List (ordered or unordered)\n const listMatch = line.match(/^(\\s*)([*\\-+]|\\d+\\.)\\s+(.*)$/);\n if (listMatch) {\n const listResult = parseList(lines, i, options);\n blocks.push(listResult.node);\n i = listResult.nextIndex;\n continue;\n }\n\n // Paragraph - collect lines until empty line or block element\n const paragraphLines = [];\n while (i < lines.length) {\n const pLine = lines[i];\n\n // Stop on empty line\n if (pLine.trim() === '') break;\n\n // Stop on block elements\n if (/^(```|~~~)/.test(pLine)) break;\n if (/^#{1,6}\\s/.test(pLine)) break;\n if (/^---+\\s*$/.test(pLine) || /^\\*\\*\\*+\\s*$/.test(pLine) || /^___+\\s*$/.test(pLine)) break;\n if (/^>\\s*/.test(pLine)) break;\n if (/^(\\s*)([*\\-+]|\\d+\\.)\\s+/.test(pLine)) break;\n if (pLine.includes('|') && i + 1 < lines.length && /^\\|?[\\s\\-:|]+\\|?$/.test(lines[i + 1])) break;\n\n paragraphLines.push(pLine);\n i++;\n }\n\n if (paragraphLines.length > 0) {\n blocks.push({\n type: 'paragraph',\n children: parseInline(paragraphLines.join('\\n'), options)\n });\n }\n }\n\n return blocks;\n}\n\n/**\n * Try to parse a table starting at the given line\n */\nfunction tryParseTable(lines, startIndex, options) {\n // Need at least 2 lines (header + separator)\n if (startIndex + 1 >= lines.length) return null;\n\n const headerLine = lines[startIndex];\n const separatorLine = lines[startIndex + 1];\n\n // Check if separator line is valid\n if (!/^\\|?[\\s\\-:|]+\\|?$/.test(separatorLine) || !separatorLine.includes('-')) {\n return null;\n }\n\n // Parse header\n const headerCells = parseTableRow(headerLine);\n if (headerCells.length === 0) return null;\n\n // Parse alignments from separator\n const separatorCells = parseTableRow(separatorLine);\n const alignments = separatorCells.map(cell => {\n const trimmed = cell.trim();\n if (trimmed.startsWith(':') && trimmed.endsWith(':')) return 'center';\n if (trimmed.endsWith(':')) return 'right';\n return 'left';\n });\n\n // Parse headers with inline formatting\n const headers = headerCells.map(cell => parseInline(cell.trim(), options));\n\n // Parse body rows\n const rows = [];\n let i = startIndex + 2;\n while (i < lines.length) {\n const rowLine = lines[i];\n if (!rowLine.includes('|') || rowLine.trim() === '') break;\n\n const cells = parseTableRow(rowLine);\n rows.push(cells.map(cell => parseInline(cell.trim(), options)));\n i++;\n }\n\n return {\n node: {\n type: 'table',\n headers,\n rows,\n alignments\n },\n nextIndex: i\n };\n}\n\n/**\n * Parse a table row into cells\n */\nfunction parseTableRow(line) {\n // Handle pipes at start/end or not\n let trimmed = line.trim();\n if (trimmed.startsWith('|')) trimmed = trimmed.slice(1);\n if (trimmed.endsWith('|')) trimmed = trimmed.slice(0, -1);\n return trimmed.split('|');\n}\n\n/**\n * Parse a list starting at the given line\n */\nfunction parseList(lines, startIndex, options) {\n const items = [];\n let i = startIndex;\n let loopCount = 0;\n\n // Determine initial list type\n const firstMatch = lines[i].match(/^(\\s*)([*\\-+]|\\d+\\.)\\s+(.*)$/);\n const isOrdered = /^\\d+\\./.test(firstMatch[2]);\n const baseIndent = firstMatch[1].length;\n\n while (i < lines.length && loopCount < MAX_LOOP_ITERATIONS) {\n loopCount++;\n const line = lines[i];\n const match = line.match(/^(\\s*)([*\\-+]|\\d+\\.)\\s+(.*)$/);\n\n if (!match) break;\n\n const [, indent, marker, content] = match;\n const indentLevel = indent.length;\n\n // If less indented than base, stop\n if (indentLevel < baseIndent) break;\n\n // If same indentation but different list type, stop\n const itemIsOrdered = /^\\d+\\./.test(marker);\n if (indentLevel === baseIndent && itemIsOrdered !== isOrdered) break;\n\n // If more indented, it's a nested list - handle by collecting sub-lines\n if (indentLevel > baseIndent) {\n // This is a nested list item, collect and parse as sublist\n const subLines = [];\n let subLoopCount = 0;\n while (i < lines.length && subLoopCount < MAX_LOOP_ITERATIONS) {\n subLoopCount++;\n const subLine = lines[i];\n const subMatch = subLine.match(/^(\\s*)([*\\-+]|\\d+\\.)\\s+/);\n if (!subMatch) break;\n if (subMatch[1].length < baseIndent) break;\n if (subMatch[1].length === baseIndent) break;\n subLines.push(subLine);\n i++;\n }\n\n if (subLines.length > 0 && items.length > 0) {\n // Add nested list to last item\n const nestedResult = parseList(subLines, 0, options);\n const lastItem = items[items.length - 1];\n if (!lastItem.children) {\n lastItem.children = [];\n } else if (!Array.isArray(lastItem.children)) {\n lastItem.children = [{ type: 'paragraph', children: lastItem.children }];\n }\n lastItem.children.push(nestedResult.node);\n }\n continue;\n }\n\n // Parse list item\n const itemNode = {\n type: 'list_item',\n checked: null,\n children: null\n };\n\n // Check for task list syntax\n const taskMatch = content.match(/^\\[([x ])\\]\\s*(.*)$/i);\n if (taskMatch && !isOrdered) {\n itemNode.checked = taskMatch[1].toLowerCase() === 'x';\n itemNode.children = parseInline(taskMatch[2], options);\n } else {\n itemNode.children = parseInline(content, options);\n }\n\n items.push(itemNode);\n i++;\n }\n\n return {\n node: {\n type: 'list',\n ordered: isOrdered,\n items\n },\n nextIndex: i\n };\n}\n\n/**\n * Parse inline elements\n */\nfunction parseInline(text, options) {\n if (!text) return [];\n\n const nodes = [];\n let remaining = text;\n\n while (remaining.length > 0) {\n let matched = false;\n\n // Line break (1+ trailing spaces or explicit \\n after processing)\n // Handle inline line breaks (two spaces at end of line or backslash before newline)\n const brMatch = remaining.match(/^(.+?)(?: {2}|\\\\\\n|\\n)/);\n if (brMatch && remaining.includes('\\n')) {\n const beforeBr = remaining.indexOf('\\n');\n const beforeText = remaining.slice(0, beforeBr);\n const afterText = remaining.slice(beforeBr + 1);\n\n // Check if line break is significant (2+ trailing spaces or backslash)\n if (beforeText.endsWith(' ') || beforeText.endsWith('\\\\')) {\n const cleanText = beforeText.replace(/\\\\$/, '').replace(/ +$/, '');\n if (cleanText) {\n nodes.push(...parseInlineContent(cleanText, options));\n }\n nodes.push({ type: 'br' });\n remaining = afterText;\n matched = true;\n continue;\n }\n }\n\n // Images: \n const imgMatch = remaining.match(/^!\\[([^\\]]*)\\]\\(\\s*([^)\\s]+)\\s*\\)/);\n if (imgMatch) {\n nodes.push({\n type: 'image',\n alt: imgMatch[1],\n url: imgMatch[2].trim() // Forgiving: trim whitespace in URL\n });\n remaining = remaining.slice(imgMatch[0].length);\n matched = true;\n continue;\n }\n\n // Links: [text](url)\n const linkMatch = remaining.match(/^\\[([^\\]]+)\\]\\(\\s*([^)\\s]+)\\s*\\)/);\n if (linkMatch) {\n nodes.push({\n type: 'link',\n url: linkMatch[2].trim(), // Forgiving: trim whitespace in URL\n children: parseInlineContent(linkMatch[1], options)\n });\n remaining = remaining.slice(linkMatch[0].length);\n matched = true;\n continue;\n }\n\n // Inline code: `code`\n const codeMatch = remaining.match(/^`([^`]+)`/);\n if (codeMatch) {\n nodes.push({\n type: 'code',\n value: codeMatch[1]\n });\n remaining = remaining.slice(codeMatch[0].length);\n matched = true;\n continue;\n }\n\n // Bold: **text** or __text__\n const boldMatch = remaining.match(/^(\\*\\*|__)(.+?)\\1/);\n if (boldMatch) {\n nodes.push({\n type: 'strong',\n children: parseInlineContent(boldMatch[2], options)\n });\n remaining = remaining.slice(boldMatch[0].length);\n matched = true;\n continue;\n }\n\n // Strikethrough: ~~text~~\n const strikeMatch = remaining.match(/^~~(.+?)~~/);\n if (strikeMatch) {\n nodes.push({\n type: 'del',\n children: parseInlineContent(strikeMatch[1], options)\n });\n remaining = remaining.slice(strikeMatch[0].length);\n matched = true;\n continue;\n }\n\n // Italic: *text* or _text_ (not at word boundary for underscores)\n const emMatch = remaining.match(/^(\\*|_)(?!\\1)(.+?)(?<!\\1)\\1(?!\\1)/);\n if (emMatch) {\n nodes.push({\n type: 'em',\n children: parseInlineContent(emMatch[2], options)\n });\n remaining = remaining.slice(emMatch[0].length);\n matched = true;\n continue;\n }\n\n // Autolinks: URLs starting with http:// or https://\n const urlMatch = remaining.match(/^(https?:\\/\\/[^\\s<>[\\]]+)/);\n if (urlMatch) {\n nodes.push({\n type: 'link',\n url: urlMatch[1],\n children: [{ type: 'text', value: urlMatch[1] }]\n });\n remaining = remaining.slice(urlMatch[0].length);\n matched = true;\n continue;\n }\n\n // Plain text - consume until next potential inline element or end\n if (!matched) {\n // Find next potential inline marker\n const nextMarker = remaining.search(/[`*_~![\\\\n]|https?:\\/\\//);\n if (nextMarker === -1) {\n // No more markers, consume rest as text\n nodes.push({ type: 'text', value: remaining });\n break;\n } else if (nextMarker === 0) {\n // Current char is a marker but didn't match - consume it as text\n nodes.push({ type: 'text', value: remaining[0] });\n remaining = remaining.slice(1);\n } else {\n // Consume text up to next marker\n nodes.push({ type: 'text', value: remaining.slice(0, nextMarker) });\n remaining = remaining.slice(nextMarker);\n }\n }\n }\n\n // Merge adjacent text nodes\n return mergeTextNodes(nodes);\n}\n\n/**\n * Parse inline content (recursive helper for nested inline elements)\n */\nfunction parseInlineContent(text, options) {\n // For simple nested content, use parseInline\n // But handle newlines as spaces for inline content\n const normalized = text.replace(/\\n/g, ' ');\n return parseInline(normalized, options);\n}\n\n/**\n * Merge adjacent text nodes\n */\nfunction mergeTextNodes(nodes) {\n const merged = [];\n for (const node of nodes) {\n if (node.type === 'text' && merged.length > 0 && merged[merged.length - 1].type === 'text') {\n merged[merged.length - 1].value += node.value;\n } else {\n merged.push(node);\n }\n }\n return merged;\n}\n\n// Attach version\nquikdown_ast.version = quikdownVersion;\n\n// Export for both CommonJS and ES6\n/* istanbul ignore next */\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = quikdown_ast;\n}\n\n// For browser global\n/* istanbul ignore next */\nif (typeof window !== 'undefined') {\n window.quikdown_ast = quikdown_ast;\n}\n\nexport default quikdown_ast;\n"],"names":["quikdown_ast","markdown","options","type","children","parseBlocks","replace","text","blocks","lines","split","i","length","line","trim","fenceMatch","match","openFence","langPart","lang","codeLines","push","content","join","fence","test","headingMatch","hashes","level","parseInline","includes","tableResult","tryParseTable","node","nextIndex","quoteLines","listResult","parseList","paragraphLines","pLine","startIndex","headerLine","separatorLine","headerCells","parseTableRow","alignments","map","cell","trimmed","startsWith","endsWith","headers","rows","rowLine","cells","slice","items","loopCount","firstMatch","isOrdered","baseIndent","indent","marker","indentLevel","itemIsOrdered","subLines","subLoopCount","subLine","subMatch","nestedResult","lastItem","Array","isArray","itemNode","checked","taskMatch","toLowerCase","ordered","nodes","remaining","matched","beforeBr","indexOf","beforeText","afterText","cleanText","parseInlineContent","imgMatch","alt","url","linkMatch","codeMatch","value","boldMatch","strikeMatch","emMatch","urlMatch","nextMarker","search","merged","mergeTextNodes","version","module","exports","window"],"mappings":"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"}
|