script cache invalidation and verse menu

This commit is contained in:
2026-10-19 15:32:06 -07:00
parent 317f5bf746
commit 9d95ae3ec2
7 changed files with 317 additions and 58 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Books - KJV API</title>
<link rel="stylesheet" href="/styles/page.css?v={{ v }}">
<link rel="stylesheet" href="{{ asset_url('/styles/page.css') }}">
</head>
<body>
<header>
+56 -1
View File
@@ -14,6 +14,9 @@
.item p { margin: 0.25rem 0; }
.item button { min-height: 36px; padding: 0.2rem 0.9rem; font-size: 0.85rem;
background-color: var(--color-bg-tertiary); color: var(--color-text-primary); border: 1px solid var(--color-border); }
.item .mynote { white-space: pre-wrap; border-left: 3px solid #d9a93b; padding-left: 0.6rem; color: var(--color-text-secondary); }
.file-btn { display: inline-block; padding: 0.6rem 1.2rem; border: 1px solid var(--color-border); border-radius: var(--border-radius-sm);
background-color: var(--color-bg-tertiary); font-weight: 600; }
.empty { color: var(--color-text-muted); }
.note { font-size: 0.9rem; color: var(--color-text-muted); }
</style>
@@ -26,6 +29,8 @@
<h2>Saved</h2>
<p class="note">Saved in this browser only. Clearing site data or using another device starts fresh.</p>
<h3>Notes</h3>
<div id="notes"></div>
<h3>Favorite verses</h3>
<div id="fav"></div>
<h3>Definitions</h3>
@@ -33,8 +38,17 @@
<h3>Cross references</h3>
<div id="xrefs"></div>
<h3>Backup</h3>
<p class="note">Browser storage can be cleared without warning. Download a copy of your notes and saved items, and restore it here or on another device.</p>
<p>
<button type="button" id="backup">Download backup</button>
<label class="choice"><input type="file" id="restore" accept="application/json,.json" hidden>
<span class="file-btn">Restore from backup</span></label>
</p>
<p class="note" id="backup-status" role="status"></p>
<script>
var KEYS = { fav: 'kjv.favorites', defs: 'kjv.definitions', xrefs: 'kjv.crossrefs' };
var KEYS = { notes: 'kjv.notes', fav: 'kjv.favorites', defs: 'kjv.definitions', xrefs: 'kjv.crossrefs' };
function load(key) {
try { var v = JSON.parse(localStorage.getItem(key)); return Array.isArray(v) ? v : []; }
@@ -76,6 +90,11 @@ function refLink(ref) {
}
function draw() {
render(KEYS.notes, 'notes', 'No notes yet. Tap a verse (or its \u22ef button) and choose Note.', function (box, n) {
box.appendChild(refLink(n.ref));
if (n.text) { box.appendChild(el('div', 'text', n.text)); }
box.appendChild(el('p', 'mynote', n.note));
});
render(KEYS.fav, 'fav', 'No favorites yet. Tap a verse and choose Favorite.', function (box, f) {
box.appendChild(refLink(f.ref));
box.appendChild(el('div', 'text', f.text));
@@ -92,6 +111,42 @@ function draw() {
});
}
draw();
document.getElementById('backup').addEventListener('click', function () {
var data = { kjv_backup: 1, saved: new Date().toISOString() };
Object.keys(KEYS).forEach(function (name) { data[name] = load(KEYS[name]); });
var blob = new Blob([JSON.stringify(data, null, 1)], { type: 'application/json' });
var link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'kjv-saved-' + new Date().toISOString().slice(0, 10) + '.json';
document.body.appendChild(link);
link.click();
link.remove();
setTimeout(function () { URL.revokeObjectURL(link.href); }, 1000);
});
document.getElementById('restore').addEventListener('change', function (event) {
var file = event.target.files[0];
var status = document.getElementById('backup-status');
if (!file) { return; }
var reader = new FileReader();
reader.onload = function () {
try {
var data = JSON.parse(reader.result);
if (!data || data.kjv_backup !== 1) { throw new Error('not a backup'); }
if (!confirm('Replace the notes and saved items in this browser with this backup?')) { return; }
Object.keys(KEYS).forEach(function (name) {
if (Array.isArray(data[name])) { localStorage.setItem(KEYS[name], JSON.stringify(data[name])); }
});
draw();
status.textContent = 'Backup restored.';
} catch (e) {
status.textContent = 'That file is not a valid backup.';
}
};
reader.readAsText(file);
event.target.value = '';
});
</script>
</body>
</html>
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sequence Report - KJV API</title>
<link rel="stylesheet" href="/styles/page.css?v={{ v }}">
<link rel="stylesheet" href="{{ asset_url('/styles/page.css') }}">
</head>
<body>
<header>
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shortcode Report - KJV API</title>
<link rel="stylesheet" href="/styles/page.css?v={{ v }}">
<link rel="stylesheet" href="{{ asset_url('/styles/page.css') }}">
</head>
<body>
<header>