(function () {
const tokenPattern = /%%MDTOKEN(\d+)%%/g;
function escapeHtml(value) {
return String(value || "").replace(/[&<>"']/g, (char) => ({
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
})[char]);
}
function sanitizeUrl(value) {
const url = String(value || "").trim().replace(/[\u0000-\u001f\u007f\s]+/g, "");
if (/^(https?:\/\/|\/|\.\/|\.\.\/|uploads\/|mailto:|#)/i.test(url)) {
return escapeHtml(url);
}
return "#";
}
function storeToken(tokens, html) {
const token = `%%MDTOKEN${tokens.length}%%`;
tokens.push(html);
return token;
}
function renderInline(markdown) {
const tokens = [];
let text = String(markdown || "");
text = text.replace(/`([^`]+)`/g, (_, code) =>
storeToken(tokens, `${escapeHtml(code)}`),
);
text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, url) =>
storeToken(
tokens,
``,
),
);
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, url) =>
storeToken(
tokens,
`${escapeHtml(label)}`,
),
);
text = escapeHtml(text)
.replace(/\*\*([^*]+)\*\*/g, "$1")
.replace(/__([^_]+)__/g, "$1")
.replace(/\*([^*]+)\*/g, "$1")
.replace(/_([^_]+)_/g, "$1");
return text.replace(tokenPattern, (_, index) => tokens[Number(index)] || "");
}
function renderList(lines) {
const items = lines
.map((line) => line.replace(/^\s*[-*]\s+/, ""))
.map((line) => `
${renderInline(paragraph.join(" "))}
`); paragraph = []; } function flushList() { if (!list.length) return; blocks.push(renderList(list)); list = []; } lines.forEach((line) => { if (line.trim().startsWith("```")) { if (codeBlock) { blocks.push(`${escapeHtml(codeBlock.join("\n"))}`);
codeBlock = null;
} else {
flushParagraph();
flushList();
codeBlock = [];
}
return;
}
if (codeBlock) {
codeBlock.push(line);
return;
}
if (!line.trim()) {
flushParagraph();
flushList();
return;
}
const heading = line.match(/^(#{1,3})\s+(.+)$/);
if (heading) {
flushParagraph();
flushList();
const level = heading[1].length + 2;
blocks.push(`${escapeHtml(codeBlock.join("\n"))}`);
}
return blocks.join("");
}
window.renderMarkdown = renderMarkdown;
})();