mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
Add offline debug page with service worker status and cached pages list
- Shows service worker registration status - Lists all cache storage names - Checks if Bible data (31,102 verses) is cached - Displays all cached pages grouped by category (clickable links) - Shows online/offline connection badge - Includes Bible reader for reading any chapter from cached JSON Visit /offline to see what's available without internet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Offline Bible Reader - KJV Study</title>
|
||||
<title>Offline Mode - KJV Study</title>
|
||||
<link rel="stylesheet" href="/static/tufte.css"/>
|
||||
<link rel="manifest" href="/static/manifest.json"/>
|
||||
<style>
|
||||
@@ -13,6 +13,8 @@
|
||||
--text-secondary: #666;
|
||||
--border-color: #ddd;
|
||||
--link-color: #333;
|
||||
--success-color: #4a7c59;
|
||||
--error-color: #c41e3a;
|
||||
}
|
||||
[data-theme="dark"] {
|
||||
--bg-color: #1a1a1a;
|
||||
@@ -24,26 +26,73 @@
|
||||
body {
|
||||
background: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
max-width: 800px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
|
||||
h2 { font-size: 1.4rem; margin-top: 2rem; border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; }
|
||||
h3 { font-size: 1.1rem; margin-top: 1.5rem; color: var(--text-secondary); }
|
||||
.subtitle { color: var(--text-secondary); font-style: italic; margin-bottom: 2rem; }
|
||||
.offline-badge {
|
||||
.badge {
|
||||
display: inline-block;
|
||||
background: #c41e3a;
|
||||
color: white;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
margin-left: 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.badge-offline { background: var(--error-color); color: white; }
|
||||
.badge-online { background: var(--success-color); color: white; }
|
||||
.debug-section {
|
||||
background: var(--border-color);
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
margin: 1rem 0;
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.debug-section dt { font-weight: bold; margin-top: 0.5rem; }
|
||||
.debug-section dd { margin-left: 1rem; color: var(--text-secondary); }
|
||||
.status-ok { color: var(--success-color); }
|
||||
.status-error { color: var(--error-color); }
|
||||
.cached-links {
|
||||
column-count: 3;
|
||||
column-gap: 2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.cached-links { column-count: 2; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.cached-links { column-count: 1; }
|
||||
}
|
||||
.cached-links a {
|
||||
display: block;
|
||||
padding: 0.25rem 0;
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.cached-links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.category {
|
||||
break-inside: avoid;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.category-title {
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.nav-section {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
margin: 2rem 0;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -74,6 +123,10 @@
|
||||
#chapter-content {
|
||||
line-height: 2;
|
||||
font-size: 1.2rem;
|
||||
display: none;
|
||||
}
|
||||
#chapter-content.active {
|
||||
display: block;
|
||||
}
|
||||
#chapter-content p {
|
||||
margin: 1rem 0;
|
||||
@@ -89,13 +142,6 @@
|
||||
padding: 2rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.error {
|
||||
background: #fee;
|
||||
border: 1px solid #c00;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
color: #c00;
|
||||
}
|
||||
#keyboard-hint {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
@@ -110,12 +156,39 @@
|
||||
.back-link a {
|
||||
color: var(--link-color);
|
||||
}
|
||||
#cached-count {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--success-color);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Bible Reader <span class="offline-badge">Offline</span></h1>
|
||||
<p class="subtitle">King James Version - Available without internet</p>
|
||||
<h1>Offline Mode <span class="badge" id="connection-badge">checking...</span></h1>
|
||||
<p class="subtitle">KJV Study - Available without internet</p>
|
||||
|
||||
<h2>Service Worker Status</h2>
|
||||
<div class="debug-section">
|
||||
<dl>
|
||||
<dt>Service Worker</dt>
|
||||
<dd id="sw-status">Checking...</dd>
|
||||
<dt>Cache Storage</dt>
|
||||
<dd id="cache-status">Checking...</dd>
|
||||
<dt>Bible Data (verses-1769.json)</dt>
|
||||
<dd id="bible-status">Checking...</dd>
|
||||
<dt>Pages Cached</dt>
|
||||
<dd id="pages-cached">Checking...</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<h2>Cached Pages <span id="cached-count">0</span></h2>
|
||||
<p>These pages are available offline:</p>
|
||||
<div id="cached-links" class="cached-links">
|
||||
<p class="loading">Scanning cache...</p>
|
||||
</div>
|
||||
|
||||
<h2>Bible Reader</h2>
|
||||
<p>Read any chapter using cached Bible data:</p>
|
||||
<div class="nav-section">
|
||||
<select id="book-select">
|
||||
<option value="">Select a book...</option>
|
||||
@@ -130,13 +203,13 @@
|
||||
</div>
|
||||
|
||||
<div id="chapter-content">
|
||||
<p class="loading">Loading Bible data...</p>
|
||||
<p class="loading">Select a book and chapter above...</p>
|
||||
</div>
|
||||
|
||||
<p id="keyboard-hint">Tip: Use ← → arrow keys to navigate chapters</p>
|
||||
|
||||
<div class="back-link">
|
||||
<a href="/">← Back to KJV Study</a> (when online)
|
||||
<a href="/">← Back to KJV Study</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -145,13 +218,178 @@
|
||||
const theme = localStorage.getItem('theme') || 'light';
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
|
||||
// Bible data
|
||||
// Debug elements
|
||||
const connectionBadge = document.getElementById('connection-badge');
|
||||
const swStatus = document.getElementById('sw-status');
|
||||
const cacheStatus = document.getElementById('cache-status');
|
||||
const bibleStatus = document.getElementById('bible-status');
|
||||
const pagesCached = document.getElementById('pages-cached');
|
||||
const cachedLinks = document.getElementById('cached-links');
|
||||
const cachedCount = document.getElementById('cached-count');
|
||||
|
||||
// Update connection status
|
||||
function updateConnectionStatus() {
|
||||
if (navigator.onLine) {
|
||||
connectionBadge.textContent = 'Online';
|
||||
connectionBadge.className = 'badge badge-online';
|
||||
} else {
|
||||
connectionBadge.textContent = 'Offline';
|
||||
connectionBadge.className = 'badge badge-offline';
|
||||
}
|
||||
}
|
||||
updateConnectionStatus();
|
||||
window.addEventListener('online', updateConnectionStatus);
|
||||
window.addEventListener('offline', updateConnectionStatus);
|
||||
|
||||
// Check service worker status
|
||||
async function checkServiceWorker() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.getRegistration();
|
||||
if (registration) {
|
||||
const state = registration.active ? 'Active' :
|
||||
registration.waiting ? 'Waiting' :
|
||||
registration.installing ? 'Installing' : 'Unknown';
|
||||
swStatus.innerHTML = '<span class="status-ok">✓ Registered (' + state + ')</span>';
|
||||
swStatus.innerHTML += '<br>Scope: ' + registration.scope;
|
||||
} else {
|
||||
swStatus.innerHTML = '<span class="status-error">✗ Not registered</span>';
|
||||
swStatus.innerHTML += '<br>Try refreshing the page or check if content blockers are blocking it.';
|
||||
}
|
||||
} catch (err) {
|
||||
swStatus.innerHTML = '<span class="status-error">✗ Error: ' + err.message + '</span>';
|
||||
}
|
||||
} else {
|
||||
swStatus.innerHTML = '<span class="status-error">✗ Not supported in this browser</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Check cache status and list cached pages
|
||||
async function checkCaches() {
|
||||
if ('caches' in window) {
|
||||
try {
|
||||
const cacheNames = await caches.keys();
|
||||
if (cacheNames.length > 0) {
|
||||
cacheStatus.innerHTML = '<span class="status-ok">✓ Available</span>';
|
||||
cacheStatus.innerHTML += '<br>Caches: ' + cacheNames.join(', ');
|
||||
} else {
|
||||
cacheStatus.innerHTML = '<span class="status-error">✗ No caches found</span>';
|
||||
}
|
||||
|
||||
// List all cached URLs
|
||||
const allUrls = [];
|
||||
for (const cacheName of cacheNames) {
|
||||
const cache = await caches.open(cacheName);
|
||||
const keys = await cache.keys();
|
||||
keys.forEach(req => {
|
||||
const url = new URL(req.url);
|
||||
if (url.pathname && !url.pathname.startsWith('/static/')) {
|
||||
allUrls.push(url.pathname);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove duplicates and sort
|
||||
const uniqueUrls = [...new Set(allUrls)].sort();
|
||||
pagesCached.innerHTML = '<span class="status-ok">' + uniqueUrls.length + ' pages</span>';
|
||||
cachedCount.textContent = uniqueUrls.length;
|
||||
|
||||
// Group by category
|
||||
const categories = {
|
||||
'Main': [],
|
||||
'Books': [],
|
||||
'Topics': [],
|
||||
'Stories': [],
|
||||
'Study Guides': [],
|
||||
'Reading Plans': [],
|
||||
'Parables': [],
|
||||
'Resources': [],
|
||||
'Other': []
|
||||
};
|
||||
|
||||
uniqueUrls.forEach(url => {
|
||||
if (url === '/' || url === '/books' || url === '/offline' || url === '/search') {
|
||||
categories['Main'].push(url);
|
||||
} else if (url.startsWith('/book/')) {
|
||||
categories['Books'].push(url);
|
||||
} else if (url.startsWith('/topics/')) {
|
||||
categories['Topics'].push(url);
|
||||
} else if (url.startsWith('/stories/')) {
|
||||
categories['Stories'].push(url);
|
||||
} else if (url.startsWith('/study-guides/')) {
|
||||
categories['Study Guides'].push(url);
|
||||
} else if (url.startsWith('/reading-plans/')) {
|
||||
categories['Reading Plans'].push(url);
|
||||
} else if (url.startsWith('/parables/')) {
|
||||
categories['Parables'].push(url);
|
||||
} else if (url.match(/^\/(christology|names-of-god|biblical-|fruits-|messianic-|miracles-|prayers-|spirits-|types-|women-|ten-|the-twelve|trinity|worship|grace|eschatology|ecclesiology|soteriology|pneumatology|hamartiology|anthropology|bibliology|theology-proper|justification|sanctification|providence|kingdom-of-god|law-and-gospel|armor-of-god|beatitudes|i-am-statements|names-of-christ|personifications|tetragrammaton|blood-in-scripture)/)) {
|
||||
categories['Resources'].push(url);
|
||||
} else {
|
||||
categories['Other'].push(url);
|
||||
}
|
||||
});
|
||||
|
||||
// Render links
|
||||
let html = '';
|
||||
for (const [category, urls] of Object.entries(categories)) {
|
||||
if (urls.length > 0) {
|
||||
html += '<div class="category">';
|
||||
html += '<div class="category-title">' + category + ' (' + urls.length + ')</div>';
|
||||
urls.forEach(url => {
|
||||
const label = url === '/' ? 'Home' : url.replace(/^\//, '').replace(/-/g, ' ');
|
||||
html += '<a href="' + url + '">' + label + '</a>';
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
cachedLinks.innerHTML = html || '<p>No pages cached yet. Browse the site while online to cache pages.</p>';
|
||||
|
||||
} catch (err) {
|
||||
cacheStatus.innerHTML = '<span class="status-error">✗ Error: ' + err.message + '</span>';
|
||||
cachedLinks.innerHTML = '<p class="status-error">Could not read cache.</p>';
|
||||
}
|
||||
} else {
|
||||
cacheStatus.innerHTML = '<span class="status-error">✗ Not supported</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Check Bible data
|
||||
async function checkBibleData() {
|
||||
try {
|
||||
// Try cache first
|
||||
let found = false;
|
||||
const cacheNames = await caches.keys();
|
||||
for (const cacheName of cacheNames) {
|
||||
const cache = await caches.open(cacheName);
|
||||
const response = await cache.match('/static/verses-1769.json');
|
||||
if (response) {
|
||||
const data = await response.json();
|
||||
const verseCount = Object.keys(data).length;
|
||||
bibleStatus.innerHTML = '<span class="status-ok">✓ Cached (' + verseCount.toLocaleString() + ' verses)</span>';
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
bibleStatus.innerHTML = '<span class="status-error">✗ Not cached</span>';
|
||||
bibleStatus.innerHTML += '<br>Visit the site while online to cache Bible data.';
|
||||
}
|
||||
} catch (err) {
|
||||
bibleStatus.innerHTML = '<span class="status-error">✗ Error: ' + err.message + '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Run all checks
|
||||
checkServiceWorker();
|
||||
checkCaches();
|
||||
checkBibleData();
|
||||
|
||||
// Bible Reader functionality
|
||||
let bibleData = null;
|
||||
let bookStructure = {};
|
||||
let currentBook = '';
|
||||
let currentChapter = 1;
|
||||
|
||||
// Book order
|
||||
const BOOKS = [
|
||||
'Genesis', 'Exodus', 'Leviticus', 'Numbers', 'Deuteronomy',
|
||||
'Joshua', 'Judges', 'Ruth', '1 Samuel', '2 Samuel',
|
||||
@@ -176,36 +414,30 @@
|
||||
const nextBtn = document.getElementById('next-btn');
|
||||
const content = document.getElementById('chapter-content');
|
||||
|
||||
// Load Bible data from cache
|
||||
async function loadBibleData() {
|
||||
try {
|
||||
// Try to fetch from cache first
|
||||
const cache = await caches.open('kjvstudy-static-v1');
|
||||
let response = await cache.match('/static/verses-1769.json');
|
||||
|
||||
let response = null;
|
||||
const cacheNames = await caches.keys();
|
||||
for (const cacheName of cacheNames) {
|
||||
const cache = await caches.open(cacheName);
|
||||
response = await cache.match('/static/verses-1769.json');
|
||||
if (response) break;
|
||||
}
|
||||
if (!response) {
|
||||
// Try network as fallback
|
||||
response = await fetch('/static/verses-1769.json');
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
throw new Error('Could not load Bible data');
|
||||
}
|
||||
|
||||
bibleData = await response.json();
|
||||
buildBookStructure();
|
||||
populateBookSelect();
|
||||
content.innerHTML = '<p>Select a book and chapter to begin reading.</p>';
|
||||
|
||||
// Check URL for book/chapter
|
||||
parseUrlAndNavigate();
|
||||
} catch (err) {
|
||||
content.innerHTML = '<div class="error">Could not load Bible data. Please visit the site while online first to cache the data.</div>';
|
||||
console.error('Failed to load Bible:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Build structure: { book: { chapter: [verse numbers] } }
|
||||
function buildBookStructure() {
|
||||
bookStructure = {};
|
||||
for (const ref in bibleData) {
|
||||
@@ -217,7 +449,6 @@
|
||||
bookStructure[book][chapter].push(parseInt(verse));
|
||||
}
|
||||
}
|
||||
// Sort verses
|
||||
for (const book in bookStructure) {
|
||||
for (const chapter in bookStructure[book]) {
|
||||
bookStructure[book][chapter].sort((a, b) => a - b);
|
||||
@@ -225,7 +456,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Populate book dropdown
|
||||
function populateBookSelect() {
|
||||
BOOKS.forEach(book => {
|
||||
if (bookStructure[book]) {
|
||||
@@ -237,11 +467,9 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Populate chapter dropdown
|
||||
function populateChapterSelect(book) {
|
||||
chapterSelect.innerHTML = '<option value="">Chapter</option>';
|
||||
if (!bookStructure[book]) return;
|
||||
|
||||
const chapters = Object.keys(bookStructure[book]).map(Number).sort((a, b) => a - b);
|
||||
chapters.forEach(ch => {
|
||||
const opt = document.createElement('option');
|
||||
@@ -252,115 +480,76 @@
|
||||
chapterSelect.disabled = false;
|
||||
}
|
||||
|
||||
// Render chapter
|
||||
function renderChapter(book, chapter) {
|
||||
if (!bookStructure[book] || !bookStructure[book][chapter]) {
|
||||
content.innerHTML = '<p>Chapter not found.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
currentBook = book;
|
||||
currentChapter = parseInt(chapter);
|
||||
content.classList.add('active');
|
||||
|
||||
const verses = bookStructure[book][chapter];
|
||||
let html = '<h2>' + book + ' ' + chapter + '</h2>';
|
||||
|
||||
let html = '<h3>' + book + ' ' + chapter + '</h3>';
|
||||
verses.forEach(verseNum => {
|
||||
const ref = book + ' ' + chapter + ':' + verseNum;
|
||||
const text = bibleData[ref] || '';
|
||||
// Remove paragraph markers (#)
|
||||
const cleanText = text.replace(/^#\s*/, '');
|
||||
html += '<p><span class="verse-num">' + verseNum + '</span> ' + cleanText + '</p>';
|
||||
});
|
||||
|
||||
content.innerHTML = html;
|
||||
|
||||
// Update navigation
|
||||
updateNavButtons();
|
||||
|
||||
// Update URL
|
||||
history.replaceState(null, '', '/offline?book=' + encodeURIComponent(book) + '&chapter=' + chapter);
|
||||
|
||||
// Scroll to top
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
// Update prev/next buttons
|
||||
function updateNavButtons() {
|
||||
const chapters = Object.keys(bookStructure[currentBook] || {}).map(Number).sort((a, b) => a - b);
|
||||
const bookIndex = BOOKS.indexOf(currentBook);
|
||||
|
||||
// Previous
|
||||
if (currentChapter > chapters[0]) {
|
||||
prevBtn.disabled = false;
|
||||
} else if (bookIndex > 0) {
|
||||
prevBtn.disabled = false;
|
||||
} else {
|
||||
prevBtn.disabled = true;
|
||||
}
|
||||
|
||||
// Next
|
||||
if (currentChapter < chapters[chapters.length - 1]) {
|
||||
nextBtn.disabled = false;
|
||||
} else if (bookIndex < BOOKS.length - 1) {
|
||||
nextBtn.disabled = false;
|
||||
} else {
|
||||
nextBtn.disabled = true;
|
||||
}
|
||||
prevBtn.disabled = !(currentChapter > chapters[0] || bookIndex > 0);
|
||||
nextBtn.disabled = !(currentChapter < chapters[chapters.length - 1] || bookIndex < BOOKS.length - 1);
|
||||
}
|
||||
|
||||
// Navigate to previous chapter
|
||||
function goPrev() {
|
||||
const chapters = Object.keys(bookStructure[currentBook] || {}).map(Number).sort((a, b) => a - b);
|
||||
const chapterIndex = chapters.indexOf(currentChapter);
|
||||
|
||||
if (chapterIndex > 0) {
|
||||
chapterSelect.value = chapters[chapterIndex - 1];
|
||||
renderChapter(currentBook, chapters[chapterIndex - 1]);
|
||||
} else {
|
||||
// Go to previous book's last chapter
|
||||
const bookIndex = BOOKS.indexOf(currentBook);
|
||||
if (bookIndex > 0) {
|
||||
const prevBook = BOOKS[bookIndex - 1];
|
||||
const prevChapters = Object.keys(bookStructure[prevBook] || {}).map(Number).sort((a, b) => a - b);
|
||||
const lastChapter = prevChapters[prevChapters.length - 1];
|
||||
bookSelect.value = prevBook;
|
||||
populateChapterSelect(prevBook);
|
||||
chapterSelect.value = lastChapter;
|
||||
renderChapter(prevBook, lastChapter);
|
||||
chapterSelect.value = prevChapters[prevChapters.length - 1];
|
||||
renderChapter(prevBook, prevChapters[prevChapters.length - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigate to next chapter
|
||||
function goNext() {
|
||||
const chapters = Object.keys(bookStructure[currentBook] || {}).map(Number).sort((a, b) => a - b);
|
||||
const chapterIndex = chapters.indexOf(currentChapter);
|
||||
|
||||
if (chapterIndex < chapters.length - 1) {
|
||||
chapterSelect.value = chapters[chapterIndex + 1];
|
||||
renderChapter(currentBook, chapters[chapterIndex + 1]);
|
||||
} else {
|
||||
// Go to next book's first chapter
|
||||
const bookIndex = BOOKS.indexOf(currentBook);
|
||||
if (bookIndex < BOOKS.length - 1) {
|
||||
const nextBook = BOOKS[bookIndex + 1];
|
||||
const nextChapters = Object.keys(bookStructure[nextBook] || {}).map(Number).sort((a, b) => a - b);
|
||||
const firstChapter = nextChapters[0];
|
||||
bookSelect.value = nextBook;
|
||||
populateChapterSelect(nextBook);
|
||||
chapterSelect.value = firstChapter;
|
||||
renderChapter(nextBook, firstChapter);
|
||||
chapterSelect.value = nextChapters[0];
|
||||
renderChapter(nextBook, nextChapters[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse URL parameters
|
||||
function parseUrlAndNavigate() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const book = params.get('book');
|
||||
const chapter = params.get('chapter');
|
||||
|
||||
if (book && bookStructure[book]) {
|
||||
bookSelect.value = book;
|
||||
populateChapterSelect(book);
|
||||
@@ -371,12 +560,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
bookSelect.addEventListener('change', function() {
|
||||
const book = this.value;
|
||||
if (book) {
|
||||
populateChapterSelect(book);
|
||||
// Auto-select first chapter
|
||||
const chapters = Object.keys(bookStructure[book]).map(Number).sort((a, b) => a - b);
|
||||
if (chapters.length > 0) {
|
||||
chapterSelect.value = chapters[0];
|
||||
@@ -386,19 +573,16 @@
|
||||
});
|
||||
|
||||
chapterSelect.addEventListener('change', function() {
|
||||
const chapter = this.value;
|
||||
if (chapter && currentBook) {
|
||||
renderChapter(currentBook, chapter);
|
||||
if (this.value && currentBook) {
|
||||
renderChapter(currentBook, this.value);
|
||||
}
|
||||
});
|
||||
|
||||
prevBtn.addEventListener('click', goPrev);
|
||||
nextBtn.addEventListener('click', goNext);
|
||||
|
||||
// Keyboard navigation
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.target.tagName === 'SELECT') return;
|
||||
|
||||
if (e.key === 'ArrowLeft' || e.key === 'h') {
|
||||
e.preventDefault();
|
||||
if (!prevBtn.disabled) goPrev();
|
||||
@@ -408,7 +592,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Load data
|
||||
loadBibleData();
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user