script cache invalidation and verse menu
This commit is contained in:
+128
-14
@@ -1,8 +1,9 @@
|
||||
// Tap a verse to open a small menu: favorite it, look up the 1828 Webster
|
||||
// definition of its words, or see its cross references. Everything saved
|
||||
// lives in this browser's localStorage (read back on the /saved page).
|
||||
// Every verse card gets an actions button (and the card itself is tappable)
|
||||
// that opens a small menu: favorite the verse, write a note on it, look up the
|
||||
// 1828 Webster definition of its words, or see its cross references.
|
||||
// Everything saved lives in this browser's localStorage (see the /saved page).
|
||||
(function () {
|
||||
var KEYS = { fav: "kjv.favorites", defs: "kjv.definitions", xrefs: "kjv.crossrefs" };
|
||||
var KEYS = { fav: "kjv.favorites", defs: "kjv.definitions", xrefs: "kjv.crossrefs", notes: "kjv.notes" };
|
||||
var open = null; // { box, menu }
|
||||
|
||||
function load(key) {
|
||||
@@ -71,12 +72,72 @@
|
||||
return "/find?kw=" + encodeURIComponent(ref) + "&view=rich";
|
||||
}
|
||||
|
||||
function markFavorites(root) {
|
||||
function noteFor(ref) {
|
||||
var list = load(KEYS.notes);
|
||||
var at = indexOf(list, function (n) { return n.ref === ref; });
|
||||
return at >= 0 ? list[at].note : "";
|
||||
}
|
||||
|
||||
// Shows the saved note (if any) under the verse text.
|
||||
function renderNote(box) {
|
||||
var old = box.querySelector(".verse-note");
|
||||
if (old) {
|
||||
old.remove();
|
||||
}
|
||||
var info = verseInfo(box);
|
||||
var note = noteFor(info.ref);
|
||||
box.classList.toggle("has-note", !!note);
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
var text = box.querySelector(".verse-text");
|
||||
var node = el("div", "verse-note", note);
|
||||
node.setAttribute("title", "Your note");
|
||||
if (text) {
|
||||
text.after(node);
|
||||
} else {
|
||||
box.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
function markSaved(root) {
|
||||
var favs = load(KEYS.fav);
|
||||
var boxes = (root || document).querySelectorAll(".verse-container");
|
||||
for (var i = 0; i < boxes.length; i++) {
|
||||
var ref = verseInfo(boxes[i]).ref;
|
||||
boxes[i].classList.toggle("is-fav", indexOf(favs, function (f) { return f.ref === ref; }) >= 0);
|
||||
renderNote(boxes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// A visible way in to the menu on every verse, so nobody has to guess that the card is tappable.
|
||||
function addMenuButtons() {
|
||||
var boxes = document.querySelectorAll(".verse-container");
|
||||
for (var i = 0; i < boxes.length; i++) {
|
||||
var header = boxes[i].querySelector(".verse-reference");
|
||||
if (!header || header.querySelector(".vm-more")) {
|
||||
continue;
|
||||
}
|
||||
var button = el("button", "vm-more", "\u22ef");
|
||||
button.type = "button";
|
||||
button.setAttribute("aria-label", "Verse actions: notes, favorite, define, cross references");
|
||||
button.setAttribute("title", "Notes, favorite, define, cross references");
|
||||
button.addEventListener("click", (function (box) {
|
||||
return function () { toggleMenu(box); };
|
||||
})(boxes[i]));
|
||||
header.appendChild(button);
|
||||
}
|
||||
var count = document.querySelector(".results-count");
|
||||
if (count && boxes.length && !document.querySelector(".verse-hint")) {
|
||||
count.after(el("div", "verse-hint", "Tap a verse, or its \u22ef button, to add a note, favorite it, define words or see cross references."));
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMenu(box) {
|
||||
if (open && open.box === box) {
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu(box);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +242,49 @@
|
||||
.catch(function () { panel.textContent = "Could not load cross references."; });
|
||||
}
|
||||
|
||||
// Notes save as you type; clearing the box deletes the note.
|
||||
function showNote(info, panel, box, onChange) {
|
||||
panel.textContent = "";
|
||||
var area = el("textarea", "vm-note-input");
|
||||
area.rows = 4;
|
||||
area.placeholder = "Your note on " + info.ref + "...";
|
||||
area.setAttribute("aria-label", "Note for " + info.ref);
|
||||
area.value = noteFor(info.ref);
|
||||
var status = el("div", "vm-hint", "Saved in this browser as you type.");
|
||||
var timer = null;
|
||||
function commit() {
|
||||
var value = area.value.trim();
|
||||
var list = load(KEYS.notes);
|
||||
var at = indexOf(list, function (n) { return n.ref === info.ref; });
|
||||
if (!value) {
|
||||
if (at >= 0) {
|
||||
list.splice(at, 1);
|
||||
}
|
||||
} else if (at >= 0) {
|
||||
list[at].note = value;
|
||||
list[at].t = Date.now();
|
||||
} else {
|
||||
list.unshift({ ref: info.ref, text: info.text, note: value, t: Date.now() });
|
||||
}
|
||||
save(KEYS.notes, list);
|
||||
renderNote(box);
|
||||
onChange();
|
||||
status.textContent = value ? "Saved." : "Note removed.";
|
||||
}
|
||||
area.addEventListener("input", function () {
|
||||
status.textContent = "Saving\u2026";
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(commit, 400);
|
||||
});
|
||||
area.addEventListener("blur", function () {
|
||||
clearTimeout(timer);
|
||||
commit();
|
||||
});
|
||||
panel.appendChild(area);
|
||||
panel.appendChild(status);
|
||||
area.focus();
|
||||
}
|
||||
|
||||
function openMenu(box) {
|
||||
closeMenu();
|
||||
var info = verseInfo(box);
|
||||
@@ -206,6 +310,19 @@
|
||||
paintFav();
|
||||
});
|
||||
|
||||
var noteBtn = el("button", "vm-btn");
|
||||
noteBtn.type = "button";
|
||||
function paintNote() {
|
||||
var has = !!noteFor(info.ref);
|
||||
noteBtn.textContent = has ? "\u270e Edit note" : "\u270e Note";
|
||||
noteBtn.classList.toggle("saved", has);
|
||||
}
|
||||
paintNote();
|
||||
noteBtn.addEventListener("click", function () {
|
||||
panel.hidden = false;
|
||||
showNote(info, panel, box, paintNote);
|
||||
});
|
||||
|
||||
var define = el("button", "vm-btn", "Define words");
|
||||
define.type = "button";
|
||||
define.addEventListener("click", function () {
|
||||
@@ -225,7 +342,7 @@
|
||||
close.setAttribute("aria-label", "Close menu");
|
||||
close.addEventListener("click", closeMenu);
|
||||
|
||||
[fav, define, xref, close].forEach(function (b) { actions.appendChild(b); });
|
||||
[fav, noteBtn, define, xref, close].forEach(function (b) { actions.appendChild(b); });
|
||||
menu.appendChild(actions);
|
||||
menu.appendChild(panel);
|
||||
box.appendChild(menu);
|
||||
@@ -241,25 +358,22 @@
|
||||
return;
|
||||
}
|
||||
// links, buttons and the open menu keep their own behaviour; so does selecting text
|
||||
if (event.target.closest("a, button, input, .verse-menu")) {
|
||||
if (event.target.closest("a, button, input, textarea, .verse-menu")) {
|
||||
return;
|
||||
}
|
||||
var selection = window.getSelection && window.getSelection().toString();
|
||||
if (selection) {
|
||||
return;
|
||||
}
|
||||
if (open && open.box === box) {
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu(box);
|
||||
}
|
||||
toggleMenu(box);
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key === "Escape") {
|
||||
if (event.key === "Escape" && !(event.target && event.target.tagName === "TEXTAREA")) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
markFavorites();
|
||||
markSaved();
|
||||
addMenuButtons();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user