johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
import type * as getFeed from '#lib/lexicons/dev/knotless/getFeed.ts';
import { clock, subject } from '#lib/format.ts';
// The kinds of event this page knows; any other is shown as it comes.
type Known = Extract<getFeed.EventView['action'], { $type: `dev.knotless.getFeed#${string}` }>;
type Pushed = Extract<Known, { $type: 'dev.knotless.getFeed#pushed' }>;
/** Seconds between one account's pushes to a repository that are shown as one. */
const ROLLED = 30 * 60;
/** What the feed can be narrowed to, as the page's `kind` names it. */
export const KINDS = [
['pushes', 'Pushes'],
['pulls', 'Pulls'],
['issues', 'Issues'],
['comments', 'Comments'],
['stars', 'Stars'],
['repos', 'Repositories']
] as const;
export type Kind = (typeof KINDS)[number][0];
/**
* A ref's part of a push: the commits it brought, merges left out of the
* list and counted apart, as are those other refs had first.
*/
export type Branch = {
ref: string;
/** The commit the ref was left at, absent from an older answer. */
to?: string;
created: boolean;
forced: boolean;
commits: getFeed.CommitView[];
/** The commits it brought that are not merges, listed or not. */
count: number;
merges: number;
elsewhere: number;
};
function branch(a: Pushed): Branch {
const real = a.commits.filter((c) => !c.merge);
const unlisted = Math.max(0, a.new - a.commits.length);
return {
ref: a.ref,
to: a.to,
created: a.created,
forced: !!a.forced,
commits: real,
count: real.length + unlisted,
merges: a.commits.length - real.length,
elsewhere: a.count - a.new
};
}
/** A ref that moved only to bring in what other refs had. */
export const caughtUp = (b: Branch) => !b.created && !b.forced && b.count === 0;
type Base = { actor: getFeed.ActorView; repo: getFeed.RepoView; createdAt: string; since: string };
/**
* What a line of the feed shows: an event as it came; one account's
* pushes to a repository close together; a pull merged, with the push
* that landed it; or stars, of one repository by many or of many by one.
*/
export type Entry = Base &
(
| { kind: 'event'; event: getFeed.EventView }
| { kind: 'pushes'; branches: Branch[]; caught: string[] }
| { kind: 'merged'; subject: string; title: string; landed: Branch | null }
| { kind: 'starred'; actors: getFeed.ActorView[]; repos: getFeed.RepoView[] }
);
const sameRepo = (a: getFeed.RepoView, b: getFeed.RepoView) => a.did === b.did && a.name === b.name;
const seconds = (iso: string) => Date.parse(iso) / 1000;
/**
* `events`, newest first, as the lines that show them. A merged pull takes
* in the push beside it that landed it, one of whose commits has its
* title. One account's pushes to a repository, each within half an hour
* of the next and nothing between them, are one line, as are stars in a
* row of one repository or by one account.
*/
export function entries(events: getFeed.EventView[]): Entry[] {
const landed = new Map<number, number>();
events.forEach((e, i) => {
const a = e.action as Known;
if (a.$type !== 'dev.knotless.getFeed#changed' || a.state !== 'merged') return;
const by = [i - 1, i + 1].find((j) => {
const p = events[j]?.action as Known | undefined;
return (
p?.$type === 'dev.knotless.getFeed#pushed' &&
sameRepo(events[j].repo, e.repo) &&
!landed.has(j) &&
p.commits.some((c) => subject(c.message) === a.title)
);
});
if (by !== undefined) landed.set(by, i);
});
const into = new Map([...landed].map(([push, merge]) => [merge, push]));
const out: Entry[] = [];
events.forEach((e, i) => {
if (landed.has(i)) return;
const a = e.action as Known;
const last = out.at(-1);
const base = { actor: e.actor, repo: e.repo, createdAt: e.createdAt, since: e.createdAt };
if (a.$type === 'dev.knotless.getFeed#pushed') {
const b = branch(a);
const rolls =
last?.kind === 'pushes' &&
last.actor.did === e.actor.did &&
sameRepo(last.repo, e.repo) &&
seconds(last.since) - seconds(e.createdAt) <= ROLLED;
const to = rolls ? last : null;
if (to) to.since = e.createdAt;
const pushes = to ?? out[out.push({ ...base, kind: 'pushes', branches: [], caught: [] }) - 1];
if (pushes.kind !== 'pushes') return;
if (caughtUp(b)) pushes.caught.push(b.ref);
else pushes.branches.push(b);
} else if (a.$type === 'dev.knotless.getFeed#changed' && a.state === 'merged') {
const push = into.get(i);
const p = push === undefined ? null : (events[push].action as Pushed);
out.push({
...base,
kind: 'merged',
subject: a.subject,
title: a.title,
landed: p && branch(p)
});
} else if (a.$type === 'dev.knotless.getFeed#starred') {
if (last?.kind === 'starred' && last.repos.length === 1 && sameRepo(last.repo, e.repo)) {
if (!last.actors.some((x) => x.did === e.actor.did)) last.actors.push(e.actor);
last.since = e.createdAt;
} else if (
last?.kind === 'starred' &&
last.actors.length === 1 &&
last.actor.did === e.actor.did
) {
if (!last.repos.some((r) => sameRepo(r, e.repo))) last.repos.push(e.repo);
last.since = e.createdAt;
} else out.push({ ...base, kind: 'starred', actors: [e.actor], repos: [e.repo] });
} else out.push({ ...base, kind: 'event', event: e });
});
return out;
}
/** When a line's events happened: one time, or from the first to the last, as `17:42–45`. */
export function span(since: string, at: string): string {
const [from, to] = [clock(since), clock(at)];
if (from === to) return to;
return from.slice(0, 3) === to.slice(0, 3) ? `${from}–${to.slice(3)}` : `${from}–${to}`;
}