This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / components / repo / PierreDiff.svelte
2.7 kB 74 lines
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>