From 5aecdb415b61d5b937add6f89379cde88ed3a59c Mon Sep 17 00:00:00 2001 From: andyhrc Date: Thu, 3 Sep 2026 16:50:03 +0800 Subject: [PATCH] fix: decode URI-encoded local paths Decode MarkdownIt's URI escaping before resolving local links and images, while preserving malformed raw HTML URLs. --- src/composables/useLinkRewriter.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/composables/useLinkRewriter.ts b/src/composables/useLinkRewriter.ts index 33b6795..24a3cf5 100644 --- a/src/composables/useLinkRewriter.ts +++ b/src/composables/useLinkRewriter.ts @@ -37,6 +37,20 @@ function isExternal(url: string): boolean { return /^(https?:|data:|blob:|mailto:|tel:|asset:|tauri:)/i.test(url); } +/** Decode URI escaping added by MarkdownIt before using a local URL as a file path. */ +function resolveLocalPath(baseDir: string, urlPath: string): string { + let path = urlPath; + try { + path = decodeURIComponent(urlPath); + } catch { + // Raw HTML may contain a stray "%". Keep it unchanged instead of breaking + // every link rewrite in the rendered document. + } + return isAbsoluteWin(path) || isAbsoluteUnix(path) + ? path + : joinPath(baseDir, path); +} + export interface RewriteContext { currentFile: string; rootDir: string; @@ -55,8 +69,7 @@ export function rewriteImagesAndLinks( const src = img.getAttribute("src") || ""; if (!src || isExternal(src)) return; try { - const abs = - isAbsoluteWin(src) || isAbsoluteUnix(src) ? src : joinPath(baseDir, src); + const abs = resolveLocalPath(baseDir, src); img.src = convertFileSrc(abs); } catch { /* skip */ @@ -86,11 +99,8 @@ export function rewriteImagesAndLinks( const pathPart = hashIdx >= 0 ? href.slice(0, hashIdx) : href; const hash = hashIdx >= 0 ? href.slice(hashIdx + 1) : ""; if (!pathPart) return; - if (!/\.(md|markdown|mdx|txt)$/i.test(pathPart)) return; - const abs = - isAbsoluteWin(pathPart) || isAbsoluteUnix(pathPart) - ? pathPart - : joinPath(baseDir, pathPart); + const abs = resolveLocalPath(baseDir, pathPart); + if (!/\.(md|markdown|mdx|txt)$/i.test(abs)) return; a.addEventListener("click", (e) => { e.preventDefault(); onInternalLink(abs, hash);