(markdown)
| 2597 | } |
| 2598 | |
| 2599 | function renderMarkdown(markdown) { |
| 2600 | const lines = String(markdown || "").replace(/\r\n/g, "\n").split("\n"); |
| 2601 | const out = []; |
| 2602 | let paragraph = []; |
| 2603 | let listItems = []; |
| 2604 | let listType = ""; |
| 2605 | let quoteLines = []; |
| 2606 | let inCode = false; |
| 2607 | let codeLines = []; |
| 2608 | |
| 2609 | const renderInlineMarkdown = (text) => { |
| 2610 | let html = escapeHtml(text || ""); |
| 2611 | html = html.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noreferrer">$1</a>'); |
| 2612 | html = html.replace(/`([^`]+)`/g, "<code>$1</code>"); |
| 2613 | html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>"); |
| 2614 | html = html.replace(/(^|[\s(])(https?:\/\/[^\s<)]+)/g, '$1<a href="$2" target="_blank" rel="noreferrer">$2</a>'); |
| 2615 | return html; |
| 2616 | }; |
| 2617 | const flushParagraph = () => { |
| 2618 | if (!paragraph.length) return; |
| 2619 | out.push(`<p>${renderInlineMarkdown(paragraph.join(" "))}</p>`); |
| 2620 | paragraph = []; |
| 2621 | }; |
| 2622 | const flushList = () => { |
| 2623 | if (!listItems.length) return; |
| 2624 | out.push(`<${listType}>${listItems.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join("")}</${listType}>`); |
| 2625 | listItems = []; |
| 2626 | listType = ""; |
| 2627 | }; |
| 2628 | const flushQuote = () => { |
| 2629 | if (!quoteLines.length) return; |
| 2630 | out.push(`<blockquote>${quoteLines.map((item) => `<p>${renderInlineMarkdown(item)}</p>`).join("")}</blockquote>`); |
| 2631 | quoteLines = []; |
| 2632 | }; |
| 2633 | const flushCode = () => { |
| 2634 | if (!inCode) return; |
| 2635 | out.push(`<pre><code>${escapeHtml(codeLines.join("\n"))}</code></pre>`); |
| 2636 | inCode = false; |
| 2637 | codeLines = []; |
| 2638 | }; |
| 2639 | |
| 2640 | for (const rawLine of lines) { |
| 2641 | const line = rawLine.replace(/\t/g, " "); |
| 2642 | const trimmed = line.trim(); |
| 2643 | if (trimmed.startsWith("```")) { |
| 2644 | flushParagraph(); |
| 2645 | flushList(); |
| 2646 | flushQuote(); |
| 2647 | if (inCode) flushCode(); |
| 2648 | else { |
| 2649 | inCode = true; |
| 2650 | codeLines = []; |
| 2651 | } |
| 2652 | continue; |
| 2653 | } |
| 2654 | if (inCode) { |
| 2655 | codeLines.push(line); |
| 2656 | continue; |
no test coverage detected