// The types in this file are the theme contract in Go: what a template receives, nothing about how it is // produced. Split from render.go when that file crossed the size warning — the seam was already here, since // harness/theme-contract.md describes exactly this and nothing else. package render import ( "html/template" "io/fs" "time" "khosra/internal/content" ) // head is what every kind of page shares: the document shell the base template needs. Absence is the // zero value — a template reads what exists and never fails on a missing field (invariant 1). type head struct { // Title may be empty for a bundle; a listing always has one. Title string // Lang is the locale being served. Lang string // Canonical is the permalink of what was actually served, which differs from the URL requested when // the fallback chain supplied another language (ADR-0009). Canonical string // Alternates lists every language this key exists in, for hreflang. Alternates []Alternate // Style is the reference theme's stylesheet, inlined so a bare site root needs no asset route. Style template.CSS // Site is what the site declared about itself in site.yaml (ADR-0039). Zero when it declared nothing. Site content.Settings // Sections are the site's sections, for navigation: only .Title and .URL are set. Empty until the engine is // told where to find them, which cmd does at wiring time (ADR-0049). Sections []Item } // Page is one bundle rendered. type Page struct { head // Key is the bundle's identity, useful for building links. Key string // HTML is the rendered body. HTML template.HTML // Extra carries every frontmatter key the parser does not name (ADR-0002). Extra map[string]any // Sequence is the series this page sits in, nil when it sits in none. Sequence *Sequence // Tags are this bundle's own terms, each with the URL of its listing. Empty when it carries none. Tags []Item // ExtrasURL links this bundle's supporting files, empty when it has none — so a theme can offer them // without guessing whether they exist (ADR-0047). ExtrasURL string // Assets is the theme's own markup for everything this page's shortcodes and `use:` asked for, already // rendered and deduplicated. Emit it in ``. Empty for a page that asked for nothing, which is // nearly all of them (ADR-0079). Assets template.HTML // Styles and Scripts are URLs of this bundle's own CSS and JS, built by the engine because a theme must // not construct an address. Empty unless frontmatter named files beside the body. Styles, Scripts []string } // Sequence is a series as a page sees it: its members in reading order, and where this page is in them // (ADR-0033). type Sequence struct { // Title is the series' title, empty when the landing page omits one; URL is its permalink. Title, URL string // Members are every entry in reading order — ascending, unlike a dated listing. Members []Item // Index is this page's 1-based position, zero when this page is the series landing itself. Count is // how many members there are. Index, Count int // Prev and Next are the neighbours in reading order, nil at the ends and on the landing page. Prev is // the *earlier* entry, the opposite sense of a listing's PrevURL. Prev, Next *Item // First and Last are the ends of the series, set whenever it has members. First, Last *Item } // Extras is a bundle's supporting files, browsable (ADR-0047). type Extras struct { head // Bundle is the page these files belong to, so a theme has its title and a way back. Bundle Item // Entries is the whole tree, sorted by path. Entries []content.Entry // Selected is the entry the URL named, nil on the bare listing. Selected *Selected } // Selected is the entry a request picked out of the tree. type Selected struct { content.Entry // HTML is the rendered file for a markdown or text entry, empty for anything else. HTML template.HTML // RawURL always works: it is how a theme embeds an image, links a PDF, or offers a download. RawURL string } // List is a collection page: the result of a Query, one page of it. type List struct { head // Items are the entries on this page, in the Query's order. Items []Item // Page is 1-based; Pages is the total, at least 1 even when empty. Page, Pages int // PrevURL and NextURL are empty at the ends. Newer is "prev" because the order is newest first. PrevURL, NextURL string // Groups is the same entries partitioned by section, set alongside Items for a tag listing. Both shapes // are offered because which one to show is the theme's decision, not the engine's (ADR-0046). Groups []Group } // Group is a named run of entries within a listing. type Group struct { Name string Items []Item } // Item is one entry in a listing. type Item struct { Title string Key string URL string Date time.Time // Section is the entry's top-level section, so a flat listing can label where an entry came from without // the engine deciding that it must be grouped (ADR-0046). Section string } // Alternate is one language a bundle exists in. type Alternate struct { Lang string // URL is absolute when the site declares a base, because hreflang is read by machines that resolve nothing // against the page. URL string // Path is the same address, root-relative — what a *visible* language link wants. Found by looking at a // rendered page: using URL sent a reader from a local server to the canonical host (ADR-0049). Path string } // Partial renders a named fragment. A feature under internal/ext is handed one of these at wiring time, // because markup belongs to the theme and a feature must not write any (ADR-0036). type Partial func(name string, data Fragment) ([]byte, error) // Fragment is what a fragment template receives (ADR-0037, widened by ADR-0042). type Fragment struct { // Args are the call's key="value" pairs, exactly as written. Escaping is the template's. Args map[string]string // Pictures are what the engine gathered rather than the author wrote: one for a figure, many for a // gallery, none when the call names nothing a picture. Kept apart from Args so a supplied value can never // be mistaken for an authored one. Pictures []Picture // Lang is the language being served, so a fragment can localise its own words through `t` (ADR-0067). Lang string // Body is a container call's content, already rendered. Empty for a leaf call (ADR-0064). Body template.HTML // Headings are the document's headings, for a call that builds a table of contents (ADR-0065). Headings []Heading } // Heading is one heading in the document, with the id an anchor links to (ADR-0065). type Heading struct { Level int Text, ID string } // Picture is one image a fragment can render (ADR-0042). type Picture struct { // Src is the author's own file, relative to the bundle. A browser that ignores Srcset still gets the // picture that was put there. Src string // Srcset offers the derivatives, closed by the original at its own width; empty when the picture is // already small enough that no derivative was worth making. Srcset string // Width and Height are the original's intrinsic size, so a page can reserve the box before the bytes // arrive. Zero when the file could not be read. Width, Height int } // Origin tells a feature which bundle is being rendered, so a path in a call can resolve relative to it. // // Features read it from the parser context with OriginFrom. It carries the site's fs.FS rather than a // directory name alone, because every read goes through the rooted filesystem and never a joined path // (ADR-0031). type Origin struct { // Dir is the bundle's directory, relative to the site root: "content/comics/the-long-monsoon". Dir string // Files is the site root. Nil when the renderer was built without one, in which case a feature that // needs files degrades rather than guessing. Files fs.FS // Lang is the language of the variant being rendered, so a feature can hand it to a fragment (ADR-0067). Lang string // Resolve reports the URL a bundle key is served at in a language, and false when no bundle has that key. // It exists so a feature can turn a path on disk into an address without knowing what a route is: a slug // moves the address and never the key (ADR-0035), so only the index can answer. Nil when the renderer was // built without one, in which case a feature that needs it does nothing. Resolve func(key, lang string) (url string, ok bool) }