(markdown, currentRel)
| 190 | } |
| 191 | |
| 192 | function markdownToHtml(markdown, currentRel) { |
| 193 | const lines = markdown.replace(/\r\n/g, "\n").split("\n"); |
| 194 | const html = []; |
| 195 | let paragraph = []; |
| 196 | let list = null; |
| 197 | let fence = null; |
| 198 | |
| 199 | const flushParagraph = () => { |
| 200 | if (!paragraph.length) return; |
| 201 | html.push(`<p>${inline(paragraph.join(" "), currentRel)}</p>`); |
| 202 | paragraph = []; |
| 203 | }; |
| 204 | const closeList = () => { |
| 205 | if (!list) return; |
| 206 | html.push(`</${list}>`); |
| 207 | list = null; |
| 208 | }; |
| 209 | const splitRow = (line) => |
| 210 | line |
| 211 | .replace(/^\s*\|/, "") |
| 212 | .replace(/\|\s*$/, "") |
| 213 | .split("|") |
| 214 | .map((s) => s.trim()); |
| 215 | const isDivider = (line) => /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$/.test(line); |
| 216 | |
| 217 | for (let i = 0; i < lines.length; i++) { |
| 218 | const line = lines[i]; |
| 219 | if (line.trim().startsWith("<img ")) { |
| 220 | flushParagraph(); |
| 221 | closeList(); |
| 222 | continue; |
| 223 | } |
| 224 | const fenceMatch = line.match(/^```(\w+)?\s*$/); |
| 225 | if (fenceMatch) { |
| 226 | flushParagraph(); |
| 227 | closeList(); |
| 228 | if (fence) { |
| 229 | html.push( |
| 230 | `<pre><code class="language-${fence.lang}">${escapeHtml(fence.lines.join("\n"))}</code></pre>`, |
| 231 | ); |
| 232 | fence = null; |
| 233 | } else { |
| 234 | fence = { lang: fenceMatch[1] || "text", lines: [] }; |
| 235 | } |
| 236 | continue; |
| 237 | } |
| 238 | if (fence) { |
| 239 | fence.lines.push(line); |
| 240 | continue; |
| 241 | } |
| 242 | if (!line.trim()) { |
| 243 | flushParagraph(); |
| 244 | closeList(); |
| 245 | continue; |
| 246 | } |
| 247 | const heading = line.match(/^(#{1,4})\s+(.+)$/); |
| 248 | if (heading) { |
| 249 | flushParagraph(); |
no test coverage detected