export function markdownToHTML(text) {
// 0) Remove .../... blocks
text = text.replace(
/(^|\n)\s*[\s\S]*?<\/think(?:ing)?>\s*(\n\s*\n)?/gi,
(_, lead) => (lead ? '\n' : '')
);
// 1) Normalize line endings
let tmp = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
// 2) Extract code blocks and replace with placeholders
const codeblocks = [];
const placeholder = idx => `@@CODEBLOCK${idx}@@`;
tmp = tmp.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
codeblocks.push({ lang, code });
return placeholder(codeblocks.length - 1);
});
// 3) HTML-escape special characters
let escaped = tmp
.replace(/&/g, "&")
.replace(//g, ">");
// 4) Headings
escaped = escaped
.replace(/^#### (.+)$/gm, "$1
")
.replace(/^### (.+)$/gm, "$1
")
.replace(/^## (.+)$/gm, "$1
")
.replace(/^# (.+)$/gm, "$1
");
// 4.5) Unordered lists
escaped = escaped.replace(
/(^|\n)([ \t]*\* .+(?:\n[ \t]*\* .+)*)/g,
(_, lead, listBlock) => {
const items = listBlock
.split(/\n/)
.map(line => line.replace(/^[ \t]*\*\s+/, '').trim())
.map(item => `${item}`)
.join('');
return `${lead}`;
}
);
// 5) Bold, italic, inline code
let html = escaped
.replace(/\*\*(.+?)\*\*/g, "$1")
.replace(/(?$1")
.replace(/`(.+?)`/g, "$1");
// 6) Restore code blocks
html = html.replace(/@@CODEBLOCK(\d+)@@/g, (_, idx) => {
const { lang, code } = codeblocks[+idx];
const escapedCode = code.replace(//g, ">");
return `${escapedCode}
`;
});
// 7) Convert line-breaks to
html = html.replace(/\n/g, "
");
// 8) Cleanup stray
immediately before/after lists
html = html
.replace(/
\s*()/g, "$1")
.replace(/(<\/ul>)\s*
/g, "$1");
return html;
}