mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
On-demand offline caching with all stories
Changes: - Caching now only starts when user clicks "Download for Offline Use" on /offline page - Added all 186 Bible stories (including kids mode) to cache list - Added progress bar and status indicators on /offline page - Bumped cache version to v4 - Total cacheable pages: ~1,500 (1189 chapters + 186 stories + 66 books + resources) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+214
-14
@@ -1,5 +1,5 @@
|
||||
// KJV Study Service Worker - Offline Bible Access
|
||||
const CACHE_VERSION = 'v3';
|
||||
const CACHE_VERSION = 'v4';
|
||||
const STATIC_CACHE = 'kjvstudy-static-' + CACHE_VERSION;
|
||||
const BIBLE_CACHE = 'kjvstudy-bible-' + CACHE_VERSION;
|
||||
const PAGE_CACHE = 'kjvstudy-pages-' + CACHE_VERSION;
|
||||
@@ -9,8 +9,6 @@ const STATIC_ASSETS = [
|
||||
'/',
|
||||
'/offline',
|
||||
'/static/tufte.css',
|
||||
'/static/style.css',
|
||||
'/static/app.js',
|
||||
'/static/manifest.json',
|
||||
'/static/verses-1769.json',
|
||||
'/books'
|
||||
@@ -1209,6 +1207,196 @@ const CHAPTER_PAGES = [
|
||||
"/book/Revelation/chapter/22",
|
||||
];
|
||||
|
||||
// All Bible story pages (186 stories - includes kids mode)
|
||||
const STORY_PAGES = [
|
||||
"/stories/god-creates-the-world",
|
||||
"/stories/adam-and-eve-in-the-garden",
|
||||
"/stories/the-fall-of-man",
|
||||
"/stories/cain-and-abel",
|
||||
"/stories/noah-and-the-ark",
|
||||
"/stories/the-tower-of-babel",
|
||||
"/stories/the-call-of-abraham",
|
||||
"/stories/abraham-and-lot-separate",
|
||||
"/stories/the-birth-of-isaac",
|
||||
"/stories/abraham-offers-isaac",
|
||||
"/stories/jacob-and-esau",
|
||||
"/stories/jacobs-ladder",
|
||||
"/stories/jacob-wrestles-with-god",
|
||||
"/stories/josephs-coat-and-dreams",
|
||||
"/stories/joseph-sold-into-slavery",
|
||||
"/stories/joseph-reveals-himself",
|
||||
"/stories/jobs-righteousness-and-prosperity",
|
||||
"/stories/satans-challenge-and-jobs-first-test",
|
||||
"/stories/jobs-physical-affliction",
|
||||
"/stories/job-cries-out-and-friends-accuse",
|
||||
"/stories/elihu-speaks-words-of-wisdom",
|
||||
"/stories/god-speaks-from-the-whirlwind",
|
||||
"/stories/jobs-restoration-and-blessing",
|
||||
"/stories/baby-moses-in-the-basket",
|
||||
"/stories/the-burning-bush",
|
||||
"/stories/the-ten-plagues",
|
||||
"/stories/the-passover",
|
||||
"/stories/crossing-the-red-sea",
|
||||
"/stories/the-ten-commandments",
|
||||
"/stories/the-golden-calf",
|
||||
"/stories/rahab-and-the-spies",
|
||||
"/stories/the-walls-of-jericho",
|
||||
"/stories/gideon-and-the-fleece",
|
||||
"/stories/gideons-three-hundred",
|
||||
"/stories/samson-the-strong-man",
|
||||
"/stories/samson-and-delilah",
|
||||
"/stories/samsons-final-victory",
|
||||
"/stories/ruth-and-naomi",
|
||||
"/stories/ruth-and-boaz",
|
||||
"/stories/samsons-birth-announced",
|
||||
"/stories/samsons-riddle-and-wedding",
|
||||
"/stories/samsons-exploits",
|
||||
"/stories/samson-and-delilah",
|
||||
"/stories/samsons-final-victory",
|
||||
"/stories/naomis-loss-and-return",
|
||||
"/stories/ruth-meets-boaz",
|
||||
"/stories/ruth-at-the-threshing-floor",
|
||||
"/stories/boaz-redeems-ruth",
|
||||
"/stories/hannahs-prayer-for-a-son",
|
||||
"/stories/the-boy-samuel-hears-god",
|
||||
"/stories/the-ark-is-captured",
|
||||
"/stories/israel-demands-a-king",
|
||||
"/stories/samuel-anoints-saul",
|
||||
"/stories/sauls-disobedience",
|
||||
"/stories/samuel-anoints-david",
|
||||
"/stories/samuels-death",
|
||||
"/stories/hannahs-prayer",
|
||||
"/stories/god-calls-samuel",
|
||||
"/stories/david-and-goliath",
|
||||
"/stories/david-and-jonathan",
|
||||
"/stories/solomons-wisdom",
|
||||
"/stories/elijah-and-the-prophets-of-baal",
|
||||
"/stories/elijah-and-the-still-small-voice",
|
||||
"/stories/elijah-and-the-ravens",
|
||||
"/stories/elijah-and-the-widows-oil",
|
||||
"/stories/fire-from-heaven",
|
||||
"/stories/elijah-taken-to-heaven",
|
||||
"/stories/elisha-and-the-widows-oil",
|
||||
"/stories/naaman-is-healed",
|
||||
"/stories/jonah-and-the-great-fish",
|
||||
"/stories/jonah-and-nineveh",
|
||||
"/stories/the-fiery-furnace",
|
||||
"/stories/daniel-in-the-lions-den",
|
||||
"/stories/isaiahs-vision",
|
||||
"/stories/ezekiel-valley-of-dry-bones",
|
||||
"/stories/jonah-runs-from-god",
|
||||
"/stories/jonah-prays-from-the-fish",
|
||||
"/stories/nineveh-repents",
|
||||
"/stories/jonahs-anger-and-gods-compassion",
|
||||
"/stories/daniel-refuses-kings-food",
|
||||
"/stories/nebuchadnezzars-dream",
|
||||
"/stories/fiery-furnace",
|
||||
"/stories/writing-on-the-wall",
|
||||
"/stories/daniel-in-lions-den",
|
||||
"/stories/daniels-visions",
|
||||
"/stories/esther-becomes-queen",
|
||||
"/stories/hamans-plot",
|
||||
"/stories/for-such-a-time-as-this",
|
||||
"/stories/esthers-brave-plan",
|
||||
"/stories/the-jews-are-delivered",
|
||||
"/stories/nehemiahs-burden",
|
||||
"/stories/permission-to-rebuild",
|
||||
"/stories/rebuilding-despite-opposition",
|
||||
"/stories/justice-and-generosity",
|
||||
"/stories/wall-completed",
|
||||
"/stories/ezra-reads-the-law",
|
||||
"/stories/renewal-and-covenant",
|
||||
"/stories/angel-visits-mary",
|
||||
"/stories/no-room-at-the-inn",
|
||||
"/stories/shepherds-and-angels",
|
||||
"/stories/the-wise-men",
|
||||
"/stories/escape-to-egypt",
|
||||
"/stories/jesus-baptized",
|
||||
"/stories/jesus-tempted",
|
||||
"/stories/water-into-wine",
|
||||
"/stories/heals-paralyzed-man",
|
||||
"/stories/calms-the-storm",
|
||||
"/stories/feeds-five-thousand",
|
||||
"/stories/walks-on-water",
|
||||
"/stories/heals-blind-man",
|
||||
"/stories/raises-lazarus",
|
||||
"/stories/heals-ten-lepers",
|
||||
"/stories/jesus-and-children",
|
||||
"/stories/heals-on-sabbath",
|
||||
"/stories/forgives-sinful-woman",
|
||||
"/stories/heals-jairus-daughter",
|
||||
"/stories/transfiguration",
|
||||
"/stories/man-with-many-spirits",
|
||||
"/stories/woman-at-the-well",
|
||||
"/stories/jesus-and-zacchaeus",
|
||||
"/stories/woman-touched-his-cloak",
|
||||
"/stories/centurions-servant",
|
||||
"/stories/mary-and-martha",
|
||||
"/stories/raises-widows-son",
|
||||
"/stories/jesus-calms-the-storm",
|
||||
"/stories/jesus-feeds-five-thousand",
|
||||
"/stories/jesus-walks-on-water",
|
||||
"/stories/jesus-heals-the-blind-man",
|
||||
"/stories/jesus-raises-lazarus",
|
||||
"/stories/jesus-heals-ten-lepers",
|
||||
"/stories/jesus-heals-the-paralyzed-man",
|
||||
"/stories/the-good-samaritan",
|
||||
"/stories/the-prodigal-son",
|
||||
"/stories/the-lost-sheep",
|
||||
"/stories/the-sower",
|
||||
"/stories/the-talents",
|
||||
"/stories/the-pharisee-and-tax-collector",
|
||||
"/stories/the-rich-fool",
|
||||
"/stories/the-unmerciful-servant",
|
||||
"/stories/the-lost-coin",
|
||||
"/stories/the-ten-virgins",
|
||||
"/stories/the-mustard-seed",
|
||||
"/stories/the-wheat-and-weeds",
|
||||
"/stories/hidden-treasure-and-pearl",
|
||||
"/stories/rich-man-and-lazarus",
|
||||
"/stories/wise-and-foolish-builders",
|
||||
"/stories/workers-in-the-vineyard",
|
||||
"/stories/the-great-banquet",
|
||||
"/stories/the-persistent-widow",
|
||||
"/stories/the-sheep-and-goats",
|
||||
"/stories/triumphal-entry",
|
||||
"/stories/the-last-supper",
|
||||
"/stories/garden-of-gethsemane",
|
||||
"/stories/peter-denies-jesus",
|
||||
"/stories/the-crucifixion",
|
||||
"/stories/the-resurrection",
|
||||
"/stories/jesus-appears-to-thomas",
|
||||
"/stories/road-to-emmaus",
|
||||
"/stories/jesus-restores-peter",
|
||||
"/stories/great-commission",
|
||||
"/stories/the-ascension",
|
||||
"/stories/day-of-pentecost",
|
||||
"/stories/peter-john-heal-lame-man",
|
||||
"/stories/conversion-of-saul",
|
||||
"/stories/peters-vision",
|
||||
"/stories/paul-silas-in-prison",
|
||||
"/stories/pauls-shipwreck",
|
||||
"/stories/from-persecutor-to-preacher",
|
||||
"/stories/first-missionary-journey",
|
||||
"/stories/prison-songs-at-midnight",
|
||||
"/stories/teaching-in-athens",
|
||||
"/stories/shipwreck-and-malta",
|
||||
"/stories/johns-vision-of-christ",
|
||||
"/stories/letters-to-seven-churches",
|
||||
"/stories/throne-room-of-heaven",
|
||||
"/stories/seven-seals",
|
||||
"/stories/two-witnesses",
|
||||
"/stories/woman-and-dragon",
|
||||
"/stories/beast-and-mark",
|
||||
"/stories/fall-of-babylon",
|
||||
"/stories/return-of-christ",
|
||||
"/stories/new-heaven-new-earth",
|
||||
"/stories/jobs-faith-through-suffering",
|
||||
"/stories/ruths-loyalty-and-love",
|
||||
"/stories/esther-saves-her-people",
|
||||
"/stories/shadrach-meshach-and-abednego",
|
||||
];
|
||||
|
||||
// Resource and reference pages
|
||||
const RESOURCE_PAGES = [
|
||||
"/anthropology",
|
||||
@@ -1250,6 +1438,7 @@ const RESOURCE_PAGES = [
|
||||
"/soteriology",
|
||||
"/spirits-and-demons",
|
||||
"/stories",
|
||||
"/stories/kids",
|
||||
"/strongs",
|
||||
"/strongs/greek",
|
||||
"/strongs/hebrew",
|
||||
@@ -1337,9 +1526,9 @@ const BOOK_PAGES = [
|
||||
];
|
||||
|
||||
// Combine all pages for background caching
|
||||
const PAGES_TO_CACHE = [...CHAPTER_PAGES, ...RESOURCE_PAGES, ...BOOK_PAGES];
|
||||
const PAGES_TO_CACHE = [...CHAPTER_PAGES, ...STORY_PAGES, ...RESOURCE_PAGES, ...BOOK_PAGES];
|
||||
|
||||
// Install event - cache static assets
|
||||
// Install event - cache static assets only (NOT pages)
|
||||
self.addEventListener('install', (event) => {
|
||||
console.log('[SW] Installing service worker...');
|
||||
event.waitUntil(
|
||||
@@ -1358,7 +1547,7 @@ self.addEventListener('install', (event) => {
|
||||
);
|
||||
});
|
||||
|
||||
// Activate event - clean up old caches and start background caching
|
||||
// Activate event - clean up old caches (NO automatic page caching)
|
||||
self.addEventListener('activate', (event) => {
|
||||
console.log('[SW] Activating service worker...');
|
||||
event.waitUntil(
|
||||
@@ -1381,20 +1570,18 @@ self.addEventListener('activate', (event) => {
|
||||
console.log('[SW] Service worker activated');
|
||||
return self.clients.claim();
|
||||
})
|
||||
.then(() => {
|
||||
// Start background pre-caching after activation
|
||||
startBackgroundCaching();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Background pre-caching - cache all pages gradually
|
||||
// Background pre-caching - only triggered when user requests it
|
||||
let cachingInProgress = false;
|
||||
let cachedCount = 0;
|
||||
let totalToCache = 0;
|
||||
|
||||
async function startBackgroundCaching() {
|
||||
if (cachingInProgress) return;
|
||||
cachingInProgress = true;
|
||||
cachedCount = 0;
|
||||
|
||||
console.log('[SW] Starting background pre-cache of', PAGES_TO_CACHE.length, 'pages...');
|
||||
|
||||
@@ -1409,7 +1596,14 @@ async function startBackgroundCaching() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[SW] Need to cache', uncachedPages.length, 'pages');
|
||||
totalToCache = uncachedPages.length;
|
||||
console.log('[SW] Need to cache', totalToCache, 'pages');
|
||||
|
||||
if (totalToCache === 0) {
|
||||
notifyClients({ type: 'CACHE_COMPLETE', total: PAGES_TO_CACHE.length });
|
||||
cachingInProgress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache pages in batches with delay to avoid overwhelming the server
|
||||
const BATCH_SIZE = 10;
|
||||
@@ -1426,11 +1620,11 @@ async function startBackgroundCaching() {
|
||||
await cache.put(url, response);
|
||||
cachedCount++;
|
||||
// Notify clients of progress every 10 pages
|
||||
if (cachedCount % 10 === 0) {
|
||||
if (cachedCount % 10 === 0 || cachedCount === totalToCache) {
|
||||
notifyClients({
|
||||
type: 'CACHE_PROGRESS',
|
||||
cached: cachedCount,
|
||||
total: uncachedPages.length
|
||||
total: totalToCache
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1582,6 +1776,12 @@ self.addEventListener('message', (event) => {
|
||||
self.skipWaiting();
|
||||
}
|
||||
|
||||
// START_CACHING - triggered from /offline page
|
||||
if (event.data && event.data.type === 'START_CACHING') {
|
||||
console.log('[SW] Received START_CACHING request');
|
||||
startBackgroundCaching();
|
||||
}
|
||||
|
||||
// Pre-cache specific book/chapter on demand
|
||||
if (event.data && event.data.type === 'CACHE_CHAPTER') {
|
||||
const { book, chapter } = event.data;
|
||||
|
||||
@@ -161,12 +161,93 @@
|
||||
font-weight: bold;
|
||||
color: var(--success-color);
|
||||
}
|
||||
.cache-controls {
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
margin: 1.5rem 0;
|
||||
border: 1px solid #bae6fd;
|
||||
}
|
||||
[data-theme="dark"] .cache-controls {
|
||||
background: linear-gradient(135deg, #1e3a5f 0%, #1e293b 100%);
|
||||
border-color: #334155;
|
||||
}
|
||||
.cache-controls h3 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.cache-controls p {
|
||||
margin: 0 0 1rem 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.cache-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: var(--success-color);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.cache-btn:hover {
|
||||
filter: brightness(1.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.cache-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
.cache-progress {
|
||||
margin-top: 1rem;
|
||||
display: none;
|
||||
}
|
||||
.cache-progress.active {
|
||||
display: block;
|
||||
}
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: var(--border-color);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--success-color);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.progress-text {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.cache-complete {
|
||||
color: var(--success-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Offline Mode <span class="badge" id="connection-badge">checking...</span></h1>
|
||||
<p class="subtitle">KJV Study - Available without internet</p>
|
||||
|
||||
<div class="cache-controls">
|
||||
<h3>Enable Offline Access</h3>
|
||||
<p>Download the entire site for offline use: all 1,189 Bible chapters, 186 stories (including kids mode), study guides, and resources. Total: ~1,500 pages.</p>
|
||||
<button class="cache-btn" id="start-cache-btn">Download for Offline Use</button>
|
||||
<div class="cache-progress" id="cache-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progress-fill"></div>
|
||||
</div>
|
||||
<div class="progress-text" id="progress-text">Preparing...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Service Worker Status</h2>
|
||||
<div class="debug-section">
|
||||
<dl>
|
||||
@@ -384,6 +465,52 @@
|
||||
checkCaches();
|
||||
checkBibleData();
|
||||
|
||||
// Cache controls
|
||||
const startCacheBtn = document.getElementById('start-cache-btn');
|
||||
const cacheProgress = document.getElementById('cache-progress');
|
||||
const progressFill = document.getElementById('progress-fill');
|
||||
const progressText = document.getElementById('progress-text');
|
||||
|
||||
startCacheBtn.addEventListener('click', async function() {
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
alert('Service workers are not supported in this browser.');
|
||||
return;
|
||||
}
|
||||
|
||||
const registration = await navigator.serviceWorker.getRegistration();
|
||||
if (!registration || !registration.active) {
|
||||
alert('Service worker is not active. Please refresh the page and try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable button and show progress
|
||||
startCacheBtn.disabled = true;
|
||||
startCacheBtn.textContent = 'Downloading...';
|
||||
cacheProgress.classList.add('active');
|
||||
progressText.textContent = 'Starting download...';
|
||||
|
||||
// Send message to service worker to start caching
|
||||
registration.active.postMessage({ type: 'START_CACHING' });
|
||||
});
|
||||
|
||||
// Listen for progress messages from service worker
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.addEventListener('message', function(event) {
|
||||
const data = event.data;
|
||||
if (data.type === 'CACHE_PROGRESS') {
|
||||
const pct = Math.round((data.cached / data.total) * 100);
|
||||
progressFill.style.width = pct + '%';
|
||||
progressText.textContent = 'Downloading: ' + data.cached + ' / ' + data.total + ' pages (' + pct + '%)';
|
||||
} else if (data.type === 'CACHE_COMPLETE') {
|
||||
progressFill.style.width = '100%';
|
||||
progressText.innerHTML = '<span class="cache-complete">✓ Download complete! ' + data.total + ' pages available offline.</span>';
|
||||
startCacheBtn.textContent = 'Downloaded!';
|
||||
// Refresh the cache list
|
||||
setTimeout(checkCaches, 500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Bible Reader functionality
|
||||
let bibleData = null;
|
||||
let bookStructure = {};
|
||||
|
||||
Reference in New Issue
Block a user