johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
<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>