library.ts

2.7 KB · raw

1import { type RepoStats, repoStats, type Save, timeline } from "../mirror/queries.ts";2import { publicRepoNames } from "../server/db.ts";3import { type Html, html } from "./html.ts";4import {5	openPublicRepo,6	type PageContext,7	type PublicRepo,8	plural,9	relativeTime,10} from "./layout.ts";11import { readmeAtHead, readmeIntro } from "./repo.ts";1213/** A public repo as the front page, browse and the profile show it. `saves` are oldest first. */14export type LibraryRepo = PublicRepo & { stats: RepoStats; description: string; saves: Save[] };1516export type RecentSave = { repo: string; save: Save };1718/** Every public repo in card order (§20: private ones never reach a page, count or card). */19export function publicLibrary(ctx: PageContext): LibraryRepo[] {20	return publicRepoNames(ctx.kepr).flatMap((name) => {21		const repo = openPublicRepo(ctx, name);22		if (!repo) return [];23		const description = readmeIntro(name, readmeAtHead(repo))?.text ?? "";24		return [{ ...repo, stats: repoStats(repo.mirror), description, saves: timeline(repo.mirror) }];25	});26}2728/** The newest saves across the library, checkpoints never among them. */29export function recentSaves(library: LibraryRepo[], limit: number): RecentSave[] {30	return library31		.flatMap((repo) => repo.saves.slice(-limit).map((save) => ({ repo: repo.settings.name, save })))32		.sort((a, b) => b.save.created_at - a.save.created_at)33		.slice(0, limit);34}3536export function repoCards(library: LibraryRepo[]): Html {37	return html`<div class="card-grid">${library.map(repoCard)}</div>`;38}3940function repoCard(repo: LibraryRepo): Html {41	const { name, category } = repo.settings;42	const { stats } = repo;43	return html`<a class="repo-card" href="/${name}">44<div class="repo-card-head"><span class="repo-card-name">${name}</span><span class="repo-card-saves mono">${plural(stats.saves, "save")}</span></div>45${repo.description && html`<p class="repo-card-desc">${repo.description}</p>`}46${ticks(repo.saves)}47<div class="repo-card-foot">${category && html`<span class="cat-chip">${category}</span>`}${stats.language && html`<span class="repo-card-lang">${stats.language}</span>`}${stats.updated !== null && html`<span class="repo-card-when">${relativeTime(stats.updated)}</span>`}</div>48</a>`;49}5051// §21.2's mini-timeline: one tick per save, placed by time like the repo page's dots.52function ticks(saves: Save[]): Html {53	const first = saves[0]?.created_at ?? 0;54	const span = (saves[saves.length - 1]?.created_at ?? 0) - first;55	const lines = saves.map((save) => {56		const x = span === 0 ? 50 : 1 + (98 * (save.created_at - first)) / span;57		return html`<line x1="${x.toFixed(2)}" x2="${x.toFixed(2)}" y1="9" y2="27"/>`;58	});59	return html`<svg class="repo-card-ticks" viewBox="0 0 100 36" preserveAspectRatio="none" aria-hidden="true">${lines}</svg>`;60}