From ce3ea2d28fa3356efdba5f6207045bc5eca139df Mon Sep 17 00:00:00 2001 From: plx Date: Sun, 2 Nov 2025 18:33:35 -0600 Subject: [PATCH 1/2] Escape inline markdown output --- src/lib/markdown.ts | 56 +++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts index 45512df..78668ca 100644 --- a/src/lib/markdown.ts +++ b/src/lib/markdown.ts @@ -1,37 +1,53 @@ export function renderInlineMarkdown(text: string): string { if (!text) return ""; - // Helper function to escape HTML entities - const escapeHtml = (str: string): string => { - return str + const escapeHtml = (str: string): string => + str .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); - }; - // Process inline markdown patterns - let html = text - // Code: `text` - escape content inside backticks, then wrap in - .replace(/`([^`]+)`/g, (_, content) => `${escapeHtml(content)}`) + const pattern = /(`([^`]+)`)|(\*\*([^*]+)\*\*)|(__([^_]+)__)|(~~([^~]+)~~)|(? - .replace(/\*\*([^*]+)\*\*/g, (_, content) => `${escapeHtml(content)}`) - .replace(/__([^_]+)__/g, (_, content) => `${escapeHtml(content)}`) + let result = ""; + let lastIndex = 0; - // Italic: *text* or _text_ (but not part of bold) - escape content, then wrap in - .replace(/(? `${escapeHtml(content)}`) - .replace(/(? `${escapeHtml(content)}`) + for (const match of text.matchAll(pattern)) { + const matchIndex = match.index ?? 0; + if (matchIndex > lastIndex) { + result += escapeHtml(text.slice(lastIndex, matchIndex)); + } - // Strikethrough: ~~text~~ - escape content, then wrap in - .replace(/~~([^~]+)~~/g, (_, content) => `${escapeHtml(content)}`); + if (match[1]) { + // `code` + result += `${escapeHtml(match[2] ?? "")}`; + } else if (match[3]) { + // **bold** + result += `${escapeHtml(match[4] ?? "")}`; + } else if (match[5]) { + // __bold__ + result += `${escapeHtml(match[6] ?? "")}`; + } else if (match[7]) { + // ~~strikethrough~~ + result += `${escapeHtml(match[8] ?? "")}`; + } else if (match[9]) { + // *italic* + result += `${escapeHtml(match[10] ?? "")}`; + } else if (match[11]) { + // _italic_ + result += `${escapeHtml(match[12] ?? "")}`; + } - // Escape any remaining unprocessed text (text outside of markdown patterns) - // This is tricky because we need to avoid escaping the HTML we just created - // For now, we'll leave plain text unescaped since Astro should handle it + lastIndex = matchIndex + match[0].length; + } - return html; + if (lastIndex < text.length) { + result += escapeHtml(text.slice(lastIndex)); + } + + return result; } /** From fa34b2be98ad9b68b49eff9f1c4bedaeb83e7a74 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 23:55:12 +0000 Subject: [PATCH 2/2] Fix underscore italic rendering by correcting regex group indices The regex pattern has 10 capturing groups total, but the code was checking match[11]/match[12] for underscore italics which don't exist. Additionally, asterisk italics were using match[10] instead of match[9]. Fixed both issues: - match[9] now correctly used for *italic* content - match[10] now correctly used for _italic_ content This prevents _italic_ text from being silently dropped from output. Co-authored-by: plx --- src/lib/markdown.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts index 78668ca..abb1b1c 100644 --- a/src/lib/markdown.ts +++ b/src/lib/markdown.ts @@ -34,10 +34,10 @@ export function renderInlineMarkdown(text: string): string { result += `${escapeHtml(match[8] ?? "")}`; } else if (match[9]) { // *italic* - result += `${escapeHtml(match[10] ?? "")}`; - } else if (match[11]) { + result += `${escapeHtml(match[9] ?? "")}`; + } else if (match[10]) { // _italic_ - result += `${escapeHtml(match[12] ?? "")}`; + result += `${escapeHtml(match[10] ?? "")}`; } lastIndex = matchIndex + match[0].length;