Better
This commit is contained in:
1 parent
ea193e1264
commit
2fea308b38
106 files changed
+1234
-878
No files matched your search
@@ -0,0 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>World English Bible Updated</title>
|
||||
<!-- Vendored rather than linked from a CDN: sw.js only caches same-origin
|
||||
responses, so a CDN stylesheet would leave the app unstyled offline. -->
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<meta name="theme-color" content="#1a202c">
|
||||
</head>
|
||||
<body class="bg-body-tertiary">
|
||||
|
||||
<!-- Served by server.py in place of any address a browser navigates to while
|
||||
no service worker is controlling this origin: a first visit, a cleared
|
||||
cache, a reload that bypassed the worker.
|
||||
|
||||
The pages on disk are fragments and the shell is a template, and it is the
|
||||
worker that puts them together. So this page has one job: register the
|
||||
worker and reload once it is running, which lands on the real app at the
|
||||
address that was asked for. It is only ever seen once per device. -->
|
||||
|
||||
<main class="boot container py-5 text-center">
|
||||
<div class="spinner-border text-secondary mb-3" role="status" aria-hidden="true"></div>
|
||||
<p id="boot-status" class="text-body-secondary">Preparing the offline copy…</p>
|
||||
<noscript><p class="alert alert-warning d-inline-block">This app builds its
|
||||
pages in the browser and needs JavaScript.</p></noscript>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function status(text) {
|
||||
document.getElementById('boot-status').textContent = text;
|
||||
}
|
||||
|
||||
// The reload lands on this page again if the worker somehow activates
|
||||
// without taking over the navigation. Two attempts is enough to cover a
|
||||
// browser that needed one, and stops a device that never will from
|
||||
// reloading forever.
|
||||
var tries = 0;
|
||||
try { tries = parseInt(sessionStorage.getItem('boot:tries') || '0', 10); }
|
||||
catch (err) { tries = 0; }
|
||||
|
||||
function go() {
|
||||
try { sessionStorage.removeItem('boot:tries'); } catch (err) {}
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (tries >= 2) return compose('The offline copy could not start.');
|
||||
try { sessionStorage.setItem('boot:tries', String(tries + 1)); } catch (err) {}
|
||||
|
||||
navigator.serviceWorker.register('sw.js').then(function (registration) {
|
||||
// Active already: this load raced the install and is simply not
|
||||
// controlled yet. Otherwise wait for the one installing to get there.
|
||||
if (registration.active) return go();
|
||||
var worker = registration.installing || registration.waiting;
|
||||
if (!worker) return compose('The offline copy did not install.');
|
||||
worker.addEventListener('statechange', function (event) {
|
||||
if (event.target.state === 'activated') go();
|
||||
});
|
||||
}, function (err) {
|
||||
compose('Could not install the offline copy: ' + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
// Everything below is the path where there will never be a worker: a browser
|
||||
// without them, or -- much more likely -- the app served over plain http://
|
||||
// from a LAN address, which is not a secure context. Rather than spin here
|
||||
// forever, the page composes itself, using the same renderer and the same
|
||||
// templates the worker would have used. No offline copy and no bookmark, but
|
||||
// the text reads. Delete this if the app is only ever served over TLS.
|
||||
function compose(why) {
|
||||
console.warn(why);
|
||||
status('Loading…');
|
||||
script('js/nunjucks.min.js')
|
||||
.then(function () { return script('js/render.js'); })
|
||||
.then(function () {
|
||||
var view = createRenderer(readText);
|
||||
var page = location.pathname.split('/').pop();
|
||||
return readText('./' + page + location.search)
|
||||
.catch(function () {
|
||||
return view.notice('Not available',
|
||||
'This page could not be loaded. Check your connection and try again.');
|
||||
})
|
||||
// mark is null: the bookmark lives in the worker's IndexedDB, and
|
||||
// there is no worker to ask. bookmark.njk renders the slot hidden
|
||||
// and it fills itself the moment one appears.
|
||||
.then(function (fragment) { return view.compose(fragment, null); });
|
||||
})
|
||||
.then(become, function (err) { status('Could not start: ' + err.message); });
|
||||
}
|
||||
|
||||
function script(src) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var el = document.createElement('script');
|
||||
el.src = src;
|
||||
el.onload = function () { resolve(); };
|
||||
el.onerror = function () { reject(new Error('cannot load ' + src)); };
|
||||
document.head.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
function readText(path) {
|
||||
return fetch(path, { credentials: 'same-origin' }).then(function (res) {
|
||||
if (!res.ok) throw new Error(path + ': ' + res.status);
|
||||
return res.text();
|
||||
});
|
||||
}
|
||||
|
||||
// Replaces this document in place: same URL, same history entry, no second
|
||||
// request. Written rather than assigned into the DOM because the shell
|
||||
// carries <script> tags, and scripts put into a page any other way never run.
|
||||
function become(html) {
|
||||
document.open();
|
||||
document.write(html);
|
||||
document.close();
|
||||
}
|
||||
|
||||
if ('serviceWorker' in navigator) start();
|
||||
else compose('This browser has no service worker support.');
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user