summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-21 09:45:21 +0300
committerPaul Buetow <paul@buetow.org>2026-06-21 09:45:21 +0300
commited549aaa423f10d15171c678303fea679d997220 (patch)
tree811ec27c8393cdb2ddf38411997988b1f075f491
parent6e6cc0b44fce859669bd967c5d98a39074d8251c (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--Justfile2
-rwxr-xr-xbin/shuriken22
-rw-r--r--share/templates/default/details.tmpl4
-rw-r--r--share/templates/default/footer.tmpl2
-rw-r--r--share/templates/default/header.tmpl8
-rw-r--r--share/templates/default/preview.tmpl4
-rw-r--r--share/templates/default/redirect.tmpl5
-rw-r--r--share/templates/default/splash.tmpl8
-rw-r--r--share/templates/default/view.tmpl4
-rw-r--r--src/lib/album-render.source.sh12
-rw-r--r--src/lib/stats-filter-album.source.sh6
-rwxr-xr-xsrc/shuriken.sh2
-rwxr-xr-xtests/cli.sh16
13 files changed, 51 insertions, 44 deletions
diff --git a/Justfile b/Justfile
index 5505691..f654c63 100644
--- a/Justfile
+++ b/Justfile
@@ -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">&lArr;</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">&rArr;</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">&lArr;</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">&rArr;</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 '&amp;&quot;&#39;.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"