132 lines
3.0 KiB
Go
132 lines
3.0 KiB
Go
package web
|
|
|
|
import (
|
|
"embed"
|
|
"html/template"
|
|
"io"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
//go:embed templates/*
|
|
var templateFS embed.FS
|
|
|
|
// Templates holds the parsed templates
|
|
type Templates struct {
|
|
templates map[string]*template.Template
|
|
}
|
|
|
|
// Template helper functions
|
|
var templateFuncs = template.FuncMap{
|
|
"divFloat": func(a int64, b int) float64 {
|
|
return float64(a) / float64(b)
|
|
},
|
|
"add": func(a, b int) int {
|
|
return a + b
|
|
},
|
|
"sub": func(a, b int) int {
|
|
return a - b
|
|
},
|
|
"mul": func(a, b int) int {
|
|
return a * b
|
|
},
|
|
"min": func(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
},
|
|
}
|
|
|
|
// NewTemplates creates and parses all templates
|
|
func NewTemplates() (*Templates, error) {
|
|
t := &Templates{
|
|
templates: make(map[string]*template.Template),
|
|
}
|
|
|
|
// Load all layout and partial templates first
|
|
layoutFiles, err := fs.Glob(templateFS, "templates/layouts/*.html")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
partialFiles, err := fs.Glob(templateFS, "templates/partials/*.html")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load page templates
|
|
pageFiles, err := fs.Glob(templateFS, "templates/pages/*.html")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Combine layouts and partials
|
|
baseFiles := append(layoutFiles, partialFiles...)
|
|
|
|
// Parse each page template with layouts and partials
|
|
for _, pageFile := range pageFiles {
|
|
files := append([]string{pageFile}, baseFiles...)
|
|
|
|
// Read and parse all files
|
|
tmpl := template.New(filepath.Base(pageFile)).Funcs(templateFuncs)
|
|
for _, file := range files {
|
|
content, err := templateFS.ReadFile(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = tmpl.Parse(string(content))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Extract page name (e.g., "login" from "templates/pages/login.html")
|
|
name := strings.TrimSuffix(filepath.Base(pageFile), ".html")
|
|
t.templates[name] = tmpl
|
|
}
|
|
|
|
// Also parse partials standalone for htmx partial responses
|
|
for _, partialFile := range partialFiles {
|
|
content, err := templateFS.ReadFile(partialFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tmpl, err := template.New(filepath.Base(partialFile)).Funcs(templateFuncs).Parse(string(content))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
name := "partial:" + strings.TrimSuffix(filepath.Base(partialFile), ".html")
|
|
t.templates[name] = tmpl
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
// RenderPage renders a full page template
|
|
func (t *Templates) RenderPage(w io.Writer, name string, data interface{}) error {
|
|
tmpl, ok := t.templates[name]
|
|
if !ok {
|
|
return &ErrTemplateNotFound{Name: name}
|
|
}
|
|
return tmpl.ExecuteTemplate(w, "base", data)
|
|
}
|
|
|
|
// RenderPartial renders a partial template (for htmx responses)
|
|
func (t *Templates) RenderPartial(w io.Writer, name string, data interface{}) error {
|
|
tmpl, ok := t.templates["partial:"+name]
|
|
if !ok {
|
|
return &ErrTemplateNotFound{Name: name}
|
|
}
|
|
return tmpl.ExecuteTemplate(w, name, data)
|
|
}
|
|
|
|
// ErrTemplateNotFound is returned when a template is not found
|
|
type ErrTemplateNotFound struct {
|
|
Name string
|
|
}
|
|
|
|
func (e *ErrTemplateNotFound) Error() string {
|
|
return "template not found: " + e.Name
|
|
}
|