This repository has no description
1<script lang="ts">
2 import { untrack } from "svelte";
3 import { FileDiff, type FileDiffMetadata } from "@pierre/diffs";
4 import { pierreDiffOptions, type DiffStyle } from "./pierre";
5
6 interface Props {
7 files: FileDiffMetadata[];
8 // side-by-side vs stacked
9 diffStyle?: DiffStyle;
10 // server-prerendered shadow dom, aligned with files. only set for the
11 // initial ssr, client navigations let pierre paint on its own
12 prerenderedHTML?: (string | undefined)[];
13 }
14
15 let { files, diffStyle = "unified", prerenderedHTML = [] }: Props = $props();
16
17 let container: HTMLElement | undefined = $state();
18
19 let mounted: { instance: FileDiff; file: FileDiffMetadata }[] = [];
20 // the style at hydrate time, the options effect skips right after a
21 // (re)mount
22 let mountedStyle: DiffStyle | undefined;
23
24 // pierre does not update on its own. hydrate paints once per file, prop
25 // changes re-render. diffStyle is read untracked so a toggle re-renders
26 // through the effect below, a remount would lose expanded hunks
27 const mountAll = (source: FileDiffMetadata[], html: (string | undefined)[]) => {
28 const host = container;
29 if (!host) return;
30 for (const { instance } of mounted) instance.cleanUp();
31 mounted = [];
32
33 mounted = source.map((file, index) => {
34 const element = host.children[index] as HTMLElement;
35 // the html prop never reaches the client, pierre marks its ssr
36 // output with data-dehydrated
37 if (html[index] === undefined && element.shadowRoot?.querySelector("[data-dehydrated]") == null)
38 element.shadowRoot?.replaceChildren();
39 const instance = new FileDiff(pierreDiffOptions(untrack(() => diffStyle)), undefined, true);
40 instance.hydrate({ fileDiff: file, fileContainer: element, prerenderedHTML: html[index] });
41 return { instance, file };
42 });
43 mountedStyle = untrack(() => diffStyle);
44 };
45
46 $effect(() => {
47 mountAll(files, prerenderedHTML);
48 return () => {
49 for (const { instance } of mounted) instance.cleanUp();
50 mounted = [];
51 };
52 });
53
54 $effect(() => {
55 const style = diffStyle;
56 if (style === mountedStyle) return;
57 mountedStyle = style;
58 for (const { instance, file } of mounted) {
59 instance.setOptions(pierreDiffOptions(style));
60 instance.render({ fileDiff: file, forceRender: true });
61 }
62 });
63</script>
64
65<div bind:this={container} class="flex flex-col gap-4">
66 {#each files as file, index (index)}
67 <diffs-container class="diffs-container">
68 {#if prerenderedHTML[index]}
69 <!-- pierre's own renderer output, token text is escaped by its hast builder -->
70 <template shadowrootmode="open">{@html prerenderedHTML[index]}</template>
71 {/if}
72 </diffs-container>
73 {/each}
74</div>