167 lines
5.1 KiB
Go
167 lines
5.1 KiB
Go
/*
|
|
* 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 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>")
|
|
},
|
|
"formatDateAttr": func(time time.Time) string {
|
|
return time.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
|
|
},
|
|
"int": func(v interface{}) int {
|
|
switch v := v.(type) {
|
|
case int:
|
|
return v
|
|
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)
|
|
},
|
|
})
|
|
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)
|
|
}
|