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 | |
| 14 | 14 | var funcs = template.FuncMap{ |
| 15 | | "albumURL": albumURL, |
| 16 | | "photoURL": photoURL, |
| 17 | | "imgURL": imgURL, |
| 18 | | "srcset": srcset, |
| 19 | | "ar": aspect, |
| 20 | | "exif": exifLine, |
| 21 | | "add": func(a, b int) int { return a + b }, |
| 15 | "albumURL": albumURL, |
| 16 | "photoURL": photoURL, |
| 17 | "imgURL": imgURL, |
| 18 | "srcset": srcset, |
| 19 | "preloadSrcset": preloadSrcset, |
| 20 | "ar": aspect, |
| 21 | "exif": exifLine, |
| 22 | "add": func(a, b int) int { return a + b }, |
| 22 | 23 | } |
| 23 | 24 | |
| 24 | 25 | func albumURL(a *library.Album) string { |
| @@ -50,6 +51,13 @@ func srcset(it *library.Item) string { |
| 50 | 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. |
| 57 | func preloadSrcset(it *library.Item) template.HTMLAttr { |
| 58 | return template.HTMLAttr(`imagesrcset="` + template.HTMLEscapeString(srcset(it)) + `"`) |
| 59 | } |
| 60 | |
| 53 | 61 | func aspect(it *library.Item) string { |
| 54 | 62 | if it.Meta.Height == 0 { |
| 55 | 63 | return "1" |
internal/web/server_test.go
+1 −1
| @@ -96,7 +96,7 @@ func TestPages(t *testing.T) { |
| 96 | 96 | }{ |
| 97 | 97 | {"/", 200, []string{`href="/tokyo/"`, `data-ar="1.5000"`, "Tokyo", "2025", `href="/info/"`}, nil}, |
| 98 | 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 | 100 | {"/tokyo/b.jpg/", 200, []string{`rel="prev" href="/tokyo/a.jpg/"`, "2 / 2"}, []string{`rel="next"`}}, |
| 101 | 101 | {"/info/", 200, []string{"<em>there</em>"}, nil}, |
| 102 | 102 | {"/nope/", 404, []string{"404"}, nil}, |
internal/web/templates/photo.html
+2 −2
| @@ -11,6 +11,6 @@ |
| 11 | 11 | <span>{{exif .Item}}</span> |
| 12 | 12 | </footer> |
| 13 | 13 | <a id="up" href="{{albumURL .Album}}" hidden></a> |
| 14 | | {{with .Prev}}<link rel="preload" as="image" imagesrcset="{{srcset .}}" imagesizes="100vw">{{end}} |
| 15 | | {{with .Next}}<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" {{preloadSrcset .}} imagesizes="100vw">{{end}} |
| 16 | 16 | {{end}} |