]> git.plutz.net Git - shellwiki/commitdiff
slideshow handler, using code from "clickslide"
authorPaul Hänsch <paul@plutz.net>
Wed, 23 Sep 2026 15:29:29 +0000 (17:29 +0200)
committerPaul Hänsch <paul@plutz.net>
Wed, 23 Sep 2026 15:29:29 +0000 (17:29 +0200)
handlers/70_slideshow.sh [new file with mode: 0755]
themes/slideshow.css [new file with mode: 0644]

diff --git a/handlers/70_slideshow.sh b/handlers/70_slideshow.sh
new file mode 100755 (executable)
index 0000000..066aa88
--- /dev/null
@@ -0,0 +1,148 @@
+#!/bin/sh
+
+# Copyright 2019 - 2026 Paul Hänsch
+# 
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+# 
+# THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+# IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+case $PATH_INFO in
+  */\[slideshow\]) : ;;
+  *) return 1 ;;
+esac
+
+printf "%s\r\n" "Content-Type: text/html; charset=utf-8" ""
+
+cat <<-EOF
+       <!DOCTYPE HTML>
+       <html${LANGUAGE:+ lang=\"${LANGUAGE}\"}><head>
+         <link rel="stylesheet" type="text/css" href="/%5B.%5D/themes/slideshow.css">
+         <title>$(HTML ${PAGE_TITLE})</title>
+       </head><body id="$(HTML "${PATH_INFO}")">
+          <a href="#" class="toplevel">overview</a>
+       EOF
+
+page="${PATH_INFO%\[*\]}"
+wiki "$page" | {
+  read -r line
+  prev='' next='' idoff=0
+  depth=0 ucdepth=-1
+
+  while :; do 
+    tag="${tag}${line%%>*}"
+  
+    if [ "$line" = "${line%%>*}" ]; then
+      # $line did not contain ">" and thus was added to $tag entirely
+      if ! read -r line; then
+        printf %s\\n "$tag"
+        break
+      fi
+      tag="${tag}${BR}"
+    else
+      # $line is shortened by segment added to $tag
+      line="${line#*>}"
+      tag="${tag}>"
+    fi
+
+    case $tag in
+      *\<section\ class=\"h1\"\ *id=\"?*\"*\>*)  # Count slide id, insert "next" button
+        prev="$next"
+        next="${tag#*<section }" next="${next#*id=\"}" next="${next%\"*}"
+        next="autoslide${idoff}"
+        idoff="$((idoff + 1))"
+        printf '%s<a class="nextslide" href="#%s">next</a><div class="slide" count="%i" id="%s" %s' \
+          "${tag%<section *}" "$next" "$idoff" "$next" "${tag#*<section }"
+        tag='' depth=$((depth + 1)) sldepth=$depth
+        ;;
+      *\<section\ class=\"h1\"\ *\>*)
+        prev="$next"
+        next="autoslide${idoff}"
+        idoff="$((idoff + 1))"
+        printf '%s<a class="nextslide" href="#%s">next</a><div class="slide" count="%i" id="%s" %s' \
+          "${tag%<section*}" "$next" "$idoff" "$next" "${tag#*<section}"
+        tag='' depth=$((depth + 1)) sldepth=$depth
+        ;;
+      *\<section\ class=\"h2\"\ *id=\"?*\"*\>*)  # Count slide id, insert "next" button
+        prev="$next"
+        next="${tag#*<section }" next="${next#*id=\"}" next="${next%\"*}"
+        next="autoslide${idoff}"
+        idoff="$((idoff + 1))"
+        if [ $depth -eq $sldepth ]; then
+          printf '%s</div><a class="prevslide" href="#%s">previous</a>' \
+            "${tag%<section*}" "$prev"
+        else
+          printf '%s' "${tag%<section*}"
+        fi
+        printf '<a class="nextslide" href="#%s">next</a><div class="slide" count="%i" id="%s" %s' \
+          "$next" "$idoff" "$next" "${tag#*<section }"
+        tag='' depth=$((depth + 1)) sldepth=$depth
+        ;;
+      *\<section\ class=\"h2\"\ *\>*|*\<section\>*)
+        prev="$next"
+        next="autoslide${idoff}"
+        idoff="$((idoff + 1))"
+        if [ $depth -eq $sldepth ]; then
+          printf '%s</div><a class="prevslide" href="#%s">previous</a>' \
+            "${tag%<section*}" "$prev"
+        else
+          printf '%s' "${tag%<section*}"
+        fi
+        printf '<a class="nextslide" href="#%s">next</a><div class="slide" count="%i" id="%s" %s' \
+          "$next" "$idoff" "$next" "${tag#*<section}"
+        tag='' depth=$((depth + 1)) sldepth=$depth
+        ;;
+      *\</section\>*)  # Insert "previous" button
+        if [ $depth -eq $sldepth ]; then
+          printf '%s</div><a class="prevslide" href="#%s">previous</a>%s' \
+            "${tag%</section>*}" "$prev" "${tag#*</section>}"
+        else
+          printf %s "${tag%</section>*}"
+        fi
+        tag='' depth=$((depth - 1))
+        ;;
+      # *\<body*\>*)  # Insert toplevel link
+      #   printf '%s<a href="#" class="toplevel">overview</a>' "$tag"
+      #   tag='' depth=$((depth + 1))
+      #   ;;
+      # *\</body*\>*)  # Insert total slide count
+      #   printf '<span class="count">%i</span>%s' "$idoff" "$tag"
+      #   tag='' depth=$((depth - 1))
+      #   ;;
+      *\<*class=\"uncover\"*\>*)  # Insert checkboxes in "uncover" lists
+        #printf '%s<li></li>' "$tag"
+        printf '%s' "$tag"
+        tag='' depth=$((depth + 1))
+        ucdepth=$((depth + 1))
+        ;;
+      *\</*\>*)
+        printf %s "$tag"
+        tag='' depth=$((depth - 1))
+        [ $depth -lt $ucdepth ] && ucdepth=-1
+        ;;
+      *\<*\>*)
+        if [ $ucdepth = $depth ]; then
+          printf '%s<input type="checkbox" class="uncover"/><%s' "${tag%<*}" "${tag#*<}"
+        else
+          printf %s "$tag"
+        fi
+        [ "${tag}" = "${tag%/>}" ] && depth=$((depth + 1))
+        tag=''
+        ;;
+      *) :
+        ;;
+    esac
+  done
+  printf '<span class="count">%i</span>' "$idoff"
+}
+
+printf '</body></html>'
+
+return 0
diff --git a/themes/slideshow.css b/themes/slideshow.css
new file mode 100644 (file)
index 0000000..2f5ca87
--- /dev/null
@@ -0,0 +1,312 @@
+/* === Colors === */
+
+footer,
+body a.toplevel:before,
+body span.count,
+div.slide:target:after,
+div.slide:target + a, div.slide:target + a + a,
+:target + a:before, :target + a + a:before
+{ color: #888; }
+
+body
+{ background-color: #AAA;
+  background-image: repeating-linear-gradient( to bottom right, #AAA, #999 8px);
+}
+
+div.slide
+{ background-color: #FFF;
+  background-image: repeating-linear-gradient( #FFF, #EEE 4px);
+}
+
+div.slide:target:after,
+footer:before, body:after
+{ background-color: #EEE;
+  background-image: repeating-linear-gradient(#EEE, #DDD 8px);
+}
+
+div.slide h1, div.slide h2
+{ background-color: #EEE;
+  background-image: repeating-linear-gradient(#EEE, #DDD 8px);
+}
+
+div.slide pre, div.slide code
+{ background-color: #DDD;
+  background-image: repeating-linear-gradient(#CCC, #DDD 4px);
+}
+
+div.slide:after  /* slide number on overview screen */
+{ color: #AAA; }
+
+/* === Overall Layout === */
+
+* {
+  box-sizing: border-box;
+  margin: 0; padding: 0;
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: cover;
+}
+
+/*  min-height: 240px */     body { font-size:  9px; }
+@media(min-height: 320px)  { body { font-size: 12px; } }
+@media(min-height: 480px)  { body { font-size: 19px; } }
+@media(min-height: 720px)  { body { font-size: 28px; } }
+@media(min-height: 768px)  { body { font-size: 30px; } }
+@media(min-height: 800px)  { body { font-size: 32px; } }
+@media(min-height: 960px)  { body { font-size: 38px; } }
+@media(min-height: 1024px) { body { font-size: 41px; } }
+@media(min-height: 1080px) { body { font-size: 43px; } }
+@media(min-height: 2160px) { body { font-size: 86px; } }
+
+body {
+  font-family: sans-serif;
+  padding: 2em 1em 6em 1em;
+  #background-color: #555;
+  text-align: center;
+  overflow-x: hidden;
+}
+body * {
+  text-align: left;
+  font-size: inherit;
+}
+
+body:after {
+  position: fixed;
+  bottom: 0; left: 0;
+  width: 100%;
+  height: 1.25em;
+  content: ' ';
+  #background: #EEE repeating-linear-gradient(#EEE, #DDD 8px);
+}
+
+a.previous, a.next ,a.toplevel {
+  text-decoration: none;
+}
+
+footer {
+  position: fixed;
+  bottom: 0; left: 0;
+  width: 100%;
+  font-size: .75em;
+  padding: .5em 4em .5em 12em;
+  text-align: center;
+  #color: #888;
+  z-index: 4;
+  /* footer must not have background, so slide number is not covered */
+}
+footer:before {  /* fake background in case footer spans multiple lines */
+  content: ' ';
+  position: absolute;
+  top: 0; left: 0;
+  bottom: 1.5em;
+  width: 100%;
+  #background: #EEE repeating-linear-gradient(#EEE, #DDD 8px);
+  z-index: -1;
+}
+
+body a.toplevel {
+  position: fixed;
+  bottom: 0; left: 8em;
+  width: 2ex; overflow: hidden;
+  font-size: .875em;
+  padding-bottom: .25em;
+  color: transparent;
+  z-index: 5;
+}
+body a.toplevel:before {
+  #color: #888;
+  content: '\2196';
+}
+
+body span.count {
+  position: fixed;
+  bottom: 0; left: 6.25em;
+  font-size: .875em;
+  padding-bottom: .125em;
+  #color: #888;
+  z-index: 5;
+}
+
+/* ==== Overview Screen ==== */
+
+div.slide, a.nextslide {
+  font-size: .15625em;
+}
+
+div.slide {
+  position: relative;
+  display: inline-block;
+  overflow: hidden;
+  width: 48em; height: 30em;
+  margin-left: -48em;
+  border: 1px solid black;
+}
+
+div.slide:after {  /* slide number */
+  position: absolute;
+  bottom: 0; left: 0;
+  font-size: 15em;
+  line-height: 1em;
+  text-align: left;
+  content: attr(count);
+  padding-left: .125em;
+  #color: #DDD;
+}
+
+div.slide + a { display: none; }
+a.nextslide {
+  display: inline-block;
+  position: relative;
+  vertical-align: top;
+  width: 48em; line-height: 30em;
+  color: transparent;
+  z-index: 2;
+}
+#div.slide:nth-of-type(1) { margin-right: .5em; }
+
+/* ==== Slide Appearance ==== */
+
+div.slide {
+  #background: #FFF no-repeat center;
+  text-align: center;
+  line-height: 1.5em;
+}
+
+div.slide > * {
+  margin-top: 1em;
+  max-width: 85%;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+div.slide > *:first-child:last-child,
+div.slide > h2:first-child + :last-child {
+  margin-top: 20%;
+  transform: translate(0, -50%);
+}
+
+div.slide h1, div.slide h2 {
+  display: block;
+  text-align: center;
+  font-size: 1.5em;
+  margin-top: 0;
+  margin-bottom: 1em;
+  width: 100%; max-width: 100%;
+  #background: #EEE repeating-linear-gradient(#EEE, #DDD 8px);
+}
+
+div.slide h1,
+div.slide > h1:first-child:last-child {
+  margin-top: 25%;
+  margin-bottom: 0;
+  padding: .5em 2em;
+}
+
+div.slide h2 {
+  padding: .25em 1em;
+}
+
+div.slide > table {
+  width: 85%;
+}
+div.slide * table {
+  width: 100%;
+}
+
+div.slide ul li {
+  list-style-type: square;
+  margin-left: 2em;
+}
+
+div.slide ul ul, div.slide ul ol,
+div.slide ol ul, div.slide ol ol {
+  margin-left: -1em;
+}
+
+div.slide > ul,
+div.slide > ol {
+  width: fit-content;
+  margin: .25em auto;
+}
+
+div.slide li pre {
+  margin-bottom: .25em;
+}
+
+div.slide pre, div.slide code {
+  #background-color: rgba(224,224,224,128);
+  font-family: monospace;
+}
+div.slide pre {
+  padding: 1ex;
+  text-align: left;
+  overflow: hidden;
+}
+div.slide code {
+  padding: 0 .5em;
+}
+
+/* ==== Slide Display ==== */
+
+div.slide:target {
+  position: fixed;
+  z-index: 3;
+  top: 0; bottom: 0; left: 0; right: 0;
+  width: auto; height: 100%;
+  margin: 0;
+  border: none;
+  font-size: inherit;
+}
+
+/* slide number, footer bar*/
+div.slide:target:after {
+  position: absolute;
+  bottom: 0; left: 0;
+  width: 100%;
+  font-size: .875em;
+  line-height: 1.5em;
+  text-align: left;
+  content: attr(count) " /";
+  padding-left: 4em;
+  #color: #888;
+  #background: #EEE repeating-linear-gradient(#EEE, #DDD 8px);
+}
+
+/* back and forth link buttons */
+div.slide:target + a,
+div.slide:target + a + a {
+  position: static;
+  display: block;
+  font-size: inherit;
+  line-height: 1em;
+  #color: #888;
+}
+:target + a:before, :target + a + a:before {
+  display: block;
+  position: fixed;
+  bottom: 0;
+  #color: #888;
+  padding: 0 1em .25em 1em;
+  z-index: 4;
+}
+:target + a:before { left:  0; }
+:target + a:before { content: '\22d8'; }
+:target + a + a:before { right: 0; }
+:target + a + a:before { content: '\22d9'; }
+
+.uncover[type="checkbox"] + * { opacity: .2; }
+.uncover[type="checkbox"]:checked + * { opacity: 1; }
+
+.uncover[type="checkbox"] {
+  position: fixed;
+  top: 0; bottom: 0; left: 0; right: 0;
+  width: 100%; height: 100%;
+  opacity: 0;
+  z-index: 1;
+}
+
+.uncover[type="checkbox"] + * + .uncover[type="checkbox"] { display: none; }
+.uncover[type="checkbox"]:checked + * + .uncover[type="checkbox"] { display: inline; }
+.uncover[type="checkbox"]:checked + * + .uncover[type="checkbox"]:checked,
+.uncover[type="checkbox"]:checked { display: none; }
+