Remove bootstrap

This commit is contained in:
mark committed 2026-10-21 15:33:08 -04:00
1 parent 04ea39607d
commit 531a32e394
14 files changed
+154 -395

No files matched your search

+3 -202
View File
@@ -22,11 +22,9 @@ const PRECACHE = [
'./',
'./manifest.webmanifest',
'./css/app.css',
'./css/bootstrap.min.css',
'./fonts/DayLeafInitials.woff2',
'./ornaments/group-l-49.svg',
'./js/htmx.min.js',
'./js/bootstrap.bundle.min.js'
'./js/htmx.min.js'
].concat(TEMPLATE_URLS);
function cacheFor(req) {
@@ -125,88 +123,6 @@ const view = createRenderer(readTemplate);
const router = new Router(new URL('./', location.href).pathname);
const VERSES_URL = './verses.json';
let verses = null;
function readVerses() {
if (!verses) {
verses = readText(pageRequest(VERSES_URL))
.then(JSON.parse)
.catch((err) => { verses = null; throw err; });
}
return verses;
}
function parseAnchor(id) {
const chapter = /^ch(\d+)$/.exec(id || '');
if (chapter) return { chapter: +chapter[1], verse: null };
const verse = /^v(\d+)_[0-9-]+$/.exec(id || '');
if (verse) return { chapter: +verse[1], verse: id };
return null;
}
function verseLabels(chapters, number) {
const entry = chapters[number - 1];
if (typeof entry !== 'number') return entry || [];
const out = [];
for (let i = 1; i <= entry; i++) out.push(String(i));
return out;
}
function referenceFor(map, code, anchor) {
const chapters = map && Object.prototype.hasOwnProperty.call(map, code)
? map[code] : null;
if (!chapters || !chapters.length) return null;
if (!chapters.some((entry) => (typeof entry === 'number' ? entry : entry.length))) {
return null;
}
const at = parseAnchor(anchor) || { chapter: 1, verse: null };
const number = at.chapter >= 1 && at.chapter <= chapters.length ? at.chapter : 1;
const verse = at.chapter === number ? at.verse : null;
return {
book: code,
chapter: 'ch' + number,
chapters: chapters.map((_, i) => ({ id: 'ch' + (i + 1), label: String(i + 1) })),
verse: verse,
verses: verseLabels(chapters, number)
.map((label) => ({ id: 'v' + number + '_' + label, label: label }))
};
}
function bookOf(url) {
return url ? router.relative(url).slice(1) : '';
}
async function referenceOf(code, anchor) {
if (!code) return null;
try {
return referenceFor(await readVerses(), code, anchor);
} catch (err) {
return null;
}
}
async function verseOf(page, at) {
const code = bookOf(page);
const anchor = parseAnchor(at);
if (!page || !code || !anchor || !anchor.verse) return null;
const reference = await referenceOf(code, at);
if (!reference) return null;
const option = reference.verses.find((verse) => verse.id === at);
if (!option) return null;
return {
ref: code + ' ' + anchor.chapter + ':' + option.label,
href: code + '#' + at,
url: page.origin + page.pathname + '#' + at
};
}
const OFFLINE_NOTICE = ['Not available offline',
'This page has not been opened on this device, so there is no copy stored ' +
'here. Reconnect and try again, or open one you have read before.'];
@@ -227,14 +143,9 @@ async function servePage(path, code, call) {
if (!code) {
fragment += '\n' + await view.render('version.njk', { version: VERSIONS });
}
const reference = await referenceOf(code, null);
if (!call.navigate && !call.restore) {
return fragment + '\n'
+ await view.render('reference.njk', { reference: reference, oob: true });
}
if (!call.navigate && !call.restore) return fragment;
try {
return await view.compose(fragment, { mark: await getBookmark(),
reference: reference });
return await view.compose(fragment);
} catch (err) {
console.error('compose failed: ' + err.message);
return fragment;
@@ -245,76 +156,6 @@ router.get('/', (call) => servePage('./', '', call));
router.get('/:page', (call) =>
servePage('./' + call.params.page, call.params.page, call));
router.get('/api/reference', async (call) => {
const params = call.url.searchParams;
const page = call.currentUrl();
const moved = params.has('at') ? params.get('at') : null;
const reference = await referenceOf(
bookOf(page), moved === null ? (page && page.hash.slice(1)) : moved);
if (moved === null) {
return view.render('reference.njk', { reference: reference, settled: true });
}
if (!reference) return new Response('', { status: 204 });
const target = reference.verse || reference.chapter;
return new Response(await view.render('verses.njk', { reference: reference }), {
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-store',
'HX-Reswap': 'innerHTML settle:0 show:#' + target + ':top',
'HX-Replace-Url': page.pathname + page.search + '#' + target
}
});
});
router.get('/api/verse', async (call) => {
const verse = await verseOf(call.currentUrl(), call.url.searchParams.get('at'));
if (!verse) return new Response('', { status: 204 });
return view.render('verse.njk', { verse: verse });
});
router.post('/api/bookmark', async (call) => {
try {
const form = await call.form();
const ref = String(form.get('ref') || '').trim();
const href = String(form.get('href') || '').trim();
if (!ref || !href) return new Response('', { status: 400 });
await withStore('readwrite',
(s) => s.put({ ref: ref, href: href, savedAt: Date.now() }, BOOKMARK_KEY));
return new Response(await bookmarkFragment('bookmarked'), {
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-store',
'HX-Trigger-After-Settle': 'verse-done'
}
});
} catch (err) {
return bookmarkFragment('bookmark-refused');
}
});
const TOASTS = {
'copied': { text: 'Link copied.' },
'copy-refused': { text: 'Copy the link above: this browser would not '
+ 'take it.', bad: true },
'bookmarked': { text: 'Bookmark set.' },
'bookmark-refused': { text: 'Could not save the bookmark: storage '
+ 'unavailable.', bad: true }
};
function toastFragment(say, oob) {
const toast = TOASTS[say];
return view.render('toast.njk', { text: toast.text, bad: toast.bad, oob: oob });
}
router.get('/api/toast', (call) => {
const say = call.url.searchParams.get('say');
if (!TOASTS[say]) return new Response('', { status: 204 });
return toastFragment(say, false);
});
let filling = null;
let complete = false;
@@ -351,43 +192,3 @@ async function fillLibrary() {
}
}
}
const DB_NAME = 'bible';
const DB_STORE = 'kv';
const BOOKMARK_KEY = 'bookmark';
const DEFAULT_BOOKMARK = { ref: 'GEN 1:1', href: 'GEN#v1_1' };
function openDb() {
return new Promise((resolve, reject) => {
const req = indexedDB.open(DB_NAME, 1);
req.onupgradeneeded = () => req.result.createObjectStore(DB_STORE);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
async function withStore(mode, fn) {
const db = await openDb();
try {
return await new Promise((resolve, reject) => {
const tx = db.transaction(DB_STORE, mode);
const req = fn(tx.objectStore(DB_STORE));
tx.oncomplete = () => resolve(req ? req.result : undefined);
tx.onabort = tx.onerror = () => reject(tx.error);
});
} finally {
db.close();
}
}
function getBookmark() {
return withStore('readonly', (s) => s.get(BOOKMARK_KEY))
.catch(() => null)
.then((mark) => mark || DEFAULT_BOOKMARK);
}
async function bookmarkFragment(say) {
return await view.render('bookmark.njk', { mark: await getBookmark() })
+ '\n' + await toastFragment(say, true);
}