2026-03-07 06:20:39 +00:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
|
|
|
|
"html/template"
|
2026-03-08 06:51:04 +00:00
|
|
|
"io/fs"
|
2026-03-07 06:20:39 +00:00
|
|
|
"log"
|
|
|
|
|
"net/http"
|
2026-03-08 06:51:04 +00:00
|
|
|
|
|
|
|
|
"github.com/ghostersk/gowebmail"
|
2026-03-07 06:20:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Renderer holds one compiled *template.Template per page name.
|
|
|
|
|
// Each entry is parsed from base.html + <page>.html in isolation so that
|
|
|
|
|
// {{define}} blocks from one page never bleed into another (the ParseGlob bug).
|
|
|
|
|
type Renderer struct {
|
|
|
|
|
templates map[string]*template.Template
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewRenderer parses every page template paired with the base layout.
|
|
|
|
|
// Call once at startup; fails fast if any template has a syntax error.
|
|
|
|
|
func NewRenderer() (*Renderer, error) {
|
|
|
|
|
pages := []string{
|
|
|
|
|
"app.html",
|
|
|
|
|
"login.html",
|
|
|
|
|
"mfa.html",
|
|
|
|
|
"admin.html",
|
|
|
|
|
}
|
2026-03-08 06:51:04 +00:00
|
|
|
templateFS, err := fs.Sub(gowebmail.WebFS, "web/templates")
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("embed templates fs: %v", err)
|
|
|
|
|
}
|
2026-03-07 06:20:39 +00:00
|
|
|
|
|
|
|
|
r := &Renderer{templates: make(map[string]*template.Template, len(pages))}
|
|
|
|
|
|
|
|
|
|
for _, page := range pages {
|
|
|
|
|
// New instance per page — base FIRST, then the page file.
|
|
|
|
|
// This means the page's {{define}} blocks override the base's {{block}} defaults
|
|
|
|
|
// without any other page's definitions being present in the same pool.
|
2026-03-08 06:51:04 +00:00
|
|
|
t, err := template.ParseFS(templateFS, "base.html", page)
|
2026-03-07 06:20:39 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("renderer: parse %s: %w", page, err)
|
|
|
|
|
}
|
2026-03-08 06:51:04 +00:00
|
|
|
|
|
|
|
|
name := page[:len(page)-5]
|
2026-03-07 06:20:39 +00:00
|
|
|
r.templates[name] = t
|
|
|
|
|
log.Printf("renderer: loaded template %q", name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return r, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render executes the named page template and writes it to w.
|
|
|
|
|
// Renders into a buffer first so a mid-execution error doesn't send partial HTML.
|
|
|
|
|
func (r *Renderer) Render(w http.ResponseWriter, name string, data interface{}) {
|
|
|
|
|
t, ok := r.templates[name]
|
|
|
|
|
if !ok {
|
|
|
|
|
log.Printf("renderer: unknown template %q", name)
|
|
|
|
|
http.Error(w, "page not found", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
// Always execute "base" — it pulls in the page's block overrides automatically.
|
|
|
|
|
if err := t.ExecuteTemplate(&buf, "base", data); err != nil {
|
|
|
|
|
log.Printf("renderer: execute %q: %v", name, err)
|
|
|
|
|
http.Error(w, "internal server error", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
|
|
|
buf.WriteTo(w)
|
|
|
|
|
}
|