BETTER
This commit is contained in:
1 parent
4c59f077d5
commit
c8dbeaeca0
87 files changed
+135
-1827
No files matched your search
@@ -66,3 +66,19 @@
|
||||
}
|
||||
.footnotes-title { font-size: 1rem; }
|
||||
.marker { font-weight: 700; }
|
||||
|
||||
/* The navbar is sticky, so an element scrolled to the top of the viewport
|
||||
lands underneath it. These two are everything an anchor in this app points
|
||||
at -- chapter headings and verse numbers -- so the offset lives on them
|
||||
rather than on a scroll container. Roughly the navbar's height. */
|
||||
.chapter,
|
||||
.v { scroll-margin-top: 4.5rem; }
|
||||
|
||||
/* The navbar's reference picker. form-select is width:100% by default, which
|
||||
in a flex row means both selects try to take the whole navbar; and the
|
||||
default right padding leaves the caret sitting on top of a three-digit
|
||||
chapter number. */
|
||||
.reference .form-select {
|
||||
width: auto;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
+89
-1
@@ -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
|
||||
|
||||
@@ -76,6 +76,29 @@
|
||||
data-bs-theme="dark">
|
||||
<div class="container-xl">
|
||||
<a class="navbar-brand" href="./">Bible</a>
|
||||
{# The reference picker: which book you are in, and where in it to go.
|
||||
Outside the collapse deliberately -- it is the main way of moving around
|
||||
a book now that the chapter list is gone from the page, and a chapter
|
||||
picker behind a hamburger is no use on a phone.
|
||||
|
||||
Empty here and filled by app.js from the book on the page. The verse
|
||||
list has to be rebuilt in the browser every time the chapter changes, so
|
||||
there is nothing for a template to usefully render first.
|
||||
|
||||
d-none for the empty state rather than the hidden attribute: Bootstrap
|
||||
declares .d-flex after Reboot's [hidden] rule, both !important and at
|
||||
the same specificity, so the utility would win. #}
|
||||
<div class="reference navbar-text d-none align-items-center gap-1 ms-3"
|
||||
id="reference">
|
||||
<span class="reference-book text-light fw-semibold" id="reference-book"></span>
|
||||
<label class="visually-hidden" for="reference-chapter">Chapter</label>
|
||||
<select class="reference-chapter form-select form-select-sm"
|
||||
id="reference-chapter"></select>
|
||||
<span class="reference-sep text-light">:</span>
|
||||
<label class="visually-hidden" for="reference-verse">Verse</label>
|
||||
<select class="reference-verse form-select form-select-sm"
|
||||
id="reference-verse"></select>
|
||||
</div>
|
||||
<button class="navbar-toggler" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#site-nav"
|
||||
aria-controls="site-nav" aria-expanded="false"
|
||||
|
||||
Reference in new issue
Block a user