diff --git a/assets/sw.js b/assets/sw.js index 1060f65..0903383 100644 --- a/assets/sw.js +++ b/assets/sw.js @@ -311,9 +311,19 @@ function verseLabels(chapters, number) { // no verse anchors to offer. // // anchor says which chapter to show the verses of, and which verse to mark -// selected. Null starts at 1:1, which is where every book starts: this says -// where you are going, not where you are, and the address bar is the only -// record of the latter. +// selected. Anything this cannot read -- nothing at all, an empty select, a +// footnote id, a chapter past the end of the book -- is chapter 1, and chapter +// 1 is filled in as if it had been picked. There is no state where a chapter +// is only sort of chosen: a select showing an option is a select that chose +// it, and the verses beside it have to be that chapter's. +// +// Every book that gets a picker has verses in its first chapter -- the two +// that do not, the front matter and the glossary, are turned away above -- so +// falling back to 1 always fills the verse list rather than emptying it. +// +// The verse is left unselected when the anchor names only a chapter. That is +// not the same as unfilled: the options are chapter 1's, and the select shows +// the first of them. It says where you are going, not where you are. function referenceFor(map, code, anchor) { const chapters = map && Object.prototype.hasOwnProperty.call(map, code) ? map[code] : null; @@ -328,12 +338,15 @@ function referenceFor(map, code, anchor) { const at = parseAnchor(anchor) || { chapter: 1, verse: null }; const number = at.chapter >= 1 && at.chapter <= chapters.length ? at.chapter : 1; + // Only when the anchor got as far as naming this chapter. A fallback to 1 + // has not chosen a verse, it has just run out of anchor. + 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: at.verse, + verse: verse, verses: verseLabels(chapters, number) .map((label) => ({ id: 'v' + number + '_' + label, label: label })) }; @@ -415,19 +428,30 @@ router.get('/api/bookmark', () => bookmarkFragment('')); // The picker, and the two moves it makes. // -// no parameters the whole picker, for whichever book is on screen. Asked -// for by the picker itself, after a history restore -- which -// swaps the history element straight in and processes no -// out-of-band elements -- and when a worker first appears -// under a page boot.html composed. +// no parameters the whole picker, standing where the anchor in the address +// bar puts it. Asked for by the picker itself: on load, +// after a history restore -- which swaps the history element +// straight in and processes no out-of-band elements -- and +// when a worker first appears under a page boot.html +// composed. // chapter=ch3 the verses of that chapter, and land on the chapter. // verse=v3_16 the same verses, that one selected, and land on the verse. // -// The book is not in the URL, deliberately: it comes from HX-Current-URL, -// which is the address the page is showing now rather than the address it was -// showing when the picker was last rendered. The picker sits in the navbar and -// outlives the page under it, so anything baked into its attributes is the one -// thing here that can go stale. +// Neither the book nor the anchor is in the URL, deliberately. Both come from +// HX-Current-URL, which htmx sets from location.href on every request it +// makes: the whole address, fragment included. +// +// For the book, because the picker sits in the navbar and outlives the page +// under it -- an address baked into its attributes is the one thing here that +// can go stale. For the anchor, because this is the only way a fragment +// reaches this file at all. A browser opening /GEN#v3_16 asks for /GEN and +// keeps the rest to itself, so the shell is composed without it and the picker +// is rendered at 1:1 like any other. The page then asks this route for itself, +// and that request is the one carrying the anchor. +// +// Which also puts the picker where it belongs after a back or forward, and +// under a page boot.html composed once a worker turns up, for nothing extra: +// those requests carry the address bar too. // // What the response headers are doing is the whole of what app.js used to do // on a change event, and htmx has a name for each part of it: @@ -442,11 +466,33 @@ router.get('/api/bookmark', () => bookmarkFragment('')); // history entry per option spun past. router.get('/api/reference', async (call) => { const params = call.url.searchParams; - const anchor = params.get('verse') || params.get('chapter'); const page = call.currentUrl(); - const reference = await referenceOf(bookOf(page), anchor); + // Which select the reader moved, if either did -- by whether the parameter + // is there, not by whether it says anything. A select posts an empty value + // when it has no options, and reading that as "no select moved" answers it + // with a whole picker: htmx swaps a