'use strict'; // 极简 Markdown → HTML(只覆盖 LLM 常用:粗体、链接、列表、段落、行内代码)。 // LibreDesk 的 live chat 消息按 HTML 渲染,直接发 Markdown 会显示成 ** 和 [text](url)。 function escapeHtml(text) { return String(text || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function inline(text) { const links = []; let out = text.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, (_, label, url) => { links.push(`${label}`); return `\u0000L${links.length - 1}\u0000`; }); out = out.replace(/\*\*([^*]+)\*\*/g, '$1'); out = out.replace(/`([^`]+)`/g, '$1'); // 裸 URL 自动转链接(占位符里还没有 http,避免重复处理 href) out = out.replace(/(https?:\/\/[^\s<]+)/g, '$1'); out = out.replace(/\u0000L(\d+)\u0000/g, (_, i) => links[Number(i)] || ''); return out; } function markdownToHtml(markdown) { const escaped = escapeHtml(markdown); const lines = escaped.split(/\r?\n/); const html = []; let paragraph = []; let list = null; // 'ul' | 'ol' const flushParagraph = () => { if (!paragraph.length) return; html.push(`

${paragraph.map(inline).join('
')}

`); paragraph = []; }; const closeList = () => { if (!list) return; html.push(``); list = null; }; for (const rawLine of lines) { const line = rawLine.trim(); if (!line) { flushParagraph(); closeList(); continue; } const bullet = /^[-*]\s+(.*)$/.exec(line); const numbered = /^\d+[.)]\s+(.*)$/.exec(line); if (bullet || numbered) { flushParagraph(); const kind = bullet ? 'ul' : 'ol'; if (list !== kind) { closeList(); list = kind; html.push(`<${kind}>`); } html.push(`
  • ${inline((bullet || numbered)[1])}
  • `); continue; } closeList(); const heading = /^#{1,3}\s+(.*)$/.exec(line); if (heading) { flushParagraph(); html.push(`

    ${inline(heading[1])}

    `); continue; } paragraph.push(line); } flushParagraph(); closeList(); return html.join(''); } module.exports = { markdownToHtml, escapeHtml };