134 lines
4.7 KiB
JavaScript
134 lines
4.7 KiB
JavaScript
// Verse modal wiring. The modal markup lives in base.njk and the bookmark
|
|
// lives in the service worker, so all this file does is decide which verse
|
|
// was clicked, fill the form, and show it -- Bootstrap handles opening,
|
|
// dismissing and focus, and htmx carries the save.
|
|
(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() || 'index.html';
|
|
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;
|
|
el.querySelector('.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
|
|
// 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.
|
|
// Dismissal is not handled here: the close button carries data-bs-dismiss,
|
|
// and Bootstrap owns the backdrop and Escape.
|
|
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);
|
|
});
|
|
|
|
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);
|
|
}
|
|
});
|
|
|
|
// Close the modal once a bookmark write has come back and swapped, leaving
|
|
// the status line up long enough to be read.
|
|
document.body.addEventListener('htmx:afterSwap', function (event) {
|
|
markVerses(event.target);
|
|
if (event.detail.target && event.detail.target.id === 'bookmark') {
|
|
setTimeout(hideModal, 900);
|
|
}
|
|
});
|
|
|
|
markVerses(document);
|
|
|
|
// The shell already carries the bookmark, so this is only for the case where
|
|
// the worker started controlling the page after it was rendered.
|
|
if ('serviceWorker' in navigator) {
|
|
navigator.serviceWorker.ready.then(function () {
|
|
document.body.dispatchEvent(new CustomEvent('bookmark-load'));
|
|
}).catch(function () {});
|
|
}
|
|
})();
|