Add single-day pages linked from timeline date headings
Each timeline date heading now links to /day/<YYYY-MM-DD>/, a page holding just that day's photos with a back link to the timeline. renderDayPages writes one per groupByDay run; the ISO label doubles as the URL slug.
This commit is contained in:
@@ -48,6 +48,7 @@ Missing sidecar → filename as caption, today as date. Photos sort newest first
|
|||||||
- Two views of the same photos, both 3-column square grid → 2-column on mobile, both with infinite-scroll load-more. Page size is configurable via `-page-size` (default 60; NixOS `services.photogallery.pageSize`):
|
- Two views of the same photos, both 3-column square grid → 2-column on mobile, both with infinite-scroll load-more. Page size is configurable via `-page-size` (default 60; NixOS `services.photogallery.pageSize`):
|
||||||
- **Timeline is the site root** (`/`, `/page/2/`, …). Photos grouped by calendar day: one ISO date heading (`2026-06-14`) + count per day, then the square grid. `groupByDay` collapses the newest-first list into consecutive same-day runs; `chunkDayGroups` packs whole days into pages targeting `pageSize`, never splitting a day across a page boundary (so appended pages never duplicate a heading — the load-more JS relies on this).
|
- **Timeline is the site root** (`/`, `/page/2/`, …). Photos grouped by calendar day: one ISO date heading (`2026-06-14`) + count per day, then the square grid. `groupByDay` collapses the newest-first list into consecutive same-day runs; `chunkDayGroups` packs whole days into pages targeting `pageSize`, never splitting a day across a page boundary (so appended pages never duplicate a heading — the load-more JS relies on this).
|
||||||
- **Grid** (`/grid/`, `/grid/page/2/`, …) is the flat square grid, linked from the profile header.
|
- **Grid** (`/grid/`, `/grid/page/2/`, …) is the flat square grid, linked from the profile header.
|
||||||
|
- 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
|
- Single photo page with sidebar and newer/older navigation
|
||||||
|
|
||||||
### Photo page navigation
|
### Photo page navigation
|
||||||
@@ -133,6 +134,7 @@ nix build # succeeds
|
|||||||
| `assets.go` | `//go:embed` for templates and static |
|
| `assets.go` | `//go:embed` for templates and static |
|
||||||
| `templates/timeline.html` | Site root: photos grouped by day, paginated |
|
| `templates/timeline.html` | Site root: photos grouped by day, paginated |
|
||||||
| `templates/grid.html` | Flat square grid at `/grid/`, paginated |
|
| `templates/grid.html` | Flat square grid at `/grid/`, paginated |
|
||||||
|
| `templates/day.html` | Single-day page at `/day/<date>/` |
|
||||||
| `templates/photo.html` | Single photo page, neighbour links and prefetch |
|
| `templates/photo.html` | Single photo page, neighbour links and prefetch |
|
||||||
| `static/photo-nav.js` | Arrow-key and swipe navigation |
|
| `static/photo-nav.js` | Arrow-key and swipe navigation |
|
||||||
| `module.nix` | NixOS module (systemd service, tmpfiles, nginx vhost) |
|
| `module.nix` | NixOS module (systemd service, tmpfiles, nginx vhost) |
|
||||||
|
|||||||
+35
-1
@@ -106,7 +106,7 @@ func (g *Generator) Build() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range []string{"images", "thumbs", "medium", "photo", "grid", "static"} {
|
for _, d := range []string{"images", "thumbs", "medium", "photo", "grid", "day", "static"} {
|
||||||
if err := os.MkdirAll(filepath.Join(g.outputDir, d), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Join(g.outputDir, d), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -124,6 +124,9 @@ func (g *Generator) Build() error {
|
|||||||
if err := g.renderGridPages(photos); err != nil {
|
if err := g.renderGridPages(photos); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := g.renderDayPages(photos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for i := range photos {
|
for i := range photos {
|
||||||
var newer, older *Photo
|
var newer, older *Photo
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
@@ -508,6 +511,37 @@ func (g *Generator) renderTimelinePages(photos []Photo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renderDayPages writes one page per calendar day under public/day/<ISO>/,
|
||||||
|
// holding just that day's photos. Timeline date headings link here.
|
||||||
|
func (g *Generator) renderDayPages(photos []Photo) error {
|
||||||
|
for _, grp := range groupByDay(photos) {
|
||||||
|
dir := filepath.Join(g.outputDir, "day", grp.day)
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err := g.render("day.html", filepath.Join(dir, "index.html"), map[string]any{
|
||||||
|
"Date": grp.Label,
|
||||||
|
"Count": grp.Count,
|
||||||
|
"Photos": grp.Photos,
|
||||||
|
"Total": len(photos),
|
||||||
|
"BaseURL": g.baseURL,
|
||||||
|
"Year": time.Now().Year(),
|
||||||
|
"Author": g.author,
|
||||||
|
"Handle": g.handle,
|
||||||
|
"Bio": g.bio,
|
||||||
|
"BioAlt": g.bioAlt,
|
||||||
|
"Initial": g.initial(),
|
||||||
|
"FontsURL": g.fontsURL,
|
||||||
|
"SerifFamily": g.serifFamily,
|
||||||
|
"MlFamily": g.mlFamily,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Generator) render(tmpl, dst string, data any) error {
|
func (g *Generator) render(tmpl, dst string, data any) error {
|
||||||
f, err := os.Create(dst)
|
f, err := os.Create(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -217,6 +217,15 @@ body {
|
|||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tl-date a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tl-date a:hover {
|
||||||
|
color: var(--aqua);
|
||||||
|
}
|
||||||
|
|
||||||
.tl-count {
|
.tl-count {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{.Date}} · Photo Gallery</title>
|
||||||
|
<link rel="stylesheet" href="{{.FontsURL}}">
|
||||||
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
<style>:root { --serif: '{{.SerifFamily}}', Georgia, serif; --mj: '{{.MlFamily}}', sans-serif; }</style>
|
||||||
|
<link rel="alternate" type="application/atom+xml" href="/feed.xml" title="Photo Feed">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="profile-hdr">
|
||||||
|
<div class="profile-inner">
|
||||||
|
<div class="avatar-ring">
|
||||||
|
<span class="avatar-initials">{{.Initial}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="pinfo">
|
||||||
|
<h1>{{.Author}}</h1>
|
||||||
|
<p class="handle">{{.Handle}}</p>
|
||||||
|
<p class="pcount"><strong>{{.Total}}</strong> posts</p>
|
||||||
|
<p class="pbio">{{.Bio}}{{if .BioAlt}}<br><span class="ml">{{.BioAlt}}</span>{{end}}</p>
|
||||||
|
<a href="/" class="rss-a">← Timeline</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="grid-wrap">
|
||||||
|
<section class="tl-day">
|
||||||
|
<h2 class="tl-date">{{.Date}}<span class="tl-count">{{.Count}}</span></h2>
|
||||||
|
<div class="pgrid">
|
||||||
|
{{range .Photos}}
|
||||||
|
<a href="/photo/{{.Slug}}/" class="ptile">
|
||||||
|
<img src="/{{.Thumb}}" alt="{{.Caption}}" loading="lazy">
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="site-footer">
|
||||||
|
© 2000–{{.Year}} {{.Author}}. All rights reserved.
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<div class="grid-wrap">
|
<div class="grid-wrap">
|
||||||
{{range .Groups}}
|
{{range .Groups}}
|
||||||
<section class="tl-day">
|
<section class="tl-day">
|
||||||
<h2 class="tl-date">{{.Label}}<span class="tl-count">{{.Count}}</span></h2>
|
<h2 class="tl-date"><a href="/day/{{.Label}}/">{{.Label}}</a><span class="tl-count">{{.Count}}</span></h2>
|
||||||
<div class="pgrid">
|
<div class="pgrid">
|
||||||
{{range .Photos}}
|
{{range .Photos}}
|
||||||
<a href="/photo/{{.Slug}}/" class="ptile">
|
<a href="/photo/{{.Slug}}/" class="ptile">
|
||||||
|
|||||||
Reference in New Issue
Block a user