Add albums: group photos by free-text tags
An album is a tag. Photos carry lowercase free-text tags in their .toml sidecar (`tags = [...]`), parallel to the existing day grouping — no database, all derived at build time. - albums.go: parseTags (lowercase/trim/dedupe, slug via sanitiseSlug), groupByTag mirroring groupByDay, and rendering for album pages, the index, and the JSON API. Slug collisions between distinct names are suffixed and logged. - Routes: /album/<slug>/ (paginated square grid per tag), /albums/ (index with counts), /tags.json (machine-readable tag list, served statically like feed.xml). - POST /upload gains a `tags` field written into the sidecar; the web upload form gains a tags input. - Tags shown on each photo page linking to their album; an Albums link in the timeline/grid header.
This commit is contained in:
1 parent
f33bdd55b1
commit
a42abfab48
11 files changed
+469
-6
No files matched your search
@@ -33,7 +33,7 @@ func NewServer(port, contentDir, outputDir string, serve bool, g *Generator) *Se
|
||||
|
||||
func (s *Server) Listen() error {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /upload", s.handleUploadPage)
|
||||
mux.HandleFunc("GET /upload", s.handleUploadPage)
|
||||
mux.HandleFunc("POST /upload", s.handleUpload)
|
||||
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "ok")
|
||||
@@ -67,6 +67,7 @@ func (s *Server) handleUploadPage(w http.ResponseWriter, r *http.Request) {
|
||||
// image — the image file (jpg or png)
|
||||
// caption — human-readable caption (optional, defaults to filename)
|
||||
// date — YYYY-MM-DD (optional, defaults to today)
|
||||
// tags — comma-separated album tags (optional; lowercased, deduped)
|
||||
func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseMultipartForm(32 << 20); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
@@ -95,11 +96,18 @@ func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
caption = strings.TrimSuffix(header.Filename, ext)
|
||||
}
|
||||
|
||||
// Normalize the tags once here so the sidecar stores clean lowercase names;
|
||||
// the generator derives album slugs from them at build time.
|
||||
var tags []string
|
||||
for _, t := range parseTags(r.FormValue("tags")) {
|
||||
tags = append(tags, t.Name)
|
||||
}
|
||||
|
||||
slug := sanitiseSlug(strings.TrimSuffix(header.Filename, ext))
|
||||
if slug == "" {
|
||||
slug = fmt.Sprintf("photo-%d", time.Now().UnixMilli())
|
||||
}
|
||||
imgPath := filepath.Join(s.contentDir, slug+ext)
|
||||
imgPath := filepath.Join(s.contentDir, slug+ext)
|
||||
tomlPath := filepath.Join(s.contentDir, slug+".toml")
|
||||
|
||||
out, err := os.Create(imgPath)
|
||||
@@ -121,7 +129,7 @@ func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
defer tf.Close()
|
||||
if err := toml.NewEncoder(tf).Encode(PhotoMeta{Caption: caption, Date: dateStr}); err != nil {
|
||||
if err := toml.NewEncoder(tf).Encode(PhotoMeta{Caption: caption, Date: dateStr, Tags: tags}); err != nil {
|
||||
http.Error(w, "could not write metadata", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user