172 lines
3.7 KiB
Go
172 lines
3.7 KiB
Go
package render
|
|
|
|
import (
|
|
"html/template"
|
|
"io"
|
|
"io/fs"
|
|
"log/slog"
|
|
"net/http"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
type Manager interface {
|
|
GetPage(page string, username string) Renderer
|
|
GetBlock(page string, block string) Renderer
|
|
GetComponent(path string, name string) Renderer
|
|
FromRequest(r *http.Request, page string, username string) Renderer
|
|
}
|
|
|
|
type RendererManager struct {
|
|
pages map[string]Renderer
|
|
components map[string]Renderer
|
|
}
|
|
|
|
func ParseTemplates(f fs.FS, fm template.FuncMap) (*RendererManager, error) {
|
|
rm := RendererManager{
|
|
pages: make(map[string]Renderer),
|
|
components: make(map[string]Renderer),
|
|
}
|
|
|
|
var components []string
|
|
err := fs.WalkDir(f, ".", func(path string, d fs.DirEntry, err error) error {
|
|
if strings.HasPrefix(path, "pages") {
|
|
return fs.SkipDir
|
|
}
|
|
if strings.HasSuffix(path, ".gohtml") {
|
|
components = append(components, path)
|
|
|
|
if strings.HasPrefix(path, "components") {
|
|
p := strings.TrimSuffix(strings.TrimPrefix(path, "components/"), ".gohtml")
|
|
tmpl, err := template.New("").Funcs(fm).ParseFS(f, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
slog.Debug("registering new component", "name", p, "path", path)
|
|
rm.components[p] = Renderer{
|
|
tmpl: tmpl,
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = fs.WalkDir(f, "pages", func(path string, d fs.DirEntry, err error) error {
|
|
if strings.HasSuffix(path, ".gohtml") {
|
|
var cs []string
|
|
parent := strings.Split(filepath.Dir(path), "/")
|
|
for _, c := range components {
|
|
if !strings.HasPrefix(c, "components") {
|
|
cs = append(cs, c)
|
|
continue
|
|
}
|
|
|
|
if strings.Count(c, "/") == 1 {
|
|
cs = append(cs, c)
|
|
continue
|
|
}
|
|
|
|
parts := strings.Split(filepath.Dir(c), "/")
|
|
if slices.Equal(parent[1:], parts[1:]) {
|
|
cs = append(cs, c)
|
|
}
|
|
}
|
|
|
|
tmpl, err := template.New("").Funcs(fm).ParseFS(f, append(cs, path)...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
name := strings.TrimPrefix(strings.TrimSuffix(path, ".gohtml"), "pages/")
|
|
slog.Debug("registering new page", "name", name, "path", path)
|
|
rm.pages[name] = Renderer{
|
|
tmpl: tmpl,
|
|
}
|
|
}
|
|
return err
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &rm, nil
|
|
}
|
|
|
|
func (rm RendererManager) GetPage(page, username string) Renderer {
|
|
p, ok := rm.pages[page]
|
|
if !ok {
|
|
slog.Error("template not found", "page", page)
|
|
}
|
|
p.username = username
|
|
return p
|
|
}
|
|
|
|
func (rm RendererManager) GetBlock(page, block string) Renderer {
|
|
p, ok := rm.pages[page]
|
|
if !ok {
|
|
slog.Error("template not found", "page", page)
|
|
}
|
|
p.target = block
|
|
return p
|
|
}
|
|
|
|
func (rm RendererManager) GetComponent(path, name string) Renderer {
|
|
c, ok := rm.components[path]
|
|
if !ok {
|
|
slog.Error("component not found", "path", path)
|
|
}
|
|
c.target = name
|
|
return c
|
|
}
|
|
|
|
func (rm RendererManager) FromRequest(r *http.Request, page string, username string) Renderer {
|
|
p, ok := rm.pages[page]
|
|
if !ok {
|
|
slog.Error("template not found", "page", page)
|
|
}
|
|
|
|
p.username = username
|
|
|
|
if r.Header.Get("HX-Boosted") == "true" {
|
|
p.target = "content"
|
|
} else if target := r.Header.Get("HX-Target"); target != "" {
|
|
p.target = target
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
type Renderer struct {
|
|
tmpl *template.Template
|
|
target string
|
|
username string
|
|
}
|
|
|
|
func (r Renderer) Render(w io.Writer, data any) error {
|
|
var err error
|
|
if r.target != "" {
|
|
// TODO: allow client to use it's own template for auth
|
|
// I use it like this since this is what I do for all my projects for now. (and it's not the best)
|
|
err = r.tmpl.ExecuteTemplate(w, r.target, data)
|
|
} else {
|
|
err = r.tmpl.ExecuteTemplate(w, "page", layoutData{Data: data, Username: r.username})
|
|
}
|
|
|
|
if err != nil {
|
|
slog.Error("failed to render template", "error", err)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
type layoutData struct {
|
|
Username string
|
|
Data any
|
|
}
|