publish a bundle's extras as a browsable tree
Re-adopts the parked extras entry as ADR-0047: `extras/` inside a bundle is skipped
by the scanner entirely, so a `.md` in there is an asset with no identity and no URL
of its own. The engine enumerates the tree, sorts it by path, classifies by
extension, renders markdown and text, and offers everything else as bytes. One route
with two behaviours — `…/extras/{path}` selects an entry, `?raw` returns the file.
Almost everything it needed already existed, which is the sign the model was right:
the scanner had a directory exclusion, `Assets()` knew which bundles own a
directory, and `Lookup` already decided visibility — so a draft hides its extras
with no new check. A test proves that, including `?raw`.
Two deviations from the parked shape, both because the shape was written before the
code. The directory name is fixed rather than a cascade key, since nothing reads a
section-level setting yet. And an entry is resolved against the *enumeration* rather
than the filesystem: not being in the listing is a stronger answer than os.Root
refusing a path, and cheaper.
Selecting is a link and a full page. No JavaScript is involved, and a
sidebar-and-pane layout is the theme's business — which is the layer rule applied
before writing the feature rather than after.
Three size warnings fired as a result and were fixed by splitting at seams, not by
sharding: render.go gave up its type declarations to view.go, which is the theme
contract in Go and nothing else; serve() split into a dispatcher and serveBundle;
resolve() gave up its language-prefix step to cutLang.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
// 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
|
||||
// docs/theme-contract.md describes exactly this and nothing else.
|
||||
|
||||
package render
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 string
|
||||
}
|
||||
Reference in New Issue
Block a user