198 lines
6.0 KiB
Go
198 lines
6.0 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"
|
||
"dev.tandem.ws/tandem/camper/pkg/form"
|
||
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,
|
||
"colspan": func(colspan int, cursor *form.Cursor) *form.Cursor {
|
||
cursor.Colspan = colspan
|
||
return cursor
|
||
},
|
||
})
|
||
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)
|
||
}
|