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) } }