anagram and dictionary added

This commit is contained in:
2026-03-09 14:43:03 -07:00
parent 32002c7770
commit 9c6438d10e
5 changed files with 202 additions and 30 deletions

View File

@@ -372,6 +372,12 @@
})); }));
} }
async function loadWordAnagrams(text) {
return fetchJson(buildApiUrl("/api/v1/words/anagrams", {
text
}));
}
async function loadDeckOptions(forceRefresh = false) { async function loadDeckOptions(forceRefresh = false) {
if (!forceRefresh && deckOptionsCache) { if (!forceRefresh && deckOptionsCache) {
return deckOptionsCache; return deckOptionsCache;
@@ -502,6 +508,7 @@
loadDeckManifest, loadDeckManifest,
loadDeckOptions, loadDeckOptions,
loadGematriaWordsByValue, loadGematriaWordsByValue,
loadWordAnagrams,
loadQuizCategories, loadQuizCategories,
loadQuizTemplates, loadQuizTemplates,
loadTarotCards, loadTarotCards,

View File

@@ -2326,7 +2326,17 @@
justify-content: flex-end; justify-content: flex-end;
} }
.alpha-gematria-toggle { .alpha-tool-mode-group {
display: inline-flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin: 0;
padding: 0;
border: none;
}
.alpha-tool-mode-option {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
@@ -2335,6 +2345,16 @@
cursor: pointer; cursor: pointer;
-webkit-user-select: none; -webkit-user-select: none;
user-select: none; user-select: none;
padding: 6px 10px;
border: 1px solid #3f3f46;
border-radius: 999px;
background: #101018;
}
.alpha-tool-mode-option:has(input:checked) {
border-color: #6366f1;
background: #1c1b35;
color: #eef2ff;
} }
.alpha-gematria-controls { .alpha-gematria-controls {

View File

@@ -9,7 +9,7 @@
inputEl: null, inputEl: null,
resultEl: null, resultEl: null,
breakdownEl: null, breakdownEl: null,
modeToggleEl: null, modeEls: [],
matchesEl: null, matchesEl: null,
inputLabelEl: null, inputLabelEl: null,
cipherLabelEl: null cipherLabelEl: null
@@ -23,10 +23,13 @@
activeCipherId: "", activeCipherId: "",
forwardInputText: "", forwardInputText: "",
reverseInputText: "", reverseInputText: "",
anagramInputText: "",
activeMode: "forward", activeMode: "forward",
scriptCharMap: new Map(), scriptCharMap: new Map(),
reverseLookupCache: new Map(), reverseLookupCache: new Map(),
reverseRequestId: 0 anagramLookupCache: new Map(),
reverseRequestId: 0,
anagramRequestId: 0
}; };
function getAlphabets() { function getAlphabets() {
@@ -43,7 +46,7 @@
inputEl: null, inputEl: null,
resultEl: null, resultEl: null,
breakdownEl: null, breakdownEl: null,
modeToggleEl: null, modeEls: [],
matchesEl: null, matchesEl: null,
inputLabelEl: null, inputLabelEl: null,
cipherLabelEl: null cipherLabelEl: null
@@ -54,10 +57,20 @@
return state.activeMode === "reverse"; return state.activeMode === "reverse";
} }
function isAnagramMode() {
return state.activeMode === "anagram";
}
function getCurrentInputText() { function getCurrentInputText() {
return isReverseMode() if (isReverseMode()) {
? state.reverseInputText return state.reverseInputText;
: state.forwardInputText; }
if (isAnagramMode()) {
return state.anagramInputText;
}
return state.forwardInputText;
} }
function formatCount(value) { function formatCount(value) {
@@ -295,39 +308,50 @@
matchesEl.hidden = true; matchesEl.hidden = true;
} }
function getModeElements(modeEls) {
return Array.isArray(modeEls)
? modeEls.filter((element) => element instanceof HTMLInputElement)
: [];
}
function updateModeUi() { function updateModeUi() {
const { const {
cipherEl, cipherEl,
inputEl, inputEl,
modeToggleEl, modeEls,
matchesEl, matchesEl,
inputLabelEl, inputLabelEl,
cipherLabelEl cipherLabelEl
} = getElements(); } = getElements();
const reverseMode = isReverseMode(); const reverseMode = isReverseMode();
const anagramMode = isAnagramMode();
const radioEls = getModeElements(modeEls);
if (modeToggleEl) { radioEls.forEach((element) => {
modeToggleEl.checked = reverseMode; element.checked = String(element.value || "") === state.activeMode;
} });
if (inputLabelEl) { if (inputLabelEl) {
inputLabelEl.textContent = reverseMode ? "Value" : "Text"; inputLabelEl.textContent = reverseMode ? "Value" : (anagramMode ? "Letters" : "Text");
} }
if (cipherLabelEl) { if (cipherLabelEl) {
cipherLabelEl.textContent = reverseMode ? "Cipher (disabled in reverse mode)" : "Cipher"; cipherLabelEl.textContent = (reverseMode || anagramMode) ? "Cipher (not used in this mode)" : "Cipher";
} }
if (cipherEl) { if (cipherEl) {
cipherEl.disabled = reverseMode; const disableCipher = reverseMode || anagramMode;
cipherEl.closest(".alpha-gematria-field")?.classList.toggle("is-disabled", reverseMode); cipherEl.disabled = disableCipher;
cipherEl.closest(".alpha-gematria-field")?.classList.toggle("is-disabled", disableCipher);
} }
if (inputEl) { if (inputEl) {
inputEl.placeholder = reverseMode ? "Enter a whole number, e.g. 33" : "Type or paste text"; inputEl.placeholder = reverseMode
? "Enter a whole number, e.g. 33"
: (anagramMode ? "Type letters or a word, e.g. listen" : "Type or paste text");
inputEl.inputMode = reverseMode ? "numeric" : "text"; inputEl.inputMode = reverseMode ? "numeric" : "text";
inputEl.spellcheck = !reverseMode; inputEl.spellcheck = !(reverseMode || anagramMode);
const nextValue = getCurrentInputText(); const nextValue = getCurrentInputText();
if (inputEl.value !== nextValue) { if (inputEl.value !== nextValue) {
@@ -335,7 +359,7 @@
} }
} }
if (!reverseMode) { if (!reverseMode && !anagramMode) {
clearReverseMatches(matchesEl); clearReverseMatches(matchesEl);
} }
} }
@@ -369,6 +393,17 @@
return payload; return payload;
} }
async function loadAnagramLookup(text) {
const cacheKey = String(text || "").trim().toLowerCase();
if (state.anagramLookupCache.has(cacheKey)) {
return state.anagramLookupCache.get(cacheKey);
}
const payload = await window.TarotDataService?.loadWordAnagrams?.(text);
state.anagramLookupCache.set(cacheKey, payload);
return payload;
}
function renderReverseLookupMatches(payload, numericValue) { function renderReverseLookupMatches(payload, numericValue) {
const { resultEl, breakdownEl, matchesEl } = getElements(); const { resultEl, breakdownEl, matchesEl } = getElements();
if (!resultEl || !breakdownEl || !matchesEl) { if (!resultEl || !breakdownEl || !matchesEl) {
@@ -489,6 +524,93 @@
} }
} }
function renderAnagramMatches(payload) {
const { resultEl, breakdownEl, matchesEl } = getElements();
if (!resultEl || !breakdownEl || !matchesEl) {
return;
}
const matches = Array.isArray(payload?.matches) ? payload.matches : [];
const count = Number(payload?.count);
const displayCount = Number.isFinite(count) ? count : matches.length;
const visibleMatches = matches.slice(0, 120);
const letterCount = Number(payload?.letterCount);
resultEl.textContent = `Anagrams: ${formatCount(displayCount)}`;
if (!displayCount) {
breakdownEl.textContent = "No exact dictionary anagrams matched these letters.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Try another letter set or a different spelling.");
return;
}
breakdownEl.textContent = `Found ${formatCount(displayCount)} anagrams for ${formatCount(letterCount)} letters.${displayCount > visibleMatches.length ? ` Showing first ${formatCount(visibleMatches.length)}.` : ""}`;
const fragment = document.createDocumentFragment();
visibleMatches.forEach((match) => {
const cardEl = document.createElement("article");
cardEl.className = "alpha-gematria-match";
const wordEl = document.createElement("div");
wordEl.className = "alpha-gematria-match-word";
wordEl.textContent = String(match?.word || "--");
cardEl.appendChild(wordEl);
const definition = String(match?.definition || "").trim();
if (definition) {
const definitionEl = document.createElement("div");
definitionEl.className = "alpha-gematria-match-definition";
definitionEl.textContent = definition;
cardEl.appendChild(definitionEl);
}
fragment.appendChild(cardEl);
});
matchesEl.replaceChildren(fragment);
matchesEl.hidden = false;
}
async function renderAnagramResult() {
const { resultEl, breakdownEl, matchesEl } = getElements();
if (!resultEl || !breakdownEl || !matchesEl) {
return;
}
const rawText = state.anagramInputText;
if (!String(rawText || "").trim()) {
resultEl.textContent = "Anagrams: --";
breakdownEl.textContent = "Enter letters to search for exact dictionary anagrams.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Anagram Maker finds exact word anagrams from the API word index.");
return;
}
const requestId = state.anagramRequestId + 1;
state.anagramRequestId = requestId;
resultEl.textContent = "Anagrams: --";
breakdownEl.textContent = "Searching anagram index...";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Loading anagrams...");
try {
const payload = await loadAnagramLookup(rawText);
if (requestId !== state.anagramRequestId || !isAnagramMode()) {
return;
}
renderAnagramMatches(payload);
} catch {
if (requestId !== state.anagramRequestId || !isAnagramMode()) {
return;
}
resultEl.textContent = "Anagrams: --";
breakdownEl.textContent = "Anagram lookup is unavailable right now.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Unable to load anagrams from the API.");
}
}
function computeGematria(text, cipher, baseAlphabet) { function computeGematria(text, cipher, baseAlphabet) {
const normalizedInput = normalizeGematriaText(text); const normalizedInput = normalizeGematriaText(text);
const scriptMap = state.scriptCharMap instanceof Map const scriptMap = state.scriptCharMap instanceof Map
@@ -569,11 +691,16 @@
return; return;
} }
if (isAnagramMode()) {
void renderAnagramResult();
return;
}
renderForwardGematriaResult(); renderForwardGematriaResult();
} }
function bindGematriaListeners() { function bindGematriaListeners() {
const { cipherEl, inputEl, modeToggleEl } = getElements(); const { cipherEl, inputEl, modeEls } = getElements();
if (state.listenersBound || !cipherEl || !inputEl) { if (state.listenersBound || !cipherEl || !inputEl) {
return; return;
} }
@@ -586,17 +713,25 @@
inputEl.addEventListener("input", () => { inputEl.addEventListener("input", () => {
if (isReverseMode()) { if (isReverseMode()) {
state.reverseInputText = inputEl.value || ""; state.reverseInputText = inputEl.value || "";
} else if (isAnagramMode()) {
state.anagramInputText = inputEl.value || "";
} else { } else {
state.forwardInputText = inputEl.value || ""; state.forwardInputText = inputEl.value || "";
} }
renderGematriaResult(); renderGematriaResult();
}); });
modeToggleEl?.addEventListener("change", () => { getModeElements(modeEls).forEach((modeEl) => {
state.activeMode = modeToggleEl.checked ? "reverse" : "forward"; modeEl.addEventListener("change", () => {
if (!modeEl.checked) {
return;
}
state.activeMode = String(modeEl.value || "forward").trim() || "forward";
updateModeUi(); updateModeUi();
renderGematriaResult(); renderGematriaResult();
}); });
});
state.listenersBound = true; state.listenersBound = true;
} }

View File

@@ -52,7 +52,7 @@
let tabAll, tabHebrew, tabGreek, tabEnglish, tabArabic, tabEnochian; let tabAll, tabHebrew, tabGreek, tabEnglish, tabArabic, tabEnochian;
let searchInputEl, searchClearEl, typeFilterEl; let searchInputEl, searchClearEl, typeFilterEl;
let gematriaCipherEl, gematriaInputEl, gematriaResultEl, gematriaBreakdownEl; let gematriaCipherEl, gematriaInputEl, gematriaResultEl, gematriaBreakdownEl;
let gematriaReverseEl, gematriaMatchesEl, gematriaInputLabelEl, gematriaCipherLabelEl; let gematriaModeEls, gematriaMatchesEl, gematriaInputLabelEl, gematriaCipherLabelEl;
function getElements() { function getElements() {
listEl = document.getElementById("alpha-letter-list"); listEl = document.getElementById("alpha-letter-list");
@@ -73,7 +73,7 @@
gematriaInputEl = document.getElementById("alpha-gematria-input"); gematriaInputEl = document.getElementById("alpha-gematria-input");
gematriaResultEl = document.getElementById("alpha-gematria-result"); gematriaResultEl = document.getElementById("alpha-gematria-result");
gematriaBreakdownEl = document.getElementById("alpha-gematria-breakdown"); gematriaBreakdownEl = document.getElementById("alpha-gematria-breakdown");
gematriaReverseEl = document.getElementById("alpha-gematria-reverse"); gematriaModeEls = Array.from(document.querySelectorAll("input[name='alpha-tool-mode']"));
gematriaMatchesEl = document.getElementById("alpha-gematria-matches"); gematriaMatchesEl = document.getElementById("alpha-gematria-matches");
gematriaInputLabelEl = document.getElementById("alpha-gematria-input-label"); gematriaInputLabelEl = document.getElementById("alpha-gematria-input-label");
gematriaCipherLabelEl = document.getElementById("alpha-gematria-cipher-label"); gematriaCipherLabelEl = document.getElementById("alpha-gematria-cipher-label");
@@ -86,7 +86,7 @@
inputEl: gematriaInputEl, inputEl: gematriaInputEl,
resultEl: gematriaResultEl, resultEl: gematriaResultEl,
breakdownEl: gematriaBreakdownEl, breakdownEl: gematriaBreakdownEl,
modeToggleEl: gematriaReverseEl, modeEls: gematriaModeEls,
matchesEl: gematriaMatchesEl, matchesEl: gematriaMatchesEl,
inputLabelEl: gematriaInputLabelEl, inputLabelEl: gematriaInputLabelEl,
cipherLabelEl: gematriaCipherLabelEl cipherLabelEl: gematriaCipherLabelEl

View File

@@ -629,10 +629,20 @@
<div class="planet-meta-card alpha-gematria-card"> <div class="planet-meta-card alpha-gematria-card">
<strong>Gematria Lookup</strong> <strong>Gematria Lookup</strong>
<div class="alpha-gematria-toolbar"> <div class="alpha-gematria-toolbar">
<label class="alpha-gematria-toggle" for="alpha-gematria-reverse"> <fieldset class="alpha-tool-mode-group" aria-label="Alphabet tool mode">
<input id="alpha-gematria-reverse" type="checkbox"> <label class="alpha-tool-mode-option" for="alpha-tool-mode-gematria">
<span>Reverse lookup</span> <input id="alpha-tool-mode-gematria" name="alpha-tool-mode" type="radio" value="forward" checked>
<span>Gematria</span>
</label> </label>
<label class="alpha-tool-mode-option" for="alpha-tool-mode-reverse">
<input id="alpha-tool-mode-reverse" name="alpha-tool-mode" type="radio" value="reverse">
<span>Reverse Lookup</span>
</label>
<label class="alpha-tool-mode-option" for="alpha-tool-mode-anagram">
<input id="alpha-tool-mode-anagram" name="alpha-tool-mode" type="radio" value="anagram">
<span>Anagram Maker</span>
</label>
</fieldset>
</div> </div>
<div class="alpha-gematria-controls"> <div class="alpha-gematria-controls">
<label class="alpha-gematria-field alpha-gematria-field-cipher" for="alpha-gematria-cipher"> <label class="alpha-gematria-field alpha-gematria-field-cipher" for="alpha-gematria-cipher">