Something went wrong. Try again.
Original-language reader for Kant, Aristotle & more: select any passage to gloss, translate, or hear it read aloud, with exact citations.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151import type { JSX } from "react"import { Link } from "react-router-dom"
import { LoginButton } from "@/auth/LoginButton"import { SettingsMenu } from "@/components/settings-menu"import { Skeleton } from "@/components/ui/skeleton"import { useCatalog } from "@/lib/corpus"import type { Author, TextManifest } from "@/lib/schemas/model"import { AUTHOR_ORDER, LANG_LABEL, SCHEME_LABEL } from "@/read/labels"
/** * The corpus index — entry point to the reader. Groups the catalog by author, * then by work, and links each text to `/read/:id`. Editorial colophon styling: * a Playfair masthead over the Noto-Sans body, warm paper ground. */
interface WorkGroup { work: string texts: TextManifest[]}interface AuthorGroup { author: Author lang: string works: WorkGroup[] count: number}
function group(texts: readonly TextManifest[]): AuthorGroup[] { const byAuthor = new Map<Author, TextManifest[]>() for (const t of texts) { const list = byAuthor.get(t.author) if (list) list.push(t) else byAuthor.set(t.author, [t]) }
const ordered = [...byAuthor.keys()].sort( (a, b) => AUTHOR_ORDER.indexOf(a) - AUTHOR_ORDER.indexOf(b) )
return ordered.map((author) => { const authorTexts = byAuthor.get(author) ?? [] const byWork = new Map<string, TextManifest[]>() for (const t of authorTexts) { const list = byWork.get(t.work) if (list) list.push(t) else byWork.set(t.work, [t]) } const works: WorkGroup[] = [...byWork.entries()].map(([work, ts]) => ({ work, texts: ts, })) const lang = authorTexts[0] ? LANG_LABEL[authorTexts[0].lang] : "" return { author, lang, works, count: authorTexts.length } })}
function Masthead(): JSX.Element { return ( <header className="lib-masthead"> <p className="lib-kicker">Original-language reader</p> <h1 className="lib-wordmark">lekterion</h1> <p className="lib-tagline"> Husserl · Heidegger · Kant · Aristotle · Descartes — read closely, gloss any span, cite by locus. </p> </header> )}
function AuthorSection({ group: g }: { group: AuthorGroup }): JSX.Element { return ( <section className="lib-author"> <div className="lib-author-head"> <h2 className="lib-author-name">{g.author}</h2> <span className="lib-author-meta"> {g.lang} · {g.count} {g.count === 1 ? "text" : "texts"} </span> </div> {g.works.map((w) => ( <div key={w.work} className="lib-work"> <h3 className="lib-work-title">{w.work}</h3> <ul className="lib-text-list"> {w.texts.map((t) => ( <li key={t.id}> <Link to={`/read/${t.id}`} className="lib-text-row"> <span className="lib-text-title">{t.title}</span> <span className="lib-leader" aria-hidden="true" /> <span className="lib-text-scheme"> {SCHEME_LABEL[t.locusScheme]} </span> </Link> </li> ))} </ul> </div> ))} </section> )}
function LibrarySkeleton(): JSX.Element { return ( <div className="lib-grid" aria-busy="true"> {Array.from({ length: 4 }).map((_, i) => ( <div key={i} className="lib-author"> <Skeleton className="mb-4 h-7 w-48" /> <div className="space-y-2"> {Array.from({ length: 4 }).map((__, j) => ( <Skeleton key={j} className="h-5 w-full max-w-md" /> ))} </div> </div> ))} </div> )}
export function Library(): JSX.Element { const resource = useCatalog()
return ( <div className="lib-shell"> <div className="lib-column"> <div className="flex items-center justify-end gap-2 pt-4"> <SettingsMenu /> <LoginButton /> </div> <Masthead /> {resource.status === "loading" && <LibrarySkeleton />} {resource.status === "error" && ( <p className="reader-notice-body"> The corpus catalog could not be loaded: {resource.error.message} </p> )} {resource.status === "ready" && ( <div className="lib-grid"> {group(resource.data.texts).map((g) => ( <AuthorSection key={g.author} group={g} /> ))} </div> )} </div> <footer className="lib-footer"> {resource.status === "ready" ? `${String(resource.data.texts.length)} texts · ingest v${String(resource.data.ingestVersion)}` : " "} </footer> </div> )}