camper/pkg/template/render.go

193 lines
5.8 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package template
import (
"fmt"
"html/template"
"io"
"math"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/number"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/build"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
)
func adminTemplateFile(name string) string {
return "web/templates/admin/" + name
}
func publicTemplateFile(name string) string {
return "web/templates/public/" + name
}
func MustRenderAdmin(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
layout := "layout.gohtml"
if httplib.IsHTMxRequest(r) {
layout = "htmx.gohtml"
}
mustRenderLayout(w, user, company, adminTemplateFile, data, layout, filename)
}
func MustRenderAdminFiles(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, data interface{}, filenames ...string) {
layout := "layout.gohtml"
if httplib.IsHTMxRequest(r) {
layout = "htmx.gohtml"
}
filenames = append([]string{layout}, filenames...)
mustRenderLayout(w, user, company, adminTemplateFile, data, filenames...)
}
func MustRenderAdminNoLayout(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
mustRenderLayout(w, user, company, adminTemplateFile, data, filename)
}
func MustRenderAdminNoLayoutFiles(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, data interface{}, filenames ...string) {
mustRenderLayout(w, user, company, adminTemplateFile, data, filenames...)
}
func MustRenderPublic(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
layout := "layout.gohtml"
mustRenderLayout(w, user, company, publicTemplateFile, data, layout, filename)
}
func MustRenderPublicNoLayout(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
mustRenderLayout(w, user, company, publicTemplateFile, data, filename)
}
func MustRenderPublicFiles(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, data interface{}, filenames ...string) {
layout := "layout.gohtml"
filenames = append([]string{layout}, filenames...)
mustRenderLayout(w, user, company, publicTemplateFile, data, filenames...)
}
func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, templateFile func(string) string, data interface{}, templates ...string) {
t := template.New(templates[len(templates)-1])
t.Funcs(template.FuncMap{
"camperVersion": func() string {
return build.Version
},
"gettext": user.Locale.Get,
"pgettext": user.Locale.GetC,
"currentLocale": func() string {
return user.Locale.Language.String()
},
"isLoggedIn": func() bool {
return user.LoggedIn
},
"isAdmin": user.IsAdmin,
"CSRFHeader": func() string {
return fmt.Sprintf(`"%s": "%s"`, auth.CSRFTokenHeader, user.CSRFToken)
},
"CSRFInput": func() template.HTML {
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, auth.CSRFTokenField, user.CSRFToken))
},
"raw": func(s string) template.HTML {
return template.HTML(s)
},
"replaceAll": func(s, old, new string) string {
return strings.ReplaceAll(s, old, new)
},
"humanizeBytes": func(bytes int64) string {
return humanizeBytes(bytes)
},
"formatPrice": func(price string) string {
return FormatPrice(price, user.Locale.Language, user.Locale.CurrencyPattern, company.DecimalDigits, company.CurrencySymbol)
},
"formatDate": func(time time.Time) template.HTML {
return template.HTML(`<time datetime="` + time.Format(database.ISODateFormat) + `">` + time.Format("02/01/2006") + "</time>")
},
"formatDateTime": func(time time.Time) template.HTML {
return template.HTML(`<time datetime="` + time.Format(database.ISODateFormat) + `">` + time.Format("02/01/2006 15:04:05") + "</time>")
},
"formatDateAttr": func(time time.Time) string {
return time.Format(database.ISODateFormat)
},
"formatPercent": func(value int) string {
return fmt.Sprintf("%d %%", value)
},
"today": func() string {
return time.Now().Format(database.ISODateFormat)
},
"queryEscape": func(s string) string {
return url.QueryEscape(s)
},
"inc": func(i int) int {
return i + 1
},
"dec": func(i int) int {
return i - 1
},
"add": func(y, x int) int {
return x + y
},
"sub": func(y, x int) int {
return x - y
},
"int": func(v interface{}) int {
switch v := v.(type) {
case int:
return v
case bool:
if v {
return 1
} else {
return 0
}
case time.Weekday:
return int(v)
case time.Month:
return int(v)
default:
panic(fmt.Errorf("could not convert to integer"))
}
},
"hexToDec": func(s string) int {
num, _ := strconv.ParseInt(s, 16, 0)
return int(num)
},
"slugify": Slugify,
})
templates = append(templates, "form.gohtml")
files := make([]string, len(templates))
for i, tmpl := range templates {
if len(tmpl) > 4 && tmpl[0] == 'w' && tmpl[1] == 'e' && tmpl[2] == 'b' && tmpl[3] == '/' {
files[i] = tmpl
} else {
files[i] = templateFile(tmpl)
}
}
if _, err := t.ParseFiles(files...); err != nil {
panic(err)
}
if rw, ok := w.(http.ResponseWriter); ok {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
}
if err := t.ExecuteTemplate(w, path.Base(templates[0]), data); err != nil {
panic(err)
}
}
func FormatPrice(price string, language language.Tag, currencyPattern string, decimalDigits int, currencySymbol string) string {
p := message.NewPrinter(language)
f, err := strconv.ParseFloat(price, 64)
if err != nil {
f = math.NaN()
}
return p.Sprintf(currencyPattern, decimalDigits, number.Decimal(f), currencySymbol)
}