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
@@ -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) |
|
||||
|
||||
Reference in new issue
Block a user