package todo
import (
"bytes"
"embed"
"html/template"
"net/http"
"strconv"
"amadan.net/rastrillo/rastrillo"
)
//go:embed templates static
var appFS embed.FS
// assets fingerprints static/: {{asset "static/index.css"}} returns a
// content-hashed URL served immutable-cacheable, and the bare name
// (/static/index.css) is served too, never long-cached.
var assets = rastrillo.NewAssets(appFS)
var templates = template.Must(template.New("").
Funcs(template.FuncMap{
"asset": assets.Path,
"plural": func(n int) string {
if n == 1 {
return "item"
}
return "items"
},
}).
ParseFS(appFS, "templates/*.html"))
// render executes a template into a buffer before anything touches the
// wire, so a template error becomes a clean 500 and the response
// carries an exact Content-Length.
func render(w http.ResponseWriter, name string, data any) {
renderStatus(w, http.StatusOK, name, data)
}
func renderStatus(w http.ResponseWriter, status int, name string, data any) {
var buf bytes.Buffer
if err := templates.ExecuteTemplate(&buf, name, data); err != nil {
http.Error(w, "something went wrong", http.StatusInternalServerError)
return
}
h := w.Header()
h.Set("Content-Type", "text/html; charset=utf-8")
h.Set("Content-Length", strconv.Itoa(buf.Len()))
w.WriteHeader(status)
buf.WriteTo(w)
}
// ErrorPage is the rastrillo.ErrorPageFunc Options.ErrorPage points at,
// so a panic gets the same page a handled failure does: TodoMVC's frame
// with the status and, for a 500, the ref the log line carries.
func ErrorPage(w http.ResponseWriter, r *http.Request, status int, ref string) {
title := http.StatusText(status)
if status == http.StatusInternalServerError {
title = "Something went wrong on our side"
}
renderStatus(w, status, "errors", map[string]any{"Title": title, "Ref": ref})
}
func errorPage(w http.ResponseWriter, status int, title string) {
renderStatus(w, status, "errors", map[string]any{"Title": title})
}