Single-page site with gallery by album and event, contact form over SMTP, Docker dev/prod setup, and on-server image derivatives. Gallery photos stay local (app/images/ is gitignored). Co-authored-by: Cursor <cursoragent@cursor.com>
60 lines
1.3 KiB
Go
60 lines
1.3 KiB
Go
package gallery
|
|
|
|
import (
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var zipExportSuffix = regexp.MustCompile(`-\d{8}T\d{6}Z-1-\d+$`)
|
|
|
|
// URLPath escapes a relative image path for use in URL paths.
|
|
func URLPath(rel string) string {
|
|
parts := strings.Split(rel, "/")
|
|
for i, p := range parts {
|
|
parts[i] = url.PathEscape(p)
|
|
}
|
|
return strings.Join(parts, "/")
|
|
}
|
|
|
|
func collectionFromRel(rel string) (key, label string) {
|
|
parts := strings.Split(rel, "/")
|
|
if len(parts) <= 2 {
|
|
return "", ""
|
|
}
|
|
inner := strings.TrimSpace(parts[len(parts)-2])
|
|
label = cleanCollectionName(inner)
|
|
if label == "" {
|
|
return "", ""
|
|
}
|
|
return collectionKey(label), label
|
|
}
|
|
|
|
func cleanCollectionName(name string) string {
|
|
name = strings.TrimSpace(name)
|
|
name = zipExportSuffix.ReplaceAllString(name, "")
|
|
return strings.TrimSpace(name)
|
|
}
|
|
|
|
func collectionKey(label string) string {
|
|
key := strings.ToLower(label)
|
|
key = strings.ReplaceAll(key, " ", "-")
|
|
key = strings.Map(func(r rune) rune {
|
|
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
|
|
return r
|
|
}
|
|
return -1
|
|
}, key)
|
|
return key
|
|
}
|
|
|
|
// CollectionLabel returns display name for a collection filter key.
|
|
func CollectionLabel(key string, images []Image) string {
|
|
for _, img := range images {
|
|
if img.CollectionKey == key {
|
|
return img.Collection
|
|
}
|
|
}
|
|
return key
|
|
}
|