~bigbes/sr-ht-spec

ref: a4d8cc52e917cf1db07c876e8b36654dcf28741b sr-ht-spec/web/templates.go -rw-r--r-- 4.1 KiB
a4d8cc52 — Eugene Blikh authn: remove the local agent-token plane 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
package web

import (
	"bytes"
	"embed"
	"html/template"
	"log"
	"net/http"
	"strings"
)

// tmplFS holds the page templates. Each page is parsed together with the shared
// layout into its own template set so that per-page "content" defines do not
// collide across pages.
//
//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 the template helpers shared by every page.
var funcMap = template.FuncMap{
	// shortsha abbreviates an object id to 8 hex chars.
	"shortsha": func(s string) string {
		if len(s) > 8 {
			return s[:8]
		}
		return s
	},
	// 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 },
}

// pageNames are the content templates; each is parsed with layout.html.
var pageNames = []string{"index", "space", "document", "search", "error", "proposal", "inbox"}

// pages maps a page name to its parsed template set (layout + partials + that
// page). threads.html is parsed into every set rather than only into the
// proposal page's: it defines review-thread markup and nothing else, and a
// partial that only some sets know about is a lookup that fails on the page
// that later needs it.
var pages = func() map[string]*template.Template {
	m := make(map[string]*template.Template, len(pageNames))
	for _, name := range pageNames {
		t := template.New("layout.html").Funcs(funcMap)
		t = template.Must(t.ParseFS(tmplFS,
			"templates/layout.html", "templates/threads.html", "templates/"+name+".html"))
		m[name] = t
	}
	return m
}()

// blockThreadsTmpl is the per-block comment markup, taken out of the proposal
// page's own set so the diff renderer — which builds its HTML in Go and cannot
// reach a page template through the usual {{template}} call — and the page
// itself cannot drift into two spellings of a thread.
//
// 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 := pages["proposal"].Lookup("blockthreads")
	if t == nil {
		panic(`web: templates/threads.html does not define "blockthreads"`)
	}
	return t
}()

// render executes a page into a buffer first, so a template error yields a
// clean 500 rather than a half-written response. On success it writes the
// status and the buffered HTML.
func (s *Server) render(w http.ResponseWriter, status int, page string, vd viewData) {
	t, ok := pages[page]
	if !ok {
		log.Printf("web: unknown template page %q", page)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	var buf bytes.Buffer
	if err := t.ExecuteTemplate(&buf, "layout.html", vd); err != nil {
		log.Printf("web: executing template %q: %v", page, err)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(status)
	_, _ = buf.WriteTo(w)
}

// errorData is the payload of the error page.
type errorData struct {
	Status     int
	StatusText string
	Message    string
}

// renderError renders the chrome-wrapped error page. It never recurses into
// render on failure (render falls back to http.Error itself).
func (s *Server) renderError(w http.ResponseWriter, r *http.Request, status int, message string) {
	vd := s.chrome(r)
	vd.Title = http.StatusText(status)
	vd.Data = errorData{
		Status:     status,
		StatusText: http.StatusText(status),
		Message:    message,
	}
	s.render(w, status, "error", vd)
}