diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-21 09:45:21 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-21 09:45:21 +0300 |
| commit | ed549aaa423f10d15171c678303fea679d997220 (patch) | |
| tree | 811ec27c8393cdb2ddf38411997988b1f075f491 | |
| parent | 6e6cc0b44fce859669bd967c5d98a39074d8251c (diff) | |
Fix W3C HTML validation errors in generated pages; release 0.10.10.10.1
Every shuriken-generated page failed W3C validation. Fixed across the
templates and the bash thumbnail builders so all page categories (splash,
gallery, photo view, details, stats overview, stats filter mini-albums and
redirect stubs) validate cleanly:
- Add <!DOCTYPE html>, <html lang="en"> and <meta charset="utf-8"> to the
header, splash and redirect templates.
- Drop the obsolete type="text/css" on <style> and border='0' on <img>.
- Fix invalid CSS "margin: 2 auto" -> "margin: 2px auto".
- Add required alt attributes to every <img> (splash, thumbnails, views).
- Replace the obsolete name attribute on <a> thumbnail anchors with id (in
album-render.source.sh and stats-filter-album.source.sh, where the markup
is actually built, plus preview.tmpl for consistency).
- Give redirect stubs a <title>; drop trailing slashes on void elements.
Updated the affected cli.sh assertions and rebuilt bin/shuriken.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| -rw-r--r-- | Justfile | 2 | ||||
| -rwxr-xr-x | bin/shuriken | 22 | ||||
| -rw-r--r-- | share/templates/default/details.tmpl | 4 | ||||
| -rw-r--r-- | share/templates/default/footer.tmpl | 2 | ||||
| -rw-r--r-- | share/templates/default/header.tmpl | 8 | ||||
| -rw-r--r-- | share/templates/default/preview.tmpl | 4 | ||||
| -rw-r--r-- | share/templates/default/redirect.tmpl | 5 | ||||
| -rw-r--r-- | share/templates/default/splash.tmpl | 8 | ||||
| -rw-r--r-- | share/templates/default/view.tmpl | 4 | ||||
| -rw-r--r-- | src/lib/album-render.source.sh | 12 | ||||
| -rw-r--r-- | src/lib/stats-filter-album.source.sh | 6 | ||||
| -rwxr-xr-x | src/shuriken.sh | 2 | ||||
| -rwxr-xr-x | tests/cli.sh | 16 |
13 files changed, 51 insertions, 44 deletions
@@ -1,7 +1,7 @@ set shell := ["bash", "-euo", "pipefail", "-c"] NAME := "shuriken" -VERSION := "0.10.0" +VERSION := "0.10.1" DESTDIR := env_var_or_default("DESTDIR", "") PREFIX := env_var_or_default("PREFIX", "/usr") BINDIR := env_var_or_default("BINDIR", PREFIX + "/bin") diff --git a/bin/shuriken b/bin/shuriken index 144b4b9..2cd6c8b 100755 --- a/bin/shuriken +++ b/bin/shuriken @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -# shuriken (c) 2011 - 2014, 2022 by Paul Buetow +# shuriken (c) 2011 - 2014, 2022, 2025, 2026 by Paul Buetow # https://codeberg.org/snonux/shuriken.sh if (( BASH_VERSINFO[0] < 5 \ @@ -10,7 +10,7 @@ if (( BASH_VERSINFO[0] < 5 \ exit 1 fi -declare -r VERSION='0.10.0' +declare -r VERSION='0.10.1' declare -r DEFAULTRC="${SHURIKEN_DEFAULT_RC:-/etc/default/shuriken}" declare -r PACKAGED_TEMPLATE_DIR='/usr/share/shuriken/templates/default' declare -r PACKAGED_ASSET_DIR='/usr/share/shuriken/assets' @@ -2608,9 +2608,9 @@ queue_preview_page_render_job() { } # Append one thumbnail's markup to a page's accumulating thumbnail-grid buffer. -# Produces exactly the bytes the old per-thumbnail preview.tmpl emitted (the -# <a name=... href=...><img class='thumb <anim>' .../></a> block), so batching -# all thumbnails into one previewpage render stays byte-identical. Every +# Produces the per-thumbnail preview.tmpl block (the +# <a id=... href=...><img class='thumb <anim>' alt=... src=...></a> block), so +# batching all thumbnails into one previewpage render stays consistent. Every # interpolated value is HTML-escaped like the template's context_html fields; the # seeded "slow" animation class is preserved exactly. Blocks are separated by a # newline; the previewpage template adds the single trailing newline, matching @@ -2655,10 +2655,10 @@ build_preview_thumbnail() { anim_html=$(_html_escape "$animation_class") backhref_html=$(_html_escape "$backhref") thumbs_dir_html=$(_html_escape "$thumbs_dir") - printf '<a name=%s href=%s>\n' \ + printf '<a id=%s href=%s>\n' \ "'$photo_html'" "'$page_num-$preview_num.html'" - printf " <img class='thumb %s' src='%s/%s/%s' />\n</a>" \ - "$anim_html" "$backhref_html" "$thumbs_dir_html" "$photo_html" + printf " <img class='thumb %s' alt='%s' src='%s/%s/%s'>\n</a>" \ + "$anim_html" "$photo_html" "$backhref_html" "$thumbs_dir_html" "$photo_html" } render_view_page() { @@ -4453,9 +4453,9 @@ _stats_filter_thumbnail() { animation_class=$(random_animation_css_class slow "$photo") # The view page sits in the same directory as this gallery, so link to it by # bare index; the thumbnail image lives at the album root via backhref. - printf ' <a name="%s" href="%d.html">' "$photo_html" "$index" - printf '<img class="thumb %s" src="%s/%s/%s" /></a>\n' \ - "$animation_class" "$backhref_html" "$STATS_THUMBS_DIR" "$photo_html" + printf ' <a id="%s" href="%d.html">' "$photo_html" "$index" + printf '<img class="thumb %s" alt="%s" src="%s/%s/%s"></a>\n' \ + "$animation_class" "$photo_html" "$backhref_html" "$STATS_THUMBS_DIR" "$photo_html" } # Build the full thumbnail grid for one filter from its newline-separated photo diff --git a/share/templates/default/details.tmpl b/share/templates/default/details.tmpl index f8e09d3..cd70328 100644 --- a/share/templates/default/details.tmpl +++ b/share/templates/default/details.tmpl @@ -9,12 +9,12 @@ cat <<END <div class="details-layout"> <div class="details-photo-column"> <a class="details-photo-link" href="${render_page_num_html}-${render_view_next_html}-details.html"> - <img class='view details-photo ${render_animation_class_html}' border='0' src='${render_backhref_html}/${render_photos_dir_html}/${render_photo_html}'${render_exif_tooltip_attr} /> + <img class='view details-photo ${render_animation_class_html}' alt='${render_photo_html}' src='${render_backhref_html}/${render_photos_dir_html}/${render_photo_html}'${render_exif_tooltip_attr}> </a> <div class="navigator details-navigator"> <a href="${render_page_num_html}-${render_view_prev_html}-details.html" class="arrow">⇐</a> <a href="${render_page_num_html}-${render_preview_num_html}.html">Image view</a> | - <a href ='${render_backhref_html}/${render_photos_dir_html}/${render_photo_html}'>Direct link</a> + <a href='${render_backhref_html}/${render_photos_dir_html}/${render_photo_html}'>Direct link</a> <a href="${render_page_num_html}-${render_view_next_html}-details.html" class="arrow">⇒</a> </div> </div> diff --git a/share/templates/default/footer.tmpl b/share/templates/default/footer.tmpl index c9906a5..3b8865c 100644 --- a/share/templates/default/footer.tmpl +++ b/share/templates/default/footer.tmpl @@ -1,7 +1,7 @@ if [ "${render_tarball_include}" = 'yes' ]; then cat <<END <div class="footer"> - Download all photos in original size <a href='${render_backhref_html}/${render_tarball_name_html}'>here</a><br /><br /> + Download all photos in original size <a href='${render_backhref_html}/${render_tarball_name_html}'>here</a><br><br> </div> END fi diff --git a/share/templates/default/header.tmpl b/share/templates/default/header.tmpl index 37c3559..4ba159e 100644 --- a/share/templates/default/header.tmpl +++ b/share/templates/default/header.tmpl @@ -1,9 +1,11 @@ cat <<END -<html> +<!DOCTYPE html> +<html lang="en"> <head> + <meta charset="utf-8"> <title>${render_title_html}</title> <link rel="icon" href="${render_backhref_html}/favicon.ico" type="image/x-icon"> - <style type="text/css"> + <style> body { background-color: #000000; color: #ffffff; @@ -36,7 +38,7 @@ cat <<END border: 3px solid #ffffff; padding: 3px; max-width: 400px; - margin: 2 auto; + margin: 2px auto; } div.details-panel { diff --git a/share/templates/default/preview.tmpl b/share/templates/default/preview.tmpl index a57cb06..f5fdb57 100644 --- a/share/templates/default/preview.tmpl +++ b/share/templates/default/preview.tmpl @@ -1,5 +1,5 @@ cat <<END -<a name='${render_photo_html}' href='${render_page_num_html}-${render_preview_num_html}.html'> - <img class='thumb ${render_animation_class_html}' src='${render_backhref_html}/${render_thumbs_dir_html}/${render_photo_html}' /> +<a id='${render_photo_html}' href='${render_page_num_html}-${render_preview_num_html}.html'> + <img class='thumb ${render_animation_class_html}' alt='${render_photo_html}' src='${render_backhref_html}/${render_thumbs_dir_html}/${render_photo_html}'> </a> END diff --git a/share/templates/default/redirect.tmpl b/share/templates/default/redirect.tmpl index b7119aa..4aeb22d 100644 --- a/share/templates/default/redirect.tmpl +++ b/share/templates/default/redirect.tmpl @@ -1,6 +1,9 @@ cat <<END -<html> +<!DOCTYPE html> +<html lang="en"> <head> + <meta charset="utf-8"> + <title>Redirecting</title> <link rel="icon" href="./favicon.ico" type="image/x-icon"> <meta http-equiv='refresh' content='0; url=${render_redirect_page_html}.html'> </head> diff --git a/share/templates/default/splash.tmpl b/share/templates/default/splash.tmpl index 5363978..de5837a 100644 --- a/share/templates/default/splash.tmpl +++ b/share/templates/default/splash.tmpl @@ -1,9 +1,11 @@ cat <<END -<html> +<!DOCTYPE html> +<html lang="en"> <head> + <meta charset="utf-8"> <title>${render_title_html}</title> <link rel="icon" href="${render_backhref_html}/favicon.ico" type="image/x-icon"> - <style type="text/css"> + <style> body { background-color: #000000; color: #ffffff; @@ -68,7 +70,7 @@ cat <<END <main class="splash"> <h1>${render_title_html}</h1> <a href="${render_enter_page_html}.html"> - <img class="splash-photo" src="${render_backhref_html}/${render_photos_dir_html}/${render_photo_html}" /> + <img class="splash-photo" alt="${render_title_html}" src="${render_backhref_html}/${render_photos_dir_html}/${render_photo_html}"> </a> <p class="enter"> <a href="${render_enter_page_html}.html">Enter album</a> diff --git a/share/templates/default/view.tmpl b/share/templates/default/view.tmpl index c6a0e7e..b1e6c79 100644 --- a/share/templates/default/view.tmpl +++ b/share/templates/default/view.tmpl @@ -9,13 +9,13 @@ fi cat <<END <div class='view'> <a href="${render_page_num_html}-${render_view_next_html}.html"> - <img class='view ${render_animation_class_html}' border='0' src='${render_backhref_html}/${render_photos_dir_html}/${render_photo_html}'${render_exif_tooltip_attr} /> + <img class='view ${render_animation_class_html}' alt='${render_photo_html}' src='${render_backhref_html}/${render_photos_dir_html}/${render_photo_html}'${render_exif_tooltip_attr}> </a> <div class="navigator"> <a href="${render_page_num_html}-${render_view_prev_html}.html" class="arrow">⇐</a> <a href="page-${render_page_num_html}.html#${render_photo_html}">Thumbnails</a> | <a href="${render_page_num_html}-${render_preview_num_html}-details.html">Details</a> | - <a href ='${render_backhref_html}/${render_photos_dir_html}/${render_photo_html}'>Direct link</a> + <a href='${render_backhref_html}/${render_photos_dir_html}/${render_photo_html}'>Direct link</a> <a href="${render_page_num_html}-${render_view_next_html}.html" class="arrow">⇒</a> </div> </div> diff --git a/src/lib/album-render.source.sh b/src/lib/album-render.source.sh index e5817f3..b077dd5 100644 --- a/src/lib/album-render.source.sh +++ b/src/lib/album-render.source.sh @@ -187,9 +187,9 @@ queue_preview_page_render_job() { } # Append one thumbnail's markup to a page's accumulating thumbnail-grid buffer. -# Produces exactly the bytes the old per-thumbnail preview.tmpl emitted (the -# <a name=... href=...><img class='thumb <anim>' .../></a> block), so batching -# all thumbnails into one previewpage render stays byte-identical. Every +# Produces the per-thumbnail preview.tmpl block (the +# <a id=... href=...><img class='thumb <anim>' alt=... src=...></a> block), so +# batching all thumbnails into one previewpage render stays consistent. Every # interpolated value is HTML-escaped like the template's context_html fields; the # seeded "slow" animation class is preserved exactly. Blocks are separated by a # newline; the previewpage template adds the single trailing newline, matching @@ -234,10 +234,10 @@ build_preview_thumbnail() { anim_html=$(_html_escape "$animation_class") backhref_html=$(_html_escape "$backhref") thumbs_dir_html=$(_html_escape "$thumbs_dir") - printf '<a name=%s href=%s>\n' \ + printf '<a id=%s href=%s>\n' \ "'$photo_html'" "'$page_num-$preview_num.html'" - printf " <img class='thumb %s' src='%s/%s/%s' />\n</a>" \ - "$anim_html" "$backhref_html" "$thumbs_dir_html" "$photo_html" + printf " <img class='thumb %s' alt='%s' src='%s/%s/%s'>\n</a>" \ + "$anim_html" "$photo_html" "$backhref_html" "$thumbs_dir_html" "$photo_html" } render_view_page() { diff --git a/src/lib/stats-filter-album.source.sh b/src/lib/stats-filter-album.source.sh index 3f55342..49eb390 100644 --- a/src/lib/stats-filter-album.source.sh +++ b/src/lib/stats-filter-album.source.sh @@ -53,9 +53,9 @@ _stats_filter_thumbnail() { animation_class=$(random_animation_css_class slow "$photo") # The view page sits in the same directory as this gallery, so link to it by # bare index; the thumbnail image lives at the album root via backhref. - printf ' <a name="%s" href="%d.html">' "$photo_html" "$index" - printf '<img class="thumb %s" src="%s/%s/%s" /></a>\n' \ - "$animation_class" "$backhref_html" "$STATS_THUMBS_DIR" "$photo_html" + printf ' <a id="%s" href="%d.html">' "$photo_html" "$index" + printf '<img class="thumb %s" alt="%s" src="%s/%s/%s"></a>\n' \ + "$animation_class" "$photo_html" "$backhref_html" "$STATS_THUMBS_DIR" "$photo_html" } # Build the full thumbnail grid for one filter from its newline-separated photo diff --git a/src/shuriken.sh b/src/shuriken.sh index de53d56..4fe3fd5 100755 --- a/src/shuriken.sh +++ b/src/shuriken.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -# shuriken (c) 2011 - 2014, 2022 by Paul Buetow +# shuriken (c) 2011 - 2014, 2022, 2025, 2026 by Paul Buetow # https://codeberg.org/snonux/shuriken.sh if (( BASH_VERSINFO[0] < 5 \ diff --git a/tests/cli.sh b/tests/cli.sh index 006967b..436970a 100755 --- a/tests/cli.sh +++ b/tests/cli.sh @@ -693,8 +693,8 @@ test_generate_no_shuffle_override_uses_sorted_order() { page_html=$(<"$TEST_TMPDIR/dist/page-1.html") test::assert_contains_before \ - "name='01-landscape.jpg'" \ - "name='06-extra.jpg'" \ + "id='01-landscape.jpg'" \ + "id='06-extra.jpg'" \ "$page_html" test::assert_path_absent "$sort_log" test::teardown @@ -2975,7 +2975,7 @@ test_integration_generates_album_outputs_and_cleans() { page_html=$(<"$TEST_TMPDIR/dist/page-1.html") details_html=$(<"$TEST_TMPDIR/dist/1-1-details.html") top_index_html=$(<"$TEST_TMPDIR/dist/index.html") - test::assert_contains "name='04 filename with spaces.jpg'" \ + test::assert_contains "id='04 filename with spaces.jpg'" \ "$(<"$TEST_TMPDIR/dist/page-2.html")" test::assert_contains 'Next 2 pictures' "$page_html" test::assert_contains \ @@ -3009,7 +3009,7 @@ test_integration_generates_album_outputs_and_cleans() { test::assert_contains 'Enter album' "$top_index_html" test::assert_contains 'href="page-1.html"' "$top_index_html" test::assert_contains \ - '<img class="splash-photo" src="./photos/' \ + '<img class="splash-photo" alt="Integration album" src="./photos/' \ "$top_index_html" test::assert_not_contains '<script' "$top_index_html" test::assert_not_contains 'javascript:' "$top_index_html" @@ -3467,7 +3467,7 @@ test_refresh_splash_rewrites_only_index_from_existing_assets() { after_metadata=$(<"$TEST_TMPDIR/dist/shuriken.json") test::assert_contains 'Refreshed splash page' "$output" - test::assert_contains '<img class="splash-photo" src="./photos/' \ + test::assert_contains '<img class="splash-photo" alt="Refresh splash album" src="./photos/' \ "$after_index" if [ "$before_index" = "$after_index" ]; then echo 'FAIL: expected --refresh-splash to choose a new splash image' >&2 @@ -4690,7 +4690,7 @@ BASH html=$(cat "$s/camera-canon-eos-5d/index.html") test::assert_contains 'href="1.html"' "$html" test::assert_contains 'class="thumb ' "$html" - test::assert_contains 'src="../../thumbs/a.jpg" />' "$html" + test::assert_contains 'src="../../thumbs/a.jpg"></a>' "$html" test::assert_contains 'href="2.html"' "$html" # Heading shows the (trusted) camera label and a back-to-stats link. test::assert_contains 'Canon EOS 5D' "$html" @@ -5387,12 +5387,12 @@ test_generate_escapes_html_values() { "$page_html" test::assert_contains "url(\"./blurs/$css_photo\")" "$top_index_html" test::assert_contains "src=\"./photos/$photo_html\"" "$top_index_html" - test::assert_contains "name='$photo_html'" "$page_html" + test::assert_contains "id='$photo_html'" "$page_html" test::assert_contains "src='./thumbs/$photo_html'" "$page_html" test::assert_contains '&"'.tar' "$page_html" test::assert_contains "href=\"page-1.html#$photo_html\"" "$view_html" test::assert_contains 'href="1-1-details.html">Details</a>' "$view_html" - test::assert_contains "href ='./photos/$photo_html'" "$view_html" + test::assert_contains "href='./photos/$photo_html'" "$view_html" test::assert_contains \ "href=\"$original_basepath_html/$photo_html\"" \ "$view_html" |
