Files
TaroTime/app/ui-alphabet-gematria.js
2026-03-08 22:24:34 -07:00

369 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
"use strict";
let config = {
getAlphabets: () => null,
getGematriaDb: () => null,
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;
}
function getConfiguredGematriaDb() {
return config.getGematriaDb?.() || null;
}
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;
}
state.loadingPromise = Promise.resolve()
.then(async () => {
const configuredDb = getConfiguredGematriaDb();
if (configuredDb) {
return configuredDb;
}
const referenceData = await window.TarotDataService?.loadReferenceData?.();
return referenceData?.gematriaCiphers || null;
})
.then((db) => {
if (!db) {
throw new Error("Gematria cipher data unavailable from API.");
}
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
};
const configuredDb = getConfiguredGematriaDb();
if (configuredDb) {
state.db = sanitizeGematriaDb(configuredDb);
}
}
window.AlphabetGematriaUi = {
...(window.AlphabetGematriaUi || {}),
init,
refreshScriptMap,
ensureCalculator
};
})();