93 lines
4.2 KiB
JavaScript
93 lines
4.2 KiB
JavaScript
/*
|
|||
|
|
* red-letter.js — Words of Christ (red-letter) support. Isolated + additive.
|
||
|
|
*
|
||
|
|
* Loads the bundled verse-level dataset (red-letter.json, derived from the public-domain World
|
||
|
|
* English Bible \wj markers), merges any user reference overrides, and toggles visibility via a
|
||
|
|
* body class (body.red-letter-on) so on/off is instant with no re-render. The actual per-word
|
||
|
|
* marking lives in app.js (window.applyRedLetterMarks), which this module re-invokes once the data
|
||
|
|
* has loaded. Exposed on window.redLetter for the Settings page and (Phase 3) OSC.
|
||
|
|
*/
|
||
|
|
(function () {
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
var ENABLED_KEY = 'redLetterEnabled'; // 'true' | 'false' (default off)
|
||
|
|
var OVERRIDES_KEY = 'redLetterOverrides'; // free text, one reference per line
|
||
|
|
|
||
|
|
var BOOKS = ['Genesis','Exodus','Leviticus','Numbers','Deuteronomy','Joshua','Judges','Ruth',
|
||
|
|
'1 Samuel','2 Samuel','1 Kings','2 Kings','1 Chronicles','2 Chronicles','Ezra','Nehemiah','Esther',
|
||
|
|
'Job','Psalms','Proverbs','Ecclesiastes','Song of Solomon','Isaiah','Jeremiah','Lamentations',
|
||
|
|
'Ezekiel','Daniel','Hosea','Joel','Amos','Obadiah','Jonah','Micah','Nahum','Habakkuk','Zephaniah',
|
||
|
|
'Haggai','Zechariah','Malachi','Matthew','Mark','Luke','John','Acts','Romans','1 Corinthians',
|
||
|
|
'2 Corinthians','Galatians','Ephesians','Philippians','Colossians','1 Thessalonians','2 Thessalonians',
|
||
|
|
'1 Timothy','2 Timothy','Titus','Philemon','Hebrews','James','1 Peter','2 Peter','1 John','2 John',
|
||
|
|
'3 John','Jude','Revelation'];
|
||
|
|
|
||
|
|
function isEnabled() { try { return localStorage.getItem(ENABLED_KEY) === 'true'; } catch (_) { return false; } }
|
||
|
|
function applyBodyClass() { try { document.body.classList.toggle('red-letter-on', isEnabled()); } catch (_) {} }
|
||
|
|
|
||
|
|
function normalizeBook(name) {
|
||
|
|
var n = String(name || '').trim().toLowerCase();
|
||
|
|
for (var i = 0; i < BOOKS.length; i++) if (BOOKS[i].toLowerCase() === n) return BOOKS[i];
|
||
|
|
// loose match (e.g. "song of songs", "psalm")
|
||
|
|
if (n === 'song of songs') return 'Song of Solomon';
|
||
|
|
if (n === 'psalm') return 'Psalms';
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Merge user overrides ("John 3:16", "Matthew 5:3-12", one per line) into the dataset.
|
||
|
|
function mergeOverrides(data) {
|
||
|
|
var raw = '';
|
||
|
|
try { raw = localStorage.getItem(OVERRIDES_KEY) || ''; } catch (_) {}
|
||
|
|
if (!raw.trim()) return;
|
||
|
|
raw.split(/\r?\n/).forEach(function (line) {
|
||
|
|
line = line.trim();
|
||
|
|
if (!line) return;
|
||
|
|
var m = line.match(/^(.+?)\s+(\d+):(\d+)(?:\s*-\s*(\d+))?$/);
|
||
|
|
if (!m) return;
|
||
|
|
var book = normalizeBook(m[1]);
|
||
|
|
if (!book) return;
|
||
|
|
var chapter = m[2], vStart = parseInt(m[3], 10), vEnd = m[4] ? parseInt(m[4], 10) : vStart;
|
||
|
|
if (!data[book]) data[book] = {};
|
||
|
|
if (!Array.isArray(data[book][chapter])) data[book][chapter] = [];
|
||
|
|
for (var v = vStart; v <= vEnd; v++) if (data[book][chapter].indexOf(v) === -1) data[book][chapter].push(v);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function reapply() {
|
||
|
|
try {
|
||
|
|
var app = window.bibleApp;
|
||
|
|
if (app && app.activeTabId != null && typeof window.applyRedLetterMarks === 'function') {
|
||
|
|
window.applyRedLetterMarks(app.activeTabId);
|
||
|
|
}
|
||
|
|
} catch (_) {}
|
||
|
|
}
|
||
|
|
|
||
|
|
function load() {
|
||
|
|
fetch('/red-letter.json', { cache: 'no-store' })
|
||
|
|
.then(function (r) { return r.ok ? r.json() : null; })
|
||
|
|
.then(function (data) {
|
||
|
|
if (!data || typeof data !== 'object') return;
|
||
|
|
try { mergeOverrides(data); } catch (_) {}
|
||
|
|
window.__redLetterData = data;
|
||
|
|
reapply(); // mark any already-rendered chapter now that data exists
|
||
|
|
})
|
||
|
|
.catch(function () { /* offline-safe: red-letter simply stays inactive */ });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Public API (Settings + OSC)
|
||
|
|
window.redLetter = {
|
||
|
|
isEnabled: isEnabled,
|
||
|
|
setEnabled: function (on) {
|
||
|
|
try { localStorage.setItem(ENABLED_KEY, on ? 'true' : 'false'); } catch (_) {}
|
||
|
|
applyBodyClass();
|
||
|
|
},
|
||
|
|
toggle: function () { window.redLetter.setEnabled(!isEnabled()); return isEnabled(); },
|
||
|
|
// Re-read overrides + re-merge + re-mark (called after Settings saves references live, if ever)
|
||
|
|
reloadData: function () { load(); }
|
||
|
|
};
|
||
|
|
|
||
|
|
function init() { applyBodyClass(); load(); }
|
||
|
|
if (document.readyState !== 'loading') init();
|
||
|
|
else document.addEventListener('DOMContentLoaded', init);
|
||
|
|
})();
|