Commit 0ed36f2a40

0ed36f2a409e00e0c52a4830e7c07517040f59ce

parent: 9854108a6b

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-26 18:55 UTC

Render preload imagesrcset as a whole attribute

html/template treats unknown attributes containing "src" as a single URL
and percent-encoded the spaces and commas of the srcset list.

Ref #2

Layout: unified · split

internal/web/funcs.go +15 −7
@@ -12,13 +12,14 @@ import (
12) 12)
13 13
14var funcs = template.FuncMap{ 14var funcs = template.FuncMap{
15 "albumURL": albumURL, 15 "albumURL": albumURL,
16 "photoURL": photoURL, 16 "photoURL": photoURL,
17 "imgURL": imgURL, 17 "imgURL": imgURL,
18 "srcset": srcset, 18 "srcset": srcset,
19 "ar": aspect, 19 "preloadSrcset": preloadSrcset,
20 "exif": exifLine, 20 "ar": aspect,
21 "add": func(a, b int) int { return a + b }, 21 "exif": exifLine,
22 "add": func(a, b int) int { return a + b },
22} 23}
23 24
24func albumURL(a *library.Album) string { 25func albumURL(a *library.Album) string {
@@ -50,6 +51,13 @@ func srcset(it *library.Item) string {
50 return strings.Join(parts, ", ") 51 return strings.Join(parts, ", ")
51} 52}
52 53
54// preloadSrcset renders the whole imagesrcset attribute: html/template treats
55// any unknown attribute containing "src" as a single URL and would escape the
56// spaces and commas a srcset list needs.
57func preloadSrcset(it *library.Item) template.HTMLAttr {
58 return template.HTMLAttr(`imagesrcset="` + template.HTMLEscapeString(srcset(it)) + `"`)
59}
60
53func aspect(it *library.Item) string { 61func aspect(it *library.Item) string {
54 if it.Meta.Height == 0 { 62 if it.Meta.Height == 0 {
55 return "1" 63 return "1"
internal/web/server_test.go +1 −1
@@ -96,7 +96,7 @@ func TestPages(t *testing.T) {
96 }{ 96 }{
97 {"/", 200, []string{`href="/tokyo/"`, `data-ar="1.5000"`, "Tokyo", "2025", `href="/info/"`}, nil}, 97 {"/", 200, []string{`href="/tokyo/"`, `data-ar="1.5000"`, "Tokyo", "2025", `href="/info/"`}, nil},
98 {"/tokyo/", 200, []string{`href="/tokyo/a.jpg/"`, `href="/tokyo/b.jpg/"`, `/img/tokyo/a.jpg/960.jpg?v=`, `480w`, `class="on"`, `alt="a.jpg"`}, []string{`alt=""`}}, 98 {"/tokyo/", 200, []string{`href="/tokyo/a.jpg/"`, `href="/tokyo/b.jpg/"`, `/img/tokyo/a.jpg/960.jpg?v=`, `480w`, `class="on"`, `alt="a.jpg"`}, []string{`alt=""`}},
99 {"/tokyo/a.jpg/", 200, []string{"a.jpg — 1 / 2", "X-T5 · f/2 · 1/125 · ISO 1600 · 2025-03-14", `rel="next" href="/tokyo/b.jpg/"`, `id="up" href="/tokyo/"`, `alt="a.jpg"`, `rel="preload" as="image" imagesrcset="/img/tokyo/b.jpg/480.jpg`, `imagesizes="100vw"`}, []string{`rel="prev"`, `alt=""`}}, 99 {"/tokyo/a.jpg/", 200, []string{"a.jpg — 1 / 2", "X-T5 · f/2 · 1/125 · ISO 1600 · 2025-03-14", `rel="next" href="/tokyo/b.jpg/"`, `id="up" href="/tokyo/"`, `alt="a.jpg"`, `rel="preload" as="image" imagesrcset="/img/tokyo/b.jpg/480.jpg?v=`, ` 480w, /img/tokyo/b.jpg/960.jpg?v=`, `imagesizes="100vw"`}, []string{`rel="prev"`, `alt=""`}},
100 {"/tokyo/b.jpg/", 200, []string{`rel="prev" href="/tokyo/a.jpg/"`, "2 / 2"}, []string{`rel="next"`}}, 100 {"/tokyo/b.jpg/", 200, []string{`rel="prev" href="/tokyo/a.jpg/"`, "2 / 2"}, []string{`rel="next"`}},
101 {"/info/", 200, []string{"<em>there</em>"}, nil}, 101 {"/info/", 200, []string{"<em>there</em>"}, nil},
102 {"/nope/", 404, []string{"404"}, nil}, 102 {"/nope/", 404, []string{"404"}, nil},
internal/web/templates/photo.html +2 −2
@@ -11,6 +11,6 @@
11 <span>{{exif .Item}}</span> 11 <span>{{exif .Item}}</span>
12</footer> 12</footer>
13<a id="up" href="{{albumURL .Album}}" hidden></a> 13<a id="up" href="{{albumURL .Album}}" hidden></a>
14{{with .Prev}}<link rel="preload" as="image" imagesrcset="{{srcset .}}" imagesizes="100vw">{{end}} 14{{with .Prev}}<link rel="preload" as="image" {{preloadSrcset .}} imagesizes="100vw">{{end}}
15{{with .Next}}<link rel="preload" as="image" imagesrcset="{{srcset .}}" imagesizes="100vw">{{end}} 15{{with .Next}}<link rel="preload" as="image" {{preloadSrcset .}} imagesizes="100vw">{{end}}
16{{end}} 16{{end}}