From 9b6fcb482bdfe283ab6e00cfd908a107b1cece58 Mon Sep 17 00:00:00 2001 From: puttaalu Date: Wed, 19 Aug 2026 23:46:28 +0200 Subject: [PATCH] Add a timeline page grouping photos by day New /timeline/ page renders photos grouped into consecutive same-day runs (ISO date heading plus per-day count), reusing the square grid. groupByDay collapses the newest-first photo list; the page is not paginated. Linked from the profile header. --- CLAUDE.md | 2 ++ generator.go | 53 +++++++++++++++++++++++++++++++++++++++++ static/style.css | 28 ++++++++++++++++++++++ templates/index.html | 1 + templates/timeline.html | 48 +++++++++++++++++++++++++++++++++++++ 5 files changed, 132 insertions(+) create mode 100644 templates/timeline.html diff --git a/CLAUDE.md b/CLAUDE.md index 2c24119..43f5ce4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,6 +47,7 @@ Missing sidecar → filename as caption, today as date. Photos sort newest first - Manjari for Malayalam text (bio line), via `-ml-family` - 3-column square grid → 2-column on mobile, 60 photos per page (`/`, `/page/2/`, …) - Single photo page with sidebar and newer/older navigation +- Timeline page (`/timeline/`) groups photos by calendar day: one ISO date heading (`2026-06-14`) + count per day, then the same square grid. Linked from the profile header; `groupByDay` in `generator.go` collapses the newest-first photo list into consecutive same-day runs, so it is not paginated. ### Photo page navigation @@ -130,6 +131,7 @@ nix build # succeeds | `watcher.go` | fsnotify watcher with 500ms debounce | | `assets.go` | `//go:embed` for templates and static | | `templates/photo.html` | Single photo page, neighbour links and prefetch | +| `templates/timeline.html` | Timeline page, photos grouped by day | | `static/photo-nav.js` | Arrow-key and swipe navigation | | `module.nix` | NixOS module (systemd service, tmpfiles, nginx vhost) | | `flake.nix` | `buildGoModule` package + devShell + nixosModules | diff --git a/generator.go b/generator.go index f347a97..c43c230 100644 --- a/generator.go +++ b/generator.go @@ -43,6 +43,14 @@ type Photo struct { Date time.Time } +// DayGroup is a run of photos sharing the same calendar day, for the timeline. +type DayGroup struct { + day string // "2006-01-02", grouping key (unexported: not for templates) + Label string // "14 June 2026" + Count int + Photos []Photo +} + type Generator struct { contentDir string outputDir string @@ -120,6 +128,9 @@ func (g *Generator) Build() error { return err } } + if err := g.renderTimeline(photos); err != nil { + return err + } if err := g.renderFeed(photos); err != nil { return err } @@ -385,6 +396,48 @@ func (g *Generator) renderPhotoPage(p Photo, newer, older *Photo) error { }) } +// groupByDay collapses photos (already sorted newest first) into per-day runs, +// preserving order. Two photos land in the same group iff their dates share a +// calendar day. +func groupByDay(photos []Photo) []DayGroup { + var groups []DayGroup + for _, p := range photos { + day := p.Date.Format("2006-01-02") + if n := len(groups); n > 0 && groups[n-1].day == day { + groups[n-1].Photos = append(groups[n-1].Photos, p) + groups[n-1].Count++ + continue + } + groups = append(groups, DayGroup{ + day: day, + Label: day, // ISO 2006-01-02 + Count: 1, + Photos: []Photo{p}, + }) + } + return groups +} + +// renderTimeline writes public/timeline/index.html, photos grouped by day. +func (g *Generator) renderTimeline(photos []Photo) error { + dir := filepath.Join(g.outputDir, "timeline") + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + return g.render("timeline.html", filepath.Join(dir, "index.html"), map[string]any{ + "Groups": groupByDay(photos), + "Total": len(photos), + "BaseURL": g.baseURL, + "Year": time.Now().Year(), + "Author": g.author, + "Handle": g.handle, + "Initial": g.initial(), + "FontsURL": g.fontsURL, + "SerifFamily": g.serifFamily, + "MlFamily": g.mlFamily, + }) +} + func (g *Generator) render(tmpl, dst string, data any) error { f, err := os.Create(dst) if err != nil { diff --git a/static/style.css b/static/style.css index 76fb71f..866c0c9 100644 --- a/static/style.css +++ b/static/style.css @@ -187,6 +187,34 @@ body { .pager-newer { margin-right: auto; } .pager-older { margin-left: auto; } +/* ── Timeline ──────────────────────────────────────────────── */ + +.tl-day { + margin-bottom: 34px; +} + +.tl-date { + display: flex; + align-items: baseline; + gap: 10px; + font-family: monospace; + font-size: 15px; + font-weight: 600; + color: var(--fg2); + letter-spacing: 0.04em; + padding: 0 3px 10px; + margin-bottom: 3px; + border-bottom: 1px solid var(--line); +} + +.tl-count { + font-family: monospace; + font-size: 11px; + font-weight: 400; + color: var(--fg4); + letter-spacing: 0.04em; +} + /* ── Single photo page ─────────────────────────────────────── */ .photo-page { diff --git a/templates/index.html b/templates/index.html index a83f242..c24b811 100644 --- a/templates/index.html +++ b/templates/index.html @@ -20,6 +20,7 @@

{{.Handle}}

{{.Total}} posts

{{.Bio}}{{if .BioAlt}}
{{.BioAlt}}{{end}}

+ Timeline + + + Timeline · Photo Gallery + + + + + + +
+ +
+ +
+
+ {{range .Groups}} +
+

{{.Label}}{{.Count}}

+
+ {{range .Photos}} + + {{.Caption}} + + {{end}} +
+
+ {{end}} +
+
+ + + +