import { ClientOnly } from "@tanstack/react-router"; import { File } from "@pierre/diffs/react"; import type { SelectedLineRange } from "@pierre/diffs"; import { useEffect, useMemo, useRef, useState } from "react"; const FALLBACK_LINES = 40; const LINE_HASH = /^#L(\d+)(?:-L?(\d+))?$/; const SCROLL_ATTEMPTS = 20; const SCROLL_RETRY_MS = 100; /** * `disableFileHeader` leaves the shadow root's
empty (a @pierre/diffs bug), so the built-in
* header is hidden with CSS instead; the page already shows the file name and size above.
*/
const BASE_OPTIONS = {
theme: "pierre-dark",
overflow: "scroll",
enableLineSelection: true,
unsafeCSS: "[data-diffs-header] { display: none; }",
} as const;
/** `#L12` or `#L12-L20` (also `#L12-20`), the GitHub-style line anchor. */
export function parseLineHash(hash: string): SelectedLineRange | null {
const match = LINE_HASH.exec(hash);
if (!match) return null;
const start = Number(match[1]);
const end = match[2] ? Number(match[2]) : start;
return start > 0 ? { start: Math.min(start, end), end: Math.max(start, end) } : null;
}
function lineHash(range: SelectedLineRange): string {
return range.start === range.end ? `#L${range.start}` : `#L${range.start}-L${range.end}`;
}
function PlainFallback({ contents }: { contents: string }) {
return (
{contents.split("\n").slice(0, FALLBACK_LINES).join("\n")}
);
}
/** The file renders into a shadow root after highlighting, so the target line is polled for briefly. */
function scrollToLine(container: HTMLElement | null, line: number) {
let attempts = 0;
const tryScroll = () => {
const host = container?.querySelector("diffs-container");
const row = host?.shadowRoot?.querySelector(`[data-line="${line}"]`);
if (row) row.scrollIntoView({ block: "center" });
else if (attempts++ < SCROLL_ATTEMPTS) setTimeout(tryScroll, SCROLL_RETRY_MS);
};
tryScroll();
}
function HighlightedFile({ name, contents }: { name: string; contents: string }) {
const container = useRef(null);
const [selected, setSelected] = useState(() => parseLineHash(window.location.hash));
useEffect(() => {
const initial = parseLineHash(window.location.hash);
if (initial) scrollToLine(container.current, initial.start);
const onHash = () => {
const range = parseLineHash(window.location.hash);
setSelected(range);
if (range) scrollToLine(container.current, range.start);
};
window.addEventListener("hashchange", onHash);
return () => window.removeEventListener("hashchange", onHash);
}, []);
const options = useMemo(
() => ({
...BASE_OPTIONS,
onLineSelected: (range: SelectedLineRange | null) => {
setSelected(range);
const url = new URL(window.location.href);
url.hash = range ? lineHash(range) : "";
window.history.replaceState(window.history.state, "", url);
},
}),
[],
);
return (
);
}
export function CodeFile({ name, contents }: { name: string; contents: string }) {
return (
}>
);
}