75 lines
1.8 KiB
TypeScript
75 lines
1.8 KiB
TypeScript
|
|
const esc = (s: string) =>
|
|
s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
|
|
function inline(s: string): string {
|
|
return esc(s)
|
|
.replace(/`([^`]+)`/g, (_, c) => `<code>${c}</code>`)
|
|
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
|
|
.replace(/(^|[\s(])_([^_]+)_/g, "$1<em>$2</em>")
|
|
.replace(
|
|
/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
|
|
'<a href="$2" target="_blank" rel="noopener">$1</a>',
|
|
);
|
|
}
|
|
|
|
export function renderMarkdown(src: string): string {
|
|
const lines = src.replace(/\r\n/g, "\n").split("\n");
|
|
const out: string[] = [];
|
|
let i = 0;
|
|
|
|
while (i < lines.length) {
|
|
const line = lines[i];
|
|
|
|
if (line.trim() === "") {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (line.trim().startsWith("```")) {
|
|
const buf: string[] = [];
|
|
i++;
|
|
while (i < lines.length && !lines[i].trim().startsWith("```")) {
|
|
buf.push(esc(lines[i]));
|
|
i++;
|
|
}
|
|
i++;
|
|
out.push(`<pre><code>${buf.join("\n")}</code></pre>`);
|
|
continue;
|
|
}
|
|
|
|
const h = line.match(/^(#{2,4})\s+(.*)$/);
|
|
if (h) {
|
|
const level = h[1].length;
|
|
out.push(`<h${level}>${inline(h[2])}</h${level}>`);
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (/^\s*[-*]\s+/.test(line)) {
|
|
const items: string[] = [];
|
|
while (i < lines.length && /^\s*[-*]\s+/.test(lines[i])) {
|
|
items.push(`<li>${inline(lines[i].replace(/^\s*[-*]\s+/, ""))}</li>`);
|
|
i++;
|
|
}
|
|
out.push(`<ul>${items.join("")}</ul>`);
|
|
continue;
|
|
}
|
|
|
|
const para: string[] = [];
|
|
while (
|
|
i < lines.length &&
|
|
lines[i].trim() !== "" &&
|
|
!lines[i].trim().startsWith("```") &&
|
|
!/^(#{2,4})\s+/.test(lines[i]) &&
|
|
!/^\s*[-*]\s+/.test(lines[i])
|
|
) {
|
|
para.push(lines[i]);
|
|
i++;
|
|
}
|
|
out.push(`<p>${inline(para.join(" "))}</p>`);
|
|
}
|
|
|
|
return out.join("\n");
|
|
}
|