quikdown 1.0.5 → 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.
package/README.md CHANGED
@@ -9,9 +9,9 @@ Quikdown is a small, secure markdown parser with bidirectional conversion. Zero
9
9
 
10
10
  For small and fast projects quikdown includes built-in inline styles for a "batteries included" rendering experience, but these can be overridden with themed css (see light and dark examples).
11
11
 
12
- - **quikdown.js** (8.7KB) - Markdown to HTML Parser
13
- - **quikdown_bd.js** (13KB) - Bidirectional (HTML ↔ Markdown) Parser
14
- - **quikdown_edit.js** (36KB) - Drop-in editor component (HTML ↔ Markdown) with md/split/html views
12
+ - **quikdown.js** (9.0KB) - Markdown to HTML Parser
13
+ - **quikdown_bd.js** (13.8KB) - Bidirectional (HTML ↔ Markdown) Parser
14
+ - **quikdown_edit.js** (37.8KB) - Drop-in editor component (HTML ↔ Markdown) with md/split/html views
15
15
 
16
16
  🚀 **[Live Demo](https://deftio.github.io/quikdown/examples/quikdown-live.html)** | **[Editor Demo](https://deftio.github.io/quikdown/examples/qde/)** | **[Documentation](docs/)**
17
17
 
@@ -21,7 +21,7 @@ For small and fast projects quikdown includes built-in inline styles for a "batt
21
21
 
22
22
  - 📦 **Zero dependencies** - No external libraries required
23
23
  - 🌐 **Universal** - Works in browsers and Node.js
24
- - 🚀 **Lightweight** - 8.7KB (core), 13KB (bidirectional), 36KB (editor)
24
+ - 🚀 **Lightweight** - 9.0KB (core), 13.8KB (bidirectional), 37.8KB (editor)
25
25
  - 🔒 **Secure by default** - Built-in XSS protection with URL sanitization
26
26
  - 🎨 **Flexible styling** - Inline styles or CSS classes with theme support
27
27
  - 🔌 **Plugin system** - Extensible fence block handlers
@@ -108,7 +108,9 @@ quikdown supports built-in styles for a "batteries included" experience or you c
108
108
  const html = quikdown(markdown, {
109
109
  lazy_linefeeds: true, // Single newlines become <br>
110
110
  inline_styles: false, // Use class based CSS instead of inline styles
111
- fence_plugin: myHandler // Custom code block processor
111
+ fence_plugin: { // Custom code block processor (v1.1.0+ API)
112
+ render: myHandler // Function to render fence blocks
113
+ }
112
114
  });
113
115
  ```
114
116
 
@@ -135,18 +137,19 @@ Quikdown provides a callback for all fenced text such as code blocks, math, svg
135
137
  Handle code blocks with custom languages:
136
138
 
137
139
  ```javascript
138
- function fencePlugin(code, language) {
139
- if (language === 'mermaid') {
140
- // Process with mermaid library and return rendered diagram
141
- const id = 'mermaid-' + Math.random().toString(36).substr(2, 9);
142
- setTimeout(() => mermaid.render(id + '-svg', code).then(result => {
143
- document.getElementById(id).innerHTML = result.svg;
144
- }), 0);
145
- return `<div id="${id}" class="mermaid">Loading diagram...</div>`;
140
+ const fencePlugin = {
141
+ render: (code, language) => {
142
+ if (language === 'mermaid') {
143
+ // Process with mermaid library and return rendered diagram
144
+ const id = 'mermaid-' + Math.random().toString(36).substr(2, 9);
145
+ setTimeout(() => mermaid.render(id + '-svg', code).then(result => {
146
+ document.getElementById(id).innerHTML = result.svg;
147
+ }), 0);
148
+ return `<div id="${id}" class="mermaid">Loading diagram...</div>`;
149
+ }
150
+ // Return undefined for default handling
146
151
  }
147
- return `<pre>${code}</pre>`;
148
- // Return undefined for default handling
149
- }
152
+ };
150
153
 
151
154
  const html = quikdown(markdown, { fence_plugin: fencePlugin });
152
155
  ```
@@ -156,18 +159,21 @@ const html = quikdown(markdown, { fence_plugin: fencePlugin });
156
159
 
157
160
  quikdown includes TypeScript definitions for better IDE support and type safety:
158
161
 
159
- ``` javascript
160
- import quikdown, { QuikdownOptions } from 'quikdown';
162
+ ```typescript
163
+ import quikdown, { QuikdownOptions, FencePlugin } from 'quikdown';
164
+
165
+ const fencePlugin: FencePlugin = {
166
+ render: (content: string, language: string) => {
167
+ return `<pre class="hljs ${language}">${content}</pre>`;
168
+ }
169
+ };
161
170
 
162
171
  const options: QuikdownOptions = {
163
- inline_styles: true,
164
- fence_plugin: (content: string, language: string) => {
165
- return `<pre class="hljs ${language}">${content}</pre>`;
166
- }
172
+ inline_styles: true,
173
+ fence_plugin: fencePlugin
167
174
  };
168
175
 
169
176
  const html: string = quikdown(markdown, options);
170
-
171
177
  ```
172
178
 
173
179
  ## Supported Markdown
package/dist/quikdown.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * quikdown - Lightweight Markdown Parser
3
- * @version 1.0.5
3
+ * @version 1.1.0
4
4
  * @license BSD-2-Clause
5
5
  * @copyright DeftIO 2025
6
6
  */
@@ -20,7 +20,7 @@
20
20
  */
21
21
 
22
22
  // Version will be injected at build time
23
- const quikdownVersion = '1.0.5';
23
+ const quikdownVersion = '1.1.0';
24
24
 
25
25
  // Constants for reuse
26
26
  const CLASS_PREFIX = 'quikdown-';
@@ -146,13 +146,14 @@ function quikdown(markdown, options = {}) {
146
146
  // Trim the language specification
147
147
  const langTrimmed = lang ? lang.trim() : '';
148
148
 
149
- // If custom fence plugin is provided, use it
150
- if (fence_plugin && typeof fence_plugin === 'function') {
149
+ // If custom fence plugin is provided, use it (v1.1.0: object format required)
150
+ if (fence_plugin && fence_plugin.render && typeof fence_plugin.render === 'function') {
151
151
  codeBlocks.push({
152
152
  lang: langTrimmed,
153
153
  code: code.trimEnd(),
154
154
  custom: true,
155
- fence: fence
155
+ fence: fence,
156
+ hasReverse: !!fence_plugin.reverse
156
157
  });
157
158
  } else {
158
159
  codeBlocks.push({
@@ -191,8 +192,8 @@ function quikdown(markdown, options = {}) {
191
192
  // Merge consecutive blockquotes
192
193
  html = html.replace(/<\/blockquote>\n<blockquote>/g, '\n');
193
194
 
194
- // Process horizontal rules
195
- html = html.replace(/^---+$/gm, `<hr${getAttr('hr')}>`);
195
+ // Process horizontal rules (allow trailing spaces)
196
+ html = html.replace(/^---+\s*$/gm, `<hr${getAttr('hr')}>`);
196
197
 
197
198
  // Process lists
198
199
  html = processLists(html, getAttr, inline_styles, bidirectional);
@@ -272,7 +273,15 @@ function quikdown(markdown, options = {}) {
272
273
  html = html.replace(/ $/gm, `<br${getAttr('br')}>`);
273
274
 
274
275
  // Paragraphs (double newlines)
275
- html = html.replace(/\n\n+/g, '</p><p>');
276
+ // Don't add </p> after block elements (they're not in paragraphs)
277
+ html = html.replace(/\n\n+/g, (match, offset) => {
278
+ // Check if we're after a block element closing tag
279
+ const before = html.substring(0, offset);
280
+ if (before.match(/<\/(h[1-6]|blockquote|ul|ol|table|pre|hr)>$/)) {
281
+ return '<p>'; // Just open a new paragraph
282
+ }
283
+ return '</p><p>'; // Normal paragraph break
284
+ });
276
285
  html = '<p>' + html + '</p>';
277
286
  }
278
287
 
@@ -297,15 +306,20 @@ function quikdown(markdown, options = {}) {
297
306
  html = html.replace(pattern, replacement);
298
307
  });
299
308
 
309
+ // Fix orphaned closing </p> tags after block elements
310
+ // When a paragraph follows a block element, ensure it has opening <p>
311
+ html = html.replace(/(<\/(?:h[1-6]|blockquote|ul|ol|table|pre|hr)>)\n([^<])/g, '$1\n<p>$2');
312
+
300
313
  // Phase 4: Restore code blocks and inline code
301
314
 
302
315
  // Restore code blocks
303
316
  codeBlocks.forEach((block, i) => {
304
317
  let replacement;
305
318
 
306
- if (block.custom && fence_plugin) {
307
- // Use custom fence plugin
308
- replacement = fence_plugin(block.code, block.lang);
319
+ if (block.custom && fence_plugin && fence_plugin.render) {
320
+ // Use custom fence plugin (v1.1.0: object format with render function)
321
+ replacement = fence_plugin.render(block.code, block.lang);
322
+
309
323
  // If plugin returns undefined, fall back to default rendering
310
324
  if (replacement === undefined) {
311
325
  const langClass = !inline_styles && block.lang ? ` class="language-${block.lang}"` : '';
@@ -313,6 +327,10 @@ function quikdown(markdown, options = {}) {
313
327
  const langAttr = bidirectional && block.lang ? ` data-qd-lang="${escapeHtml(block.lang)}"` : '';
314
328
  const fenceAttr = bidirectional ? ` data-qd-fence="${escapeHtml(block.fence)}"` : '';
315
329
  replacement = `<pre${getAttr('pre')}${fenceAttr}${langAttr}><code${codeAttr}>${escapeHtml(block.code)}</code></pre>`;
330
+ } else if (bidirectional) {
331
+ // If bidirectional and plugin provided HTML, add data attributes for roundtrip
332
+ replacement = replacement.replace(/^<(\w+)/,
333
+ `<$1 data-qd-fence="${escapeHtml(block.fence)}" data-qd-lang="${escapeHtml(block.lang)}" data-qd-source="${escapeHtml(block.code)}"`);
316
334
  }
317
335
  } else {
318
336
  // Default rendering
@@ -5,17 +5,38 @@
5
5
 
6
6
  declare module 'quikdown' {
7
7
  /**
8
- * Options for configuring the quikdown parser
8
+ * Fence plugin for custom code block rendering (v1.1.0+)
9
9
  */
10
- export interface QuikdownOptions {
10
+ export interface FencePlugin {
11
11
  /**
12
- * Custom renderer for fenced code blocks.
13
- * Return undefined to use default rendering.
12
+ * Render markdown fence to HTML
14
13
  * @param content - The code block content (unescaped)
15
14
  * @param language - The language identifier (or empty string)
16
15
  * @returns HTML string or undefined for default rendering
17
16
  */
18
- fence_plugin?: (content: string, language: string) => string | undefined;
17
+ render: (content: string, language: string) => string | undefined;
18
+
19
+ /**
20
+ * Convert HTML element back to markdown fence (optional)
21
+ * @param element - The HTML element to convert
22
+ * @returns Fence details or null to use default
23
+ */
24
+ reverse?: (element: HTMLElement) => {
25
+ fence: string;
26
+ lang: string;
27
+ content: string;
28
+ } | null;
29
+ }
30
+
31
+ /**
32
+ * Options for configuring the quikdown parser
33
+ */
34
+ export interface QuikdownOptions {
35
+ /**
36
+ * Custom renderer for fenced code blocks (v1.1.0: object format required)
37
+ * @since 1.1.0 - Must be an object with render function
38
+ */
39
+ fence_plugin?: FencePlugin;
19
40
 
20
41
  /**
21
42
  * If true, uses inline styles instead of CSS classes.
@@ -5,7 +5,7 @@
5
5
  * Theme with container-based scoping.
6
6
  * Usage: <div class="quikdown-dark">...content...</div>
7
7
  *
8
- * @version 1.0.5
8
+ * @version 1.1.0
9
9
  * @source tools/generateThemeCSS.js
10
10
  */
11
11
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * quikdown - Lightweight Markdown Parser
3
- * @version 1.0.5
3
+ * @version 1.1.0
4
4
  * @license BSD-2-Clause
5
5
  * @copyright DeftIO 2025
6
6
  */
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  // Version will be injected at build time
21
- const quikdownVersion = '1.0.5';
21
+ const quikdownVersion = '1.1.0';
22
22
 
23
23
  // Constants for reuse
24
24
  const CLASS_PREFIX = 'quikdown-';
@@ -144,13 +144,14 @@ function quikdown(markdown, options = {}) {
144
144
  // Trim the language specification
145
145
  const langTrimmed = lang ? lang.trim() : '';
146
146
 
147
- // If custom fence plugin is provided, use it
148
- 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') {
149
149
  codeBlocks.push({
150
150
  lang: langTrimmed,
151
151
  code: code.trimEnd(),
152
152
  custom: true,
153
- fence: fence
153
+ fence: fence,
154
+ hasReverse: !!fence_plugin.reverse
154
155
  });
155
156
  } else {
156
157
  codeBlocks.push({
@@ -189,8 +190,8 @@ function quikdown(markdown, options = {}) {
189
190
  // Merge consecutive blockquotes
190
191
  html = html.replace(/<\/blockquote>\n<blockquote>/g, '\n');
191
192
 
192
- // Process horizontal rules
193
- html = html.replace(/^---+$/gm, `<hr${getAttr('hr')}>`);
193
+ // Process horizontal rules (allow trailing spaces)
194
+ html = html.replace(/^---+\s*$/gm, `<hr${getAttr('hr')}>`);
194
195
 
195
196
  // Process lists
196
197
  html = processLists(html, getAttr, inline_styles, bidirectional);
@@ -270,7 +271,15 @@ function quikdown(markdown, options = {}) {
270
271
  html = html.replace(/ $/gm, `<br${getAttr('br')}>`);
271
272
 
272
273
  // Paragraphs (double newlines)
273
- html = html.replace(/\n\n+/g, '</p><p>');
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
+ });
274
283
  html = '<p>' + html + '</p>';
275
284
  }
276
285
 
@@ -295,15 +304,20 @@ function quikdown(markdown, options = {}) {
295
304
  html = html.replace(pattern, replacement);
296
305
  });
297
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
+
298
311
  // Phase 4: Restore code blocks and inline code
299
312
 
300
313
  // Restore code blocks
301
314
  codeBlocks.forEach((block, i) => {
302
315
  let replacement;
303
316
 
304
- if (block.custom && fence_plugin) {
305
- // Use custom fence plugin
306
- 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
+
307
321
  // If plugin returns undefined, fall back to default rendering
308
322
  if (replacement === undefined) {
309
323
  const langClass = !inline_styles && block.lang ? ` class="language-${block.lang}"` : '';
@@ -311,6 +325,10 @@ function quikdown(markdown, options = {}) {
311
325
  const langAttr = bidirectional && block.lang ? ` data-qd-lang="${escapeHtml(block.lang)}"` : '';
312
326
  const fenceAttr = bidirectional ? ` data-qd-fence="${escapeHtml(block.fence)}"` : '';
313
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)}"`);
314
332
  }
315
333
  } else {
316
334
  // Default rendering
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * quikdown - Lightweight Markdown Parser
3
- * @version 1.0.5
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;"},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,g=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 d(e){return e.replace(/[&<>"']/g,e=>n[e])}const $=i?e=>` data-qd="${d(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&&"function"==typeof c?u.push({lang:a,code:o.trimEnd(),custom:!0,fence:n}):u.push({lang:a,code:d(o.trimEnd()),custom:!1,fence:n}),l}),h=h.replace(/`([^`]+)`/g,(e,t)=>{const n=`§IC${m.length}§`;return m.push(d(t)),n}),h=d(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,g),h=h.replace(/^(#{1,6})\s+(.+?)\s*#*$/gm,(e,t,n)=>{const r=t.length;return`<h${r}${g("h"+r)}${$(t)}>${n}</h${r}>`}),h=h.replace(/^&gt;\s+(.+)$/gm,`<blockquote${g("blockquote")}>$1</blockquote>`),h=h.replace(/<\/blockquote>\n<blockquote>/g,"\n"),h=h.replace(/^---+$/gm,`<hr${g("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),g=/^\d+\./.test(o),d=g?"ol":"ul";let $=l,f="";const h=l.match(/^\[([x ])\]\s+(.*)$/i);if(h&&!g){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:d,level:p}),a.push(`<${d}${n(d)}>`);else if(c.length===p+1){const e=c[c.length-1];e.type!==d&&(a.push(`</${e.type}>`),c.pop(),c.push({type:d,level:p}),a.push(`<${d}${n(d)}>`))}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,g,s,i),h=h.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(e,t,n)=>{const r=f(n,l.allow_unsafe_urls),o=i&&t?` data-qd-alt="${d(t)}"`:"",a=i?` data-qd-src="${d(n)}"`:"";return`<img${g("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="${d(t)}"`:"";return`<a${g("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${g("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}${g(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${g("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${g("br")}>`),h=h.replace(/\n\n+/g,"</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)}),u.forEach((e,n)=>{let r;if(e.custom&&c){if(r=c(e.code,e.lang),void 0===r){const t=!s&&e.lang?` class="language-${e.lang}"`:"",n=s?g("code"):t,o=i&&e.lang?` data-qd-lang="${d(e.lang)}"`:"",l=i?` data-qd-fence="${d(e.fence)}"`:"";r=`<pre${g("pre")}${l}${o}><code${n}>${d(e.code)}</code></pre>`}}else{const t=!s&&e.lang?` class="language-${e.lang}"`:"",n=s?g("code"):t,o=i&&e.lang?` data-qd-lang="${d(e.lang)}"`:"",l=i?` data-qd-fence="${d(e.fence)}"`:"";r=`<pre${g("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${g("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.0.5","undefined"!=typeof module&&module.exports&&(module.exports=o),"undefined"!=typeof window&&(window.quikdown=o);export{o 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 * @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\n if (fence_plugin && typeof fence_plugin === 'function') {\n codeBlocks.push({\n lang: langTrimmed,\n code: code.trimEnd(),\n custom: true,\n fence: fence\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\n html = html.replace(/^---+$/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 html = html.replace(/\\n\\n+/g, '</p><p>');\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 // 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 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 }\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","push","trimEnd","custom","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","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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"}
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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"}