From 8537fb548fb680df96b88023eae36bead0cb75b3 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 30 Nov 2025 08:51:54 -0500 Subject: [PATCH] Remove offline functionality and PWA features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed service worker, PWA manifest, offline page, and all related UI components to simplify the application and reduce bloat. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- kjvstudy_org/server.py | 26 - kjvstudy_org/static/manifest.json | 58 -- kjvstudy_org/static/sw.js | 345 ------- kjvstudy_org/templates/base.html | 125 --- kjvstudy_org/templates/offline.html | 1356 --------------------------- 5 files changed, 1910 deletions(-) delete mode 100644 kjvstudy_org/static/manifest.json delete mode 100644 kjvstudy_org/static/sw.js delete mode 100644 kjvstudy_org/templates/offline.html diff --git a/kjvstudy_org/server.py b/kjvstudy_org/server.py index 6bb0391..905ddd1 100644 --- a/kjvstudy_org/server.py +++ b/kjvstudy_org/server.py @@ -219,23 +219,6 @@ current_dir = PathLib(__file__).parent static_dir = current_dir / "static" templates_dir = current_dir / "templates" -# Serve service worker with proper header to allow root scope -@app.get("/sw.js") -async def service_worker(): - """Serve service worker from root with Service-Worker-Allowed header.""" - from fastapi.responses import FileResponse - sw_path = static_dir / "sw.js" - return FileResponse( - sw_path, - media_type="application/javascript", - headers={ - "Service-Worker-Allowed": "/", - "Cache-Control": "no-cache, no-store, must-revalidate", - "Pragma": "no-cache", - "Expires": "0" - } - ) - app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") templates = Jinja2Templates(directory=str(templates_dir)) @@ -1310,15 +1293,6 @@ def get_daily_verse(date_str=None): -@app.get("/offline", response_class=HTMLResponse) -async def offline_reader(request: Request): - """Offline Bible reader - renders chapters from cached JSON.""" - return templates.TemplateResponse( - "offline.html", - {"request": request} - ) - - @app.get("/", response_class=HTMLResponse) async def read_root(request: Request): books = bible.get_books() diff --git a/kjvstudy_org/static/manifest.json b/kjvstudy_org/static/manifest.json deleted file mode 100644 index 41730e9..0000000 --- a/kjvstudy_org/static/manifest.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "KJV Study - Bible Commentary Platform", - "short_name": "KJV Study", - "description": "Study the King James Bible with AI-powered commentary and insights", - "start_url": "/", - "display": "standalone", - "background_color": "#fdfcf9", - "theme_color": "#4b2e83", - "orientation": "portrait-primary", - "scope": "/", - "lang": "en", - "categories": ["education", "books", "reference"], - "icons": [ - { - "src": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 192 192'%3E%3Crect width='192' height='192' fill='%234b2e83' rx='24'/%3E%3Ctext x='96' y='120' text-anchor='middle' font-size='80' fill='white'%3E📖%3C/text%3E%3C/svg%3E", - "sizes": "192x192", - "type": "image/svg+xml", - "purpose": "any maskable" - }, - { - "src": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Crect width='512' height='512' fill='%234b2e83' rx='64'/%3E%3Ctext x='256' y='320' text-anchor='middle' font-size='200' fill='white'%3E📖%3C/text%3E%3C/svg%3E", - "sizes": "512x512", - "type": "image/svg+xml", - "purpose": "any" - } - ], - "shortcuts": [ - { - "name": "Browse Books", - "short_name": "Books", - "description": "Browse all Bible books", - "url": "/", - "icons": [ - { - "src": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%234b2e83'%3E%3Cpath d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z'/%3E%3C/svg%3E", - "sizes": "96x96" - } - ] - } - ], - "screenshots": [ - { - "src": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 540 720'%3E%3Crect width='540' height='720' fill='%23fdfcf9'/%3E%3Crect x='20' y='20' width='500' height='680' fill='white' stroke='%23e5e7eb' stroke-width='1' rx='8'/%3E%3Ctext x='270' y='80' text-anchor='middle' font-size='32' fill='%234b2e83' font-family='serif'%3EGenesis 1%3C/text%3E%3Ctext x='40' y='140' font-size='18' fill='%23333' font-family='serif'%3E1 In the beginning God created the heaven and the earth.%3C/text%3E%3Ctext x='40' y='180' font-size='18' fill='%23333' font-family='serif'%3E2 And the earth was without form, and void...%3C/text%3E%3C/svg%3E", - "sizes": "540x720", - "type": "image/svg+xml", - "form_factor": "narrow" - } - ], - "prefer_related_applications": false, - "related_applications": [], - "features": [ - "Cross Platform", - "offline-reading", - "note-taking", - "highlighting", - "search" - ] -} \ No newline at end of file diff --git a/kjvstudy_org/static/sw.js b/kjvstudy_org/static/sw.js deleted file mode 100644 index d1eb59b..0000000 --- a/kjvstudy_org/static/sw.js +++ /dev/null @@ -1,345 +0,0 @@ -// KJV Study Service Worker - Offline Bible Access -const CACHE_VERSION = 'v6'; -const STATIC_CACHE = 'kjvstudy-static-' + CACHE_VERSION; -const PAGE_CACHE = 'kjvstudy-pages-' + CACHE_VERSION; - -// Static assets to cache immediately on install -const STATIC_ASSETS = [ - '/', - '/offline', - '/static/tufte.css', - '/static/style.css', - '/static/manifest.json', - '/static/verses-1769.json', - '/books' -]; - -// Install event - cache static assets only -self.addEventListener('install', (event) => { - console.log('[SW] Installing service worker...'); - event.waitUntil( - caches.open(STATIC_CACHE) - .then((cache) => { - console.log('[SW] Caching static assets'); - return cache.addAll(STATIC_ASSETS); - }) - .then(() => { - console.log('[SW] Static assets cached'); - return self.skipWaiting(); - }) - .catch((err) => { - console.error('[SW] Failed to cache static assets:', err); - }) - ); -}); - -// Activate event - clean up old caches -self.addEventListener('activate', (event) => { - console.log('[SW] Activating service worker...'); - event.waitUntil( - caches.keys() - .then((cacheNames) => { - return Promise.all( - cacheNames.map((cacheName) => { - if (cacheName.startsWith('kjvstudy-') && - cacheName !== STATIC_CACHE && - cacheName !== PAGE_CACHE) { - console.log('[SW] Deleting old cache:', cacheName); - return caches.delete(cacheName); - } - }) - ); - }) - .then(() => { - console.log('[SW] Service worker activated'); - return self.clients.claim(); - }) - ); -}); - -// Parse sitemap XML and extract URLs -async function parseSitemap(url) { - try { - const response = await fetch(url); - const text = await response.text(); - const urls = []; - - // Extract URLs from tags - const locRegex = /([^<]+)<\/loc>/g; - let match; - while ((match = locRegex.exec(text)) !== null) { - // Convert absolute URL to relative path - let path = match[1].replace(/^https?:\/\/[^\/]+/, ''); - if (path) urls.push(path); - } - - return urls; - } catch (err) { - console.error('[SW] Failed to parse sitemap:', url, err); - return []; - } -} - -// Get all URLs from sitemaps -async function getAllUrlsFromSitemaps() { - const allUrls = new Set(); - - // First, fetch the sitemap index - try { - const indexResponse = await fetch('/sitemap.xml'); - const indexText = await indexResponse.text(); - - // Check if it's a sitemap index (has ) - if (indexText.includes('([^<]+)<\/loc>/g; - let match; - while ((match = locRegex.exec(indexText)) !== null) { - let sitemapUrl = match[1].replace(/^https?:\/\/[^\/]+/, ''); - sitemapUrls.push(sitemapUrl); - } - - // Fetch each child sitemap - for (const sitemapUrl of sitemapUrls) { - const urls = await parseSitemap(sitemapUrl); - urls.forEach(url => allUrls.add(url)); - } - } else { - // It's a regular sitemap - const urls = await parseSitemap('/sitemap.xml'); - urls.forEach(url => allUrls.add(url)); - } - } catch (err) { - console.error('[SW] Failed to fetch sitemap index:', err); - } - - return Array.from(allUrls); -} - -// 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] Fetching URLs from sitemaps...'); - notifyClients({ type: 'CACHE_STATUS', status: 'Fetching page list from sitemaps...' }); - - // Get all URLs from sitemaps - const allUrls = await getAllUrlsFromSitemaps(); - - console.log('[SW] Found', allUrls.length, 'URLs in sitemaps'); - - if (allUrls.length === 0) { - notifyClients({ type: 'CACHE_ERROR', error: 'No URLs found in sitemaps' }); - cachingInProgress = false; - return; - } - - const cache = await caches.open(PAGE_CACHE); - - // Check which pages are already cached - const uncachedPages = []; - for (const url of allUrls) { - const cached = await cache.match(url); - if (!cached) { - uncachedPages.push(url); - } - } - - totalToCache = uncachedPages.length; - console.log('[SW] Need to cache', totalToCache, 'pages'); - - if (totalToCache === 0) { - notifyClients({ type: 'CACHE_COMPLETE', total: allUrls.length }); - cachingInProgress = false; - return; - } - - notifyClients({ - type: 'CACHE_PROGRESS', - cached: 0, - total: totalToCache, - status: `Downloading ${totalToCache.toLocaleString()} pages...` - }); - - // Concurrent pool - keep N requests in flight at all times - const CONCURRENCY = 80; // Number of concurrent requests - const PROGRESS_INTERVAL = 200; // Notify every N completions - - let nextIndex = 0; - let lastNotified = 0; - - async function cacheUrl(url) { - try { - const response = await fetch(url); - if (response.ok) { - await cache.put(url, response); - cachedCount++; - - // Notify progress periodically - if (cachedCount - lastNotified >= PROGRESS_INTERVAL) { - lastNotified = cachedCount; - notifyClients({ - type: 'CACHE_PROGRESS', - cached: cachedCount, - total: totalToCache - }); - } - } - } catch (err) { - // Silent fail for individual pages - } - } - - async function worker() { - while (nextIndex < uncachedPages.length) { - const url = uncachedPages[nextIndex++]; - await cacheUrl(url); - } - } - - // Start concurrent workers - const workers = []; - for (let i = 0; i < Math.min(CONCURRENCY, uncachedPages.length); i++) { - workers.push(worker()); - } - - // Wait for all workers to complete - await Promise.all(workers); - - console.log('[SW] Background caching complete!', cachedCount, 'pages cached'); - cachingInProgress = false; - - notifyClients({ type: 'CACHE_COMPLETE', total: allUrls.length }); -} - -// Notify all clients of caching progress -async function notifyClients(message) { - const clients = await self.clients.matchAll(); - clients.forEach(client => client.postMessage(message)); -} - -// Fetch event - serve from cache, fallback to network -self.addEventListener('fetch', (event) => { - const url = new URL(event.request.url); - - // Only handle same-origin requests - if (url.origin !== location.origin) { - return; - } - - // Skip non-GET requests - if (event.request.method !== 'GET') { - return; - } - - // Skip API requests - network first with cache fallback - if (url.pathname.startsWith('/api/')) { - event.respondWith( - fetch(event.request) - .then((response) => { - if (response.ok) { - const responseClone = response.clone(); - caches.open(PAGE_CACHE).then((cache) => { - cache.put(event.request, responseClone); - }); - } - return response; - }) - .catch(() => caches.match(event.request)) - ); - return; - } - - // Static assets - cache first - if (url.pathname.startsWith('/static/')) { - event.respondWith( - caches.match(event.request) - .then((cachedResponse) => { - if (cachedResponse) { - return cachedResponse; - } - return fetch(event.request).then((response) => { - if (response.ok) { - const responseClone = response.clone(); - caches.open(STATIC_CACHE).then((cache) => { - cache.put(event.request, responseClone); - }); - } - return response; - }); - }) - ); - return; - } - - // All other requests - network first, cache fallback, offline reader as last resort - event.respondWith( - fetch(event.request) - .then((response) => { - if (response.ok) { - const responseClone = response.clone(); - caches.open(PAGE_CACHE).then((cache) => { - cache.put(event.request, responseClone); - }); - } - return response; - }) - .catch(() => { - return caches.match(event.request).then((cachedResponse) => { - if (cachedResponse) { - return cachedResponse; - } - - // For HTML pages, try to redirect to offline reader with context - if (event.request.headers.get('Accept')?.includes('text/html')) { - // Try to extract book/chapter from various URL patterns - const bookChapterMatch = url.pathname.match(/\/(?:book|interlinear)\/([^\/]+)(?:\/(?:chapter\/)?(\d+))?/); - if (bookChapterMatch) { - const book = decodeURIComponent(bookChapterMatch[1]); - const chapter = bookChapterMatch[2] || '1'; - return Response.redirect('/offline?book=' + encodeURIComponent(book) + '&chapter=' + chapter, 302); - } - - // Default to offline page - return caches.match('/offline'); - } - }); - }) - ); -}); - -// Handle messages from the main thread -self.addEventListener('message', (event) => { - if (event.data && event.data.type === 'SKIP_WAITING') { - self.skipWaiting(); - } - - if (event.data && event.data.type === 'START_CACHING') { - console.log('[SW] Received START_CACHING request'); - startBackgroundCaching(); - } - - // Report current caching status - if (event.data && event.data.type === 'GET_CACHE_STATUS') { - if (cachingInProgress) { - event.source.postMessage({ - type: 'CACHE_PROGRESS', - cached: cachedCount, - total: totalToCache, - inProgress: true - }); - } else { - event.source.postMessage({ - type: 'CACHE_IDLE', - inProgress: false - }); - } - } -}); diff --git a/kjvstudy_org/templates/base.html b/kjvstudy_org/templates/base.html index 66d0957..51a49ea 100644 --- a/kjvstudy_org/templates/base.html +++ b/kjvstudy_org/templates/base.html @@ -1306,15 +1306,6 @@

- - - @@ -2255,122 +2246,6 @@ s.parentNode.insertBefore(t, s); })(); {% endif %} - - // Service Worker for offline support - (function() { - if ('serviceWorker' in navigator) { - window.addEventListener('load', function() { - navigator.serviceWorker.register('/sw.js', { scope: '/' }) - .then(function(registration) { - console.log('[App] Service Worker registered:', registration.scope); - - // Check for updates periodically - setInterval(function() { - registration.update(); - }, 60 * 60 * 1000); // Check every hour - }) - .catch(function(error) { - console.log('[App] Service Worker registration failed:', error); - }); - }); - - // Listen for cache progress messages from service worker - navigator.serviceWorker.addEventListener('message', function(event) { - var data = event.data; - if (data.type === 'CACHE_PROGRESS') { - showCacheProgress(data.cached, data.total); - } else if (data.type === 'CACHE_COMPLETE') { - showCacheComplete(data.total); - } - }); - } - - // Cache progress indicator - var cacheIndicator = null; - function showCacheProgress(cached, total) { - if (!cacheIndicator) { - cacheIndicator = document.createElement('a'); - cacheIndicator.id = 'cache-indicator'; - cacheIndicator.href = '/offline'; - cacheIndicator.style.cssText = 'position:fixed;bottom:1rem;left:1rem;padding:0.5rem 1rem;background:#4a7c59;color:white;border-radius:4px;font-size:0.85rem;z-index:9999;box-shadow:0 2px 8px rgba(0,0,0,0.2);transition:opacity 0.3s;text-decoration:none;cursor:pointer;'; - document.body.appendChild(cacheIndicator); - } - var pct = Math.round((cached / total) * 100); - cacheIndicator.innerHTML = 'Caching for offline: ' + pct + '%'; - cacheIndicator.style.opacity = '1'; - } - - function showCacheComplete(total) { - if (cacheIndicator) { - cacheIndicator.innerHTML = 'Ready for offline! (' + total.toLocaleString() + ' pages)'; - setTimeout(function() { - cacheIndicator.style.opacity = '0'; - setTimeout(function() { - if (cacheIndicator && cacheIndicator.parentNode) { - cacheIndicator.parentNode.removeChild(cacheIndicator); - cacheIndicator = null; - } - }, 300); - }, 3000); - } - } - - // Offline/Online indicator and PDF button handling - function updateOnlineStatus() { - var indicator = document.getElementById('offline-indicator'); - if (!indicator) { - indicator = document.createElement('div'); - indicator.id = 'offline-indicator'; - indicator.style.cssText = 'position:fixed;bottom:1rem;right:1rem;padding:0.5rem 1rem;background:#c41e3a;color:white;border-radius:4px;font-size:0.85rem;z-index:9999;display:none;box-shadow:0 2px 8px rgba(0,0,0,0.2);'; - indicator.innerHTML = 'Offline Mode'; - document.body.appendChild(indicator); - } - - // Find all PDF links/buttons - var pdfLinks = document.querySelectorAll('a[href*="/pdf"], .pdf-btn, a[href$=".pdf"]'); - - if (!navigator.onLine) { - indicator.style.display = 'block'; - // Disable PDF buttons when offline - pdfLinks.forEach(function(link) { - link.dataset.originalHref = link.href; - link.removeAttribute('href'); - link.style.opacity = '0.4'; - link.style.cursor = 'not-allowed'; - link.style.textDecoration = 'line-through'; - link.title = 'PDF unavailable offline'; - link.addEventListener('click', preventClick); - }); - } else { - indicator.style.display = 'none'; - // Re-enable PDF buttons when online - pdfLinks.forEach(function(link) { - if (link.dataset.originalHref) { - link.href = link.dataset.originalHref; - } - link.style.opacity = ''; - link.style.cursor = ''; - link.style.textDecoration = ''; - link.title = ''; - link.removeEventListener('click', preventClick); - }); - } - } - - function preventClick(e) { - e.preventDefault(); - e.stopPropagation(); - } - - window.addEventListener('online', updateOnlineStatus); - window.addEventListener('offline', updateOnlineStatus); - // Run after DOM is ready to catch all PDF links - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', updateOnlineStatus); - } else { - updateOnlineStatus(); - } - })(); diff --git a/kjvstudy_org/templates/offline.html b/kjvstudy_org/templates/offline.html deleted file mode 100644 index 3d0e21b..0000000 --- a/kjvstudy_org/templates/offline.html +++ /dev/null @@ -1,1356 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Offline Access - KJV Study{% endblock %} -{% block description %}Download KJV Study for offline access{% endblock %} - -{% block head %} - -{% endblock %} - -{% block content %} - - -

Offline Access

-

Download the Complete KJV Bible for Offline Study

- -
-

Access the scriptures anywhere, anytime. Download all ~48,000 pages including every chapter, verse commentary, interlinear text, Bible stories, and study resources for complete offline access.

-
- - -
-

System Status

-
-
-
Connection
-
Checking...
-
-
-
Service Worker
-
Checking...
-
-
-
Cache Storage
-
Checking...
-
-
-
Bible Data
-
Checking...
-
-
-
Pages Cached
-
Checking...
-
-
-
SW Version
-
Checking...
-
-
-
- - -
-
-
...
-
-
Checking status...
-
Please wait
-
-
-
- - -
-

Download for Offline

-
-
-

Download all pages to your device for offline access. This includes chapters, verse commentary, interlinear analysis, stories, topics, and study resources.

-

Estimated size: ~150MB | Pages: ~48,000

-
- -
-
-
-
-
Preparing...
-
-
-
- - - - - -
-

Bible Reader

-
-

Read directly from cached data. Works offline without internet.

-
- - - -
-
-

Keyboard: Left/Right arrows navigate chapters | g = Genesis 1 | ? = help

-
-
- - -
- Cached Pages (0) - -
- - -{% endblock %}