quikdown 1.0.2 → 1.0.4
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 +269 -26
- package/dist/quikdown-lex.cjs +810 -0
- package/dist/quikdown-lex.esm.js +808 -0
- package/dist/quikdown-lex.esm.min.js +8 -0
- package/dist/quikdown-lex.esm.min.js.map +1 -0
- package/dist/quikdown-lex.umd.js +816 -0
- package/dist/quikdown-lex.umd.min.js +8 -0
- package/dist/quikdown-lex.umd.min.js.map +1 -0
- package/dist/quikdown.cjs +164 -169
- package/dist/quikdown.d.ts +70 -0
- package/dist/quikdown.dark.css +115 -56
- package/dist/quikdown.dark.min.css +2 -0
- package/dist/quikdown.esm.js +164 -169
- package/dist/quikdown.esm.min.js +2 -2
- package/dist/quikdown.esm.min.js.map +1 -1
- package/dist/quikdown.light.css +84 -52
- package/dist/quikdown.light.min.css +2 -0
- package/dist/quikdown.umd.js +164 -169
- package/dist/quikdown.umd.min.js +2 -2
- package/dist/quikdown.umd.min.js.map +1 -1
- package/dist/quikdown_bd.cjs +677 -0
- package/dist/quikdown_bd.d.ts +80 -0
- package/dist/quikdown_bd.esm.js +675 -0
- package/dist/quikdown_bd.esm.min.js +8 -0
- package/dist/quikdown_bd.esm.min.js.map +1 -0
- package/dist/quikdown_bd.umd.js +683 -0
- package/dist/quikdown_bd.umd.min.js +8 -0
- package/dist/quikdown_bd.umd.min.js.map +1 -0
- package/package.json +27 -4
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quikdown-lex - Lightweight Markdown Parser (Lexer Implementation)
|
|
3
|
+
* @version 1.0.3dev4
|
|
4
|
+
* @license BSD-2-Clause
|
|
5
|
+
* @copyright DeftIO 2025
|
|
6
|
+
*/
|
|
7
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).quikdown=t()}(this,function(){"use strict";const e={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"},t={"&":"&","<":"<",">":">",'"':""","'":"'"};function n(s,r={}){if(!s||"string"!=typeof s)return"";const o={inline_styles:r.inline_styles||!1,class_prefix:r.class_prefix||"quikdown-",allow_unsafe_urls:r.allow_unsafe_urls||!1,fence_plugin:r.fence_plugin||null},i=s.split("\n"),l=[];let a=0,c=null,f=[],u=[];const h=(t,n="")=>{if(o.inline_styles){const s=e[t]||"",r=n?s?`${s};${n}`:n:s;return r?` style="${r}"`:""}return` class="${o.class_prefix}${t}"`},p=e=>e.replace(/[&<>"']/g,e=>t[e]),d=e=>{if(!e)return"";if(o.allow_unsafe_urls)return e;const t=e.trim(),n=t.toLowerCase();return/^(javascript|vbscript|data):/i.test(n)?/^data:image\//i.test(n)?t:"#":t},g=e=>{if(!e)return"";const t=[];return e=e.replace(/`([^`]+)`/g,(e,n)=>(t.push(p(n)),`${t.length-1}`)),e=(e=(e=(e=(e=(e=(e=(e=(e=(e=(e=p(e)).replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(e,t,n)=>`<img${h("img")} src="${d(n)}" alt="${t}">`)).replace(/\[([^\]]+)\]\(([^)]+)\)/g,(e,t,n)=>{const s=d(n),r=/^https?:\/\//i.test(s)?' rel="noopener noreferrer"':"";return`<a${h("a")} href="${s}"${r}>${t}</a>`})).replace(/(^|\s)(https?:\/\/[^\s<]+)/g,(e,t,n)=>`${t}<a${h("a")} href="${d(n)}" rel="noopener noreferrer">${n}</a>`)).replace(/\*\*(.+?)\*\*/g,`<strong${h("strong")}>$1</strong>`)).replace(/__(.+?)__/g,`<strong${h("strong")}>$1</strong>`)).replace(/(?<!\*)\*(?!\*)([^*]+)\*(?!\*)/g,`<em${h("em")}>$1</em>`)).replace(/(?<!_)_(?!_)([^_]+)_(?!_)/g,`<em${h("em")}>$1</em>`)).replace(/~~(.+?)~~/g,`<del${h("del")}>$1</del>`)).replace(/ $/gm,`<br${h("br")}>`)).replace(/\x01(\d+)\x02/g,(e,n)=>`<code${h("code")}>${t[n]}</code>`)},m=e=>{const t=e.trim();if(!t)return 0;switch(t[0]){case"#":if(/^#{1,6}\s+/.test(t))return 1;break;case"-":case"*":case"_":if(/^[-*_](\s*[-*_]){2,}$/.test(t))return 2;if(/^[*+-]\s+/.test(t))return 5;break;case"+":if(/^\+\s+/.test(t))return 5;break;case"`":case"~":if(/^[`~]{3,}/.test(t))return 3;break;case">":return 4;case"|":return/^\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?$/.test(t)?8:7;default:if(/^\d+\.\s+/.test(t))return 6;if(t.includes("|"))return/^\s*:?-+:?\s*(\|\s*:?-+:?\s*)+\s*$/.test(t)?8:7}return/^\s+[*+-]\s+/.test(e)?5:/^\s+\d+\.\s+/.test(e)?6:9},$=()=>{if(f.length>0){const e=f.join("\n");l.push(`<p>${g(e)}</p>`),f=[]}},b=()=>{if(u.length>0){const e=u.join("\n").trim();if(1!==u.length||e.includes("\n"))if(2!==u.length||""!==u[1]||u[0].includes("\n")){const t=u.filter(e=>""!==e);if(0===t.length);else if(2===t.length&&t.every(e=>!e.includes("\n")&&e.trim().length>0))l.push(`<blockquote${h("blockquote")}>${g(t[0])}</blockquote>`),l.push(`<blockquote${h("blockquote")}>${g(t[1])}</blockquote>`);else{const t=n(e,o);l.push(`<blockquote${h("blockquote")}>${t}</blockquote>`)}}else l.push(`<blockquote${h("blockquote")}>${g(u[0])}</blockquote>`);else l.push(`<blockquote${h("blockquote")}>${g(e)}</blockquote>`);u=[]}},k=e=>{const t=[];let n=e;for(;n<i.length;){const e=i[n].match(/^(\s*)([*+-]|\d+\.)\s+(.+)$/);if(!e)break;const[,s,r,a]=e,c=Math.floor(s.length/2),f=/^\d+\./.test(r),u=f?"ol":"ul";let p=a,d=h("li");if(!f){const e=a.match(/^\[([x ])\]\s+(.*)$/i);if(e){const t="x"===e[1].toLowerCase(),n=o.inline_styles?' style="margin-right:.5em"':` class="${o.class_prefix}task-checkbox"`;d=o.inline_styles?' style="list-style:none"':` class="${o.class_prefix}task-item"`,p=`<input type="checkbox"${n}${t?" checked":""} disabled> ${e[2]}`}}for(;t.length>0&&c<t[t.length-1].indent;){const e=t.pop();e.items.push(`\n</${e.type}>`)}if(0===t.length||c>t[t.length-1].indent){const e={type:u,indent:c,items:c>0?[`\n<${u}${h(u)}>`]:[`<${u}${h(u)}>`]};c>0&&t.length>0&&(t[t.length-1].items.push(e.items[0]),e.items=[]),t.push(e)}else if(t[t.length-1].type!==u){const e=t.pop();e.items.push(`\n</${e.type}>`),t.length>0?t[t.length-1].items.push(e.items.join("")):l.push(e.items.join(""));const n={type:u,indent:c,items:[`<${u}${h(u)}>`]};t.push(n)}t[t.length-1].items.push(`\n<li${d}>${g(p)}</li>`),n++}for(;t.length>1;){const e=t.pop();e.items.push(`\n</${e.type}>`),t[t.length-1].items.push(e.items.join(""))}if(t.length>0){const e=t[0];e.items.push(`\n</${e.type}>`),l.push(e.items.join(""))}return n},x=e=>{let t=e;const n=[];let s=null;const r=[],o=i[t].trim().replace(/^\|/,"").replace(/\|$/,"").split("|").map(e=>e.trim());if(n.push(...o),t++,!(t<i.length))return e;if(8!==m(i[t]))return e;{const e=i[t].trim().replace(/^\|/,"").replace(/\|$/,"").split("|");s=e.map(e=>{const t=e.trim();return t.startsWith(":")&&t.endsWith(":")?"center":t.endsWith(":")?"right":"left"}),t++}for(;t<i.length;){const e=m(i[t]);if(7!==e&&8!==e)break;const n=i[t].trim().replace(/^\|/,"").replace(/\|$/,"").split("|").map(e=>e.trim());r.push(n),t++}let a=`<table${h("table")}>`;return n.length>0&&(a+=`\n<thead${h("thead")}>\n<tr${h("tr")}>\n`,n.forEach((e,t)=>{const n=s&&"left"!==s[t]?`text-align:${s[t]}`:"";a+=`<th${h("th",n)}>${g(e)}</th>\n`}),a+="</tr>\n</thead>"),r.length>0&&(a+=`\n<tbody${h("tbody")}>\n`,r.forEach(e=>{a+=`<tr${h("tr")}>\n`,e.forEach((e,t)=>{const n=s&&"left"!==s[t]?`text-align:${s[t]}`:"";a+=`<td${h("td",n)}>${g(e)}</td>\n`}),a+="</tr>\n"}),a+="</tbody>"),a+="\n</table>",l.push(a),t};let _=0;for(;_<i.length;){const e=i[_],t=m(e);switch(a){case 0:switch(t){case 0:_++;break;case 1:const t=e.trim().match(/^(#{1,6})\s+(.+?)(?:\s*#*)?$/);if(t){const e=t[1].length,n=t[2];l.push(`<h${e}${h("h"+e)}>${g(n)}</h${e}>`)}_++;break;case 2:l.push(`<hr${h("hr")}>`),_++;break;case 3:const n=e.trim().match(/^([`~]{3,})(.*)$/);n&&(a=1,c={marker:n[1][0],count:n[1].length,lang:(n[2]||"").trim(),lines:[]}),_++;break;case 4:a=4,u=[e.replace(/^\s*>\s?/,"")],_++;break;case 5:case 6:_=k(_);break;case 7:case 8:const s=x(_);s===_?(a=5,f=[e],_++):_=s;break;default:a=5,f=[e],_++}break;case 1:const n=e.trim();if(new RegExp(`^${c.marker}{${c.count},}\\s*$`).test(n)){const e=c.lines.join("\n");let t="";if(o.fence_plugin&&(t=o.fence_plugin(e,c.lang)),!t||void 0===t){const n=!o.inline_styles&&c.lang?` class="language-${c.lang}"`:"",s=o.inline_styles?h("code"):n;t=`<pre${h("pre")}><code${s}>${p(e)}</code></pre>`}l.push(t),a=0,c=null}else c.lines.push(e);_++;break;case 5:switch(t){case 0:$(),a=0,_++;break;case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:$(),a=0;break;default:f.push(e),_++}break;case 4:4===t?(u.push(e.replace(/^\s*>\s?/,"")),_++):0===t&&_+1<i.length&&4===m(i[_+1])?(u.push(""),_++):(b(),a=0)}}return $(),b(),l.join("").trim()}return n.emitStyles=function(t="quikdown-",n="light"){const s=e,r={"#f4f4f4":"#2a2a2a","#f0f0f0":"#2a2a2a","#f2f2f2":"#2a2a2a","#ddd":"#3a3a3a","#06c":"#6db3f2",_textColor:"#e0e0e0"},o={_textColor:"#333"};let i="";for(const[e,l]of Object.entries(s))if(l){let s=l;if("dark"===n&&r){for(const[e,t]of Object.entries(r))e.startsWith("_")||(s=s.replace(new RegExp(e,"g"),t));["h1","h2","h3","h4","h5","h6","td","li","blockquote"].includes(e)&&(s+=`;color:${r._textColor}`)}else if("light"===n&&o){["h1","h2","h3","h4","h5","h6","td","li","blockquote"].includes(e)&&(s+=`;color:${o._textColor}`)}i+=`.${t}${e} { ${s} }\n`}return i},n.configure=function(e){return function(t){return n(t,e)}},n.version="1.0.3dev4","undefined"!=typeof module&&module.exports&&(module.exports=n),"undefined"!=typeof window&&(window.quikdown=n),n});
|
|
8
|
+
//# sourceMappingURL=quikdown-lex.umd.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quikdown-lex.umd.min.js","sources":["../src/quikdown-lex.js"],"sourcesContent":["/**\n * quikdown-lex - Hand-coded lexer/parser implementation\n * \n * This is a state-machine based markdown parser that processes input\n * line-by-line with explicit state tracking. The approach trades regex\n * complexity for hand-coded state transitions, resulting in smaller\n * minified size and more predictable performance.\n * \n * Architecture:\n * 1. Line-by-line processing with lookahead\n * 2. Explicit state tracking (NORMAL, FENCE, TABLE, LIST, BLOCKQUOTE)\n * 3. Single-pass inline processing\n * 4. Direct HTML generation (no intermediate AST)\n * \n * @version __QUIKDOWN_VERSION__\n */\n\n// ===========================================================================\n// CONSTANTS & CONFIGURATION\n// ===========================================================================\n\n// Compact style map - keys match HTML tags, values are CSS strings\n// Optimized: no spaces after colons, decimal values shortened\nconst 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-item': 'list-style:none',\n 'task-checkbox': 'margin-right:.5em'\n};\n\n// HTML escape map for XSS prevention\nconst ESC_MAP = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n};\n\n// Line type constants for state machine\nconst LINE_BLANK = 0;\nconst LINE_HEADING = 1;\nconst LINE_HR = 2;\nconst LINE_FENCE = 3;\nconst LINE_BLOCKQUOTE = 4;\nconst LINE_LIST_UNORDERED = 5;\nconst LINE_LIST_ORDERED = 6;\nconst LINE_TABLE = 7;\nconst LINE_TABLE_SEP = 8;\nconst LINE_TEXT = 9;\n\n// Parser states\nconst STATE_NORMAL = 0;\nconst STATE_FENCE = 1;\nconst STATE_LIST = 2;\nconst STATE_TABLE = 3;\nconst STATE_BLOCKQUOTE = 4;\nconst STATE_PARAGRAPH = 5;\n\n// ===========================================================================\n// MAIN PARSER FUNCTION\n// ===========================================================================\n\nfunction quikdown(markdown, options = {}) {\n // Early return for invalid input\n if (!markdown || typeof markdown !== 'string') return '';\n \n // Parse options with defaults\n const opts = {\n inline_styles: options.inline_styles || false,\n class_prefix: options.class_prefix || 'quikdown-',\n allow_unsafe_urls: options.allow_unsafe_urls || false,\n fence_plugin: options.fence_plugin || null\n };\n \n // Split into lines for processing\n const lines = markdown.split('\\n');\n const output = [];\n \n // Parser state\n let state = STATE_NORMAL;\n let stateData = null; // Holds state-specific data\n \n // Buffers for accumulating content\n let paragraphBuffer = [];\n let blockquoteBuffer = [];\n \n // ===========================================================================\n // HELPER FUNCTIONS\n // ===========================================================================\n \n /**\n * Generate HTML attribute (class or inline style)\n * @param {string} tag - HTML tag name\n * @param {string} extraStyle - Additional inline styles\n * @returns {string} HTML attribute string\n */\n const getAttr = (tag, extraStyle = '') => {\n if (opts.inline_styles) {\n const baseStyle = STYLES[tag] || '';\n const combined = extraStyle \n ? (baseStyle ? `${baseStyle};${extraStyle}` : extraStyle)\n : baseStyle;\n return combined ? ` style=\"${combined}\"` : '';\n }\n return ` class=\"${opts.class_prefix}${tag}\"`;\n };\n \n /**\n * Escape HTML entities to prevent XSS\n * @param {string} str - Input string\n * @returns {string} Escaped string\n */\n const escapeHtml = (str) => {\n return str.replace(/[&<>\"']/g, m => ESC_MAP[m]);\n };\n \n /**\n * Sanitize URLs to prevent XSS attacks\n * @param {string} url - Input URL\n * @returns {string} Sanitized URL or '#' if dangerous\n */\n const sanitizeUrl = (url) => {\n if (!url) return '';\n if (opts.allow_unsafe_urls) return url;\n \n const trimmed = url.trim();\n const lower = trimmed.toLowerCase();\n \n // Block dangerous protocols except data:image\n if (/^(javascript|vbscript|data):/i.test(lower)) {\n if (/^data:image\\//i.test(lower)) return trimmed;\n return '#';\n }\n \n return trimmed;\n };\n \n /**\n * Process inline markdown elements (bold, italic, links, etc.)\n * Single-pass processing with minimal allocations\n * @param {string} text - Input text\n * @returns {string} HTML with inline formatting\n */\n const processInline = (text) => {\n if (!text) return '';\n \n // Step 1: Protect inline code by extracting it\n const codes = [];\n text = text.replace(/`([^`]+)`/g, (_, code) => {\n codes.push(escapeHtml(code));\n return `\\x01${codes.length - 1}\\x02`; // Use control chars as markers\n });\n \n // Step 2: Escape HTML entities\n text = escapeHtml(text);\n \n // Step 3: Process images (must come before links)\n text = text.replace(/!\\[([^\\]]*)\\]\\(([^)]+)\\)/g, (_, alt, src) => {\n return `<img${getAttr('img')} src=\"${sanitizeUrl(src)}\" alt=\"${alt}\">`;\n });\n \n // Step 4: Process links\n text = text.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (_, label, href) => {\n const url = sanitizeUrl(href);\n const isExternal = /^https?:\\/\\//i.test(url);\n const rel = isExternal ? ' rel=\"noopener noreferrer\"' : '';\n return `<a${getAttr('a')} href=\"${url}\"${rel}>${label}</a>`;\n });\n \n // Step 5: Process autolinks\n text = text.replace(/(^|\\s)(https?:\\/\\/[^\\s<]+)/g, (_, prefix, url) => {\n return `${prefix}<a${getAttr('a')} href=\"${sanitizeUrl(url)}\" rel=\"noopener noreferrer\">${url}</a>`;\n });\n \n // Step 6: Process bold (** and __)\n text = text.replace(/\\*\\*(.+?)\\*\\*/g, `<strong${getAttr('strong')}>$1</strong>`);\n text = text.replace(/__(.+?)__/g, `<strong${getAttr('strong')}>$1</strong>`);\n \n // Step 7: Process italic (* and _) - using lookahead/behind\n text = text.replace(/(?<!\\*)\\*(?!\\*)([^*]+)\\*(?!\\*)/g, `<em${getAttr('em')}>$1</em>`);\n text = text.replace(/(?<!_)_(?!_)([^_]+)_(?!_)/g, `<em${getAttr('em')}>$1</em>`);\n \n // Step 8: Process strikethrough\n text = text.replace(/~~(.+?)~~/g, `<del${getAttr('del')}>$1</del>`);\n \n // Step 9: Process line breaks (two spaces at end of line)\n text = text.replace(/ $/gm, `<br${getAttr('br')}>`);\n \n // Step 10: Restore inline code\n text = text.replace(/\\x01(\\d+)\\x02/g, (_, idx) => {\n return `<code${getAttr('code')}>${codes[idx]}</code>`;\n });\n \n return text;\n };\n \n /**\n * Identify line type using optimized checks\n * @param {string} line - Input line\n * @returns {number} Line type constant\n */\n const getLineType = (line) => {\n const trimmed = line.trim();\n \n // Empty line\n if (!trimmed) return LINE_BLANK;\n \n // Use first character for quick discrimination\n const firstChar = trimmed[0];\n \n switch (firstChar) {\n case '#':\n // Heading: # through ######\n if (/^#{1,6}\\s+/.test(trimmed)) return LINE_HEADING;\n break;\n \n case '-':\n case '*':\n case '_':\n // Could be HR or list\n if (/^[-*_](\\s*[-*_]){2,}$/.test(trimmed)) return LINE_HR;\n if (/^[*+-]\\s+/.test(trimmed)) return LINE_LIST_UNORDERED;\n break;\n \n case '+':\n // Unordered list with +\n if (/^\\+\\s+/.test(trimmed)) return LINE_LIST_UNORDERED;\n break;\n \n case '`':\n case '~':\n // Fence marker (3+ backticks or tildes)\n if (/^[`~]{3,}/.test(trimmed)) return LINE_FENCE;\n break;\n \n case '>':\n // Blockquote\n return LINE_BLOCKQUOTE;\n \n case '|':\n // Table (starts with pipe)\n if (/^\\|?\\s*:?-+:?\\s*(\\|\\s*:?-+:?\\s*)*\\|?$/.test(trimmed)) {\n return LINE_TABLE_SEP;\n }\n return LINE_TABLE;\n \n default:\n // Check for ordered list (digit)\n if (/^\\d+\\.\\s+/.test(trimmed)) return LINE_LIST_ORDERED;\n \n // Check for table without leading pipe\n if (trimmed.includes('|')) {\n if (/^\\s*:?-+:?\\s*(\\|\\s*:?-+:?\\s*)+\\s*$/.test(trimmed)) {\n return LINE_TABLE_SEP;\n }\n return LINE_TABLE;\n }\n }\n \n // Check indented list items\n if (/^\\s+[*+-]\\s+/.test(line)) return LINE_LIST_UNORDERED;\n if (/^\\s+\\d+\\.\\s+/.test(line)) return LINE_LIST_ORDERED;\n \n return LINE_TEXT;\n };\n \n /**\n * Flush accumulated paragraph buffer to output\n */\n const flushParagraph = () => {\n if (paragraphBuffer.length > 0) {\n const content = paragraphBuffer.join('\\n');\n output.push(`<p>${processInline(content)}</p>`);\n paragraphBuffer = [];\n }\n };\n \n /**\n * Flush accumulated blockquote buffer to output\n */\n const flushBlockquote = () => {\n if (blockquoteBuffer.length > 0) {\n const innerContent = blockquoteBuffer.join('\\n').trim();\n \n // Check if it's a simple single-line blockquote without block elements\n if (blockquoteBuffer.length === 1 && !innerContent.includes('\\n')) {\n // Simple blockquote - just process inline\n output.push(`<blockquote${getAttr('blockquote')}>${processInline(innerContent)}</blockquote>`);\n } else if (blockquoteBuffer.length === 2 && blockquoteBuffer[1] === '' && !blockquoteBuffer[0].includes('\\n')) {\n // Two lines but second is empty - treat as single line\n output.push(`<blockquote${getAttr('blockquote')}>${processInline(blockquoteBuffer[0])}</blockquote>`);\n } else {\n // Multi-line blockquote - treat all lines as single block\n const lines = blockquoteBuffer.filter(line => line !== '');\n if (lines.length === 0) {\n // All empty lines, skip\n } else if (lines.length === 2 && lines.every(line => !line.includes('\\n') && line.trim().length > 0)) {\n // Two consecutive lines - keep them separate as two blockquotes\n output.push(`<blockquote${getAttr('blockquote')}>${processInline(lines[0])}</blockquote>`);\n output.push(`<blockquote${getAttr('blockquote')}>${processInline(lines[1])}</blockquote>`);\n } else {\n // Complex content - recursively parse\n const innerHtml = quikdown(innerContent, opts);\n output.push(`<blockquote${getAttr('blockquote')}>${innerHtml}</blockquote>`);\n }\n }\n blockquoteBuffer = [];\n }\n };\n \n /**\n * Process a list starting at current position\n * @param {number} startIdx - Starting line index\n * @returns {number} Next line index to process\n */\n const processList = (startIdx) => {\n const listStack = []; // Stack of { type, indent, items }\n let i = startIdx;\n \n while (i < lines.length) {\n const line = lines[i];\n const match = line.match(/^(\\s*)([*+-]|\\d+\\.)\\s+(.+)$/);\n \n if (!match) {\n // Not a list item, end list processing\n break;\n }\n \n const [, spaces, marker, content] = match;\n const indent = Math.floor(spaces.length / 2);\n const isOrdered = /^\\d+\\./.test(marker);\n const listType = isOrdered ? 'ol' : 'ul';\n \n // Process task list syntax\n let itemContent = content;\n let itemAttr = getAttr('li');\n \n if (!isOrdered) {\n const taskMatch = content.match(/^\\[([x ])\\]\\s+(.*)$/i);\n if (taskMatch) {\n const checked = taskMatch[1].toLowerCase() === 'x';\n const checkboxAttr = opts.inline_styles \n ? ' style=\"margin-right:.5em\"' \n : ` class=\"${opts.class_prefix}task-checkbox\"`;\n itemAttr = opts.inline_styles \n ? ' style=\"list-style:none\"' \n : ` class=\"${opts.class_prefix}task-item\"`;\n itemContent = `<input type=\"checkbox\"${checkboxAttr}${checked ? ' checked' : ''} disabled> ${taskMatch[2]}`;\n }\n }\n \n // Manage list stack based on indentation\n while (listStack.length > 0 && indent < listStack[listStack.length - 1].indent) {\n // Close deeper lists\n const closed = listStack.pop();\n closed.items.push(`\\n</${closed.type}>`);\n }\n \n if (listStack.length === 0 || indent > listStack[listStack.length - 1].indent) {\n // Start new list level\n const newList = {\n type: listType,\n indent: indent,\n items: indent > 0 ? [`\\n<${listType}${getAttr(listType)}>`] : [`<${listType}${getAttr(listType)}>`]\n };\n if (indent > 0 && listStack.length > 0) {\n // Nested list - add to parent's items\n listStack[listStack.length - 1].items.push(newList.items[0]);\n newList.items = [];\n }\n listStack.push(newList);\n } else if (listStack[listStack.length - 1].type !== listType) {\n // Different list type at same level - close and open new\n const closed = listStack.pop();\n closed.items.push(`\\n</${closed.type}>`);\n if (listStack.length > 0) {\n listStack[listStack.length - 1].items.push(closed.items.join(''));\n } else {\n output.push(closed.items.join(''));\n }\n const newList = {\n type: listType,\n indent: indent,\n items: [`<${listType}${getAttr(listType)}>`]\n };\n listStack.push(newList);\n }\n \n // Add list item to current list\n listStack[listStack.length - 1].items.push(\n `\\n<li${itemAttr}>${processInline(itemContent)}</li>`\n );\n \n i++;\n }\n \n // Close all open lists\n while (listStack.length > 1) {\n const closed = listStack.pop();\n closed.items.push(`\\n</${closed.type}>`);\n listStack[listStack.length - 1].items.push(closed.items.join(''));\n }\n \n if (listStack.length > 0) {\n const finalList = listStack[0];\n finalList.items.push(`\\n</${finalList.type}>`);\n output.push(finalList.items.join(''));\n }\n \n return i;\n };\n \n /**\n * Process a table starting at current position\n * @param {number} startIdx - Starting line index\n * @returns {number} Next line index to process\n */\n const processTable = (startIdx) => {\n let i = startIdx;\n const headerCells = [];\n let alignments = null;\n const bodyRows = [];\n \n // Parse first row as potential header\n const firstLine = lines[i].trim();\n const firstCells = firstLine.replace(/^\\|/, '').replace(/\\|$/, '').split('|').map(c => c.trim());\n headerCells.push(...firstCells);\n i++;\n \n // Check for separator line - REQUIRED for valid table\n if (i < lines.length) {\n const lineType = getLineType(lines[i]);\n if (lineType === LINE_TABLE_SEP) {\n // Parse alignments from separator\n const sepLine = lines[i].trim();\n const sepCells = sepLine.replace(/^\\|/, '').replace(/\\|$/, '').split('|');\n alignments = sepCells.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 i++;\n } else {\n // No separator, not a valid table - return without processing\n return startIdx;\n }\n } else {\n // End of input without separator - not a valid table\n return startIdx;\n }\n \n // Parse body rows\n while (i < lines.length) {\n const lineType = getLineType(lines[i]);\n if (lineType !== LINE_TABLE && lineType !== LINE_TABLE_SEP) break;\n \n const line = lines[i].trim();\n const cells = line.replace(/^\\|/, '').replace(/\\|$/, '').split('|').map(c => c.trim());\n bodyRows.push(cells);\n i++;\n }\n \n // Generate table HTML\n let html = `<table${getAttr('table')}>`;\n \n // Add header if present\n if (headerCells.length > 0) {\n html += `\\n<thead${getAttr('thead')}>\\n<tr${getAttr('tr')}>\\n`;\n headerCells.forEach((cell, idx) => {\n const align = alignments && alignments[idx] !== 'left' \n ? `text-align:${alignments[idx]}` \n : '';\n html += `<th${getAttr('th', align)}>${processInline(cell)}</th>\\n`;\n });\n html += `</tr>\\n</thead>`;\n }\n \n // Add body rows\n if (bodyRows.length > 0) {\n html += `\\n<tbody${getAttr('tbody')}>\\n`;\n bodyRows.forEach(row => {\n html += `<tr${getAttr('tr')}>\\n`;\n row.forEach((cell, idx) => {\n const align = alignments && alignments[idx] !== 'left' \n ? `text-align:${alignments[idx]}` \n : '';\n html += `<td${getAttr('td', align)}>${processInline(cell)}</td>\\n`;\n });\n html += `</tr>\\n`;\n });\n html += `</tbody>`;\n }\n \n html += `\\n</table>`;\n output.push(html);\n \n return i;\n };\n \n // ===========================================================================\n // MAIN PARSING LOOP - STATE MACHINE\n // ===========================================================================\n \n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n const lineType = getLineType(line);\n \n // STATE MACHINE - Handle current state and line type\n switch (state) {\n \n // -----------------------------------------------------------------------\n // NORMAL STATE - Can transition to any other state\n // -----------------------------------------------------------------------\n case STATE_NORMAL:\n switch (lineType) {\n case LINE_BLANK:\n // Blank line - just skip\n i++;\n break;\n \n case LINE_HEADING:\n // Parse heading\n const headingMatch = line.trim().match(/^(#{1,6})\\s+(.+?)(?:\\s*#*)?$/);\n if (headingMatch) {\n const level = headingMatch[1].length;\n const text = headingMatch[2];\n output.push(`<h${level}${getAttr('h' + level)}>${processInline(text)}</h${level}>`);\n }\n i++;\n break;\n \n case LINE_HR:\n // Horizontal rule\n output.push(`<hr${getAttr('hr')}>`);\n i++;\n break;\n \n case LINE_FENCE:\n // Start fence block\n const fenceMatch = line.trim().match(/^([`~]{3,})(.*)$/);\n if (fenceMatch) {\n state = STATE_FENCE;\n stateData = {\n marker: fenceMatch[1][0],\n count: fenceMatch[1].length,\n lang: (fenceMatch[2] || '').trim(),\n lines: []\n };\n }\n i++;\n break;\n \n case LINE_BLOCKQUOTE:\n // Start blockquote\n state = STATE_BLOCKQUOTE;\n blockquoteBuffer = [line.replace(/^\\s*>\\s?/, '')];\n i++;\n break;\n \n case LINE_LIST_UNORDERED:\n case LINE_LIST_ORDERED:\n // Process entire list\n i = processList(i);\n break;\n \n case LINE_TABLE:\n case LINE_TABLE_SEP:\n // Process entire table\n const newIdx = processTable(i);\n if (newIdx === i) {\n // Not a valid table, treat as text\n state = STATE_PARAGRAPH;\n paragraphBuffer = [line];\n i++;\n } else {\n i = newIdx;\n }\n break;\n \n case LINE_TEXT:\n default:\n // Start paragraph\n state = STATE_PARAGRAPH;\n paragraphBuffer = [line];\n i++;\n break;\n }\n break;\n \n // -----------------------------------------------------------------------\n // FENCE STATE - Inside a code fence\n // -----------------------------------------------------------------------\n case STATE_FENCE:\n // Check for closing fence\n const trimmed = line.trim();\n const closePattern = new RegExp(`^${stateData.marker}{${stateData.count},}\\\\s*$`);\n \n if (closePattern.test(trimmed)) {\n // End fence - output code block\n const code = stateData.lines.join('\\n');\n let output_html = '';\n \n // Try fence plugin first\n if (opts.fence_plugin) {\n output_html = opts.fence_plugin(code, stateData.lang);\n }\n \n // Fall back to default rendering\n if (!output_html || output_html === undefined) {\n const langAttr = !opts.inline_styles && stateData.lang \n ? ` class=\"language-${stateData.lang}\"` \n : '';\n const codeAttr = opts.inline_styles ? getAttr('code') : langAttr;\n output_html = `<pre${getAttr('pre')}><code${codeAttr}>${escapeHtml(code)}</code></pre>`;\n }\n \n output.push(output_html);\n state = STATE_NORMAL;\n stateData = null;\n } else {\n // Continue accumulating fence content\n stateData.lines.push(line);\n }\n i++;\n break;\n \n // -----------------------------------------------------------------------\n // PARAGRAPH STATE - Accumulating paragraph lines\n // -----------------------------------------------------------------------\n case STATE_PARAGRAPH:\n switch (lineType) {\n case LINE_BLANK:\n // End paragraph\n flushParagraph();\n state = STATE_NORMAL;\n i++;\n break;\n \n case LINE_HEADING:\n case LINE_HR:\n case LINE_FENCE:\n case LINE_BLOCKQUOTE:\n case LINE_LIST_UNORDERED:\n case LINE_LIST_ORDERED:\n case LINE_TABLE:\n case LINE_TABLE_SEP:\n // End paragraph and process new block\n flushParagraph();\n state = STATE_NORMAL;\n // Don't increment i - reprocess this line in NORMAL state\n break;\n \n case LINE_TEXT:\n default:\n // Continue paragraph\n paragraphBuffer.push(line);\n i++;\n break;\n }\n break;\n \n // -----------------------------------------------------------------------\n // BLOCKQUOTE STATE - Accumulating blockquote lines\n // -----------------------------------------------------------------------\n case STATE_BLOCKQUOTE:\n if (lineType === LINE_BLOCKQUOTE) {\n // Continue blockquote\n blockquoteBuffer.push(line.replace(/^\\s*>\\s?/, ''));\n i++;\n } else if (lineType === LINE_BLANK && i + 1 < lines.length && \n getLineType(lines[i + 1]) === LINE_BLOCKQUOTE) {\n // Blank line within blockquote\n blockquoteBuffer.push('');\n i++;\n } else {\n // End blockquote\n flushBlockquote();\n state = STATE_NORMAL;\n // Don't increment i - reprocess this line\n }\n break;\n }\n }\n \n // Flush any remaining content\n flushParagraph();\n flushBlockquote();\n \n return output.join('').trim();\n}\n\n// ===========================================================================\n// STATIC METHODS\n// ===========================================================================\n\n/**\n * Emit CSS styles for all quikdown elements\n * @param {string} prefix - Class prefix (default: 'quikdown-')\n * @param {string} theme - Optional theme: 'light' (default) or 'dark'\n * @returns {string} CSS stylesheet\n */\nquikdown.emitStyles = function(prefix = 'quikdown-', theme = 'light') {\n const styles = 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 * Create a configured parser function\n * @param {Object} options - Parser options\n * @returns {Function} Configured parser\n */\nquikdown.configure = function(options) {\n return function(markdown) {\n return quikdown(markdown, options);\n };\n};\n\n/**\n * Version string\n */\nquikdown.version = '__QUIKDOWN_VERSION__';\n\n// ===========================================================================\n// EXPORTS\n// ===========================================================================\n\n// CommonJS\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = quikdown;\n}\n\n// Browser global\nif (typeof window !== 'undefined') {\n window.quikdown = quikdown;\n}\n\n// ES6 default export\nexport default quikdown;"],"names":["STYLES","h1","h2","h3","h4","h5","h6","pre","code","blockquote","table","th","td","hr","img","a","strong","em","del","ul","ol","li","ESC_MAP","quikdown","markdown","options","opts","inline_styles","class_prefix","allow_unsafe_urls","fence_plugin","lines","split","output","state","stateData","paragraphBuffer","blockquoteBuffer","getAttr","tag","extraStyle","baseStyle","combined","escapeHtml","str","replace","m","sanitizeUrl","url","trimmed","trim","lower","toLowerCase","test","processInline","text","codes","_","push","length","alt","src","label","href","rel","prefix","idx","getLineType","line","includes","flushParagraph","content","join","flushBlockquote","innerContent","filter","every","innerHtml","processList","startIdx","listStack","i","match","spaces","marker","indent","Math","floor","isOrdered","listType","itemContent","itemAttr","taskMatch","checked","checkboxAttr","closed","pop","items","type","newList","finalList","processTable","headerCells","alignments","bodyRows","firstCells","map","c","sepCells","cell","startsWith","endsWith","lineType","cells","html","forEach","align","row","headingMatch","level","fenceMatch","count","lang","newIdx","RegExp","output_html","undefined","langAttr","codeAttr","emitStyles","theme","styles","themeOverrides","_textColor","css","style","Object","entries","themedStyle","oldColor","newColor","configure","version","module","exports","window"],"mappings":"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"}
|
package/dist/quikdown.cjs
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* quikdown - Lightweight Markdown Parser
|
|
3
|
-
* @version 1.0.
|
|
3
|
+
* @version 1.0.4
|
|
4
4
|
* @license BSD-2-Clause
|
|
5
5
|
* @copyright DeftIO 2025
|
|
6
6
|
*/
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
-
// Auto-generated version file - DO NOT EDIT MANUALLY
|
|
10
|
-
// This file is automatically updated by tools/updateVersion.js
|
|
11
|
-
|
|
12
|
-
const quikdownVersion = "1.0.2";
|
|
13
|
-
|
|
14
9
|
/**
|
|
15
10
|
* quikdown - A minimal markdown parser optimized for chat/LLM output
|
|
16
11
|
* Supports tables, code blocks, lists, and common formatting
|
|
@@ -22,64 +17,71 @@ const quikdownVersion = "1.0.2";
|
|
|
22
17
|
* @returns {string} - The rendered HTML
|
|
23
18
|
*/
|
|
24
19
|
|
|
20
|
+
// Version will be injected at build time
|
|
21
|
+
const quikdownVersion = '1.0.4';
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const { fence_plugin, inline_styles = false } = options;
|
|
23
|
+
// Constants for reuse
|
|
24
|
+
const CLASS_PREFIX = 'quikdown-';
|
|
25
|
+
const PLACEHOLDER_CB = '§CB';
|
|
26
|
+
const PLACEHOLDER_IC = '§IC';
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
h1: 'font-size: 2em; font-weight: 600; margin: 0.67em 0; text-align: left',
|
|
36
|
-
h2: 'font-size: 1.5em; font-weight: 600; margin: 0.83em 0',
|
|
37
|
-
h3: 'font-size: 1.25em; font-weight: 600; margin: 1em 0',
|
|
38
|
-
h4: 'font-size: 1em; font-weight: 600; margin: 1.33em 0',
|
|
39
|
-
h5: 'font-size: 0.875em; font-weight: 600; margin: 1.67em 0',
|
|
40
|
-
h6: 'font-size: 0.85em; font-weight: 600; margin: 2em 0',
|
|
41
|
-
pre: 'background: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto; margin: 1em 0',
|
|
42
|
-
code: 'background: #f0f0f0; padding: 2px 4px; border-radius: 3px; font-family: monospace',
|
|
43
|
-
blockquote: 'border-left: 4px solid #ddd; margin-left: 0; padding-left: 1em',
|
|
44
|
-
table: 'border-collapse: collapse; width: 100%; margin: 1em 0',
|
|
45
|
-
thead: '',
|
|
46
|
-
tbody: '',
|
|
47
|
-
tr: '',
|
|
48
|
-
th: 'border: 1px solid #ddd; padding: 8px; background-color: #f2f2f2; font-weight: bold; text-align: left',
|
|
49
|
-
td: 'border: 1px solid #ddd; padding: 8px; text-align: left',
|
|
50
|
-
hr: 'border: none; border-top: 1px solid #ddd; margin: 1em 0',
|
|
51
|
-
img: 'max-width: 100%; height: auto',
|
|
52
|
-
a: 'color: #0066cc; text-decoration: underline',
|
|
53
|
-
strong: 'font-weight: bold',
|
|
54
|
-
em: 'font-style: italic',
|
|
55
|
-
del: 'text-decoration: line-through',
|
|
56
|
-
ul: 'margin: 0.5em 0; padding-left: 2em',
|
|
57
|
-
ol: 'margin: 0.5em 0; padding-left: 2em',
|
|
58
|
-
li: 'margin: 0.25em 0',
|
|
59
|
-
br: ''
|
|
60
|
-
};
|
|
28
|
+
// Escape map at module level
|
|
29
|
+
const ESC_MAP = {'&':'&','<':'<','>':'>','"':'"',"'":'''};
|
|
61
30
|
|
|
62
|
-
|
|
63
|
-
|
|
31
|
+
// Single source of truth for all style definitions - optimized
|
|
32
|
+
const QUIKDOWN_STYLES = {
|
|
33
|
+
h1: 'font-size:2em;font-weight:600;margin:.67em 0;text-align:left',
|
|
34
|
+
h2: 'font-size:1.5em;font-weight:600;margin:.83em 0',
|
|
35
|
+
h3: 'font-size:1.25em;font-weight:600;margin:1em 0',
|
|
36
|
+
h4: 'font-size:1em;font-weight:600;margin:1.33em 0',
|
|
37
|
+
h5: 'font-size:.875em;font-weight:600;margin:1.67em 0',
|
|
38
|
+
h6: 'font-size:.85em;font-weight:600;margin:2em 0',
|
|
39
|
+
pre: 'background:#f4f4f4;padding:10px;border-radius:4px;overflow-x:auto;margin:1em 0',
|
|
40
|
+
code: 'background:#f0f0f0;padding:2px 4px;border-radius:3px;font-family:monospace',
|
|
41
|
+
blockquote: 'border-left:4px solid #ddd;margin-left:0;padding-left:1em',
|
|
42
|
+
table: 'border-collapse:collapse;width:100%;margin:1em 0',
|
|
43
|
+
th: 'border:1px solid #ddd;padding:8px;background-color:#f2f2f2;font-weight:bold;text-align:left',
|
|
44
|
+
td: 'border:1px solid #ddd;padding:8px;text-align:left',
|
|
45
|
+
hr: 'border:none;border-top:1px solid #ddd;margin:1em 0',
|
|
46
|
+
img: 'max-width:100%;height:auto',
|
|
47
|
+
a: 'color:#06c;text-decoration:underline',
|
|
48
|
+
strong: 'font-weight:bold',
|
|
49
|
+
em: 'font-style:italic',
|
|
50
|
+
del: 'text-decoration:line-through',
|
|
51
|
+
ul: 'margin:.5em 0;padding-left:2em',
|
|
52
|
+
ol: 'margin:.5em 0;padding-left:2em',
|
|
53
|
+
li: 'margin:.25em 0',
|
|
54
|
+
// Task list specific styles
|
|
55
|
+
'task-item': 'list-style:none',
|
|
56
|
+
'task-checkbox': 'margin-right:.5em'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Factory function to create getAttr for a given context
|
|
60
|
+
function createGetAttr(inline_styles, styles) {
|
|
61
|
+
return function(tag, additionalStyle = '') {
|
|
64
62
|
if (inline_styles) {
|
|
65
|
-
const style = styles[tag]
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const style = styles[tag];
|
|
64
|
+
if (!style && !additionalStyle) return '';
|
|
65
|
+
const fullStyle = additionalStyle ? (style ? `${style};${additionalStyle}` : additionalStyle) : style;
|
|
66
|
+
return ` style="${fullStyle}"`;
|
|
68
67
|
} else {
|
|
69
|
-
return ` class="
|
|
68
|
+
return ` class="${CLASS_PREFIX}${tag}"`;
|
|
70
69
|
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function quikdown(markdown, options = {}) {
|
|
74
|
+
if (!markdown || typeof markdown !== 'string') {
|
|
75
|
+
return '';
|
|
71
76
|
}
|
|
77
|
+
|
|
78
|
+
const { fence_plugin, inline_styles = false } = options;
|
|
79
|
+
const styles = QUIKDOWN_STYLES; // Use module-level styles
|
|
80
|
+
const getAttr = createGetAttr(inline_styles, styles); // Create getAttr once
|
|
72
81
|
|
|
73
82
|
// Escape HTML entities to prevent XSS
|
|
74
83
|
function escapeHtml(text) {
|
|
75
|
-
|
|
76
|
-
'&': '&',
|
|
77
|
-
'<': '<',
|
|
78
|
-
'>': '>',
|
|
79
|
-
'"': '"',
|
|
80
|
-
"'": '''
|
|
81
|
-
};
|
|
82
|
-
return text.replace(/[&<>"']/g, m => map[m]);
|
|
84
|
+
return text.replace(/[&<>"']/g, m => ESC_MAP[m]);
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
// Sanitize URLs to prevent XSS attacks
|
|
@@ -89,7 +91,6 @@ function quikdown(markdown, options = {}) {
|
|
|
89
91
|
// If unsafe URLs are explicitly allowed, return as-is
|
|
90
92
|
if (allowUnsafe) return url;
|
|
91
93
|
|
|
92
|
-
// Trim and lowercase for checking
|
|
93
94
|
const trimmedUrl = url.trim();
|
|
94
95
|
const lowerUrl = trimmedUrl.toLowerCase();
|
|
95
96
|
|
|
@@ -121,7 +122,7 @@ function quikdown(markdown, options = {}) {
|
|
|
121
122
|
// Match paired fences - ``` with ``` and ~~~ with ~~~
|
|
122
123
|
// Fence must be at start of line
|
|
123
124
|
html = html.replace(/^(```|~~~)([^\n]*)\n([\s\S]*?)^\1$/gm, (match, fence, lang, code) => {
|
|
124
|
-
const placeholder =
|
|
125
|
+
const placeholder = `${PLACEHOLDER_CB}${codeBlocks.length}§`;
|
|
125
126
|
|
|
126
127
|
// Trim the language specification
|
|
127
128
|
const langTrimmed = lang ? lang.trim() : '';
|
|
@@ -145,7 +146,7 @@ function quikdown(markdown, options = {}) {
|
|
|
145
146
|
|
|
146
147
|
// Extract inline code
|
|
147
148
|
html = html.replace(/`([^`]+)`/g, (match, code) => {
|
|
148
|
-
const placeholder =
|
|
149
|
+
const placeholder = `${PLACEHOLDER_IC}${inlineCodes.length}§`;
|
|
149
150
|
inlineCodes.push(escapeHtml(code));
|
|
150
151
|
return placeholder;
|
|
151
152
|
});
|
|
@@ -156,7 +157,7 @@ function quikdown(markdown, options = {}) {
|
|
|
156
157
|
// Phase 2: Process block elements
|
|
157
158
|
|
|
158
159
|
// Process tables
|
|
159
|
-
html = processTable(html,
|
|
160
|
+
html = processTable(html, getAttr);
|
|
160
161
|
|
|
161
162
|
// Process headings (supports optional trailing #'s)
|
|
162
163
|
html = html.replace(/^(#{1,6})\s+(.+?)\s*#*$/gm, (match, hashes, content) => {
|
|
@@ -173,7 +174,7 @@ function quikdown(markdown, options = {}) {
|
|
|
173
174
|
html = html.replace(/^---+$/gm, `<hr${getAttr('hr')}>`);
|
|
174
175
|
|
|
175
176
|
// Process lists
|
|
176
|
-
html = processLists(html,
|
|
177
|
+
html = processLists(html, getAttr, inline_styles);
|
|
177
178
|
|
|
178
179
|
// Phase 3: Process inline elements
|
|
179
180
|
|
|
@@ -198,16 +199,18 @@ function quikdown(markdown, options = {}) {
|
|
|
198
199
|
return `${prefix}<a${getAttr('a')} href="${sanitizedUrl}" rel="noopener noreferrer">${url}</a>`;
|
|
199
200
|
});
|
|
200
201
|
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
202
|
+
// Process inline formatting (bold, italic, strikethrough)
|
|
203
|
+
const inlinePatterns = [
|
|
204
|
+
[/\*\*(.+?)\*\*/g, 'strong'],
|
|
205
|
+
[/__(.+?)__/g, 'strong'],
|
|
206
|
+
[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, 'em'],
|
|
207
|
+
[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em'],
|
|
208
|
+
[/~~(.+?)~~/g, 'del']
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
inlinePatterns.forEach(([pattern, tag]) => {
|
|
212
|
+
html = html.replace(pattern, `<${tag}${getAttr(tag)}>$1</${tag}>`);
|
|
213
|
+
});
|
|
211
214
|
|
|
212
215
|
// Line breaks (two spaces at end of line)
|
|
213
216
|
html = html.replace(/ $/gm, `<br${getAttr('br')}>`);
|
|
@@ -216,21 +219,26 @@ function quikdown(markdown, options = {}) {
|
|
|
216
219
|
html = html.replace(/\n\n+/g, '</p><p>');
|
|
217
220
|
html = '<p>' + html + '</p>';
|
|
218
221
|
|
|
219
|
-
// Clean up empty paragraphs and unwrap block elements
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
222
|
+
// Clean up empty paragraphs and unwrap block elements
|
|
223
|
+
const cleanupPatterns = [
|
|
224
|
+
[/<p><\/p>/g, ''],
|
|
225
|
+
[/<p>(<h[1-6][^>]*>)/g, '$1'],
|
|
226
|
+
[/(<\/h[1-6]>)<\/p>/g, '$1'],
|
|
227
|
+
[/<p>(<blockquote[^>]*>)/g, '$1'],
|
|
228
|
+
[/(<\/blockquote>)<\/p>/g, '$1'],
|
|
229
|
+
[/<p>(<ul[^>]*>|<ol[^>]*>)/g, '$1'],
|
|
230
|
+
[/(<\/ul>|<\/ol>)<\/p>/g, '$1'],
|
|
231
|
+
[/<p>(<hr[^>]*>)<\/p>/g, '$1'],
|
|
232
|
+
[/<p>(<table[^>]*>)/g, '$1'],
|
|
233
|
+
[/(<\/table>)<\/p>/g, '$1'],
|
|
234
|
+
[/<p>(<pre[^>]*>)/g, '$1'],
|
|
235
|
+
[/(<\/pre>)<\/p>/g, '$1'],
|
|
236
|
+
[new RegExp(`<p>(${PLACEHOLDER_CB}\\d+§)<\/p>`, 'g'), '$1']
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
cleanupPatterns.forEach(([pattern, replacement]) => {
|
|
240
|
+
html = html.replace(pattern, replacement);
|
|
241
|
+
});
|
|
234
242
|
|
|
235
243
|
// Phase 4: Restore code blocks and inline code
|
|
236
244
|
|
|
@@ -254,13 +262,13 @@ function quikdown(markdown, options = {}) {
|
|
|
254
262
|
replacement = `<pre${getAttr('pre')}><code${codeAttr}>${block.code}</code></pre>`;
|
|
255
263
|
}
|
|
256
264
|
|
|
257
|
-
const placeholder =
|
|
265
|
+
const placeholder = `${PLACEHOLDER_CB}${i}§`;
|
|
258
266
|
html = html.replace(placeholder, replacement);
|
|
259
267
|
});
|
|
260
268
|
|
|
261
269
|
// Restore inline code
|
|
262
270
|
inlineCodes.forEach((code, i) => {
|
|
263
|
-
const placeholder =
|
|
271
|
+
const placeholder = `${PLACEHOLDER_IC}${i}§`;
|
|
264
272
|
html = html.replace(placeholder, `<code${getAttr('code')}>${code}</code>`);
|
|
265
273
|
});
|
|
266
274
|
|
|
@@ -270,31 +278,21 @@ function quikdown(markdown, options = {}) {
|
|
|
270
278
|
/**
|
|
271
279
|
* Process inline markdown formatting
|
|
272
280
|
*/
|
|
273
|
-
function processInlineMarkdown(text,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
// Process italic (must not match bold markers)
|
|
290
|
-
text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, `<em${getAttr('em')}>$1</em>`);
|
|
291
|
-
text = text.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, `<em${getAttr('em')}>$1</em>`);
|
|
292
|
-
|
|
293
|
-
// Process strikethrough
|
|
294
|
-
text = text.replace(/~~(.+?)~~/g, `<del${getAttr('del')}>$1</del>`);
|
|
295
|
-
|
|
296
|
-
// Process inline code
|
|
297
|
-
text = text.replace(/`([^`]+)`/g, `<code${getAttr('code')}>$1</code>`);
|
|
281
|
+
function processInlineMarkdown(text, getAttr) {
|
|
282
|
+
|
|
283
|
+
// Process inline formatting patterns
|
|
284
|
+
const patterns = [
|
|
285
|
+
[/\*\*(.+?)\*\*/g, 'strong'],
|
|
286
|
+
[/__(.+?)__/g, 'strong'],
|
|
287
|
+
[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, 'em'],
|
|
288
|
+
[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em'],
|
|
289
|
+
[/~~(.+?)~~/g, 'del'],
|
|
290
|
+
[/`([^`]+)`/g, 'code']
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
patterns.forEach(([pattern, tag]) => {
|
|
294
|
+
text = text.replace(pattern, `<${tag}${getAttr(tag)}>$1</${tag}>`);
|
|
295
|
+
});
|
|
298
296
|
|
|
299
297
|
return text;
|
|
300
298
|
}
|
|
@@ -302,7 +300,7 @@ function processInlineMarkdown(text, inline_styles, styles) {
|
|
|
302
300
|
/**
|
|
303
301
|
* Process markdown tables
|
|
304
302
|
*/
|
|
305
|
-
function processTable(text,
|
|
303
|
+
function processTable(text, getAttr) {
|
|
306
304
|
const lines = text.split('\n');
|
|
307
305
|
const result = [];
|
|
308
306
|
let inTable = false;
|
|
@@ -322,7 +320,7 @@ function processTable(text, inline_styles, styles) {
|
|
|
322
320
|
// Not a table line
|
|
323
321
|
if (inTable) {
|
|
324
322
|
// Process the accumulated table
|
|
325
|
-
const tableHtml = buildTable(tableLines,
|
|
323
|
+
const tableHtml = buildTable(tableLines, getAttr);
|
|
326
324
|
if (tableHtml) {
|
|
327
325
|
result.push(tableHtml);
|
|
328
326
|
} else {
|
|
@@ -338,7 +336,7 @@ function processTable(text, inline_styles, styles) {
|
|
|
338
336
|
|
|
339
337
|
// Handle table at end of text
|
|
340
338
|
if (inTable && tableLines.length > 0) {
|
|
341
|
-
const tableHtml = buildTable(tableLines,
|
|
339
|
+
const tableHtml = buildTable(tableLines, getAttr);
|
|
342
340
|
if (tableHtml) {
|
|
343
341
|
result.push(tableHtml);
|
|
344
342
|
} else {
|
|
@@ -352,17 +350,7 @@ function processTable(text, inline_styles, styles) {
|
|
|
352
350
|
/**
|
|
353
351
|
* Build an HTML table from markdown table lines
|
|
354
352
|
*/
|
|
355
|
-
function buildTable(lines,
|
|
356
|
-
// Helper to get attributes
|
|
357
|
-
function getAttr(tag, additionalStyle = '') {
|
|
358
|
-
if (inline_styles) {
|
|
359
|
-
const style = styles[tag] || '';
|
|
360
|
-
const fullStyle = additionalStyle ? `${style}; ${additionalStyle}` : style;
|
|
361
|
-
return fullStyle ? ` style="${fullStyle}"` : '';
|
|
362
|
-
} else {
|
|
363
|
-
return ` class="quikdown-${tag}"`;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
353
|
+
function buildTable(lines, getAttr) {
|
|
366
354
|
|
|
367
355
|
if (lines.length < 2) return null;
|
|
368
356
|
|
|
@@ -402,8 +390,8 @@ function buildTable(lines, inline_styles, styles) {
|
|
|
402
390
|
// Handle pipes at start/end or not
|
|
403
391
|
const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
|
|
404
392
|
cells.forEach((cell, i) => {
|
|
405
|
-
const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align
|
|
406
|
-
const processedCell = processInlineMarkdown(cell.trim(),
|
|
393
|
+
const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';
|
|
394
|
+
const processedCell = processInlineMarkdown(cell.trim(), getAttr);
|
|
407
395
|
html += `<th${getAttr('th', alignStyle)}>${processedCell}</th>\n`;
|
|
408
396
|
});
|
|
409
397
|
html += '</tr>\n';
|
|
@@ -419,8 +407,8 @@ function buildTable(lines, inline_styles, styles) {
|
|
|
419
407
|
// Handle pipes at start/end or not
|
|
420
408
|
const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
|
|
421
409
|
cells.forEach((cell, i) => {
|
|
422
|
-
const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align
|
|
423
|
-
const processedCell = processInlineMarkdown(cell.trim(),
|
|
410
|
+
const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';
|
|
411
|
+
const processedCell = processInlineMarkdown(cell.trim(), getAttr);
|
|
424
412
|
html += `<td${getAttr('td', alignStyle)}>${processedCell}</td>\n`;
|
|
425
413
|
});
|
|
426
414
|
html += '</tr>\n';
|
|
@@ -435,17 +423,7 @@ function buildTable(lines, inline_styles, styles) {
|
|
|
435
423
|
/**
|
|
436
424
|
* Process markdown lists (ordered and unordered)
|
|
437
425
|
*/
|
|
438
|
-
function processLists(text,
|
|
439
|
-
// Helper to get attributes
|
|
440
|
-
function getAttr(tag, additionalStyle = '') {
|
|
441
|
-
if (inline_styles) {
|
|
442
|
-
const style = styles[tag] || '';
|
|
443
|
-
const fullStyle = additionalStyle ? `${style}; ${additionalStyle}` : style;
|
|
444
|
-
return fullStyle ? ` style="${fullStyle}"` : '';
|
|
445
|
-
} else {
|
|
446
|
-
return ` class="quikdown-${tag}"`;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
426
|
+
function processLists(text, getAttr, inline_styles) {
|
|
449
427
|
|
|
450
428
|
const lines = text.split('\n');
|
|
451
429
|
const result = [];
|
|
@@ -469,10 +447,10 @@ function processLists(text, inline_styles, styles) {
|
|
|
469
447
|
const [, checked, taskContent] = taskMatch;
|
|
470
448
|
const isChecked = checked.toLowerCase() === 'x';
|
|
471
449
|
const checkboxAttr = inline_styles
|
|
472
|
-
? ' style="margin-right
|
|
473
|
-
:
|
|
450
|
+
? ' style="margin-right:.5em"'
|
|
451
|
+
: ` class="${CLASS_PREFIX}task-checkbox"`;
|
|
474
452
|
listItemContent = `<input type="checkbox"${checkboxAttr}${isChecked ? ' checked' : ''} disabled> ${taskContent}`;
|
|
475
|
-
taskListClass = inline_styles ? ' style="list-style:
|
|
453
|
+
taskListClass = inline_styles ? ' style="list-style:none"' : ` class="${CLASS_PREFIX}task-item"`;
|
|
476
454
|
}
|
|
477
455
|
|
|
478
456
|
// Close deeper levels
|
|
@@ -520,39 +498,56 @@ function processLists(text, inline_styles, styles) {
|
|
|
520
498
|
|
|
521
499
|
/**
|
|
522
500
|
* Emit CSS styles for quikdown elements
|
|
501
|
+
* @param {string} prefix - Optional class prefix (default: 'quikdown-')
|
|
502
|
+
* @param {string} theme - Optional theme: 'light' (default) or 'dark'
|
|
523
503
|
* @returns {string} CSS string with quikdown styles
|
|
524
504
|
*/
|
|
525
|
-
quikdown.emitStyles = function() {
|
|
526
|
-
const styles =
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
a: 'color: #0066cc; text-decoration: underline',
|
|
542
|
-
strong: 'font-weight: bold',
|
|
543
|
-
em: 'font-style: italic',
|
|
544
|
-
del: 'text-decoration: line-through',
|
|
545
|
-
ul: 'margin: 0.5em 0; padding-left: 2em',
|
|
546
|
-
ol: 'margin: 0.5em 0; padding-left: 2em',
|
|
547
|
-
li: 'margin: 0.25em 0',
|
|
548
|
-
'task-item': 'list-style: none',
|
|
549
|
-
'task-checkbox': 'margin-right: 0.5em'
|
|
505
|
+
quikdown.emitStyles = function(prefix = 'quikdown-', theme = 'light') {
|
|
506
|
+
const styles = QUIKDOWN_STYLES;
|
|
507
|
+
|
|
508
|
+
// Define theme color overrides
|
|
509
|
+
const themeOverrides = {
|
|
510
|
+
dark: {
|
|
511
|
+
'#f4f4f4': '#2a2a2a', // pre background
|
|
512
|
+
'#f0f0f0': '#2a2a2a', // code background
|
|
513
|
+
'#f2f2f2': '#2a2a2a', // th background
|
|
514
|
+
'#ddd': '#3a3a3a', // borders
|
|
515
|
+
'#06c': '#6db3f2', // links
|
|
516
|
+
_textColor: '#e0e0e0'
|
|
517
|
+
},
|
|
518
|
+
light: {
|
|
519
|
+
_textColor: '#333' // Explicit text color for light theme
|
|
520
|
+
}
|
|
550
521
|
};
|
|
551
522
|
|
|
552
523
|
let css = '';
|
|
553
524
|
for (const [tag, style] of Object.entries(styles)) {
|
|
554
525
|
if (style) {
|
|
555
|
-
|
|
526
|
+
let themedStyle = style;
|
|
527
|
+
|
|
528
|
+
// Apply theme overrides if dark theme
|
|
529
|
+
if (theme === 'dark' && themeOverrides.dark) {
|
|
530
|
+
// Replace colors
|
|
531
|
+
for (const [oldColor, newColor] of Object.entries(themeOverrides.dark)) {
|
|
532
|
+
if (!oldColor.startsWith('_')) {
|
|
533
|
+
themedStyle = themedStyle.replace(new RegExp(oldColor, 'g'), newColor);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Add text color for certain elements in dark theme
|
|
538
|
+
const needsTextColor = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'li', 'blockquote'];
|
|
539
|
+
if (needsTextColor.includes(tag)) {
|
|
540
|
+
themedStyle += `;color:${themeOverrides.dark._textColor}`;
|
|
541
|
+
}
|
|
542
|
+
} else if (theme === 'light' && themeOverrides.light) {
|
|
543
|
+
// Add explicit text color for light theme elements too
|
|
544
|
+
const needsTextColor = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'li', 'blockquote'];
|
|
545
|
+
if (needsTextColor.includes(tag)) {
|
|
546
|
+
themedStyle += `;color:${themeOverrides.light._textColor}`;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
css += `.${prefix}${tag} { ${themedStyle} }\n`;
|
|
556
551
|
}
|
|
557
552
|
}
|
|
558
553
|
|