diff --git a/web/src/lib/components/repo/fileAnchor.svelte.ts b/web/src/lib/components/repo/fileAnchor.svelte.ts index 2e87c1f1c..f338b902e 100644 --- a/web/src/lib/components/repo/fileAnchor.svelte.ts +++ b/web/src/lib/components/repo/fileAnchor.svelte.ts @@ -1,11 +1,25 @@ +import type { SelectedLineRange } from "@pierre/diffs"; import { browser } from "$app/env"; +import { diffLineAnchor, parseDiffLineAnchor } from "#lib/components/repo/lineAnchor.ts"; const PREFIX = "#file-"; -export const fileAnchor = (name: string): string => PREFIX + encodeURIComponent(name); +export const fileAnchor = (name: string, range?: SelectedLineRange | null): string => + PREFIX + encodeURIComponent(name) + (range ? `:${diffLineAnchor(range)}` : ""); -const fileFromAnchor = (hash: string): string | undefined => - hash.startsWith(PREFIX) ? decodeURIComponent(hash.slice(PREFIX.length)) : undefined; +const fileFromAnchor = ( + hash: string +): { name: string; range: SelectedLineRange | null } | undefined => { + if (!hash.startsWith(PREFIX)) return undefined; + const body = hash.slice(PREFIX.length); + const split = body.lastIndexOf(":"); + const range = split < 0 ? null : parseDiffLineAnchor(body.slice(split + 1)); + try { + return { name: decodeURIComponent(range ? body.slice(0, split) : body), range }; + } catch { + return undefined; + } +}; const SETTLE_MS = 250; const ATTEMPTS = 60; @@ -15,7 +29,9 @@ const SCROLL_KEYS = new Set(["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Spac /** * Re-aligns deep-linked file anchor while streamed hunks settle layout heights. */ -export const jumpToAnchoredFile = (jump: (name: string) => number | undefined): void => { +export const jumpToAnchoredFile = ( + jump: (name: string, range: SelectedLineRange | null) => number | undefined +): void => { let hash = $state(browser ? location.hash : ""); let landed: string | undefined; @@ -29,8 +45,9 @@ export const jumpToAnchoredFile = (jump: (name: string) => number | undefined): }); $effect(() => { - const name = fileFromAnchor(hash); - if (!name || name === landed) return; + const target = fileFromAnchor(hash); + const anchor = hash; + if (!target || anchor === landed) return; let attempts = 0; let previous: number | undefined; @@ -38,20 +55,22 @@ export const jumpToAnchoredFile = (jump: (name: string) => number | undefined): clearInterval(timer); window.removeEventListener("wheel", give); window.removeEventListener("keydown", onKey); + window.removeEventListener("pointerdown", give); }; const onKey = (event: KeyboardEvent) => { if (SCROLL_KEYS.has(event.key)) give(); }; const settle = () => { - const at = jump(name); - if (at != null && at === previous) landed = name; + const at = jump(target.name, target.range); + if (at != null && at === previous) landed = anchor; previous = at; - if (landed === name || ++attempts > ATTEMPTS) give(); + if (landed === anchor || ++attempts > ATTEMPTS) give(); }; const timer = setInterval(settle, SETTLE_MS); settle(); window.addEventListener("wheel", give, { passive: true }); window.addEventListener("keydown", onKey); + window.addEventListener("pointerdown", give); return give; }); };