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.
This commit is contained in:
2026-08-19 23:46:28 +02:00
parent c169c030d6
commit 9b6fcb482b
5 changed files with 132 additions and 0 deletions
+53
View File
@@ -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 {