function escapeHtml(value) { const map = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; return String(value || "").replace(/[&<>"']/g, (char) => map[char]); } function safeUrl(value) { const raw = String(value || "").trim(); if (!raw) return ""; if (/^(https?:\/\/|\/(?!\/)|#)/i.test(raw)) return escapeHtml(raw); return ""; } function renderMarkdownInline(value) { let output = escapeHtml(value); output = output.replace(/`([^`]+)`/g, "$1"); output = output.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, url) => { const href = safeUrl(url); return href ? `${escapeHtml(text)}` : escapeHtml(text); }); output = output.replace(/\*\*([^*]+)\*\*/g, "$1"); output = output.replace(/\*([^*]+)\*/g, "$1"); return output; } function renderMarkdown(value) { const lines = String(value || "").replace(/\r\n?/g, "\n").split("\n"); let html = ""; let paragraph = []; let listType = null; let inCode = false; let codeLang = ""; let codeLines = []; const flushParagraph = () => { if (!paragraph.length) return; html += `

${renderMarkdownInline(paragraph.join(" "))}

`; paragraph = []; }; const closeList = () => { if (!listType) return; html += ``; listType = null; }; for (const line of lines) { const trimmed = line.trim(); if (inCode) { if (trimmed.startsWith("```")) { const langClass = codeLang ? ` class="language-${escapeHtml(codeLang)}"` : ""; html += `
${escapeHtml(codeLines.join("\n"))}
`; inCode = false; codeLang = ""; codeLines = []; continue; } codeLines.push(line); continue; } if (trimmed.startsWith("```")) { flushParagraph(); closeList(); inCode = true; codeLang = trimmed.slice(3).trim().replace(/[^a-z0-9_-]/gi, "").slice(0, 32); continue; } if (!trimmed) { flushParagraph(); closeList(); continue; } const heading = trimmed.match(/^(#{1,6})\s+(.*)$/); if (heading) { flushParagraph(); closeList(); const level = heading[1].length; html += `${renderMarkdownInline(heading[2])}`; continue; } const list = trimmed.match(/^([*-]|\d+\.)\s+(.*)$/); if (list) { flushParagraph(); const nextType = list[1].endsWith(".") ? "ol" : "ul"; if (listType && listType !== nextType) closeList(); if (!listType) { listType = nextType; html += `<${listType}>`; } html += `
  • ${renderMarkdownInline(list[2])}
  • `; continue; } paragraph.push(trimmed); } if (inCode) { const langClass = codeLang ? ` class="language-${escapeHtml(codeLang)}"` : ""; html += `
    ${escapeHtml(codeLines.join("\n"))}
    `; } flushParagraph(); closeList(); return html || "

    "; } module.exports = { escapeHtml, renderMarkdown, renderMarkdownInline, safeUrl };