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:
puttaalu committed 2026-10-08 16:14:09 +02:00
1 parent f33bdd55b1
commit a42abfab48
11 files changed
+469 -6

No files matched your search

+20
View File
@@ -36,10 +36,15 @@ In production nginx sits in front: serves `public/` directly, proxies `/upload`
```toml
caption = "Tempelhof at sunrise"
date = "2026-06-14"
tags = ["berlin", "sunrise"]
```
Missing sidecar → filename as caption, today as date. Photos sort newest first by that date.
`tags` is optional, free-text, and defines albums (see below). Tags are
normalized to lowercase on load/upload (`parseTags` in `albums.go`); a tag may
contain spaces (`"berlin trip"`), and its URL slug is derived with `sanitiseSlug`.
## Design
- Gruvbox dark hard colour scheme
@@ -51,6 +56,18 @@ Missing sidecar → filename as caption, today as date. Photos sort newest first
- Each timeline date heading links to a **single-day page** (`/day/<YYYY-MM-DD>/`) holding just that day's photos. `renderDayPages` writes one per `groupByDay` run; the ISO label doubles as the URL slug.
- Single photo page with sidebar and newer/older navigation
### Albums (tags)
A second grouping axis, parallel to days: an album is just a tag (`albums.go`).
`groupByTag` mirrors `groupByDay`, and `renderAlbumPages` writes a paginated
square-grid album per tag at `/album/<slug>/` (like the grid view). `/albums/`
is the index of every tag with counts; `/tags.json` is the machine-readable tag
list (`[{tag, slug, count}]`, served statically like `feed.xml`). Tags show on
each photo page and link to their album; an **Albums** link sits in the
timeline/grid header. Tags are assigned at upload via the `tags` field on
`POST /upload` (comma-separated) or by editing a sidecar's `tags`. All derived at
build time — no database.
### Link previews
Photo pages carry Open Graph + Twitter-card meta tags so a shared link unfurls
@@ -142,12 +159,15 @@ nix build # succeeds
|---|---|
| `main.go` | Flags, wires generator + watcher + server |
| `generator.go` | Scans content dir, renders HTML + Atom feed, generates thumbs/mediums |
| `albums.go` | Tag parsing/normalization, `groupByTag`, album pages + `/albums/` + `/tags.json` |
| `server.go` | Upload API + optional static file serving |
| `watcher.go` | fsnotify watcher with 500ms debounce |
| `assets.go` | `//go:embed` for templates and static |
| `templates/timeline.html` | Site root: photos grouped by day, paginated |
| `templates/grid.html` | Flat square grid at `/grid/`, paginated |
| `templates/day.html` | Single-day page at `/day/<date>/` |
| `templates/album.html` | Album (tag) page at `/album/<slug>/`, paginated |
| `templates/albums.html` | Album index at `/albums/` |
| `templates/photo.html` | Single photo page, neighbour links and prefetch |
| `static/photo-nav.js` | Arrow-key and swipe navigation |
| `module.nix` | NixOS module (systemd service, tmpfiles, nginx vhost) |