~bigbes/sr-ht-spec

ref: ef7bddf8b3d64cb4c204a064ad73dbacc78443cb sr-ht-spec/web/templates.go -rw-r--r-- 3.6 KiB
ef7bddf8 — Eugene Blikh ci: publish the apk into artifacts.sr.ht as well 4 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
package web

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

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

// tmplFS holds the page templates. sr-ht-ecore's pages.Load discovers them:
// layout.html is the chrome every page is executed through, a file whose name
// starts with "_" is a partial parsed into every set, and everything else is a
// page that must define "content".
//
// The glob and not the bare directory, because Go's embed excludes names
// starting with '_' when it walks a directory and _threads.html is exactly such
// a name.
//
//go:embed templates/*.html
var tmplFS embed.FS

// staticFS holds the built assets: the hashed stylesheet produced by `make css`
// and the logo. It is compiled into the binary, which is why `make css` alone
// does not restyle a running daemon — see the package doc.
//
//go:embed static
var staticFS embed.FS

// funcMap holds this service's own template helpers.
//
// It carries only what is ours: pages.Load merges it over chrome.Funcs — the
// generic helpers every custom service on this instance was carrying its own
// copy of, `shortsha` among them — in that order, so a name may be shadowed
// deliberately rather than by accident of map ordering. Nothing shadows one
// today, and a helper that diverged from the shared spelling of the same name
// would be the drift ecore exists to prevent.
var funcMap = template.FuncMap{
	// indent renders a tree depth as non-breaking space, so the space view's
	// hierarchy reads as a hierarchy without a nested-list template recursion.
	// A negative depth (a level-1 heading, once decremented) indents nothing.
	"indent": func(depth int) template.HTML {
		if depth <= 0 {
			return ""
		}
		return template.HTML(strings.Repeat("&nbsp;&nbsp;&nbsp;&nbsp;", depth))
	},
	// dec turns a 1-based heading level into a 0-based indent depth.
	"dec": func(n int) int { return n - 1 },
}

// blockThreadsTmpl is the per-block comment markup, parsed on its own so the
// diff renderer — which builds its HTML in Go and cannot reach a page template
// through the usual {{template}} call — and the proposal page cannot drift into
// two spellings of a thread. It is the same file the page set parses as a
// partial, so there is one spelling and not two.
//
// A missing define is a build-time mistake in this package, so it panics at
// init the way template.Must does, rather than yielding a page with the
// comments silently absent.
var blockThreadsTmpl = func() *template.Template {
	t := template.Must(template.New("_threads.html").
		Funcs(chrome.Funcs()).Funcs(funcMap).
		ParseFS(tmplFS, "templates/_threads.html"))
	blocks := t.Lookup("blockthreads")
	if blocks == nil {
		panic(`web: templates/_threads.html does not define "blockthreads"`)
	}
	return blocks
}()

// renderError renders the chrome-wrapped error page: this service's view struct
// around ecore's shared error body.
//
// An empty message takes the standard sentence for the status, so a refusal
// with nothing of its own to add says what every other service on this instance
// says. It never recurses on failure — pages.Render answers the response itself
// and hands back only a line for the log.
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)
	if err := s.pages.Render(w, status, pages.ErrorPage, vd); err != nil {
		slog.ErrorContext(r.Context(), "rendering the error page failed after it was answered",
			"status", status, "method", r.Method, "path", r.URL.Path, scribe.Err(err))
	}
}