/** Social card URLs and their version hashes; shared by page heads (client and server) and the /og handler. */ /** Bump when a card layout changes so every cached card URL changes with it. */ export const OG_RENDERER_VERSION = "1"; export const OG_WIDTH = 1200; export const OG_HEIGHT = 630; export const OG_PREFIX = "/og/"; export const SITE_OG_PATH = "/og/site.png"; export const SITE_DESCRIPTION = "gitfor hosts Git repositories with pull requests, Actions-style CI, issues, security scanning and sites. Open source under 0BSD, and you can run your own instance."; export const SITE_OG_ALT = "gitfor: open-source Git hosting, code review and CI"; /** FNV-1a 32-bit over the joined fields: cheap, sync and stable on both sides; it only needs to change, not resist collisions. */ export function ogVersion(fields: ReadonlyArray): string { const input = [ OG_RENDERER_VERSION, ...fields.map((field) => (field === null || field === undefined ? "" : String(field))), ].join("\u0000"); let hash = 0x811c9dc5; for (let index = 0; index < input.length; index++) { hash ^= input.charCodeAt(index); hash = Math.imul(hash, 0x01000193); } return (hash >>> 0).toString(16).padStart(8, "0"); } export type RepoCardData = { owner: string; name: string; description: string | null; stars: number; forks: number; openIssues: number; openPulls: number; avatarKey: string | null; latestRelease: string | null; }; export type AccountCardData = { name: string; kind: "user" | "org"; displayName: string | null; bio: string | null; avatarKey: string | null; publicRepos: number; }; export function repoCardVersion(repo: RepoCardData): string { return ogVersion([ "repo", repo.owner, repo.name, repo.description, repo.stars, repo.forks, repo.openIssues, repo.openPulls, repo.avatarKey, repo.latestRelease, ]); } export function accountCardVersion(account: AccountCardData): string { return ogVersion([ "account", account.name, account.kind, account.displayName, account.bio, account.avatarKey, account.publicRepos, ]); } export function featureCardVersion(slug: string, title: string, lede: string): string { return ogVersion(["feature", slug, title, lede]); } export function siteCardVersion(): string { return ogVersion(["site"]); } export function repoOgPath(repo: RepoCardData): string { return `/og/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}.png?v=${repoCardVersion(repo)}`; } export function accountOgPath(account: AccountCardData): string { return `/og/${encodeURIComponent(account.name)}.png?v=${accountCardVersion(account)}`; } export function featureOgPath(slug: string, title: string, lede: string): string { return `/og/feature/${encodeURIComponent(slug)}.png?v=${featureCardVersion(slug, title, lede)}`; } export function siteOgPath(): string { return `${SITE_OG_PATH}?v=${siteCardVersion()}`; }