Guard markdown preview anchor decoding

This commit is contained in:
Neil
2026-05-30 04:11:21 -07:00
parent fb32a9cd39
commit 70f8e7a3f2
2 changed files with 14 additions and 1 deletions
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'
import type { Worktree } from '../../../../shared/types'
import {
decodeMarkdownPreviewAnchor,
deriveMarkdownPreviewSourceRoot,
findMarkdownPreviewOpenedEditFileId,
findMarkdownPreviewSourceOpenFile,
@@ -31,6 +32,10 @@ function makeWorktree(id: string, path: string): Worktree {
}
describe('MarkdownPreview source link routing', () => {
it('falls back to the raw anchor when percent-decoding fails', () => {
expect(decodeMarkdownPreviewAnchor('%E0%A4%A')).toBe('%E0%A4%A')
})
it('keeps the explicit source worktree when it exists', () => {
const source = makeWorktree('wt-source', '/repo')
const nested = makeWorktree('wt-nested', '/repo/packages/app')
@@ -315,6 +315,14 @@ function parseLineTarget(hash: string): { line: number; column?: number } | null
return { line: Number(match[1]), column: match[2] ? Number(match[2]) : undefined }
}
export function decodeMarkdownPreviewAnchor(rawAnchor: string): string {
try {
return decodeURIComponent(rawAnchor)
} catch {
return rawAnchor
}
}
function normalizeMarkdownPreviewAbsolutePath(absolutePath: string): string {
return absolutePath.replaceAll('\\', '/')
}
@@ -684,7 +692,7 @@ export default function MarkdownPreview({
return false
}
const decodedAnchor = decodeURIComponent(rawAnchor)
const decodedAnchor = decodeMarkdownPreviewAnchor(rawAnchor)
let target: HTMLElement | null = null
for (const candidate of body.querySelectorAll<HTMLElement>('[id]')) {
if (candidate.id === decodedAnchor) {