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(" ", 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))
}
}