~bigbes/sr-ht-compare

ref: 19f0585714695aa711b0a70e601ded7770118bd6 sr-ht-compare/web/templates.go -rw-r--r-- 4.3 KiB
19f05857 — bigbes ci: drop the 'all' from go mod download, which rewrote go.sum 9 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package web

import (
	"embed"
	"html/template"
	"log/slog"
	"net/http"

	"go.bigb.es/auxilia/scribe"
	"sourcecraft.dev/bigbes/sr-ht-ecore/chrome"
	"sourcecraft.dev/bigbes/sr-ht-ecore/pages"
)

// tmplFS holds the page templates. pages.Load discovers the pages in it and
// parses each one into its own set together with the shared layout, so that
// per-page "content"/"scripts" defines do not collide across pages — and so
// that adding templates/whatever.html is the whole registration of a page.
//
//go:embed templates/*.html
var tmplFS embed.FS

// staticFS holds the built front-end assets: the hashed bundle.<hash>.js and
// the hashed stylesheet, and nothing else since the favicon became chrome's
// inlined one. It is served read-only under /static/.
//
//go:embed static
var staticFS embed.FS

// The globs that find this build's content-addressed artefacts in staticFS.
// `make css` and the bundle build each write exactly one file, removing the
// previous build's first, so a glob here has at most one match to pick.
const (
	cssGlob    = "static/main.min.*.css"
	bundleGlob = "static/bundle.*.js"
)

// funcMap holds the template helpers shared by every page.
//
// It starts from chrome.Funcs — so the shared partials find the helpers they
// were written against, and so "shortsha" is the instance's one abbreviation
// rule rather than this service's copy of it — and adds compare's own on top.
// Adding after is deliberate: a name may then be shadowed on purpose rather than
// by accident of map ordering.
// The commit timestamps this service used to format with a "date" helper of its
// own now go through chrome's "reltime" and "abstime": a listing says "3 days
// ago" and hovers to the exact UTC stamp, which is the one spelling the whole
// instance shows.
var funcMap = func() template.FuncMap {
	m := chrome.Funcs()

	// statusClass maps a git file-change status letter to a CSS modifier used by
	// the .diff-status badge (see the diff-status rules in layout.html). Anything
	// unrecognized falls back to the neutral "o".
	m["statusClass"] = func(s string) string {
		if s == "" {
			return "o"
		}
		switch s[0] {
		case 'A', 'a':
			return "a"
		case 'M', 'm':
			return "m"
		case 'D', 'd':
			return "d"
		case 'R', 'r':
			return "r"
		default:
			return "o"
		}
	}

	// statusLabel spells out a status letter for the badge's tooltip.
	m["statusLabel"] = func(s string) string {
		if s == "" {
			return "changed"
		}
		switch s[0] {
		case 'A', 'a':
			return "added"
		case 'M', 'm':
			return "modified"
		case 'D', 'd':
			return "deleted"
		case 'R', 'r':
			return "renamed"
		default:
			return "changed"
		}
	}

	return m
}()

// render writes one page, and logs whatever pages.Render gives back.
//
// The log line is the whole of what a caller may do with that error: Render has
// already answered the response — a 500 carrying a fixed string when the
// template failed — so handing it to fail would write a second response over a
// committed one, or, when the failure is in the error page itself, recurse
// through the page that just broke. That is why this returns nothing.
func (s *Server) render(w http.ResponseWriter, status int, page string, vd viewData) {
	if err := s.pages.Render(w, status, page, vd); err != nil {
		slog.Error("web: render", scribe.Err(err), "page", page, "status", status)
	}
}

// renderError renders the chrome-wrapped error page for a status.
//
// An empty message takes the instance's standard sentence for that status, so
// the 404 a hidden repository produces reads exactly like the 404 of a
// repository that never existed — which is the point of answering 404 rather
// than 403 in the first place. A message is only ever this service's own words
// about what the viewer typed (a malformed compare spec), never an error from
// below.
func (s *Server) renderError(w http.ResponseWriter, r *http.Request, status int, message string) {
	vd := s.view(r, http.StatusText(status))
	vd.Data = pages.Error(status, message)
	s.render(w, status, pages.ErrorPage, vd)
}

// handleNotFound is the 404 for a route the router does not have and for an
// asset path that is not a file. It is a http.HandlerFunc so it can be handed
// to chi's NotFound and to assets.Handler, which both want one.
func (s *Server) handleNotFound(w http.ResponseWriter, r *http.Request) {
	s.renderError(w, r, http.StatusNotFound, "")
}