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

1629 lines
53 KiB
JavaScript
Raw Normal View History

2026-03-09 23:27:03 -07:00
(function () {
"use strict";
const dataService = window.TarotDataService || {};
const state = {
initialized: false,
catalog: null,
selectedSourceId: "",
selectedWorkId: "",
selectedSectionId: "",
currentPassage: null,
lexiconEntry: null,
lexiconRequestId: 0,
lexiconOccurrenceResults: null,
lexiconOccurrenceLoading: false,
lexiconOccurrenceError: "",
lexiconOccurrenceVisible: false,
lexiconOccurrenceRequestId: 0,
globalSearchQuery: "",
localSearchQuery: "",
activeSearchScope: "global",
searchQuery: "",
searchResults: null,
searchLoading: false,
searchError: "",
searchRequestId: 0,
2026-03-10 14:55:01 -07:00
highlightedVerseId: "",
displayPreferencesBySource: {}
2026-03-09 23:27:03 -07:00
};
let sourceListEl;
let sourceCountEl;
let globalSearchFormEl;
let globalSearchInputEl;
let localSearchFormEl;
let localSearchInputEl;
let workSelectEl;
let sectionSelectEl;
let detailNameEl;
let detailSubEl;
let detailBodyEl;
2026-03-12 02:35:02 -07:00
let textLayoutEl;
2026-03-09 23:27:03 -07:00
let lexiconPopupEl;
let lexiconPopupTitleEl;
let lexiconPopupSubtitleEl;
let lexiconPopupBodyEl;
let lexiconPopupCloseEl;
let lexiconReturnFocusEl = null;
function normalizeId(value) {
return String(value || "")
.trim()
.toLowerCase();
}
function getElements() {
sourceListEl = document.getElementById("alpha-text-source-list");
sourceCountEl = document.getElementById("alpha-text-source-count");
globalSearchFormEl = document.getElementById("alpha-text-global-search-form");
globalSearchInputEl = document.getElementById("alpha-text-global-search-input");
localSearchFormEl = document.getElementById("alpha-text-local-search-form");
localSearchInputEl = document.getElementById("alpha-text-local-search-input");
workSelectEl = document.getElementById("alpha-text-work-select");
sectionSelectEl = document.getElementById("alpha-text-section-select");
detailNameEl = document.getElementById("alpha-text-detail-name");
detailSubEl = document.getElementById("alpha-text-detail-sub");
detailBodyEl = document.getElementById("alpha-text-detail-body");
2026-03-12 02:35:02 -07:00
textLayoutEl = sourceListEl?.closest?.(".planet-layout") || detailBodyEl?.closest?.(".planet-layout") || null;
2026-03-09 23:27:03 -07:00
ensureLexiconPopup();
}
2026-03-12 02:35:02 -07:00
function showDetailOnlyMode() {
if (!(textLayoutEl instanceof HTMLElement)) {
return;
}
window.TarotChromeUi?.initializeSidebarPopouts?.();
window.TarotChromeUi?.initializeDetailPopouts?.();
window.TarotChromeUi?.initializeSidebarAutoCollapse?.();
window.TarotChromeUi?.showDetailOnly?.(textLayoutEl);
}
2026-03-09 23:27:03 -07:00
function ensureLexiconPopup() {
if (lexiconPopupEl instanceof HTMLElement) {
return;
}
const popup = document.createElement("div");
popup.className = "alpha-text-lexicon-popup";
popup.hidden = true;
popup.setAttribute("aria-hidden", "true");
const backdrop = document.createElement("div");
backdrop.className = "alpha-text-lexicon-popup-backdrop";
backdrop.addEventListener("click", closeLexiconEntry);
const card = document.createElement("section");
card.className = "alpha-text-lexicon-popup-card";
card.setAttribute("role", "dialog");
card.setAttribute("aria-modal", "true");
card.setAttribute("aria-labelledby", "alpha-text-lexicon-popup-title");
card.setAttribute("tabindex", "-1");
const header = document.createElement("div");
header.className = "alpha-text-lexicon-popup-header";
const headingWrap = document.createElement("div");
headingWrap.className = "alpha-text-lexicon-popup-heading";
const title = document.createElement("h3");
title.id = "alpha-text-lexicon-popup-title";
title.textContent = "Lexicon Entry";
const subtitle = document.createElement("p");
subtitle.className = "alpha-text-lexicon-popup-subtitle";
subtitle.textContent = "Strong's definition";
headingWrap.append(title, subtitle);
const closeButton = document.createElement("button");
closeButton.type = "button";
closeButton.className = "alpha-text-lexicon-popup-close";
closeButton.textContent = "Close";
closeButton.addEventListener("click", closeLexiconEntry);
header.append(headingWrap, closeButton);
const body = document.createElement("div");
body.className = "alpha-text-lexicon-popup-body";
card.append(header, body);
popup.append(backdrop, card);
document.body.appendChild(popup);
lexiconPopupEl = popup;
lexiconPopupTitleEl = title;
lexiconPopupSubtitleEl = subtitle;
lexiconPopupBodyEl = body;
lexiconPopupCloseEl = closeButton;
}
function getSources() {
return Array.isArray(state.catalog?.sources) ? state.catalog.sources : [];
}
function findById(entries, value) {
const needle = normalizeId(value);
return (Array.isArray(entries) ? entries : []).find((entry) => normalizeId(entry?.id) === needle) || null;
}
function getSelectedSource() {
return findById(getSources(), state.selectedSourceId);
}
function getSelectedWork(source = getSelectedSource()) {
return findById(source?.works, state.selectedWorkId);
}
function getSelectedSection(source = getSelectedSource(), work = getSelectedWork(source)) {
return findById(work?.sections, state.selectedSectionId);
}
2026-03-10 14:55:01 -07:00
function normalizeTextValue(value) {
return String(value || "").trim();
}
const GREEK_TRANSLITERATION_MAP = {
α: "a", β: "b", γ: "g", δ: "d", ε: "e", ζ: "z", η: "e", θ: "th",
ι: "i", κ: "k", λ: "l", μ: "m", ν: "n", ξ: "x", ο: "o", π: "p",
ρ: "r", σ: "s", ς: "s", τ: "t", υ: "u", φ: "ph", χ: "ch", ψ: "ps",
ω: "o"
};
const HEBREW_TRANSLITERATION_MAP = {
א: "a", ב: "b", ג: "g", ד: "d", ה: "h", ו: "v", ז: "z", ח: "ch",
ט: "t", י: "y", כ: "k", ך: "k", ל: "l", מ: "m", ם: "m", נ: "n",
ן: "n", ס: "s", ע: "a", פ: "p", ף: "p", צ: "ts", ץ: "ts", ק: "q",
ר: "r", ש: "sh", ת: "t"
};
function stripSourceScriptMarks(value) {
return String(value || "")
.normalize("NFD")
.replace(/[\u0300-\u036f\u0591-\u05c7]/g, "");
}
function transliterateSourceScriptText(value) {
const stripped = stripSourceScriptMarks(value);
let result = "";
for (const character of stripped) {
const lowerCharacter = character.toLowerCase();
const mapped = GREEK_TRANSLITERATION_MAP[lowerCharacter]
|| HEBREW_TRANSLITERATION_MAP[character]
|| HEBREW_TRANSLITERATION_MAP[lowerCharacter];
result += mapped != null ? mapped : character;
}
return result
.replace(/\s+([,.;:!?])/g, "$1")
.replace(/\s+/g, " ")
.trim();
}
function buildTokenDerivedTransliteration(verse) {
const tokenText = (Array.isArray(verse?.tokens) ? verse.tokens : [])
.map((token) => normalizeTextValue(token?.original))
.filter(Boolean)
.join(" ")
.replace(/\s+([,.;:!?])/g, "$1")
.trim();
return tokenText ? transliterateSourceScriptText(tokenText) : "";
}
function getVerseTransliteration(verse, source = null) {
const metadata = verse?.metadata && typeof verse.metadata === "object" ? verse.metadata : {};
const explicit = [
verse?.transliteration,
verse?.xlit,
metadata?.transliteration,
metadata?.transliterationText,
metadata?.xlit,
metadata?.romanizedText,
metadata?.romanized
].map(normalizeTextValue).find(Boolean) || "";
if (explicit) {
return explicit;
}
if (source?.features?.hasTokenAnnotations) {
return buildTokenDerivedTransliteration(verse);
}
return "";
}
2026-03-09 23:27:03 -07:00
function getSearchInput(scope) {
return scope === "source" ? localSearchInputEl : globalSearchInputEl;
}
function getStoredSearchQuery(scope) {
return scope === "source" ? state.localSearchQuery : state.globalSearchQuery;
}
function setStoredSearchQuery(scope, value) {
if (scope === "source") {
state.localSearchQuery = value;
return;
}
state.globalSearchQuery = value;
}
function updateSearchControls() {
2026-03-10 14:55:01 -07:00
return;
}
function clearActiveSearchUi(options = {}) {
const preserveHighlight = options.preserveHighlight === true;
const scope = state.activeSearchScope === "source" ? "source" : "global";
setStoredSearchQuery(scope, "");
const input = getSearchInput(scope);
if (input instanceof HTMLInputElement) {
input.value = "";
}
state.searchQuery = "";
state.searchResults = null;
state.searchLoading = false;
state.searchError = "";
state.searchRequestId += 1;
if (!preserveHighlight) {
state.highlightedVerseId = "";
}
updateSearchControls();
}
function getSourceDisplayCapabilities(source, passage) {
const verses = Array.isArray(passage?.verses) ? passage.verses : [];
const hasOriginal = verses.some((verse) => normalizeTextValue(verse?.originalText));
const hasTransliteration = verses.some((verse) => getVerseTransliteration(verse, source));
const hasInterlinear = Boolean(source?.features?.hasTokenAnnotations);
const textModeCount = 1 + (hasOriginal ? 1 : 0) + (hasTransliteration ? 1 : 0);
return {
hasTranslation: true,
hasOriginal,
hasTransliteration,
hasInterlinear,
hasAnyExtras: hasOriginal || hasTransliteration || hasInterlinear,
supportsAllTextMode: textModeCount > 1
};
}
function getDefaultTextDisplayMode(capabilities) {
if (capabilities?.hasTranslation) {
return "translation";
}
if (capabilities?.hasOriginal) {
return "original";
}
if (capabilities?.hasTransliteration) {
return "transliteration";
}
return "translation";
}
function getAvailableTextDisplayModes(capabilities) {
const modes = [];
if (capabilities?.hasTranslation) {
modes.push("translation");
}
if (capabilities?.hasOriginal) {
modes.push("original");
}
if (capabilities?.hasTransliteration) {
modes.push("transliteration");
}
if (capabilities?.supportsAllTextMode) {
modes.push("all");
}
return modes;
}
function getSourceDisplayPreferences(source, passage) {
const sourceId = normalizeId(source?.id);
const capabilities = getSourceDisplayCapabilities(source, passage);
const availableTextModes = getAvailableTextDisplayModes(capabilities);
const stored = sourceId ? state.displayPreferencesBySource[sourceId] : null;
let textMode = stored?.textMode;
if (!availableTextModes.includes(textMode)) {
textMode = getDefaultTextDisplayMode(capabilities);
}
const preferences = {
textMode,
showInterlinear: capabilities.hasInterlinear ? Boolean(stored?.showInterlinear) : false
};
if (sourceId) {
state.displayPreferencesBySource[sourceId] = preferences;
2026-03-09 23:27:03 -07:00
}
2026-03-10 14:55:01 -07:00
return {
...preferences,
capabilities,
availableTextModes
};
}
function updateSourceDisplayPreferences(source, patch) {
const sourceId = normalizeId(source?.id);
if (!sourceId) {
return;
}
const current = state.displayPreferencesBySource[sourceId] || {};
state.displayPreferencesBySource[sourceId] = {
...current,
...patch
};
}
function formatTextDisplayModeLabel(mode) {
switch (mode) {
case "translation":
return "Translation";
case "original":
return "Original";
case "transliteration":
return "Transliteration";
case "all":
return "All";
default:
return "Display";
2026-03-09 23:27:03 -07:00
}
}
function clearSearchState() {
state.searchQuery = "";
state.searchResults = null;
state.searchLoading = false;
state.searchError = "";
state.highlightedVerseId = "";
state.searchRequestId += 1;
updateSearchControls();
}
function clearScopedSearch(scope) {
setStoredSearchQuery(scope, "");
const input = getSearchInput(scope);
if (input instanceof HTMLInputElement) {
input.value = "";
}
if (state.activeSearchScope === scope) {
clearSearchState();
} else {
updateSearchControls();
}
}
function escapeRegExp(value) {
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function appendHighlightedText(target, text, query) {
if (!(target instanceof HTMLElement)) {
return;
}
const sourceText = String(text || "");
const normalizedQuery = String(query || "").trim();
target.replaceChildren();
if (!normalizedQuery) {
target.textContent = sourceText;
return;
}
const matcher = new RegExp(escapeRegExp(normalizedQuery), "ig");
let lastIndex = 0;
let match = matcher.exec(sourceText);
while (match) {
if (match.index > lastIndex) {
target.appendChild(document.createTextNode(sourceText.slice(lastIndex, match.index)));
}
const mark = document.createElement("mark");
mark.className = "alpha-text-mark";
mark.textContent = sourceText.slice(match.index, match.index + match[0].length);
target.appendChild(mark);
lastIndex = match.index + match[0].length;
match = matcher.exec(sourceText);
}
if (lastIndex < sourceText.length) {
target.appendChild(document.createTextNode(sourceText.slice(lastIndex)));
}
}
function isHighlightedVerse(verse) {
return normalizeId(verse?.id) && normalizeId(verse?.id) === normalizeId(state.highlightedVerseId);
}
function scrollHighlightedVerseIntoView() {
const highlightedVerse = detailBodyEl?.querySelector?.(".alpha-text-verse.is-highlighted");
const detailPanel = highlightedVerse?.closest?.(".planet-detail-panel");
if (!(highlightedVerse instanceof HTMLElement) || !(detailPanel instanceof HTMLElement)) {
return;
}
const verseRect = highlightedVerse.getBoundingClientRect();
const panelRect = detailPanel.getBoundingClientRect();
const targetTop = detailPanel.scrollTop
+ (verseRect.top - panelRect.top)
- (detailPanel.clientHeight / 2)
+ (verseRect.height / 2);
detailPanel.scrollTo({
top: Math.max(0, targetTop),
behavior: "smooth"
});
}
function createCard(title) {
const card = document.createElement("div");
2026-03-10 14:55:01 -07:00
card.className = "detail-meta-card planet-meta-card";
2026-03-09 23:27:03 -07:00
if (title) {
const heading = document.createElement("strong");
heading.textContent = title;
card.appendChild(heading);
}
return card;
}
function createEmptyMessage(text) {
const message = document.createElement("div");
message.className = "alpha-text-empty";
message.textContent = text;
return message;
}
function renderPlaceholder(title, subtitle, message) {
if (detailNameEl) {
detailNameEl.textContent = title;
}
if (detailSubEl) {
detailSubEl.textContent = subtitle;
}
if (!detailBodyEl) {
return;
}
detailBodyEl.replaceChildren();
const card = createCard("Text Reader");
card.appendChild(createEmptyMessage(message));
detailBodyEl.appendChild(card);
}
2026-03-10 14:55:01 -07:00
function navigateToPassageTarget(target) {
if (!target) {
return;
}
state.selectedWorkId = target.workId;
state.selectedSectionId = target.sectionId;
state.lexiconEntry = null;
renderSelectors();
void loadSelectedPassage();
}
function getPassageLocationLabel(passage) {
const source = passage?.source || getSelectedSource();
const work = passage?.work || getSelectedWork(source);
const section = passage?.section || getSelectedSection(source, work);
return `${work?.title || "--"} · ${section?.title || section?.label || "--"}`;
}
2026-03-09 23:27:03 -07:00
function syncSelectionForSource(source) {
const works = Array.isArray(source?.works) ? source.works : [];
if (!works.length) {
state.selectedWorkId = "";
state.selectedSectionId = "";
return;
}
if (!findById(works, state.selectedWorkId)) {
state.selectedWorkId = works[0].id;
}
const work = getSelectedWork(source);
const sections = Array.isArray(work?.sections) ? work.sections : [];
if (!findById(sections, state.selectedSectionId)) {
state.selectedSectionId = sections[0]?.id || "";
}
}
async function ensureCatalogLoaded(forceRefresh = false) {
if (!forceRefresh && state.catalog) {
return state.catalog;
}
const payload = await dataService.loadTextLibrary?.(forceRefresh);
state.catalog = payload && typeof payload === "object"
? payload
: { meta: {}, sources: [], lexicons: [] };
if (!state.selectedSourceId) {
state.selectedSourceId = getSources()[0]?.id || "";
}
syncSelectionForSource(getSelectedSource());
return state.catalog;
}
function fillSelect(selectEl, entries, selectedValue, labelBuilder) {
if (!(selectEl instanceof HTMLSelectElement)) {
return;
}
selectEl.replaceChildren();
(Array.isArray(entries) ? entries : []).forEach((entry) => {
const option = document.createElement("option");
option.value = entry.id;
option.textContent = typeof labelBuilder === "function" ? labelBuilder(entry) : String(entry?.label || entry?.title || entry?.id || "");
option.selected = normalizeId(entry.id) === normalizeId(selectedValue);
selectEl.appendChild(option);
});
selectEl.disabled = !selectEl.options.length;
}
function renderSourceList() {
if (!sourceListEl) {
return;
}
sourceListEl.replaceChildren();
const sources = getSources();
sources.forEach((source) => {
const button = document.createElement("button");
button.type = "button";
button.className = "planet-list-item alpha-text-source-btn";
button.dataset.sourceId = source.id;
button.setAttribute("role", "option");
const isSelected = normalizeId(source.id) === normalizeId(state.selectedSourceId);
button.classList.toggle("is-selected", isSelected);
button.setAttribute("aria-selected", isSelected ? "true" : "false");
const name = document.createElement("span");
name.className = "planet-list-name";
name.textContent = source.title;
const meta = document.createElement("span");
meta.className = "alpha-text-source-meta";
const sectionLabel = source.sectionLabel || "Section";
meta.textContent = `${source.shortTitle || source.title} · ${source.stats?.workCount || 0} ${source.workLabel || "Works"} · ${source.stats?.sectionCount || 0} ${sectionLabel.toLowerCase()}s`;
button.append(name, meta);
button.addEventListener("click", () => {
if (normalizeId(source.id) === normalizeId(state.selectedSourceId)) {
2026-03-12 02:35:02 -07:00
showDetailOnlyMode();
2026-03-09 23:27:03 -07:00
return;
}
state.selectedSourceId = source.id;
state.currentPassage = null;
state.lexiconEntry = null;
state.highlightedVerseId = "";
syncSelectionForSource(getSelectedSource());
renderSourceList();
renderSelectors();
2026-03-12 02:35:02 -07:00
showDetailOnlyMode();
2026-03-09 23:27:03 -07:00
if (state.searchQuery && state.activeSearchScope === "source") {
void Promise.all([loadSelectedPassage(), runSearch("source")]);
return;
}
void loadSelectedPassage();
});
sourceListEl.appendChild(button);
});
if (!sources.length) {
sourceListEl.appendChild(createEmptyMessage("No text sources are available."));
}
if (sourceCountEl) {
sourceCountEl.textContent = `${sources.length} sources`;
}
}
function renderSelectors() {
const source = getSelectedSource();
const work = getSelectedWork(source);
const works = Array.isArray(source?.works) ? source.works : [];
const sections = Array.isArray(work?.sections) ? work.sections : [];
fillSelect(workSelectEl, works, state.selectedWorkId, (entry) => `${entry.title} (${entry.sectionCount} ${String(source?.sectionLabel || "section").toLowerCase()}s)`);
fillSelect(sectionSelectEl, sections, state.selectedSectionId, (entry) => `${entry.label} · ${entry.verseCount} verses`);
}
function closeLexiconEntry() {
dismissLexiconEntry();
}
function clearLexiconOccurrenceState() {
state.lexiconOccurrenceResults = null;
state.lexiconOccurrenceLoading = false;
state.lexiconOccurrenceError = "";
state.lexiconOccurrenceVisible = false;
state.lexiconOccurrenceRequestId += 1;
}
function dismissLexiconEntry(options = {}) {
const shouldRestoreFocus = options.restoreFocus !== false;
state.lexiconRequestId += 1;
state.lexiconEntry = null;
clearLexiconOccurrenceState();
renderLexiconPopup();
const returnFocusEl = lexiconReturnFocusEl;
lexiconReturnFocusEl = null;
if (shouldRestoreFocus && returnFocusEl instanceof HTMLElement && returnFocusEl.isConnected) {
requestAnimationFrame(() => {
if (returnFocusEl.isConnected) {
returnFocusEl.focus();
}
});
}
}
async function toggleLexiconOccurrences() {
const lexiconId = state.lexiconEntry?.lexicon?.id || state.lexiconEntry?.lexiconId || "";
const entryId = state.lexiconEntry?.entryId || "";
if (!lexiconId || !entryId) {
return;
}
if (state.lexiconOccurrenceVisible && !state.lexiconOccurrenceLoading) {
state.lexiconOccurrenceVisible = false;
renderLexiconPopup();
return;
}
state.lexiconOccurrenceVisible = true;
if (state.lexiconOccurrenceResults || state.lexiconOccurrenceError) {
renderLexiconPopup();
return;
}
const requestId = state.lexiconOccurrenceRequestId + 1;
state.lexiconOccurrenceRequestId = requestId;
state.lexiconOccurrenceLoading = true;
state.lexiconOccurrenceError = "";
renderLexiconPopup();
try {
const payload = await dataService.loadTextLexiconOccurrences?.(lexiconId, entryId, { limit: 100 });
if (requestId !== state.lexiconOccurrenceRequestId) {
return;
}
state.lexiconOccurrenceResults = payload;
state.lexiconOccurrenceLoading = false;
renderLexiconPopup();
} catch (error) {
if (requestId !== state.lexiconOccurrenceRequestId) {
return;
}
state.lexiconOccurrenceLoading = false;
state.lexiconOccurrenceError = error?.message || "Unable to load verse occurrences for this Strong's entry.";
renderLexiconPopup();
}
}
async function openLexiconOccurrence(result) {
dismissLexiconEntry({ restoreFocus: false });
await openSearchResult(result);
}
function appendLexiconOccurrencePreview(target, result) {
if (!(target instanceof HTMLElement)) {
return;
}
target.replaceChildren();
const previewTokens = Array.isArray(result?.previewTokens) ? result.previewTokens : [];
if (!previewTokens.length) {
target.textContent = result?.preview || result?.reference || "";
return;
}
previewTokens.forEach((token, index) => {
const text = String(token?.text || "").trim();
if (!text) {
return;
}
const previousText = String(previewTokens[index - 1]?.text || "").trim();
if (index > 0 && text !== "..." && previousText !== "...") {
target.appendChild(document.createTextNode(" "));
}
if (token?.isMatch) {
const mark = document.createElement("mark");
mark.className = "alpha-text-mark alpha-text-mark--lexicon";
mark.textContent = text;
target.appendChild(mark);
return;
}
target.appendChild(document.createTextNode(text));
});
}
function renderLexiconPopup() {
ensureLexiconPopup();
if (!(lexiconPopupEl instanceof HTMLElement) || !(lexiconPopupBodyEl instanceof HTMLElement)) {
return;
}
const payload = state.lexiconEntry;
const wasHidden = lexiconPopupEl.hidden;
if (!payload) {
lexiconPopupEl.hidden = true;
lexiconPopupEl.setAttribute("aria-hidden", "true");
lexiconPopupTitleEl.textContent = "Lexicon Entry";
lexiconPopupSubtitleEl.textContent = "Strong's definition";
lexiconPopupBodyEl.replaceChildren();
return;
}
lexiconPopupTitleEl.textContent = payload.entryId ? `Strong's ${payload.entryId}` : "Lexicon Entry";
lexiconPopupSubtitleEl.textContent = payload.loading
? "Loading definition..."
: "Strong's definition";
lexiconPopupBodyEl.replaceChildren();
if (payload.loading) {
lexiconPopupBodyEl.appendChild(createEmptyMessage(`Loading ${payload.entryId}...`));
} else if (payload.error) {
lexiconPopupBodyEl.appendChild(createEmptyMessage(payload.error));
} else {
const entry = payload.entry || {};
const head = document.createElement("div");
head.className = "alpha-text-lexicon-head";
const idPill = document.createElement("button");
idPill.type = "button";
idPill.className = "alpha-text-lexicon-id alpha-text-lexicon-id--button";
idPill.textContent = payload.entryId || "--";
idPill.setAttribute("aria-expanded", state.lexiconOccurrenceVisible ? "true" : "false");
idPill.addEventListener("click", () => {
void toggleLexiconOccurrences();
});
head.appendChild(idPill);
if (entry.lemma) {
const lemma = document.createElement("span");
lemma.className = "alpha-text-token-original";
lemma.textContent = entry.lemma;
head.appendChild(lemma);
}
lexiconPopupBodyEl.appendChild(head);
const rows = [
["Transliteration", entry.xlit],
["Pronunciation", entry.pron],
["Derivation", entry.derivation],
["Strong's Definition", entry.strongs_def],
["KJV Definition", entry.kjv_def]
].filter(([, value]) => String(value || "").trim());
if (rows.length) {
const dl = document.createElement("dl");
dl.className = "alpha-dl";
rows.forEach(([label, value]) => {
const dt = document.createElement("dt");
dt.textContent = label;
const dd = document.createElement("dd");
dd.textContent = String(value || "").trim();
dl.append(dt, dd);
});
lexiconPopupBodyEl.appendChild(dl);
}
const occurrenceHint = document.createElement("p");
occurrenceHint.className = "alpha-text-lexicon-hint";
occurrenceHint.textContent = "Click the Strong's number to show verses that use this entry.";
lexiconPopupBodyEl.appendChild(occurrenceHint);
if (state.lexiconOccurrenceVisible) {
const occurrenceSection = document.createElement("section");
occurrenceSection.className = "alpha-text-lexicon-occurrences";
const occurrenceTitle = document.createElement("strong");
occurrenceTitle.textContent = "Verse Occurrences";
occurrenceSection.appendChild(occurrenceTitle);
if (state.lexiconOccurrenceLoading) {
occurrenceSection.appendChild(createEmptyMessage(`Loading verses for ${payload.entryId}...`));
} else if (state.lexiconOccurrenceError) {
occurrenceSection.appendChild(createEmptyMessage(state.lexiconOccurrenceError));
} else {
const occurrencePayload = state.lexiconOccurrenceResults;
const totalMatches = Number(occurrencePayload?.totalMatches) || 0;
const summary = document.createElement("p");
summary.className = "alpha-text-search-summary";
summary.textContent = totalMatches
? `${totalMatches} verses use ${payload.entryId}.${occurrencePayload?.truncated ? ` Showing the first ${occurrencePayload.resultCount} results.` : ""}`
: `No verses found for ${payload.entryId}.`;
occurrenceSection.appendChild(summary);
if (Array.isArray(occurrencePayload?.results) && occurrencePayload.results.length) {
const occurrenceList = document.createElement("div");
occurrenceList.className = "alpha-text-lexicon-occurrence-list";
occurrencePayload.results.forEach((result) => {
const button = document.createElement("button");
button.type = "button";
button.className = "alpha-text-lexicon-occurrence";
const headRow = document.createElement("div");
headRow.className = "alpha-text-search-result-head";
const reference = document.createElement("span");
reference.className = "alpha-text-search-reference";
reference.textContent = result.reference || `${result.workTitle} ${result.sectionLabel}:${result.verseNumber}`;
const location = document.createElement("span");
location.className = "alpha-text-search-location";
location.textContent = `${result.sourceShortTitle || result.sourceTitle} · ${result.workTitle} · ${result.sectionLabel}`;
const preview = document.createElement("p");
preview.className = "alpha-text-search-preview alpha-text-search-preview--compact";
appendLexiconOccurrencePreview(preview, result);
button.addEventListener("click", () => {
void openLexiconOccurrence(result);
});
headRow.append(reference, location);
button.append(headRow, preview);
occurrenceList.appendChild(button);
});
occurrenceSection.appendChild(occurrenceList);
}
}
lexiconPopupBodyEl.appendChild(occurrenceSection);
}
}
lexiconPopupEl.hidden = false;
lexiconPopupEl.setAttribute("aria-hidden", "false");
if (wasHidden && lexiconPopupCloseEl instanceof HTMLButtonElement) {
requestAnimationFrame(() => {
lexiconPopupCloseEl.focus();
});
}
}
async function loadLexiconEntry(lexiconId, entryId, triggerElement) {
if (!lexiconId || !entryId) {
return;
}
if (triggerElement instanceof HTMLElement) {
lexiconReturnFocusEl = triggerElement;
}
const requestId = state.lexiconRequestId + 1;
state.lexiconRequestId = requestId;
clearLexiconOccurrenceState();
state.lexiconEntry = {
loading: true,
lexiconId,
entryId: String(entryId).toUpperCase()
};
renderDetail();
try {
const payload = await dataService.loadTextLexiconEntry?.(lexiconId, entryId);
if (requestId !== state.lexiconRequestId) {
return;
}
state.lexiconEntry = payload;
renderDetail();
} catch (error) {
if (requestId !== state.lexiconRequestId) {
return;
}
state.lexiconEntry = {
error: error?.message || "Unable to load lexicon entry.",
lexiconId,
entryId: String(entryId).toUpperCase()
};
renderDetail();
}
}
function createMetaGrid(passage) {
const source = passage?.source || getSelectedSource();
const work = passage?.work || getSelectedWork(source);
const section = passage?.section || getSelectedSection(source, work);
2026-03-10 14:55:01 -07:00
const displayPreferences = getSourceDisplayPreferences(source, passage);
2026-03-09 23:27:03 -07:00
const metaGrid = document.createElement("div");
metaGrid.className = "alpha-text-meta-grid";
const overviewCard = createCard("Source Overview");
overviewCard.innerHTML += `
<dl class="alpha-dl">
<dt>Source</dt><dd>${source?.title || "--"}</dd>
<dt>Tradition</dt><dd>${source?.tradition || "--"}</dd>
<dt>Language</dt><dd>${source?.language || "--"}</dd>
<dt>Script</dt><dd>${source?.script || "--"}</dd>
<dt>${source?.workLabel || "Work"}</dt><dd>${work?.title || "--"}</dd>
<dt>${source?.sectionLabel || "Section"}</dt><dd>${section?.label || "--"}</dd>
</dl>
`;
metaGrid.appendChild(overviewCard);
2026-03-10 14:55:01 -07:00
if (displayPreferences.capabilities.hasAnyExtras) {
const extraCard = createCard("Extra");
extraCard.classList.add("alpha-text-extra-card");
if (displayPreferences.availableTextModes.length > 1) {
const displayGroup = document.createElement("div");
displayGroup.className = "alpha-text-extra-group";
const displayLabel = document.createElement("span");
displayLabel.className = "alpha-text-extra-label";
displayLabel.textContent = "Display";
const displayButtons = document.createElement("div");
displayButtons.className = "alpha-nav-btns alpha-text-extra-actions";
displayPreferences.availableTextModes.forEach((mode) => {
const button = document.createElement("button");
button.type = "button";
button.className = "alpha-nav-btn";
button.textContent = formatTextDisplayModeLabel(mode);
button.setAttribute("aria-pressed", displayPreferences.textMode === mode ? "true" : "false");
button.classList.toggle("is-selected", displayPreferences.textMode === mode);
button.addEventListener("click", () => {
updateSourceDisplayPreferences(source, { textMode: mode });
renderDetail();
});
displayButtons.appendChild(button);
});
2026-03-09 23:27:03 -07:00
2026-03-10 14:55:01 -07:00
displayGroup.append(displayLabel, displayButtons);
extraCard.appendChild(displayGroup);
2026-03-09 23:27:03 -07:00
}
2026-03-10 14:55:01 -07:00
if (displayPreferences.capabilities.hasInterlinear) {
const interlinearGroup = document.createElement("div");
interlinearGroup.className = "alpha-text-extra-group";
const interlinearLabel = document.createElement("span");
interlinearLabel.className = "alpha-text-extra-label";
interlinearLabel.textContent = "Reader";
const interlinearButtons = document.createElement("div");
interlinearButtons.className = "alpha-nav-btns alpha-text-extra-actions";
const interlinearButton = document.createElement("button");
interlinearButton.type = "button";
interlinearButton.className = "alpha-nav-btn";
interlinearButton.textContent = "Interlinear";
interlinearButton.setAttribute("aria-pressed", displayPreferences.showInterlinear ? "true" : "false");
interlinearButton.classList.toggle("is-selected", displayPreferences.showInterlinear);
interlinearButton.addEventListener("click", () => {
updateSourceDisplayPreferences(source, { showInterlinear: !displayPreferences.showInterlinear });
renderDetail();
});
2026-03-09 23:27:03 -07:00
2026-03-10 14:55:01 -07:00
interlinearButtons.appendChild(interlinearButton);
interlinearGroup.append(interlinearLabel, interlinearButtons);
extraCard.appendChild(interlinearGroup);
}
metaGrid.appendChild(extraCard);
}
2026-03-09 23:27:03 -07:00
if (source?.features?.hasTokenAnnotations) {
const noteCard = createCard("Reader Mode");
noteCard.appendChild(createEmptyMessage("This source is tokenized. Click a Strong's code chip to open its lexicon entry."));
metaGrid.appendChild(noteCard);
}
return metaGrid;
}
function createPlainVerse(verse) {
2026-03-10 14:55:01 -07:00
const source = getSelectedSource();
const displayPreferences = getSourceDisplayPreferences(source, state.currentPassage);
2026-03-09 23:27:03 -07:00
const article = document.createElement("article");
article.className = "alpha-text-verse";
article.classList.toggle("is-highlighted", isHighlightedVerse(verse));
const head = document.createElement("div");
head.className = "alpha-text-verse-head";
const reference = document.createElement("span");
reference.className = "alpha-text-verse-reference";
reference.textContent = verse.reference || (verse.number ? `Verse ${verse.number}` : "");
head.append(reference);
2026-03-10 14:55:01 -07:00
article.append(head);
appendVerseTextLines(article, verse, source, displayPreferences, verse.text || "");
2026-03-09 23:27:03 -07:00
return article;
}
function buildTokenTranslationText(tokens, fallbackText) {
const glossText = (Array.isArray(tokens) ? tokens : [])
.map((token) => String(token?.gloss || "").trim())
.filter(Boolean)
.join(" ")
.replace(/\s+([,.;:!?])/g, "$1")
.trim();
return glossText || String(fallbackText || "").trim();
}
2026-03-10 14:55:01 -07:00
function appendVerseTextLines(target, verse, source, displayPreferences, translationText) {
if (!(target instanceof HTMLElement)) {
return;
}
const mode = displayPreferences?.textMode || "translation";
const originalText = normalizeTextValue(verse?.originalText);
const transliterationText = getVerseTransliteration(verse, source);
const lines = [];
const appendLine = (text, variant) => {
const normalizedText = normalizeTextValue(text);
if (!normalizedText || lines.some((entry) => entry.text === normalizedText)) {
return;
}
lines.push({ text: normalizedText, variant });
};
if (mode === "all") {
appendLine(translationText, "translation");
appendLine(originalText, "original");
appendLine(transliterationText, "transliteration");
} else if (mode === "original") {
appendLine(originalText || translationText, originalText ? "original" : "translation");
} else if (mode === "transliteration") {
appendLine(transliterationText || translationText, transliterationText ? "transliteration" : "translation");
} else {
appendLine(translationText, "translation");
}
if (!lines.length) {
appendLine(translationText, "translation");
}
lines.forEach((line) => {
const text = document.createElement("p");
text.className = `alpha-text-verse-text alpha-text-verse-text--${line.variant}`;
appendHighlightedText(text, line.text, isHighlightedVerse(verse) ? state.searchQuery : "");
target.appendChild(text);
});
}
function createTokenVerse(verse, lexiconId, displayPreferences, source) {
2026-03-09 23:27:03 -07:00
const article = document.createElement("article");
2026-03-10 14:55:01 -07:00
article.className = "alpha-text-verse";
article.classList.toggle("alpha-text-verse--interlinear", Boolean(displayPreferences?.showInterlinear));
2026-03-09 23:27:03 -07:00
article.classList.toggle("is-highlighted", isHighlightedVerse(verse));
const head = document.createElement("div");
head.className = "alpha-text-verse-head";
const reference = document.createElement("span");
reference.className = "alpha-text-verse-reference";
reference.textContent = verse.reference || (verse.number ? `Verse ${verse.number}` : "");
const tokenGrid = document.createElement("div");
tokenGrid.className = "alpha-text-token-grid";
(Array.isArray(verse?.tokens) ? verse.tokens : []).forEach((token) => {
const strongId = Array.isArray(token?.strongs) ? token.strongs[0] : "";
const tokenEl = document.createElement(strongId ? "button" : "div");
tokenEl.className = `alpha-text-token${strongId ? " alpha-text-token--interactive" : ""}`;
if (tokenEl instanceof HTMLButtonElement) {
tokenEl.type = "button";
tokenEl.addEventListener("click", () => {
void loadLexiconEntry(lexiconId, strongId, tokenEl);
});
}
const glossEl = document.createElement("span");
glossEl.className = "alpha-text-token-gloss";
glossEl.textContent = token?.gloss || "—";
const originalEl = document.createElement("span");
originalEl.className = "alpha-text-token-original";
originalEl.textContent = token?.original || "—";
tokenEl.append(glossEl, originalEl);
if (strongId) {
const strongsEl = document.createElement("span");
strongsEl.className = "alpha-text-token-strongs";
strongsEl.textContent = Array.isArray(token.strongs) ? token.strongs.join(" · ") : strongId;
tokenEl.appendChild(strongsEl);
}
tokenGrid.appendChild(tokenEl);
});
head.append(reference);
2026-03-10 14:55:01 -07:00
article.append(head);
appendVerseTextLines(article, verse, source, displayPreferences, buildTokenTranslationText(verse?.tokens, verse?.text));
if (displayPreferences?.showInterlinear) {
article.appendChild(tokenGrid);
}
2026-03-09 23:27:03 -07:00
return article;
}
function createReaderCard(passage) {
const source = passage?.source || getSelectedSource();
2026-03-10 14:55:01 -07:00
const displayPreferences = getSourceDisplayPreferences(source, passage);
const card = createCard(getPassageLocationLabel(passage));
card.classList.add("alpha-text-reader-card");
2026-03-09 23:27:03 -07:00
const reader = document.createElement("div");
reader.className = "alpha-text-reader";
if (passage?.errorMessage) {
reader.appendChild(createEmptyMessage(passage.errorMessage));
card.appendChild(reader);
return card;
}
const verses = Array.isArray(passage?.verses) ? passage.verses : [];
if (!verses.length) {
reader.appendChild(createEmptyMessage("No verses were found for this section."));
card.appendChild(reader);
return card;
}
verses.forEach((verse) => {
const verseEl = source?.features?.hasTokenAnnotations
2026-03-10 14:55:01 -07:00
? createTokenVerse(verse, source.features.lexiconIds?.[0] || "", displayPreferences, source)
2026-03-09 23:27:03 -07:00
: createPlainVerse(verse);
reader.appendChild(verseEl);
});
card.appendChild(reader);
2026-03-10 14:55:01 -07:00
const navigation = document.createElement("div");
navigation.className = "alpha-text-reader-navigation";
if (passage?.navigation?.previous) {
const previousButton = document.createElement("button");
previousButton.type = "button";
previousButton.className = "alpha-nav-btn alpha-text-reader-nav-btn";
previousButton.textContent = "← Previous";
previousButton.addEventListener("click", () => {
navigateToPassageTarget(passage.navigation.previous);
});
navigation.appendChild(previousButton);
}
if (passage?.navigation?.next) {
const nextButton = document.createElement("button");
nextButton.type = "button";
nextButton.className = "alpha-nav-btn alpha-text-reader-nav-btn alpha-text-reader-nav-btn--next";
nextButton.textContent = "Next →";
nextButton.addEventListener("click", () => {
navigateToPassageTarget(passage.navigation.next);
});
navigation.appendChild(nextButton);
}
if (navigation.childElementCount) {
card.appendChild(navigation);
}
2026-03-09 23:27:03 -07:00
return card;
}
function createSearchCard() {
const hasSearchState = state.searchLoading || state.searchError || state.searchResults || state.searchQuery;
if (!hasSearchState) {
return null;
}
const card = createCard("Search Results");
const scopeLabel = state.activeSearchScope === "global"
? "all texts"
: (state.searchResults?.scope?.source?.title || getSelectedSource()?.title || "current source");
const summary = document.createElement("p");
summary.className = "alpha-text-search-summary";
if (state.searchLoading) {
summary.textContent = `Searching ${scopeLabel} for \"${state.searchQuery}\"...`;
card.appendChild(summary);
return card;
}
if (state.searchError) {
summary.textContent = `Search scope: ${scopeLabel}`;
card.append(summary, createEmptyMessage(state.searchError));
return card;
}
const payload = state.searchResults;
const totalMatches = Number(payload?.totalMatches) || 0;
const truncatedNote = payload?.truncated ? ` Showing the first ${payload.resultCount} results.` : "";
summary.textContent = `${totalMatches} matches in ${scopeLabel}.${truncatedNote}`;
card.appendChild(summary);
if (!Array.isArray(payload?.results) || !payload.results.length) {
card.appendChild(createEmptyMessage(`No matches found for \"${state.searchQuery}\".`));
return card;
}
const resultsEl = document.createElement("div");
resultsEl.className = "alpha-text-search-results";
payload.results.forEach((result) => {
const button = document.createElement("button");
button.type = "button";
button.className = "alpha-text-search-result";
button.classList.toggle(
"is-active",
normalizeId(result?.sourceId) === normalizeId(state.selectedSourceId)
&& normalizeId(result?.workId) === normalizeId(state.selectedWorkId)
&& normalizeId(result?.sectionId) === normalizeId(state.selectedSectionId)
&& normalizeId(result?.verseId) === normalizeId(state.highlightedVerseId)
);
const head = document.createElement("div");
head.className = "alpha-text-search-result-head";
const reference = document.createElement("span");
reference.className = "alpha-text-search-reference";
reference.textContent = result.reference || `${result.workTitle} ${result.sectionLabel}:${result.verseNumber}`;
const location = document.createElement("span");
location.className = "alpha-text-search-location";
location.textContent = state.activeSearchScope === "global"
? `${result.sourceShortTitle || result.sourceTitle} · ${result.workTitle} · ${result.sectionLabel}`
: `${result.workTitle} · ${result.sectionLabel}`;
const preview = document.createElement("p");
preview.className = "alpha-text-search-preview";
appendHighlightedText(preview, result.preview || result.reference || "", state.searchQuery);
button.addEventListener("click", () => {
void openSearchResult(result);
});
head.append(reference, location);
button.append(head, preview);
resultsEl.appendChild(button);
});
card.appendChild(resultsEl);
return card;
}
function isGlobalSearchOnlyMode() {
return state.activeSearchScope === "global"
&& Boolean(state.searchQuery)
&& !state.highlightedVerseId;
}
function renderDetail() {
const source = getSelectedSource();
const work = getSelectedWork(source);
const section = getSelectedSection(source, work);
const globalSearchOnlyMode = isGlobalSearchOnlyMode();
if (!source || !work || !section) {
renderPlaceholder("Text Reader", "Select a source to begin", "Choose a text source and section from the left panel.");
renderLexiconPopup();
return;
}
if (detailNameEl) {
detailNameEl.textContent = globalSearchOnlyMode
? `Global Search${state.searchQuery ? `: ${state.searchQuery}` : ""}`
: (state.currentPassage?.section?.title || section.title);
}
if (detailSubEl) {
detailSubEl.textContent = globalSearchOnlyMode
? "All text sources"
: `${source.title} · ${work.title}`;
}
if (!detailBodyEl) {
return;
}
detailBodyEl.replaceChildren();
const searchCard = createSearchCard();
if (searchCard) {
detailBodyEl.appendChild(searchCard);
}
if (globalSearchOnlyMode) {
renderLexiconPopup();
return;
}
if (!state.currentPassage) {
const loadingCard = createCard("Text Reader");
loadingCard.appendChild(createEmptyMessage("Loading section…"));
detailBodyEl.appendChild(loadingCard);
renderLexiconPopup();
return;
}
detailBodyEl.appendChild(createMetaGrid(state.currentPassage));
detailBodyEl.appendChild(createReaderCard(state.currentPassage));
renderLexiconPopup();
}
async function loadSelectedPassage() {
const source = getSelectedSource();
const work = getSelectedWork(source);
const section = getSelectedSection(source, work);
if (!source || !work || !section) {
state.currentPassage = null;
renderDetail();
return;
}
state.currentPassage = null;
renderDetail();
try {
state.currentPassage = await dataService.loadTextSection?.(source.id, work.id, section.id);
renderDetail();
if (state.highlightedVerseId) {
requestAnimationFrame(scrollHighlightedVerseIntoView);
}
} catch (error) {
state.currentPassage = {
source,
work,
section,
verses: [],
errorMessage: error?.message || "Unable to load this section."
};
renderDetail();
}
}
async function runSearch(scope, forceRefresh = false) {
const searchFn = dataService.searchTextLibrary;
if (typeof searchFn !== "function") {
state.searchError = "Text search is unavailable.";
state.searchLoading = false;
state.searchResults = null;
renderDetail();
return;
}
const normalizedScope = scope === "source" ? "source" : "global";
const query = String(getSearchInput(normalizedScope)?.value || getStoredSearchQuery(normalizedScope) || "").trim();
setStoredSearchQuery(normalizedScope, query);
state.activeSearchScope = normalizedScope;
state.searchQuery = query;
state.searchError = "";
state.searchResults = null;
state.highlightedVerseId = "";
updateSearchControls();
if (!query) {
clearSearchState();
renderDetail();
return;
}
const requestId = state.searchRequestId + 1;
state.searchRequestId = requestId;
state.searchLoading = true;
renderDetail();
try {
const payload = await searchFn(query, {
sourceId: normalizedScope === "source" ? state.selectedSourceId : "",
limit: 50
}, forceRefresh);
if (requestId !== state.searchRequestId) {
return;
}
state.searchResults = payload;
state.searchLoading = false;
renderDetail();
} catch (error) {
if (requestId !== state.searchRequestId) {
return;
}
state.searchLoading = false;
state.searchError = error?.message || "Unable to search this text library.";
renderDetail();
}
}
async function openSearchResult(result) {
if (!result) {
return;
}
state.selectedSourceId = result.sourceId;
state.selectedWorkId = result.workId;
state.selectedSectionId = result.sectionId;
state.highlightedVerseId = result.verseId;
dismissLexiconEntry({ restoreFocus: false });
syncSelectionForSource(getSelectedSource());
renderSourceList();
renderSelectors();
2026-03-12 02:35:02 -07:00
showDetailOnlyMode();
2026-03-09 23:27:03 -07:00
await loadSelectedPassage();
2026-03-10 14:55:01 -07:00
clearActiveSearchUi({ preserveHighlight: true });
renderDetail();
2026-03-09 23:27:03 -07:00
}
function bindControls() {
if (state.initialized) {
return;
}
if (globalSearchFormEl instanceof HTMLFormElement) {
globalSearchFormEl.addEventListener("submit", (event) => {
event.preventDefault();
void runSearch("global");
});
}
if (globalSearchInputEl instanceof HTMLInputElement) {
globalSearchInputEl.addEventListener("input", () => {
state.globalSearchQuery = String(globalSearchInputEl.value || "").trim();
updateSearchControls();
if (!state.globalSearchQuery && state.activeSearchScope === "global" && state.searchQuery) {
clearSearchState();
renderDetail();
}
});
}
if (localSearchFormEl instanceof HTMLFormElement) {
localSearchFormEl.addEventListener("submit", (event) => {
event.preventDefault();
void runSearch("source");
});
}
if (localSearchInputEl instanceof HTMLInputElement) {
localSearchInputEl.addEventListener("input", () => {
state.localSearchQuery = String(localSearchInputEl.value || "").trim();
updateSearchControls();
if (!state.localSearchQuery && state.activeSearchScope === "source" && state.searchQuery) {
clearSearchState();
renderDetail();
}
});
}
if (workSelectEl) {
workSelectEl.addEventListener("change", () => {
state.selectedWorkId = String(workSelectEl.value || "");
const source = getSelectedSource();
syncSelectionForSource(source);
state.currentPassage = null;
state.lexiconEntry = null;
state.highlightedVerseId = "";
renderSelectors();
void loadSelectedPassage();
});
}
if (sectionSelectEl) {
sectionSelectEl.addEventListener("change", () => {
state.selectedSectionId = String(sectionSelectEl.value || "");
state.currentPassage = null;
state.lexiconEntry = null;
state.highlightedVerseId = "";
void loadSelectedPassage();
});
}
document.addEventListener("keydown", (event) => {
if (event.key === "Escape" && state.lexiconEntry) {
closeLexiconEntry();
}
});
state.initialized = true;
}
async function ensureAlphabetTextSection() {
getElements();
bindControls();
2026-03-12 02:35:02 -07:00
window.TarotChromeUi?.initializeSidebarPopouts?.();
window.TarotChromeUi?.initializeDetailPopouts?.();
2026-03-09 23:27:03 -07:00
if (!sourceListEl || !detailBodyEl) {
return;
}
await ensureCatalogLoaded();
renderSourceList();
renderSelectors();
updateSearchControls();
if (!state.currentPassage) {
await loadSelectedPassage();
return;
}
renderDetail();
}
function resetState() {
state.catalog = null;
state.currentPassage = null;
state.lexiconEntry = null;
state.selectedSourceId = "";
state.selectedWorkId = "";
state.selectedSectionId = "";
state.lexiconRequestId = 0;
state.lexiconOccurrenceResults = null;
state.lexiconOccurrenceLoading = false;
state.lexiconOccurrenceError = "";
state.lexiconOccurrenceVisible = false;
state.lexiconOccurrenceRequestId = 0;
state.globalSearchQuery = "";
state.localSearchQuery = "";
state.activeSearchScope = "global";
state.searchQuery = "";
state.searchResults = null;
state.searchLoading = false;
state.searchError = "";
state.searchRequestId = 0;
state.highlightedVerseId = "";
lexiconReturnFocusEl = null;
if (globalSearchInputEl instanceof HTMLInputElement) {
globalSearchInputEl.value = "";
}
if (localSearchInputEl instanceof HTMLInputElement) {
localSearchInputEl.value = "";
}
updateSearchControls();
renderLexiconPopup();
}
document.addEventListener("connection:updated", resetState);
window.AlphabetTextUi = {
ensureAlphabetTextSection
};
})();