update lots of reader fixes

This commit is contained in:
2026-07-23 00:15:21 -07:00
parent 88aaab2e00
commit c6b95e23c7
10 changed files with 510 additions and 150 deletions
+206 -21
View File
@@ -85,6 +85,7 @@
searchError: "",
searchRequestId: 0,
highlightedVerseId: "",
highlightedVerseFresh: false,
displayPreferencesBySource: {},
showVerseHeads: readStoredBoolean(STORAGE_KEYS.showVerseHeads, true),
showSourceOverview: readStoredBoolean(STORAGE_KEYS.showSourceOverview, true),
@@ -125,6 +126,8 @@
let showExtraCardEl;
let readerFontSizeSelectEl;
let exportButtonEl;
let settingsBtnEl;
let settingsPopoverEl;
let lexiconPopupEl;
let lexiconPopupTitleEl;
let lexiconPopupSubtitleEl;
@@ -132,6 +135,15 @@
let lexiconPopupCloseEl;
let lexiconReturnFocusEl = null;
let searchTimer = null;
function debounceRunSearch(scope) {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
void runSearch(scope);
}, 300);
}
function normalizeId(value) {
return String(value || "")
.trim()
@@ -155,6 +167,8 @@
showExtraCardEl = document.getElementById("alpha-text-show-extra-card");
readerFontSizeSelectEl = document.getElementById("alpha-text-font-size-select");
exportButtonEl = document.querySelector("#alphabet-text-section .detail-export-btn");
settingsBtnEl = document.getElementById("alpha-text-settings-btn");
settingsPopoverEl = document.getElementById("alpha-text-settings-popover");
translationSelectEl = document.getElementById("alpha-text-translation-select");
translationControlEl = translationSelectEl?.closest?.(".alpha-text-control") || null;
compareSelectEl = document.getElementById("alpha-text-compare-select");
@@ -757,7 +771,9 @@
input.value = "";
}
state.searchQuery = "";
const keepQuery = preserveHighlight && state.searchQuery ? state.searchQuery : "";
state.searchQuery = preserveHighlight ? keepQuery : "";
state.searchResults = null;
state.searchLoading = false;
state.searchError = "";
@@ -766,8 +782,6 @@
if (!preserveHighlight) {
state.highlightedVerseId = "";
}
updateSearchControls();
}
function getSourceDisplayCapabilities(source, passage) {
@@ -1924,10 +1938,34 @@
return null;
}
const card = createCard("Search Results");
const payload = state.searchResults;
const totalMatches = Number(payload?.totalMatches) || 0;
const scopeLabel = state.activeSearchScope === "global"
? "all texts"
: (state.searchResults?.scope?.source?.title || getSelectedSource()?.title || "current source");
: (payload?.scope?.source?.title || getSelectedSource()?.title || "current source");
const card = createCard("Search Results");
const diagnostic = document.createElement("div");
diagnostic.className = "alpha-text-search-diagnostic";
diagnostic.style.cssText = "padding:12px 16px;border:2px solid #ef4444;border-radius:8px;margin-bottom:12px;font-size:13px;line-height:1.6;color:#fca5a5;background:#1a0a0a";
const lines = [
`<strong>Query:</strong> <em>"${state.searchQuery || "--"}"</em>`,
`<strong>Scope:</strong> ${scopeLabel}`,
state.searchLoading
? `<strong>Status:</strong> Searching...`
: state.searchError
? `<strong>Status:</strong> <span style="color:#f87171">Error: ${state.searchError}</span>`
: totalMatches > 0
? `<strong>Status:</strong> Found <strong>${totalMatches}</strong> match${totalMatches !== 1 ? "es" : ""}${payload?.truncated ? ` (showing ${payload.resultCount})` : ""}`
: `<strong>Status:</strong> No matches found`,
payload ? `<strong>API Response:</strong> totalMatches=${totalMatches} resultCount=${payload.resultCount} truncated=${payload.truncated} results.length=${Array.isArray(payload.results) ? payload.results.length : "N/A"}` : "",
``
].filter(Boolean);
diagnostic.innerHTML = lines.join("<br>");
card.appendChild(diagnostic);
const summary = document.createElement("p");
summary.className = "alpha-text-search-summary";
@@ -1957,12 +1995,19 @@
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}`;
summary.textContent = "";
card.append(summary, actions);
if (state.searchLoading) {
card.appendChild(createEmptyMessage("Searching..."));
return card;
}
if (state.searchError) {
card.appendChild(createEmptyMessage(state.searchError));
return card;
}
if (!Array.isArray(payload?.results) || !payload.results.length) {
card.appendChild(createEmptyMessage(`No matches found for \"${state.searchQuery}\".`));
return card;
@@ -2027,7 +2072,18 @@
const globalSearchOnlyMode = isGlobalSearchOnlyMode();
setGlobalSearchHeadingMode(globalSearchOnlyMode);
if (!source || !work || !section) {
if (!detailBodyEl) {
return;
}
if (!globalSearchOnlyMode && (!source || !work || !section)) {
if (state.searchQuery) {
const fallbackCard = createCard("Search");
fallbackCard.appendChild(createEmptyMessage(`Searching for "${state.searchQuery}"...`));
detailBodyEl.replaceChildren();
detailBodyEl.appendChild(fallbackCard);
return;
}
renderPlaceholder("Text Reader", "Select a source to begin", "Choose a text source and section from the left panel.");
renderLexiconPopup();
return;
@@ -2047,11 +2103,99 @@
return;
}
detailBodyEl.replaceChildren();
if (globalSearchOnlyMode) {
const existing = document.getElementById("alpha-text-search-overlay");
if (existing) {
existing.remove();
}
if (!state.searchResults && !state.searchError && state.searchLoading) {
return;
}
const banner = document.createElement("div");
banner.id = "alpha-text-search-overlay";
banner.style.cssText = "position:fixed;inset:0;z-index:9999;background:#0c0c12;overflow:auto;padding:24px";
const header = document.createElement("div");
header.style.cssText = "margin-bottom:16px;display:flex;align-items:center;justify-content:space-between";
const title = document.createElement("h2");
title.style.cssText = "color:#e4e4e7;margin:0;font-size:20px";
title.textContent = state.searchQuery ? `Search: "${state.searchQuery}"` : "Search Results";
const closeBtn = document.createElement("button");
closeBtn.type = "button";
closeBtn.textContent = "✕ Close";
closeBtn.style.cssText = "padding:8px 16px;border:1px solid #3f3f46;border-radius:6px;background:#18181b;color:#d4d4d8;cursor:pointer";
closeBtn.addEventListener("click", () => {
banner.remove();
clearTimeout(searchTimer);
clearSearchState();
clearScopedSearch("global");
clearScopedSearch("source");
state.searchRequestId += 1;
setGlobalSearchHeadingMode(false);
void loadSelectedPassage();
});
header.append(title, closeBtn);
const content = document.createElement("div");
if (state.searchLoading) {
content.textContent = "Searching...";
content.style.cssText = "color:#a1a1aa;padding:24px;text-align:center";
} else if (state.searchError) {
content.textContent = state.searchError;
content.style.cssText = "color:#f87171;padding:24px";
} else if (state.searchResults) {
const p = state.searchResults;
const summary = document.createElement("p");
summary.style.cssText = "color:#a1a1aa;margin:0 0 16px";
const matchCount = p.truncated ? p.resultCount : p.totalMatches;
summary.textContent = `${matchCount}${p.truncated ? "+" : ""} match${matchCount !== 1 ? "es" : ""}`;
content.appendChild(summary);
if (Array.isArray(p.results)) {
p.results.forEach((r) => {
const btn = document.createElement("button");
btn.type = "button";
const isActive = normalizeId(r?.sourceId) === normalizeId(state.selectedSourceId)
&& normalizeId(r?.workId) === normalizeId(state.selectedWorkId)
&& normalizeId(r?.sectionId) === normalizeId(state.selectedSectionId)
&& normalizeId(r?.verseId) === normalizeId(state.highlightedVerseId);
btn.style.cssText = "display:block;width:100%;text-align:left;padding:10px 14px;margin-bottom:8px;border:2px solid "
+ (isActive ? "#818cf8" : "#3f3f46")
+ ";border-radius:8px;background:" + (isActive ? "rgba(99,102,241,0.15)" : "#18181b")
+ ";color:#d4d4d8;cursor:pointer";
const refEl = document.createElement("strong");
refEl.textContent = r.reference || "";
btn.appendChild(refEl);
btn.appendChild(document.createElement("br"));
const previewEl = document.createElement("small");
appendHighlightedText(previewEl, (r.preview || "").slice(0, 200), state.searchQuery);
btn.appendChild(previewEl);
btn.addEventListener("click", () => {
state.highlightedVerseFresh = true;
banner.remove();
void openSearchResult(r);
});
content.appendChild(btn);
});
}
} else {
content.textContent = "No results.";
content.style.cssText = "color:#a1a1aa;padding:24px";
}
banner.append(header, content);
document.body.appendChild(banner);
return;
}
const searchCard = createSearchCard();
if (searchCard) {
if (searchCard && (globalSearchOnlyMode || state.searchLoading)) {
detailBodyEl.replaceChildren();
detailBodyEl.appendChild(searchCard);
return;
}
if (globalSearchOnlyMode) {
@@ -2059,6 +2203,8 @@
return;
}
detailBodyEl.replaceChildren();
if (!state.currentPassage) {
const loadingCard = createCard("Text Reader");
loadingCard.appendChild(createEmptyMessage("Loading section…"));
@@ -2160,7 +2306,19 @@
renderDetail();
if (state.highlightedVerseId) {
requestAnimationFrame(scrollHighlightedVerseIntoView);
requestAnimationFrame(() => {
scrollHighlightedVerseIntoView();
if (state.highlightedVerseFresh) {
const verseEl = detailBodyEl?.querySelector?.(".alpha-text-verse.is-highlighted");
if (verseEl instanceof HTMLElement) {
verseEl.classList.add("is-highlighted-fresh");
setTimeout(() => {
verseEl.classList.remove("is-highlighted-fresh");
}, 2600);
}
state.highlightedVerseFresh = false;
}
});
}
}
@@ -2185,15 +2343,13 @@
updateSearchControls();
if (!query) {
clearSearchState();
state.searchLoading = false;
state.searchError = `Search query is empty (scope: ${normalizedScope}, input: ${getSearchInput(normalizedScope) ? "found" : "missing"}, stored: "${getStoredSearchQuery(normalizedScope) || "none"}")`;
state.searchResults = null;
renderDetail();
return;
}
if (normalizedScope === "global" || normalizedScope === "source") {
showDetailOnlyMode();
}
const requestId = state.searchRequestId + 1;
state.searchRequestId = requestId;
state.searchLoading = true;
@@ -2242,7 +2398,7 @@
showDetailOnlyMode();
await loadSelectedPassage();
clearActiveSearchUi({ preserveHighlight: true });
renderDetail();
updateSearchControls();
}
function bindControls() {
@@ -2253,7 +2409,7 @@
if (globalSearchFormEl instanceof HTMLFormElement) {
globalSearchFormEl.addEventListener("submit", (event) => {
event.preventDefault();
void runSearch("global");
debounceRunSearch("global");
});
}
@@ -2278,7 +2434,7 @@
if (localSearchFormEl instanceof HTMLFormElement) {
localSearchFormEl.addEventListener("submit", (event) => {
event.preventDefault();
void runSearch("source");
void debounceRunSearch("source");
});
}
@@ -2441,6 +2597,35 @@
});
}
if (settingsBtnEl instanceof HTMLButtonElement && settingsPopoverEl instanceof HTMLElement) {
settingsBtnEl.addEventListener("click", () => {
const isOpen = settingsPopoverEl.hidden === false;
settingsPopoverEl.hidden = isOpen;
settingsBtnEl.setAttribute("aria-expanded", isOpen ? "false" : "true");
});
const closeBtn = settingsPopoverEl.querySelector(".alpha-text-settings-close");
if (closeBtn instanceof HTMLButtonElement) {
closeBtn.addEventListener("click", () => {
settingsPopoverEl.hidden = true;
settingsBtnEl.setAttribute("aria-expanded", "false");
});
}
document.addEventListener("click", (event) => {
if (settingsPopoverEl.hidden) {
return;
}
const target = event.target;
if (target instanceof Node) {
if (!settingsPopoverEl.contains(target) && target !== settingsBtnEl) {
settingsPopoverEl.hidden = true;
settingsBtnEl.setAttribute("aria-expanded", "false");
}
}
});
}
state.initialized = true;
}