package pkg import ( "fmt" "golang.org/x/text/language" "golang.org/x/text/message" "golang.org/x/text/number" "html/template" "io" "math" "net/http" "strconv" "strings" "time" ) const overrideMethodName = "_method" func templateFile(name string) string { return "web/template/" + name } func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename string, data interface{}) { mustRenderTemplateFragment(wr, r, layout, filename, layout, data) } func mustRenderTemplateFragment(wr io.Writer, r *http.Request, layout string, filename string, fragment string, data interface{}) { locale := getLocale(r) company := getCompany(r) user := getUser(r) t := template.New(filename) t.Funcs(template.FuncMap{ "gettext": locale.Get, "pgettext": locale.GetC, "currentLocale": func() string { return locale.Language.String() }, "requestURIMatches": func(uri string) bool { return r.RequestURI == uri }, "requestURIHasPrefix": func(uri string) bool { return strings.HasPrefix(r.RequestURI, uri) }, "companyURI": func(uri string) string { return companyURI(company, uri) }, "formatPrice": func(price string) string { return formatPrice(price, locale.Language, locale.CurrencyPattern, company.DecimalDigits, company.CurrencySymbol) }, "formatPriceSpan": func(price string) template.HTML { return template.HTML(formatPrice(price, locale.Language, locale.CurrencyPattern, company.DecimalDigits, ""+company.CurrencySymbol+"")) }, "formatDate": func(time time.Time) template.HTML { return template.HTML(`") }, "unsafe": func(s string) template.HTML { return template.HTML(s) }, "formatPercent": func(value int) string { return fmt.Sprintf("%d %%", value) }, "csrfToken": func() template.HTML { return template.HTML(fmt.Sprintf(``, csrfTokenField, user.CsrfToken)) }, "csrfHeader": func() string { return fmt.Sprintf(`"%s": "%s"`, csrfTokenHeader, user.CsrfToken) }, "addInputAttr": func(attr string, field *InputField) *InputField { field.Attributes = append(field.Attributes, template.HTMLAttr(attr)) return field }, "addSelectAttr": func(attr string, field *SelectField) *SelectField { field.Attributes = append(field.Attributes, template.HTMLAttr(attr)) return field }, "addTagsAttr": func(attr string, field *TagsField) *TagsField { field.Attributes = append(field.Attributes, template.HTMLAttr(attr)) return field }, "boolToInt": func(b bool) int { if b { return 1 } else { return 0 } }, "add": func(y, x int) int { return x + y }, "sub": func(y, x int) int { return x - y }, "deleteMethod": func() template.HTML { return overrideMethodField(http.MethodDelete) }, "putMethod": func() template.HTML { return overrideMethodField(http.MethodPut) }, "humanizeBytes": func(bytes int64) string { sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} return humanizeBytes(bytes, 1024, sizes) }, "slugify": func(s string) string { return slugify(s) }, "numerusVersion": func() string { return Version }, }) if _, err := t.ParseFiles(templateFile(filename), templateFile(layout), templateFile("form.gohtml")); err != nil { panic(err) } if w, ok := wr.(http.ResponseWriter); ok { w.Header().Set("Content-Type", "text/html; charset=utf-8") } if err := t.ExecuteTemplate(wr, fragment, data); err != nil { panic(err) } } func companyURI(company *Company, uri string) string { if company == nil { return uri } return "/company/" + company.Slug + uri } func overrideMethodField(method string) template.HTML { return template.HTML(fmt.Sprintf(``, overrideMethodName, method)) } func mustRenderAppTemplate(w io.Writer, r *http.Request, filename string, data interface{}) { mustRenderTemplate(w, r, "app.gohtml", filename, data) } func mustRenderModalTemplate(w io.Writer, r *http.Request, filename string, data interface{}) { if IsHTMxRequest(r) { mustRenderTemplate(w, r, "modal.gohtml", filename, data) } else { mustRenderAppTemplate(w, r, filename, data) } } func mustRenderMainTemplate(w io.Writer, r *http.Request, filename string, data interface{}) { if IsHTMxRequest(r) { mustRenderTemplate(w, r, "main.gohtml", filename, data) } else { mustRenderAppTemplate(w, r, filename, data) } } func mustRenderStandaloneTemplate(w io.Writer, r *http.Request, filename string, data interface{}) { mustRenderStandaloneTemplateFragment(w, r, filename, "standalone.gohtml", data) } func mustRenderStandaloneTemplateFragment(w io.Writer, r *http.Request, filename string, fragment string, data interface{}) { mustRenderTemplateFragment(w, r, "standalone.gohtml", filename, fragment, data) } func mustRenderWebTemplate(w io.Writer, r *http.Request, filename string, data interface{}) { mustRenderTemplate(w, r, "web.gohtml", filename, data) } func humanizeBytes(s int64, base float64, sizes []string) string { if s < 10 { return fmt.Sprintf("%d B", s) } e := math.Floor(logn(float64(s), base)) suffix := sizes[int(e)] val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 f := "%.0f %s" if val < 10 { f = "%.1f %s" } return fmt.Sprintf(f, val, suffix) } func logn(n, b float64) float64 { return math.Log(n) / math.Log(b) } 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) }