quikdown 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * quikdown - Lightweight Markdown Parser
3
- * @version 1.0.4
3
+ * @version 1.1.0
4
4
  * @license BSD-2-Clause
5
5
  * @copyright DeftIO 2025
6
6
  */
@@ -12,11 +12,13 @@
12
12
  * @param {Function} options.fence_plugin - Custom renderer for fenced code blocks
13
13
  * (content, fence_string) => html string
14
14
  * @param {boolean} options.inline_styles - If true, uses inline styles instead of classes
15
+ * @param {boolean} options.bidirectional - If true, adds data-qd attributes for source tracking
16
+ * @param {boolean} options.lazy_linefeeds - If true, single newlines become <br> tags
15
17
  * @returns {string} - The rendered HTML
16
18
  */
17
19
 
18
20
  // Version will be injected at build time
19
- const quikdownVersion = '1.0.4';
21
+ const quikdownVersion = '1.1.0';
20
22
 
21
23
  // Constants for reuse
22
24
  const CLASS_PREFIX = 'quikdown-';
@@ -58,12 +60,25 @@ const QUIKDOWN_STYLES = {
58
60
  function createGetAttr(inline_styles, styles) {
59
61
  return function(tag, additionalStyle = '') {
60
62
  if (inline_styles) {
61
- const style = styles[tag];
63
+ let style = styles[tag];
62
64
  if (!style && !additionalStyle) return '';
63
- const fullStyle = additionalStyle ? (style ? `${style};${additionalStyle}` : additionalStyle) : style;
65
+
66
+ // Remove default text-align if we're adding a different alignment
67
+ if (additionalStyle && additionalStyle.includes('text-align') && style && style.includes('text-align')) {
68
+ style = style.replace(/text-align:[^;]+;?/, '').trim();
69
+ if (style && !style.endsWith(';')) style += ';';
70
+ }
71
+
72
+ /* istanbul ignore next - defensive: additionalStyle without style doesn't occur with current tags */
73
+ const fullStyle = additionalStyle ? (style ? `${style}${additionalStyle}` : additionalStyle) : style;
64
74
  return ` style="${fullStyle}"`;
65
75
  } else {
66
- return ` class="${CLASS_PREFIX}${tag}"`;
76
+ const classAttr = ` class="${CLASS_PREFIX}${tag}"`;
77
+ // Apply inline styles for alignment even when using CSS classes
78
+ if (additionalStyle) {
79
+ return `${classAttr} style="${additionalStyle}"`;
80
+ }
81
+ return classAttr;
67
82
  }
68
83
  };
69
84
  }
@@ -73,7 +88,7 @@ function quikdown(markdown, options = {}) {
73
88
  return '';
74
89
  }
75
90
 
76
- const { fence_plugin, inline_styles = false } = options;
91
+ const { fence_plugin, inline_styles = false, bidirectional = false, lazy_linefeeds = false } = options;
77
92
  const styles = QUIKDOWN_STYLES; // Use module-level styles
78
93
  const getAttr = createGetAttr(inline_styles, styles); // Create getAttr once
79
94
 
@@ -82,8 +97,12 @@ function quikdown(markdown, options = {}) {
82
97
  return text.replace(/[&<>"']/g, m => ESC_MAP[m]);
83
98
  }
84
99
 
100
+ // Helper to add data-qd attributes for bidirectional support
101
+ const dataQd = bidirectional ? (marker) => ` data-qd="${escapeHtml(marker)}"` : () => '';
102
+
85
103
  // Sanitize URLs to prevent XSS attacks
86
104
  function sanitizeUrl(url, allowUnsafe = false) {
105
+ /* istanbul ignore next - defensive programming, regex ensures url is never empty */
87
106
  if (!url) return '';
88
107
 
89
108
  // If unsafe URLs are explicitly allowed, return as-is
@@ -125,18 +144,21 @@ function quikdown(markdown, options = {}) {
125
144
  // Trim the language specification
126
145
  const langTrimmed = lang ? lang.trim() : '';
127
146
 
128
- // If custom fence plugin is provided, use it
129
- if (fence_plugin && typeof fence_plugin === 'function') {
147
+ // If custom fence plugin is provided, use it (v1.1.0: object format required)
148
+ if (fence_plugin && fence_plugin.render && typeof fence_plugin.render === 'function') {
130
149
  codeBlocks.push({
131
150
  lang: langTrimmed,
132
151
  code: code.trimEnd(),
133
- custom: true
152
+ custom: true,
153
+ fence: fence,
154
+ hasReverse: !!fence_plugin.reverse
134
155
  });
135
156
  } else {
136
157
  codeBlocks.push({
137
158
  lang: langTrimmed,
138
159
  code: escapeHtml(code.trimEnd()),
139
- custom: false
160
+ custom: false,
161
+ fence: fence
140
162
  });
141
163
  }
142
164
  return placeholder;
@@ -160,7 +182,7 @@ function quikdown(markdown, options = {}) {
160
182
  // Process headings (supports optional trailing #'s)
161
183
  html = html.replace(/^(#{1,6})\s+(.+?)\s*#*$/gm, (match, hashes, content) => {
162
184
  const level = hashes.length;
163
- return `<h${level}${getAttr('h' + level)}>${content}</h${level}>`;
185
+ return `<h${level}${getAttr('h' + level)}${dataQd(hashes)}>${content}</h${level}>`;
164
186
  });
165
187
 
166
188
  // Process blockquotes (must handle escaped > since we already escaped HTML)
@@ -168,18 +190,20 @@ function quikdown(markdown, options = {}) {
168
190
  // Merge consecutive blockquotes
169
191
  html = html.replace(/<\/blockquote>\n<blockquote>/g, '\n');
170
192
 
171
- // Process horizontal rules
172
- html = html.replace(/^---+$/gm, `<hr${getAttr('hr')}>`);
193
+ // Process horizontal rules (allow trailing spaces)
194
+ html = html.replace(/^---+\s*$/gm, `<hr${getAttr('hr')}>`);
173
195
 
174
196
  // Process lists
175
- html = processLists(html, getAttr, inline_styles);
197
+ html = processLists(html, getAttr, inline_styles, bidirectional);
176
198
 
177
199
  // Phase 3: Process inline elements
178
200
 
179
201
  // Images (must come before links, with URL sanitization)
180
202
  html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
181
203
  const sanitizedSrc = sanitizeUrl(src, options.allow_unsafe_urls);
182
- return `<img${getAttr('img')} src="${sanitizedSrc}" alt="${alt}">`;
204
+ const altAttr = bidirectional && alt ? ` data-qd-alt="${escapeHtml(alt)}"` : '';
205
+ const srcAttr = bidirectional ? ` data-qd-src="${escapeHtml(src)}"` : '';
206
+ return `<img${getAttr('img')} src="${sanitizedSrc}" alt="${alt}"${altAttr}${srcAttr}${dataQd('!')}>`;
183
207
  });
184
208
 
185
209
  // Links (with URL sanitization)
@@ -188,7 +212,8 @@ function quikdown(markdown, options = {}) {
188
212
  const sanitizedHref = sanitizeUrl(href, options.allow_unsafe_urls);
189
213
  const isExternal = /^https?:\/\//i.test(sanitizedHref);
190
214
  const rel = isExternal ? ' rel="noopener noreferrer"' : '';
191
- return `<a${getAttr('a')} href="${sanitizedHref}"${rel}>${text}</a>`;
215
+ const textAttr = bidirectional ? ` data-qd-text="${escapeHtml(text)}"` : '';
216
+ return `<a${getAttr('a')} href="${sanitizedHref}"${rel}${textAttr}${dataQd('[')}>${text}</a>`;
192
217
  });
193
218
 
194
219
  // Autolinks - convert bare URLs to clickable links
@@ -199,23 +224,64 @@ function quikdown(markdown, options = {}) {
199
224
 
200
225
  // Process inline formatting (bold, italic, strikethrough)
201
226
  const inlinePatterns = [
202
- [/\*\*(.+?)\*\*/g, 'strong'],
203
- [/__(.+?)__/g, 'strong'],
204
- [/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, 'em'],
205
- [/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em'],
206
- [/~~(.+?)~~/g, 'del']
227
+ [/\*\*(.+?)\*\*/g, 'strong', '**'],
228
+ [/__(.+?)__/g, 'strong', '__'],
229
+ [/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, 'em', '*'],
230
+ [/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em', '_'],
231
+ [/~~(.+?)~~/g, 'del', '~~']
207
232
  ];
208
233
 
209
- inlinePatterns.forEach(([pattern, tag]) => {
210
- html = html.replace(pattern, `<${tag}${getAttr(tag)}>$1</${tag}>`);
234
+ inlinePatterns.forEach(([pattern, tag, marker]) => {
235
+ html = html.replace(pattern, `<${tag}${getAttr(tag)}${dataQd(marker)}>$1</${tag}>`);
211
236
  });
212
237
 
213
- // Line breaks (two spaces at end of line)
214
- html = html.replace(/ $/gm, `<br${getAttr('br')}>`);
215
-
216
- // Paragraphs (double newlines)
217
- html = html.replace(/\n\n+/g, '</p><p>');
218
- html = '<p>' + html + '</p>';
238
+ // Line breaks
239
+ if (lazy_linefeeds) {
240
+ // Lazy linefeeds: single newline becomes <br> (except between paragraphs and after/before block elements)
241
+ const blocks = [];
242
+ let bi = 0;
243
+
244
+ // Protect tables and lists
245
+ html = html.replace(/<(table|[uo]l)[^>]*>[\s\S]*?<\/\1>/g, m => {
246
+ blocks[bi] = m;
247
+ return `§B${bi++}§`;
248
+ });
249
+
250
+ // Handle paragraphs and block elements
251
+ html = html.replace(/\n\n+/g, '§P§')
252
+ // After block elements
253
+ .replace(/(<\/(?:h[1-6]|blockquote|pre)>)\n/g, '$1§N§')
254
+ .replace(/(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)\n/g, '$1§N§')
255
+ // Before block elements
256
+ .replace(/\n(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)/g, '§N§$1')
257
+ .replace(/\n(§B\d+§)/g, '§N§$1')
258
+ .replace(/(§B\d+§)\n/g, '$1§N§')
259
+ // Convert remaining newlines
260
+ .replace(/\n/g, `<br${getAttr('br')}>`)
261
+ // Restore
262
+ .replace(/§N§/g, '\n')
263
+ .replace(/§P§/g, '</p><p>');
264
+
265
+ // Restore protected blocks
266
+ blocks.forEach((b, i) => html = html.replace(`§B${i}§`, b));
267
+
268
+ html = '<p>' + html + '</p>';
269
+ } else {
270
+ // Standard: two spaces at end of line for line breaks
271
+ html = html.replace(/ $/gm, `<br${getAttr('br')}>`);
272
+
273
+ // Paragraphs (double newlines)
274
+ // Don't add </p> after block elements (they're not in paragraphs)
275
+ html = html.replace(/\n\n+/g, (match, offset) => {
276
+ // Check if we're after a block element closing tag
277
+ const before = html.substring(0, offset);
278
+ if (before.match(/<\/(h[1-6]|blockquote|ul|ol|table|pre|hr)>$/)) {
279
+ return '<p>'; // Just open a new paragraph
280
+ }
281
+ return '</p><p>'; // Normal paragraph break
282
+ });
283
+ html = '<p>' + html + '</p>';
284
+ }
219
285
 
220
286
  // Clean up empty paragraphs and unwrap block elements
221
287
  const cleanupPatterns = [
@@ -238,26 +304,39 @@ function quikdown(markdown, options = {}) {
238
304
  html = html.replace(pattern, replacement);
239
305
  });
240
306
 
307
+ // Fix orphaned closing </p> tags after block elements
308
+ // When a paragraph follows a block element, ensure it has opening <p>
309
+ html = html.replace(/(<\/(?:h[1-6]|blockquote|ul|ol|table|pre|hr)>)\n([^<])/g, '$1\n<p>$2');
310
+
241
311
  // Phase 4: Restore code blocks and inline code
242
312
 
243
313
  // Restore code blocks
244
314
  codeBlocks.forEach((block, i) => {
245
315
  let replacement;
246
316
 
247
- if (block.custom && fence_plugin) {
248
- // Use custom fence plugin
249
- replacement = fence_plugin(block.code, block.lang);
317
+ if (block.custom && fence_plugin && fence_plugin.render) {
318
+ // Use custom fence plugin (v1.1.0: object format with render function)
319
+ replacement = fence_plugin.render(block.code, block.lang);
320
+
250
321
  // If plugin returns undefined, fall back to default rendering
251
322
  if (replacement === undefined) {
252
323
  const langClass = !inline_styles && block.lang ? ` class="language-${block.lang}"` : '';
253
324
  const codeAttr = inline_styles ? getAttr('code') : langClass;
254
- replacement = `<pre${getAttr('pre')}><code${codeAttr}>${escapeHtml(block.code)}</code></pre>`;
325
+ const langAttr = bidirectional && block.lang ? ` data-qd-lang="${escapeHtml(block.lang)}"` : '';
326
+ const fenceAttr = bidirectional ? ` data-qd-fence="${escapeHtml(block.fence)}"` : '';
327
+ replacement = `<pre${getAttr('pre')}${fenceAttr}${langAttr}><code${codeAttr}>${escapeHtml(block.code)}</code></pre>`;
328
+ } else if (bidirectional) {
329
+ // If bidirectional and plugin provided HTML, add data attributes for roundtrip
330
+ replacement = replacement.replace(/^<(\w+)/,
331
+ `<$1 data-qd-fence="${escapeHtml(block.fence)}" data-qd-lang="${escapeHtml(block.lang)}" data-qd-source="${escapeHtml(block.code)}"`);
255
332
  }
256
333
  } else {
257
334
  // Default rendering
258
335
  const langClass = !inline_styles && block.lang ? ` class="language-${block.lang}"` : '';
259
336
  const codeAttr = inline_styles ? getAttr('code') : langClass;
260
- replacement = `<pre${getAttr('pre')}><code${codeAttr}>${block.code}</code></pre>`;
337
+ const langAttr = bidirectional && block.lang ? ` data-qd-lang="${escapeHtml(block.lang)}"` : '';
338
+ const fenceAttr = bidirectional ? ` data-qd-fence="${escapeHtml(block.fence)}"` : '';
339
+ replacement = `<pre${getAttr('pre')}${fenceAttr}${langAttr}><code${codeAttr}>${block.code}</code></pre>`;
261
340
  }
262
341
 
263
342
  const placeholder = `${PLACEHOLDER_CB}${i}§`;
@@ -267,7 +346,7 @@ function quikdown(markdown, options = {}) {
267
346
  // Restore inline code
268
347
  inlineCodes.forEach((code, i) => {
269
348
  const placeholder = `${PLACEHOLDER_IC}${i}§`;
270
- html = html.replace(placeholder, `<code${getAttr('code')}>${code}</code>`);
349
+ html = html.replace(placeholder, `<code${getAttr('code')}${dataQd('`')}>${code}</code>`);
271
350
  });
272
351
 
273
352
  return html.trim();
@@ -381,9 +460,9 @@ function buildTable(lines, getAttr) {
381
460
  let html = `<table${getAttr('table')}>\n`;
382
461
 
383
462
  // Build header
384
- if (headerLines.length > 0) {
385
- html += `<thead${getAttr('thead')}>\n`;
386
- headerLines.forEach(line => {
463
+ // Note: headerLines will always have length > 0 since separatorIndex starts from 1
464
+ html += `<thead${getAttr('thead')}>\n`;
465
+ headerLines.forEach(line => {
387
466
  html += `<tr${getAttr('tr')}>\n`;
388
467
  // Handle pipes at start/end or not
389
468
  const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
@@ -393,9 +472,8 @@ function buildTable(lines, getAttr) {
393
472
  html += `<th${getAttr('th', alignStyle)}>${processedCell}</th>\n`;
394
473
  });
395
474
  html += '</tr>\n';
396
- });
397
- html += '</thead>\n';
398
- }
475
+ });
476
+ html += '</thead>\n';
399
477
 
400
478
  // Build body
401
479
  if (bodyLines.length > 0) {
@@ -421,12 +499,16 @@ function buildTable(lines, getAttr) {
421
499
  /**
422
500
  * Process markdown lists (ordered and unordered)
423
501
  */
424
- function processLists(text, getAttr, inline_styles) {
502
+ function processLists(text, getAttr, inline_styles, bidirectional) {
425
503
 
426
504
  const lines = text.split('\n');
427
505
  const result = [];
428
506
  let listStack = []; // Track nested lists
429
507
 
508
+ // Helper to escape HTML for data-qd attributes
509
+ const escapeHtml = (text) => text.replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[m]);
510
+ const dataQd = bidirectional ? (marker) => ` data-qd="${escapeHtml(marker)}"` : () => '';
511
+
430
512
  for (let i = 0; i < lines.length; i++) {
431
513
  const line = lines[i];
432
514
  const match = line.match(/^(\s*)([*\-+]|\d+\.)\s+(.+)$/);
@@ -474,7 +556,7 @@ function processLists(text, getAttr, inline_styles) {
474
556
  }
475
557
 
476
558
  const liAttr = taskListClass || getAttr('li');
477
- result.push(`<li${liAttr}>${listItemContent}</li>`);
559
+ result.push(`<li${liAttr}${dataQd(marker)}>${listItemContent}</li>`);
478
560
  } else {
479
561
  // Not a list item, close all lists
480
562
  while (listStack.length > 0) {
@@ -520,8 +602,7 @@ quikdown.emitStyles = function(prefix = 'quikdown-', theme = 'light') {
520
602
 
521
603
  let css = '';
522
604
  for (const [tag, style] of Object.entries(styles)) {
523
- if (style) {
524
- let themedStyle = style;
605
+ let themedStyle = style;
525
606
 
526
607
  // Apply theme overrides if dark theme
527
608
  if (theme === 'dark' && themeOverrides.dark) {
@@ -544,9 +625,8 @@ quikdown.emitStyles = function(prefix = 'quikdown-', theme = 'light') {
544
625
  themedStyle += `;color:${themeOverrides.light._textColor}`;
545
626
  }
546
627
  }
547
-
548
- css += `.${prefix}${tag} { ${themedStyle} }\n`;
549
- }
628
+
629
+ css += `.${prefix}${tag} { ${themedStyle} }\n`;
550
630
  }
551
631
 
552
632
  return css;
@@ -569,11 +649,13 @@ quikdown.configure = function(options) {
569
649
  quikdown.version = quikdownVersion;
570
650
 
571
651
  // Export for both CommonJS and ES6
652
+ /* istanbul ignore next */
572
653
  if (typeof module !== 'undefined' && module.exports) {
573
654
  module.exports = quikdown;
574
655
  }
575
656
 
576
657
  // For browser global
658
+ /* istanbul ignore next */
577
659
  if (typeof window !== 'undefined') {
578
660
  window.quikdown = quikdown;
579
661
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * quikdown - Lightweight Markdown Parser
3
- * @version 1.0.4
3
+ * @version 1.1.0
4
4
  * @license BSD-2-Clause
5
5
  * @copyright DeftIO 2025
6
6
  */
7
- const e="quikdown-",t="§CB",n={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},o={h1:"font-size:2em;font-weight:600;margin:.67em 0;text-align:left",h2:"font-size:1.5em;font-weight:600;margin:.83em 0",h3:"font-size:1.25em;font-weight:600;margin:1em 0",h4:"font-size:1em;font-weight:600;margin:1.33em 0",h5:"font-size:.875em;font-weight:600;margin:1.67em 0",h6:"font-size:.85em;font-weight:600;margin:2em 0",pre:"background:#f4f4f4;padding:10px;border-radius:4px;overflow-x:auto;margin:1em 0",code:"background:#f0f0f0;padding:2px 4px;border-radius:3px;font-family:monospace",blockquote:"border-left:4px solid #ddd;margin-left:0;padding-left:1em",table:"border-collapse:collapse;width:100%;margin:1em 0",th:"border:1px solid #ddd;padding:8px;background-color:#f2f2f2;font-weight:bold;text-align:left",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:#06c;text-decoration:underline",strong:"font-weight:bold",em:"font-style:italic",del:"text-decoration:line-through",ul:"margin:.5em 0;padding-left:2em",ol:"margin:.5em 0;padding-left:2em",li:"margin:.25em 0","task-item":"list-style:none","task-checkbox":"margin-right:.5em"};function r(r,l={}){if(!r||"string"!=typeof r)return"";const{fence_plugin:i,inline_styles:a=!1}=l,c=function(t,n){return function(o,r=""){if(t){const e=n[o];return e||r?` style="${r?e?`${e};${r}`:r:e}"`:""}return` class="${e}${o}"`}}(a,o);function p(e){return e.replace(/[&<>"']/g,e=>n[e])}function g(e,t=!1){if(!e)return"";if(t)return e;const n=e.trim(),o=n.toLowerCase(),r=["javascript:","vbscript:","data:"];for(const e of r)if(o.startsWith(e))return"data:"===e&&o.startsWith("data:image/")?n:"#";return n}let f=r;const d=[],h=[];f=f.replace(/^(```|~~~)([^\n]*)\n([\s\S]*?)^\1$/gm,(e,n,o,r)=>{const l=`${t}${d.length}§`,s=o?o.trim():"";return i&&"function"==typeof i?d.push({lang:s,code:r.trimEnd(),custom:!0}):d.push({lang:s,code:p(r.trimEnd()),custom:!1}),l}),f=f.replace(/`([^`]+)`/g,(e,t)=>{const n=`§IC${h.length}§`;return h.push(p(t)),n}),f=p(f),f=function(e,t){const n=e.split("\n"),o=[];let r=!1,l=[];for(let e=0;e<n.length;e++){const i=n[e].trim();if(i.includes("|")&&(i.startsWith("|")||/[^\\|]/.test(i)))r||(r=!0,l=[]),l.push(i);else{if(r){const e=s(l,t);e?o.push(e):o.push(...l),r=!1,l=[]}o.push(n[e])}}if(r&&l.length>0){const e=s(l,t);e?o.push(e):o.push(...l)}return o.join("\n")}(f,c),f=f.replace(/^(#{1,6})\s+(.+?)\s*#*$/gm,(e,t,n)=>{const o=t.length;return`<h${o}${c("h"+o)}>${n}</h${o}>`}),f=f.replace(/^&gt;\s+(.+)$/gm,`<blockquote${c("blockquote")}>$1</blockquote>`),f=f.replace(/<\/blockquote>\n<blockquote>/g,"\n"),f=f.replace(/^---+$/gm,`<hr${c("hr")}>`),f=function(t,n,o){const r=t.split("\n"),l=[];let s=[];for(let t=0;t<r.length;t++){const i=r[t],a=i.match(/^(\s*)([*\-+]|\d+\.)\s+(.+)$/);if(a){const[,t,r,i]=a,c=Math.floor(t.length/2),p=/^\d+\./.test(r),g=p?"ol":"ul";let f=i,d="";const h=i.match(/^\[([x ])\]\s+(.*)$/i);if(h&&!p){const[,t,n]=h,r="x"===t.toLowerCase();f=`<input type="checkbox"${o?' style="margin-right:.5em"':` class="${e}task-checkbox"`}${r?" checked":""} disabled> ${n}`,d=o?' style="list-style:none"':` class="${e}task-item"`}for(;s.length>c+1;){const e=s.pop();l.push(`</${e.type}>`)}if(s.length===c)s.push({type:g,level:c}),l.push(`<${g}${n(g)}>`);else if(s.length===c+1){const e=s[s.length-1];e.type!==g&&(l.push(`</${e.type}>`),s.pop(),s.push({type:g,level:c}),l.push(`<${g}${n(g)}>`))}const u=d||n("li");l.push(`<li${u}>${f}</li>`)}else{for(;s.length>0;){const e=s.pop();l.push(`</${e.type}>`)}l.push(i)}}for(;s.length>0;){const e=s.pop();l.push(`</${e.type}>`)}return l.join("\n")}(f,c,a),f=f.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(e,t,n)=>{const o=g(n,l.allow_unsafe_urls);return`<img${c("img")} src="${o}" alt="${t}">`}),f=f.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(e,t,n)=>{const o=g(n,l.allow_unsafe_urls),r=/^https?:\/\//i.test(o)?' rel="noopener noreferrer"':"";return`<a${c("a")} href="${o}"${r}>${t}</a>`}),f=f.replace(/(^|\s)(https?:\/\/[^\s<]+)/g,(e,t,n)=>{const o=g(n,l.allow_unsafe_urls);return`${t}<a${c("a")} href="${o}" rel="noopener noreferrer">${n}</a>`});[[/\*\*(.+?)\*\*/g,"strong"],[/__(.+?)__/g,"strong"],[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,"em"],[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g,"em"],[/~~(.+?)~~/g,"del"]].forEach(([e,t])=>{f=f.replace(e,`<${t}${c(t)}>$1</${t}>`)}),f=f.replace(/ $/gm,`<br${c("br")}>`),f=f.replace(/\n\n+/g,"</p><p>"),f="<p>"+f+"</p>";return[[/<p><\/p>/g,""],[/<p>(<h[1-6][^>]*>)/g,"$1"],[/(<\/h[1-6]>)<\/p>/g,"$1"],[/<p>(<blockquote[^>]*>)/g,"$1"],[/(<\/blockquote>)<\/p>/g,"$1"],[/<p>(<ul[^>]*>|<ol[^>]*>)/g,"$1"],[/(<\/ul>|<\/ol>)<\/p>/g,"$1"],[/<p>(<hr[^>]*>)<\/p>/g,"$1"],[/<p>(<table[^>]*>)/g,"$1"],[/(<\/table>)<\/p>/g,"$1"],[/<p>(<pre[^>]*>)/g,"$1"],[/(<\/pre>)<\/p>/g,"$1"],[new RegExp(`<p>(${t}\\d+§)</p>`,"g"),"$1"]].forEach(([e,t])=>{f=f.replace(e,t)}),d.forEach((e,n)=>{let o;if(e.custom&&i){if(o=i(e.code,e.lang),void 0===o){const t=!a&&e.lang?` class="language-${e.lang}"`:"",n=a?c("code"):t;o=`<pre${c("pre")}><code${n}>${p(e.code)}</code></pre>`}}else{const t=!a&&e.lang?` class="language-${e.lang}"`:"",n=a?c("code"):t;o=`<pre${c("pre")}><code${n}>${e.code}</code></pre>`}const r=`${t}${n}§`;f=f.replace(r,o)}),h.forEach((e,t)=>{const n=`§IC${t}§`;f=f.replace(n,`<code${c("code")}>${e}</code>`)}),f.trim()}function l(e,t){return[[/\*\*(.+?)\*\*/g,"strong"],[/__(.+?)__/g,"strong"],[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,"em"],[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g,"em"],[/~~(.+?)~~/g,"del"],[/`([^`]+)`/g,"code"]].forEach(([n,o])=>{e=e.replace(n,`<${o}${t(o)}>$1</${o}>`)}),e}function s(e,t){if(e.length<2)return null;let n=-1;for(let t=1;t<e.length;t++)if(/^\|?[\s\-:|]+\|?$/.test(e[t])&&e[t].includes("-")){n=t;break}if(-1===n)return null;const o=e.slice(0,n),r=e.slice(n+1),s=e[n].trim().replace(/^\|/,"").replace(/\|$/,"").split("|").map(e=>{const t=e.trim();return t.startsWith(":")&&t.endsWith(":")?"center":t.endsWith(":")?"right":"left"});let i=`<table${t("table")}>\n`;return o.length>0&&(i+=`<thead${t("thead")}>\n`,o.forEach(e=>{i+=`<tr${t("tr")}>\n`;e.trim().replace(/^\|/,"").replace(/\|$/,"").split("|").forEach((e,n)=>{const o=s[n]&&"left"!==s[n]?`text-align:${s[n]}`:"",r=l(e.trim(),t);i+=`<th${t("th",o)}>${r}</th>\n`}),i+="</tr>\n"}),i+="</thead>\n"),r.length>0&&(i+=`<tbody${t("tbody")}>\n`,r.forEach(e=>{i+=`<tr${t("tr")}>\n`;e.trim().replace(/^\|/,"").replace(/\|$/,"").split("|").forEach((e,n)=>{const o=s[n]&&"left"!==s[n]?`text-align:${s[n]}`:"",r=l(e.trim(),t);i+=`<td${t("td",o)}>${r}</td>\n`}),i+="</tr>\n"}),i+="</tbody>\n"),i+="</table>",i}r.emitStyles=function(e="quikdown-",t="light"){const n=o,r={"#f4f4f4":"#2a2a2a","#f0f0f0":"#2a2a2a","#f2f2f2":"#2a2a2a","#ddd":"#3a3a3a","#06c":"#6db3f2",_textColor:"#e0e0e0"},l={_textColor:"#333"};let s="";for(const[o,i]of Object.entries(n))if(i){let n=i;if("dark"===t&&r){for(const[e,t]of Object.entries(r))e.startsWith("_")||(n=n.replace(new RegExp(e,"g"),t));["h1","h2","h3","h4","h5","h6","td","li","blockquote"].includes(o)&&(n+=`;color:${r._textColor}`)}else if("light"===t&&l){["h1","h2","h3","h4","h5","h6","td","li","blockquote"].includes(o)&&(n+=`;color:${l._textColor}`)}s+=`.${e}${o} { ${n} }\n`}return s},r.configure=function(e){return function(t){return r(t,e)}},r.version="1.0.4","undefined"!=typeof module&&module.exports&&(module.exports=r),"undefined"!=typeof window&&(window.quikdown=r);export{r as default};
7
+ const e="quikdown-",t="§CB",n={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},r={h1:"font-size:2em;font-weight:600;margin:.67em 0;text-align:left",h2:"font-size:1.5em;font-weight:600;margin:.83em 0",h3:"font-size:1.25em;font-weight:600;margin:1em 0",h4:"font-size:1em;font-weight:600;margin:1.33em 0",h5:"font-size:.875em;font-weight:600;margin:1.67em 0",h6:"font-size:.85em;font-weight:600;margin:2em 0",pre:"background:#f4f4f4;padding:10px;border-radius:4px;overflow-x:auto;margin:1em 0",code:"background:#f0f0f0;padding:2px 4px;border-radius:3px;font-family:monospace",blockquote:"border-left:4px solid #ddd;margin-left:0;padding-left:1em",table:"border-collapse:collapse;width:100%;margin:1em 0",th:"border:1px solid #ddd;padding:8px;background-color:#f2f2f2;font-weight:bold;text-align:left",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:#06c;text-decoration:underline",strong:"font-weight:bold",em:"font-style:italic",del:"text-decoration:line-through",ul:"margin:.5em 0;padding-left:2em",ol:"margin:.5em 0;padding-left:2em",li:"margin:.25em 0","task-item":"list-style:none","task-checkbox":"margin-right:.5em"};function o(o,l={}){if(!o||"string"!=typeof o)return"";const{fence_plugin:c,inline_styles:s=!1,bidirectional:i=!1,lazy_linefeeds:p=!1}=l,d=function(t,n){return function(r,o=""){if(t){let e=n[r];return e||o?(o&&o.includes("text-align")&&e&&e.includes("text-align")&&(e=e.replace(/text-align:[^;]+;?/,"").trim(),e&&!e.endsWith(";")&&(e+=";")),` style="${o?e?`${e}${o}`:o:e}"`):""}{const t=` class="${e}${r}"`;return o?`${t} style="${o}"`:t}}}(s,r);function g(e){return e.replace(/[&<>"']/g,e=>n[e])}const $=i?e=>` data-qd="${g(e)}"`:()=>"";function f(e,t=!1){if(!e)return"";if(t)return e;const n=e.trim(),r=n.toLowerCase(),o=["javascript:","vbscript:","data:"];for(const e of o)if(r.startsWith(e))return"data:"===e&&r.startsWith("data:image/")?n:"#";return n}let h=o;const u=[],m=[];h=h.replace(/^(```|~~~)([^\n]*)\n([\s\S]*?)^\1$/gm,(e,n,r,o)=>{const l=`${t}${u.length}§`,a=r?r.trim():"";return c&&c.render&&"function"==typeof c.render?u.push({lang:a,code:o.trimEnd(),custom:!0,fence:n,hasReverse:!!c.reverse}):u.push({lang:a,code:g(o.trimEnd()),custom:!1,fence:n}),l}),h=h.replace(/`([^`]+)`/g,(e,t)=>{const n=`§IC${m.length}§`;return m.push(g(t)),n}),h=g(h),h=function(e,t){const n=e.split("\n"),r=[];let o=!1,l=[];for(let e=0;e<n.length;e++){const c=n[e].trim();if(c.includes("|")&&(c.startsWith("|")||/[^\\|]/.test(c)))o||(o=!0,l=[]),l.push(c);else{if(o){const e=a(l,t);e?r.push(e):r.push(...l),o=!1,l=[]}r.push(n[e])}}if(o&&l.length>0){const e=a(l,t);e?r.push(e):r.push(...l)}return r.join("\n")}(h,d),h=h.replace(/^(#{1,6})\s+(.+?)\s*#*$/gm,(e,t,n)=>{const r=t.length;return`<h${r}${d("h"+r)}${$(t)}>${n}</h${r}>`}),h=h.replace(/^&gt;\s+(.+)$/gm,`<blockquote${d("blockquote")}>$1</blockquote>`),h=h.replace(/<\/blockquote>\n<blockquote>/g,"\n"),h=h.replace(/^---+\s*$/gm,`<hr${d("hr")}>`),h=function(t,n,r,o){const l=t.split("\n"),a=[];let c=[];const s=e=>e.replace(/[&<>"']/g,e=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[e])),i=o?e=>` data-qd="${s(e)}"`:()=>"";for(let t=0;t<l.length;t++){const o=l[t],s=o.match(/^(\s*)([*\-+]|\d+\.)\s+(.+)$/);if(s){const[,t,o,l]=s,p=Math.floor(t.length/2),d=/^\d+\./.test(o),g=d?"ol":"ul";let $=l,f="";const h=l.match(/^\[([x ])\]\s+(.*)$/i);if(h&&!d){const[,t,n]=h,o="x"===t.toLowerCase();$=`<input type="checkbox"${r?' style="margin-right:.5em"':` class="${e}task-checkbox"`}${o?" checked":""} disabled> ${n}`,f=r?' style="list-style:none"':` class="${e}task-item"`}for(;c.length>p+1;){const e=c.pop();a.push(`</${e.type}>`)}if(c.length===p)c.push({type:g,level:p}),a.push(`<${g}${n(g)}>`);else if(c.length===p+1){const e=c[c.length-1];e.type!==g&&(a.push(`</${e.type}>`),c.pop(),c.push({type:g,level:p}),a.push(`<${g}${n(g)}>`))}const u=f||n("li");a.push(`<li${u}${i(o)}>${$}</li>`)}else{for(;c.length>0;){const e=c.pop();a.push(`</${e.type}>`)}a.push(o)}}for(;c.length>0;){const e=c.pop();a.push(`</${e.type}>`)}return a.join("\n")}(h,d,s,i),h=h.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(e,t,n)=>{const r=f(n,l.allow_unsafe_urls),o=i&&t?` data-qd-alt="${g(t)}"`:"",a=i?` data-qd-src="${g(n)}"`:"";return`<img${d("img")} src="${r}" alt="${t}"${o}${a}${$("!")}>`}),h=h.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(e,t,n)=>{const r=f(n,l.allow_unsafe_urls),o=/^https?:\/\//i.test(r)?' rel="noopener noreferrer"':"",a=i?` data-qd-text="${g(t)}"`:"";return`<a${d("a")} href="${r}"${o}${a}${$("[")}>${t}</a>`}),h=h.replace(/(^|\s)(https?:\/\/[^\s<]+)/g,(e,t,n)=>{const r=f(n,l.allow_unsafe_urls);return`${t}<a${d("a")} href="${r}" rel="noopener noreferrer">${n}</a>`});if([[/\*\*(.+?)\*\*/g,"strong","**"],[/__(.+?)__/g,"strong","__"],[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,"em","*"],[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g,"em","_"],[/~~(.+?)~~/g,"del","~~"]].forEach(([e,t,n])=>{h=h.replace(e,`<${t}${d(t)}${$(n)}>$1</${t}>`)}),p){const e=[];let t=0;h=h.replace(/<(table|[uo]l)[^>]*>[\s\S]*?<\/\1>/g,n=>(e[t]=n,`§B${t++}§`)),h=h.replace(/\n\n+/g,"§P§").replace(/(<\/(?:h[1-6]|blockquote|pre)>)\n/g,"$1§N§").replace(/(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)\n/g,"$1§N§").replace(/\n(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)/g,"§N§$1").replace(/\n(§B\d+§)/g,"§N§$1").replace(/(§B\d+§)\n/g,"$1§N§").replace(/\n/g,`<br${d("br")}>`).replace(/§N§/g,"\n").replace(/§P§/g,"</p><p>"),e.forEach((e,t)=>h=h.replace(`§B${t}§`,e)),h="<p>"+h+"</p>"}else h=h.replace(/ $/gm,`<br${d("br")}>`),h=h.replace(/\n\n+/g,(e,t)=>h.substring(0,t).match(/<\/(h[1-6]|blockquote|ul|ol|table|pre|hr)>$/)?"<p>":"</p><p>"),h="<p>"+h+"</p>";return[[/<p><\/p>/g,""],[/<p>(<h[1-6][^>]*>)/g,"$1"],[/(<\/h[1-6]>)<\/p>/g,"$1"],[/<p>(<blockquote[^>]*>)/g,"$1"],[/(<\/blockquote>)<\/p>/g,"$1"],[/<p>(<ul[^>]*>|<ol[^>]*>)/g,"$1"],[/(<\/ul>|<\/ol>)<\/p>/g,"$1"],[/<p>(<hr[^>]*>)<\/p>/g,"$1"],[/<p>(<table[^>]*>)/g,"$1"],[/(<\/table>)<\/p>/g,"$1"],[/<p>(<pre[^>]*>)/g,"$1"],[/(<\/pre>)<\/p>/g,"$1"],[new RegExp(`<p>(${t}\\d+§)</p>`,"g"),"$1"]].forEach(([e,t])=>{h=h.replace(e,t)}),h=h.replace(/(<\/(?:h[1-6]|blockquote|ul|ol|table|pre|hr)>)\n([^<])/g,"$1\n<p>$2"),u.forEach((e,n)=>{let r;if(e.custom&&c&&c.render)if(r=c.render(e.code,e.lang),void 0===r){const t=!s&&e.lang?` class="language-${e.lang}"`:"",n=s?d("code"):t,o=i&&e.lang?` data-qd-lang="${g(e.lang)}"`:"",l=i?` data-qd-fence="${g(e.fence)}"`:"";r=`<pre${d("pre")}${l}${o}><code${n}>${g(e.code)}</code></pre>`}else i&&(r=r.replace(/^<(\w+)/,`<$1 data-qd-fence="${g(e.fence)}" data-qd-lang="${g(e.lang)}" data-qd-source="${g(e.code)}"`));else{const t=!s&&e.lang?` class="language-${e.lang}"`:"",n=s?d("code"):t,o=i&&e.lang?` data-qd-lang="${g(e.lang)}"`:"",l=i?` data-qd-fence="${g(e.fence)}"`:"";r=`<pre${d("pre")}${l}${o}><code${n}>${e.code}</code></pre>`}const o=`${t}${n}§`;h=h.replace(o,r)}),m.forEach((e,t)=>{const n=`§IC${t}§`;h=h.replace(n,`<code${d("code")}${$("`")}>${e}</code>`)}),h.trim()}function l(e,t){return[[/\*\*(.+?)\*\*/g,"strong"],[/__(.+?)__/g,"strong"],[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,"em"],[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g,"em"],[/~~(.+?)~~/g,"del"],[/`([^`]+)`/g,"code"]].forEach(([n,r])=>{e=e.replace(n,`<${r}${t(r)}>$1</${r}>`)}),e}function a(e,t){if(e.length<2)return null;let n=-1;for(let t=1;t<e.length;t++)if(/^\|?[\s\-:|]+\|?$/.test(e[t])&&e[t].includes("-")){n=t;break}if(-1===n)return null;const r=e.slice(0,n),o=e.slice(n+1),a=e[n].trim().replace(/^\|/,"").replace(/\|$/,"").split("|").map(e=>{const t=e.trim();return t.startsWith(":")&&t.endsWith(":")?"center":t.endsWith(":")?"right":"left"});let c=`<table${t("table")}>\n`;return c+=`<thead${t("thead")}>\n`,r.forEach(e=>{c+=`<tr${t("tr")}>\n`;e.trim().replace(/^\|/,"").replace(/\|$/,"").split("|").forEach((e,n)=>{const r=a[n]&&"left"!==a[n]?`text-align:${a[n]}`:"",o=l(e.trim(),t);c+=`<th${t("th",r)}>${o}</th>\n`}),c+="</tr>\n"}),c+="</thead>\n",o.length>0&&(c+=`<tbody${t("tbody")}>\n`,o.forEach(e=>{c+=`<tr${t("tr")}>\n`;e.trim().replace(/^\|/,"").replace(/\|$/,"").split("|").forEach((e,n)=>{const r=a[n]&&"left"!==a[n]?`text-align:${a[n]}`:"",o=l(e.trim(),t);c+=`<td${t("td",r)}>${o}</td>\n`}),c+="</tr>\n"}),c+="</tbody>\n"),c+="</table>",c}o.emitStyles=function(e="quikdown-",t="light"){const n=r,o={"#f4f4f4":"#2a2a2a","#f0f0f0":"#2a2a2a","#f2f2f2":"#2a2a2a","#ddd":"#3a3a3a","#06c":"#6db3f2",_textColor:"#e0e0e0"},l={_textColor:"#333"};let a="";for(const[r,c]of Object.entries(n)){let n=c;if("dark"===t&&o){for(const[e,t]of Object.entries(o))e.startsWith("_")||(n=n.replace(new RegExp(e,"g"),t));["h1","h2","h3","h4","h5","h6","td","li","blockquote"].includes(r)&&(n+=`;color:${o._textColor}`)}else if("light"===t&&l){["h1","h2","h3","h4","h5","h6","td","li","blockquote"].includes(r)&&(n+=`;color:${l._textColor}`)}a+=`.${e}${r} { ${n} }\n`}return a},o.configure=function(e){return function(t){return o(t,e)}},o.version="1.1.0","undefined"!=typeof module&&module.exports&&(module.exports=o),"undefined"!=typeof window&&(window.quikdown=o);export{o as default};
8
8
  //# sourceMappingURL=quikdown.esm.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"quikdown.esm.min.js","sources":["../src/quikdown.js"],"sourcesContent":["/**\n * quikdown - A minimal markdown parser optimized for chat/LLM output\n * Supports tables, code blocks, lists, and common formatting\n * @param {string} markdown - The markdown source text\n * @param {Object} options - Optional configuration object\n * @param {Function} options.fence_plugin - Custom renderer for fenced code blocks\n * (content, fence_string) => html string\n * @param {boolean} options.inline_styles - If true, uses inline styles instead of classes\n * @returns {string} - The rendered HTML\n */\n\n// Version will be injected at build time \nconst quikdownVersion = '__QUIKDOWN_VERSION__';\n\n// Constants for reuse\nconst CLASS_PREFIX = 'quikdown-';\nconst PLACEHOLDER_CB = '§CB';\nconst PLACEHOLDER_IC = '§IC';\n\n// Escape map at module level\nconst ESC_MAP = {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'};\n\n// Single source of truth for all style definitions - optimized\nconst QUIKDOWN_STYLES = {\n h1: 'font-size:2em;font-weight:600;margin:.67em 0;text-align:left',\n h2: 'font-size:1.5em;font-weight:600;margin:.83em 0',\n h3: 'font-size:1.25em;font-weight:600;margin:1em 0',\n h4: 'font-size:1em;font-weight:600;margin:1.33em 0',\n h5: 'font-size:.875em;font-weight:600;margin:1.67em 0',\n h6: 'font-size:.85em;font-weight:600;margin:2em 0',\n pre: 'background:#f4f4f4;padding:10px;border-radius:4px;overflow-x:auto;margin:1em 0',\n code: 'background:#f0f0f0;padding:2px 4px;border-radius:3px;font-family:monospace',\n blockquote: 'border-left:4px solid #ddd;margin-left:0;padding-left:1em',\n table: 'border-collapse:collapse;width:100%;margin:1em 0',\n th: 'border:1px solid #ddd;padding:8px;background-color:#f2f2f2;font-weight:bold;text-align:left',\n td: 'border:1px solid #ddd;padding:8px;text-align:left',\n hr: 'border:none;border-top:1px solid #ddd;margin:1em 0',\n img: 'max-width:100%;height:auto',\n a: 'color:#06c;text-decoration:underline',\n strong: 'font-weight:bold',\n em: 'font-style:italic',\n del: 'text-decoration:line-through',\n ul: 'margin:.5em 0;padding-left:2em',\n ol: 'margin:.5em 0;padding-left:2em',\n li: 'margin:.25em 0',\n // Task list specific styles\n 'task-item': 'list-style:none',\n 'task-checkbox': 'margin-right:.5em'\n};\n\n// Factory function to create getAttr for a given context\nfunction createGetAttr(inline_styles, styles) {\n return function(tag, additionalStyle = '') {\n if (inline_styles) {\n const style = styles[tag];\n if (!style && !additionalStyle) return '';\n const fullStyle = additionalStyle ? (style ? `${style};${additionalStyle}` : additionalStyle) : style;\n return ` style=\"${fullStyle}\"`;\n } else {\n return ` class=\"${CLASS_PREFIX}${tag}\"`;\n }\n };\n}\n\nfunction quikdown(markdown, options = {}) {\n if (!markdown || typeof markdown !== 'string') {\n return '';\n }\n \n const { fence_plugin, inline_styles = false } = options;\n const styles = QUIKDOWN_STYLES; // Use module-level styles\n const getAttr = createGetAttr(inline_styles, styles); // Create getAttr once\n\n // Escape HTML entities to prevent XSS\n function escapeHtml(text) {\n return text.replace(/[&<>\"']/g, m => ESC_MAP[m]);\n }\n \n // Sanitize URLs to prevent XSS attacks\n function sanitizeUrl(url, allowUnsafe = false) {\n if (!url) return '';\n \n // If unsafe URLs are explicitly allowed, return as-is\n if (allowUnsafe) return url;\n \n const trimmedUrl = url.trim();\n const lowerUrl = trimmedUrl.toLowerCase();\n \n // Block dangerous protocols\n const dangerousProtocols = ['javascript:', 'vbscript:', 'data:'];\n \n for (const protocol of dangerousProtocols) {\n if (lowerUrl.startsWith(protocol)) {\n // Exception: Allow data:image/* for images\n if (protocol === 'data:' && lowerUrl.startsWith('data:image/')) {\n return trimmedUrl;\n }\n // Return safe empty link for dangerous protocols\n return '#';\n }\n }\n \n return trimmedUrl;\n }\n\n // Process the markdown in phases\n let html = markdown;\n \n // Phase 1: Extract and protect code blocks and inline code\n const codeBlocks = [];\n const inlineCodes = [];\n \n // Extract fenced code blocks first (supports both ``` and ~~~)\n // Match paired fences - ``` with ``` and ~~~ with ~~~\n // Fence must be at start of line\n html = html.replace(/^(```|~~~)([^\\n]*)\\n([\\s\\S]*?)^\\1$/gm, (match, fence, lang, code) => {\n const placeholder = `${PLACEHOLDER_CB}${codeBlocks.length}§`;\n \n // Trim the language specification\n const langTrimmed = lang ? lang.trim() : '';\n \n // If custom fence plugin is provided, use it\n if (fence_plugin && typeof fence_plugin === 'function') {\n codeBlocks.push({\n lang: langTrimmed,\n code: code.trimEnd(),\n custom: true\n });\n } else {\n codeBlocks.push({\n lang: langTrimmed,\n code: escapeHtml(code.trimEnd()),\n custom: false\n });\n }\n return placeholder;\n });\n \n // Extract inline code\n html = html.replace(/`([^`]+)`/g, (match, code) => {\n const placeholder = `${PLACEHOLDER_IC}${inlineCodes.length}§`;\n inlineCodes.push(escapeHtml(code));\n return placeholder;\n });\n \n // Now escape HTML in the rest of the content\n html = escapeHtml(html);\n \n // Phase 2: Process block elements\n \n // Process tables\n html = processTable(html, getAttr);\n \n // Process headings (supports optional trailing #'s)\n html = html.replace(/^(#{1,6})\\s+(.+?)\\s*#*$/gm, (match, hashes, content) => {\n const level = hashes.length;\n return `<h${level}${getAttr('h' + level)}>${content}</h${level}>`;\n });\n \n // Process blockquotes (must handle escaped > since we already escaped HTML)\n html = html.replace(/^&gt;\\s+(.+)$/gm, `<blockquote${getAttr('blockquote')}>$1</blockquote>`);\n // Merge consecutive blockquotes\n html = html.replace(/<\\/blockquote>\\n<blockquote>/g, '\\n');\n \n // Process horizontal rules\n html = html.replace(/^---+$/gm, `<hr${getAttr('hr')}>`);\n \n // Process lists\n html = processLists(html, getAttr, inline_styles);\n \n // Phase 3: Process inline elements\n \n // Images (must come before links, with URL sanitization)\n html = html.replace(/!\\[([^\\]]*)\\]\\(([^)]+)\\)/g, (match, alt, src) => {\n const sanitizedSrc = sanitizeUrl(src, options.allow_unsafe_urls);\n return `<img${getAttr('img')} src=\"${sanitizedSrc}\" alt=\"${alt}\">`;\n });\n \n // Links (with URL sanitization)\n html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (match, text, href) => {\n // Sanitize URL to prevent XSS\n const sanitizedHref = sanitizeUrl(href, options.allow_unsafe_urls);\n const isExternal = /^https?:\\/\\//i.test(sanitizedHref);\n const rel = isExternal ? ' rel=\"noopener noreferrer\"' : '';\n return `<a${getAttr('a')} href=\"${sanitizedHref}\"${rel}>${text}</a>`;\n });\n \n // Autolinks - convert bare URLs to clickable links\n html = html.replace(/(^|\\s)(https?:\\/\\/[^\\s<]+)/g, (match, prefix, url) => {\n const sanitizedUrl = sanitizeUrl(url, options.allow_unsafe_urls);\n return `${prefix}<a${getAttr('a')} href=\"${sanitizedUrl}\" rel=\"noopener noreferrer\">${url}</a>`;\n });\n \n // Process inline formatting (bold, italic, strikethrough)\n const inlinePatterns = [\n [/\\*\\*(.+?)\\*\\*/g, 'strong'],\n [/__(.+?)__/g, 'strong'],\n [/(?<!\\*)\\*(?!\\*)(.+?)(?<!\\*)\\*(?!\\*)/g, 'em'],\n [/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em'],\n [/~~(.+?)~~/g, 'del']\n ];\n \n inlinePatterns.forEach(([pattern, tag]) => {\n html = html.replace(pattern, `<${tag}${getAttr(tag)}>$1</${tag}>`);\n });\n \n // Line breaks (two spaces at end of line)\n html = html.replace(/ $/gm, `<br${getAttr('br')}>`);\n \n // Paragraphs (double newlines)\n html = html.replace(/\\n\\n+/g, '</p><p>');\n html = '<p>' + html + '</p>';\n \n // Clean up empty paragraphs and unwrap block elements\n const cleanupPatterns = [\n [/<p><\\/p>/g, ''],\n [/<p>(<h[1-6][^>]*>)/g, '$1'],\n [/(<\\/h[1-6]>)<\\/p>/g, '$1'],\n [/<p>(<blockquote[^>]*>)/g, '$1'],\n [/(<\\/blockquote>)<\\/p>/g, '$1'],\n [/<p>(<ul[^>]*>|<ol[^>]*>)/g, '$1'],\n [/(<\\/ul>|<\\/ol>)<\\/p>/g, '$1'],\n [/<p>(<hr[^>]*>)<\\/p>/g, '$1'],\n [/<p>(<table[^>]*>)/g, '$1'],\n [/(<\\/table>)<\\/p>/g, '$1'],\n [/<p>(<pre[^>]*>)/g, '$1'],\n [/(<\\/pre>)<\\/p>/g, '$1'],\n [new RegExp(`<p>(${PLACEHOLDER_CB}\\\\d+§)<\\/p>`, 'g'), '$1']\n ];\n \n cleanupPatterns.forEach(([pattern, replacement]) => {\n html = html.replace(pattern, replacement);\n });\n \n // Phase 4: Restore code blocks and inline code\n \n // Restore code blocks\n codeBlocks.forEach((block, i) => {\n let replacement;\n \n if (block.custom && fence_plugin) {\n // Use custom fence plugin\n replacement = fence_plugin(block.code, block.lang);\n // If plugin returns undefined, fall back to default rendering\n if (replacement === undefined) {\n const langClass = !inline_styles && block.lang ? ` class=\"language-${block.lang}\"` : '';\n const codeAttr = inline_styles ? getAttr('code') : langClass;\n replacement = `<pre${getAttr('pre')}><code${codeAttr}>${escapeHtml(block.code)}</code></pre>`;\n }\n } else {\n // Default rendering\n const langClass = !inline_styles && block.lang ? ` class=\"language-${block.lang}\"` : '';\n const codeAttr = inline_styles ? getAttr('code') : langClass;\n replacement = `<pre${getAttr('pre')}><code${codeAttr}>${block.code}</code></pre>`;\n }\n \n const placeholder = `${PLACEHOLDER_CB}${i}§`;\n html = html.replace(placeholder, replacement);\n });\n \n // Restore inline code\n inlineCodes.forEach((code, i) => {\n const placeholder = `${PLACEHOLDER_IC}${i}§`;\n html = html.replace(placeholder, `<code${getAttr('code')}>${code}</code>`);\n });\n \n return html.trim();\n}\n\n/**\n * Process inline markdown formatting\n */\nfunction processInlineMarkdown(text, getAttr) {\n \n // Process inline formatting patterns\n const patterns = [\n [/\\*\\*(.+?)\\*\\*/g, 'strong'],\n [/__(.+?)__/g, 'strong'],\n [/(?<!\\*)\\*(?!\\*)(.+?)(?<!\\*)\\*(?!\\*)/g, 'em'],\n [/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em'],\n [/~~(.+?)~~/g, 'del'],\n [/`([^`]+)`/g, 'code']\n ];\n \n patterns.forEach(([pattern, tag]) => {\n text = text.replace(pattern, `<${tag}${getAttr(tag)}>$1</${tag}>`);\n });\n \n return text;\n}\n\n/**\n * Process markdown tables\n */\nfunction processTable(text, getAttr) {\n const lines = text.split('\\n');\n const result = [];\n let inTable = false;\n let tableLines = [];\n \n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n \n // Check if this line looks like a table row (with or without trailing |)\n if (line.includes('|') && (line.startsWith('|') || /[^\\\\|]/.test(line))) {\n if (!inTable) {\n inTable = true;\n tableLines = [];\n }\n tableLines.push(line);\n } else {\n // Not a table line\n if (inTable) {\n // Process the accumulated table\n const tableHtml = buildTable(tableLines, getAttr);\n if (tableHtml) {\n result.push(tableHtml);\n } else {\n // Not a valid table, restore original lines\n result.push(...tableLines);\n }\n inTable = false;\n tableLines = [];\n }\n result.push(lines[i]);\n }\n }\n \n // Handle table at end of text\n if (inTable && tableLines.length > 0) {\n const tableHtml = buildTable(tableLines, getAttr);\n if (tableHtml) {\n result.push(tableHtml);\n } else {\n result.push(...tableLines);\n }\n }\n \n return result.join('\\n');\n}\n\n/**\n * Build an HTML table from markdown table lines\n */\nfunction buildTable(lines, getAttr) {\n \n if (lines.length < 2) return null;\n \n // Check for separator line (second line should be the separator)\n let separatorIndex = -1;\n for (let i = 1; i < lines.length; i++) {\n // Support separator with or without leading/trailing pipes\n if (/^\\|?[\\s\\-:|]+\\|?$/.test(lines[i]) && lines[i].includes('-')) {\n separatorIndex = i;\n break;\n }\n }\n \n if (separatorIndex === -1) return null;\n \n const headerLines = lines.slice(0, separatorIndex);\n const bodyLines = lines.slice(separatorIndex + 1);\n \n // Parse alignment from separator\n const separator = lines[separatorIndex];\n // Handle pipes at start/end or not\n const separatorCells = separator.trim().replace(/^\\|/, '').replace(/\\|$/, '').split('|');\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 let html = `<table${getAttr('table')}>\\n`;\n \n // Build header\n if (headerLines.length > 0) {\n html += `<thead${getAttr('thead')}>\\n`;\n headerLines.forEach(line => {\n html += `<tr${getAttr('tr')}>\\n`;\n // Handle pipes at start/end or not\n const cells = line.trim().replace(/^\\|/, '').replace(/\\|$/, '').split('|');\n cells.forEach((cell, i) => {\n const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';\n const processedCell = processInlineMarkdown(cell.trim(), getAttr);\n html += `<th${getAttr('th', alignStyle)}>${processedCell}</th>\\n`;\n });\n html += '</tr>\\n';\n });\n html += '</thead>\\n';\n }\n \n // Build body\n if (bodyLines.length > 0) {\n html += `<tbody${getAttr('tbody')}>\\n`;\n bodyLines.forEach(line => {\n html += `<tr${getAttr('tr')}>\\n`;\n // Handle pipes at start/end or not\n const cells = line.trim().replace(/^\\|/, '').replace(/\\|$/, '').split('|');\n cells.forEach((cell, i) => {\n const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';\n const processedCell = processInlineMarkdown(cell.trim(), getAttr);\n html += `<td${getAttr('td', alignStyle)}>${processedCell}</td>\\n`;\n });\n html += '</tr>\\n';\n });\n html += '</tbody>\\n';\n }\n \n html += '</table>';\n return html;\n}\n\n/**\n * Process markdown lists (ordered and unordered)\n */\nfunction processLists(text, getAttr, inline_styles) {\n \n const lines = text.split('\\n');\n const result = [];\n let listStack = []; // Track nested lists\n \n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const match = line.match(/^(\\s*)([*\\-+]|\\d+\\.)\\s+(.+)$/);\n \n if (match) {\n const [, indent, marker, content] = match;\n const level = Math.floor(indent.length / 2);\n const isOrdered = /^\\d+\\./.test(marker);\n const listType = isOrdered ? 'ol' : 'ul';\n \n // Check for task list items\n let listItemContent = content;\n let taskListClass = '';\n const taskMatch = content.match(/^\\[([x ])\\]\\s+(.*)$/i);\n if (taskMatch && !isOrdered) {\n const [, checked, taskContent] = taskMatch;\n const isChecked = checked.toLowerCase() === 'x';\n const checkboxAttr = inline_styles \n ? ' style=\"margin-right:.5em\"' \n : ` class=\"${CLASS_PREFIX}task-checkbox\"`;\n listItemContent = `<input type=\"checkbox\"${checkboxAttr}${isChecked ? ' checked' : ''} disabled> ${taskContent}`;\n taskListClass = inline_styles ? ' style=\"list-style:none\"' : ` class=\"${CLASS_PREFIX}task-item\"`;\n }\n \n // Close deeper levels\n while (listStack.length > level + 1) {\n const list = listStack.pop();\n result.push(`</${list.type}>`);\n }\n \n // Open new level if needed\n if (listStack.length === level) {\n // Need to open a new list\n listStack.push({ type: listType, level });\n result.push(`<${listType}${getAttr(listType)}>`);\n } else if (listStack.length === level + 1) {\n // Check if we need to switch list type\n const currentList = listStack[listStack.length - 1];\n if (currentList.type !== listType) {\n result.push(`</${currentList.type}>`);\n listStack.pop();\n listStack.push({ type: listType, level });\n result.push(`<${listType}${getAttr(listType)}>`);\n }\n }\n \n const liAttr = taskListClass || getAttr('li');\n result.push(`<li${liAttr}>${listItemContent}</li>`);\n } else {\n // Not a list item, close all lists\n while (listStack.length > 0) {\n const list = listStack.pop();\n result.push(`</${list.type}>`);\n }\n result.push(line);\n }\n }\n \n // Close any remaining lists\n while (listStack.length > 0) {\n const list = listStack.pop();\n result.push(`</${list.type}>`);\n }\n \n return result.join('\\n');\n}\n\n/**\n * Emit CSS styles for quikdown elements\n * @param {string} prefix - Optional class prefix (default: 'quikdown-')\n * @param {string} theme - Optional theme: 'light' (default) or 'dark'\n * @returns {string} CSS string with quikdown styles\n */\nquikdown.emitStyles = function(prefix = 'quikdown-', theme = 'light') {\n const styles = QUIKDOWN_STYLES;\n \n // Define theme color overrides\n const themeOverrides = {\n dark: {\n '#f4f4f4': '#2a2a2a', // pre background\n '#f0f0f0': '#2a2a2a', // code background\n '#f2f2f2': '#2a2a2a', // th background\n '#ddd': '#3a3a3a', // borders\n '#06c': '#6db3f2', // links\n _textColor: '#e0e0e0'\n },\n light: {\n _textColor: '#333' // Explicit text color for light theme\n }\n };\n \n let css = '';\n for (const [tag, style] of Object.entries(styles)) {\n if (style) {\n let themedStyle = style;\n \n // Apply theme overrides if dark theme\n if (theme === 'dark' && themeOverrides.dark) {\n // Replace colors\n for (const [oldColor, newColor] of Object.entries(themeOverrides.dark)) {\n if (!oldColor.startsWith('_')) {\n themedStyle = themedStyle.replace(new RegExp(oldColor, 'g'), newColor);\n }\n }\n \n // Add text color for certain elements in dark theme\n const needsTextColor = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'li', 'blockquote'];\n if (needsTextColor.includes(tag)) {\n themedStyle += `;color:${themeOverrides.dark._textColor}`;\n }\n } else if (theme === 'light' && themeOverrides.light) {\n // Add explicit text color for light theme elements too\n const needsTextColor = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'li', 'blockquote'];\n if (needsTextColor.includes(tag)) {\n themedStyle += `;color:${themeOverrides.light._textColor}`;\n }\n }\n \n css += `.${prefix}${tag} { ${themedStyle} }\\n`;\n }\n }\n \n return css;\n};\n\n/**\n * Configure quikdown with options and return a function\n * @param {Object} options - Configuration options\n * @returns {Function} Configured quikdown function\n */\nquikdown.configure = function(options) {\n return function(markdown) {\n return quikdown(markdown, options);\n };\n};\n\n/**\n * Version information\n */\nquikdown.version = quikdownVersion;\n\n// Export for both CommonJS and ES6\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = quikdown;\n}\n\n// For browser global\nif (typeof window !== 'undefined') {\n window.quikdown = quikdown;\n}\n\nexport default quikdown;"],"names":["CLASS_PREFIX","PLACEHOLDER_CB","ESC_MAP","QUIKDOWN_STYLES","h1","h2","h3","h4","h5","h6","pre","code","blockquote","table","th","td","hr","img","a","strong","em","del","ul","ol","li","quikdown","markdown","options","fence_plugin","inline_styles","getAttr","styles","tag","additionalStyle","style","createGetAttr","escapeHtml","text","replace","m","sanitizeUrl","url","allowUnsafe","trimmedUrl","trim","lowerUrl","toLowerCase","dangerousProtocols","protocol","startsWith","html","codeBlocks","inlineCodes","match","fence","lang","placeholder","length","langTrimmed","push","trimEnd","custom","lines","split","result","inTable","tableLines","i","line","includes","test","tableHtml","buildTable","join","processTable","hashes","content","level","listStack","indent","marker","Math","floor","isOrdered","listType","listItemContent","taskListClass","taskMatch","checked","taskContent","isChecked","list","pop","type","currentList","liAttr","processLists","alt","src","sanitizedSrc","allow_unsafe_urls","href","sanitizedHref","rel","prefix","sanitizedUrl","forEach","pattern","RegExp","replacement","block","undefined","langClass","codeAttr","processInlineMarkdown","separatorIndex","headerLines","slice","bodyLines","alignments","map","cell","trimmed","endsWith","alignStyle","processedCell","emitStyles","theme","themeOverrides","_textColor","css","Object","entries","themedStyle","oldColor","newColor","configure","version","module","exports","window"],"mappings":"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"}
1
+ {"version":3,"file":"quikdown.esm.min.js","sources":["../src/quikdown.js"],"sourcesContent":["/**\n * quikdown - A minimal markdown parser optimized for chat/LLM output\n * Supports tables, code blocks, lists, and common formatting\n * @param {string} markdown - The markdown source text\n * @param {Object} options - Optional configuration object\n * @param {Function} options.fence_plugin - Custom renderer for fenced code blocks\n * (content, fence_string) => html string\n * @param {boolean} options.inline_styles - If true, uses inline styles instead of classes\n * @param {boolean} options.bidirectional - If true, adds data-qd attributes for source tracking\n * @param {boolean} options.lazy_linefeeds - If true, single newlines become <br> tags\n * @returns {string} - The rendered HTML\n */\n\n// Version will be injected at build time \nconst quikdownVersion = '__QUIKDOWN_VERSION__';\n\n// Constants for reuse\nconst CLASS_PREFIX = 'quikdown-';\nconst PLACEHOLDER_CB = '§CB';\nconst PLACEHOLDER_IC = '§IC';\n\n// Escape map at module level\nconst ESC_MAP = {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'};\n\n// Single source of truth for all style definitions - optimized\nconst QUIKDOWN_STYLES = {\n h1: 'font-size:2em;font-weight:600;margin:.67em 0;text-align:left',\n h2: 'font-size:1.5em;font-weight:600;margin:.83em 0',\n h3: 'font-size:1.25em;font-weight:600;margin:1em 0',\n h4: 'font-size:1em;font-weight:600;margin:1.33em 0',\n h5: 'font-size:.875em;font-weight:600;margin:1.67em 0',\n h6: 'font-size:.85em;font-weight:600;margin:2em 0',\n pre: 'background:#f4f4f4;padding:10px;border-radius:4px;overflow-x:auto;margin:1em 0',\n code: 'background:#f0f0f0;padding:2px 4px;border-radius:3px;font-family:monospace',\n blockquote: 'border-left:4px solid #ddd;margin-left:0;padding-left:1em',\n table: 'border-collapse:collapse;width:100%;margin:1em 0',\n th: 'border:1px solid #ddd;padding:8px;background-color:#f2f2f2;font-weight:bold;text-align:left',\n td: 'border:1px solid #ddd;padding:8px;text-align:left',\n hr: 'border:none;border-top:1px solid #ddd;margin:1em 0',\n img: 'max-width:100%;height:auto',\n a: 'color:#06c;text-decoration:underline',\n strong: 'font-weight:bold',\n em: 'font-style:italic',\n del: 'text-decoration:line-through',\n ul: 'margin:.5em 0;padding-left:2em',\n ol: 'margin:.5em 0;padding-left:2em',\n li: 'margin:.25em 0',\n // Task list specific styles\n 'task-item': 'list-style:none',\n 'task-checkbox': 'margin-right:.5em'\n};\n\n// Factory function to create getAttr for a given context\nfunction createGetAttr(inline_styles, styles) {\n return function(tag, additionalStyle = '') {\n if (inline_styles) {\n let style = styles[tag];\n if (!style && !additionalStyle) return '';\n \n // Remove default text-align if we're adding a different alignment\n if (additionalStyle && additionalStyle.includes('text-align') && style && style.includes('text-align')) {\n style = style.replace(/text-align:[^;]+;?/, '').trim();\n if (style && !style.endsWith(';')) style += ';';\n }\n \n /* istanbul ignore next - defensive: additionalStyle without style doesn't occur with current tags */\n const fullStyle = additionalStyle ? (style ? `${style}${additionalStyle}` : additionalStyle) : style;\n return ` style=\"${fullStyle}\"`;\n } else {\n const classAttr = ` class=\"${CLASS_PREFIX}${tag}\"`;\n // Apply inline styles for alignment even when using CSS classes\n if (additionalStyle) {\n return `${classAttr} style=\"${additionalStyle}\"`;\n }\n return classAttr;\n }\n };\n}\n\nfunction quikdown(markdown, options = {}) {\n if (!markdown || typeof markdown !== 'string') {\n return '';\n }\n \n const { fence_plugin, inline_styles = false, bidirectional = false, lazy_linefeeds = false } = options;\n const styles = QUIKDOWN_STYLES; // Use module-level styles\n const getAttr = createGetAttr(inline_styles, styles); // Create getAttr once\n\n // Escape HTML entities to prevent XSS\n function escapeHtml(text) {\n return text.replace(/[&<>\"']/g, m => ESC_MAP[m]);\n }\n \n // Helper to add data-qd attributes for bidirectional support\n const dataQd = bidirectional ? (marker) => ` data-qd=\"${escapeHtml(marker)}\"` : () => '';\n \n // Sanitize URLs to prevent XSS attacks\n function sanitizeUrl(url, allowUnsafe = false) {\n /* istanbul ignore next - defensive programming, regex ensures url is never empty */\n if (!url) return '';\n \n // If unsafe URLs are explicitly allowed, return as-is\n if (allowUnsafe) return url;\n \n const trimmedUrl = url.trim();\n const lowerUrl = trimmedUrl.toLowerCase();\n \n // Block dangerous protocols\n const dangerousProtocols = ['javascript:', 'vbscript:', 'data:'];\n \n for (const protocol of dangerousProtocols) {\n if (lowerUrl.startsWith(protocol)) {\n // Exception: Allow data:image/* for images\n if (protocol === 'data:' && lowerUrl.startsWith('data:image/')) {\n return trimmedUrl;\n }\n // Return safe empty link for dangerous protocols\n return '#';\n }\n }\n \n return trimmedUrl;\n }\n\n // Process the markdown in phases\n let html = markdown;\n \n // Phase 1: Extract and protect code blocks and inline code\n const codeBlocks = [];\n const inlineCodes = [];\n \n // Extract fenced code blocks first (supports both ``` and ~~~)\n // Match paired fences - ``` with ``` and ~~~ with ~~~\n // Fence must be at start of line\n html = html.replace(/^(```|~~~)([^\\n]*)\\n([\\s\\S]*?)^\\1$/gm, (match, fence, lang, code) => {\n const placeholder = `${PLACEHOLDER_CB}${codeBlocks.length}§`;\n \n // Trim the language specification\n const langTrimmed = lang ? lang.trim() : '';\n \n // If custom fence plugin is provided, use it (v1.1.0: object format required)\n if (fence_plugin && fence_plugin.render && typeof fence_plugin.render === 'function') {\n codeBlocks.push({\n lang: langTrimmed,\n code: code.trimEnd(),\n custom: true,\n fence: fence,\n hasReverse: !!fence_plugin.reverse\n });\n } else {\n codeBlocks.push({\n lang: langTrimmed,\n code: escapeHtml(code.trimEnd()),\n custom: false,\n fence: fence\n });\n }\n return placeholder;\n });\n \n // Extract inline code\n html = html.replace(/`([^`]+)`/g, (match, code) => {\n const placeholder = `${PLACEHOLDER_IC}${inlineCodes.length}§`;\n inlineCodes.push(escapeHtml(code));\n return placeholder;\n });\n \n // Now escape HTML in the rest of the content\n html = escapeHtml(html);\n \n // Phase 2: Process block elements\n \n // Process tables\n html = processTable(html, getAttr);\n \n // Process headings (supports optional trailing #'s)\n html = html.replace(/^(#{1,6})\\s+(.+?)\\s*#*$/gm, (match, hashes, content) => {\n const level = hashes.length;\n return `<h${level}${getAttr('h' + level)}${dataQd(hashes)}>${content}</h${level}>`;\n });\n \n // Process blockquotes (must handle escaped > since we already escaped HTML)\n html = html.replace(/^&gt;\\s+(.+)$/gm, `<blockquote${getAttr('blockquote')}>$1</blockquote>`);\n // Merge consecutive blockquotes\n html = html.replace(/<\\/blockquote>\\n<blockquote>/g, '\\n');\n \n // Process horizontal rules (allow trailing spaces)\n html = html.replace(/^---+\\s*$/gm, `<hr${getAttr('hr')}>`);\n \n // Process lists\n html = processLists(html, getAttr, inline_styles, bidirectional);\n \n // Phase 3: Process inline elements\n \n // Images (must come before links, with URL sanitization)\n html = html.replace(/!\\[([^\\]]*)\\]\\(([^)]+)\\)/g, (match, alt, src) => {\n const sanitizedSrc = sanitizeUrl(src, options.allow_unsafe_urls);\n const altAttr = bidirectional && alt ? ` data-qd-alt=\"${escapeHtml(alt)}\"` : '';\n const srcAttr = bidirectional ? ` data-qd-src=\"${escapeHtml(src)}\"` : '';\n return `<img${getAttr('img')} src=\"${sanitizedSrc}\" alt=\"${alt}\"${altAttr}${srcAttr}${dataQd('!')}>`;\n });\n \n // Links (with URL sanitization)\n html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (match, text, href) => {\n // Sanitize URL to prevent XSS\n const sanitizedHref = sanitizeUrl(href, options.allow_unsafe_urls);\n const isExternal = /^https?:\\/\\//i.test(sanitizedHref);\n const rel = isExternal ? ' rel=\"noopener noreferrer\"' : '';\n const textAttr = bidirectional ? ` data-qd-text=\"${escapeHtml(text)}\"` : '';\n return `<a${getAttr('a')} href=\"${sanitizedHref}\"${rel}${textAttr}${dataQd('[')}>${text}</a>`;\n });\n \n // Autolinks - convert bare URLs to clickable links\n html = html.replace(/(^|\\s)(https?:\\/\\/[^\\s<]+)/g, (match, prefix, url) => {\n const sanitizedUrl = sanitizeUrl(url, options.allow_unsafe_urls);\n return `${prefix}<a${getAttr('a')} href=\"${sanitizedUrl}\" rel=\"noopener noreferrer\">${url}</a>`;\n });\n \n // Process inline formatting (bold, italic, strikethrough)\n const inlinePatterns = [\n [/\\*\\*(.+?)\\*\\*/g, 'strong', '**'],\n [/__(.+?)__/g, 'strong', '__'],\n [/(?<!\\*)\\*(?!\\*)(.+?)(?<!\\*)\\*(?!\\*)/g, 'em', '*'],\n [/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em', '_'],\n [/~~(.+?)~~/g, 'del', '~~']\n ];\n \n inlinePatterns.forEach(([pattern, tag, marker]) => {\n html = html.replace(pattern, `<${tag}${getAttr(tag)}${dataQd(marker)}>$1</${tag}>`);\n });\n \n // Line breaks\n if (lazy_linefeeds) {\n // Lazy linefeeds: single newline becomes <br> (except between paragraphs and after/before block elements)\n const blocks = [];\n let bi = 0;\n \n // Protect tables and lists \n html = html.replace(/<(table|[uo]l)[^>]*>[\\s\\S]*?<\\/\\1>/g, m => {\n blocks[bi] = m;\n return `§B${bi++}§`;\n });\n \n // Handle paragraphs and block elements\n html = html.replace(/\\n\\n+/g, '§P§')\n // After block elements\n .replace(/(<\\/(?:h[1-6]|blockquote|pre)>)\\n/g, '$1§N§')\n .replace(/(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)\\n/g, '$1§N§')\n // Before block elements \n .replace(/\\n(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)/g, '§N§$1')\n .replace(/\\n(§B\\d+§)/g, '§N§$1')\n .replace(/(§B\\d+§)\\n/g, '$1§N§')\n // Convert remaining newlines\n .replace(/\\n/g, `<br${getAttr('br')}>`)\n // Restore\n .replace(/§N§/g, '\\n')\n .replace(/§P§/g, '</p><p>');\n \n // Restore protected blocks\n blocks.forEach((b, i) => html = html.replace(`§B${i}§`, b));\n \n html = '<p>' + html + '</p>';\n } else {\n // Standard: two spaces at end of line for line breaks\n html = html.replace(/ $/gm, `<br${getAttr('br')}>`);\n \n // Paragraphs (double newlines)\n // Don't add </p> after block elements (they're not in paragraphs)\n html = html.replace(/\\n\\n+/g, (match, offset) => {\n // Check if we're after a block element closing tag\n const before = html.substring(0, offset);\n if (before.match(/<\\/(h[1-6]|blockquote|ul|ol|table|pre|hr)>$/)) {\n return '<p>'; // Just open a new paragraph\n }\n return '</p><p>'; // Normal paragraph break\n });\n html = '<p>' + html + '</p>';\n }\n \n // Clean up empty paragraphs and unwrap block elements\n const cleanupPatterns = [\n [/<p><\\/p>/g, ''],\n [/<p>(<h[1-6][^>]*>)/g, '$1'],\n [/(<\\/h[1-6]>)<\\/p>/g, '$1'],\n [/<p>(<blockquote[^>]*>)/g, '$1'],\n [/(<\\/blockquote>)<\\/p>/g, '$1'],\n [/<p>(<ul[^>]*>|<ol[^>]*>)/g, '$1'],\n [/(<\\/ul>|<\\/ol>)<\\/p>/g, '$1'],\n [/<p>(<hr[^>]*>)<\\/p>/g, '$1'],\n [/<p>(<table[^>]*>)/g, '$1'],\n [/(<\\/table>)<\\/p>/g, '$1'],\n [/<p>(<pre[^>]*>)/g, '$1'],\n [/(<\\/pre>)<\\/p>/g, '$1'],\n [new RegExp(`<p>(${PLACEHOLDER_CB}\\\\d+§)<\\/p>`, 'g'), '$1']\n ];\n \n cleanupPatterns.forEach(([pattern, replacement]) => {\n html = html.replace(pattern, replacement);\n });\n \n // Fix orphaned closing </p> tags after block elements\n // When a paragraph follows a block element, ensure it has opening <p>\n html = html.replace(/(<\\/(?:h[1-6]|blockquote|ul|ol|table|pre|hr)>)\\n([^<])/g, '$1\\n<p>$2');\n \n // Phase 4: Restore code blocks and inline code\n \n // Restore code blocks\n codeBlocks.forEach((block, i) => {\n let replacement;\n \n if (block.custom && fence_plugin && fence_plugin.render) {\n // Use custom fence plugin (v1.1.0: object format with render function)\n replacement = fence_plugin.render(block.code, block.lang);\n \n // If plugin returns undefined, fall back to default rendering\n if (replacement === undefined) {\n const langClass = !inline_styles && block.lang ? ` class=\"language-${block.lang}\"` : '';\n const codeAttr = inline_styles ? getAttr('code') : langClass;\n const langAttr = bidirectional && block.lang ? ` data-qd-lang=\"${escapeHtml(block.lang)}\"` : '';\n const fenceAttr = bidirectional ? ` data-qd-fence=\"${escapeHtml(block.fence)}\"` : '';\n replacement = `<pre${getAttr('pre')}${fenceAttr}${langAttr}><code${codeAttr}>${escapeHtml(block.code)}</code></pre>`;\n } else if (bidirectional) {\n // If bidirectional and plugin provided HTML, add data attributes for roundtrip\n replacement = replacement.replace(/^<(\\w+)/, \n `<$1 data-qd-fence=\"${escapeHtml(block.fence)}\" data-qd-lang=\"${escapeHtml(block.lang)}\" data-qd-source=\"${escapeHtml(block.code)}\"`);\n }\n } else {\n // Default rendering\n const langClass = !inline_styles && block.lang ? ` class=\"language-${block.lang}\"` : '';\n const codeAttr = inline_styles ? getAttr('code') : langClass;\n const langAttr = bidirectional && block.lang ? ` data-qd-lang=\"${escapeHtml(block.lang)}\"` : '';\n const fenceAttr = bidirectional ? ` data-qd-fence=\"${escapeHtml(block.fence)}\"` : '';\n replacement = `<pre${getAttr('pre')}${fenceAttr}${langAttr}><code${codeAttr}>${block.code}</code></pre>`;\n }\n \n const placeholder = `${PLACEHOLDER_CB}${i}§`;\n html = html.replace(placeholder, replacement);\n });\n \n // Restore inline code\n inlineCodes.forEach((code, i) => {\n const placeholder = `${PLACEHOLDER_IC}${i}§`;\n html = html.replace(placeholder, `<code${getAttr('code')}${dataQd('`')}>${code}</code>`);\n });\n \n return html.trim();\n}\n\n/**\n * Process inline markdown formatting\n */\nfunction processInlineMarkdown(text, getAttr) {\n \n // Process inline formatting patterns\n const patterns = [\n [/\\*\\*(.+?)\\*\\*/g, 'strong'],\n [/__(.+?)__/g, 'strong'],\n [/(?<!\\*)\\*(?!\\*)(.+?)(?<!\\*)\\*(?!\\*)/g, 'em'],\n [/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em'],\n [/~~(.+?)~~/g, 'del'],\n [/`([^`]+)`/g, 'code']\n ];\n \n patterns.forEach(([pattern, tag]) => {\n text = text.replace(pattern, `<${tag}${getAttr(tag)}>$1</${tag}>`);\n });\n \n return text;\n}\n\n/**\n * Process markdown tables\n */\nfunction processTable(text, getAttr) {\n const lines = text.split('\\n');\n const result = [];\n let inTable = false;\n let tableLines = [];\n \n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n \n // Check if this line looks like a table row (with or without trailing |)\n if (line.includes('|') && (line.startsWith('|') || /[^\\\\|]/.test(line))) {\n if (!inTable) {\n inTable = true;\n tableLines = [];\n }\n tableLines.push(line);\n } else {\n // Not a table line\n if (inTable) {\n // Process the accumulated table\n const tableHtml = buildTable(tableLines, getAttr);\n if (tableHtml) {\n result.push(tableHtml);\n } else {\n // Not a valid table, restore original lines\n result.push(...tableLines);\n }\n inTable = false;\n tableLines = [];\n }\n result.push(lines[i]);\n }\n }\n \n // Handle table at end of text\n if (inTable && tableLines.length > 0) {\n const tableHtml = buildTable(tableLines, getAttr);\n if (tableHtml) {\n result.push(tableHtml);\n } else {\n result.push(...tableLines);\n }\n }\n \n return result.join('\\n');\n}\n\n/**\n * Build an HTML table from markdown table lines\n */\nfunction buildTable(lines, getAttr) {\n \n if (lines.length < 2) return null;\n \n // Check for separator line (second line should be the separator)\n let separatorIndex = -1;\n for (let i = 1; i < lines.length; i++) {\n // Support separator with or without leading/trailing pipes\n if (/^\\|?[\\s\\-:|]+\\|?$/.test(lines[i]) && lines[i].includes('-')) {\n separatorIndex = i;\n break;\n }\n }\n \n if (separatorIndex === -1) return null;\n \n const headerLines = lines.slice(0, separatorIndex);\n const bodyLines = lines.slice(separatorIndex + 1);\n \n // Parse alignment from separator\n const separator = lines[separatorIndex];\n // Handle pipes at start/end or not\n const separatorCells = separator.trim().replace(/^\\|/, '').replace(/\\|$/, '').split('|');\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 let html = `<table${getAttr('table')}>\\n`;\n \n // Build header\n // Note: headerLines will always have length > 0 since separatorIndex starts from 1\n html += `<thead${getAttr('thead')}>\\n`;\n headerLines.forEach(line => {\n html += `<tr${getAttr('tr')}>\\n`;\n // Handle pipes at start/end or not\n const cells = line.trim().replace(/^\\|/, '').replace(/\\|$/, '').split('|');\n cells.forEach((cell, i) => {\n const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';\n const processedCell = processInlineMarkdown(cell.trim(), getAttr);\n html += `<th${getAttr('th', alignStyle)}>${processedCell}</th>\\n`;\n });\n html += '</tr>\\n';\n });\n html += '</thead>\\n';\n \n // Build body\n if (bodyLines.length > 0) {\n html += `<tbody${getAttr('tbody')}>\\n`;\n bodyLines.forEach(line => {\n html += `<tr${getAttr('tr')}>\\n`;\n // Handle pipes at start/end or not\n const cells = line.trim().replace(/^\\|/, '').replace(/\\|$/, '').split('|');\n cells.forEach((cell, i) => {\n const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';\n const processedCell = processInlineMarkdown(cell.trim(), getAttr);\n html += `<td${getAttr('td', alignStyle)}>${processedCell}</td>\\n`;\n });\n html += '</tr>\\n';\n });\n html += '</tbody>\\n';\n }\n \n html += '</table>';\n return html;\n}\n\n/**\n * Process markdown lists (ordered and unordered)\n */\nfunction processLists(text, getAttr, inline_styles, bidirectional) {\n \n const lines = text.split('\\n');\n const result = [];\n let listStack = []; // Track nested lists\n \n // Helper to escape HTML for data-qd attributes\n const escapeHtml = (text) => text.replace(/[&<>\"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'})[m]);\n const dataQd = bidirectional ? (marker) => ` data-qd=\"${escapeHtml(marker)}\"` : () => '';\n \n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const match = line.match(/^(\\s*)([*\\-+]|\\d+\\.)\\s+(.+)$/);\n \n if (match) {\n const [, indent, marker, content] = match;\n const level = Math.floor(indent.length / 2);\n const isOrdered = /^\\d+\\./.test(marker);\n const listType = isOrdered ? 'ol' : 'ul';\n \n // Check for task list items\n let listItemContent = content;\n let taskListClass = '';\n const taskMatch = content.match(/^\\[([x ])\\]\\s+(.*)$/i);\n if (taskMatch && !isOrdered) {\n const [, checked, taskContent] = taskMatch;\n const isChecked = checked.toLowerCase() === 'x';\n const checkboxAttr = inline_styles \n ? ' style=\"margin-right:.5em\"' \n : ` class=\"${CLASS_PREFIX}task-checkbox\"`;\n listItemContent = `<input type=\"checkbox\"${checkboxAttr}${isChecked ? ' checked' : ''} disabled> ${taskContent}`;\n taskListClass = inline_styles ? ' style=\"list-style:none\"' : ` class=\"${CLASS_PREFIX}task-item\"`;\n }\n \n // Close deeper levels\n while (listStack.length > level + 1) {\n const list = listStack.pop();\n result.push(`</${list.type}>`);\n }\n \n // Open new level if needed\n if (listStack.length === level) {\n // Need to open a new list\n listStack.push({ type: listType, level });\n result.push(`<${listType}${getAttr(listType)}>`);\n } else if (listStack.length === level + 1) {\n // Check if we need to switch list type\n const currentList = listStack[listStack.length - 1];\n if (currentList.type !== listType) {\n result.push(`</${currentList.type}>`);\n listStack.pop();\n listStack.push({ type: listType, level });\n result.push(`<${listType}${getAttr(listType)}>`);\n }\n }\n \n const liAttr = taskListClass || getAttr('li');\n result.push(`<li${liAttr}${dataQd(marker)}>${listItemContent}</li>`);\n } else {\n // Not a list item, close all lists\n while (listStack.length > 0) {\n const list = listStack.pop();\n result.push(`</${list.type}>`);\n }\n result.push(line);\n }\n }\n \n // Close any remaining lists\n while (listStack.length > 0) {\n const list = listStack.pop();\n result.push(`</${list.type}>`);\n }\n \n return result.join('\\n');\n}\n\n/**\n * Emit CSS styles for quikdown elements\n * @param {string} prefix - Optional class prefix (default: 'quikdown-')\n * @param {string} theme - Optional theme: 'light' (default) or 'dark'\n * @returns {string} CSS string with quikdown styles\n */\nquikdown.emitStyles = function(prefix = 'quikdown-', theme = 'light') {\n const styles = QUIKDOWN_STYLES;\n \n // Define theme color overrides\n const themeOverrides = {\n dark: {\n '#f4f4f4': '#2a2a2a', // pre background\n '#f0f0f0': '#2a2a2a', // code background\n '#f2f2f2': '#2a2a2a', // th background\n '#ddd': '#3a3a3a', // borders\n '#06c': '#6db3f2', // links\n _textColor: '#e0e0e0'\n },\n light: {\n _textColor: '#333' // Explicit text color for light theme\n }\n };\n \n let css = '';\n for (const [tag, style] of Object.entries(styles)) {\n let themedStyle = style;\n \n // Apply theme overrides if dark theme\n if (theme === 'dark' && themeOverrides.dark) {\n // Replace colors\n for (const [oldColor, newColor] of Object.entries(themeOverrides.dark)) {\n if (!oldColor.startsWith('_')) {\n themedStyle = themedStyle.replace(new RegExp(oldColor, 'g'), newColor);\n }\n }\n \n // Add text color for certain elements in dark theme\n const needsTextColor = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'li', 'blockquote'];\n if (needsTextColor.includes(tag)) {\n themedStyle += `;color:${themeOverrides.dark._textColor}`;\n }\n } else if (theme === 'light' && themeOverrides.light) {\n // Add explicit text color for light theme elements too\n const needsTextColor = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'li', 'blockquote'];\n if (needsTextColor.includes(tag)) {\n themedStyle += `;color:${themeOverrides.light._textColor}`;\n }\n }\n \n css += `.${prefix}${tag} { ${themedStyle} }\\n`;\n }\n \n return css;\n};\n\n/**\n * Configure quikdown with options and return a function\n * @param {Object} options - Configuration options\n * @returns {Function} Configured quikdown function\n */\nquikdown.configure = function(options) {\n return function(markdown) {\n return quikdown(markdown, options);\n };\n};\n\n/**\n * Version information\n */\nquikdown.version = quikdownVersion;\n\n// Export for both CommonJS and ES6\n/* istanbul ignore next */\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = quikdown;\n}\n\n// For browser global\n/* istanbul ignore next */\nif (typeof window !== 'undefined') {\n window.quikdown = quikdown;\n}\n\nexport default quikdown;"],"names":["CLASS_PREFIX","PLACEHOLDER_CB","ESC_MAP","QUIKDOWN_STYLES","h1","h2","h3","h4","h5","h6","pre","code","blockquote","table","th","td","hr","img","a","strong","em","del","ul","ol","li","quikdown","markdown","options","fence_plugin","inline_styles","bidirectional","lazy_linefeeds","getAttr","styles","tag","additionalStyle","style","includes","replace","trim","endsWith","classAttr","createGetAttr","escapeHtml","text","m","dataQd","marker","sanitizeUrl","url","allowUnsafe","trimmedUrl","lowerUrl","toLowerCase","dangerousProtocols","protocol","startsWith","html","codeBlocks","inlineCodes","match","fence","lang","placeholder","length","langTrimmed","render","push","trimEnd","custom","hasReverse","reverse","lines","split","result","inTable","tableLines","i","line","test","tableHtml","buildTable","join","processTable","hashes","content","level","listStack","indent","Math","floor","isOrdered","listType","listItemContent","taskListClass","taskMatch","checked","taskContent","isChecked","list","pop","type","currentList","liAttr","processLists","alt","src","sanitizedSrc","allow_unsafe_urls","altAttr","srcAttr","href","sanitizedHref","rel","textAttr","prefix","sanitizedUrl","forEach","pattern","blocks","bi","b","offset","substring","RegExp","replacement","block","undefined","langClass","codeAttr","langAttr","fenceAttr","processInlineMarkdown","separatorIndex","headerLines","slice","bodyLines","alignments","map","cell","trimmed","alignStyle","processedCell","emitStyles","theme","themeOverrides","_textColor","css","Object","entries","themedStyle","oldColor","newColor","configure","version","module","exports","window"],"mappings":"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"}