/** * Helpers for the Workshop, craft and Tabletop pages: ordering, craft seals, * make photos, the sword rack, and which pill a state wears. */ import type { SealName } from "@gk/ui/components/seals.ts"; import type { Note } from "./content.ts"; import { mediaSrc, type SiteIndex } from "./site.ts"; type Pill = "live" | "state" | "current" | "neutral"; /** By `order`, notes without one last, then by title. */ export function byOrder(notes: T[]): T[] { const o = (n: Note) => ((n.data as { order?: number }).order ?? Number.MAX_SAFE_INTEGER); return [...notes].sort((a, b) => o(a) - o(b) || a.title.localeCompare(b.title)); } /** Newest `date` first; undated notes keep their order after the dated ones. */ export function byDateDesc(notes: T[]): T[] { const d = (n: Note) => String((n.data as { date?: string }).date ?? ""); return [...notes].sort((a, b) => d(b).localeCompare(d(a))); } /** A craft's own seal: dice crafts, leather, print and paper. Undefined means the family seal. */ export function craftSeal(craft: Note<"craft">): SealName | undefined { const text = `${craft.title} ${craft.data.blurb ?? ""}`.toLowerCase(); if (/leather/.test(text)) return "craft-leather"; if (/dice|resin/.test(text)) return "craft-dice"; if (/print|paper|bookbind|card|decal/.test(text)) return "craft-print"; return undefined; } /** A make's photos as image addresses (the `photos` list, else its `cover`). */ export function makePhotos(make: Note<"make">): string[] { const d = make.data as { photos?: string[]; cover?: string }; const list = d.photos?.length ? d.photos : d.cover ? [d.cover] : []; return list.map((p) => mediaSrc(p)).filter((x): x is string => !!x); } /** Makes tagged for the sword rack. */ export function isSword(make: Note<"make">): boolean { const tags = make.data.tags ?? []; return tags.some((t) => /sword/i.test(t)); } /** The makes of a craft. */ export function makesOf(s: SiteIndex, craft: Note<"craft">): Note<"make">[] { return s.all("make").filter((m) => s.byName(m.data.craft)?.path === craft.path); } /** Build logs for a craft: posts linking to it or its makes. */ export function buildLogs(s: SiteIndex, craft: Note<"craft">): Note<"post">[] { return s.backlinks(craft, { type: "post" }) as Note<"post">[]; } /** The tools of a craft, as notes where published, in the craft's order. */ export function toolsOf(s: SiteIndex, craft: Note<"craft">): { title: string; note?: Note<"tool"> }[] { return (craft.data.tools ?? []).map((t) => { const l = s.link(t); return { title: l.title, note: l.note?.type === "tool" ? (l.note as Note<"tool">) : undefined }; }); } /** The tools placed at a station. */ export function toolsAt(s: SiteIndex, station: Note<"station">): Note<"tool">[] { return s.all("tool").filter((t) => s.byName(t.data.station)?.path === station.path); } export const craftPill = (status: string): Pill => (status === "Paused" ? "neutral" : "live"); export const systemPill = (state: string): Pill => (state === "running" ? "live" : "neutral"); /** Card game stances: the main event is gold, active ones live, the rest plain. */ export function stancePill(stance: string): Pill { const s = stance.toLowerCase(); if (s === "main event") return "current"; if (/getting into|exploring|playing|learning/.test(s)) return "live"; return "neutral"; } /** "want to run" -> "Want to run". */ export const sentence = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); /** Order systems are grouped in. */ export const systemStates = ["running", "played", "want to run", "want to play", "want to try"] as const; /** Statuses whose kind belongs to a page (Making and Learning for /workshop). */ export function statusesFor(s: SiteIndex, page: string) { return s.statuses().filter((st) => s.kind(st.data.kind).page === page); } /** Archidekt deck address. */ export const archidektUrl = (id: number) => `https://archidekt.com/decks/${id}`; /** Deck's list address: Archidekt when set, else its own list link. */ export function deckUrl(deck: Note<"deck">): string | undefined { return deck.data.archidektId ? archidektUrl(deck.data.archidektId) : deck.data.listUrl; }