package appview import ( "embed" "fmt" "html/template" "io/fs" "net/http" "time" ) //go:embed templates/**/*.html var templatesFS embed.FS //go:embed static var staticFS embed.FS // Templates returns parsed templates with helper functions func Templates() (*template.Template, error) { funcMap := template.FuncMap{ "timeAgo": func(t time.Time) string { duration := time.Since(t) if duration < time.Minute { return "just now" } else if duration < time.Hour { mins := int(duration.Minutes()) if mins == 1 { return "1 minute ago" } return fmt.Sprintf("%d minutes ago", mins) } else if duration < 24*time.Hour { hours := int(duration.Hours()) if hours == 1 { return "1 hour ago" } return fmt.Sprintf("%d hours ago", hours) } else { days := int(duration.Hours() / 24) if days == 1 { return "1 day ago" } return fmt.Sprintf("%d days ago", days) } }, "humanizeBytes": func(bytes int64) string { const unit = 1024 if bytes < unit { return fmt.Sprintf("%d B", bytes) } div, exp := int64(unit), 0 for n := bytes / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) }, "truncateDigest": func(digest string, length int) string { if len(digest) <= length { return digest } return digest[:length] + "..." }, "firstChar": func(s string) string { if len(s) == 0 { return "?" } return string([]rune(s)[0]) }, "trimPrefix": func(prefix, s string) string { if len(s) >= len(prefix) && s[:len(prefix)] == prefix { return s[len(prefix):] } return s }, } tmpl := template.New("").Funcs(funcMap) tmpl, err := tmpl.ParseFS(templatesFS, "templates/**/*.html") if err != nil { return nil, err } return tmpl, nil } // StaticHandler returns HTTP handler for static files func StaticHandler() http.Handler { sub, err := fs.Sub(staticFS, "static") if err != nil { panic(err) } return http.FileServer(http.FS(sub)) }