Skip to content

Commit b8f87c4

Browse files
committed
- Reverted back to version 2.1 as device syncing was not working as I liked (that build has been included in a new "archive" folder, for future reference)
- Redesigned the import/export buttons to show the last filename, date, time, and size - Added a mainfest.json file to remove "Genesis 1 - " from the title when the app is installed as a PWA - Made some minor updates to visual/CSS stuff - Modified some other minor things (like adding Gab AI to the sidebar)
1 parent abb0ca0 commit b8f87c4

34 files changed

Lines changed: 1840 additions & 3277 deletions

README.md

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ This project was developed with assistance from AI tools including [Gab AI](http
1616
- **Verse Highlighting**: Right-click verses to highlight in one of six colors for study emphasis
1717
- **Reference Bible**: Use the reference Bible to select from multiple translation options to compare with (ASV, BSB, CSB, ESV, GNV, KJV, LSB, NASB 1995, NASB 2020, NET, NIV, NKJV, NLT); this also sets the translation for Bible Gateway searches and the Verse Analysis popup
1818
- **Search Integration**: Built-in [Bible Gateway search](https://www.biblegateway.com/usage/) functionality to search for any word or passage
19-
- **Theming**: Dark/light mode with six color themes
2019

2120
### Advanced Features
2221
- **Highlights Management**: Search and filter highlighted verses by color/content
2322
- **Keyboard Navigation**: Extensive keyboard shortcuts (F1 to toggle help)
2423
- **Data Portability**: Import/export highlights, notes, and settings
25-
- **Local Device Syncing**: Pair devices on your local network to sync your highlights, notes, and settings using a simple, 8-digit code
2624
- **Responsive Design**: Optimized for both desktop and mobile devices (mobile has limited features)
25+
- **Theming**: Dark/light mode with six color themes
2726

2827
## Markdown Keyboard Shortcuts
2928
### Basic Formatting (Ctrl/Cmd + Key)
@@ -64,13 +63,12 @@ This project was developed with assistance from AI tools including [Gab AI](http
6463
| `Alt + E` | Export Data |
6564
| `Alt + I` | Import Data |
6665
| `Alt + M` | Export Notes |
67-
| `Alt + D` | Manual Sync (if devices paired) |
6866
| `F1` | Show Help Modal |
6967

7068
*Note: Hotkeys can be customized in the settings menu*
7169

7270
### Study Resources Integration
73-
The sidebar provides organized access to extensive theological resources (Reformed Theology/Calvinism) including:
71+
The sidebar provides organized access to extensive theological resources (Reformed Theology/Calvinism focused) including:
7472
- Online Bible platforms
7573
- Christian doctrine references
7674
- Theological resources
@@ -82,10 +80,8 @@ The sidebar provides organized access to extensive theological resources (Reform
8280

8381
- **Frontend**: Pure HTML5, CSS3, and Vanilla JavaScript (ES6+)
8482
- **Libraries**:
85-
- Font Awesome for icons
8683
- Marked.js for Markdown processing
87-
- PeerJS for discovery (local device syncing)
88-
- Google for STUN servers (local device syncing)
84+
- Font Awesome for icons
8985
- **API**: Bible.helloao.org for scripture text, footnotes, and BSB audio
9086
- **Storage**: LocalStorage for user data persistence
9187
- **Build**: Use the provided scripts for setup and consistency
@@ -113,19 +109,15 @@ The sidebar provides organized access to extensive theological resources (Reform
113109

114110
## Privacy & Data
115111

116-
No cloud storage: Highlights, notes, and settings are stored locally in your browser and never pass through servers (only signaling for discovery). Device sync is optional.
112+
Highlights, notes, and settings are stored locally in your browser.
117113

118114
Data that is transmitted to external servers:
119115
- Bible passage requests are handled by bible.helloao.org
120116
- Bible Hub (interlinear) and STEP Bible (both when using Verse Analysis popup)
121117
- Bible Gateway searches
122118
- Reference Bible websites while the panel is opened
123119
- Resource links opened in external sites via the sidebar
124-
- Third-party libraries:
125-
- Font Awesome
126-
- Marked.js
127-
- PeerJS
128-
- Google STUN servers
120+
- Third-party library usage for Font Awesome and Marked.js
129121

130122
## Attribution
131123

@@ -134,21 +126,12 @@ Data that is transmitted to external servers:
134126
- Audio files hosted by [Open Bible](https://openbible.com/audio/)
135127
- Icons by [Font Awesome](https://fontawesome.com)
136128
- Markdown processing by [Marked.js](https://cdn.jsdelivr.net/npm/marked/)
137-
- Local device syncing by [PeerJS](https://unpkg.com)
138-
- STUN servers by Google
139-
- stun.l.google.com:19302
140-
- stun1.l.google.com:19302
141-
- stun2.l.google.com:19302
142-
- stun3.l.google.com:19302
143-
- stun4.l.google.com:19302
144-
145129
- **Reference Bible websites**
146130
- [Bible Gateway](https://www.biblegateway.com)
147131
- [Bible.com (YouVersion)](https://www.bible.com)
148132
- [Bible Hub](https://biblehub.com)
149133
- [STEP Bible](https://www.stepbible.org)
150134
- [eBible.org](https://ebible.org)
151-
152135
- All copyrights, trademarks, and attributions belong to their respective owners
153136

154137
## License
268 KB
Binary file not shown.

dev_tools/Minify-JS.ps1

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ $files = @(
1111
"../src/modules/settings.js",
1212
"../src/modules/state.js",
1313
"../src/modules/strongs.js",
14-
"../src/modules/sync.js",
1514
"../src/modules/ui.js",
1615
"../src/sw.js"
1716
)

dev_tools/minify-js.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ def main():
8080
"../src/modules/settings.js",
8181
"../src/modules/state.js",
8282
"../src/modules/strongs.js",
83-
"../src/modules/sync.js",
8483
"../src/modules/ui.js"
8584
]
8685

index.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

main.js

Lines changed: 39 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import { isKJV, playChapterAudio, pauseChapterAudio, resumeChapterAudio, stopCha
22
import { applyHighlight, clearHighlights, closeHighlightsModal, renderHighlights, showColorPicker, showHighlightsModal } from './modules/highlights.js';
33
import { showHelpModal } from './modules/hotkeys.js';
44
import { initBookChapterControls, loadSelectedChapter, navigateFromURL, nextPassage, prevPassage, randomPassage, setupNavigationWithURL, setupPopStateListener } from './modules/navigation.js'
5-
import { clearCache, closeSettings, deleteAllData, exportData, importData, initializeAudioControls, initialiseNarratorSelect, openSettings, saveSettings, updateAudioControlsVisibility } from './modules/settings.js'
5+
import { clearCache, closeSettings, deleteAllData, exportData, importData, initializeAudioControls, initialiseNarratorSelect, openSettings, saveSettings } from './modules/settings.js'
66
import { APP_VERSION, BOOK_ORDER, updateBibleGatewayVersion, loadFromCookies, loadFromStorage, saveToStorage, state } from './modules/state.js'
77
import { closeStrongsPopup, showStrongsReference } from './modules/strongs.js'
8-
import { initSync, syncManager } from './modules/sync.js';
98
import { exportNotes, initResizeHandles, insertMarkdown, restoreBookChapterUI, restorePanelStates, restoreSidebarState, switchNotesView, togglePanelCollapse, toggleReferencePanel, toggleSection,
109
updateMarkdownPreview, updateReferencePanel, updateNotesFontSize, updateScriptureFontSize } from './modules/ui.js'
1110
const OFFLINE_STYLES = `
@@ -31,11 +30,10 @@ let touchStartTime = 0;
3130
let longPressTimer = null;
3231
let touchStartY = 0;
3332
let isScrolling = false;
34-
let syncResumedToastShown = false;
3533
if (typeof marked !== 'undefined') {
3634
marked.setOptions({
37-
breaks: true,
38-
gfm: true
35+
breaks: true,
36+
gfm: true
3937
});
4038
}
4139
class AppError extends Error {
@@ -48,11 +46,6 @@ class AppError extends Error {
4846
}
4947
export function handleError(error, context, userFriendlyMessage) {
5048
console.error(`Error in ${context}:`, error);
51-
if (error.type === 'unavailable-id') {
52-
console.warn('[Sync] Peer ID unavailable – regenerating');
53-
const syncEvent = new CustomEvent('sync:regenerateId');
54-
document.dispatchEvent(syncEvent);
55-
}
5649
const rawMsg = userFriendlyMessage ??
5750
(error instanceof AppError ? error.message : 'An unexpected error occurred');
5851
const escapedMsg = escapeHTML(rawMsg);
@@ -101,6 +94,28 @@ export function getSimpleDate() {
10194
const year = String(now.getFullYear()).slice(-2);
10295
return `${month}-${day}-${year}`;
10396
}
97+
export function formatFileSize(bytes) {
98+
if (bytes === 0) return '0 Bytes';
99+
const k = 1024;
100+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
101+
const i = Math.floor(Math.log(bytes) / Math.log(k));
102+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
103+
}
104+
export function formatDateTime(isoDate) {
105+
const date = new Date(isoDate);
106+
const dateStr = date.toLocaleDateString('en-US', {
107+
year: 'numeric',
108+
month: 'short',
109+
day: 'numeric'
110+
});
111+
const timeStr = date.toLocaleTimeString('en-US', {
112+
hour: '2-digit',
113+
minute: '2-digit',
114+
second: '2-digit',
115+
hour12: false
116+
});
117+
return `${dateStr}, ${timeStr}`;
118+
}
104119
export function showLoading(flag) {
105120
const overlay = document.getElementById('loadingOverlay');
106121
if (overlay) overlay.classList.toggle('active', flag);
@@ -124,9 +139,8 @@ function updateOfflineStatus(isOffline) {
124139
`;
125140
document.body.appendChild(indicator);
126141
}
127-
indicator.textContent = isOffline ? 'Offline Mode (Sync Paused)' : 'Online';
142+
indicator.textContent = isOffline ? 'Offline Mode' : 'Online';
128143
indicator.style.background = isOffline ? '#ff6b6b' : '#51cf66';
129-
document.dispatchEvent(new CustomEvent('offlineStatus', { detail: { isOffline } }));
130144
setTimeout(() => {
131145
indicator.style.opacity = '0';
132146
setTimeout(() => indicator.remove(), 300);
@@ -201,12 +215,6 @@ export function escapeHTML(string) {
201215
return escape[char];
202216
});
203217
}
204-
function syncAudioControlsToggleUI() {
205-
const audioToggle = document.getElementById('audioControlsToggle');
206-
if (audioToggle) {
207-
audioToggle.checked = !!state.settings.audioControlsVisible;
208-
}
209-
}
210218
export function applyTheme() {
211219
document.documentElement.setAttribute('data-theme', state.settings.theme);
212220
const themeIcon = document.getElementById('themeIcon');
@@ -248,7 +256,6 @@ function setupEventListeners() {
248256
setupModalControls();
249257
setupMarkdownShortcuts();
250258
setupTouchEvents();
251-
setupSyncManagement();
252259
}
253260
function setupHeaderButtons() {
254261
const buttons = {
@@ -279,9 +286,15 @@ function setupAudioControls() {
279286
const playBtn = document.querySelector('.play-audio-btn');
280287
const pauseBtn = document.querySelector('.pause-audio-btn');
281288
const stopBtn = document.querySelector('.stop-audio-btn');
282-
if (playBtn) playBtn.addEventListener('click', handleAudioPlayback);
283-
if (pauseBtn) pauseBtn.addEventListener('click', pauseChapterAudio);
284-
if (stopBtn) stopBtn.addEventListener('click', stopChapterAudio);
289+
if (playBtn) {
290+
playBtn.addEventListener('click', handleAudioPlayback);
291+
}
292+
if (pauseBtn) {
293+
pauseBtn.addEventListener('click', pauseChapterAudio);
294+
}
295+
if (stopBtn) {
296+
stopBtn.addEventListener('click', stopChapterAudio);
297+
}
285298
});
286299
}
287300
function handleAudioPlayback() {
@@ -464,92 +477,10 @@ function setupTouchEvents() {
464477
document.addEventListener('touchend', handleTouchEnd);
465478
document.addEventListener('touchcancel', handleTouchCancel);
466479
}
467-
function setupSyncManagement() {
468-
initSync();
469-
if (state.settings.connectedDevices?.length > 0) {
470-
syncManager.initPeer().catch(err => {
471-
console.error('[App] Failed to init peer on load:', err);
472-
});
473-
}
474-
document.addEventListener('sync:peerConnected', (ev) => {
475-
console.log('[App] Peer connected:', ev.detail.peerId);
476-
if (!syncResumedToastShown) {
477-
const notification = document.createElement('div');
478-
notification.textContent = 'Device connected!';
479-
notification.style.cssText = `
480-
position: fixed;
481-
top: 70px;
482-
right: 10px;
483-
padding: 12px 20px;
484-
background: #4caf50;
485-
color: white;
486-
border-radius: 5px;
487-
z-index: 10000;
488-
font-size: 14px;
489-
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
490-
`;
491-
document.body.appendChild(notification);
492-
setTimeout(() => notification.remove(), 3000);
493-
syncResumedToastShown = true;
494-
} else {
495-
syncResumedToastShown = false;
496-
}
497-
});
498-
document.addEventListener('sync:merged', (ev) => {
499-
if (!ev.detail.changesMade) return;
500-
console.log('[App] Applying real-time sync changes from', ev.detail.peerId);
501-
const currentBook = state.settings.manualBook;
502-
const currentChapter = state.settings.manualChapter;
503-
document.querySelectorAll('.verse').forEach(verseEl => {
504-
const ref = verseEl.dataset.verse;
505-
if (!ref) return;
506-
const versePattern = new RegExp(`^${currentBook.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')} ${currentChapter}:`);
507-
if (!versePattern.test(ref)) return;
508-
const newColor = state.highlights[ref];
509-
const currentClasses = Array.from(verseEl.classList);
510-
const currentHighlight = currentClasses.find(c => c.startsWith('highlight-'));
511-
if (newColor && currentHighlight !== `highlight-${newColor}`) {
512-
currentClasses.filter(c => c.startsWith('highlight-'))
513-
.forEach(c => verseEl.classList.remove(c));
514-
verseEl.classList.add(`highlight-${newColor}`);
515-
console.log(`[App] Updated highlight for ${ref} to ${newColor}`);
516-
} else if (!newColor && currentHighlight) {
517-
verseEl.classList.remove(currentHighlight);
518-
console.log(`[App] Removed highlight for ${ref}`);
519-
}
520-
});
521-
const notesInput = document.getElementById('notesInput');
522-
if (notesInput && document.activeElement !== notesInput) {
523-
notesInput.value = state.notes;
524-
if (state.settings.notesView === 'markdown') {
525-
updateMarkdownPreview();
526-
}
527-
console.log('[App] Updated notes from sync');
528-
}
529-
applyTheme();
530-
applyColorTheme();
531-
updateBibleGatewayVersion();
532-
initialiseNarratorSelect();
533-
updateAudioControlsVisibility();
534-
console.log('[App] UI updated from sync');
535-
});
536-
document.addEventListener('sync:error', (ev) => {
537-
console.error('[App] Sync error:', ev.detail.error);
538-
alert('Sync error: ' + ev.detail.error);
539-
});
540-
document.addEventListener('offlineStatus', (ev) => {
541-
if (ev.detail.isOffline) {
542-
console.log('[App] Pausing sync due to offline');
543-
document.dispatchEvent(new CustomEvent('sync:pauseReconnect'));
544-
}
545-
});
546-
}
547480
async function init() {
548481
try {
549-
showLoading(true);
550-
await loadFromCookies();
551482
await loadFromStorage();
552-
syncAudioControlsToggleUI();
483+
loadFromCookies();
553484
const style = document.createElement('style');
554485
style.textContent = OFFLINE_STYLES;
555486
document.head.appendChild(style);
@@ -559,7 +490,6 @@ async function init() {
559490
initBookChapterControls();
560491
initializeAudioControls();
561492
initialiseNarratorSelect();
562-
updateAudioControlsVisibility();
563493
setupNavigationWithURL();
564494
setupPopStateListener();
565495
if (!navigateFromURL()) {
@@ -586,8 +516,10 @@ async function init() {
586516
setupEventListeners();
587517
if ('serviceWorker' in navigator) {
588518
navigator.serviceWorker.register('/sw.js')
589-
.then(reg => navigator.serviceWorker.ready)
590519
.then(reg => {
520+
return navigator.serviceWorker.ready;
521+
})
522+
.then(reg => {
591523
reg.active.postMessage({ type: 'VERSION', version: APP_VERSION });
592524
console.log('Sent version to SW:', APP_VERSION);
593525
})
@@ -596,8 +528,6 @@ async function init() {
596528
console.log('App initialized successfully');
597529
} catch (error) {
598530
handleError(error, 'app initialization');
599-
} finally {
600-
showLoading(false);
601531
}
602532
}
603533
if (document.readyState === 'loading') {

modules/api.js

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { handleError, showError } from '../main.js';
1+
import { clearError, handleError, showError, showLoading } from '../main.js';
22
import { nextPassage } from './navigation.js';
3-
import { extractVerseText } from './passage.js';
3+
import { afterContentLoad, displayPassage, extractVerseText } from './passage.js';
44
import { bookNameMapping, state, saveToStorage } from './state.js';
55
const API_BASE_URL = 'https://bible.helloao.org/api';
66
const AUDIO_TIMEOUT_MS = 10000;
@@ -329,6 +329,39 @@ export function cleanupAudioPlayer() {
329329
}
330330
state.audioPlayer = null;
331331
}
332+
export async function loadPassageFromAPI(passageInfo) {
333+
try {
334+
showLoading(true);
335+
const { book, chapter, startVerse, endVerse, displayRef, translation } = passageInfo;
336+
state.currentPassageReference = displayRef;
337+
const apiTranslation = translation ? apiTranslationCode(translation) : apiTranslationCode(state.settings.bibleTranslation);
338+
const apiBook = getApiBookCode(book);
339+
const chapterData = await fetchChapter(apiTranslation, apiBook, chapter);
340+
state.currentChapterData = chapterData;
341+
const chapterFootnotes = chapterData.chapter.footnotes || [];
342+
const footnoteCounter = { value: 1 };
343+
const contentItems = processChapterContent(
344+
chapterData.chapter.content,
345+
book,
346+
chapter,
347+
startVerse,
348+
endVerse,
349+
chapterFootnotes,
350+
footnoteCounter
351+
);
352+
if (contentItems.length === 0) {
353+
throw new Error('No content found in the requested range');
354+
}
355+
displayPassage(contentItems);
356+
afterContentLoad();
357+
clearError();
358+
updateAudioControls(chapterData.thisChapterAudioLinks);
359+
} catch (error) {
360+
handleError(error, 'loadPassageFromAPI');
361+
} finally {
362+
showLoading(false);
363+
}
364+
}
332365
function processChapterContent(content, book, chapter, startVerse, endVerse, footnotes, footnoteCounter) {
333366
return content
334367
.filter(item => {

0 commit comments

Comments
 (0)