| 1 | |
| 2 | function escapeHtml(s: string): string { |
| 3 | return s |
| 4 | .replace(/&/g, "&") |
| 5 | .replace(/</g, "<") |
| 6 | .replace(/>/g, ">"); |
| 7 | } |
| 8 | |
| 9 | function inline(s: string): string { |
| 10 | return escapeHtml(s) |
| 11 | .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>") |
| 12 | .replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, "$1<em>$2</em>") |
| 13 | .replace(/`([^`]+)`/g, '<code class="ml-code">$1</code>'); |
| 14 | } |
| 15 | |
| 16 | export function renderMarkdownLite(src: string): string { |
| 17 | if (!src) return ""; |
| 18 | const lines = src.replace(/\r\n/g, "\n").split("\n"); |
| 19 | const out: string[] = []; |
| 20 | let list: { type: "ul" | "ol"; items: string[] } | null = null; |
| 21 | let para: string[] = []; |
| 22 | |
| 23 | const flushPara = () => { |
| 24 | if (para.length) { |
| 25 | out.push(`<p>${inline(para.join(" "))}</p>`); |
| 26 | para = []; |
| 27 | } |
| 28 | }; |
| 29 | const flushList = () => { |
| 30 | if (list) { |
| 31 | const inner = list.items.map((it) => `<li>${inline(it)}</li>`).join(""); |
| 32 | out.push(`<${list.type} class="ml-list">${inner}</${list.type}>`); |
| 33 | list = null; |
| 34 | } |
| 35 | }; |
| 36 | |
| 37 | for (const raw of lines) { |
| 38 | const line = raw.trimEnd(); |
| 39 | const ulm = line.match(/^\s*[-*+]\s+(.*)$/); |
| 40 | const olm = line.match(/^\s*\d+\.\s+(.*)$/); |
| 41 | if (ulm) { |
| 42 | flushPara(); |
| 43 | if (!list || list.type !== "ul") { flushList(); list = { type: "ul", items: [] }; } |
| 44 | list.items.push(ulm[1]); |
| 45 | } else if (olm) { |
| 46 | flushPara(); |
| 47 | if (!list || list.type !== "ol") { flushList(); list = { type: "ol", items: [] }; } |
| 48 | list.items.push(olm[1]); |
| 49 | } else if (line.trim() === "") { |
| 50 | flushPara(); |
| 51 | flushList(); |
| 52 | } else { |
| 53 | flushList(); |
| 54 | para.push(line.replace(/^#{1,6}\s*/, "")); |
| 55 | } |
| 56 | } |
| 57 | flushPara(); |
| 58 | flushList(); |
| 59 | return out.join(""); |
| 60 | } |