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";121314export type LibraryRepo = PublicRepo & { stats: RepoStats; description: string; saves: Save[] };1516export type RecentSave = { repo: string; save: Save };171819export 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}272829export 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}505152function ticks(saves: Save[]): Html {53 const times = saves.map((save) => save.created_at);54 55 const first = times.reduce((a, b) => Math.min(a, b), times[0] ?? 0);56 const span = times.reduce((a, b) => Math.max(a, b), first) - first;57 const lines = saves.map((save) => {58 const x = span === 0 ? 50 : 1 + (98 * (save.created_at - first)) / span;59 return html`<line x1="${x.toFixed(2)}" x2="${x.toFixed(2)}" y1="9" y2="27"/>`;60 });61 return html`<svg class="repo-card-ticks" viewBox="0 0 100 36" preserveAspectRatio="none" aria-hidden="true">${lines}</svg>`;62}