// Tiny DOM helper + the IDE's safe Markdown renderer (adapted from ide/ui/js/markdown.js): DOM nodes only, never innerHTML. // Classic script: window.IdeMd = {el, clear, renderMarkdown}; also CommonJS-exportable for node tests. (function (root, factory) { const api = factory(); if (typeof module === 'object' && module.exports) module.exports = api; else root.IdeMd = api; })(typeof window !== 'undefined' ? window : globalThis, function () { /** el('div.a.b', {attrs, onclick}, ...children) */ function el(spec, attrs, ...kids) { const m = /^([a-z0-9]*)((?:\.[\w-]+)*)$/i.exec(spec); const node = document.createElement((m && m[1]) || 'div'); if (m && m[2]) node.className = m[2].slice(1).replace(/\./g, ' '); for (const [k, v] of Object.entries(attrs || {})) { if (v === undefined || v === null || v === false) continue; if (k.startsWith('on') && typeof v === 'function') node.addEventListener(k.slice(2), v); else if (k === 'text') node.textContent = v; else node.setAttribute(k, v === true ? '' : v); } for (const kid of kids.flat()) if (kid !== null && kid !== undefined && kid !== false) node.append(kid); return node; } const clear = (n) => { while (n.firstChild) n.removeChild(n.firstChild); return n; }; const INLINE = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*\s][^*]*\*|\[[^\]]+\]\([^)\s]+\))/g; function inline(text) { const out = []; for (const [n, chunk] of text.split(INLINE).entries()) { if (n % 2 === 0) { if (chunk) out.push(chunk); continue; } if (chunk.startsWith('`')) out.push(el('code', {}, chunk.slice(1, -1))); else if (chunk.startsWith('**')) out.push(el('b', {}, chunk.slice(2, -2))); else if (chunk.startsWith('*')) out.push(el('i', {}, chunk.slice(1, -1))); else { const m = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(chunk); if (m && /^https?:\/\//.test(m[2])) out.push(el('a', { href: m[2], target: '_blank', rel: 'noopener noreferrer' }, m[1])); else out.push(m ? m[1] : chunk); } } return out; } function renderMarkdown(text) { const nodes = []; const lines = String(text || '').replace(/\r\n?/g, '\n').split('\n'); const LIST = /^\s*([-*]|\d+\.)\s+/; let i = 0; while (i < lines.length) { const line = lines[i]; if (/^```/.test(line)) { const code = []; for (i++; i < lines.length && !/^```/.test(lines[i]); i++) code.push(lines[i]); i++; nodes.push(el('pre.code', {}, code.join('\n'))); } else if (/^#{1,4}\s/.test(line)) { const level = /^#+/.exec(line)[0].length; nodes.push(el(`h${Math.min(5, level + 1)}`, {}, ...inline(line.replace(/^#+\s*/, '')))); i++; } else if (/^\s*\|.*\|\s*$/.test(line) && /^\s*\|[\s:|-]+\|\s*$/.test(lines[i + 1] || '')) { const cells = (row) => row.trim().replace(/^\||\|$/g, '').split('|').map((c) => c.trim()); const head = cells(line); const rows = []; for (i += 2; i < lines.length && /^\s*\|.*\|\s*$/.test(lines[i]); i++) rows.push(cells(lines[i])); nodes.push(el('div.md-table-wrap', {}, el('table.md-table', {}, el('thead', {}, el('tr', {}, ...head.map((c) => el('th', {}, ...inline(c))))), el('tbody', {}, ...rows.map((r) => el('tr', {}, ...r.map((c) => el('td', {}, ...inline(c))))))))); } else if (LIST.test(line)) { const ordered = /^\s*\d+\./.test(line); const items = []; for (; i < lines.length && LIST.test(lines[i]); i++) { let item = lines[i].replace(LIST, ''); while (i + 1 < lines.length && /^\s{2,}\S/.test(lines[i + 1]) && !LIST.test(lines[i + 1])) item += ' ' + lines[++i].trim(); items.push(el('li', {}, ...inline(item))); } nodes.push(el(ordered ? 'ol' : 'ul', {}, ...items)); } else if (/^\s*([-*_])\s*\1\s*\1[-*_\s]*$/.test(line)) { nodes.push(el('hr')); i++; } else if (/^>\s?/.test(line)) { const quote = []; for (; i < lines.length && /^>\s?/.test(lines[i]); i++) quote.push(lines[i].replace(/^>\s?/, '')); nodes.push(el('blockquote', {}, ...renderMarkdown(quote.join('\n')))); } else if (!line.trim()) { i++; } else { const para = []; for (; i < lines.length && lines[i].trim() && !/^(```|>|#{1,4}\s|\s*([-*]|\d+\.)\s+|\s*\|)/.test(lines[i]); i++) para.push(lines[i].trim()); nodes.push(el('p', {}, ...inline(para.join(' ')))); } } return nodes; } return { el, clear, renderMarkdown }; });