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

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
71 lines · 2.4 KB Raw History
<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}