Skip to content

Commit de5b672

Browse files
committed
feat: Update UI components and settings functionality
- Minor CSS and HTML adjustments - Remove labels for color themes - Add labels to settings, reference Bible, and audio buttons - Rename shortcut: “Toggle Reference Panel” → “Toggle Reference Bible” - Move narrator dropdown to settings menu - Add shortcuts for toggling notes and sidebar - Add font size slider for Scripture text in settings - Update export JSON to include verse content - Fix reference Bible translation resetting to ASV on refresh - Rename export files for consistency - Implement cache invalidation for version updates
1 parent 1ad7d33 commit de5b672

23 files changed

Lines changed: 642 additions & 193 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ This project was developed with assistance from AI tools including [Gab AI](http
5656
| Shortcut | Action |
5757
|----------|--------|
5858
| `Alt + B` | Toggle Reference Bible |
59+
| `Alt + N` | Toggle Notes |
60+
| `Alt + S` | Toggle Sidebar |
5961
| `Alt + P` | Play/Pause Audio |
6062
| `Alt + R` | Random Passage |
6163
| `F1` | Show Help Modal |

dev_tools/Minify-JS.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ $files = @(
1111
"../src/modules/settings.js",
1212
"../src/modules/state.js",
1313
"../src/modules/strongs.js",
14-
"../src/modules/ui.js"
14+
"../src/modules/ui.js",
15+
"../src/sw.js"
1516
)
1617

1718
Write-Host "Checking files..." -ForegroundColor Yellow

index.html

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

main.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
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, openSettings, saveSettings } from './modules/settings.js'
6-
import { BOOK_ORDER, updateBibleGatewayVersion, loadFromCookies, loadFromStorage, saveToCookies, saveToStorage, state } from './modules/state.js'
5+
import { clearCache, closeSettings, deleteAllData, exportData, importData, initializeAudioControls, initialiseNarratorSelect, openSettings, saveSettings } from './modules/settings.js'
6+
import { APP_VERSION, BOOK_ORDER, updateBibleGatewayVersion, loadFromCookies, loadFromStorage, saveToCookies, saveToStorage, state } from './modules/state.js'
77
import { closeStrongsPopup, showStrongsReference } from './modules/strongs.js'
88
import { exportNotes, initResizeHandles, insertMarkdown, restoreBookChapterUI, restorePanelStates, restoreSidebarState, switchNotesView, togglePanelCollapse, toggleReferencePanel, toggleSection,
9-
updateMarkdownPreview, updateReferencePanel } from './modules/ui.js'
9+
updateMarkdownPreview, updateReferencePanel, updateScriptureFontSize } from './modules/ui.js'
1010
const OFFLINE_STYLES = `
1111
#offlineIndicator {
1212
position: fixed;
@@ -275,7 +275,11 @@ function setupAudioControls() {
275275
const playBtn = document.querySelector('.play-audio-btn');
276276
const pauseBtn = document.querySelector('.pause-audio-btn');
277277
const stopBtn = document.querySelector('.stop-audio-btn');
278-
const narratorSelect = document.querySelector('.narrator-select');
278+
document.addEventListener('change', (e) => {
279+
if (e.target && e.target.matches('.narrator-select')) {
280+
handleNarratorChange(e);
281+
}
282+
});
279283
if (playBtn) {
280284
playBtn.addEventListener('click', handleAudioPlayback);
281285
}
@@ -285,9 +289,6 @@ function setupAudioControls() {
285289
if (stopBtn) {
286290
stopBtn.addEventListener('click', stopChapterAudio);
287291
}
288-
if (narratorSelect) {
289-
narratorSelect.addEventListener('change', handleNarratorChange);
290-
}
291292
});
292293
}
293294
function handleAudioPlayback() {
@@ -483,6 +484,7 @@ async function init() {
483484
window.addEventListener('offline', () => updateOfflineStatus(true));
484485
initBookChapterControls();
485486
initializeAudioControls();
487+
initialiseNarratorSelect();
486488
setupNavigationWithURL();
487489
setupPopStateListener();
488490
if (!navigateFromURL()) {
@@ -496,11 +498,25 @@ async function init() {
496498
applyColorTheme();
497499
restoreSidebarState();
498500
restorePanelStates();
501+
if (typeof updateScriptureFontSize === 'function') {
502+
updateScriptureFontSize(state.settings.fontSize);
503+
}
499504
updateHeaderTitle();
500505
initResizeHandles();
501506
switchNotesView(state.settings.notesView || 'text');
502507
updateBibleGatewayVersion();
503508
setupEventListeners();
509+
if ('serviceWorker' in navigator) {
510+
navigator.serviceWorker.register('/sw.js')
511+
.then(reg => {
512+
return navigator.serviceWorker.ready;
513+
})
514+
.then(reg => {
515+
reg.active.postMessage({ type: 'VERSION', version: APP_VERSION });
516+
console.log('Sent version to SW:', APP_VERSION);
517+
})
518+
.catch(err => console.error('SW registration failed:', err));
519+
}
504520
console.log('App initialized successfully');
505521
} catch (error) {
506522
handleError(error, 'app initialization');

modules/highlights.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ export function scrollToVerse(verseNumber) {
285285
console.error('Error scrolling to verse:', error);
286286
}
287287
}
288-
function getVerseTextFromStorage(reference) {
288+
export function getVerseTextFromStorage(reference) {
289289
try {
290290
const cachedVerses = JSON.parse(localStorage.getItem('cachedVerses') || '{}');
291291
return cachedVerses[reference] || null;

modules/hotkeys.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
import { escapeHTML } from '../main.js';
33
import { nextPassage, prevPassage, randomPassage, updateManualNavigation } from './navigation.js';
44
import { BOOK_ORDER, saveToStorage, state } from './state.js';
5+
import { togglePanelCollapse } from './ui.js';
56
const DEFAULT_HOTKEYS = {
6-
toggleReferencePanel: { key: 'b', altKey: false, shiftKey: false, ctrlKey: true },
7+
toggleReferencePanel: { key: 'b', altKey: true, shiftKey: false, ctrlKey: false },
8+
toggleNotes: { key: 'n', altKey: true, shiftKey: false, ctrlKey: false },
9+
toggleSidebar: { key: 's', altKey: true, shiftKey: false, ctrlKey: false },
710
prevChapter: { key: 'ArrowLeft', altKey: true, shiftKey: false, ctrlKey: false },
811
nextChapter: { key: 'ArrowRight', altKey: true, shiftKey: false, ctrlKey: false },
912
prevBook: { key: 'ArrowUp', altKey: true, shiftKey: true, ctrlKey: false },
@@ -88,6 +91,8 @@ function getHotkeyAction(event) {
8891
function executeHotkeyAction(action) {
8992
const actions = {
9093
toggleReferencePanel: toggleReferencePanel,
94+
toggleNotes: () => togglePanelCollapse('notesSection'),
95+
toggleSidebar: () => togglePanelCollapse('sidebar'),
9196
prevChapter: prevPassage,
9297
nextChapter: nextPassage,
9398
prevBook: () => navigateToAdjacentBook(-1),
@@ -245,7 +250,9 @@ function populateHotkeysList() {
245250
if (!hotkeysList || !enabledCheckbox) return;
246251
enabledCheckbox.checked = state.settings.hotkeysEnabled;
247252
const hotkeyDefinitions = [
248-
{ action: 'toggleReferencePanel', label: 'Toggle Reference Panel' },
253+
{ action: 'toggleReferencePanel', label: 'Toggle Reference Bible' },
254+
{ action: 'toggleNotes', label: 'Toggle Notes' },
255+
{ action: 'toggleSidebar', label: 'Toggle Sidebar' },
249256
{ action: 'prevChapter', label: 'Previous Chapter' },
250257
{ action: 'nextChapter', label: 'Next Chapter' },
251258
{ action: 'prevBook', label: 'Previous Book' },

modules/settings.js

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
import { stopChapterAudio } from './api.js';
2+
import { getVerseTextFromStorage } from './highlights.js';
23
import { applyColorTheme, applyTheme, getFormattedDateForFilename, getFormattedDateForDisplay, handleError, showLoading } from '../main.js';
34
import { populateChapterDropdown, updateChapterDropdownVisibility } from './navigation.js';
45
import { loadPassage } from './passage.js';
56
import { APP_VERSION, saveToCookies, saveToStorage, state, updateBibleGatewayVersion, updateURL } from './state.js';
6-
import { restorePanelStates, restoreSidebarState, switchNotesView, updateMarkdownPreview, updateReferencePanel } from './ui.js';
7+
import { restorePanelStates, restoreSidebarState, switchNotesView, updateMarkdownPreview, updateReferencePanel, updateScriptureFontSize } from './ui.js';
78
export function exportData() {
89
try {
910
const fileDate = getFormattedDateForFilename();
1011
const exportDate = getFormattedDateForDisplay();
12+
const highlightsWithText = Object.entries(state.highlights).reduce(
13+
(acc, [reference, color]) => {
14+
const verseText = getVerseTextFromStorage(reference) || '';
15+
acc[reference] = { color: color, text: verseText };
16+
return acc;
17+
},
18+
{}
19+
);
1120
const payload = {
1221
version: '2.0',
1322
exportDate: exportDate,
14-
highlights: { ...state.highlights },
23+
highlights: highlightsWithText,
1524
notes: state.notes,
1625
settings: { ...state.settings }
1726
};
@@ -21,7 +30,7 @@ export function exportData() {
2130
const url = URL.createObjectURL(blob);
2231
const link = document.createElement('a');
2332
link.href = url;
24-
link.download = `provinent-bible-study-backup-${fileDate}.json`;
33+
link.download = `provinent-scripture-study-backup-${fileDate}.json`;
2534
link.style.display = 'none';
2635
document.body.appendChild(link);
2736
link.click();
@@ -73,7 +82,26 @@ function confirmImport() {
7382
}
7483
function applyImportedData(incoming) {
7584
Object.assign(state.settings, incoming.settings);
76-
state.highlights = incoming.highlights || {};
85+
const incomingHighlights = incoming.highlights || {};
86+
const colorMap = {};
87+
const verseTextMap = {};
88+
Object.entries(incomingHighlights).forEach(([reference, data]) => {
89+
if (typeof data === 'string') {
90+
colorMap[reference] = data;
91+
} else if (data && typeof data === 'object') {
92+
colorMap[reference] = data.color;
93+
verseTextMap[reference] = data.text;
94+
}
95+
});
96+
state.highlights = colorMap;
97+
try {
98+
const cachedRaw = localStorage.getItem('cachedVerses');
99+
const cached = cachedRaw ? JSON.parse(cachedRaw) : {};
100+
const merged = { ...cached, ...verseTextMap };
101+
localStorage.setItem('cachedVerses', JSON.stringify(merged));
102+
} catch (e) {
103+
console.error('Failed to merge cached verses on import:', e);
104+
}
77105
state.notes = incoming.notes || '';
78106
}
79107
function saveImportedData() {
@@ -124,9 +152,21 @@ function populateSettingsForm() {
124152
const translationSelect = document.getElementById('bibleTranslationSetting');
125153
const audioToggle = document.getElementById('audioControlsToggle');
126154
const versionElement = document.getElementById('appVersion');
155+
const fontSizeSlider = document.getElementById('fontSizeSlider');
156+
const fontSizeValue = document.getElementById('fontSizeValue');
127157
if (translationSelect) translationSelect.value = state.settings.bibleTranslation;
128158
if (audioToggle) audioToggle.checked = state.settings.audioControlsVisible;
129159
if (versionElement) versionElement.textContent = APP_VERSION;
160+
if (fontSizeSlider) {
161+
fontSizeSlider.value = state.settings.fontSize || 16;
162+
fontSizeSlider.addEventListener('input', () => {
163+
const val = fontSizeSlider.value;
164+
if (fontSizeValue) fontSizeValue.textContent = `${val}px`;
165+
});
166+
}
167+
if (fontSizeValue) {
168+
fontSizeValue.textContent = `${state.settings.fontSize || 16}px`;
169+
}
130170
updateColorThemeSelection();
131171
}
132172
function updateColorThemeSelection() {
@@ -168,10 +208,14 @@ function getSettingsFromForm() {
168208
const translationSelect = document.getElementById('bibleTranslationSetting');
169209
const audioToggle = document.getElementById('audioControlsToggle');
170210
const selectedTheme = document.querySelector('.color-theme-option.selected');
211+
const narratorSelect = document.getElementById('narratorSelect');
212+
const fontSizeSlider = document.getElementById('fontSizeSlider');
171213
return {
172214
translation: translationSelect?.value || state.settings.bibleTranslation,
173215
audioControlsVisible: audioToggle?.checked ?? state.settings.audioControlsVisible,
174-
colorTheme: selectedTheme?.dataset.theme || state.settings.colorTheme
216+
colorTheme: selectedTheme?.dataset.theme || state.settings.colorTheme,
217+
narrator: narratorSelect?.value || state.settings.audioNarrator,
218+
fontSize: fontSizeSlider ? parseInt(fontSizeSlider.value, 10) : state.settings.fontSize
175219
};
176220
}
177221
function validateSettings(settings) {
@@ -186,6 +230,8 @@ async function applyNewSettings(newSettings) {
186230
state.settings.bibleTranslation = newSettings.translation;
187231
state.settings.audioControlsVisible = newSettings.audioControlsVisible;
188232
state.settings.colorTheme = newSettings.colorTheme;
233+
state.settings.audioNarrator = newSettings.narrator || state.settings.audioNarrator;
234+
state.settings.fontSize = newSettings.fontSize ?? state.settings.fontSize;
189235
updateURL(newSettings.translation, state.settings.manualBook, state.settings.manualChapter, 'push');
190236
updateAudioControlsVisibility();
191237
updateBibleGatewayVersion();
@@ -200,6 +246,9 @@ function updateUIAfterSettingsChange() {
200246
if (referenceTranslationSelect) {
201247
referenceTranslationSelect.value = state.settings.referenceVersion;
202248
}
249+
if (typeof updateScriptureFontSize === 'function') {
250+
updateScriptureFontSize(state.settings.fontSize);
251+
}
203252
}
204253
async function reloadPassageWithNewSettings() {
205254
await loadPassage();
@@ -288,4 +337,10 @@ export function initializeAudioControls() {
288337
saveToStorage();
289338
}
290339
updateAudioControlsVisibility();
340+
}
341+
export function initialiseNarratorSelect() {
342+
const sel = document.getElementById('narratorSelect');
343+
if (sel) {
344+
sel.value = state.settings.audioNarrator || 'gilbert';
345+
}
291346
}

modules/state.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { handleError } from '../main.js';
2-
export const APP_VERSION = '1.7.2025.11.17';
2+
export const APP_VERSION = '1.8.2025.11.18';
33
const SAVE_DEBOUNCE_MS = 500;
44
const COOKIE_LENGTH = 10;
55
let saveTimeout = null;
@@ -90,10 +90,13 @@ export const state = {
9090
referencePanel: 350,
9191
scriptureSection: null,
9292
notesSection: 350
93-
}
93+
},
94+
fontSize: 16
9495
},
9596
hotkeys: {
96-
toggleReferencePanel: { key: 'b', altKey: false, shiftKey: false, ctrlKey: true },
97+
toggleReferencePanel: { key: 'b', altKey: true, shiftKey: false, ctrlKey: false },
98+
toggleNotes: { key: 'n', altKey: true, shiftKey: false, ctrlKey: false },
99+
toggleSidebar: { key: 's', altKey: true, shiftKey: false, ctrlKey: false },
97100
prevChapter: { key: 'ArrowLeft', altKey: true, shiftKey: false, ctrlKey: false },
98101
nextChapter: { key: 'ArrowRight', altKey: true, shiftKey: false, ctrlKey: false },
99102
prevBook: { key: 'ArrowUp', altKey: true, shiftKey: true, ctrlKey: false },

modules/ui.js

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@ const PANEL_LIMITS = {
1818
scriptureSection: { min: 300, max: 1200 },
1919
notesSection: { min: 250, max: 800 }
2020
};
21+
export function updateScriptureFontSize(sizePx) {
22+
try {
23+
const size = Number(sizePx);
24+
if (Number.isNaN(size) || size < 10 || size > 36) return;
25+
const scriptureSection = document.getElementById('scriptureSection');
26+
if (scriptureSection) {
27+
scriptureSection.style.fontSize = `${size}px`;
28+
}
29+
const headerTitle = document.getElementById('passageHeaderTitle');
30+
const headerRef = document.getElementById('passageReference');
31+
if (headerTitle) headerTitle.style.fontSize = '';
32+
if (headerRef) headerRef.style.fontSize = '';
33+
state.settings.fontSize = size;
34+
saveToStorage();
35+
} catch (err) {
36+
console.error('Failed to update scripture font size:', err);
37+
}
38+
}
2139
export function switchNotesView(view) {
2240
try {
2341
state.settings.notesView = view;
@@ -104,7 +122,7 @@ export function exportNotes() {
104122
const url = URL.createObjectURL(blob);
105123
const link = document.createElement('a');
106124
link.href = url;
107-
link.download = `provinent-bible-study-notes-${fileDate}.md`;
125+
link.download = `provinent-scripture-study-notes-${fileDate}.md`;
108126
link.style.display = 'none';
109127
document.body.appendChild(link);
110128
link.click();
@@ -375,19 +393,19 @@ export function restorePanelStates() {
375393
panel.classList.add('panel-collapsed');
376394
}
377395
});
396+
const sourceSelect = document.getElementById('referenceSource');
397+
const translationSelect = document.getElementById('referenceTranslation');
398+
if (sourceSelect) {
399+
sourceSelect.value = state.settings.referenceSource || 'biblegateway';
400+
}
401+
if (translationSelect) {
402+
translationSelect.value = state.settings.referenceVersion || 'NASB1995';
403+
}
378404
if (state.settings.referencePanelOpen) {
379405
const referencePanel = document.getElementById('referencePanel');
380406
if (referencePanel) {
381407
referencePanel.classList.add('active');
382408
}
383-
const sourceSelect = document.getElementById('referenceSource');
384-
const translationSelect = document.getElementById('referenceTranslation');
385-
if (sourceSelect) {
386-
sourceSelect.value = state.settings.referenceSource || 'biblegateway';
387-
}
388-
if (translationSelect) {
389-
translationSelect.value = state.settings.referenceVersion || 'NASB1995';
390-
}
391409
updateReferencePanel();
392410
}
393411
} catch (error) {

0 commit comments

Comments
 (0)