141 lines
3.2 KiB
Go
141 lines
3.2 KiB
Go
package templates
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"html/template"
|
|
"io/fs"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/gorilla/csrf"
|
|
"github.com/mattnite/forgejo-tickets/internal/auth"
|
|
"github.com/mattnite/forgejo-tickets/internal/models"
|
|
)
|
|
|
|
type Renderer struct {
|
|
templates map[string]*template.Template
|
|
}
|
|
|
|
type PageData struct {
|
|
User *models.User
|
|
CSRFToken string
|
|
Flash *Flash
|
|
Data interface{}
|
|
}
|
|
|
|
type Flash struct {
|
|
Type string // "success", "error", "info"
|
|
Message string
|
|
}
|
|
|
|
func NewRenderer() (*Renderer, error) {
|
|
r := &Renderer{
|
|
templates: make(map[string]*template.Template),
|
|
}
|
|
|
|
if err := r.parseTemplates(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func (r *Renderer) parseTemplates() error {
|
|
templateDir := "web/templates"
|
|
|
|
partials, err := filepath.Glob(filepath.Join(templateDir, "partials", "*.html"))
|
|
if err != nil {
|
|
return fmt.Errorf("glob partials: %w", err)
|
|
}
|
|
|
|
err = filepath.WalkDir(filepath.Join(templateDir, "pages"), func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.IsDir() || !strings.HasSuffix(path, ".html") {
|
|
return nil
|
|
}
|
|
|
|
layout := filepath.Join(templateDir, "layouts", "base.html")
|
|
if strings.Contains(path, "admin") {
|
|
layout = filepath.Join(templateDir, "layouts", "admin.html")
|
|
}
|
|
|
|
if _, err := os.Stat(layout); os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
files := []string{layout}
|
|
files = append(files, partials...)
|
|
files = append(files, path)
|
|
|
|
name := strings.TrimPrefix(path, filepath.Join(templateDir, "pages")+"/")
|
|
name = strings.TrimSuffix(name, ".html")
|
|
|
|
tmpl, err := template.New(filepath.Base(layout)).Funcs(templateFuncs()).ParseFiles(files...)
|
|
if err != nil {
|
|
return fmt.Errorf("parse template %s: %w", name, err)
|
|
}
|
|
|
|
r.templates[name] = tmpl
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Renderer) Render(w http.ResponseWriter, req *http.Request, name string, data interface{}) {
|
|
tmpl, ok := r.templates[name]
|
|
if !ok {
|
|
http.Error(w, fmt.Sprintf("template %q not found", name), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
pd := PageData{
|
|
User: auth.CurrentUserFromRequest(req),
|
|
CSRFToken: csrf.Token(req),
|
|
Data: data,
|
|
}
|
|
|
|
if msg := req.URL.Query().Get("flash"); msg != "" {
|
|
flashType := req.URL.Query().Get("flash_type")
|
|
if flashType == "" {
|
|
flashType = "info"
|
|
}
|
|
pd.Flash = &Flash{Type: flashType, Message: msg}
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := tmpl.Execute(&buf, pd); err != nil {
|
|
http.Error(w, fmt.Sprintf("render template: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
buf.WriteTo(w)
|
|
}
|
|
|
|
func (r *Renderer) RenderError(w http.ResponseWriter, req *http.Request, status int, message string) {
|
|
w.WriteHeader(status)
|
|
if tmpl, ok := r.templates["error"]; ok {
|
|
pd := PageData{
|
|
User: auth.CurrentUserFromRequest(req),
|
|
CSRFToken: csrf.Token(req),
|
|
Data: map[string]interface{}{"Status": status, "Message": message},
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := tmpl.Execute(&buf, pd); err == nil {
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
buf.WriteTo(w)
|
|
return
|
|
}
|
|
}
|
|
http.Error(w, message, status)
|
|
}
|