summaryrefslogtreecommitdiff
path: root/internal/handlers/handlers.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers/handlers.go')
-rw-r--r--internal/handlers/handlers.go28
1 files changed, 28 insertions, 0 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index e70650d..c5df00d 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -1251,6 +1251,34 @@ func templateFuncMap() template.FuncMap {
return template.FuncMap{
"subtract": func(a, b int) int { return a - b },
"add": func(a, b int) int { return a + b },
+ // slug turns a free-text name into an id-safe fragment, so a template
+ // can give a section a stable anchor and target it by selector. Store
+ // names are user data ("Trader Joe's", "Hardware Store") and go
+ // straight into element ids, so anything outside [a-z0-9-] has to go.
+ "slug": func(s string) string {
+ var b strings.Builder
+ lastDash := true // leading dashes are suppressed
+ for _, r := range strings.ToLower(s) {
+ switch {
+ case (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9'):
+ b.WriteRune(r)
+ lastDash = false
+ default:
+ // Collapse any run of separators/punctuation into one dash.
+ if !lastDash {
+ b.WriteByte('-')
+ lastDash = true
+ }
+ }
+ }
+ out := strings.Trim(b.String(), "-")
+ if out == "" {
+ // An empty id would make every such section share the same
+ // (invalid) anchor, so give it something addressable.
+ return "unnamed"
+ }
+ return out
+ },
// multiDayLabel returns the "(starts/ends HH:MM)" suffix for a
// multi-day calendar event's row on the day it's rendered, or ""
// for a normal item or a "spans" (pass-through) day, which shows