seppedlandClaude Opus 4.7 1b2a36f805 Stop tracking .wolf/ — local OpenWolf state only
Replace the three partial .wolf patterns with a single .wolf/ rule and
remove the directory from the index. Working-tree files stay in place.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 16:11:50 +02:00

netgazet

Tiny static-site generator for print-format PDFs (newsletters, magazines). Drop PDFs into examples/, run main.py, and get a browseable, mobile-friendly HTML/CSS archive in output/.

What it does

For every *.pdf in examples/ it renders each page as a JPEG with poppler-utils (pdftocairo) and writes:

  • output/<pdf-stem>/page-NN.html — one wrapper per page with prev/next navigation and a collapsed text-extract panel (from pdftotext -layout).
  • output/<pdf-stem>/index.html — thumbnail grid for the issue.
  • output/index.html — root listing of all issues.
  • output/styles.css — single responsive stylesheet, no JavaScript.

Layout fidelity (faithful to the print PDF) over text reflow; mobile responsiveness is via img { max-width: 100% } and CSS grid auto-fill.

Requirements

  • Python ≥ 3.14 (see .python-version)

  • poppler-utils on $PATH (pdftocairo, pdftotext, pdfinfo):

    sudo apt install poppler-utils      # Debian / Ubuntu
    brew install poppler                # macOS
    

No third-party Python dependencies — pyproject.toml keeps dependencies = [].

Usage

python main.py
# or:
uv run python main.py

Then open output/index.html in a browser. Each run wipes and rebuilds output/<stem>/ for every PDF in examples/, so it's idempotent.

Auto-convert: rebuild when a PDF is dropped in examples/

Watch the folder with inotifywait (Linux; install inotify-tools):

sudo apt install inotify-tools

Save next to main.py as watch.sh:

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
WATCH_DIR="${1:-examples}"
echo "[watcher] watching $WATCH_DIR for new PDFs (Ctrl-C to stop)"
inotifywait -m -q -e close_write,moved_to --format '%f' "$WATCH_DIR" \
| while read -r filename; do
    case "$filename" in
        *.pdf|*.PDF)
            echo "[watcher] $(date '+%H:%M:%S') new PDF: $filename — rebuilding"
            python main.py
            # Optional: push to your server right after rebuild
            # rsync -avz --delete output/ user@server:/var/www/netgazet/
            ;;
    esac
done

Make executable and run:

chmod +x watch.sh
./watch.sh

The script rebuilds the whole site on every new PDF (~30 s for two 16-page issues). Fine for a handful of newsletters; for dozens, extend main.py to accept a single filename and only rebuild that issue plus the root index.

On macOS, swap inotifywait for fswatch -o "$WATCH_DIR" | while read; do ...; done.

Embedding in a WordPress page (iframe)

Once output/ is hosted somewhere (a vhost on your server, Cloudflare Pages, Netlify, etc.), embed it in any WordPress page or post:

  1. In the block editor, add a Custom HTML block.

  2. Paste:

    <iframe
      src="https://netgazet.yourdomain.tld/"
      style="width:100%; height:85vh; border:0;"
      loading="lazy"
      title="NETgazet archive">
    </iframe>
    
  3. Publish.

Notes:

  • Use HTTPS for src if your WordPress site is on HTTPS — browsers block mixed content.
  • All navigation (issue → page → next page) stays inside the iframe, so the WordPress page doesn't reload. Don't add target="_top" to links in the generated HTML — that would break out of the frame.
  • height: 85vh works on desktop and most phones. For a fixed value use height: 800px; (or similar).
  • Cross-domain iframe embeds need no special config, unless the static host sends X-Frame-Options: DENY or Content-Security-Policy: frame-ancestors 'none'. Nginx/Caddy don't add these by default — only an issue if you explicitly enabled them.

Subdomain vs subfolder (and auto-height)

The iframe src can point at any reachable URL, including a subdomain such as https://gazet.netwerk-antwerpen.be/:

<iframe
  src="https://gazet.netwerk-antwerpen.be/"
  style="width:100%; height:90vh; border:0;"
  loading="lazy"
  title="NETgazet archive">
</iframe>

That works out of the box (it's your own static files, so nothing blocks framing). Two things to know:

  • HTTPS must match. If WordPress is on https://, the subdomain must serve https:// too, or the browser blocks it as mixed content. Static hosts and most shared hosting provide free Let's Encrypt certs — just enable it.
  • A subdomain is a different origin from the WordPress domain. That has one practical consequence: cross-origin iframes can't auto-resize to their content height. The browser won't let the parent page read the iframe's height, so the postMessage auto-height trick doesn't work. You get one of:
    Hosting URL example Auto-height
    Subdomain (different origin) gazet.netwerk-antwerpen.be — use a fixed height (90vh / 1200px), accept inner scrollbars
    Subfolder (same origin) netwerk-antwerpen.be/gazet/ — same-origin, postMessage auto-height works

Pick a subfolder only if seamless auto-resizing matters more than the cleaner subdomain URL. Otherwise a subdomain with a fixed height is the simplest setup. (The Advanced iFrame Pro plugin can do cross-domain auto-height too, but it needs a helper file dropped on the subdomain.)

S
Description
No description provided
Readme
4.5 MiB
Languages
Python 100%