~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
80 lines · 2.5 KB Raw History
<script lang="ts">
	import type * as getActorRepos from '#lib/lexicons/dev/knotless/getActorRepos.ts';
	import type { Answer } from '#lib/data.ts';
	import { repoHref } from '#lib/format.ts';
	import Icon from '#lib/atoms/Icon.svelte';
	/**
	 * Beside a repository's `name`, opening to the repositories of its
	 * owner, by `did`, each linked as `authority` names them. They are asked
	 * for once, as it first opens: the viewer's private ones too, when they
	 * are the owner.
	 */
	type Repo = Answer<typeof getActorRepos.main>['repos'][number];
	let { authority, did, name }: { authority: string; did: string; name: string } = $props();
	let open = $state(false);
	let filter = $state('');
	let repos = $state<Repo[] | null>(null);
	let failed = $state(false);
	// This one first, then the rest newest first, as they are answered.
	const shown = $derived(
		(repos ?? [])
			.filter((r) => r.name.toLowerCase().includes(filter.trim().toLowerCase()))
			.sort((a, b) => Number(b.name === name) - Number(a.name === name))
	);

	async function ask() {
		if (repos || failed) return;
		try {
			const res = await fetch(
				`/xrpc/dev.knotless.getActorRepos?${new URLSearchParams({ actor: did })}`
			);
			if (!res.ok) throw new Error(res.statusText);
			repos = ((await res.json()) as Answer<typeof getActorRepos.main>).repos;
		} catch {
			failed = true;
		}
	}
	$effect(() => {
		if (open) ask();
	});
	function outside(e: MouseEvent) {
		if (open && !(e.target as Element).closest('.repo-switch')) open = false;
	}
</script>

<svelte:window onclick={outside} onkeydown={(e) => e.key === 'Escape' && (open = false)} />

<details class="popover repo-switch" bind:open>
	<summary aria-label="Switch repository" title="Switch repository"><Icon name="caret" /></summary>
	<div class="menu">
		<div class="menu-head">Switch repository</div>
		<input
			type="search"
			placeholder="Find a repository…"
			aria-label="Find a repository"
			bind:value={filter}
		/>
		{#if failed}
			<div class="none">Couldn't load {authority}'s repositories.</div>
		{:else if !repos}
			<div class="none">Loading…</div>
		{:else if shown.length}
			<ul>
				{#each shown as r (r.name)}
					<li>
						<a
							href={repoHref(authority, r.name)}
							aria-current={r.name === name ? 'true' : undefined}
							onclick={() => (open = false)}
							><Icon name="check" /><Icon name={r.private ? 'repoLocked' : 'repo'} /><span
								class="name">{r.name}</span
							></a
						>
					</li>
				{/each}
			</ul>
		{:else}
			<div class="none">No repositories match</div>
		{/if}
	</div>
</details>