rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1package gallery
2
3import (
4 "fmt"
5 "html/template"
6 "strings"
7)
8
9// The page kinds: one page per section, each at its own URL under
10// <theme>/<locale>/. The table pageKinds returns is the whole of that
11// seam: a row in it is a page in the tree, a section of the rail, a
12// row on the phone index, a step in the previous/next sequence, a
13// route off the Overview and a file TestTreeShapeIsComplete expects,
14// with nothing else to remember. The families come after the three
15// fixed rows, read off the manifest, so a family added to examples/
16// is a page the day it lands.
17
18// ReleaseBase is where the site serves the newest 1.x release's files.
19// A link to a shipped file starts with it and carries data-gallery-site;
20// internal/www holds it to its own origin.
21const ReleaseBase = "https://fiesta.rastrillo.org/1/"
22
23// familyOrder is the order the families are read in: the gallery's
24// own, as the spec gives it, and not the order the manifest happens to
25// name them in. It is the family rows of pageKinds, the sections of
26// the rail and the walk of the sequence, which ends on the last shell.
27// A family in the manifest and not here fails the render by name.
28var familyOrder = []string{"list-screen", "display", "form", "choices-and-actions", "date-and-time", "route", "primitives", "formats", "screens", "shells"}
29
30// inFamilyOrder says whether familyOrder places key.
31func inFamilyOrder(key string) bool {
32 for _, k := range familyOrder {
33 if k == key {
34 return true
35 }
36 }
37 return false
38}
39
40// pageKind is one row of the table. Title and Blurb are ids: a
41// gallery.* id into text, or a prose slot id (families.<key>.title);
42// textOf resolves either. Blurb is the one sentence the Overview says
43// when it routes a reader to the page, and is empty on the Overview,
44// which never routes to itself. Nav reads the page's rail entries off
45// the finished page, nil where it anchors nothing. Body renders the
46// page's own markup.
47type pageKind struct {
48 Kind, File, Title, Blurb string
49 Nav func(p page) []navItem
50 Body func(p page) (template.HTML, error)
51}
52
53// pageKinds is the table, in page order: the Overview, Getting
54// started, Tokens, Icons, then one row per family in the gallery's
55// family order.
56func (s *site) pageKinds() []pageKind {
57 kinds := []pageKind{
58 {Kind: "overview", File: "index.html", Title: "gallery.kind.overview", Body: s.overviewBody},
59 {Kind: "getting-started", File: "getting-started.html", Title: "gallery.kind.getting-started", Blurb: "gallery.routes.getting-started", Nav: s.assetNav, Body: s.startedBody},
60 {Kind: "tokens", File: "tokens.html", Title: "gallery.kind.tokens", Blurb: "gallery.route.tokens", Nav: s.tokenNav, Body: s.tokensBody},
61 {Kind: "icons", File: "icons.html", Title: "gallery.kind.icons", Blurb: "gallery.routes.icons", Nav: s.iconNav, Body: s.iconsBody},
62 }
63 for _, f := range s.families {
64 kinds = append(kinds, pageKind{
65 Kind: f.Key,
66 File: f.Key + ".html",
67 Title: "families." + f.Key + ".title",
68 Blurb: "families." + f.Key + ".blurb",
69 Nav: s.familyNav(f.Key),
70 Body: s.familyBody,
71 })
72 }
73 return kinds
74}
75
76// fileOf is one page kind's file. It panics on a kind that is not in
77// the table, since every caller passes a Kind read out of the table.
78func (s *site) fileOf(kind string) string {
79 for _, pk := range s.pageKinds() {
80 if pk.Kind == kind {
81 return pk.File
82 }
83 }
84 panic("gallery: no page kind " + kind)
85}
86
87// href is the address of one file of one theme and locale directory.
88// Every link the renderer writes into the tree is built here, so the
89// mount is spelled once.
90func (s *site) href(theme, locale, file string) string {
91 return s.url(theme + "/" + locale + "/" + file)
92}
93
94// titleOf is one page kind's title in locale.
95func (s *site) titleOf(locale string, pk pageKind) string { return s.textOf(locale, pk.Title) }
96
97// blurbOf is one page kind's route blurb in locale, or "" where it has
98// none.
99func (s *site) blurbOf(locale string, pk pageKind) string {
100 if pk.Blurb == "" {
101 return ""
102 }
103 return s.textOf(locale, pk.Blurb, "total", fmt.Sprint(len(s.icons)))
104}
105
106// sequence is the order the pages are read in: the table's rows, and
107// inside each family its examples in manifest order after the family's
108// own page. Each step is the page's address and the label the
109// previous/next pair names it by: a kind's title, an example's name.
110func (s *site) sequence(theme, locale string) []link {
111 var out []link
112 for _, pk := range s.pageKinds() {
113 out = append(out, link{Href: s.href(theme, locale, pk.File), Label: s.titleOf(locale, pk)})
114 for _, f := range s.families {
115 if f.Key != pk.Kind {
116 continue
117 }
118 for _, e := range f.Examples {
119 out = append(out, link{Href: s.href(theme, locale, e.Name+".html"), Label: e.Name})
120 }
121 }
122 }
123 return out
124}
125
126// steps is the pair of links at the foot of one page: the page before
127// it and the page after it in the sequence. Either is nil at the ends.
128func (s *site) steps(theme, locale, file string) (prev, next *link) {
129 seq := s.sequence(theme, locale)
130 at := -1
131 for i, step := range seq {
132 if step.Href == s.href(theme, locale, file) {
133 at = i
134 break
135 }
136 }
137 if at < 0 {
138 panic("gallery: " + file + " is not in the sequence")
139 }
140 step := func(i int, id string) *link {
141 if i < 0 || i >= len(seq) {
142 return nil
143 }
144 return &link{Href: seq[i].Href, Label: s.say(locale, id, "page", seq[i].Label)}
145 }
146 return step(at-1, "gallery.step.prev"), step(at+1, "gallery.step.next")
147}
148
149// route is one entry in the Overview's route list: another page of the
150// gallery, its name, and the one sentence saying what is on it.
151type route struct{ Label, Blurb, Href string }
152
153// routes is the Overview's way into the rest of the gallery: one per
154// page kind that is not kind, in table order, each with its row's
155// blurb.
156func (s *site) routes(theme, locale, kind string) []route {
157 var out []route
158 for _, pk := range s.pageKinds() {
159 if pk.Kind == kind {
160 continue
161 }
162 out = append(out, route{Label: s.titleOf(locale, pk), Blurb: s.blurbOf(locale, pk), Href: s.href(theme, locale, pk.File)})
163 }
164 return out
165}
166
167// anchorID is the id one anchorable thing on a page wears, and the
168// fragment the rail links it by: built from the English name, so the
169// twelve translations of a page carry the same fragments, and prefixed
170// by kind, so a token group and an icon of the same name are two ids.
171func anchorID(kind, name string) string { return kind + "-" + slug(name) }
172
173// slug lowercases and hyphenates: ASCII letters and digits survive,
174// everything else becomes one hyphen, and the ends are trimmed. The
175// inputs are English headings and identifiers already in this
176// alphabet, so there is no transliteration to do.
177func slug(s string) string {
178 var b strings.Builder
179 dash := false
180 for _, r := range strings.ToLower(s) {
181 switch {
182 case r >= 'a' && r <= 'z', r >= '0' && r <= '9':
183 b.WriteRune(r)
184 dash = false
185 default:
186 if b.Len() > 0 && !dash {
187 b.WriteByte('-')
188 dash = true
189 }
190 }
191 }
192 return strings.TrimSuffix(b.String(), "-")
193}
194