package web
import (
"html/template"
"net/http"
"path/filepath"
"time"
)
// RobotsNone is used for private, protected, expired, or temporary pages.
const RobotsNone = "noindex,nofollow,noarchive"
type Renderer struct {
templates map[string]*template.Template
appName string
appVersion string
baseURL string
}
type PageData struct {
AppName string
AppVersion string
BaseURL string
CanonicalURL string
Robots string
OGType string
Title string
Description string
ImageURL string
ImageAlt string
ImageType string
MediaURL string
MediaType string
CurrentYear int
CurrentUser any
CSRFToken string
Data any
}
func NewRenderer(templateDir, appName, appVersion, baseURL string) (*Renderer, error) {
layouts, err := filepath.Glob(filepath.Join(templateDir, "layouts", "*.html"))
if err != nil {
return nil, err
}
partials, err := filepath.Glob(filepath.Join(templateDir, "partials", "*.html"))
if err != nil {
return nil, err
}
pages, err := filepath.Glob(filepath.Join(templateDir, "pages", "*.html"))
if err != nil {
return nil, err
}
templates := make(map[string]*template.Template, len(pages))
for _, page := range pages {
files := append([]string{}, layouts...)
files = append(files, partials...)
files = append(files, page)
parsed, err := template.ParseFiles(files...)
if err != nil {
return nil, err
}
templates[filepath.Base(page)] = parsed
}
return &Renderer{
templates: templates,
appName: appName,
appVersion: appVersion,
baseURL: baseURL,
}, nil
}
func (r *Renderer) Render(w http.ResponseWriter, status int, page string, data PageData) {
data.AppName = r.appName
data.AppVersion = r.appVersion
data.BaseURL = r.baseURL
data.CurrentYear = time.Now().Year()
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(status)
tmpl, ok := r.templates[page]
if !ok {
http.Error(w, "template not found", http.StatusInternalServerError)
return
}
if err := tmpl.ExecuteTemplate(w, page, data); err != nil {
http.Error(w, "template render error", http.StatusInternalServerError)
}
}