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