Move verse selector to htmx

This commit is contained in:
mark committed 2026-09-07 08:05:45 -04:00
1 parent dd0021ce15
commit ba3b588ac5
9 files changed
+394 -135

No files matched your search

+90
View File
@@ -45,6 +45,7 @@ from __future__ import annotations
import argparse
import html
import json
import os
import re
import sys
@@ -777,6 +778,91 @@ def render_index(books, translation, out_dir):
return path
# ==========================================================================
# WRITING -- the reference map
# ==========================================================================
# bible/verses.json: every book's chapters, and every chapter's verse labels.
#
# The navbar's reference picker is built from this, by the service worker. It
# used to be built in the browser by reading the book already on the page --
# the chapters were the sections and a chapter's verses were the anchors inside
# it -- which was free but only worked where there was a document to read. A
# worker has no DOM, and the picker now lives in a shell the worker composes
# before any document exists, so what the picker needs has to be a fact on
# disk rather than something recovered from markup.
#
# Written here, from the same tree the fragments are rendered from, so the two
# cannot disagree: every option this file produces names an anchor render_book
# wrote, because both come from the same verse nodes.
#
# The encoding is compact on purpose. A chapter whose verses run 1..n is
# written as the number n; only the eight in the corpus with a gap or a range
# -- Sirach 26 ends "19-27", 28, 29 -- spell their labels out:
#
# {"GEN": [31, 25, 24, ...], "SIR": [..., ["1", ..., "19-27", "28", "29"]]}
#
# That is 6 KB for 38,000 verses against 180 KB of spelled-out labels, which is
# the difference between a file the worker can parse every time it wakes and
# one it would have to find somewhere to keep.
#
# Chapter numbers are not in the file at all: they are the array index plus
# one. render_verses checks that, rather than assuming it.
VERSES_FILE = "verses.json"
def chapter_verses(chapter):
"""The verse labels of one chapter, in document order.
Blocks only. Footnotes hang inside the chapter section too, but a note's
content never carries a verse node -- and if one ever did, it would be a
citation rather than a place in the text.
"""
labels = []
def walk(nodes):
for node in nodes:
if node["t"] == "verse":
labels.append(node["label"])
elif "c" in node:
walk(node["c"])
for block in chapter["blocks"]:
walk(block.get("content", []))
return labels
def render_verses(books, out_dir):
"""Write the reference map. Returns its path and the verses in it.
See the note above for the encoding.
"""
out = {}
total = 0
for book in books:
numbers = [c["number"] for c in book["chapters"]]
if numbers != list(range(1, len(numbers) + 1)):
# The map addresses chapters by position, so a book that skips or
# repeats one would silently mislabel every option after the gap.
# Better to stop the build than to ship a picker that lies.
raise RuntimeError("%s: chapters are not 1..%d (%s)"
% (book["code"], len(numbers), numbers))
chapters = []
for chapter in book["chapters"]:
labels = chapter_verses(chapter)
plain = [str(i + 1) for i in range(len(labels))]
chapters.append(len(labels) if labels == plain else labels)
total += len(labels)
out[book["code"]] = chapters
path = os.path.join(out_dir, VERSES_FILE)
with open(path, "w", encoding="utf-8") as fh:
json.dump(out, fh, separators=(",", ":"), ensure_ascii=False)
fh.write("\n")
return path, total
# ==========================================================================
# Entry point
# ==========================================================================
@@ -797,6 +883,7 @@ def main(argv=None):
for book in data["books"]:
total += os.path.getsize(render_book(book, data["translation"], args.out))
render_index(data["books"], data["translation"], args.out)
verses, count = render_verses(data["books"], args.out)
chapters = sum(len(b["chapters"]) for b in data["books"])
notes = sum(len(c["notes"]) for b in data["books"] for c in b["chapters"])
@@ -807,6 +894,9 @@ def main(argv=None):
print("skipped %d (contents pages)" % len(skipped), file=sys.stderr)
print("html %.1f MB total, %.0f KB average"
% (total / 1e6, total / len(data["books"]) / 1e3), file=sys.stderr)
print("verses %d in %s (%.1f KB)"
% (count, os.path.basename(verses), os.path.getsize(verses) / 1e3),
file=sys.stderr)
print("wrote %s/" % args.out.rstrip("/"), file=sys.stderr)
return 0