Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135<script lang="ts"> import { untrack } from "svelte"; import { File, type FileOptions, type SelectedLineRange } from "@pierre/diffs"; import { clearPierreRender, PIERRE_SELECTION_CSS, pierreFileOptions, prerenderFile } from "#lib/components/repo/pierre.ts"; import BlameOverlay from "#lib/components/repo/blame/BlameOverlay.svelte"; import LineHandles from "#lib/components/repo/LineHandles.svelte"; import { BlameHover } from "#lib/components/repo/blame/hover.svelte.ts"; import { getSsrBudget } from "#lib/ssr/render.ts"; import type { Blame } from "#lib/components/repo/blame/types.ts";
interface Props { // used for the header and to infer the highlighting language name: string; // null keeps the server markup up, unhydrated, until the text arrives contents: string | null; // pierre's native overflow: wrap keeps highlighting instead of scrolling wrap?: boolean; // forwarded to pierre, fires after every (async-safe) render pass onPostRender?: FileOptions<undefined, undefined>["onPostRender"]; // one run per stretch of lines sharing a commit, absent when blame is off blame?: Blame; selectedLines?: SelectedLineRange | null; onLineSelected?: (range: SelectedLineRange | null) => void; }
let { name, contents, wrap = false, onPostRender, blame, selectedLines = null, onLineSelected }: Props = $props();
let element: HTMLElement | undefined = $state(); let wrapper: HTMLElement | undefined = $state();
const hover = new BlameHover(() => wrapper); const budget = getSsrBudget();
const options = (forWrap: boolean): FileOptions<undefined, undefined> => ({ ...pierreFileOptions(forWrap), unsafeCSS: PIERRE_SELECTION_CSS, onPostRender, enableLineSelection: onLineSelected !== undefined, onLineSelected: (range) => onLineSelected?.(range), onLineEnter: ({ lineNumber, lineElement, event }) => { if (!blame || event.pointerType === "touch") return; hover.enter(lineNumber, lineElement); }, onLineLeave: () => hover.leave() });
$effect(() => { if (!blame) hover.hide(); });
let instance: File | undefined; // the async re-render reads this and it has to match what's on screen let shown: { name: string; contents: string } | undefined; const file = $derived(contents === null ? undefined : { name, contents }); const ready = $derived(file !== undefined);
// same manual updates as PierreDiff, this effect owns the instance, // a contents swap re-renders in place instead of a shadow dom teardown $effect(() => { const host = element; const current = ready ? untrack(() => file) : undefined; if (!host || !current) return; // scroll and wrap lay out differently enough in grid to re-hydrate on // toggle, and the toggle is rare const dehydrated = host.shadowRoot?.querySelector("[data-dehydrated]") != null; if (!dehydrated) clearPierreRender(host); shown = current; // pierre hands its re-renders (a finished highlight) to the owner const created: File = new File(options(wrap), undefined, () => created.render({ file: shown ?? current, forceRender: true }) ); // untracked, the render effect below owns updates untrack(() => { created.hydrate({ file: current, fileContainer: host }); created.setSelectedLines(selectedLines, { notify: false }); }); instance = created;
return () => { created.cleanUp(); instance = undefined; }; });
// hydrate paints the initial file, this pass covers later name/contents // changes $effect(() => { const next = file; if (!next || (next.name === shown?.name && next.contents === shown.contents)) return; shown = next; untrack(() => instance?.render({ file: next, forceRender: true })); });
$effect(() => { const range = selectedLines; instance?.setSelectedLines(range, { notify: false }); });</script>
<div bind:this={wrapper} class="relative isolate"> <diffs-container data-testid="fileContents" data-filename={name} bind:this={element} class="diffs-container [--diffs-gap-style:none] [--diffs-selection-base:var(--color-border-focus)]" > {#if budget} {@const prerenderedHTML = await prerenderFile(budget, name, contents)} {#if prerenderedHTML} <!-- eslint-disable-next-line svelte/no-at-html-tags -- pierre's own renderer output, token text is escaped by its hast builder --> <template shadowrootmode="open">{@html prerenderedHTML}</template> {/if} {/if} </diffs-container> {#if blame} <BlameOverlay {blame} {hover} /> {/if} {#if onLineSelected} <LineHandles container={wrapper} /> {/if}</div>