Toast
This commit is contained in:
1 parent
37ac9c2757
commit
ce73c76126
6 files changed
+158
-38
No files matched your search
+54
-13
@@ -13,6 +13,7 @@
|
||||
// POST /api/bookmark set the bookmark, and return the slot
|
||||
// GET /api/reference the navbar's chapter and verse picker
|
||||
// GET /api/verse the dialog for one verse
|
||||
// GET /api/toast one confirmation, by name
|
||||
//
|
||||
// Anything with a dot in its last segment is a file, not a route, and is
|
||||
// served from the cache or the network without going near the router. Pages
|
||||
@@ -436,8 +437,8 @@ async function servePage(path, code, call) {
|
||||
if (!call.navigate && !call.restore) {
|
||||
// The picker is in the navbar and every boosted link targets #content, so
|
||||
// a swap never reaches it. It rides along out of band instead, the way a
|
||||
// bookmark write carries the modal's status line: one response, both
|
||||
// halves of the page. Sent even when it is null -- that is the hidden
|
||||
// bookmark write carries its toast: one response, both halves of the
|
||||
// page. Sent even when it is null -- that is the hidden
|
||||
// state, and arriving at the book list has to put the picker away.
|
||||
return fragment + '\n'
|
||||
+ await view.render('reference.njk', { reference: reference, oob: true });
|
||||
@@ -578,20 +579,60 @@ router.post('/api/bookmark', async (call) => {
|
||||
await withStore('readwrite',
|
||||
(s) => s.put({ ref: ref, href: href, savedAt: Date.now() }, BOOKMARK_KEY));
|
||||
// Only on the way out of a write that committed. base.njk closes the
|
||||
// dialog on this, and a dialog that closed itself on a failure would take
|
||||
// the line explaining the failure with it.
|
||||
return new Response(await bookmarkFragment('Bookmark set.'), {
|
||||
// dialog on this, and there is nothing left in the dialog worth keeping
|
||||
// open for: the confirmation rides out of band into the toast container,
|
||||
// which is not inside it.
|
||||
return new Response(await bookmarkFragment('bookmarked'), {
|
||||
headers: {
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
'Cache-Control': 'no-store',
|
||||
'HX-Trigger-After-Settle': 'bookmark-saved'
|
||||
'HX-Trigger-After-Settle': 'verse-done'
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
return bookmarkFragment('Could not save: storage unavailable.');
|
||||
// No trigger, so the dialog stays where it is. The toast says what
|
||||
// happened whether or not it does, but a dialog that closed on a failed
|
||||
// write would look exactly like one that closed on a successful one.
|
||||
return bookmarkFragment('bookmark-refused');
|
||||
}
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ toasts
|
||||
|
||||
// Everything the app says back to a reader who has just done something. Named
|
||||
// rather than passed as text: the page asks for 'copied', not for a sentence,
|
||||
// so the wording is here with the rest of the app's words and an attribute in
|
||||
// a template never has to spell one out. bad is the ones that did not happen.
|
||||
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 }
|
||||
};
|
||||
|
||||
// oob is how it travels: on its own to the toast container, which is what the
|
||||
// copy button asks for, or out of band on an answer that is already going
|
||||
// somewhere else, which is what a bookmark write sends.
|
||||
function toastFragment(say, oob) {
|
||||
const toast = TOASTS[say];
|
||||
return view.render('toast.njk', { text: toast.text, bad: toast.bad, oob: oob });
|
||||
}
|
||||
|
||||
// One toast, by name.
|
||||
//
|
||||
// say=copied which one, from the table above.
|
||||
//
|
||||
// 204 for a name that is not in it, on the same reasoning api/verse 204s for a
|
||||
// verse that is not there: htmx swaps nothing, so a typo appends nothing,
|
||||
// rather than an empty toast sliding up to say nothing at all.
|
||||
router.get('/api/toast', (call) => {
|
||||
const say = call.url.searchParams.get('say');
|
||||
if (!TOASTS[say]) return new Response('', { status: 204 });
|
||||
return toastFragment(say, false);
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------- library
|
||||
|
||||
// The background download of the whole corpus.
|
||||
@@ -693,13 +734,13 @@ function getBookmark() {
|
||||
.then((mark) => mark || DEFAULT_BOOKMARK);
|
||||
}
|
||||
|
||||
async function bookmarkFragment(status) {
|
||||
async function bookmarkFragment(say) {
|
||||
let html = await view.render('bookmark.njk', { mark: await getBookmark() });
|
||||
if (status) {
|
||||
// Out of band, so one response updates both the navbar slot and the
|
||||
// modal's status line without the modal having to ask separately. Same
|
||||
// template the shell uses, so the two cannot drift apart.
|
||||
html += '\n' + await view.render('status.njk', { text: status, oob: true });
|
||||
if (say) {
|
||||
// Out of band, so one response updates the navbar slot and raises the
|
||||
// toast without the page having to ask for the second one. Same template
|
||||
// and same table api/toast answers from, so the two cannot drift apart.
|
||||
html += '\n' + await toastFragment(say, true);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
Reference in new issue
Block a user