Fixes
This commit is contained in:
1 parent
9407c26d2d
commit
bfb3c87558
5 files changed
+204
-129
No files matched your search
+25
-102
@@ -1,99 +1,36 @@
|
||||
// Page startup and verse modal wiring.
|
||||
// Page startup.
|
||||
//
|
||||
// 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, run the modal, put a swapped-in page
|
||||
// where it belongs before it is painted, and tell the chrome when a worker has
|
||||
// appeared to answer it.
|
||||
// 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.
|
||||
//
|
||||
// The reference picker used to be here as well, built by reading the chapters
|
||||
// and verses off the book on the page. It is markup and htmx now: sw.js
|
||||
// renders it from bible/verses.json and answers api/reference, and the scroll
|
||||
// and the address-bar update that a change event used to run through here are
|
||||
// response headers on the answer. See templates/reference.njk.
|
||||
// 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.
|
||||
//
|
||||
// The modal's markup is in templates/base.njk and the bookmark is in the
|
||||
// worker, so all this file does for it is decide which verse was clicked, fill
|
||||
// the form, and show it -- Bootstrap handles opening, dismissing and focus,
|
||||
// and htmx carries the save.
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var currentUrl = '';
|
||||
|
||||
function modalEl() {
|
||||
return document.getElementById('verse-dialog');
|
||||
}
|
||||
|
||||
function status(text) {
|
||||
var el = document.getElementById('verse-dialog-status');
|
||||
if (el) el.textContent = text || '';
|
||||
return el;
|
||||
}
|
||||
|
||||
// navigator.clipboard only exists in a secure context, so over plain http
|
||||
// this always falls through to a selectable field rather than failing.
|
||||
function copyLink(url) {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(url).then(function () {
|
||||
status('Link copied.');
|
||||
}, function () { offerManualCopy(url); });
|
||||
} else {
|
||||
offerManualCopy(url);
|
||||
}
|
||||
}
|
||||
|
||||
function offerManualCopy(url) {
|
||||
var el = status('Copy this link:');
|
||||
if (!el) return;
|
||||
var field = document.createElement('input');
|
||||
field.type = 'text';
|
||||
field.readOnly = true;
|
||||
field.value = url;
|
||||
field.className = 'verse-dialog-url form-control form-control-sm mt-2';
|
||||
el.appendChild(field);
|
||||
field.focus();
|
||||
field.select();
|
||||
}
|
||||
|
||||
// -- verse numbers ------------------------------------------------------
|
||||
|
||||
function verseInfo(span) {
|
||||
var article = document.querySelector('article.book');
|
||||
var section = span.closest('.chapter');
|
||||
var book = article
|
||||
? (article.getAttribute('data-name') || article.getAttribute('data-book'))
|
||||
: '';
|
||||
var chapter = section ? section.getAttribute('data-chapter') : '';
|
||||
var page = location.pathname.split('/').pop() || './';
|
||||
return {
|
||||
ref: book + ' ' + chapter + ':' + (span.textContent || '').trim(),
|
||||
// Relative, so a saved bookmark survives a change of host or port.
|
||||
href: page + '#' + span.id,
|
||||
url: location.origin + location.pathname + '#' + span.id
|
||||
};
|
||||
}
|
||||
|
||||
function openFor(span) {
|
||||
var el = modalEl();
|
||||
if (!el || !window.bootstrap) return;
|
||||
var info = verseInfo(span);
|
||||
currentUrl = info.url;
|
||||
document.getElementById('verse-dialog-ref').textContent = info.ref;
|
||||
el.querySelector('input[name="ref"]').value = info.ref;
|
||||
el.querySelector('input[name="href"]').value = info.href;
|
||||
status('');
|
||||
window.bootstrap.Modal.getOrCreateInstance(el).show();
|
||||
}
|
||||
|
||||
function hideModal() {
|
||||
var el = modalEl();
|
||||
if (!el || !window.bootstrap) return;
|
||||
var instance = window.bootstrap.Modal.getInstance(el);
|
||||
if (instance) instance.hide();
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -109,33 +46,19 @@
|
||||
}
|
||||
|
||||
// Delegated at the document, so it survives swaps and is bound only once.
|
||||
// Dismissal is not handled here: the close button carries data-bs-dismiss,
|
||||
// and Bootstrap owns the backdrop and Escape.
|
||||
// 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 verse = event.target.closest('.v');
|
||||
if (verse && verse.id) { event.preventDefault(); openFor(verse); return; }
|
||||
if (event.target.closest('[data-action="copy"]')) copyLink(currentUrl);
|
||||
var reset = event.target.closest('#app-reset');
|
||||
if (reset) resetApp(reset);
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key !== 'Enter' && event.key !== ' ') return;
|
||||
var el = document.activeElement;
|
||||
if (el && el.classList && el.classList.contains('v') && el.id) {
|
||||
event.preventDefault();
|
||||
openFor(el);
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:afterSwap', function (event) {
|
||||
markVerses(event.target);
|
||||
var target = event.detail.target;
|
||||
if (!target) return;
|
||||
// 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);
|
||||
// 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(); }
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
'bookmark.njk',
|
||||
'reference.njk',
|
||||
'verses.njk',
|
||||
'verse.njk',
|
||||
'version.njk',
|
||||
'status.njk',
|
||||
'notice.njk'
|
||||
|
||||
Reference in new issue
Block a user