diff --git a/frontend/src/utils/tags.ts b/frontend/src/utils/tags.ts index 2474319..05949b5 100644 --- a/frontend/src/utils/tags.ts +++ b/frontend/src/utils/tags.ts @@ -5,6 +5,17 @@ function escapeHtmlAttr(s: string): string { return s.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); } +// Decode HTML entities that marked introduces before we re-escape for our own output. +// Order matters: & must be last to avoid double-decoding. +function decodeHtmlEntities(s: string): string { + return s + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/&/g, "&"); +} + export function extractTags(body: string): string[] { const cleaned = body.replace(CODE_FENCE_RE, ""); const tags = new Set(); @@ -39,8 +50,8 @@ export function linkifyWikilinks(html: string): string { .map((part, i) => { if (i % 2 === 1) return part; return part.replace(WIKILINK_RE, (_full, title: string, display?: string) => { - const trimmed = title.trim(); - const label = display || trimmed; + const trimmed = decodeHtmlEntities(title.trim()); + const label = display ? decodeHtmlEntities(display) : trimmed; const encoded = encodeURIComponent(trimmed); return `${escapeHtmlAttr(label)}`; });