function escapeHtml(input: string): string {
return input
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function formatInline(input: string): string {
let output = input;
output = output.replace(/`([^`]+)`/g, "$1");
output = output.replace(/\*\*([^*]+)\*\*/g, "$1");
output = output.replace(/\*([^*]+)\*/g, "$1");
output = output.replace(/_([^_]+)_/g, "$1");
output = output.replace(
/\[([^\]]+)\]\(([^)]+)\)/g,
'$1',
);
return output;
}
export function renderMarkdown(markdown: string): string {
const source = markdown || "";
const codeBlocks: string[] = [];
let content = source.replace(
/```(?:[\w-]+)?\n([\s\S]*?)```/g,
(_match, code) => {
const escaped = escapeHtml(code.trimEnd());
const token = `@@CODEBLOCK_${codeBlocks.length}@@`;
codeBlocks.push(`
${escaped}`);
return token;
},
);
content = escapeHtml(content);
const lines = content.split(/\r?\n/);
const blocks: string[] = [];
let inList = false;
const closeList = () => {
if (inList) {
blocks.push("");
inList = false;
}
};
for (const rawLine of lines) {
const line = rawLine.trim();
if (!line) {
closeList();
continue;
}
const headingMatch = line.match(/^(#{1,6})\s+(.*)$/);
if (headingMatch) {
closeList();
const level = headingMatch[1].length;
blocks.push(
`${formatInline(line)}
`); } closeList(); let html = blocks.join("\n"); codeBlocks.forEach((blockHtml, index) => { html = html.replace(`@@CODEBLOCK_${index}@@`, blockHtml); }); return html; }