Files

67 lines
2.4 KiB
JavaScript

// A tiny Markdown renderer for the rules. It supports exactly what the rules use: #, ##, ### headings,
// paragraphs, - and 1. lists (no nesting), **bold**, *italic*, `code`, pipe tables, --- and > quotes.
// Everything is escaped first, so a rules file can never inject markup.
import { esc } from "./desk.js";
function inline(text) {
return esc(text)
.replace(/`([^`]+)`/g, "<code>$1</code>")
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
.replace(/(^|[^*\w])\*([^*\s][^*]*?)\*(?![*\w])/g, "$1<em>$2</em>");
}
const cells = (line) => line.trim().replace(/^\||\|$/g, "").split("|").map((c) => c.trim());
export function renderMarkdown(src) {
const lines = src.replace(/\r\n?/g, "\n").split("\n");
const out = [];
let i = 0;
while (i < lines.length) {
const line = lines[i];
if (!line.trim()) { i++; continue; }
const h = /^(#{1,3})\s+(.*)$/.exec(line);
if (h) {
out.push(`<h${h[1].length}>${inline(h[2])}</h${h[1].length}>`);
i++;
continue;
}
if (/^-{3,}\s*$/.test(line)) { out.push("<hr>"); i++; continue; }
if (/^\|.*\|\s*$/.test(line) && /^\|[\s:|-]+\|\s*$/.test(lines[i + 1] ?? "")) {
const head = cells(line);
i += 2;
const rows = [];
while (i < lines.length && /^\|.*\|\s*$/.test(lines[i])) rows.push(cells(lines[i++]));
out.push(
`<table><thead><tr>${head.map((c) => `<th>${inline(c)}</th>`).join("")}</tr></thead>` +
`<tbody>${rows.map((r) => `<tr>${r.map((c) => `<td>${inline(c)}</td>`).join("")}</tr>`).join("")}</tbody></table>`,
);
continue;
}
if (/^>\s?/.test(line)) {
const quote = [];
while (i < lines.length && /^>\s?/.test(lines[i])) quote.push(lines[i++].replace(/^>\s?/, ""));
out.push(`<blockquote>${quote.map(inline).join(" ")}</blockquote>`);
continue;
}
const list = /^(-|\d+\.)\s+/.exec(line);
if (list) {
const tag = list[1] === "-" ? "ul" : "ol";
const re = tag === "ul" ? /^-\s+(.*)$/ : /^\d+\.\s+(.*)$/;
const items = [];
while (i < lines.length && re.test(lines[i])) items.push(re.exec(lines[i++])[1]);
out.push(`<${tag}>${items.map((x) => `<li>${inline(x)}</li>`).join("")}</${tag}>`);
continue;
}
const para = [];
while (i < lines.length && lines[i].trim() && !/^(#{1,3}\s|-{3,}\s*$|>|\||(-|\d+\.)\s)/.test(lines[i])) para.push(lines[i++]);
out.push(`<p>${inline(para.join(" "))}</p>`);
}
return out.join("\n");
}