27 lines
1.6 KiB
JavaScript
27 lines
1.6 KiB
JavaScript
function escapeHtml(value) {
|
|
return value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[char]));
|
|
}
|
|
function inline(value) {
|
|
return escapeHtml(value)
|
|
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
|
|
.replace(/~~([^~]+)~~/g, "<s>$1</s>")
|
|
.replace(/\*([^*]+)\*/g, "<em>$1</em>")
|
|
.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
}
|
|
export function renderMarkdown(source) {
|
|
let html = "", inCode = false, list = null;
|
|
const closeList = () => { if (list) { html += `</${list}>`; list = null; } };
|
|
for (const line of source.split("\n")) {
|
|
if (line.startsWith("```")) { closeList(); html += inCode ? "</code></pre>" : "<pre><code>"; inCode = !inCode; continue; }
|
|
if (inCode) { html += `${escapeHtml(line)}\n`; continue; }
|
|
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
|
const ul = line.match(/^\s*[-*+]\s+(.+)$/);
|
|
const ol = line.match(/^\s*\d+\.\s+(.+)$/);
|
|
if (heading) { closeList(); const n = heading[1].length; html += `<h${n}>${inline(heading[2])}</h${n}>`; }
|
|
else if (ul || ol) { const type = ul ? "ul" : "ol"; if (list !== type) { closeList(); html += `<${type}>`; list = type; } html += `<li>${inline((ul || ol)[1])}</li>`; }
|
|
else { closeList(); if (/^---+$/.test(line)) html += "<hr>"; else if (line.startsWith("> ")) html += `<blockquote>${inline(line.slice(2))}</blockquote>`; else if (line.trim()) html += `<p>${inline(line)}</p>`; else html += "<br>"; }
|
|
}
|
|
closeList(); if (inCode) html += "</code></pre>"; return html;
|
|
}
|