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

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
181 lines · 5.5 KB Raw History
<script lang="ts">
	import type { RepoViewDetailed } from '#lib/lexicons/dev/knotless/defs.ts';
	import type * as renameRepo from '#lib/lexicons/dev/knotless/renameRepo.ts';
	import type * as updateRepo from '#lib/lexicons/dev/knotless/updateRepo.ts';
	import { goto, invalidateAll } from '$app/navigation';
	import { call } from '#lib/data.ts';
	import type { Link } from '#lib/lexicons/dev/knotless/defs.ts';
	import type { UriString } from '@atproto/lex';
	import { repoHref } from '#lib/format.ts';
	import LinkRows, { typed, type TypedLink } from '#lib/molecules/LinkRows.svelte';
	import NameCheck from '#lib/molecules/NameCheck.svelte';
	/**
	 * What `repo`, owned as `authority` names it, says it is, the `links`
	 * its pages show, what it is about, its `topics`, and the branch its
	 * HEAD names, of its `branches`, as `knotless repo edit` changes them;
	 * and its name, as `knotless repo rename` does. Renaming it is its owner's
	 * alone: unless it `owns` it, an admin of the organization that owns it
	 * is told who renames it.
	 */
	let {
		repo,
		authority,
		description,
		links: shown = [],
		topics: named = [],
		defaultBranch,
		branches,
		owns = true
	}: {
		repo: RepoViewDetailed;
		authority: string;
		description?: string;
		links?: Link[];
		topics?: { name: string }[];
		defaultBranch?: string;
		branches: string[];
		owns?: boolean;
	} = $props();
	/** The most links a repository's pages show. */
	const LINKS = 5;
	let said = $state('');
	let links = $state<TypedLink[]>([]);
	let about = $state('');
	let head = $state('');
	let name = $state('');
	// Each answer of the settings starts the form over from what they say.
	$effect.pre(() => {
		said = description ?? '';
		links = shown.map((l) => ({ url: l.url as string, title: l.title ?? '' }));
		about = named.map((t) => t.name).join(' ');
		head = defaultBranch ?? '';
		name = repo.name;
	});
	let problem = $state('');
	let saved = $state('');
	// Which is under way: 'save' or 'rename'.
	let busy = $state('');
	async function save(e: SubmitEvent) {
		e.preventDefault();
		busy = 'save';
		problem = saved = '';
		try {
			await call<typeof updateRepo.main>('dev.knotless.updateRepo', {
				repo: repo.uri,
				description: said,
				links: typed(links).map((l) => ({ ...l, url: l.url as UriString })),
				topics: about
					.split(/[\s,]+/)
					.filter(Boolean)
					.map((name) => ({ name })),
				defaultBranch: head || undefined
			});
			await invalidateAll();
			saved = 'Saved.';
		} catch (err) {
			problem = (err as Error).message;
		}
		busy = '';
	}
	let renameProblem = $state('');
	let blocked = $state(false);
	async function rename(e: SubmitEvent) {
		e.preventDefault();
		busy = 'rename';
		renameProblem = '';
		try {
			const made = await call<typeof renameRepo.main>('dev.knotless.renameRepo', {
				repo: repo.uri,
				name
			});
			await goto(repoHref(authority, made.name, '/settings'));
		} catch (err) {
			renameProblem = (err as Error).message;
		}
		busy = '';
	}
</script>

<section class="setting">
	<h2>General</h2>
	<form class="form" onsubmit={save}>
		<label for="set-description">Description</label>
		<input id="set-description" name="description" maxlength="300" bind:value={said} />
		<label for="set-topics">Topics</label>
		<input
			id="set-topics"
			name="topics"
			placeholder="rust cli"
			autocapitalize="none"
			spellcheck="false"
			bind:value={about}
		/>
		<p class="hint">
			Words for what it is about, apart by spaces. Each shows under the description and finds others
			like it.
		</p>
		<fieldset class="links-field">
			<legend class="label-like">Links</legend>
			<LinkRows bind:links max={LINKS} />
			<p class="hint">
				The first, such as its website, shows under the description; the rest open from a count
				beside it.
			</p>
		</fieldset>
		<label for="set-branch">Default branch</label>
		{#if branches.length}
			<select id="set-branch" name="defaultBranch" bind:value={head}>
				{#each branches as b (b)}<option value={b}>{b}</option>{/each}
			</select>
		{:else}
			<input
				id="set-branch"
				name="defaultBranch"
				placeholder="main"
				maxlength="255"
				autocapitalize="none"
				spellcheck="false"
				bind:value={head}
			/>
		{/if}
		<p class="hint">The branch a clone checks out, and pulls are opened against first.</p>
		{#if problem}<p class="problem" role="alert">{problem}</p>{/if}
		<div class="actions">
			<button class="btn primary" type="submit" disabled={!!busy} aria-busy={busy === 'save'}
				>{busy === 'save' ? 'Saving…' : 'Save'}</button
			>{#if saved}<span class="mute" role="status">{saved}</span>{/if}
		</div>
	</form>
</section>

<section class="setting">
	<h2>Repository name</h2>
	{#if owns}
		<form class="form" onsubmit={rename}>
			<label for="set-name">Name</label>
			<div class="owned">
				<input
					id="set-name"
					name="name"
					maxlength="100"
					autocapitalize="none"
					spellcheck="false"
					required
					bind:value={name}
				/><button
					class="btn"
					type="submit"
					disabled={!!busy || blocked || name === repo.name}
					aria-busy={busy === 'rename'}>{busy === 'rename' ? 'Renaming…' : 'Rename'}</button
				>
			</div>
			<NameCheck owner={repo.owner.handle} {name} current={repo.name} bind:blocked />
			<p class="hint">
				{`The old name leads here still, for links and clones, until another repository takes it.`}
			</p>
			{#if renameProblem}<p class="problem" role="alert">{renameProblem}</p>{/if}
		</form>
	{:else}
		<p class="hint">{`Only ${authority} itself can rename this repository.`}</p>
	{/if}
</section>