jordi fita mas cf527ce070 Add the application’s version to the footer
This is mostly because it is required for the “Digital Kit”, but it also
works in our favor because now i can version the URL to the static
resources.

Go 1.18 adds the info from git if the package is build from a git
repository, but this is not the case in OBS, so i instead relay on a
constant for the version number.  This constant is “updated” by Debian’s
rules, mostly due to the discussion in [0].

[0]: https://github.com/golang/go/issues/22706
2024-01-21 20:50:16 +01:00

163 lines
4.9 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"
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("2006-01-02") + `">` + time.Format("02/01/2006") + "</time>")
},
"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)
}