Remove comments
This commit is contained in:
1 parent
872175153a
commit
057b4be7d7
18 files changed
+66
-1350
No files matched your search
+4
-147
@@ -1,187 +1,44 @@
|
||||
// Page startup.
|
||||
//
|
||||
// Pages arrive whole -- the service worker composes every one of them -- so
|
||||
// there is nothing here that builds a page.
|
||||
// What is left is the three things a composed document cannot do for itself:
|
||||
// put a swapped-in page where it belongs before it is painted, land the
|
||||
// reference picker where the reader asked to go, and tell the chrome when a
|
||||
// worker has appeared to answer it.
|
||||
//
|
||||
// Three things used to live here and no longer do, all for the same reason:
|
||||
// they were reading the page to work out something that was already known
|
||||
// somewhere better.
|
||||
//
|
||||
// The reference picker was built by reading the chapters and verses off the
|
||||
// book on screen. It is markup and htmx now: sw.js renders it from
|
||||
// bible/verses.json and answers api/reference, and the address-bar update that
|
||||
// a change event used to run through here is a response header on the answer.
|
||||
// See templates/reference.njk.
|
||||
//
|
||||
// The verse dialog was built by scraping the book name out of article
|
||||
// [data-name] and the chapter out of the enclosing section, then filling a
|
||||
// form and showing it. sw.js answers api/verse with the whole of the dialog,
|
||||
// htmx swaps it into the empty modal in templates/base.njk, and Bootstrap
|
||||
// opens it -- which is htmx's own bootstrap-modal example, with the click
|
||||
// delegated because the trigger is one of 38,000 generated verse numbers and
|
||||
// cannot carry attributes of its own. See templates/verse.njk.
|
||||
//
|
||||
// The third was marking those numbers as controls: role, tabindex and an
|
||||
// aria-label, set on every verse on load, after every swap and after every
|
||||
// history restore -- a walk over a whole book, three times over, to arrive at
|
||||
// the same three attributes every time. They are in the markup now, written
|
||||
// by compile_bible.py, because nothing about them depends on anything only the
|
||||
// browser knows. It costs the corpus a couple of megabytes and saves the pass.
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// -- events -------------------------------------------------------------
|
||||
|
||||
document.body.addEventListener('htmx:afterSwap', function (event) {
|
||||
var target = event.detail.target;
|
||||
if (!target) return;
|
||||
// Both selects answer into the verse list, so this is either of them.
|
||||
if (target.id === 'reference-verse') return landOnReference();
|
||||
if (target.id === 'content') { land(); showVersion(); }
|
||||
if (target.id === 'reference-verse') return scrollToHash();
|
||||
if (target.id === 'content') return land();
|
||||
});
|
||||
|
||||
// A history restore does not go through the request pipeline -- htmx swaps
|
||||
// the element straight in and fires this instead -- so the work afterSwap
|
||||
// does has to be repeated here or a page reached by the back button arrives
|
||||
// scrolled to wherever the last one was. The picker listens for the same
|
||||
// event itself, from the navbar, because a restore reaches nothing outside
|
||||
// #content.
|
||||
document.body.addEventListener('htmx:historyRestore', function () {
|
||||
land();
|
||||
showVersion();
|
||||
});
|
||||
|
||||
// -- startup ------------------------------------------------------------
|
||||
|
||||
function hashTarget() {
|
||||
if (!location.hash) return null;
|
||||
return document.getElementById(decodeURIComponent(location.hash.slice(1)));
|
||||
}
|
||||
|
||||
// Every scroll here is a correction, not a gesture: the page is meant to
|
||||
// arrive already in position. So they all say instant.
|
||||
//
|
||||
// Saying nothing would not mean instant. The default is "auto", which means
|
||||
// "whatever scroll-behavior computes to" -- and Bootstrap's Reboot sets
|
||||
// :root { scroll-behavior: smooth }, so the default animates. Bootstrap
|
||||
// wraps that in prefers-reduced-motion, which is the right call for the
|
||||
// chapter nav's own anchors; these are not that.
|
||||
//
|
||||
// Asked for twice, because "instant" is a later addition to an old enum and
|
||||
// a browser that does not know the value rejects the whole call with a
|
||||
// TypeError rather than ignoring the word -- so on an older WebKit this did
|
||||
// not scroll slowly, it did not scroll at all. "auto" is what the enum has
|
||||
// always had; it animates here, which is worse than instant and far better
|
||||
// than staying put.
|
||||
function jumpTo(el) {
|
||||
try {
|
||||
el.scrollIntoView({ block: 'start', behavior: 'instant' });
|
||||
} catch (err) {
|
||||
el.scrollIntoView({ block: 'start', behavior: 'auto' });
|
||||
}
|
||||
el.scrollIntoView({ block: 'start', behavior: 'instant' });
|
||||
}
|
||||
|
||||
// The same, for the top of the page and the same reason.
|
||||
function jumpToTop() {
|
||||
try {
|
||||
window.scrollTo({ top: 0, behavior: 'instant' });
|
||||
} catch (err) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
window.scrollTo({ top: 0, behavior: 'instant' });
|
||||
}
|
||||
|
||||
// The browser scrolls to the anchor itself on a document it navigated to,
|
||||
// so this is for the one it does not: a reload that landed on boot.html and
|
||||
// was reloaded again into the real page. Costs nothing when the browser has
|
||||
// already been there.
|
||||
function scrollToHash() {
|
||||
var el = hashTarget();
|
||||
if (el) jumpTo(el);
|
||||
}
|
||||
|
||||
// Where a swapped-in page should sit before it is ever seen.
|
||||
//
|
||||
// htmx does this itself -- scrolling to the anchor a boosted href named, or
|
||||
// to the top of the target when it named none -- but from the settle
|
||||
// callback, which is a setTimeout of defaultSettleDelay, 20ms. The browser
|
||||
// paints the new content at the old scroll offset first, so the jump is
|
||||
// visible every time. Here it runs in the same task as the swap, before any
|
||||
// paint, so the page simply appears where it belongs. htmx's own scroll
|
||||
// still follows and finds nothing left to do.
|
||||
//
|
||||
// htmx has already pushed the new URL by this point -- history is updated
|
||||
// before the swap, not after -- so location.hash is the anchor that was
|
||||
// asked for.
|
||||
function land() {
|
||||
var el = hashTarget();
|
||||
if (el) jumpTo(el);
|
||||
else jumpToTop();
|
||||
}
|
||||
|
||||
// Where the reference picker lands, which is the one scroll in the app that
|
||||
// is a gesture: the reader asked for chapter 3 and is waiting to be taken
|
||||
// there.
|
||||
//
|
||||
// It used to ride along on the answer, as show: on HX-Reswap, and be htmx's
|
||||
// scroll rather than this file's. Two reasons it is here now. The smaller
|
||||
// one is that htmx's own scrollIntoView says "instant" with nothing around
|
||||
// it, so the enum above takes the whole scroll down with it. The larger one
|
||||
// is the phone: a select on iOS is a picker drawn over the page, the change
|
||||
// event arrives while it is still up, and dismissing it puts the page back
|
||||
// where it stood when it opened -- undoing anything scrolled in between.
|
||||
// The options were swapping correctly and the book never moved.
|
||||
//
|
||||
// So: once now, for a pointer, and once more after the picker has had time
|
||||
// to finish, but only if the page did not stay where it was put. That test
|
||||
// is also what keeps the second attempt from yanking back a reader who has
|
||||
// started scrolling on their own.
|
||||
//
|
||||
// The anchor comes from the address bar. The worker sends HX-Replace-Url
|
||||
// with the answer and htmx updates history before it swaps, so by the time
|
||||
// this runs location.hash is already naming the chapter or verse chosen.
|
||||
var PICKER_SETTLE_MS = 350;
|
||||
|
||||
function landOnReference() {
|
||||
var el = hashTarget();
|
||||
if (!el) return;
|
||||
jumpTo(el);
|
||||
var landed = window.pageYOffset;
|
||||
setTimeout(function () {
|
||||
var again = hashTarget();
|
||||
if (again && Math.abs(window.pageYOffset - landed) > 2) jumpTo(again);
|
||||
}, PICKER_SETTLE_MS);
|
||||
}
|
||||
|
||||
// The version readout, shown under the book list and nowhere else.
|
||||
//
|
||||
// It is chrome: it sits outside #content, so it outlives every swap -- and
|
||||
// so does the body class it was rendered with, which is exactly why that
|
||||
// cannot be what decides. What is in #content can, because that is the one
|
||||
// thing a swap changes: the book list is the fragment rooted at .index.
|
||||
//
|
||||
// Toggling the class rather than the style because Bootstrap declares
|
||||
// .d-none !important, which an inline display would lose to.
|
||||
function showVersion() {
|
||||
var el = document.getElementById('app-version');
|
||||
if (!el) return;
|
||||
el.classList.toggle('d-none', !document.querySelector('#content .index'));
|
||||
}
|
||||
|
||||
// Deferred scripts run before DOMContentLoaded fires, so this always lands,
|
||||
// and it lands after htmx has processed the shell.
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
scrollToHash();
|
||||
showVersion();
|
||||
|
||||
// Registered from the page it is going to serve, not from a page of its
|
||||
// own: this load is already the real app, composed by the worker that is
|
||||
// controlling it. What this catches is the copy on disk having changed
|
||||
// since -- the browser refetches sw.js, sees different bytes, and installs
|
||||
// the new one alongside, which takes over from the next navigation on.
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('sw.js').catch(function () {});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user