Best
This commit is contained in:
1 parent
2fea308b38
commit
f5fd8e632d
4 files changed
+91
-18
No files matched your search
+54
-9
@@ -121,26 +121,71 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
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);
|
||||
if (target.id === 'content') 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
|
||||
// with dead verse numbers.
|
||||
document.body.addEventListener('htmx:historyRestore', function () {
|
||||
markVerses();
|
||||
land();
|
||||
});
|
||||
|
||||
// -- 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.
|
||||
function jumpTo(el) {
|
||||
el.scrollIntoView({ block: 'start', behavior: 'instant' });
|
||||
}
|
||||
|
||||
// A worker-composed page is an ordinary document and the browser scrolls to
|
||||
// the anchor itself. One composed by boot.html is not: it replaced a
|
||||
// document the browser had already finished with, so the fragment identifier
|
||||
// has been spent. Repeating it costs nothing in the first case and is what
|
||||
// makes a link to GEN.html#v1_1 land on the verse in the second.
|
||||
// makes a link to GEN#v1_1 land on the verse in the second.
|
||||
function scrollToHash() {
|
||||
if (!location.hash) return;
|
||||
var el = document.getElementById(decodeURIComponent(location.hash.slice(1)));
|
||||
if (el) el.scrollIntoView();
|
||||
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 window.scrollTo({ top: 0, behavior: 'instant' });
|
||||
}
|
||||
|
||||
// api/bookmark has no origin server -- it is answered by the worker -- so
|
||||
|
||||
@@ -32,6 +32,11 @@
|
||||
// htmx stamps every request it makes; a navigation never carries it.
|
||||
this.htmx = this.request.headers.get('HX-Request') === 'true';
|
||||
this.navigate = this.request.mode === 'navigate';
|
||||
// Going back or forward. htmx asks for the page again and expects a whole
|
||||
// document back, the same as a navigation -- it takes the history element
|
||||
// out of the response itself.
|
||||
this.restore =
|
||||
this.request.headers.get('HX-History-Restore-Request') === 'true';
|
||||
}
|
||||
|
||||
Call.prototype.form = function () {
|
||||
|
||||
+4
-4
@@ -257,12 +257,12 @@ async function fragmentFor(path, event) {
|
||||
return res.ok ? res.text() : view.notice(...MISSING_NOTICE);
|
||||
}
|
||||
|
||||
// One handler for both callers. htmx is swapping into a shell that is already
|
||||
// on screen and wants the fragment; a navigation has no shell yet and gets the
|
||||
// fragment composed into one.
|
||||
// One handler for every caller. htmx swapping a link into a shell already on
|
||||
// screen wants the bare fragment; a navigation has no shell yet, and a history
|
||||
// restore is rebuilding one, so both get the fragment composed into a document.
|
||||
async function servePage(path, call) {
|
||||
const fragment = await fragmentFor(path, call.event);
|
||||
if (!call.navigate) return fragment;
|
||||
if (!call.navigate && !call.restore) return fragment;
|
||||
try {
|
||||
return await view.compose(fragment, await getBookmark());
|
||||
} catch (err) {
|
||||
|
||||
@@ -19,10 +19,22 @@
|
||||
scripture; Bootstrap styles the chrome. #}
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
{# History entries are whole books, and htmx keeps its history cache in
|
||||
localStorage -- a couple of them would blow the quota. Turned off: going
|
||||
back re-requests the fragment, which the worker answers from its cache. #}
|
||||
localStorage -- a couple of them would blow the quota. Turned off, so every
|
||||
back or forward is a cache miss, and refreshOnHistoryMiss decides what a
|
||||
miss does.
|
||||
|
||||
False on purpose. True makes htmx call location.reload(true) -- a forced
|
||||
reload, which is exactly the kind of navigation a browser takes past the
|
||||
service worker, so it reaches server.py and comes back as boot.html: the
|
||||
spinner flashes by and then reloads into the real page. False makes htmx
|
||||
fetch the page over XHR instead, which the worker answers itself, from its
|
||||
own cache when offline. No navigation, no boot page, nothing to flash.
|
||||
|
||||
That request carries HX-History-Restore-Request, and the worker answers it
|
||||
with a whole composed document; htmx lifts the element marked
|
||||
hx-history-elt out of the response and puts it back in the page. #}
|
||||
<meta name="htmx-config"
|
||||
content='{"historyCacheSize":0,"refreshOnHistoryMiss":true}'>
|
||||
content='{"historyCacheSize":0,"refreshOnHistoryMiss":false}'>
|
||||
{# The bundle carries Popper, which the navbar collapse and the modal do not
|
||||
need -- but a dropdown or tooltip added later would, and this is one file
|
||||
in the precache either way. Deferred scripts run in order, so `bootstrap`
|
||||
@@ -40,9 +52,15 @@
|
||||
<meta name="apple-mobile-web-app-title" content="Bible">
|
||||
</head>
|
||||
{# Boosted links swap only the fragment, so the navbar, the modal and the
|
||||
scripts above survive navigation and never reload. #}
|
||||
scripts above survive navigation and never reload.
|
||||
|
||||
No show: modifier, deliberately. htmx adds show:top itself for a boosted
|
||||
link, but skips it when the href carries a fragment identifier and scrolls
|
||||
to that anchor after the swap instead. Spelling show:top out here overrides
|
||||
that decision for every link, which leaves a cross-reference like ISA#v61_6
|
||||
sitting at the top of Isaiah. #}
|
||||
<body class="{% block body_class %}{% endblock %} bg-body-tertiary"
|
||||
hx-boost="true" hx-target="#content" hx-swap="innerHTML show:top">
|
||||
hx-boost="true" hx-target="#content" hx-swap="innerHTML">
|
||||
|
||||
<nav class="site-header navbar navbar-expand-lg bg-dark sticky-top"
|
||||
data-bs-theme="dark">
|
||||
@@ -68,7 +86,12 @@
|
||||
the full width of a desktop window. #}
|
||||
<main class="container-xl my-4">
|
||||
<div class="row justify-content-center">
|
||||
{# The swap target. hx-target on <body> names this id, so every boosted
|
||||
link lands here -- remove it and a click does nothing at all while a
|
||||
full navigation still works. #}
|
||||
<div id="content" class="col-12 col-xl-8" hx-history-elt>
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
Reference in new issue
Block a user