johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
<script lang="ts">
import type * as getFeed from '#lib/lexicons/dev/knotless/getFeed.ts';
import type { Entry } from '#lib/feed.ts';
import type { IconName } from '#lib/icons.ts';
import { stateIcon } from '#lib/icons.ts';
import { span } from '#lib/feed.ts';
import {
changed,
commits,
excerpt,
itemHref,
kindOf,
repoHref,
said,
shortRef,
subject,
urlPath
} from '#lib/format.ts';
import Avatar from '#lib/atoms/Avatar.svelte';
import BotMark from '#lib/atoms/BotMark.svelte';
import Icon from '#lib/atoms/Icon.svelte';
import Ref from '#lib/atoms/Ref.svelte';
import Time from '#lib/atoms/Time.svelte';
import RepoLink from './RepoLink.svelte';
import CommitList from './CommitList.svelte';
import Crowd from './Crowd.svelte';
/**
* A line of the feed: who did it, by picture and name, with a mark for
* what kind of thing it was; a sentence; and what it brought or said.
* Every line names its repository, so it reads alone however lines of
* other repositories fall between; on the account's own page, `own`,
* its picture and name are left out.
*/
let { entry: x, own }: { entry: Entry; own: boolean } = $props();
// The kinds this page knows; any other falls to the switch's default, as
// its $type matches none of theirs.
type Known = Extract<getFeed.EventView['action'], { $type: `dev.knotless.getFeed#${string}` }>;
const a = $derived(x.kind === 'event' ? (x.event.action as Known) : null);
const repo = $derived(repoHref(x.repo.handle, x.repo.name));
// A branch is often gone once its pull lands, so its log is of the
// commit the push left it at, which stays.
const log = (b: { ref: string; to?: string }) =>
`${repo}/commits/${b.to ?? urlPath(shortRef(b.ref))}`;
/**
* A lone event's sentence after who did it: words, then what it was done
* to, then whether the repository follows. Each run of words is one
* text node, so it is shaped as one.
*/
type Thing =
| { ref: string }
| { item: string; title: string }
| { fork: { handle: string; name: string } }
| { repo: true }
| null;
const sentence = $derived.by((): [string, Thing, boolean] => {
switch (a?.$type) {
case 'dev.knotless.getFeed#created':
return a.forkOf
? ['forked ', { fork: a.forkOf }, false]
: ['created ', { repo: true }, false];
case 'dev.knotless.getFeed#deleted':
return ['deleted ', { ref: shortRef(a.ref) }, true];
case 'dev.knotless.getFeed#opened':
return [`opened ${kindOf(a.subject)} `, { item: a.subject, title: a.title }, true];
case 'dev.knotless.getFeed#commented':
return [
`${said(a.verdict)} ${kindOf(a.subject)} `,
{ item: a.subject, title: a.title },
true
];
case 'dev.knotless.getFeed#changed':
return [
`${changed(a.state)} ${kindOf(a.subject)} `,
{ item: a.subject, title: a.title },
true
];
default:
// A kind of event this page does not know yet.
return ['did something ', null, true];
}
});
const [lead, thing, on] = $derived(sentence);
const mark = $derived.by((): [IconName, string] => {
if (x.kind === 'pushes') return ['commit', ''];
if (x.kind === 'merged') return ['merge', 'merged'];
if (x.kind === 'starred') return ['star', 'starred'];
switch (a?.$type) {
case 'dev.knotless.getFeed#created':
return [a.forkOf ? 'fork' : 'repo', ''];
case 'dev.knotless.getFeed#deleted':
return ['branch', ''];
case 'dev.knotless.getFeed#opened':
return [kindOf(a.subject) === 'pull' ? 'pull' : 'issue', 'open'];
case 'dev.knotless.getFeed#commented':
return ['chat', ''];
case 'dev.knotless.getFeed#changed':
return [stateIcon(kindOf(a.subject), a.state), a.state === 'open' ? 'open' : ''];
default:
return ['running', ''];
}
});
const [icon, tone] = $derived(mark);
const others = $derived(x.kind === 'starred' ? x.actors.length - 1 : 0);
// One push: its ref, alone, in the sentence.
const one = $derived(x.kind === 'pushes' && x.branches.length + x.caught.length === 1 ? x : null);
const pushed = $derived(one?.branches[0]);
</script>
{#snippet where()}{' on '}<RepoLink owner={x.repo.handle} name={x.repo.name} />{/snippet}
{#snippet who()}{#if !own}<a class="who" href="/{urlPath(x.actor.handle)}">{x.actor.handle}</a
>{#if x.actor.bot}<BotMark />{/if}{/if}{own ? '' : ' '}{/snippet}
{#snippet rest(
b: { count: number; merges: number; elsewhere: number; ref: string; to?: string },
listed: number
)}<p class="feed-rest">
{#if b.count > listed}<span class="feed-badge">+{b.count - listed} more</span
>{/if}{#if b.merges}<span class="feed-badge"
>{b.merges === 1 ? '1 merge' : `${b.merges} merges`}</span
>{/if}{#if b.elsewhere}<span class="feed-badge">{`+${b.elsewhere} from other branches`}</span
>{/if}<a class="feed-log" href={log(b)}>Commits →</a>
</p>{/snippet}
<li>
<Time at={x.createdAt} show={(at) => span(x.since, at)} />
<span class="feed-node" class:own
>{#if !own}<Avatar name={x.actor.handle} did={x.actor.did} src={x.actor.avatar} />{/if}<span
class="feed-kind {tone}"><Icon name={icon} /></span
></span
>
<div class="grow">
{#if x.kind === 'pushes' && one && pushed}
<p>
{@render who()}{pushed.forced
? 'force-pushed '
: pushed.created
? 'created '
: `pushed ${commits(pushed.count)} to `}<Ref
name={shortRef(pushed.ref)}
/>{@render where()}
</p>
<CommitList commits={pushed.commits} {repo} />
{@render rest(pushed, Math.min(pushed.commits.length, 5))}
{:else if x.kind === 'pushes' && one}
<p>
{@render who()}brought <Ref name={shortRef(one.caught[0])} /> up to date{@render where()}
</p>
{:else if x.kind === 'pushes'}
<p>
{@render who()}{x.branches.length
? `pushed to ${x.branches.length + x.caught.length} branches`
: `brought ${x.caught.length} branches up to date`}{@render where()}
</p>
<ul class="feed-branches">
{#each x.branches as b, i (i)}<li>
<Ref name={shortRef(b.ref)} /><span class="clip"
>{b.commits[0] ? subject(b.commits[0].message) : ''}</span
><span class="feed-end"
><span class="feed-count">{b.created ? `new · ${b.count}` : b.count}</span><a
class="feed-log"
href={log(b)}>Commits →</a
></span
>
</li>{/each}
{#each x.caught as ref, i (i)}<li class="caught">
<Ref name={shortRef(ref)} /><span class="clip">brought up to date</span>
</li>{/each}
</ul>
{:else if x.kind === 'merged'}
<p>
{@render who()}merged pull
<a href={itemHref(repo, x.subject)}>{x.title}</a>{@render where()}
</p>
{#if x.landed}<p class="feed-rest">
{`${commits(x.landed.count)} to `}<Ref name={shortRef(x.landed.ref)} /><a
class="feed-log"
href={log(x.landed)}>Commits →</a
>
</p>{/if}
{:else if x.kind === 'starred' && x.actors.length > 1}
<Crowd actors={x.actors} />
<p>
<a class="who" href="/{urlPath(x.actor.handle)}">{x.actor.handle}</a
>{` and ${others === 1 ? '1 other' : `${others} others`} starred `}<RepoLink
owner={x.repo.handle}
name={x.repo.name}
/>
</p>
{:else if x.kind === 'starred'}
<p>
{@render who()}{x.repos.length > 1
? `starred ${x.repos.length} repositories: `
: 'starred '}{#each x.repos as r, i (i)}{i ? ', ' : ''}<RepoLink
owner={r.handle}
name={r.name}
/>{/each}
</p>
{:else}
<p>
{@render who()}{lead}{#if thing && 'ref' in thing}<Ref
name={thing.ref}
/>{:else if thing && 'item' in thing}<a href={itemHref(repo, thing.item)}>{thing.title}</a
>{:else if thing && 'fork' in thing}<RepoLink
owner={thing.fork.handle}
name={thing.fork.name}
/> to <RepoLink owner={x.repo.handle} name={x.repo.name} />{:else if thing}<RepoLink
owner={x.repo.handle}
name={x.repo.name}
/>{/if}{#if on}{@render where()}{/if}
</p>
{#if a?.$type === 'dev.knotless.getFeed#created' && a.description}
<p class="feed-note">{a.description}</p>
{:else if a?.$type === 'dev.knotless.getFeed#commented'}
<p class="feed-quote">“{excerpt(a.text, 120)}”</p>
{/if}
{/if}
</div>
</li>