package gallery import ( "fmt" "html/template" "strings" ) // The page kinds: one page per section, each at its own URL under // //. The table pageKinds returns is the whole of that // seam: a row in it is a page in the tree, a section of the rail, a // row on the phone index, a step in the previous/next sequence, a // route off the Overview and a file TestTreeShapeIsComplete expects, // with nothing else to remember. The families come after the three // fixed rows, read off the manifest, so a family added to examples/ // is a page the day it lands. // ReleaseBase is where the site serves the newest 1.x release's files. // A link to a shipped file starts with it and carries data-gallery-site; // internal/www holds it to its own origin. const ReleaseBase = "https://fiesta.rastrillo.org/1/" // familyOrder is the order the families are read in: the gallery's // own, as the spec gives it, and not the order the manifest happens to // name them in. It is the family rows of pageKinds, the sections of // the rail and the walk of the sequence, which ends on the last shell. // A family in the manifest and not here fails the render by name. var familyOrder = []string{"list-screen", "display", "form", "choices-and-actions", "date-and-time", "route", "primitives", "formats", "screens", "shells"} // inFamilyOrder says whether familyOrder places key. func inFamilyOrder(key string) bool { for _, k := range familyOrder { if k == key { return true } } return false } // pageKind is one row of the table. Title and Blurb are ids: a // gallery.* id into text, or a prose slot id (families..title); // textOf resolves either. Blurb is the one sentence the Overview says // when it routes a reader to the page, and is empty on the Overview, // which never routes to itself. Nav reads the page's rail entries off // the finished page, nil where it anchors nothing. Body renders the // page's own markup. type pageKind struct { Kind, File, Title, Blurb string Nav func(p page) []navItem Body func(p page) (template.HTML, error) } // pageKinds is the table, in page order: the Overview, Getting // started, Tokens, Icons, then one row per family in the gallery's // family order. func (s *site) pageKinds() []pageKind { kinds := []pageKind{ {Kind: "overview", File: "index.html", Title: "gallery.kind.overview", Body: s.overviewBody}, {Kind: "getting-started", File: "getting-started.html", Title: "gallery.kind.getting-started", Blurb: "gallery.routes.getting-started", Nav: s.assetNav, Body: s.startedBody}, {Kind: "tokens", File: "tokens.html", Title: "gallery.kind.tokens", Blurb: "gallery.route.tokens", Nav: s.tokenNav, Body: s.tokensBody}, {Kind: "icons", File: "icons.html", Title: "gallery.kind.icons", Blurb: "gallery.routes.icons", Nav: s.iconNav, Body: s.iconsBody}, } for _, f := range s.families { kinds = append(kinds, pageKind{ Kind: f.Key, File: f.Key + ".html", Title: "families." + f.Key + ".title", Blurb: "families." + f.Key + ".blurb", Nav: s.familyNav(f.Key), Body: s.familyBody, }) } return kinds } // fileOf is one page kind's file. It panics on a kind that is not in // the table, since every caller passes a Kind read out of the table. func (s *site) fileOf(kind string) string { for _, pk := range s.pageKinds() { if pk.Kind == kind { return pk.File } } panic("gallery: no page kind " + kind) } // href is the address of one file of one theme and locale directory. // Every link the renderer writes into the tree is built here, so the // mount is spelled once. func (s *site) href(theme, locale, file string) string { return s.url(theme + "/" + locale + "/" + file) } // titleOf is one page kind's title in locale. func (s *site) titleOf(locale string, pk pageKind) string { return s.textOf(locale, pk.Title) } // blurbOf is one page kind's route blurb in locale, or "" where it has // none. func (s *site) blurbOf(locale string, pk pageKind) string { if pk.Blurb == "" { return "" } return s.textOf(locale, pk.Blurb, "total", fmt.Sprint(len(s.icons))) } // sequence is the order the pages are read in: the table's rows, and // inside each family its examples in manifest order after the family's // own page. Each step is the page's address and the label the // previous/next pair names it by: a kind's title, an example's name. func (s *site) sequence(theme, locale string) []link { var out []link for _, pk := range s.pageKinds() { out = append(out, link{Href: s.href(theme, locale, pk.File), Label: s.titleOf(locale, pk)}) for _, f := range s.families { if f.Key != pk.Kind { continue } for _, e := range f.Examples { out = append(out, link{Href: s.href(theme, locale, e.Name+".html"), Label: e.Name}) } } } return out } // steps is the pair of links at the foot of one page: the page before // it and the page after it in the sequence. Either is nil at the ends. func (s *site) steps(theme, locale, file string) (prev, next *link) { seq := s.sequence(theme, locale) at := -1 for i, step := range seq { if step.Href == s.href(theme, locale, file) { at = i break } } if at < 0 { panic("gallery: " + file + " is not in the sequence") } step := func(i int, id string) *link { if i < 0 || i >= len(seq) { return nil } return &link{Href: seq[i].Href, Label: s.say(locale, id, "page", seq[i].Label)} } return step(at-1, "gallery.step.prev"), step(at+1, "gallery.step.next") } // route is one entry in the Overview's route list: another page of the // gallery, its name, and the one sentence saying what is on it. type route struct{ Label, Blurb, Href string } // routes is the Overview's way into the rest of the gallery: one per // page kind that is not kind, in table order, each with its row's // blurb. func (s *site) routes(theme, locale, kind string) []route { var out []route for _, pk := range s.pageKinds() { if pk.Kind == kind { continue } out = append(out, route{Label: s.titleOf(locale, pk), Blurb: s.blurbOf(locale, pk), Href: s.href(theme, locale, pk.File)}) } return out } // anchorID is the id one anchorable thing on a page wears, and the // fragment the rail links it by: built from the English name, so the // twelve translations of a page carry the same fragments, and prefixed // by kind, so a token group and an icon of the same name are two ids. func anchorID(kind, name string) string { return kind + "-" + slug(name) } // slug lowercases and hyphenates: ASCII letters and digits survive, // everything else becomes one hyphen, and the ends are trimmed. The // inputs are English headings and identifiers already in this // alphabet, so there is no transliteration to do. func slug(s string) string { var b strings.Builder dash := false for _, r := range strings.ToLower(s) { switch { case r >= 'a' && r <= 'z', r >= '0' && r <= '9': b.WriteRune(r) dash = false default: if b.Len() > 0 && !dash { b.WriteByte('-') dash = true } } } return strings.TrimSuffix(b.String(), "-") }