updated search
This commit is contained in:
@@ -3419,6 +3419,30 @@
|
||||
linear-gradient(180deg, rgba(30, 27, 75, 0.22), rgba(16, 16, 24, 0.96));
|
||||
}
|
||||
|
||||
.alpha-text-reader-toggle {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
color: #e4e4e7;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.alpha-text-reader-toggle input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
accent-color: #818cf8;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.alpha-text-reader-toggle-control {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.alpha-text-search-controls--detail {
|
||||
padding: 14px;
|
||||
border: 1px solid #2f2f39;
|
||||
@@ -3436,7 +3460,7 @@
|
||||
|
||||
.alpha-text-search-inline {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
@@ -3536,16 +3560,15 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.planet-layout.alpha-text-global-search-only {
|
||||
.planet-layout.alpha-text-global-search-only.layout-sidebar-collapsed {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.planet-layout.alpha-text-global-search-only > .planet-list-panel,
|
||||
.planet-layout.alpha-text-global-search-only.layout-sidebar-collapsed > .planet-list-panel,
|
||||
.planet-layout.alpha-text-global-search-only > .planet-detail-panel > .alpha-text-detail-heading,
|
||||
.planet-layout.alpha-text-global-search-only .alpha-text-heading-tools,
|
||||
.planet-layout.alpha-text-global-search-only .alpha-text-controls--heading,
|
||||
.planet-layout.alpha-text-global-search-only .alpha-text-search-controls--detail,
|
||||
.planet-layout.alpha-text-global-search-only .alpha-text-search-inline {
|
||||
.planet-layout.alpha-text-global-search-only .alpha-text-search-controls--detail {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -3778,6 +3801,10 @@
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.alpha-text-hide-verse-heads .alpha-text-verse-head {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alpha-text-verse-reference {
|
||||
color: #c4b5fd;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -2,6 +2,33 @@
|
||||
"use strict";
|
||||
|
||||
const dataService = window.TarotDataService || {};
|
||||
const STORAGE_KEYS = {
|
||||
showVerseHeads: "tarotime.alphaText.showVerseHeads"
|
||||
};
|
||||
|
||||
function readStoredBoolean(key, fallback) {
|
||||
try {
|
||||
const value = window.localStorage?.getItem?.(key);
|
||||
if (value === "true") {
|
||||
return true;
|
||||
}
|
||||
if (value === "false") {
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore storage failures and keep in-memory defaults.
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function writeStoredBoolean(key, value) {
|
||||
try {
|
||||
window.localStorage?.setItem?.(key, value ? "true" : "false");
|
||||
} catch (error) {
|
||||
// Ignore storage failures and keep in-memory state.
|
||||
}
|
||||
}
|
||||
|
||||
const state = {
|
||||
initialized: false,
|
||||
@@ -31,15 +58,18 @@
|
||||
searchError: "",
|
||||
searchRequestId: 0,
|
||||
highlightedVerseId: "",
|
||||
displayPreferencesBySource: {}
|
||||
displayPreferencesBySource: {},
|
||||
showVerseHeads: readStoredBoolean(STORAGE_KEYS.showVerseHeads, true)
|
||||
};
|
||||
|
||||
let sourceListEl;
|
||||
let sourceCountEl;
|
||||
let globalSearchFormEl;
|
||||
let globalSearchInputEl;
|
||||
let globalSearchClearEl;
|
||||
let localSearchFormEl;
|
||||
let localSearchInputEl;
|
||||
let localSearchClearEl;
|
||||
let translationSelectEl;
|
||||
let translationControlEl;
|
||||
let compareSelectEl;
|
||||
@@ -54,6 +84,7 @@
|
||||
let detailHeadingToolsEl;
|
||||
let detailBodyEl;
|
||||
let textLayoutEl;
|
||||
let showVerseHeadsEl;
|
||||
let lexiconPopupEl;
|
||||
let lexiconPopupTitleEl;
|
||||
let lexiconPopupSubtitleEl;
|
||||
@@ -72,8 +103,11 @@
|
||||
sourceCountEl = document.getElementById("alpha-text-source-count");
|
||||
globalSearchFormEl = document.getElementById("alpha-text-global-search-form");
|
||||
globalSearchInputEl = document.getElementById("alpha-text-global-search-input");
|
||||
globalSearchClearEl = document.getElementById("alpha-text-global-search-clear");
|
||||
localSearchFormEl = document.getElementById("alpha-text-local-search-form");
|
||||
localSearchInputEl = document.getElementById("alpha-text-local-search-input");
|
||||
localSearchClearEl = document.getElementById("alpha-text-local-search-clear");
|
||||
showVerseHeadsEl = document.getElementById("alpha-text-show-verse-heads");
|
||||
translationSelectEl = document.getElementById("alpha-text-translation-select");
|
||||
translationControlEl = translationSelectEl?.closest?.(".alpha-text-control") || null;
|
||||
compareSelectEl = document.getElementById("alpha-text-compare-select");
|
||||
@@ -88,9 +122,21 @@
|
||||
detailHeadingToolsEl = document.querySelector("#alphabet-text-section .alpha-text-heading-tools");
|
||||
detailBodyEl = document.getElementById("alpha-text-detail-body");
|
||||
textLayoutEl = sourceListEl?.closest?.(".planet-layout") || detailBodyEl?.closest?.(".planet-layout") || null;
|
||||
syncReaderDisplayControls();
|
||||
ensureLexiconPopup();
|
||||
}
|
||||
|
||||
function syncReaderDisplayControls() {
|
||||
if (showVerseHeadsEl instanceof HTMLInputElement) {
|
||||
showVerseHeadsEl.checked = Boolean(state.showVerseHeads);
|
||||
}
|
||||
|
||||
if (textLayoutEl instanceof HTMLElement) {
|
||||
textLayoutEl.classList.toggle("alpha-text-hide-verse-heads", !state.showVerseHeads);
|
||||
textLayoutEl.setAttribute("data-show-verse-heads", state.showVerseHeads ? "true" : "false");
|
||||
}
|
||||
}
|
||||
|
||||
function setGlobalSearchHeadingMode(isGlobalSearchOnly) {
|
||||
if (textLayoutEl instanceof HTMLElement) {
|
||||
textLayoutEl.classList.toggle("alpha-text-global-search-only", Boolean(isGlobalSearchOnly));
|
||||
@@ -121,6 +167,16 @@
|
||||
window.TarotChromeUi?.showDetailOnly?.(textLayoutEl);
|
||||
}
|
||||
|
||||
function showSidebarOnlyMode(persist = false) {
|
||||
if (!(textLayoutEl instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.TarotChromeUi?.initializeSidebarPopouts?.();
|
||||
window.TarotChromeUi?.initializeDetailPopouts?.();
|
||||
window.TarotChromeUi?.showSidebarOnly?.(textLayoutEl, persist);
|
||||
}
|
||||
|
||||
function ensureLexiconPopup() {
|
||||
if (lexiconPopupEl instanceof HTMLElement) {
|
||||
return;
|
||||
@@ -345,6 +401,9 @@
|
||||
if (normalizedCount === 1) {
|
||||
return `${normalizedCount} ${baseLabel}`;
|
||||
}
|
||||
if (/[^aeiou]y$/i.test(baseLabel)) {
|
||||
return `${normalizedCount} ${baseLabel.slice(0, -1)}ies`;
|
||||
}
|
||||
return `${normalizedCount} ${baseLabel.endsWith("s") ? baseLabel : `${baseLabel}s`}`;
|
||||
}
|
||||
|
||||
@@ -577,7 +636,18 @@
|
||||
}
|
||||
|
||||
function updateSearchControls() {
|
||||
return;
|
||||
const globalQuery = String(globalSearchInputEl?.value || state.globalSearchQuery || "").trim();
|
||||
const localQuery = String(localSearchInputEl?.value || state.localSearchQuery || "").trim();
|
||||
const hasGlobalSearch = Boolean(globalQuery) || (state.activeSearchScope === "global" && Boolean(state.searchQuery));
|
||||
const hasLocalSearch = Boolean(localQuery) || (state.activeSearchScope === "source" && Boolean(state.searchQuery));
|
||||
|
||||
if (globalSearchClearEl instanceof HTMLButtonElement) {
|
||||
globalSearchClearEl.disabled = !hasGlobalSearch;
|
||||
}
|
||||
|
||||
if (localSearchClearEl instanceof HTMLButtonElement) {
|
||||
localSearchClearEl.disabled = !hasLocalSearch;
|
||||
}
|
||||
}
|
||||
|
||||
function clearActiveSearchUi(options = {}) {
|
||||
@@ -735,6 +805,15 @@
|
||||
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
function buildWholeWordMatcher(query, flags = "iu") {
|
||||
const normalizedQuery = String(query || "").trim();
|
||||
if (!normalizedQuery) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new RegExp(`(^|[^\\p{L}\\p{N}])(${escapeRegExp(normalizedQuery)})(?=$|[^\\p{L}\\p{N}])`, flags);
|
||||
}
|
||||
|
||||
function appendHighlightedText(target, text, query) {
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
@@ -748,20 +827,30 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const matcher = new RegExp(escapeRegExp(normalizedQuery), "ig");
|
||||
const matcher = buildWholeWordMatcher(normalizedQuery, "giu");
|
||||
if (!matcher) {
|
||||
target.textContent = sourceText;
|
||||
return;
|
||||
}
|
||||
|
||||
let lastIndex = 0;
|
||||
let match = matcher.exec(sourceText);
|
||||
while (match) {
|
||||
if (match.index > lastIndex) {
|
||||
target.appendChild(document.createTextNode(sourceText.slice(lastIndex, match.index)));
|
||||
const prefixLength = String(match[1] || "").length;
|
||||
const matchedText = String(match[2] || "");
|
||||
const matchStart = match.index + prefixLength;
|
||||
const matchEnd = matchStart + matchedText.length;
|
||||
|
||||
if (matchStart > lastIndex) {
|
||||
target.appendChild(document.createTextNode(sourceText.slice(lastIndex, matchStart)));
|
||||
}
|
||||
|
||||
const mark = document.createElement("mark");
|
||||
mark.className = "alpha-text-mark";
|
||||
mark.textContent = sourceText.slice(match.index, match.index + match[0].length);
|
||||
mark.textContent = sourceText.slice(matchStart, matchEnd);
|
||||
target.appendChild(mark);
|
||||
|
||||
lastIndex = match.index + match[0].length;
|
||||
lastIndex = matchEnd;
|
||||
match = matcher.exec(sourceText);
|
||||
}
|
||||
|
||||
@@ -1006,7 +1095,7 @@
|
||||
|
||||
fillSelect(translationSelectEl, variants, state.selectedSourceId, (entry) => buildTranslationOptionLabel(entry));
|
||||
fillSelect(compareSelectEl, compareCandidates, compareSource?.id || "", (entry) => buildTranslationOptionLabel(entry));
|
||||
fillSelect(workSelectEl, works, state.selectedWorkId, (entry) => `${entry.title} (${entry.sectionCount} ${String(source?.sectionLabel || "section").toLowerCase()}s)`);
|
||||
fillSelect(workSelectEl, works, state.selectedWorkId, (entry) => `${entry.title} (${formatCountLabel(entry.sectionCount, String(source?.sectionLabel || "section").toLowerCase())})`);
|
||||
fillSelect(sectionSelectEl, sections, state.selectedSectionId, (entry) => `${entry.label} · ${entry.verseCount} verses`);
|
||||
|
||||
if (translationSelectEl instanceof HTMLSelectElement) {
|
||||
@@ -1703,15 +1792,28 @@
|
||||
const summary = document.createElement("p");
|
||||
summary.className = "alpha-text-search-summary";
|
||||
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "alpha-text-search-actions";
|
||||
|
||||
const closeButton = document.createElement("button");
|
||||
closeButton.type = "button";
|
||||
closeButton.className = "alpha-nav-btn";
|
||||
closeButton.textContent = "Close Search";
|
||||
closeButton.addEventListener("click", () => {
|
||||
clearScopedSearch(state.activeSearchScope === "source" ? "source" : "global");
|
||||
renderDetail();
|
||||
});
|
||||
actions.appendChild(closeButton);
|
||||
|
||||
if (state.searchLoading) {
|
||||
summary.textContent = `Searching ${scopeLabel} for \"${state.searchQuery}\"...`;
|
||||
card.appendChild(summary);
|
||||
card.append(summary, actions);
|
||||
return card;
|
||||
}
|
||||
|
||||
if (state.searchError) {
|
||||
summary.textContent = `Search scope: ${scopeLabel}`;
|
||||
card.append(summary, createEmptyMessage(state.searchError));
|
||||
card.append(summary, actions, createEmptyMessage(state.searchError));
|
||||
return card;
|
||||
}
|
||||
|
||||
@@ -1719,7 +1821,7 @@
|
||||
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);
|
||||
card.append(summary, actions);
|
||||
|
||||
if (!Array.isArray(payload?.results) || !payload.results.length) {
|
||||
card.appendChild(createEmptyMessage(`No matches found for \"${state.searchQuery}\".`));
|
||||
@@ -2026,6 +2128,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (globalSearchClearEl instanceof HTMLButtonElement) {
|
||||
globalSearchClearEl.addEventListener("click", () => {
|
||||
clearScopedSearch("global");
|
||||
renderDetail();
|
||||
});
|
||||
}
|
||||
|
||||
if (localSearchFormEl instanceof HTMLFormElement) {
|
||||
localSearchFormEl.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
@@ -2033,6 +2142,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (localSearchClearEl instanceof HTMLButtonElement) {
|
||||
localSearchClearEl.addEventListener("click", () => {
|
||||
clearScopedSearch("source");
|
||||
renderDetail();
|
||||
});
|
||||
}
|
||||
|
||||
if (localSearchInputEl instanceof HTMLInputElement) {
|
||||
localSearchInputEl.addEventListener("input", () => {
|
||||
state.localSearchQuery = String(localSearchInputEl.value || "").trim();
|
||||
@@ -2044,6 +2160,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (showVerseHeadsEl instanceof HTMLInputElement) {
|
||||
showVerseHeadsEl.addEventListener("change", () => {
|
||||
state.showVerseHeads = Boolean(showVerseHeadsEl.checked);
|
||||
writeStoredBoolean(STORAGE_KEYS.showVerseHeads, state.showVerseHeads);
|
||||
syncReaderDisplayControls();
|
||||
});
|
||||
}
|
||||
|
||||
if (translationSelectEl instanceof HTMLSelectElement) {
|
||||
translationSelectEl.addEventListener("change", () => {
|
||||
const sourceGroup = getSelectedSourceGroup();
|
||||
@@ -2134,9 +2258,11 @@
|
||||
}
|
||||
|
||||
await ensureCatalogLoaded();
|
||||
syncReaderDisplayControls();
|
||||
renderSourceList();
|
||||
renderSelectors();
|
||||
updateSearchControls();
|
||||
showSidebarOnlyMode(false);
|
||||
|
||||
if (!state.currentPassage) {
|
||||
await loadSelectedPassage();
|
||||
|
||||
Reference in New Issue
Block a user