Fixes
This commit is contained in:
1 parent
ce73c76126
commit
84c07a912b
5 files changed
+44
-133
No files matched your search
+17
-112
@@ -2,13 +2,14 @@
|
||||
//
|
||||
// Pages arrive whole -- composed by the service worker, or by boot.html on the
|
||||
// visit before there is one -- so there is nothing here that builds a page.
|
||||
// What is left is the four things a composed document cannot do for itself:
|
||||
// mark up the verse numbers as controls, 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.
|
||||
// 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.
|
||||
//
|
||||
// Two things used to live here and no longer do, both for the same reason:
|
||||
// they were reading the page to work out something the worker already knew.
|
||||
// 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
|
||||
@@ -24,39 +25,18 @@
|
||||
// delegated because the trigger is one of 38,000 generated verse numbers and
|
||||
// cannot carry attributes of its own. See templates/verse.njk.
|
||||
//
|
||||
// What is left of the dialog here is markVerses, and only because the numbers
|
||||
// have to look and behave like controls before anyone can click one.
|
||||
// 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';
|
||||
|
||||
// -- verse numbers ------------------------------------------------------
|
||||
|
||||
// Applied here rather than baked into the HTML: without this script the
|
||||
// numbers are plain text, and announcing them as buttons would lie. It also
|
||||
// earns Reboot's [role=button]{cursor:pointer}. Runs again after every htmx
|
||||
// swap, because the new verses arrive bare.
|
||||
function markVerses(root) {
|
||||
var verses = (root || document).querySelectorAll('.chapter .v');
|
||||
for (var i = 0; i < verses.length; i++) {
|
||||
verses[i].setAttribute('role', 'button');
|
||||
verses[i].setAttribute('tabindex', '0');
|
||||
verses[i].setAttribute('aria-label',
|
||||
'Verse ' + (verses[i].textContent || '').trim() + ', options');
|
||||
}
|
||||
}
|
||||
|
||||
// Delegated at the document, so it survives swaps and is bound only once.
|
||||
// The verse numbers and the dialog's own buttons are not here: opening the
|
||||
// dialog is an hx-on handler on #content, and everything inside it is
|
||||
// markup that sw.js sends. This is the reset button and nothing else.
|
||||
document.addEventListener('click', function (event) {
|
||||
if (!event.target.closest) return;
|
||||
var reset = event.target.closest('#app-reset');
|
||||
if (reset) resetApp(reset);
|
||||
});
|
||||
// -- events -------------------------------------------------------------
|
||||
|
||||
document.body.addEventListener('htmx:afterSwap', function (event) {
|
||||
markVerses(event.target);
|
||||
var target = event.detail.target;
|
||||
if (!target) return;
|
||||
// Both selects answer into the verse list, so this is either of them.
|
||||
@@ -67,10 +47,10 @@
|
||||
// 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
|
||||
// with dead verse numbers. The picker listens for the same event itself,
|
||||
// from the navbar, because a restore reaches nothing outside #content.
|
||||
// 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 () {
|
||||
markVerses();
|
||||
land();
|
||||
showVersion();
|
||||
});
|
||||
@@ -195,80 +175,6 @@
|
||||
el.classList.toggle('d-none', !document.querySelector('#content .index'));
|
||||
}
|
||||
|
||||
// -- reset --------------------------------------------------------------
|
||||
|
||||
// Everything this app has put on the device, cleared in the order that makes
|
||||
// the next load a first visit.
|
||||
//
|
||||
// The worker goes first, so that nothing is left answering out of a cache
|
||||
// while the caches themselves are being deleted. Unregistering does not stop
|
||||
// the copy already controlling this page -- an active worker keeps its
|
||||
// clients until they unload -- which is what the reload at the end is for:
|
||||
// the registration is gone by then, the navigation reaches server.py, and
|
||||
// boot.html registers a new worker against whatever is on disk now.
|
||||
//
|
||||
// Then the caches, both of them, by whatever names they carry: the point of
|
||||
// this button is the case where the names are not the ones this build would
|
||||
// have chosen. Then the bookmark, which is the only thing here a reader
|
||||
// would miss -- hence the question first.
|
||||
//
|
||||
// One after another rather than all at once, so that the order above is the
|
||||
// order it happens in, and so that a step cannot be reading storage another
|
||||
// is halfway through deleting.
|
||||
//
|
||||
// It still races the worker's own background download, which writes a book
|
||||
// into the cache on any request that arrives while this is running -- there
|
||||
// is no way to call that off from here. The reload is what settles it: the
|
||||
// worker is gone by then, and the one that installs in its place opens
|
||||
// caches under its own names regardless of what survived.
|
||||
//
|
||||
// Each step is allowed to fail on its own. A browser with no caches API, or
|
||||
// storage the user has locked down, should still get everything else
|
||||
// cleared and still reload.
|
||||
var RESET_QUESTION =
|
||||
'Clear this app from the device? The offline copy of the books and the ' +
|
||||
'bookmark go with it, and the app reloads as a first visit.';
|
||||
|
||||
function settle(promise) {
|
||||
return promise.catch(function () {});
|
||||
}
|
||||
|
||||
function forgetWorker() {
|
||||
if (!('serviceWorker' in navigator)) return Promise.resolve();
|
||||
return navigator.serviceWorker.getRegistrations().then(function (regs) {
|
||||
return Promise.all(regs.map(function (reg) { return reg.unregister(); }));
|
||||
});
|
||||
}
|
||||
|
||||
function forgetCaches() {
|
||||
if (!window.caches) return Promise.resolve();
|
||||
return caches.keys().then(function (keys) {
|
||||
return Promise.all(keys.map(function (key) { return caches.delete(key); }));
|
||||
});
|
||||
}
|
||||
|
||||
// Resolved on blocked as well as on done. Blocked means another tab still
|
||||
// has the database open, and there is nothing useful to do about it from
|
||||
// here: this tab is about to reload, and the delete completes when the last
|
||||
// one lets go.
|
||||
function forgetBookmark() {
|
||||
return new Promise(function (resolve) {
|
||||
if (!window.indexedDB) return resolve();
|
||||
var req = indexedDB.deleteDatabase('bible');
|
||||
req.onsuccess = req.onerror = req.onblocked = function () { resolve(); };
|
||||
});
|
||||
}
|
||||
|
||||
function resetApp(button) {
|
||||
if (!window.confirm(RESET_QUESTION)) return;
|
||||
button.disabled = true;
|
||||
button.textContent = 'Clearing…';
|
||||
settle(forgetWorker())
|
||||
.then(function () { return settle(forgetCaches()); })
|
||||
.then(function () { return settle(forgetBookmark()); })
|
||||
.then(function () { location.reload(); });
|
||||
}
|
||||
|
||||
// api/bookmark has no origin server -- it is answered by the worker -- so
|
||||
// there is nothing to ask until the worker is controlling this page.
|
||||
// controller, not ready: ready can resolve a tick before clients.claim()
|
||||
@@ -282,7 +188,6 @@
|
||||
// Deferred scripts run before DOMContentLoaded fires, so this always lands,
|
||||
// and it lands after htmx has processed the shell.
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
markVerses();
|
||||
scrollToHash();
|
||||
showVersion();
|
||||
|
||||
|
||||
Reference in new issue
Block a user