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