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">${45 name46 }</span><span class="repo-card-saves mono">${plural(stats.saves, "save")}</span></div>47${repo.description && html`<p class="repo-card-desc">${repo.description}</p>`}48${ticks(repo.saves)}49<div class="repo-card-foot">${50 category && html`<span class="cat-chip">${category}</span>`51 }${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>52</a>`;53}545556function ticks(saves: Save[]): Html {57 const times = saves.map((save) => save.created_at);58 59 const first = times.reduce((a, b) => Math.min(a, b), times[0] ?? 0);60 const span = times.reduce((a, b) => Math.max(a, b), first) - first;61 const lines = saves.map((save) => {62 const x = span === 0 ? 50 : 1 + (98 * (save.created_at - first)) / span;63 return html`<line x1="${x.toFixed(2)}" x2="${x.toFixed(2)}" y1="9" y2="27"/>`;64 });65 return html`<svg class="repo-card-ticks" viewBox="0 0 100 36" preserveAspectRatio="none" aria-hidden="true">${66 lines67 }</svg>`;68}