johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
<script lang="ts">
import type * as getPullDiff from '#lib/lexicons/dev/knotless/getPullDiff.ts';
import type { FileDiffView } from '#lib/lexicons/dev/knotless/defs.ts';
import type { AtUriString } from '@atproto/lex';
import { untrack } from 'svelte';
import { ask } from '#lib/data.ts';
import { TREE, matched, type Source } from '#lib/diffs.ts';
import { LaterRows } from '#lib/later.svelte.ts';
import Icon from '#lib/atoms/Icon.svelte';
import DiffFile, { type Notes } from '#lib/organisms/DiffFile.svelte';
import FileTree from '#lib/organisms/FileTree.svelte';
/**
* What a pull changes: a tree of its files, then each file's diff.
* `files` come without their rows; `shown` are those drawn with them at
* once, and the rest are fetched as each comes into view, but for one
* so large it waits to be asked for. The tree shows as `tree` says,
* as the viewer last chose, or where it is null by the screen's width:
* beside the diffs, and not on a phone. `notes` go between their lines,
* and the tree counts each file's `comments`. Given `source`, where
* the files are after it, each links there and shows more of itself as
* asked.
*/
let {
files,
shown,
repo,
uri,
tree,
notes,
comments,
source
}: {
files: FileDiffView[];
shown: FileDiffView[];
repo: AtUriString;
uri: AtUriString;
tree: boolean | null;
notes?: Notes;
comments?: (f: FileDiffView) => number;
source?: Source;
} = $props();
/** The viewer's choice here, over what the page came with. */
let chosen = $state<boolean | null>(null);
const showing = $derived(chosen ?? tree);
let aside = $state<HTMLElement>();
/** Shows the tree if it is hidden, as the screen's width may have it, or hides it, and keeps that for the next pull. */
function toggle() {
const now = !(aside && getComputedStyle(aside).display !== 'none');
chosen = now;
const secure = location.protocol === 'https:' ? '; secure' : '';
document.cookie = `${TREE}=${now ? 'shown' : 'hidden'}; path=/; max-age=31536000; samesite=lax${secure}`;
}
const first = $derived(
matched(
files,
files.map((_, i) => i),
shown
)
);
const later = new LaterRows(
() => files,
() => first,
(paths) =>
ask<typeof getPullDiff.main>('dev.knotless.getPullDiff', { repo, uri, paths }).then(
(a) => a.files
)
);
const { rowsOf, waiting, seen, askFor } = later;
let diffs = $state<HTMLElement>();
let letGo: (() => void) | undefined;
/**
* Keeps the diff the reader jumped to, `#diff-<i>`, at the top of the
* screen as the files above it are drawn and their rows come in, until
* they scroll themselves: WebKit holds no scroll anchor, so each file
* above that grows past its guessed height would push it down the page.
*/
function hold(i: number) {
const target = document.getElementById(`diff-${i}`);
if (!target || !diffs) return;
letGo?.();
const grown = new ResizeObserver(() => target.scrollIntoView({ block: 'start' }));
grown.observe(diffs);
const moved = ['wheel', 'touchstart', 'keydown', 'pointerdown'];
const stop = () => {
grown.disconnect();
for (const m of moved) removeEventListener(m, stop, true);
letGo = undefined;
};
for (const m of moved) addEventListener(m, stop, { capture: true, passive: true });
letGo = stop;
}
/** A page opened at a file's link holds it too. */
$effect(() => {
const at = /^#diff-(\d+)$/.exec(location.hash);
if (at) untrack(() => hold(Number(at[1])));
return () => letGo?.();
});
</script>
<div class="pull-files" class:tree-shown={showing === true} class:tree-hidden={showing === false}>
<button
class="btn tree-toggle"
type="button"
aria-label="Show or hide the file tree"
onclick={toggle}
><span class="hide"><Icon name="sidebarCollapse" /></span><span class="show"
><Icon name="sidebarExpand" /></span
></button
>
<aside bind:this={aside}>
<FileTree {files} href={(i) => `#diff-${i}`} onpick={hold} {comments} />
</aside>
<div class="pull-files-diffs" bind:this={diffs}>
{#each files as f, i (i)}
<div id="diff-{i}" class="anchor" use:seen={i}>
<DiffFile
file={rowsOf(i) ?? f}
waiting={waiting(i)}
ask={() => askFor(i)}
{notes}
{source}
near
/>
</div>
{/each}
</div>
</div>