Files
TaroTime/app/ui-alphabet-gematria.js

369 lines
9.5 KiB
JavaScript
Raw Normal View History

2026-03-07 05:17:50 -08:00
(function () {
"use strict";
let config = {
getAlphabets: () => null,
2026-03-08 22:24:34 -07:00
getGematriaDb: () => null,
2026-03-07 05:17:50 -08:00
getGematriaElements: () => ({
cipherEl: null,
inputEl: null,
resultEl: null,
breakdownEl: null
})
};
const state = {
loadingPromise: null,
db: null,
listenersBound: false,
activeCipherId: "",
inputText: "",
scriptCharMap: new Map()
};
function getAlphabets() {
return config.getAlphabets?.() || null;
}
2026-03-08 22:24:34 -07:00
function getConfiguredGematriaDb() {
return config.getGematriaDb?.() || null;
}
2026-03-07 05:17:50 -08:00
function getElements() {
return config.getGematriaElements?.() || {
cipherEl: null,
inputEl: null,
resultEl: null,
breakdownEl: null
};
}
function getFallbackGematriaDb() {
return {
baseAlphabet: "abcdefghijklmnopqrstuvwxyz",
ciphers: [
{
id: "simple-ordinal",
name: "Simple Ordinal",
description: "A=1 ... Z=26",
values: Array.from({ length: 26 }, (_, index) => index + 1)
}
]
};
}
function normalizeGematriaText(value) {
return String(value || "")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase();
}
function transliterationToBaseLetters(transliteration, baseAlphabet) {
const normalized = normalizeGematriaText(transliteration);
if (!normalized) {
return "";
}
const primaryVariant = normalized.split(/[\/,;|]/)[0] || normalized;
const primaryLetters = [...primaryVariant].filter((char) => baseAlphabet.includes(char));
if (primaryLetters.length) {
return primaryLetters[0];
}
const allLetters = [...normalized].filter((char) => baseAlphabet.includes(char));
return allLetters[0] || "";
}
function addScriptCharMapEntry(map, scriptChar, mappedLetters) {
const key = String(scriptChar || "").trim();
const value = String(mappedLetters || "").trim();
if (!key || !value) {
return;
}
map.set(key, value);
}
function buildGematriaScriptMap(baseAlphabet) {
const map = new Map();
const alphabets = getAlphabets() || {};
const hebrewLetters = Array.isArray(alphabets.hebrew) ? alphabets.hebrew : [];
const greekLetters = Array.isArray(alphabets.greek) ? alphabets.greek : [];
hebrewLetters.forEach((entry) => {
const mapped = transliterationToBaseLetters(entry?.transliteration, baseAlphabet);
addScriptCharMapEntry(map, entry?.char, mapped);
});
greekLetters.forEach((entry) => {
const mapped = transliterationToBaseLetters(entry?.transliteration, baseAlphabet);
addScriptCharMapEntry(map, entry?.char, mapped);
addScriptCharMapEntry(map, entry?.charLower, mapped);
addScriptCharMapEntry(map, entry?.charFinal, mapped);
});
const hebrewFinalForms = {
ך: "k",
ם: "m",
ן: "n",
ף: "p",
ץ: "t"
};
Object.entries(hebrewFinalForms).forEach(([char, mapped]) => {
if (!map.has(char) && baseAlphabet.includes(mapped)) {
addScriptCharMapEntry(map, char, mapped);
}
});
if (!map.has("ς") && baseAlphabet.includes("s")) {
addScriptCharMapEntry(map, "ς", "s");
}
return map;
}
function refreshScriptMap(baseAlphabetOverride = "") {
const db = state.db || getFallbackGematriaDb();
const baseAlphabet = String(baseAlphabetOverride || db.baseAlphabet || "abcdefghijklmnopqrstuvwxyz").toLowerCase();
state.scriptCharMap = buildGematriaScriptMap(baseAlphabet);
}
function sanitizeGematriaDb(db) {
const baseAlphabet = String(db?.baseAlphabet || "abcdefghijklmnopqrstuvwxyz").toLowerCase();
const ciphers = Array.isArray(db?.ciphers)
? db.ciphers
.map((cipher) => {
const id = String(cipher?.id || "").trim();
const name = String(cipher?.name || "").trim();
const values = Array.isArray(cipher?.values)
? cipher.values.map((value) => Number(value))
: [];
if (!id || !name || values.length !== baseAlphabet.length || values.some((value) => !Number.isFinite(value))) {
return null;
}
return {
id,
name,
description: String(cipher?.description || "").trim(),
values
};
})
.filter(Boolean)
: [];
if (!ciphers.length) {
return getFallbackGematriaDb();
}
return {
baseAlphabet,
ciphers
};
}
async function loadGematriaDb() {
if (state.db) {
return state.db;
}
if (state.loadingPromise) {
return state.loadingPromise;
}
2026-03-08 22:24:34 -07:00
state.loadingPromise = Promise.resolve()
.then(async () => {
const configuredDb = getConfiguredGematriaDb();
if (configuredDb) {
return configuredDb;
2026-03-07 05:17:50 -08:00
}
2026-03-08 22:24:34 -07:00
const referenceData = await window.TarotDataService?.loadReferenceData?.();
return referenceData?.gematriaCiphers || null;
2026-03-07 05:17:50 -08:00
})
.then((db) => {
2026-03-08 22:24:34 -07:00
if (!db) {
throw new Error("Gematria cipher data unavailable from API.");
}
2026-03-07 05:17:50 -08:00
state.db = sanitizeGematriaDb(db);
return state.db;
})
.catch(() => {
state.db = getFallbackGematriaDb();
return state.db;
})
.finally(() => {
state.loadingPromise = null;
});
return state.loadingPromise;
}
function getActiveGematriaCipher() {
const db = state.db || getFallbackGematriaDb();
const ciphers = Array.isArray(db.ciphers) ? db.ciphers : [];
if (!ciphers.length) {
return null;
}
const selectedId = state.activeCipherId || ciphers[0].id;
return ciphers.find((cipher) => cipher.id === selectedId) || ciphers[0];
}
function renderGematriaCipherOptions() {
const { cipherEl } = getElements();
if (!cipherEl) {
return;
}
const db = state.db || getFallbackGematriaDb();
const ciphers = Array.isArray(db.ciphers) ? db.ciphers : [];
cipherEl.innerHTML = "";
ciphers.forEach((cipher) => {
const option = document.createElement("option");
option.value = cipher.id;
option.textContent = cipher.name;
if (cipher.description) {
option.title = cipher.description;
}
cipherEl.appendChild(option);
});
const activeCipher = getActiveGematriaCipher();
state.activeCipherId = activeCipher?.id || "";
cipherEl.value = state.activeCipherId;
}
function computeGematria(text, cipher, baseAlphabet) {
const normalizedInput = normalizeGematriaText(text);
const scriptMap = state.scriptCharMap instanceof Map
? state.scriptCharMap
: new Map();
const letterParts = [];
let total = 0;
let count = 0;
[...normalizedInput].forEach((char) => {
const mappedLetters = baseAlphabet.includes(char)
? char
: (scriptMap.get(char) || "");
if (!mappedLetters) {
return;
}
[...mappedLetters].forEach((mappedChar) => {
const index = baseAlphabet.indexOf(mappedChar);
if (index < 0) {
return;
}
const value = Number(cipher.values[index]);
if (!Number.isFinite(value)) {
return;
}
count += 1;
total += value;
letterParts.push(`${mappedChar.toUpperCase()}(${value})`);
});
});
return {
total,
count,
breakdown: letterParts.join(" + ")
};
}
function renderGematriaResult() {
const { resultEl, breakdownEl } = getElements();
if (!resultEl || !breakdownEl) {
return;
}
const db = state.db || getFallbackGematriaDb();
if (!(state.scriptCharMap instanceof Map) || !state.scriptCharMap.size) {
refreshScriptMap(db.baseAlphabet);
}
const cipher = getActiveGematriaCipher();
if (!cipher) {
resultEl.textContent = "Total: --";
breakdownEl.textContent = "No ciphers available.";
return;
}
const { total, count, breakdown } = computeGematria(state.inputText, cipher, db.baseAlphabet);
resultEl.textContent = `Total: ${total}`;
if (!count) {
breakdownEl.textContent = `Using ${cipher.name}. Enter English, Greek, or Hebrew letters to calculate.`;
return;
}
breakdownEl.textContent = `${cipher.name} · ${count} letters · ${breakdown} = ${total}`;
}
function bindGematriaListeners() {
const { cipherEl, inputEl } = getElements();
if (state.listenersBound || !cipherEl || !inputEl) {
return;
}
cipherEl.addEventListener("change", () => {
state.activeCipherId = String(cipherEl.value || "").trim();
renderGematriaResult();
});
inputEl.addEventListener("input", () => {
state.inputText = inputEl.value || "";
renderGematriaResult();
});
state.listenersBound = true;
}
function ensureCalculator() {
const { cipherEl, inputEl, resultEl, breakdownEl } = getElements();
if (!cipherEl || !inputEl || !resultEl || !breakdownEl) {
return;
}
bindGematriaListeners();
if (inputEl.value !== state.inputText) {
inputEl.value = state.inputText;
}
void loadGematriaDb().then(() => {
refreshScriptMap((state.db || getFallbackGematriaDb()).baseAlphabet);
renderGematriaCipherOptions();
renderGematriaResult();
});
}
function init(nextConfig = {}) {
config = {
...config,
...nextConfig
};
2026-03-08 22:24:34 -07:00
const configuredDb = getConfiguredGematriaDb();
if (configuredDb) {
state.db = sanitizeGematriaDb(configuredDb);
}
2026-03-07 05:17:50 -08:00
}
window.AlphabetGematriaUi = {
...(window.AlphabetGematriaUi || {}),
init,
refreshScriptMap,
ensureCalculator
};
})();