johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
<script lang="ts">
import { nameTaken } from '#lib/data.ts';
import { repoHref } from '#lib/format.ts';
/**
* Whether `name` is free for a repository of `owner`'s, by handle, said
* under the field it is typed in, as it is typed: a name no repository
* can have, one checking, free, or taken, which links to the one that
* has it. `blocked` is true while the name cannot be given, for the
* form to hold its button. The name a repository already has
* (`current`, renaming it) says nothing.
*/
let {
owner,
name,
current,
blocked = $bindable(false)
}: { owner: string; name: string; current?: string; blocked?: boolean } = $props();
type Status = 'none' | 'invalid' | 'checking' | 'free' | 'taken';
let status = $state<Status>('none');
const typed = $derived(name.trim());
// Asked a moment after typing stops; an answer overtaken by more typing is let go.
$effect(() => {
const asked = typed;
if (!asked || asked === current) {
status = 'none';
return;
}
if (
asked.length > 100 ||
asked === '.' ||
asked === '..' ||
!/^[A-Za-z0-9._~-]+$/.test(asked)
) {
status = 'invalid';
return;
}
status = 'checking';
const ask = new AbortController();
const wait = setTimeout(() => {
nameTaken(owner, asked, ask.signal).then((taken) => {
if (!ask.signal.aborted) status = taken === null ? 'none' : taken ? 'taken' : 'free';
});
}, 250);
return () => {
clearTimeout(wait);
ask.abort();
};
});
$effect(() => {
blocked = status === 'invalid' || status === 'taken';
});
// Why what is typed is no name: the first character a name cannot hold, or its length.
const why = $derived.by(() => {
if (typed.length > 100) return 'A name is at most 100 characters.';
if (typed === '.' || typed === '..') return `“${typed}” alone is no name.`;
const bad = [...typed].find((c) => !/[A-Za-z0-9._~-]/.test(c)) ?? '';
return /\s/.test(bad) ? 'A name cannot hold a space.' : `A name cannot hold “${bad}”.`;
});
</script>
{#if status === 'invalid'}
<p class="hint name-check bad" role="status">{why}</p>
{:else if status === 'checking'}
<p class="hint name-check" role="status">{`Checking ${owner}/${typed}…`}</p>
{:else if status === 'free'}
<p class="hint name-check" role="status">{`${owner}/${typed} is free.`}</p>
{:else if status === 'taken'}
<p class="hint name-check bad" role="status">
<a href={repoHref(owner, typed)}>{`${owner}/${typed}`}</a> is taken.
</p>
{/if}