Adds a slide after the hardware diagram showing the two racks side by side — the MakerFLOSS rack at Orange Makerspace and the one at home — to make the point that the module contract is what lets those two rooms run the same system. build-slides.sh now copies image files from slides/ into the output tree at the same relative path. Marp keeps relative image URLs, so without this the images would 404 on the published site. Guarded against the local build, where OUTPUT_DIR is the source directory and the copy would be a no-op onto itself. This is the first deck in the repo with images; the convention is documented in docs/services/slides.md. Photos resampled to 1200px on the long edge (~300 KB each) from the 3072x4080 originals. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
184 lines
6.8 KiB
Bash
Executable file
184 lines
6.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Build every Marp deck in the repository into static HTML.
|
|
#
|
|
# A deck is any *.md carrying `marp: true` in its front matter. Two source
|
|
# roots, two output conventions:
|
|
#
|
|
# docs/presentations/<name>.md -> <name>.html (flat, historical)
|
|
# slides/<path>/<name>.md -> <path>/<name>.html (path mirrors URL)
|
|
#
|
|
# So slides/tappaas/how-to/new-module/index.md is served at
|
|
# https://slides.makerfloss.eu/tappaas/how-to/new-module/ .
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
OUTPUT_DIR="${OUTPUT_DIR:-$REPO_ROOT/slides}"
|
|
SLIDES_TITLE="${SLIDES_TITLE:-MakerFLOSS Slides}"
|
|
REPO_WEB_URL="${REPO_WEB_URL:-https://forgejo.makerfloss.eu/sjat/MakerFLOSS}"
|
|
|
|
mkdir -p "$OUTPUT_DIR"
|
|
|
|
# Collect decks as "<repo-relative source>|<output-relative target>" pairs.
|
|
DECKS=()
|
|
while IFS= read -r f; do
|
|
rel="${f#"$REPO_ROOT"/}"
|
|
DECKS+=("$rel|$(basename "${rel%.md}").html")
|
|
done < <(grep -rl "^marp: true" "$REPO_ROOT/docs" --include="*.md" 2>/dev/null | sort || true)
|
|
|
|
if [ -d "$REPO_ROOT/slides" ]; then
|
|
while IFS= read -r f; do
|
|
rel="${f#"$REPO_ROOT"/}"
|
|
out="${rel#slides/}"
|
|
DECKS+=("$rel|${out%.md}.html")
|
|
done < <(grep -rl "^marp: true" "$REPO_ROOT/slides" --include="*.md" 2>/dev/null | sort || true)
|
|
fi
|
|
|
|
if [ ${#DECKS[@]} -eq 0 ]; then
|
|
echo "No marp presentations found."
|
|
exit 0
|
|
fi
|
|
|
|
echo "Found ${#DECKS[@]} presentation(s):"
|
|
printf ' %s\n' "${DECKS[@]%%|*}"
|
|
|
|
# Wipe previously built HTML so deleted/de-tagged decks disappear. Only *.html
|
|
# is removed, so deck sources living inside slides/ are never touched.
|
|
find "$OUTPUT_DIR" -name "*.html" -type f -delete
|
|
|
|
render() { # render <source-rel> <target-rel>
|
|
local src="$1" dst="$2"
|
|
mkdir -p "$OUTPUT_DIR/$(dirname "$dst")"
|
|
if command -v marp &>/dev/null; then
|
|
marp --html --output "$OUTPUT_DIR/$dst" "$REPO_ROOT/$src"
|
|
elif command -v npx &>/dev/null; then
|
|
npx --yes @marp-team/marp-cli --html --output "$OUTPUT_DIR/$dst" "$REPO_ROOT/$src"
|
|
else
|
|
# marpteam/marp-cli drops privileges to its own "marp" user, so the mounted
|
|
# output tree has to be writable by anyone — and --user must NOT be passed.
|
|
chmod -R a+rwX "$OUTPUT_DIR"
|
|
docker run --rm \
|
|
-v "$REPO_ROOT":/home/marp/app:ro \
|
|
-v "$OUTPUT_DIR":/home/marp/out \
|
|
marpteam/marp-cli --html --output "/home/marp/out/$dst" "$src"
|
|
fi
|
|
}
|
|
|
|
if ! command -v marp &>/dev/null && ! command -v npx &>/dev/null; then
|
|
if command -v docker &>/dev/null; then
|
|
echo "marp not found locally — using Docker (marpteam/marp-cli)..."
|
|
else
|
|
echo "error: need one of marp, npx or docker on PATH" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
for deck in "${DECKS[@]}"; do
|
|
render "${deck%%|*}" "${deck#*|}"
|
|
done
|
|
|
|
# Post-process each built page: highlight unfinished `TODO ...` markers, and
|
|
# turn mermaid code blocks into live diagrams.
|
|
#
|
|
# Marp emits fenced mermaid blocks as <pre><code class="language-mermaid">.
|
|
# The script replaces those elements with <div class="mermaid">, then loads and
|
|
# runs mermaid.js from CDN.
|
|
postprocess() {
|
|
local html_file="$1"
|
|
python3 - "$html_file" << 'PYEOF'
|
|
import re
|
|
import sys
|
|
|
|
path = sys.argv[1]
|
|
snippet = """\
|
|
<script type="module">
|
|
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
|
document.querySelectorAll('code.language-mermaid').forEach(el => {
|
|
const div = document.createElement('div');
|
|
div.className = 'mermaid';
|
|
div.textContent = el.textContent;
|
|
el.parentElement.replaceWith(div);
|
|
});
|
|
mermaid.initialize({ startOnLoad: false, theme: 'dark' });
|
|
await mermaid.run();
|
|
</script>"""
|
|
|
|
with open(path, encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Inline <code>TODO…</code> -> <code class="todo">…</code>; decks style that class.
|
|
content, todos = re.subn(r'<code>(TODO\b[^<]*)</code>', r'<code class="todo">\1</code>', content)
|
|
|
|
if 'class="language-mermaid"' in content:
|
|
if '</body>' not in content:
|
|
print(f"Warning: </body> not found in {path}", file=sys.stderr)
|
|
sys.exit(1)
|
|
content = content.replace('</body>', snippet + '\n</body>', 1)
|
|
print(f" Injected mermaid.js into {path}")
|
|
|
|
with open(path, 'w', encoding='utf-8') as f:
|
|
f.write(content)
|
|
|
|
if todos:
|
|
print(f" {todos} unfinished TODO marker(s) in {path}")
|
|
PYEOF
|
|
}
|
|
|
|
if command -v python3 &>/dev/null; then
|
|
# The landing index.html is generated further down, so every *.html present
|
|
# right now is a built deck.
|
|
while IFS= read -r html_file; do
|
|
postprocess "$html_file"
|
|
done < <(find "$OUTPUT_DIR" -name "*.html" -type f)
|
|
else
|
|
echo "Warning: python3 not found — skipping post-processing"
|
|
fi
|
|
|
|
# Copy deck assets (images) that live next to a source under slides/. Marp keeps
|
|
# relative image URLs in the HTML, so the files must exist at the same relative
|
|
# path in the output tree. Only slides/ can carry these: docs/presentations/
|
|
# decks are flattened to <name>.html, so a relative path would not survive.
|
|
# When OUTPUT_DIR is the in-repo slides/ dir, source and target are the same
|
|
# file and there is nothing to copy.
|
|
if [ -d "$REPO_ROOT/slides" ]; then
|
|
while IFS= read -r -d '' asset; do
|
|
rel="${asset#"$REPO_ROOT"/slides/}"
|
|
dest="$OUTPUT_DIR/$rel"
|
|
[ "$asset" -ef "$dest" ] && continue
|
|
mkdir -p "$(dirname "$dest")"
|
|
cp "$asset" "$dest"
|
|
echo " Copied asset $rel"
|
|
done < <(find "$REPO_ROOT/slides" -type f \
|
|
\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" \
|
|
-o -iname "*.gif" -o -iname "*.svg" -o -iname "*.webp" \) -print0)
|
|
fi
|
|
|
|
# Regenerate index.html listing every built deck.
|
|
INDEX="$OUTPUT_DIR/index.html"
|
|
{
|
|
printf '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">'
|
|
printf '<meta name="viewport" content="width=device-width,initial-scale=1">'
|
|
printf '<title>%s</title>' "$SLIDES_TITLE"
|
|
printf '<style>'
|
|
printf 'body{font-family:sans-serif;max-width:700px;margin:3rem auto;padding:0 1.5rem;background:#fafafa;}'
|
|
printf 'h1{font-size:1.6rem;margin-bottom:.25rem;}p.sub{color:#666;margin-top:0;}'
|
|
printf 'ul{list-style:none;padding:0;margin-top:2rem;}'
|
|
printf 'li{border:1px solid #e0e0e0;border-radius:6px;margin:.75rem 0;background:#fff;}'
|
|
printf 'li a{display:block;padding:1rem 1.25rem;text-decoration:none;color:#1a1a1a;font-size:1.05rem;}'
|
|
printf 'li a:hover{background:#f0f4ff;border-radius:6px;}'
|
|
printf 'footer{margin-top:3rem;color:#aaa;font-size:.8rem;}'
|
|
printf '</style></head><body>'
|
|
printf '<h1>%s</h1>' "$SLIDES_TITLE"
|
|
printf '<p class="sub">Built from <a href="%s">%s</a></p>' \
|
|
"$REPO_WEB_URL" "${REPO_WEB_URL#https://}"
|
|
printf '<ul>'
|
|
for deck in "${DECKS[@]}"; do
|
|
href="${deck#*|}"
|
|
label="${href%/index.html}"; label="${label%.html}"
|
|
printf '<li><a href="%s">%s</a></li>' "$href" "$label"
|
|
done
|
|
printf '</ul>'
|
|
printf '<footer>Last built: %s · <a href="https://marp.app">Marp</a></footer>' "$(date -Iseconds)"
|
|
printf '</body></html>'
|
|
} > "$INDEX"
|
|
|
|
echo "Done — slides in $OUTPUT_DIR/"
|