package pages
import (
"errors"
"fmt"
"html"
"html/template"
"log"
"math"
"path/filepath"
"reflect"
"strings"
"time"
"github.com/dustin/go-humanize"
)
func funcMap() template.FuncMap {
return template.FuncMap{
"split": func(s string) []string {
return strings.Split(s, "\n")
},
"truncateAt30": func(s string) string {
if len(s) <= 30 {
return s
}
return s[:30] + "…"
},
"splitOn": func(s, sep string) []string {
return strings.Split(s, sep)
},
"add": func(a, b int) int {
return a + b
},
"sub": func(a, b int) int {
return a - b
},
"cond": func(cond interface{}, a, b string) string {
if cond == nil {
return b
}
if boolean, ok := cond.(bool); boolean && ok {
return a
}
return b
},
"didOrHandle": func(did, handle string) string {
if handle != "" {
return fmt.Sprintf("@%s", handle)
} else {
return did
}
},
"assoc": func(values ...string) ([][]string, error) {
if len(values)%2 != 0 {
return nil, fmt.Errorf("invalid assoc call, must have an even number of arguments")
}
pairs := make([][]string, 0)
for i := 0; i < len(values); i += 2 {
pairs = append(pairs, []string{values[i], values[i+1]})
}
return pairs, nil
},
"append": func(s []string, values ...string) []string {
s = append(s, values...)
return s
},
"timeFmt": humanize.Time,
"longTimeFmt": func(t time.Time) string {
return t.Format("2006-01-02 * 3:04 PM")
},
"shortTimeFmt": func(t time.Time) string {
return humanize.CustomRelTime(t, time.Now(), "", "", []humanize.RelTimeMagnitude{
{time.Second, "now", time.Second},
{2 * time.Second, "1s %s", 1},
{time.Minute, "%ds %s", time.Second},
{2 * time.Minute, "1min %s", 1},
{time.Hour, "%dmin %s", time.Minute},
{2 * time.Hour, "1hr %s", 1},
{humanize.Day, "%dhrs %s", time.Hour},
{2 * humanize.Day, "1d %s", 1},
{20 * humanize.Day, "%dd %s", humanize.Day},
{8 * humanize.Week, "%dw %s", humanize.Week},
{humanize.Year, "%dmo %s", humanize.Month},
{18 * humanize.Month, "1y %s", 1},
{2 * humanize.Year, "2y %s", 1},
{humanize.LongTime, "%dy %s", humanize.Year},
{math.MaxInt64, "a long while %s", 1},
})
},
"byteFmt": humanize.Bytes,
"length": func(slice any) int {
v := reflect.ValueOf(slice)
if v.Kind() == reflect.Slice || v.Kind() == reflect.Array {
return v.Len()
}
return 0
},
"splitN": func(s, sep string, n int) []string {
return strings.SplitN(s, sep, n)
},
"escapeHtml": func(s string) template.HTML {
if s == "" {
return template.HTML("
")
}
return template.HTML(s)
},
"unescapeHtml": func(s string) string {
return html.UnescapeString(s)
},
"nl2br": func(text string) template.HTML {
return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "
", -1))
},
"unwrapText": func(text string) string {
paragraphs := strings.Split(text, "\n\n")
for i, p := range paragraphs {
lines := strings.Split(p, "\n")
paragraphs[i] = strings.Join(lines, " ")
}
return strings.Join(paragraphs, "\n\n")
},
"sequence": func(n int) []struct{} {
return make([]struct{}, n)
},
"subslice": func(slice any, start, end int) any {
v := reflect.ValueOf(slice)
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
return nil
}
if start < 0 || start > v.Len() || end > v.Len() || start > end {
return nil
}
return v.Slice(start, end).Interface()
},
"markdown": func(text string) template.HTML {
return template.HTML(renderMarkdown(text))
},
"isNil": func(t any) bool {
// returns false for other "zero" values
return t == nil
},
"list": func(args ...any) []any {
return args
},
"dict": func(values ...any) (map[string]any, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid dict call")
}
dict := make(map[string]any, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, errors.New("dict keys must be strings")
}
dict[key] = values[i+1]
}
return dict, nil
},
"i": func(name string, classes ...string) template.HTML {
data, err := icon(name, classes)
if err != nil {
log.Printf("icon %s does not exist", name)
data, _ = icon("airplay", classes)
}
return template.HTML(data)
},
"cssContentHash": CssContentHash,
}
}
func icon(name string, classes []string) (template.HTML, error) {
iconPath := filepath.Join("static", "icons", name)
if filepath.Ext(name) == "" {
iconPath += ".svg"
}
data, err := Files.ReadFile(iconPath)
if err != nil {
return "", fmt.Errorf("icon %s not found: %w", name, err)
}
// Convert SVG data to string
svgStr := string(data)
svgTagEnd := strings.Index(svgStr, ">")
if svgTagEnd == -1 {
return "", fmt.Errorf("invalid SVG format for icon %s", name)
}
classTag := ` class="` + strings.Join(classes, " ") + `"`
modifiedSVG := svgStr[:svgTagEnd] + classTag + svgStr[svgTagEnd:]
return template.HTML(modifiedSVG), nil
}