Compare commits

..

2 Commits

Author SHA1 Message Date
bvandeusen 4aacd093e5 Merge pull request 'fix: decode HTML entities in wikilinks before re-escaping' (#11) from dev into main 2026-03-26 02:50:00 +00:00
bvandeusen aab478359b fix: decode HTML entities in wikilinks before re-escaping
marked encodes " to " in text nodes, causing linkifyWikilinks to
double-escape it (& → &) so the visible link text showed "
instead of the actual character.

Decode marked's entities on the matched title/label before running
escapeHtmlAttr so the output is correct in both the href attribute
and the visible link text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 22:40:25 -04:00
+13 -2
View File
@@ -5,6 +5,17 @@ function escapeHtmlAttr(s: string): string {
return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
// Decode HTML entities that marked introduces before we re-escape for our own output.
// Order matters: &amp; must be last to avoid double-decoding.
function decodeHtmlEntities(s: string): string {
return s
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&amp;/g, "&");
}
export function extractTags(body: string): string[] {
const cleaned = body.replace(CODE_FENCE_RE, "");
const tags = new Set<string>();
@@ -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 `<a class="wikilink" data-title="${escapeHtmlAttr(trimmed)}" href="/notes/by-title?title=${encoded}">${escapeHtmlAttr(label)}</a>`;
});