466 lines
17 KiB
JavaScript
466 lines
17 KiB
JavaScript
import API from '../API.js';
|
|
|
|
const BIBLE_TOKEN_REGEX = /@bible:([^\s]+)/gi;
|
|
const DEFAULT_TRANSLATION = "de4e12af7f28f599-01"; // KJV
|
|
|
|
export const BIBLE_BOOK_IDS = {
|
|
"Genesis": "GEN", "Exodus": "EXO", "Leviticus": "LEV", "Numbers": "NUM", "Deuteronomy": "DEU",
|
|
"Joshua": "JOS", "Judges": "JDG", "Ruth": "RUT", "1 Samuel": "1SA", "2 Samuel": "2SA",
|
|
"1 Kings": "1KI", "2 Kings": "2KI", "1 Chronicles": "1CH", "2 Chronicles": "2CH", "Ezra": "EZR",
|
|
"Nehemiah": "NEH", "Esther": "EST", "Job": "JOB", "Psalms": "PSA", "Proverbs": "PRO",
|
|
"Ecclesiastes": "ECC", "Song of Solomon": "SNG", "Isaiah": "ISA", "Jeremiah": "JER",
|
|
"Lamentations": "LAM", "Ezekiel": "EZK", "Daniel": "DAN", "Hosea": "HOS", "Joel": "JOL",
|
|
"Amos": "AMO", "Obadiah": "OBA", "Jonah": "JON", "Micah": "MIC", "Nahum": "NAM",
|
|
"Habakkuk": "HAB", "Zephaniah": "ZEP", "Haggai": "HAG", "Zechariah": "ZEC", "Malachi": "MAL",
|
|
"Matthew": "MAT", "Mark": "MRK", "Luke": "LUK", "John": "JHN", "Acts": "ACT",
|
|
"Romans": "ROM", "1 Corinthians": "1CO", "2 Corinthians": "2CO", "Galatians": "GAL",
|
|
"Ephesians": "EPH", "Philippians": "PHP", "Colossians": "COL", "1 Thessalonians": "1TH",
|
|
"2 Thessalonians": "2TH", "1 Timothy": "1TI", "2 Timothy": "2TI", "Titus": "TIT",
|
|
"Philemon": "PHM", "Hebrews": "HEB", "James": "JAS", "1 Peter": "1PE", "2 Peter": "2PE",
|
|
"1 John": "1JN", "2 John": "2JN", "3 John": "3JN", "Jude": "JUD", "Revelation": "REV"
|
|
};
|
|
|
|
const getNormalizedLocale = (locale = "") => {
|
|
return String(locale || "").toLowerCase().replace("_", "-").trim();
|
|
};
|
|
|
|
const getTranslationForLocale = (locale = "") => {
|
|
const normalized = getNormalizedLocale(locale);
|
|
if (!normalized) return "de4e12af7f28f599-01"; // KJV
|
|
if (normalized.startsWith("en")) return "de4e12af7f28f599-01"; // KJV
|
|
if (normalized.startsWith("es")) return "592420522e16049f-01"; // RVR1909
|
|
// Default to KJV
|
|
return DEFAULT_TRANSLATION;
|
|
};
|
|
|
|
const normalizeReference = (value = "") => {
|
|
try {
|
|
return decodeURIComponent(String(value || ""))
|
|
.replace(/_/g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
} catch (_error) {
|
|
return String(value || "").replace(/_/g, " ").replace(/\s+/g, " ").trim();
|
|
}
|
|
};
|
|
|
|
export const encodeBibleReference = (reference = "") => {
|
|
return String(reference || "").replace(/\s+/g, "_").trim();
|
|
};
|
|
|
|
export const createBibleToken = (reference = "") => {
|
|
const encoded = encodeBibleReference(reference);
|
|
if (!encoded) return "";
|
|
return `@bible:${encoded}`;
|
|
};
|
|
|
|
export const extractBibleReferences = (content = "") => {
|
|
const seen = new Set();
|
|
const refs = [];
|
|
if (!content || typeof content !== "string") return refs;
|
|
|
|
let match;
|
|
while ((match = BIBLE_TOKEN_REGEX.exec(content)) !== null) {
|
|
const normalized = normalizeReference(match[1]);
|
|
if (!normalized || seen.has(normalized)) continue;
|
|
seen.add(normalized);
|
|
refs.push(normalized);
|
|
}
|
|
return refs;
|
|
};
|
|
|
|
export const stripBibleTokens = (content = "") => {
|
|
if (!content || typeof content !== "string") return "";
|
|
return content
|
|
.replace(BIBLE_TOKEN_REGEX, "")
|
|
.replace(/[ \t]{2,}/g, " ")
|
|
.replace(/[ \t]+\n/g, "\n")
|
|
.trim();
|
|
};
|
|
|
|
export const AVAILABLE_TRANSLATIONS = [
|
|
{ id: "de4e12af7f28f599-01", name: "King James Version (English)" },
|
|
{ id: "9879dbb7cfe39e4d-01", name: "World English Bible (English)" },
|
|
{ id: "592420522e16049f-01", name: "Reina Valera 1909 (Spanish)" },
|
|
{ id: "48acedcf8595c754-01", name: "Palabla de Dios para ti (Spanish)" }
|
|
];
|
|
|
|
const parseChapterHtml = (htmlStr) => {
|
|
const parts = String(htmlStr || "").split(/<span[^>]*data-number=\"/);
|
|
const versesMap = {};
|
|
for (let i = 1; i < parts.length; i++) {
|
|
const part = parts[i];
|
|
const numMatch = part.match(/^(\d+)\"/);
|
|
if (!numMatch) continue;
|
|
const vNum = numMatch[1];
|
|
|
|
let text = part.replace(/^[^>]+>/, '');
|
|
text = text.replace(/<[^>]+>/g, '');
|
|
text = text.replace(new RegExp('^' + vNum + '\\s*'), '').trim();
|
|
text = text.replace(/ /g, ' ').replace(/ /g, ' ');
|
|
|
|
if (!versesMap[vNum]) versesMap[vNum] = '';
|
|
versesMap[vNum] += text + ' ';
|
|
}
|
|
return versesMap;
|
|
};
|
|
|
|
const parseChapterJson = (items) => {
|
|
const versesMap = {};
|
|
let currentVerse = null;
|
|
|
|
const extract = (nodes) => {
|
|
if (!nodes) return;
|
|
for (const item of nodes) {
|
|
if (item.name === 'verse') {
|
|
currentVerse = item.attrs?.number || currentVerse;
|
|
if (currentVerse && !versesMap[currentVerse]) versesMap[currentVerse] = "";
|
|
} else if (item.type === 'text') {
|
|
if (item.attrs?.verseId) {
|
|
const vNum = item.attrs.verseId.split('.').pop();
|
|
if (!versesMap[vNum]) versesMap[vNum] = "";
|
|
versesMap[vNum] += item.text;
|
|
} else if (currentVerse) {
|
|
// Skip if the text is exactly the verse number to prevent duplicates
|
|
if (item.text.trim() === String(currentVerse)) {
|
|
continue;
|
|
}
|
|
versesMap[currentVerse] += item.text;
|
|
}
|
|
}
|
|
if (item.items) {
|
|
extract(item.items);
|
|
}
|
|
}
|
|
};
|
|
extract(items);
|
|
return versesMap;
|
|
};
|
|
|
|
export const fetchBibleChapter = async (chapterReference = "", locale = "", customTranslation = "") => {
|
|
const { book, chapter } = parseBibleReference(chapterReference);
|
|
const bookId = BIBLE_BOOK_IDS[book];
|
|
if (!bookId) throw new Error("Missing or unknown Bible reference book");
|
|
|
|
const chapterId = `${bookId}.${chapter}`;
|
|
const preferredTranslation = customTranslation || getTranslationForLocale(locale);
|
|
|
|
// Call our backend API which has the correct key and handles auth
|
|
let response;
|
|
try {
|
|
const url = `/bible/chapters/${chapterId}`;
|
|
const queryParams = { bibleId: preferredTranslation, "content-type": "json" };
|
|
let queryParamsString = "?";
|
|
Object.keys(queryParams).forEach(p => {
|
|
queryParamsString += p + "=" + queryParams[p] + "&";
|
|
});
|
|
|
|
const localBaseUrl = global.baseUrl ?? "http://localhost:3000";
|
|
// To bypass getCall's strict JSON structure mapping (since our backend just passes the API response directly),
|
|
// we can use standard fetch with the same cookie inclusion strategy.
|
|
const fetchRes = await fetch(localBaseUrl + url + queryParamsString, {
|
|
method: 'GET',
|
|
mode: 'cors',
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept-Language': locale,
|
|
'x-app-language': locale,
|
|
}
|
|
});
|
|
if (!fetchRes.ok) throw new Error("Failed to load Bible passage");
|
|
response = await fetchRes.json();
|
|
} catch (error) {
|
|
throw new Error(error.message || "Failed to load Bible passage");
|
|
}
|
|
|
|
if (response?.error) throw new Error(response.error);
|
|
const payload = response.data;
|
|
if (!payload || !payload.content) throw new Error("Invalid Bible data");
|
|
|
|
let versesMap = {};
|
|
if (typeof payload.content === 'string') {
|
|
versesMap = parseChapterHtml(payload.content);
|
|
} else if (Array.isArray(payload.content)) {
|
|
versesMap = parseChapterJson(payload.content);
|
|
}
|
|
|
|
const verses = Object.keys(versesMap)
|
|
.sort((a, b) => Number(a) - Number(b))
|
|
.map(vNum => ({
|
|
verse: Number(vNum),
|
|
text: versesMap[vNum].replace(/\n/g, " ").trim()
|
|
}));
|
|
|
|
return {
|
|
reference: payload.reference || chapterReference,
|
|
verses: verses,
|
|
translation_id: preferredTranslation,
|
|
translation_name: AVAILABLE_TRANSLATIONS.find(t => t.id === preferredTranslation)?.name || "Translation",
|
|
};
|
|
};
|
|
|
|
export const fetchBiblePassage = async (reference = "", locale = "", customTranslation = "") => {
|
|
const { verse } = parseBibleReference(reference);
|
|
const chapterData = await fetchBibleChapter(reference, locale, customTranslation);
|
|
|
|
const verseData = chapterData.verses.find(v => v.verse === Number(verse));
|
|
if (!verseData) throw new Error("Verse not found");
|
|
|
|
return {
|
|
reference: `${chapterData.reference}:${verse}`,
|
|
text: verseData.text,
|
|
translation: chapterData.translation_name,
|
|
};
|
|
};
|
|
|
|
export const fetchBibleReference = async (reference = "", locale = "", customTranslation = "") => {
|
|
return fetchBibleChapter(reference, locale, customTranslation);
|
|
};
|
|
|
|
export const parseBibleReference = (reference = "") => {
|
|
const normalized = normalizeReference(reference);
|
|
const match = normalized.match(/^(.*)\s+(\d+)(?::(\d+)(?:-\d+)?)?$/);
|
|
if (!match) {
|
|
return {
|
|
reference: normalized,
|
|
book: normalized,
|
|
chapter: 1,
|
|
verse: 1,
|
|
chapterReference: normalized,
|
|
};
|
|
}
|
|
const book = (match[1] || "").trim();
|
|
const chapter = Number(match[2] || "1");
|
|
const verse = Number(match[3] || "1");
|
|
return {
|
|
reference: normalized,
|
|
book,
|
|
chapter,
|
|
verse,
|
|
chapterReference: `${book} ${chapter}`,
|
|
};
|
|
};
|
|
|
|
export const BIBLE_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",
|
|
];
|
|
|
|
export const BIBLE_BOOK_CHAPTERS = {
|
|
Genesis: 50,
|
|
Exodus: 40,
|
|
Leviticus: 27,
|
|
Numbers: 36,
|
|
Deuteronomy: 34,
|
|
Joshua: 24,
|
|
Judges: 21,
|
|
Ruth: 4,
|
|
"1 Samuel": 31,
|
|
"2 Samuel": 24,
|
|
"1 Kings": 22,
|
|
"2 Kings": 25,
|
|
"1 Chronicles": 29,
|
|
"2 Chronicles": 36,
|
|
Ezra: 10,
|
|
Nehemiah: 13,
|
|
Esther: 10,
|
|
Job: 42,
|
|
Psalms: 150,
|
|
Proverbs: 31,
|
|
Ecclesiastes: 12,
|
|
"Song of Solomon": 8,
|
|
Isaiah: 66,
|
|
Jeremiah: 52,
|
|
Lamentations: 5,
|
|
Ezekiel: 48,
|
|
Daniel: 12,
|
|
Hosea: 14,
|
|
Joel: 3,
|
|
Amos: 9,
|
|
Obadiah: 1,
|
|
Jonah: 4,
|
|
Micah: 7,
|
|
Nahum: 3,
|
|
Habakkuk: 3,
|
|
Zephaniah: 3,
|
|
Haggai: 2,
|
|
Zechariah: 14,
|
|
Malachi: 4,
|
|
Matthew: 28,
|
|
Mark: 16,
|
|
Luke: 24,
|
|
John: 21,
|
|
Acts: 28,
|
|
Romans: 16,
|
|
"1 Corinthians": 16,
|
|
"2 Corinthians": 13,
|
|
Galatians: 6,
|
|
Ephesians: 6,
|
|
Philippians: 4,
|
|
Colossians: 4,
|
|
"1 Thessalonians": 5,
|
|
"2 Thessalonians": 3,
|
|
"1 Timothy": 6,
|
|
"2 Timothy": 4,
|
|
Titus: 3,
|
|
Philemon: 1,
|
|
Hebrews: 13,
|
|
James: 5,
|
|
"1 Peter": 5,
|
|
"2 Peter": 3,
|
|
"1 John": 5,
|
|
"2 John": 1,
|
|
"3 John": 1,
|
|
Jude: 1,
|
|
Revelation: 22,
|
|
};
|
|
|
|
export const getBookChapterCount = (book = "") => {
|
|
return BIBLE_BOOK_CHAPTERS[book] || 1;
|
|
};
|
|
|
|
const BIBLE_BOOK_TRANSLATIONS = {
|
|
"Genesis": { "es": "Génesis", "fr": "Genèse", "da": "1 Mosebog" },
|
|
"Exodus": { "es": "Éxodo", "fr": "Exode", "da": "2 Mosebog" },
|
|
"Leviticus": { "es": "Levítico", "fr": "Lévitique", "da": "3 Mosebog" },
|
|
"Numbers": { "es": "Números", "fr": "Nombres", "da": "4 Mosebog" },
|
|
"Deuteronomy": { "es": "Deuteronomio", "fr": "Deutéronome", "da": "5 Mosebog" },
|
|
"Joshua": { "es": "Josué", "fr": "Josué", "da": "Josva" },
|
|
"Judges": { "es": "Jueces", "fr": "Juges", "da": "Dommerne" },
|
|
"Ruth": { "es": "Rut", "fr": "Ruth", "da": "Ruth" },
|
|
"1 Samuel": { "es": "1 Samuel", "fr": "1 Samuel", "da": "1 Samuel" },
|
|
"2 Samuel": { "es": "2 Samuel", "fr": "2 Samuel", "da": "2 Samuel" },
|
|
"1 Kings": { "es": "1 Reyes", "fr": "1 Rois", "da": "1 Kongebog" },
|
|
"2 Kings": { "es": "2 Reyes", "fr": "2 Rois", "da": "2 Kongebog" },
|
|
"1 Chronicles": { "es": "1 Crónicas", "fr": "1 Chroniques", "da": "1 Krønikebog" },
|
|
"2 Chronicles": { "es": "2 Crónicas", "fr": "2 Chroniques", "da": "2 Krønikebog" },
|
|
"Ezra": { "es": "Esdras", "fr": "Esdras", "da": "Ezra" },
|
|
"Nehemiah": { "es": "Nehemías", "fr": "Néhémie", "da": "Nehemias" },
|
|
"Esther": { "es": "Ester", "fr": "Esther", "da": "Ester" },
|
|
"Job": { "es": "Job", "fr": "Job", "da": "Job" },
|
|
"Psalms": { "es": "Salmos", "fr": "Psaumes", "da": "Salmerne" },
|
|
"Proverbs": { "es": "Proverbios", "fr": "Proverbes", "da": "Ordsprogene" },
|
|
"Ecclesiastes": { "es": "Eclesiastés", "fr": "Ecclésiaste", "da": "Prædikeren" },
|
|
"Song of Solomon": { "es": "Cantares", "fr": "Cantique des Cantiques", "da": "Højsangen" },
|
|
"Isaiah": { "es": "Isaías", "fr": "Ésaïe", "da": "Esajas" },
|
|
"Jeremiah": { "es": "Jeremías", "fr": "Jérémie", "da": "Jeremias" },
|
|
"Lamentations": { "es": "Lamentaciones", "fr": "Lamentations", "da": "Klagesangene" },
|
|
"Ezekiel": { "es": "Ezequiel", "fr": "Ézéchiel", "da": "Ezekiel" },
|
|
"Daniel": { "es": "Daniel", "fr": "Daniel", "da": "Daniel" },
|
|
"Hosea": { "es": "Oseas", "fr": "Osée", "da": "Hoseas" },
|
|
"Joel": { "es": "Joel", "fr": "Joël", "da": "Joel" },
|
|
"Amos": { "es": "Amós", "fr": "Amos", "da": "Amos" },
|
|
"Obadiah": { "es": "Abdías", "fr": "Abdias", "da": "Obadias" },
|
|
"Jonah": { "es": "Jonás", "fr": "Jonas", "da": "Jonas" },
|
|
"Micah": { "es": "Miqueas", "fr": "Michée", "da": "Mika" },
|
|
"Nahum": { "es": "Nahúm", "fr": "Nahum", "da": "Nahum" },
|
|
"Habakkuk": { "es": "Habacuc", "fr": "Habacuc", "da": "Habakkuk" },
|
|
"Zephaniah": { "es": "Sofonías", "fr": "Sophonie", "da": "Sefanias" },
|
|
"Haggai": { "es": "Hageo", "fr": "Aggée", "da": "Haggaj" },
|
|
"Zechariah": { "es": "Zacarías", "fr": "Zacharie", "da": "Zakarias" },
|
|
"Malachi": { "es": "Malaquías", "fr": "Malachie", "da": "Malakias" },
|
|
"Matthew": { "es": "Mateo", "fr": "Matthieu", "da": "Matthæus" },
|
|
"Mark": { "es": "Marcos", "fr": "Marc", "da": "Markus" },
|
|
"Luke": { "es": "Lucas", "fr": "Luc", "da": "Lukas" },
|
|
"John": { "es": "Juan", "fr": "Jean", "da": "Johannes" },
|
|
"Acts": { "es": "Hechos", "fr": "Actes", "da": "Apostlenes Gerninger" },
|
|
"Romans": { "es": "Romanos", "fr": "Romains", "da": "Romerne" },
|
|
"1 Corinthians": { "es": "1 Corintios", "fr": "1 Corinthiens", "da": "1 Korinther" },
|
|
"2 Corinthians": { "es": "2 Corintios", "fr": "2 Corinthiens", "da": "2 Korinther" },
|
|
"Galatians": { "es": "Gálatas", "fr": "Galates", "da": "Galaterne" },
|
|
"Ephesians": { "es": "Efesios", "fr": "Éphésiens", "da": "Efeserne" },
|
|
"Philippians": { "es": "Filipenses", "fr": "Philippiens", "da": "Filipperne" },
|
|
"Colossians": { "es": "Colosenses", "fr": "Colossiens", "da": "Kolossenserne" },
|
|
"1 Thessalonians": { "es": "1 Tesalonicenses", "fr": "1 Thessaloniciens", "da": "1 Thessaloniker" },
|
|
"2 Thessalonians": { "es": "2 Tesalonicenses", "fr": "2 Thessaloniciens", "da": "2 Thessaloniker" },
|
|
"1 Timothy": { "es": "1 Timoteo", "fr": "1 Timothée", "da": "1 Timotheus" },
|
|
"2 Timothy": { "es": "2 Timoteo", "fr": "2 Timothée", "da": "2 Timotheus" },
|
|
"Titus": { "es": "Tito", "fr": "Tite", "da": "Titus" },
|
|
"Philemon": { "es": "Filemón", "fr": "Philémon", "da": "Filemon" },
|
|
"Hebrews": { "es": "Hebreos", "fr": "Hébreux", "da": "Hebræerne" },
|
|
"James": { "es": "Santiago", "fr": "Jacques", "da": "Jakob" },
|
|
"1 Peter": { "es": "1 Pedro", "fr": "1 Pierre", "da": "1 Peter" },
|
|
"2 Peter": { "es": "2 Pedro", "fr": "2 Pierre", "da": "2 Peter" },
|
|
"1 John": { "es": "1 Juan", "fr": "1 Jean", "da": "1 Johannes" },
|
|
"2 John": { "es": "2 Juan", "fr": "2 Jean", "da": "2 Johannes" },
|
|
"3 John": { "es": "3 Juan", "fr": "3 Jean", "da": "3 Johannes" },
|
|
"Jude": { "es": "Judas", "fr": "Jude", "da": "Judas" },
|
|
"Revelation": { "es": "Apocalipsis", "fr": "Apocalypse", "da": "Åbenbaringen" }
|
|
};
|
|
|
|
export const translateBibleReference = (reference = "", locale = "en") => {
|
|
const lang = String(locale || "en").substring(0, 2).toLowerCase();
|
|
if (lang === "en") return reference;
|
|
|
|
const parsed = parseBibleReference(reference);
|
|
if (!parsed || !parsed.book || !BIBLE_BOOK_TRANSLATIONS[parsed.book]) return reference;
|
|
|
|
const translatedBook = BIBLE_BOOK_TRANSLATIONS[parsed.book][lang] || parsed.book;
|
|
return reference.replace(parsed.book, translatedBook);
|
|
};
|