summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md13
-rwxr-xr-xbin/shuriken203
-rw-r--r--docs/configuration.md39
-rw-r--r--docs/generation.md19
-rw-r--r--docs/usage.md2
-rw-r--r--share/templates/default/view.tmpl17
-rw-r--r--src/lib/action.source.sh1
-rw-r--r--src/lib/album-render.source.sh131
-rw-r--r--src/lib/bootstrap.source.sh2
-rw-r--r--src/lib/config.spec.source.sh1
-rw-r--r--src/lib/config.validate.source.sh19
-rw-r--r--src/lib/dry-run.source.sh33
-rw-r--r--src/lib/generation-metadata.source.sh3
-rw-r--r--src/lib/stats-filter-album.source.sh10
-rw-r--r--src/lib/template.source.sh1
-rw-r--r--src/shuriken.default.conf7
-rwxr-xr-xsrc/shuriken.sh2
-rwxr-xr-xtests/cli.sh195
18 files changed, 584 insertions, 114 deletions
diff --git a/README.md b/README.md
index ee381a3..8e9eca3 100644
--- a/README.md
+++ b/README.md
@@ -66,15 +66,20 @@ Common per-run overrides (see the full reference table in [docs/usage.md](docs/u
`--incoming`, `--dist`, `--template`, `--title`, `--height`, `--thumbheight`,
`--maxpreviews`, `--image-jobs`, `--random-seed`, `--shuffle`/`--no-shuffle`,
-`--splash`/`--no-splash`, `--stats`/`--no-stats`, `--tarball`/`--no-tarball`,
-`--favicon`, `--source-url`, `--sync-destination`, `--sync-delete`/`--no-sync-delete`,
-`--quiet`, `--verbose`.
+`--splash`/`--no-splash`, `--details`/`--no-details`, `--stats`/`--no-stats`,
+`--tarball`/`--no-tarball`, `--favicon`, `--source-url`, `--sync-destination`,
+`--sync-delete`/`--no-sync-delete`, `--quiet`, `--verbose`.
Feature toggles at a glance:
* **Splash page** (`SPLASH_PAGE=yes`, the default): the root `index.html` is a
no-JavaScript splash page using a random album photo. `--no-splash` restores a
top-level redirect to `page-1.html`.
+* **Details pages** (`DETAILS_PAGE=yes`, the default): every photo gets a
+ `*-details.html` EXIF summary page linked from its normal view page.
+ `--no-details` skips these pages and removes their "Details" links (from the
+ normal view pages and, when stats are enabled, the stats filter mini-albums)
+ without affecting EXIF tooltips or the stats site itself.
* **Stats site** (`STATS_PAGE=no`, the default): set `--stats` to generate a
no-JavaScript EXIF stats site under `stats/` (camera leaderboard, shooting
dates, exposure/dimension/format breakdowns), with each bucket as its own
@@ -90,7 +95,7 @@ The quick start above is all you need for a first album. Detailed reference:
* [docs/installation.md](docs/installation.md) — build, install, paths, packaging overrides, requirements.
* [docs/usage.md](docs/usage.md) — full CLI reference: every action, `--config`, the override-option table, output flags.
* [docs/configuration.md](docs/configuration.md) — the config file format, every variable, defaults, and validation rules.
-* [docs/generation.md](docs/generation.md) — how generation works: artifact reuse, the EXIF cache, `--force`, splash/stats pages, `--refresh-splash`, reproducibility, parallelism/timeouts, `shuriken.json`, favicon, source URL.
+* [docs/generation.md](docs/generation.md) — how generation works: artifact reuse, the EXIF cache, `--force`, splash/details/stats pages, `--refresh-splash`, reproducibility, parallelism/timeouts, `shuriken.json`, favicon, source URL.
* [docs/publishing.md](docs/publishing.md) — publishing with `--sync`, `SYNC_DESTINATIONS`, `SYNC_DELETE`, and the rsync command.
* [docs/templates.md](docs/templates.md) — HTML template layout and customization.
* [docs/stats-exif-audit.md](docs/stats-exif-audit.md) — EXIF field coverage audit behind the stats site (historical design record). \ No newline at end of file
diff --git a/bin/shuriken b/bin/shuriken
index 4e23419..a475a3a 100755
--- a/bin/shuriken
+++ b/bin/shuriken
@@ -55,6 +55,8 @@ declare -Ar CLI_OPTION_SPEC=(
[--no-shuffle]='kind=flag value=no config=SHUFFLE'
[--splash]='kind=flag value=yes config=SPLASH_PAGE'
[--no-splash]='kind=flag value=no config=SPLASH_PAGE'
+ [--details]='kind=flag value=yes config=DETAILS_PAGE'
+ [--no-details]='kind=flag value=no config=DETAILS_PAGE'
[--stats]='kind=flag value=yes config=STATS_PAGE'
[--no-stats]='kind=flag value=no config=STATS_PAGE'
[--tarball]='kind=flag value=yes config=TARBALL_INCLUDE'
@@ -212,6 +214,8 @@ usage() {
--random-seed VALUE
--splash
--no-splash
+ --details
+ --no-details
--stats
--no-stats
--shuffle
@@ -834,6 +838,7 @@ declare -ra TEMPLATE_RENDER_FIELD_SPECS=(
'render_cameraview_body_html|context_raw|cameraview_body|cameraview_body|cameraview'
'render_camera_thumbs_html|context_raw|camera_thumbs|camera_thumbs|camera'
'render_current_date_text|current_date_html|||header'
+ 'render_details_page_html|config_html|DETAILS_PAGE||view'
'render_enter_page_html|context_html|enter_page|enter_page|splash'
'render_exif_details_html|context_raw|exif_details|exif_details|details'
'render_exif_tooltip_html|context_html|exif_tooltip|exif_tooltip|details view'
@@ -2523,6 +2528,7 @@ _collect_generation_metadata() {
_GENERATION_METADATA["settings_random_seed"]="$RANDOM_SEED"
_GENERATION_METADATA["settings_shuffle"]="$SHUFFLE"
_GENERATION_METADATA["settings_splash_page"]="$SPLASH_PAGE"
+ _GENERATION_METADATA["settings_details_page"]="$DETAILS_PAGE"
_GENERATION_METADATA["settings_stats_page"]="$STATS_PAGE"
_GENERATION_METADATA["settings_original_basepath"]="$ORIGINAL_BASEPATH"
}
@@ -2594,6 +2600,8 @@ _generation_metadata_json_settings() {
"$(json_bool "${_GENERATION_METADATA["settings_shuffle"]}")"
printf ' "splash_page": %s,\n' \
"$(json_bool "${_GENERATION_METADATA["settings_splash_page"]}")"
+ printf ' "details_page": %s,\n' \
+ "$(json_bool "${_GENERATION_METADATA["settings_details_page"]}")"
printf ' "stats_page": %s,\n' \
"$(json_bool "${_GENERATION_METADATA["settings_stats_page"]}")"
printf ' "original_basepath": %s\n' \
@@ -2660,9 +2668,14 @@ collect_dry_run_page_plan() {
# files are touched here, so dry-run stays side-effect free.
page_count=$(album_page_count_for_image_count "$image_count")
redirect_count=$(album_redirect_count_for_page_count "$page_count")
- plan_ref["details_count"]="$image_count"
plan_ref["page_count"]="$page_count"
plan_ref["redirect_count"]="$redirect_count"
+ # One details page per photo, but only when DETAILS_PAGE=yes -- mirrors
+ # render_photo_view_and_details skipping render_details_page entirely,
+ # so the plan cannot drift from what --generate actually writes.
+ if [ "$DETAILS_PAGE" = yes ]; then
+ plan_ref["details_count"]="$image_count"
+ fi
fi
}
@@ -2707,6 +2720,7 @@ collect_dry_run_plan() {
plan_ref["random_seed"]="$RANDOM_SEED"
plan_ref["shuffle"]="$SHUFFLE"
plan_ref["splash_page"]="$SPLASH_PAGE"
+ plan_ref["details_page"]="$DETAILS_PAGE"
plan_ref["stats_page"]="$STATS_PAGE"
plan_ref["image_count"]="$image_count"
plan_ref["tarball_include"]="$TARBALL_INCLUDE"
@@ -2741,14 +2755,17 @@ _print_dry_run_settings() {
printf 'Random seed: %s\n' "${plan_ref["random_seed"]}"
printf 'Shuffle: %s\n' "${plan_ref["shuffle"]}"
printf 'Splash page: %s\n' "${plan_ref["splash_page"]}"
+ printf 'Details page: %s\n' "${plan_ref["details_page"]}"
printf 'Stats page: %s\n' "${plan_ref["stats_page"]}"
printf 'Image count: %s\n' "${plan_ref["image_count"]}"
printf 'Tarball setting: %s\n' "${plan_ref["tarball_include"]}"
printf 'Tarball name plan: %s\n' "${plan_ref["tarball_name_plan"]}"
}
-# Print the planned directories and generated-files listing (index/favicon/json,
-# image dirs, page/view/details/redirect counts, optional stats + tarball lines).
+# Print the planned directories, plus the generated-files lines that are always
+# present regardless of any page toggle (index/favicon/json/image dirs/page/view
+# counts). Split from the optional-lines half below (_print_dry_run_optional_files)
+# so each stays around 30 lines, matching the generation-metadata JSON split.
_print_dry_run_files() {
local -r plan_name="$1"; shift
# shellcheck disable=SC2178
@@ -2780,8 +2797,21 @@ _print_dry_run_files() {
"${plan_ref["dist_dir"]}" "${plan_ref["page_count"]}"
printf ' %s/[page]-[image].html (%s view pages)\n' \
"${plan_ref["dist_dir"]}" "${plan_ref["image_count"]}"
- printf ' %s/[page]-[image]-details.html (%s details pages)\n' \
- "${plan_ref["dist_dir"]}" "${plan_ref["details_count"]}"
+}
+
+# Print the generated-files lines gated behind a page toggle (details, the
+# navigation redirect count that itself depends on DETAILS_PAGE, stats, and the
+# tarball). Split out of _print_dry_run_files (see its comment) purely to keep
+# both halves short; output is unchanged from the previous single function.
+_print_dry_run_optional_files() {
+ local -r plan_name="$1"; shift
+ # shellcheck disable=SC2178
+ local -n plan_ref="$plan_name"
+
+ if [ "${plan_ref["details_page"]}" = yes ]; then
+ printf ' %s/[page]-[image]-details.html (%s details pages)\n' \
+ "${plan_ref["dist_dir"]}" "${plan_ref["details_count"]}"
+ fi
printf ' %s/[redirect].html (%s navigation redirects)\n' \
"${plan_ref["dist_dir"]}" "${plan_ref["redirect_count"]}"
if [ "${plan_ref["stats_page"]}" = yes ]; then
@@ -2805,6 +2835,7 @@ print_dry_run_plan() {
_print_dry_run_settings "$plan_name"
_print_dry_run_files "$plan_name"
+ _print_dry_run_optional_files "$plan_name"
}
# Inlined from src/lib/album-tile-layout.source.sh
@@ -3712,6 +3743,10 @@ render_view_page() {
tarball_name "$tarball_name"
}
+# Render one photo's *-details.html page. Only called by
+# render_photo_view_and_details when DETAILS_PAGE=yes; callers must not invoke
+# this directly for a photo when details are disabled, or a dangling file would
+# be produced with no page linking to it.
render_details_page() {
local -r html_dir="$1"; shift
local -r photos_dir="$1"; shift
@@ -3774,15 +3809,22 @@ render_photo_view_and_details() {
"$page_num" \
"$preview_num" \
"$photo"
- render_details_page \
- "$html_dir" \
- "$photos_dir" \
- "$blurs_dir" \
- "$backhref" \
- "$tarball_name" \
- "$page_num" \
- "$preview_num" \
- "$photo"
+
+ # DETAILS_PAGE=no skips the *-details.html file entirely (view.tmpl's Details
+ # link is likewise suppressed via render_details_page_html, so nothing links
+ # to it). This is the only place a photo's details page would be rendered, so
+ # skipping the call here is sufficient to omit the file for every photo.
+ if [ "$DETAILS_PAGE" = yes ]; then
+ render_details_page \
+ "$html_dir" \
+ "$photos_dir" \
+ "$blurs_dir" \
+ "$backhref" \
+ "$tarball_name" \
+ "$page_num" \
+ "$preview_num" \
+ "$photo"
+ fi
}
record_rendered_view_page() {
@@ -3799,32 +3841,78 @@ record_rendered_view_page() {
last_views_ref["$page"]="$preview"
}
-# Navigation-redirect count single source of truth (task nr0). Every view page
-# gets ALBUM_REDIRECTS_PER_PAGE wrap-around redirect files: the prev stub
-# (N-0.html) and its details twin, plus the next stub (N-(last+1).html) and its
-# details twin -- four files, emitted by render_page_view_redirects below for
-# every page. The LAST page additionally emits ALBUM_REDIRECTS_LAST_PAGE_EXTRA
-# files: the 0-MAXPREVIEWS / 0-MAXPREVIEWS-details entry stubs that bounce into
-# the album. Keep these two numbers in lockstep with render_page_view_redirects;
-# the dry-run plan predicts redirect_count from them via
-# album_redirect_count_for_page_count instead of a magic "*4+2".
-declare -gri ALBUM_REDIRECTS_PER_PAGE=4
-declare -gri ALBUM_REDIRECTS_LAST_PAGE_EXTRA=2
+# Navigation-redirect count single source of truth (task nr0; extended for
+# DETAILS_PAGE by task 6v0). Every view page gets ALBUM_VIEW_REDIRECTS_PER_PAGE
+# wrap-around redirect files: the prev stub (N-0.html) and the next stub
+# (N-(last+1).html). When DETAILS_PAGE=yes, each of those also gets a
+# "-details" twin, adding ALBUM_DETAILS_REDIRECTS_PER_PAGE more -- so
+# render_page_view_redirects emits 2 files per page with details disabled, 4
+# with them enabled. The LAST page additionally emits
+# ALBUM_VIEW_REDIRECTS_LAST_PAGE_EXTRA entry stubs (plus their details twins
+# when enabled): the 0-MAXPREVIEWS / loop-to-1 stubs that bounce into the
+# album. Keep these numbers in lockstep with render_page_view_redirects; the
+# dry-run plan predicts redirect_count from them via
+# album_redirect_count_for_page_count instead of a magic formula.
+declare -gri ALBUM_VIEW_REDIRECTS_PER_PAGE=2
+declare -gri ALBUM_DETAILS_REDIRECTS_PER_PAGE=2
+declare -gri ALBUM_VIEW_REDIRECTS_LAST_PAGE_EXTRA=1
+declare -gri ALBUM_DETAILS_REDIRECTS_LAST_PAGE_EXTRA=1
+
+# Per-page / last-page-extra redirect file counts for the CURRENT DETAILS_PAGE
+# setting: the view-only counts, plus the details counts when DETAILS_PAGE=yes.
+# Split out of album_redirect_count_for_page_count so that function stays a
+# short arithmetic one-liner.
+album_redirects_per_page() {
+ local -i count=$ALBUM_VIEW_REDIRECTS_PER_PAGE
+
+ if [ "$DETAILS_PAGE" = yes ]; then
+ count+=$ALBUM_DETAILS_REDIRECTS_PER_PAGE
+ fi
+ printf '%d\n' "$count"
+}
+
+album_redirects_last_page_extra() {
+ local -i count=$ALBUM_VIEW_REDIRECTS_LAST_PAGE_EXTRA
+
+ if [ "$DETAILS_PAGE" = yes ]; then
+ count+=$ALBUM_DETAILS_REDIRECTS_LAST_PAGE_EXTRA
+ fi
+ printf '%d\n' "$count"
+}
# Total navigation redirects a run produces for a given number of preview pages:
-# four per page plus the last page's extra entry stubs. Zero pages -> zero
-# redirects (render_view_redirects returns early on an empty album). This is the
-# count render_page_view_redirects actually writes across all pages, expressed
-# once so the dry-run plan cannot drift from real generation.
+# album_redirects_per_page per page plus the last page's extra entry stubs.
+# Zero pages -> zero redirects (render_view_redirects returns early on an empty
+# album). This is the count render_page_view_redirects actually writes across
+# all pages, expressed once so the dry-run plan cannot drift from real
+# generation.
album_redirect_count_for_page_count() {
local -ri page_count="$1"; shift
+ local -i per_page
+ local -i last_page_extra
if (( page_count <= 0 )); then
printf '0\n'
return
fi
- printf '%d\n' "$(( page_count * ALBUM_REDIRECTS_PER_PAGE \
- + ALBUM_REDIRECTS_LAST_PAGE_EXTRA ))"
+ per_page=$(album_redirects_per_page)
+ last_page_extra=$(album_redirects_last_page_extra)
+ printf '%d\n' "$(( page_count * per_page + last_page_extra ))"
+}
+
+# Render one details-redirect stub, but only when DETAILS_PAGE=yes (mirrors
+# render_photo_view_and_details skipping render_details_page entirely). A no-op
+# under DETAILS_PAGE=no keeps every "-details.html" navigation stub from ever
+# being written, so no generated page can link to a missing one.
+_render_details_redirect() {
+ local -r html_dir="$1"; shift
+ local -r filename="$1"; shift
+ local -r target="$1"; shift
+
+ if [ "$DETAILS_PAGE" != yes ]; then
+ return
+ fi
+ template redirect "$filename" html_dir "$html_dir" redirect_page "$target"
}
# Render every navigation redirect for a single view page (the prev/next
@@ -3834,7 +3922,7 @@ album_redirect_count_for_page_count() {
# files for distinct pages are produced here. The wrap-around redirects for the
# very last page (0-MAXPREVIEWS and the loop-to-1 links) are emitted as part of
# that page's group. Per-page / last-page file counts are fixed by
-# ALBUM_REDIRECTS_PER_PAGE / ALBUM_REDIRECTS_LAST_PAGE_EXTRA above.
+# album_redirects_per_page / album_redirects_last_page_extra above.
render_page_view_redirects() {
local -r html_dir="$1"; shift
local -ri page="$1"; shift
@@ -3846,30 +3934,26 @@ render_page_view_redirects() {
template redirect "$prevredirect.html" \
html_dir "$html_dir" \
redirect_page "$(( page - 1 ))-${MAXPREVIEWS}"
- template redirect "$prevredirect-details.html" \
- html_dir "$html_dir" \
- redirect_page "$(( page - 1 ))-${MAXPREVIEWS}-details"
+ _render_details_redirect "$html_dir" "$prevredirect-details.html" \
+ "$(( page - 1 ))-${MAXPREVIEWS}-details"
if (( page == max_page )); then
template redirect "0-$MAXPREVIEWS.html" \
html_dir "$html_dir" \
redirect_page "${page}-$lastview"
- template redirect "0-$MAXPREVIEWS-details.html" \
- html_dir "$html_dir" \
- redirect_page "${page}-$lastview-details"
+ _render_details_redirect "$html_dir" "0-$MAXPREVIEWS-details.html" \
+ "${page}-$lastview-details"
template redirect "$nextredirect.html" \
html_dir "$html_dir" \
redirect_page '1-1'
- template redirect "$nextredirect-details.html" \
- html_dir "$html_dir" \
- redirect_page '1-1-details'
+ _render_details_redirect "$html_dir" "$nextredirect-details.html" \
+ '1-1-details'
else
template redirect "$nextredirect.html" \
html_dir "$html_dir" \
redirect_page "$(( page + 1 ))-1"
- template redirect "$nextredirect-details.html" \
- html_dir "$html_dir" \
- redirect_page "$(( page + 1 ))-1-details"
+ _render_details_redirect "$html_dir" "$nextredirect-details.html" \
+ "$(( page + 1 ))-1-details"
fi
}
@@ -5460,7 +5544,9 @@ render_stats_page() {
# within that filter. The "--<index>" suffix cannot collide with another gallery
# name because a pagebase never contains "--". All pages reuse the album's shared
# photos/thumbs/blurs assets (only the HTML differs); view pages link "Details"
-# to the album's own details page via the album_view_page_for_photo accessor.
+# to the album's own details page via the album_view_page_for_photo accessor,
+# but only when DETAILS_PAGE=yes actually rendered that page (see
+# _stats_build_filterview_body below).
# Pages render in
# parallel through the shared job pool, throttled to IMAGE_JOBS. The galleries
# reuse camera.tmpl and the view pages reuse cameraview.tmpl.
@@ -5603,7 +5689,11 @@ _stats_build_filterview_body() {
tooltip_attr=" title=\"$(html_escape "$tooltip")\""
fi
view_page=$(album_view_page_for_photo "$photo")
- if [ -n "$view_page" ]; then
+ # Only link to the album's details page when it was actually rendered
+ # (DETAILS_PAGE=yes); otherwise album_view_page_for_photo resolving a page
+ # would still point at a details file that render_photo_view_and_details
+ # never wrote, leaving a dangling link.
+ if [ -n "$view_page" ] && [ "$DETAILS_PAGE" = yes ]; then
details_link=$(printf \
' <a href="%s/%s-details.html">Details</a> <span class="nav-sep">|</span>' \
"$backhref_html" "$view_page")
@@ -5772,6 +5862,7 @@ declare -gra CONFIG_SPECS=(
'RANDOM_SEED||yes|yes||scalar'
'SHUFFLE|no|yes|yes|yesno|scalar'
'SPLASH_PAGE|yes|yes|yes|yesno|scalar'
+ 'DETAILS_PAGE|yes|yes|yes|yesno|scalar'
'STATS_PAGE|no|yes|yes|yesno|scalar'
'TARBALL_INCLUDE|yes|yes|yes|yesno|scalar'
'TARBALL_SUFFIX|.tar|yes|no||scalar'
@@ -6517,8 +6608,20 @@ validate_template_file() {
validate_template_dir() {
local template_name
- local -a required_templates=(
- details
+ local -a required_templates=()
+
+ validate_template_dir_access || return
+
+ # details.tmpl is only needed when DETAILS_PAGE=yes actually renders it
+ # (mirrors the SPLASH_PAGE=yes conditional below), so an album that opts out
+ # of details pages does not need to keep a details.tmpl around at all. Kept
+ # first in the list (as it always was) so the reporting order for the
+ # unconditional templates below is unchanged from before DETAILS_PAGE
+ # existed.
+ if [ "$DETAILS_PAGE" = yes ]; then
+ required_templates+=(details)
+ fi
+ required_templates+=(
footer
header
next
@@ -6529,8 +6632,6 @@ validate_template_dir() {
view
)
- validate_template_dir_access || return
-
if [ "$SPLASH_PAGE" = yes ]; then
required_templates+=(splash)
fi
@@ -6694,6 +6795,7 @@ validate_common_config() {
SYNC_TIMEOUT
SHUFFLE
SPLASH_PAGE
+ DETAILS_PAGE
STATS_PAGE
TARBALL_INCLUDE
FAVICON
@@ -7115,6 +7217,7 @@ log_configured_action() {
log_verbose "Effective ImageMagick timeout: ${IMAGEMAGICK_TIMEOUT}s"
log_verbose "Effective tar timeout: ${TAR_TIMEOUT}s"
log_verbose "Effective splash page setting: $SPLASH_PAGE"
+ log_verbose "Effective details page setting: $DETAILS_PAGE"
log_verbose "Effective stats page setting: $STATS_PAGE"
log_verbose "Effective tarball setting: $TARBALL_INCLUDE"
log_verbose "Effective sync delete setting: $SYNC_DELETE"
diff --git a/docs/configuration.md b/docs/configuration.md
index 69a8835..e07227a 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -20,6 +20,7 @@ values for the current run.
| `TAR_TIMEOUT` | `120` | Tarball creation timeout in seconds. Positive integer. |
| `SHUFFLE` | `no` | Randomly shuffle all previews. `yes`/`no`. |
| `SPLASH_PAGE` | `yes` | Generate a splash landing page at `index.html`. `yes`/`no`. |
+| `DETAILS_PAGE` | `yes` | Generate each photo's `*-details.html` page (and its "Details" link). `yes`/`no`. See "Details pages" below. |
| `STATS_PAGE` | `no` | Generate the EXIF stats site under `stats/`. `yes`/`no`. |
| `RANDOM_SEED` | _(unset)_ | Any non-empty value makes splash/background picks, animation classes, timestamps, and shuffle order repeatable. |
| `INCOMING_DIR` | `$(pwd)/incoming` | Directory containing source photos (full path). |
@@ -38,6 +39,32 @@ values for the current run.
> `yes`, so a freshly `--init`'d config enables the tarball. The runtime default
> applied when a config file leaves it unset is `no`.
+## Details pages
+
+`DETAILS_PAGE` (default `yes`) controls whether each photo gets its own
+`*-details.html` page -- a dedicated view showing the full EXIF summary table,
+reachable via the "Details" link on that photo's normal view page and, when
+`STATS_PAGE=yes`, from the matching filter mini-album's view pages too.
+
+Setting `DETAILS_PAGE=no` (or passing `--no-details`) skips generating these
+pages entirely and removes every "Details" link that would point at one, so no
+generated page ever links to a missing file. Everything else keeps working
+unchanged:
+
+* The normal thumbnail overview pages and per-photo view pages are still
+ generated.
+* The per-photo EXIF tooltip (the `title=""` attribute shown on hover) is
+ unaffected -- it is controlled independently of the details page.
+* `STATS_PAGE` is unaffected: the EXIF stats site and its filter mini-albums
+ still generate normally with `DETAILS_PAGE=no`; only their "Details" links
+ (which would otherwise point at the main album's per-photo details page) are
+ omitted.
+
+A later `--generate` run that switches `DETAILS_PAGE` from `yes` back to `no`
+does not leave stale `*-details.html` files behind: generation stages the new
+output in a fresh directory and atomically replaces `DIST_DIR`, so files an
+older generation wrote but the current run does not produce are naturally gone.
+
## Supported source images
Only regular files found directly in `INCOMING_DIR` (not in subdirectories) with
@@ -57,11 +84,11 @@ The checks (details in `src/lib/config.validate.source.sh`):
`IMAGEMAGICK_TIMEOUT`, `TAR_TIMEOUT`; `HEIGHT` is an optional positive integer.
* **Percentage (0-100 integer)**: `THUMB_SUBDIVIDE_PERCENT`,
`THUMB_FEATURE_PERCENT`.
-* **`yes`/`no` settings**: `SHUFFLE`, `SPLASH_PAGE`, `STATS_PAGE`,
- `TARBALL_INCLUDE`, `SYNC_DELETE` (where applicable).
+* **`yes`/`no` settings**: `SHUFFLE`, `SPLASH_PAGE`, `DETAILS_PAGE`,
+ `STATS_PAGE`, `TARBALL_INCLUDE`, `SYNC_DELETE` (where applicable).
* **Readable input**: `INCOMING_DIR` must be a readable directory; `TEMPLATE_DIR`
must be a readable directory containing the required templates (plus `splash`
- when `SPLASH_PAGE=yes`).
+ when `SPLASH_PAGE=yes`, and `details` when `DETAILS_PAGE=yes`).
* **Writable output**: `DIST_DIR` (or its nearest existing parent) must be
writable.
* **ImageMagick** availability (`magick` or `convert`).
@@ -80,9 +107,9 @@ Generation stops before writing album output when validation fails.
`CONFIG_SOURCE`, `INCOMING_DIR`, `DIST_DIR`, `TEMPLATE_DIR`, `FAVICON`,
`SOURCE_URL`, `TITLE`, `HEIGHT`, `THUMBHEIGHT`, `MAXPREVIEWS`,
`THUMB_SUBDIVIDE_PERCENT`, `THUMB_FEATURE_PERCENT`, `IMAGE_JOBS`,
-`IMAGEMAGICK_TIMEOUT`, `RANDOM_SEED`, `SHUFFLE`, `SPLASH_PAGE`, `STATS_PAGE`,
-`TARBALL_INCLUDE`, `TARBALL_SUFFIX`, `TAR_TIMEOUT`, `TAR_OPTS`, `SYNC_DELETE`,
-`SYNC_DESTINATIONS`, `ORIGINAL_BASEPATH`.
+`IMAGEMAGICK_TIMEOUT`, `RANDOM_SEED`, `SHUFFLE`, `SPLASH_PAGE`, `DETAILS_PAGE`,
+`STATS_PAGE`, `TARBALL_INCLUDE`, `TARBALL_SUFFIX`, `TAR_TIMEOUT`, `TAR_OPTS`,
+`SYNC_DELETE`, `SYNC_DESTINATIONS`, `ORIGINAL_BASEPATH`.
Scalar values use Bash `%q` quoting; `TAR_OPTS` and `SYNC_DESTINATIONS` are
normalized to Bash array assignments, so the output can be parsed by shell
diff --git a/docs/generation.md b/docs/generation.md
index 4d29840..126f2bd 100644
--- a/docs/generation.md
+++ b/docs/generation.md
@@ -45,6 +45,20 @@ the site favicon) using the existing `photos` and `blurs` output, so it avoids
reprocessing images and rerendering album pages. It requires `SPLASH_PAGE=yes`;
pass `--random-seed VALUE` when you need a repeatable pick.
+## Details pages
+
+Every photo gets a `*-details.html` page by default: a dedicated view with the
+full EXIF summary table, reachable via the "Details" link on that photo's
+normal view page. Set `DETAILS_PAGE=no` or pass `--no-details` to skip
+generating these pages; the "Details" link (on the normal view pages and, when
+`STATS_PAGE=yes`, on the filter mini-album view pages) is removed along with
+it, so no generated page links to a missing file.
+
+`DETAILS_PAGE=no` only affects the details pages and their links. It does not
+disable the per-photo EXIF tooltip (the `title=""` attribute shown on hover
+over a photo) or `STATS_PAGE`: both stay independently controlled and keep
+working exactly as configured.
+
## Stats site
`shuriken` can also generate a no-JavaScript stats site with EXIF-derived
@@ -97,8 +111,9 @@ metadata records:
* generated photo, thumbnail, and HTML file counts;
* tarball status (included + file);
* effective settings (title, height, thumbheight, maxpreviews, subdivide
- percent, feature percent, image jobs, random seed, shuffle, splash page, stats
- page, original basepath) useful for debugging a published album.
+ percent, feature percent, image jobs, random seed, shuffle, splash page,
+ details page, stats page, original basepath) useful for debugging a published
+ album.
## Favicon
diff --git a/docs/usage.md b/docs/usage.md
index 3f85471..9436e36 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -76,6 +76,8 @@ config variable documented in [configuration.md](configuration.md).
| `--no-shuffle` | `SHUFFLE=no` |
| `--splash` | `SPLASH_PAGE=yes` |
| `--no-splash` | `SPLASH_PAGE=no` |
+| `--details` | `DETAILS_PAGE=yes` |
+| `--no-details` | `DETAILS_PAGE=no` |
| `--stats` | `STATS_PAGE=yes` |
| `--no-stats` | `STATS_PAGE=no` |
| `--tarball` | `TARBALL_INCLUDE=yes` |
diff --git a/share/templates/default/view.tmpl b/share/templates/default/view.tmpl
index c28859b..afabe29 100644
--- a/share/templates/default/view.tmpl
+++ b/share/templates/default/view.tmpl
@@ -1,5 +1,7 @@
# A non-empty EXIF summary becomes a title="" attribute so hovering the image
-# shows the camera/exposure details, mirroring the details view.
+# shows the camera/exposure details, mirroring the details view. This tooltip is
+# independent of the Details link below: it stays available even when
+# DETAILS_PAGE=no has suppressed the details page itself.
if [ -n "$render_exif_tooltip_html" ]; then
render_exif_tooltip_attr=" title=\"$render_exif_tooltip_html\""
else
@@ -14,7 +16,20 @@ cat <<END
<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> <span class="nav-sep">|</span>
+END
+
+# The Details link is only emitted when DETAILS_PAGE=yes actually rendered the
+# *-details.html page it points to; otherwise it would be a dangling link.
+# Keeping this as its own conditional cat block (rather than inlining the link
+# into a shell variable) keeps the DETAILS_PAGE=yes output byte-for-byte
+# identical to before this toggle existed.
+if [ "${render_details_page_html}" = 'yes' ]; then
+ cat <<END
<a href="${render_page_num_html}-${render_preview_num_html}-details.html">Details</a> <span class="nav-sep">|</span>
+END
+fi
+
+cat <<END
<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>
diff --git a/src/lib/action.source.sh b/src/lib/action.source.sh
index b0460f0..cbc928e 100644
--- a/src/lib/action.source.sh
+++ b/src/lib/action.source.sh
@@ -177,6 +177,7 @@ log_configured_action() {
log_verbose "Effective ImageMagick timeout: ${IMAGEMAGICK_TIMEOUT}s"
log_verbose "Effective tar timeout: ${TAR_TIMEOUT}s"
log_verbose "Effective splash page setting: $SPLASH_PAGE"
+ log_verbose "Effective details page setting: $DETAILS_PAGE"
log_verbose "Effective stats page setting: $STATS_PAGE"
log_verbose "Effective tarball setting: $TARBALL_INCLUDE"
log_verbose "Effective sync delete setting: $SYNC_DELETE"
diff --git a/src/lib/album-render.source.sh b/src/lib/album-render.source.sh
index 7cfff0c..a6012bb 100644
--- a/src/lib/album-render.source.sh
+++ b/src/lib/album-render.source.sh
@@ -223,6 +223,10 @@ render_view_page() {
tarball_name "$tarball_name"
}
+# Render one photo's *-details.html page. Only called by
+# render_photo_view_and_details when DETAILS_PAGE=yes; callers must not invoke
+# this directly for a photo when details are disabled, or a dangling file would
+# be produced with no page linking to it.
render_details_page() {
local -r html_dir="$1"; shift
local -r photos_dir="$1"; shift
@@ -285,15 +289,22 @@ render_photo_view_and_details() {
"$page_num" \
"$preview_num" \
"$photo"
- render_details_page \
- "$html_dir" \
- "$photos_dir" \
- "$blurs_dir" \
- "$backhref" \
- "$tarball_name" \
- "$page_num" \
- "$preview_num" \
- "$photo"
+
+ # DETAILS_PAGE=no skips the *-details.html file entirely (view.tmpl's Details
+ # link is likewise suppressed via render_details_page_html, so nothing links
+ # to it). This is the only place a photo's details page would be rendered, so
+ # skipping the call here is sufficient to omit the file for every photo.
+ if [ "$DETAILS_PAGE" = yes ]; then
+ render_details_page \
+ "$html_dir" \
+ "$photos_dir" \
+ "$blurs_dir" \
+ "$backhref" \
+ "$tarball_name" \
+ "$page_num" \
+ "$preview_num" \
+ "$photo"
+ fi
}
record_rendered_view_page() {
@@ -310,32 +321,78 @@ record_rendered_view_page() {
last_views_ref["$page"]="$preview"
}
-# Navigation-redirect count single source of truth (task nr0). Every view page
-# gets ALBUM_REDIRECTS_PER_PAGE wrap-around redirect files: the prev stub
-# (N-0.html) and its details twin, plus the next stub (N-(last+1).html) and its
-# details twin -- four files, emitted by render_page_view_redirects below for
-# every page. The LAST page additionally emits ALBUM_REDIRECTS_LAST_PAGE_EXTRA
-# files: the 0-MAXPREVIEWS / 0-MAXPREVIEWS-details entry stubs that bounce into
-# the album. Keep these two numbers in lockstep with render_page_view_redirects;
-# the dry-run plan predicts redirect_count from them via
-# album_redirect_count_for_page_count instead of a magic "*4+2".
-declare -gri ALBUM_REDIRECTS_PER_PAGE=4
-declare -gri ALBUM_REDIRECTS_LAST_PAGE_EXTRA=2
+# Navigation-redirect count single source of truth (task nr0; extended for
+# DETAILS_PAGE by task 6v0). Every view page gets ALBUM_VIEW_REDIRECTS_PER_PAGE
+# wrap-around redirect files: the prev stub (N-0.html) and the next stub
+# (N-(last+1).html). When DETAILS_PAGE=yes, each of those also gets a
+# "-details" twin, adding ALBUM_DETAILS_REDIRECTS_PER_PAGE more -- so
+# render_page_view_redirects emits 2 files per page with details disabled, 4
+# with them enabled. The LAST page additionally emits
+# ALBUM_VIEW_REDIRECTS_LAST_PAGE_EXTRA entry stubs (plus their details twins
+# when enabled): the 0-MAXPREVIEWS / loop-to-1 stubs that bounce into the
+# album. Keep these numbers in lockstep with render_page_view_redirects; the
+# dry-run plan predicts redirect_count from them via
+# album_redirect_count_for_page_count instead of a magic formula.
+declare -gri ALBUM_VIEW_REDIRECTS_PER_PAGE=2
+declare -gri ALBUM_DETAILS_REDIRECTS_PER_PAGE=2
+declare -gri ALBUM_VIEW_REDIRECTS_LAST_PAGE_EXTRA=1
+declare -gri ALBUM_DETAILS_REDIRECTS_LAST_PAGE_EXTRA=1
+
+# Per-page / last-page-extra redirect file counts for the CURRENT DETAILS_PAGE
+# setting: the view-only counts, plus the details counts when DETAILS_PAGE=yes.
+# Split out of album_redirect_count_for_page_count so that function stays a
+# short arithmetic one-liner.
+album_redirects_per_page() {
+ local -i count=$ALBUM_VIEW_REDIRECTS_PER_PAGE
+
+ if [ "$DETAILS_PAGE" = yes ]; then
+ count+=$ALBUM_DETAILS_REDIRECTS_PER_PAGE
+ fi
+ printf '%d\n' "$count"
+}
+
+album_redirects_last_page_extra() {
+ local -i count=$ALBUM_VIEW_REDIRECTS_LAST_PAGE_EXTRA
+
+ if [ "$DETAILS_PAGE" = yes ]; then
+ count+=$ALBUM_DETAILS_REDIRECTS_LAST_PAGE_EXTRA
+ fi
+ printf '%d\n' "$count"
+}
# Total navigation redirects a run produces for a given number of preview pages:
-# four per page plus the last page's extra entry stubs. Zero pages -> zero
-# redirects (render_view_redirects returns early on an empty album). This is the
-# count render_page_view_redirects actually writes across all pages, expressed
-# once so the dry-run plan cannot drift from real generation.
+# album_redirects_per_page per page plus the last page's extra entry stubs.
+# Zero pages -> zero redirects (render_view_redirects returns early on an empty
+# album). This is the count render_page_view_redirects actually writes across
+# all pages, expressed once so the dry-run plan cannot drift from real
+# generation.
album_redirect_count_for_page_count() {
local -ri page_count="$1"; shift
+ local -i per_page
+ local -i last_page_extra
if (( page_count <= 0 )); then
printf '0\n'
return
fi
- printf '%d\n' "$(( page_count * ALBUM_REDIRECTS_PER_PAGE \
- + ALBUM_REDIRECTS_LAST_PAGE_EXTRA ))"
+ per_page=$(album_redirects_per_page)
+ last_page_extra=$(album_redirects_last_page_extra)
+ printf '%d\n' "$(( page_count * per_page + last_page_extra ))"
+}
+
+# Render one details-redirect stub, but only when DETAILS_PAGE=yes (mirrors
+# render_photo_view_and_details skipping render_details_page entirely). A no-op
+# under DETAILS_PAGE=no keeps every "-details.html" navigation stub from ever
+# being written, so no generated page can link to a missing one.
+_render_details_redirect() {
+ local -r html_dir="$1"; shift
+ local -r filename="$1"; shift
+ local -r target="$1"; shift
+
+ if [ "$DETAILS_PAGE" != yes ]; then
+ return
+ fi
+ template redirect "$filename" html_dir "$html_dir" redirect_page "$target"
}
# Render every navigation redirect for a single view page (the prev/next
@@ -345,7 +402,7 @@ album_redirect_count_for_page_count() {
# files for distinct pages are produced here. The wrap-around redirects for the
# very last page (0-MAXPREVIEWS and the loop-to-1 links) are emitted as part of
# that page's group. Per-page / last-page file counts are fixed by
-# ALBUM_REDIRECTS_PER_PAGE / ALBUM_REDIRECTS_LAST_PAGE_EXTRA above.
+# album_redirects_per_page / album_redirects_last_page_extra above.
render_page_view_redirects() {
local -r html_dir="$1"; shift
local -ri page="$1"; shift