quikdown 1.0.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.
@@ -0,0 +1,585 @@
1
+ /**
2
+ * quikdown - Lightweight Markdown Parser
3
+ * @version 1.0.1
4
+ * @license BSD-2-Clause
5
+ * @copyright DeftIO 2025
6
+ */
7
+ // Auto-generated version file - DO NOT EDIT MANUALLY
8
+ // This file is automatically updated by tools/updateVersion.js
9
+
10
+ const quikdownVersion = "1.0.1";
11
+
12
+ /**
13
+ * quikdown - A minimal markdown parser optimized for chat/LLM output
14
+ * Supports tables, code blocks, lists, and common formatting
15
+ * @param {string} markdown - The markdown source text
16
+ * @param {Object} options - Optional configuration object
17
+ * @param {Function} options.fence_plugin - Custom renderer for fenced code blocks
18
+ * (content, fence_string) => html string
19
+ * @param {boolean} options.inline_styles - If true, uses inline styles instead of classes
20
+ * @returns {string} - The rendered HTML
21
+ */
22
+
23
+
24
+ function quikdown(markdown, options = {}) {
25
+ if (!markdown || typeof markdown !== 'string') {
26
+ return '';
27
+ }
28
+
29
+ const { fence_plugin, inline_styles = false } = options;
30
+
31
+ // Style definitions - deduplicated to save space
32
+ const headingStyle = 'margin-top: 0.5em; margin-bottom: 0.3em';
33
+ const listStyle = 'margin: 0.5em 0; padding-left: 2em';
34
+ const cellBorder = 'border: 1px solid #ddd; padding: 8px';
35
+
36
+ const styles = {
37
+ h1: headingStyle,
38
+ h2: headingStyle,
39
+ h3: headingStyle,
40
+ h4: headingStyle,
41
+ h5: headingStyle,
42
+ h6: headingStyle,
43
+ pre: 'background: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto',
44
+ code: 'background: #f0f0f0; padding: 2px 4px; border-radius: 3px',
45
+ blockquote: 'border-left: 4px solid #ddd; margin-left: 0; padding-left: 1em; color: #666',
46
+ table: 'border-collapse: collapse; width: 100%; margin: 1em 0',
47
+ thead: '',
48
+ tbody: '',
49
+ tr: '',
50
+ th: cellBorder + '; background-color: #f2f2f2; font-weight: bold',
51
+ td: cellBorder + '; text-align: left',
52
+ hr: 'border: none; border-top: 1px solid #ddd; margin: 1em 0',
53
+ img: 'max-width: 100%; height: auto',
54
+ a: 'color: #0066cc; text-decoration: underline',
55
+ strong: 'font-weight: bold',
56
+ em: 'font-style: italic',
57
+ del: 'text-decoration: line-through',
58
+ ul: listStyle,
59
+ ol: listStyle,
60
+ li: 'margin: 0.25em 0',
61
+ br: ''
62
+ };
63
+
64
+ // Helper to get class or style attribute
65
+ function getAttr(tag, additionalStyle = '') {
66
+ if (inline_styles) {
67
+ const style = styles[tag] || '';
68
+ const fullStyle = additionalStyle ? `${style}; ${additionalStyle}` : style;
69
+ return fullStyle ? ` style="${fullStyle}"` : '';
70
+ } else {
71
+ return ` class="quikdown-${tag}"`;
72
+ }
73
+ }
74
+
75
+ // Escape HTML entities to prevent XSS
76
+ function escapeHtml(text) {
77
+ const map = {
78
+ '&': '&',
79
+ '<': '&lt;',
80
+ '>': '&gt;',
81
+ '"': '&quot;',
82
+ "'": '&#39;'
83
+ };
84
+ return text.replace(/[&<>"']/g, m => map[m]);
85
+ }
86
+
87
+ // Sanitize URLs to prevent XSS attacks
88
+ function sanitizeUrl(url, allowUnsafe = false) {
89
+ if (!url) return '';
90
+
91
+ // If unsafe URLs are explicitly allowed, return as-is
92
+ if (allowUnsafe) return url;
93
+
94
+ // Trim and lowercase for checking
95
+ const trimmedUrl = url.trim();
96
+ const lowerUrl = trimmedUrl.toLowerCase();
97
+
98
+ // Block dangerous protocols
99
+ const dangerousProtocols = ['javascript:', 'vbscript:', 'data:'];
100
+
101
+ for (const protocol of dangerousProtocols) {
102
+ if (lowerUrl.startsWith(protocol)) {
103
+ // Exception: Allow data:image/* for images
104
+ if (protocol === 'data:' && lowerUrl.startsWith('data:image/')) {
105
+ return trimmedUrl;
106
+ }
107
+ // Return safe empty link for dangerous protocols
108
+ return '#';
109
+ }
110
+ }
111
+
112
+ return trimmedUrl;
113
+ }
114
+
115
+ // Process the markdown in phases
116
+ let html = markdown;
117
+
118
+ // Phase 1: Extract and protect code blocks and inline code
119
+ const codeBlocks = [];
120
+ const inlineCodes = [];
121
+
122
+ // Extract fenced code blocks first (supports both ``` and ~~~)
123
+ html = html.replace(/(?:```|~~~)([^\n]*)\n([\s\S]*?)(?:```|~~~)/g, (match, lang, code) => {
124
+ const placeholder = `%%%CODEBLOCK${codeBlocks.length}%%%`;
125
+
126
+ // If custom fence plugin is provided, use it
127
+ if (fence_plugin && typeof fence_plugin === 'function') {
128
+ codeBlocks.push({
129
+ lang: lang || '',
130
+ code: code.trimEnd(),
131
+ custom: true
132
+ });
133
+ } else {
134
+ codeBlocks.push({
135
+ lang: lang || '',
136
+ code: escapeHtml(code.trimEnd()),
137
+ custom: false
138
+ });
139
+ }
140
+ return placeholder;
141
+ });
142
+
143
+ // Extract inline code
144
+ html = html.replace(/`([^`]+)`/g, (match, code) => {
145
+ const placeholder = `%%%INLINECODE${inlineCodes.length}%%%`;
146
+ inlineCodes.push(escapeHtml(code));
147
+ return placeholder;
148
+ });
149
+
150
+ // Now escape HTML in the rest of the content
151
+ html = escapeHtml(html);
152
+
153
+ // Phase 2: Process block elements
154
+
155
+ // Process tables
156
+ html = processTable(html, inline_styles, styles);
157
+
158
+ // Process headings (supports optional trailing #'s)
159
+ html = html.replace(/^(#{1,6})\s+(.+?)\s*#*$/gm, (match, hashes, content) => {
160
+ const level = hashes.length;
161
+ return `<h${level}${getAttr('h' + level)}>${content}</h${level}>`;
162
+ });
163
+
164
+ // Process blockquotes (must handle escaped > since we already escaped HTML)
165
+ html = html.replace(/^&gt;\s+(.+)$/gm, `<blockquote${getAttr('blockquote')}>$1</blockquote>`);
166
+ // Merge consecutive blockquotes
167
+ html = html.replace(/<\/blockquote>\n<blockquote>/g, '\n');
168
+
169
+ // Process horizontal rules
170
+ html = html.replace(/^---+$/gm, `<hr${getAttr('hr')}>`);
171
+
172
+ // Process lists
173
+ html = processLists(html, inline_styles, styles);
174
+
175
+ // Phase 3: Process inline elements
176
+
177
+ // Images (must come before links, with URL sanitization)
178
+ html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
179
+ const sanitizedSrc = sanitizeUrl(src, options.allow_unsafe_urls);
180
+ return `<img${getAttr('img')} src="${sanitizedSrc}" alt="${alt}">`;
181
+ });
182
+
183
+ // Links (with URL sanitization)
184
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, href) => {
185
+ // Sanitize URL to prevent XSS
186
+ const sanitizedHref = sanitizeUrl(href, options.allow_unsafe_urls);
187
+ const isExternal = /^https?:\/\//i.test(sanitizedHref);
188
+ const rel = isExternal ? ' rel="noopener noreferrer"' : '';
189
+ return `<a${getAttr('a')} href="${sanitizedHref}"${rel}>${text}</a>`;
190
+ });
191
+
192
+ // Autolinks - convert bare URLs to clickable links
193
+ html = html.replace(/(^|\s)(https?:\/\/[^\s<]+)/g, (match, prefix, url) => {
194
+ const sanitizedUrl = sanitizeUrl(url, options.allow_unsafe_urls);
195
+ return `${prefix}<a${getAttr('a')} href="${sanitizedUrl}" rel="noopener noreferrer">${url}</a>`;
196
+ });
197
+
198
+ // Bold (must use non-greedy matching)
199
+ html = html.replace(/\*\*(.+?)\*\*/g, `<strong${getAttr('strong')}>$1</strong>`);
200
+ html = html.replace(/__(.+?)__/g, `<strong${getAttr('strong')}>$1</strong>`);
201
+
202
+ // Italic (must not match bold markers)
203
+ html = html.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, `<em${getAttr('em')}>$1</em>`);
204
+ html = html.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, `<em${getAttr('em')}>$1</em>`);
205
+
206
+ // Strikethrough
207
+ html = html.replace(/~~(.+?)~~/g, `<del${getAttr('del')}>$1</del>`);
208
+
209
+ // Line breaks (two spaces at end of line)
210
+ html = html.replace(/ $/gm, `<br${getAttr('br')}>`);
211
+
212
+ // Paragraphs (double newlines)
213
+ html = html.replace(/\n\n+/g, '</p><p>');
214
+ html = '<p>' + html + '</p>';
215
+
216
+ // Clean up empty paragraphs and unwrap block elements (account for attributes)
217
+ html = html.replace(/<p><\/p>/g, '');
218
+ html = html.replace(/<p>(<h[1-6][^>]*>)/g, '$1');
219
+ html = html.replace(/(<\/h[1-6]>)<\/p>/g, '$1');
220
+ html = html.replace(/<p>(<blockquote[^>]*>)/g, '$1');
221
+ html = html.replace(/(<\/blockquote>)<\/p>/g, '$1');
222
+ html = html.replace(/<p>(<ul[^>]*>|<ol[^>]*>)/g, '$1');
223
+ html = html.replace(/(<\/ul>|<\/ol>)<\/p>/g, '$1');
224
+ html = html.replace(/<p>(<hr[^>]*>)<\/p>/g, '$1');
225
+ html = html.replace(/<p>(<table[^>]*>)/g, '$1');
226
+ html = html.replace(/(<\/table>)<\/p>/g, '$1');
227
+ html = html.replace(/<p>(<pre[^>]*>)/g, '$1');
228
+ html = html.replace(/(<\/pre>)<\/p>/g, '$1');
229
+ // Also unwrap code block placeholders
230
+ html = html.replace(/<p>(%%%CODEBLOCK\d+%%%)<\/p>/g, '$1');
231
+
232
+ // Phase 4: Restore code blocks and inline code
233
+
234
+ // Restore code blocks
235
+ codeBlocks.forEach((block, i) => {
236
+ let replacement;
237
+
238
+ if (block.custom && fence_plugin) {
239
+ // Use custom fence plugin
240
+ replacement = fence_plugin(block.code, block.lang);
241
+ // If plugin returns undefined, fall back to default rendering
242
+ if (replacement === undefined) {
243
+ const langClass = !inline_styles && block.lang ? ` class="language-${block.lang}"` : '';
244
+ const codeAttr = inline_styles ? getAttr('code') : langClass;
245
+ replacement = `<pre${getAttr('pre')}><code${codeAttr}>${escapeHtml(block.code)}</code></pre>`;
246
+ }
247
+ } else {
248
+ // Default rendering
249
+ const langClass = !inline_styles && block.lang ? ` class="language-${block.lang}"` : '';
250
+ const codeAttr = inline_styles ? getAttr('code') : langClass;
251
+ replacement = `<pre${getAttr('pre')}><code${codeAttr}>${block.code}</code></pre>`;
252
+ }
253
+
254
+ const placeholder = `%%%CODEBLOCK${i}%%%`;
255
+ html = html.replace(placeholder, replacement);
256
+ });
257
+
258
+ // Restore inline code
259
+ inlineCodes.forEach((code, i) => {
260
+ const placeholder = `%%%INLINECODE${i}%%%`;
261
+ html = html.replace(placeholder, `<code${getAttr('code')}>${code}</code>`);
262
+ });
263
+
264
+ return html.trim();
265
+ }
266
+
267
+ /**
268
+ * Process inline markdown formatting
269
+ */
270
+ function processInlineMarkdown(text, inline_styles, styles) {
271
+ // Helper to get attributes
272
+ function getAttr(tag, additionalStyle = '') {
273
+ if (inline_styles) {
274
+ const style = styles[tag] || '';
275
+ const fullStyle = additionalStyle ? `${style}; ${additionalStyle}` : style;
276
+ return fullStyle ? ` style="${fullStyle}"` : '';
277
+ } else {
278
+ return ` class="quikdown-${tag}"`;
279
+ }
280
+ }
281
+
282
+ // Process bold
283
+ text = text.replace(/\*\*(.+?)\*\*/g, `<strong${getAttr('strong')}>$1</strong>`);
284
+ text = text.replace(/__(.+?)__/g, `<strong${getAttr('strong')}>$1</strong>`);
285
+
286
+ // Process italic (must not match bold markers)
287
+ text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, `<em${getAttr('em')}>$1</em>`);
288
+ text = text.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, `<em${getAttr('em')}>$1</em>`);
289
+
290
+ // Process strikethrough
291
+ text = text.replace(/~~(.+?)~~/g, `<del${getAttr('del')}>$1</del>`);
292
+
293
+ // Process inline code
294
+ text = text.replace(/`([^`]+)`/g, `<code${getAttr('code')}>$1</code>`);
295
+
296
+ return text;
297
+ }
298
+
299
+ /**
300
+ * Process markdown tables
301
+ */
302
+ function processTable(text, inline_styles, styles) {
303
+ const lines = text.split('\n');
304
+ const result = [];
305
+ let inTable = false;
306
+ let tableLines = [];
307
+
308
+ for (let i = 0; i < lines.length; i++) {
309
+ const line = lines[i].trim();
310
+
311
+ // Check if this line looks like a table row (with or without trailing |)
312
+ if (line.includes('|') && (line.startsWith('|') || /[^\\|]/.test(line))) {
313
+ if (!inTable) {
314
+ inTable = true;
315
+ tableLines = [];
316
+ }
317
+ tableLines.push(line);
318
+ } else {
319
+ // Not a table line
320
+ if (inTable) {
321
+ // Process the accumulated table
322
+ const tableHtml = buildTable(tableLines, inline_styles, styles);
323
+ if (tableHtml) {
324
+ result.push(tableHtml);
325
+ } else {
326
+ // Not a valid table, restore original lines
327
+ result.push(...tableLines);
328
+ }
329
+ inTable = false;
330
+ tableLines = [];
331
+ }
332
+ result.push(lines[i]);
333
+ }
334
+ }
335
+
336
+ // Handle table at end of text
337
+ if (inTable && tableLines.length > 0) {
338
+ const tableHtml = buildTable(tableLines, inline_styles, styles);
339
+ if (tableHtml) {
340
+ result.push(tableHtml);
341
+ } else {
342
+ result.push(...tableLines);
343
+ }
344
+ }
345
+
346
+ return result.join('\n');
347
+ }
348
+
349
+ /**
350
+ * Build an HTML table from markdown table lines
351
+ */
352
+ function buildTable(lines, inline_styles, styles) {
353
+ // Helper to get attributes
354
+ function getAttr(tag, additionalStyle = '') {
355
+ if (inline_styles) {
356
+ const style = styles[tag] || '';
357
+ const fullStyle = additionalStyle ? `${style}; ${additionalStyle}` : style;
358
+ return fullStyle ? ` style="${fullStyle}"` : '';
359
+ } else {
360
+ return ` class="quikdown-${tag}"`;
361
+ }
362
+ }
363
+
364
+ if (lines.length < 2) return null;
365
+
366
+ // Check for separator line (second line should be the separator)
367
+ let separatorIndex = -1;
368
+ for (let i = 1; i < lines.length; i++) {
369
+ // Support separator with or without leading/trailing pipes
370
+ if (/^\|?[\s\-:|]+\|?$/.test(lines[i]) && lines[i].includes('-')) {
371
+ separatorIndex = i;
372
+ break;
373
+ }
374
+ }
375
+
376
+ if (separatorIndex === -1) return null;
377
+
378
+ const headerLines = lines.slice(0, separatorIndex);
379
+ const bodyLines = lines.slice(separatorIndex + 1);
380
+
381
+ // Parse alignment from separator
382
+ const separator = lines[separatorIndex];
383
+ // Handle pipes at start/end or not
384
+ const separatorCells = separator.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
385
+ const alignments = separatorCells.map(cell => {
386
+ const trimmed = cell.trim();
387
+ if (trimmed.startsWith(':') && trimmed.endsWith(':')) return 'center';
388
+ if (trimmed.endsWith(':')) return 'right';
389
+ return 'left';
390
+ });
391
+
392
+ let html = `<table${getAttr('table')}>\n`;
393
+
394
+ // Build header
395
+ if (headerLines.length > 0) {
396
+ html += `<thead${getAttr('thead')}>\n`;
397
+ headerLines.forEach(line => {
398
+ html += `<tr${getAttr('tr')}>\n`;
399
+ // Handle pipes at start/end or not
400
+ const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
401
+ cells.forEach((cell, i) => {
402
+ const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align: ${alignments[i]}` : '';
403
+ const processedCell = processInlineMarkdown(cell.trim(), inline_styles, styles);
404
+ html += `<th${getAttr('th', alignStyle)}>${processedCell}</th>\n`;
405
+ });
406
+ html += '</tr>\n';
407
+ });
408
+ html += '</thead>\n';
409
+ }
410
+
411
+ // Build body
412
+ if (bodyLines.length > 0) {
413
+ html += `<tbody${getAttr('tbody')}>\n`;
414
+ bodyLines.forEach(line => {
415
+ html += `<tr${getAttr('tr')}>\n`;
416
+ // Handle pipes at start/end or not
417
+ const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
418
+ cells.forEach((cell, i) => {
419
+ const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align: ${alignments[i]}` : '';
420
+ const processedCell = processInlineMarkdown(cell.trim(), inline_styles, styles);
421
+ html += `<td${getAttr('td', alignStyle)}>${processedCell}</td>\n`;
422
+ });
423
+ html += '</tr>\n';
424
+ });
425
+ html += '</tbody>\n';
426
+ }
427
+
428
+ html += '</table>';
429
+ return html;
430
+ }
431
+
432
+ /**
433
+ * Process markdown lists (ordered and unordered)
434
+ */
435
+ function processLists(text, inline_styles, styles) {
436
+ // Helper to get attributes
437
+ function getAttr(tag, additionalStyle = '') {
438
+ if (inline_styles) {
439
+ const style = styles[tag] || '';
440
+ const fullStyle = additionalStyle ? `${style}; ${additionalStyle}` : style;
441
+ return fullStyle ? ` style="${fullStyle}"` : '';
442
+ } else {
443
+ return ` class="quikdown-${tag}"`;
444
+ }
445
+ }
446
+
447
+ const lines = text.split('\n');
448
+ const result = [];
449
+ let listStack = []; // Track nested lists
450
+
451
+ for (let i = 0; i < lines.length; i++) {
452
+ const line = lines[i];
453
+ const match = line.match(/^(\s*)([*\-+]|\d+\.)\s+(.+)$/);
454
+
455
+ if (match) {
456
+ const [, indent, marker, content] = match;
457
+ const level = Math.floor(indent.length / 2);
458
+ const isOrdered = /^\d+\./.test(marker);
459
+ const listType = isOrdered ? 'ol' : 'ul';
460
+
461
+ // Check for task list items
462
+ let listItemContent = content;
463
+ let taskListClass = '';
464
+ const taskMatch = content.match(/^\[([x ])\]\s+(.*)$/i);
465
+ if (taskMatch && !isOrdered) {
466
+ const [, checked, taskContent] = taskMatch;
467
+ const isChecked = checked.toLowerCase() === 'x';
468
+ const checkboxAttr = inline_styles
469
+ ? ' style="margin-right: 0.5em"'
470
+ : ' class="quikdown-task-checkbox"';
471
+ listItemContent = `<input type="checkbox"${checkboxAttr}${isChecked ? ' checked' : ''} disabled> ${taskContent}`;
472
+ taskListClass = inline_styles ? ' style="list-style: none"' : ' class="quikdown-task-item"';
473
+ }
474
+
475
+ // Close deeper levels
476
+ while (listStack.length > level + 1) {
477
+ const list = listStack.pop();
478
+ result.push(`</${list.type}>`);
479
+ }
480
+
481
+ // Open new level if needed
482
+ if (listStack.length === level) {
483
+ // Need to open a new list
484
+ listStack.push({ type: listType, level });
485
+ result.push(`<${listType}${getAttr(listType)}>`);
486
+ } else if (listStack.length === level + 1) {
487
+ // Check if we need to switch list type
488
+ const currentList = listStack[listStack.length - 1];
489
+ if (currentList.type !== listType) {
490
+ result.push(`</${currentList.type}>`);
491
+ listStack.pop();
492
+ listStack.push({ type: listType, level });
493
+ result.push(`<${listType}${getAttr(listType)}>`);
494
+ }
495
+ }
496
+
497
+ const liAttr = taskListClass || getAttr('li');
498
+ result.push(`<li${liAttr}>${listItemContent}</li>`);
499
+ } else {
500
+ // Not a list item, close all lists
501
+ while (listStack.length > 0) {
502
+ const list = listStack.pop();
503
+ result.push(`</${list.type}>`);
504
+ }
505
+ result.push(line);
506
+ }
507
+ }
508
+
509
+ // Close any remaining lists
510
+ while (listStack.length > 0) {
511
+ const list = listStack.pop();
512
+ result.push(`</${list.type}>`);
513
+ }
514
+
515
+ return result.join('\n');
516
+ }
517
+
518
+ /**
519
+ * Emit CSS styles for quikdown elements
520
+ * @returns {string} CSS string with quikdown styles
521
+ */
522
+ quikdown.emitStyles = function() {
523
+ const styles = {
524
+ h1: 'margin-top: 0.5em; margin-bottom: 0.3em',
525
+ h2: 'margin-top: 0.5em; margin-bottom: 0.3em',
526
+ h3: 'margin-top: 0.5em; margin-bottom: 0.3em',
527
+ h4: 'margin-top: 0.5em; margin-bottom: 0.3em',
528
+ h5: 'margin-top: 0.5em; margin-bottom: 0.3em',
529
+ h6: 'margin-top: 0.5em; margin-bottom: 0.3em',
530
+ pre: 'background: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto',
531
+ code: 'background: #f0f0f0; padding: 2px 4px; border-radius: 3px',
532
+ blockquote: 'border-left: 4px solid #ddd; margin-left: 0; padding-left: 1em; color: #666',
533
+ table: 'border-collapse: collapse; width: 100%; margin: 1em 0',
534
+ th: 'border: 1px solid #ddd; padding: 8px; background-color: #f2f2f2; font-weight: bold',
535
+ td: 'border: 1px solid #ddd; padding: 8px; text-align: left',
536
+ hr: 'border: none; border-top: 1px solid #ddd; margin: 1em 0',
537
+ img: 'max-width: 100%; height: auto',
538
+ a: 'color: #0066cc; text-decoration: underline',
539
+ strong: 'font-weight: bold',
540
+ em: 'font-style: italic',
541
+ del: 'text-decoration: line-through',
542
+ ul: 'margin: 0.5em 0; padding-left: 2em',
543
+ ol: 'margin: 0.5em 0; padding-left: 2em',
544
+ li: 'margin: 0.25em 0',
545
+ 'task-item': 'list-style: none',
546
+ 'task-checkbox': 'margin-right: 0.5em'
547
+ };
548
+
549
+ let css = '';
550
+ for (const [tag, style] of Object.entries(styles)) {
551
+ if (style) {
552
+ css += `.quikdown-${tag} { ${style} }\n`;
553
+ }
554
+ }
555
+
556
+ return css;
557
+ };
558
+
559
+ /**
560
+ * Configure quikdown with options and return a function
561
+ * @param {Object} options - Configuration options
562
+ * @returns {Function} Configured quikdown function
563
+ */
564
+ quikdown.configure = function(options) {
565
+ return function(markdown) {
566
+ return quikdown(markdown, options);
567
+ };
568
+ };
569
+
570
+ /**
571
+ * Version information
572
+ */
573
+ quikdown.version = quikdownVersion;
574
+
575
+ // Export for both CommonJS and ES6
576
+ if (typeof module !== 'undefined' && module.exports) {
577
+ module.exports = quikdown;
578
+ }
579
+
580
+ // For browser global
581
+ if (typeof window !== 'undefined') {
582
+ window.quikdown = quikdown;
583
+ }
584
+
585
+ export { quikdown as default };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * quikdown - Lightweight Markdown Parser
3
+ * @version 1.0.1
4
+ * @license BSD-2-Clause
5
+ * @copyright DeftIO 2025
6
+ */
7
+ function e(e,t={}){if(!e||"string"!=typeof e)return"";const{fence_plugin:o,inline_styles:n=!1}=t,l="margin-top: 0.5em; margin-bottom: 0.3em",a="margin: 0.5em 0; padding-left: 2em",c="border: 1px solid #ddd; padding: 8px",i={h1:l,h2:l,h3:l,h4:l,h5:l,h6:l,pre:"background: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto",code:"background: #f0f0f0; padding: 2px 4px; border-radius: 3px",blockquote:"border-left: 4px solid #ddd; margin-left: 0; padding-left: 1em; color: #666",table:"border-collapse: collapse; width: 100%; margin: 1em 0",thead:"",tbody:"",tr:"",th:c+"; background-color: #f2f2f2; font-weight: bold",td:c+"; text-align: left",hr:"border: none; border-top: 1px solid #ddd; margin: 1em 0",img:"max-width: 100%; height: auto",a:"color: #0066cc; text-decoration: underline",strong:"font-weight: bold",em:"font-style: italic",del:"text-decoration: line-through",ul:a,ol:a,li:"margin: 0.25em 0",br:""};function s(e,t=""){if(n){const r=i[e]||"",o=t?`${r}; ${t}`:r;return o?` style="${o}"`:""}return` class="quikdown-${e}"`}function p(e){const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};return e.replace(/[&<>"']/g,e=>t[e])}function d(e,t=!1){if(!e)return"";if(t)return e;const r=e.trim(),o=r.toLowerCase(),n=["javascript:","vbscript:","data:"];for(const e of n)if(o.startsWith(e))return"data:"===e&&o.startsWith("data:image/")?r:"#";return r}let g=e;const u=[],$=[];return g=g.replace(/(?:```|~~~)([^\n]*)\n([\s\S]*?)(?:```|~~~)/g,(e,t,r)=>{const n=`%%%CODEBLOCK${u.length}%%%`;return o&&"function"==typeof o?u.push({lang:t||"",code:r.trimEnd(),custom:!0}):u.push({lang:t||"",code:p(r.trimEnd()),custom:!1}),n}),g=g.replace(/`([^`]+)`/g,(e,t)=>{const r=`%%%INLINECODE${$.length}%%%`;return $.push(p(t)),r}),g=p(g),g=function(e,t,o){const n=e.split("\n"),l=[];let a=!1,c=[];for(let e=0;e<n.length;e++){const i=n[e].trim();if(i.includes("|")&&(i.startsWith("|")||/[^\\|]/.test(i)))a||(a=!0,c=[]),c.push(i);else{if(a){const e=r(c,t,o);e?l.push(e):l.push(...c),a=!1,c=[]}l.push(n[e])}}if(a&&c.length>0){const e=r(c,t,o);e?l.push(e):l.push(...c)}return l.join("\n")}(g,n,i),g=g.replace(/^(#{1,6})\s+(.+?)\s*#*$/gm,(e,t,r)=>{const o=t.length;return`<h${o}${s("h"+o)}>${r}</h${o}>`}),g=g.replace(/^&gt;\s+(.+)$/gm,`<blockquote${s("blockquote")}>$1</blockquote>`),g=g.replace(/<\/blockquote>\n<blockquote>/g,"\n"),g=g.replace(/^---+$/gm,`<hr${s("hr")}>`),g=function(e,t,r){function o(e,o=""){if(t){const t=r[e]||"",n=o?`${t}; ${o}`:t;return n?` style="${n}"`:""}return` class="quikdown-${e}"`}const n=e.split("\n"),l=[];let a=[];for(let e=0;e<n.length;e++){const r=n[e],c=r.match(/^(\s*)([*\-+]|\d+\.)\s+(.+)$/);if(c){const[,e,r,n]=c,i=Math.floor(e.length/2),s=/^\d+\./.test(r),p=s?"ol":"ul";let d=n,g="";const u=n.match(/^\[([x ])\]\s+(.*)$/i);if(u&&!s){const[,e,r]=u,o="x"===e.toLowerCase();d=`<input type="checkbox"${t?' style="margin-right: 0.5em"':' class="quikdown-task-checkbox"'}${o?" checked":""} disabled> ${r}`,g=t?' style="list-style: none"':' class="quikdown-task-item"'}for(;a.length>i+1;){const e=a.pop();l.push(`</${e.type}>`)}if(a.length===i)a.push({type:p,level:i}),l.push(`<${p}${o(p)}>`);else if(a.length===i+1){const e=a[a.length-1];e.type!==p&&(l.push(`</${e.type}>`),a.pop(),a.push({type:p,level:i}),l.push(`<${p}${o(p)}>`))}const $=g||o("li");l.push(`<li${$}>${d}</li>`)}else{for(;a.length>0;){const e=a.pop();l.push(`</${e.type}>`)}l.push(r)}}for(;a.length>0;){const e=a.pop();l.push(`</${e.type}>`)}return l.join("\n")}(g,n,i),g=g.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(e,r,o)=>{const n=d(o,t.allow_unsafe_urls);return`<img${s("img")} src="${n}" alt="${r}">`}),g=g.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(e,r,o)=>{const n=d(o,t.allow_unsafe_urls),l=/^https?:\/\//i.test(n)?' rel="noopener noreferrer"':"";return`<a${s("a")} href="${n}"${l}>${r}</a>`}),g=g.replace(/(^|\s)(https?:\/\/[^\s<]+)/g,(e,r,o)=>{const n=d(o,t.allow_unsafe_urls);return`${r}<a${s("a")} href="${n}" rel="noopener noreferrer">${o}</a>`}),g=g.replace(/\*\*(.+?)\*\*/g,`<strong${s("strong")}>$1</strong>`),g=g.replace(/__(.+?)__/g,`<strong${s("strong")}>$1</strong>`),g=g.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,`<em${s("em")}>$1</em>`),g=g.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g,`<em${s("em")}>$1</em>`),g=g.replace(/~~(.+?)~~/g,`<del${s("del")}>$1</del>`),g=g.replace(/ $/gm,`<br${s("br")}>`),g=g.replace(/\n\n+/g,"</p><p>"),g="<p>"+g+"</p>",g=g.replace(/<p><\/p>/g,""),g=g.replace(/<p>(<h[1-6][^>]*>)/g,"$1"),g=g.replace(/(<\/h[1-6]>)<\/p>/g,"$1"),g=g.replace(/<p>(<blockquote[^>]*>)/g,"$1"),g=g.replace(/(<\/blockquote>)<\/p>/g,"$1"),g=g.replace(/<p>(<ul[^>]*>|<ol[^>]*>)/g,"$1"),g=g.replace(/(<\/ul>|<\/ol>)<\/p>/g,"$1"),g=g.replace(/<p>(<hr[^>]*>)<\/p>/g,"$1"),g=g.replace(/<p>(<table[^>]*>)/g,"$1"),g=g.replace(/(<\/table>)<\/p>/g,"$1"),g=g.replace(/<p>(<pre[^>]*>)/g,"$1"),g=g.replace(/(<\/pre>)<\/p>/g,"$1"),g=g.replace(/<p>(%%%CODEBLOCK\d+%%%)<\/p>/g,"$1"),u.forEach((e,t)=>{let r;if(e.custom&&o){if(r=o(e.code,e.lang),void 0===r){const t=!n&&e.lang?` class="language-${e.lang}"`:"",o=n?s("code"):t;r=`<pre${s("pre")}><code${o}>${p(e.code)}</code></pre>`}}else{const t=!n&&e.lang?` class="language-${e.lang}"`:"",o=n?s("code"):t;r=`<pre${s("pre")}><code${o}>${e.code}</code></pre>`}const l=`%%%CODEBLOCK${t}%%%`;g=g.replace(l,r)}),$.forEach((e,t)=>{const r=`%%%INLINECODE${t}%%%`;g=g.replace(r,`<code${s("code")}>${e}</code>`)}),g.trim()}function t(e,t,r){function o(e,o=""){if(t){const t=r[e]||"",n=o?`${t}; ${o}`:t;return n?` style="${n}"`:""}return` class="quikdown-${e}"`}return e=(e=(e=(e=(e=(e=e.replace(/\*\*(.+?)\*\*/g,`<strong${o("strong")}>$1</strong>`)).replace(/__(.+?)__/g,`<strong${o("strong")}>$1</strong>`)).replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,`<em${o("em")}>$1</em>`)).replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g,`<em${o("em")}>$1</em>`)).replace(/~~(.+?)~~/g,`<del${o("del")}>$1</del>`)).replace(/`([^`]+)`/g,`<code${o("code")}>$1</code>`)}function r(e,r,o){function n(e,t=""){if(r){const r=o[e]||"",n=t?`${r}; ${t}`:r;return n?` style="${n}"`:""}return` class="quikdown-${e}"`}if(e.length<2)return null;let l=-1;for(let t=1;t<e.length;t++)if(/^\|?[\s\-:|]+\|?$/.test(e[t])&&e[t].includes("-")){l=t;break}if(-1===l)return null;const a=e.slice(0,l),c=e.slice(l+1),i=e[l].trim().replace(/^\|/,"").replace(/\|$/,"").split("|").map(e=>{const t=e.trim();return t.startsWith(":")&&t.endsWith(":")?"center":t.endsWith(":")?"right":"left"});let s=`<table${n("table")}>\n`;return a.length>0&&(s+=`<thead${n("thead")}>\n`,a.forEach(e=>{s+=`<tr${n("tr")}>\n`;e.trim().replace(/^\|/,"").replace(/\|$/,"").split("|").forEach((e,l)=>{const a=i[l]&&"left"!==i[l]?`text-align: ${i[l]}`:"",c=t(e.trim(),r,o);s+=`<th${n("th",a)}>${c}</th>\n`}),s+="</tr>\n"}),s+="</thead>\n"),c.length>0&&(s+=`<tbody${n("tbody")}>\n`,c.forEach(e=>{s+=`<tr${n("tr")}>\n`;e.trim().replace(/^\|/,"").replace(/\|$/,"").split("|").forEach((e,l)=>{const a=i[l]&&"left"!==i[l]?`text-align: ${i[l]}`:"",c=t(e.trim(),r,o);s+=`<td${n("td",a)}>${c}</td>\n`}),s+="</tr>\n"}),s+="</tbody>\n"),s+="</table>",s}e.emitStyles=function(){const e={h1:"margin-top: 0.5em; margin-bottom: 0.3em",h2:"margin-top: 0.5em; margin-bottom: 0.3em",h3:"margin-top: 0.5em; margin-bottom: 0.3em",h4:"margin-top: 0.5em; margin-bottom: 0.3em",h5:"margin-top: 0.5em; margin-bottom: 0.3em",h6:"margin-top: 0.5em; margin-bottom: 0.3em",pre:"background: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto",code:"background: #f0f0f0; padding: 2px 4px; border-radius: 3px",blockquote:"border-left: 4px solid #ddd; margin-left: 0; padding-left: 1em; color: #666",table:"border-collapse: collapse; width: 100%; margin: 1em 0",th:"border: 1px solid #ddd; padding: 8px; background-color: #f2f2f2; font-weight: bold",td:"border: 1px solid #ddd; padding: 8px; text-align: left",hr:"border: none; border-top: 1px solid #ddd; margin: 1em 0",img:"max-width: 100%; height: auto",a:"color: #0066cc; text-decoration: underline",strong:"font-weight: bold",em:"font-style: italic",del:"text-decoration: line-through",ul:"margin: 0.5em 0; padding-left: 2em",ol:"margin: 0.5em 0; padding-left: 2em",li:"margin: 0.25em 0","task-item":"list-style: none","task-checkbox":"margin-right: 0.5em"};let t="";for(const[r,o]of Object.entries(e))o&&(t+=`.quikdown-${r} { ${o} }\n`);return t},e.configure=function(t){return function(r){return e(r,t)}},e.version="1.0.1","undefined"!=typeof module&&module.exports&&(module.exports=e),"undefined"!=typeof window&&(window.quikdown=e);export{e as default};
8
+ //# sourceMappingURL=quikdown.esm.min.js.map