This commit is contained in:
mark committed 2026-09-04 10:51:29 -04:00
1 parent 4c59f077d5
commit c8dbeaeca0
87 files changed
+135 -1827

No files matched your search

+89 -1
View File
@@ -128,7 +128,93 @@
// Close the modal once a bookmark write has come back and swapped, leaving
// the status line up long enough to be read.
if (target.id === 'bookmark') return setTimeout(hideModal, 900);
if (target.id === 'content') land();
if (target.id === 'content') { fillReference(); land(); }
});
// -- reference picker ---------------------------------------------------
// The "GEN 1:1" in the navbar. Built from the book on the page rather than
// from a table shipped beside it: the chapters are the sections and a
// chapter's verses are the anchors inside it, so an option can never name a
// target that is not there to scroll to, and there is no second file to keep
// in step, cache, or version.
//
// It is not kept in step with the scroll position, on purpose. It says where
// you are going, not where you are, and it starts at 1:1 in every book.
function byId(id) {
return document.getElementById(id);
}
function fillOptions(select, items) {
select.textContent = '';
for (var i = 0; i < items.length; i++) {
var option = document.createElement('option');
// The value is the id of the element to land on, so nothing downstream
// has to know how a verse anchor is spelled.
option.value = items[i].value;
option.textContent = items[i].label;
select.appendChild(option);
}
}
function fillVerses(chapterId) {
var section = byId(chapterId);
var verses = section ? section.querySelectorAll('.v') : [];
var items = [];
for (var i = 0; i < verses.length; i++) {
items.push({ value: verses[i].id,
label: (verses[i].textContent || '').trim() });
}
fillOptions(byId('reference-verse'), items);
}
function fillReference() {
var picker = byId('reference');
if (!picker) return;
var book = document.querySelector('#content article.book');
picker.classList.toggle('d-none', !book);
picker.classList.toggle('d-flex', !!book);
if (!book) return;
byId('reference-book').textContent = book.getAttribute('data-book') || '';
var chapters = book.querySelectorAll('.chapter[data-chapter]');
var items = [];
for (var i = 0; i < chapters.length; i++) {
items.push({ value: chapters[i].id,
label: chapters[i].getAttribute('data-chapter') });
}
fillOptions(byId('reference-chapter'), items);
if (items.length) fillVerses(items[0].value);
}
// The URL is the whole mechanism: name the anchor, then land on it. Nothing
// is stored anywhere else, so a reload or a shared link ends up in the same
// place.
//
// replaceState rather than assigning location.hash: spinning through a
// select should not stack one history entry per option, and assigning the
// hash hands the scroll to the browser, which Bootstrap has set to smooth.
function goTo(id) {
var target = byId(id);
if (!target) return;
history.replaceState(null, '', '#' + id);
jumpTo(target);
}
// Delegated at the document, so it survives every swap and binds once.
// Changing the chapter rebuilds the verses and lands on the chapter heading,
// which is what 1 is directly below; changing the verse lands on the verse.
document.addEventListener('change', function (event) {
var target = event.target;
if (!target || !target.id) return;
if (target.id === 'reference-chapter') {
fillVerses(target.value);
goTo(target.value);
} else if (target.id === 'reference-verse') {
goTo(target.value);
}
});
// A history restore does not go through the request pipeline -- htmx swaps
@@ -137,6 +223,7 @@
// with dead verse numbers.
document.body.addEventListener('htmx:historyRestore', function () {
markVerses();
fillReference();
land();
});
@@ -202,6 +289,7 @@
// and it lands after htmx has processed the shell.
document.addEventListener('DOMContentLoaded', function () {
markVerses();
fillReference();
scrollToHash();
// Registered from the page it is going to serve, not from a page of its