function escapeHtml(value: string): string { return value .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function formatInline(value: string): string { return escapeHtml(value) .replace(/`([^`]+)`/g, "$1") .replace(/\*\*([^*]+)\*\*/g, "$1"); } function isTableSeparator(line: string): boolean { const cells = line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|"); return cells.length > 1 && cells.every((cell) => /^:?-{3,}:?$/.test(cell.trim())); } function splitTableRow(line: string): string[] { return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim()); } function renderTable(lines: string[]): string { const headers = splitTableRow(lines[0] ?? ""); const rows = lines.slice(2).map(splitTableRow); const head = headers.map((cell) => `${formatInline(cell)}`).join(""); const body = rows .map((row) => `${row.map((cell) => `${formatInline(cell)}`).join("")}`) .join(""); return `
${head}${body}
`; } function renderList(lines: string[]): string { const items = lines.map((line) => line.replace(/^\s*[-*]\s+/, "")); return ``; } function renderParagraph(lines: string[]): string { return `

${lines.map((line) => formatInline(line.trim())).join("
")}

`; } export function markdownToHtml(md: string): string { const lines = md.replace(/\r\n/g, "\n").split("\n"); const blocks: string[] = []; let index = 0; while (index < lines.length) { const line = lines[index] ?? ""; const trimmed = line.trim(); if (!trimmed) { index += 1; continue; } if (trimmed.startsWith("```")) { const codeLines: string[] = []; index += 1; while (index < lines.length && !(lines[index] ?? "").trim().startsWith("```")) { codeLines.push(lines[index] ?? ""); index += 1; } index += 1; blocks.push(`
${escapeHtml(codeLines.join("\n"))}
`); continue; } const heading = /^(#{1,4})\s+(.+)$/.exec(trimmed); if (heading) { const level = heading[1].length; blocks.push(`${formatInline(heading[2])}`); index += 1; continue; } if (trimmed.includes("|") && index + 1 < lines.length && isTableSeparator(lines[index + 1] ?? "")) { const tableLines = [line, lines[index + 1] ?? ""]; index += 2; while (index < lines.length && (lines[index] ?? "").trim().includes("|")) { tableLines.push(lines[index] ?? ""); index += 1; } blocks.push(renderTable(tableLines)); continue; } if (/^\s*[-*]\s+/.test(line)) { const listLines = [line]; index += 1; while (index < lines.length && /^\s*[-*]\s+/.test(lines[index] ?? "")) { listLines.push(lines[index] ?? ""); index += 1; } blocks.push(renderList(listLines)); continue; } const paragraphLines = [line]; index += 1; while ( index < lines.length && (lines[index] ?? "").trim() && !/^(#{1,4})\s+/.test((lines[index] ?? "").trim()) && !/^\s*[-*]\s+/.test(lines[index] ?? "") && !((lines[index] ?? "").trim().includes("|") && index + 1 < lines.length && isTableSeparator(lines[index + 1] ?? "")) ) { paragraphLines.push(lines[index] ?? ""); index += 1; } blocks.push(renderParagraph(paragraphLines)); } return blocks.join(""); } export function formatMarkdown(text: string): string { return markdownToHtml(text); }