Paginate the gallery grid with 60 photos per page
Emit /page/N/ pages alongside index.html and render a pager at the bottom of each page. Ships as a plain link that navigates without JS, and an IntersectionObserver enhancement that fetches the next page and appends tiles for an infinite-scroll feel.
This commit is contained in:
+66
-16
@@ -12,6 +12,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
const (
|
||||
thumbSize = 600
|
||||
mediumMaxSize = 1600
|
||||
pageSize = 60
|
||||
)
|
||||
|
||||
// PhotoMeta is the structure of a .toml sidecar file.
|
||||
@@ -103,7 +105,7 @@ func (g *Generator) Build() error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := g.renderIndex(photos); err != nil {
|
||||
if err := g.renderIndexPages(photos); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range photos {
|
||||
@@ -290,21 +292,69 @@ func generateMedium(src, dst string, maxDim int) error {
|
||||
return jpeg.Encode(out, resized, &jpeg.Options{Quality: 88})
|
||||
}
|
||||
|
||||
// renderIndex writes public/index.html.
|
||||
func (g *Generator) renderIndex(photos []Photo) error {
|
||||
return g.render("index.html", filepath.Join(g.outputDir, "index.html"), map[string]any{
|
||||
"Photos": 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,
|
||||
})
|
||||
// renderIndexPages writes public/index.html and public/page/N/index.html
|
||||
// files, chunking photos into pageSize slices for progressive pagination.
|
||||
func (g *Generator) renderIndexPages(photos []Photo) error {
|
||||
total := len(photos)
|
||||
totalPages := (total + pageSize - 1) / pageSize
|
||||
if totalPages == 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
|
||||
for i := 0; i < totalPages; i++ {
|
||||
start := i * pageSize
|
||||
end := start + pageSize
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
slice := photos[start:end]
|
||||
|
||||
dst := filepath.Join(g.outputDir, "index.html")
|
||||
if i > 0 {
|
||||
dir := filepath.Join(g.outputDir, "page", strconv.Itoa(i+1))
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
dst = filepath.Join(dir, "index.html")
|
||||
}
|
||||
|
||||
prev := ""
|
||||
next := ""
|
||||
switch i {
|
||||
case 0:
|
||||
// no prev
|
||||
case 1:
|
||||
prev = "/"
|
||||
default:
|
||||
prev = "/page/" + strconv.Itoa(i) + "/"
|
||||
}
|
||||
if i+1 < totalPages {
|
||||
next = "/page/" + strconv.Itoa(i+2) + "/"
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"Photos": slice,
|
||||
"Total": total,
|
||||
"Page": i + 1,
|
||||
"TotalPages": totalPages,
|
||||
"PrevURL": prev,
|
||||
"NextURL": next,
|
||||
"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 := g.render("index.html", dst, data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// renderPhotoPage writes public/photo/<slug>/index.html.
|
||||
|
||||
Reference in New Issue
Block a user