update text and now panel
This commit is contained in:
156
app/styles.css
156
app/styles.css
@@ -1183,13 +1183,15 @@
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
}
|
||||
.planet-meta-card {
|
||||
.planet-meta-card,
|
||||
.detail-meta-card {
|
||||
border: 1px solid #3f3f46;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
background: #111118;
|
||||
}
|
||||
.planet-meta-card strong {
|
||||
.planet-meta-card strong,
|
||||
.detail-meta-card strong {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #a1a1aa;
|
||||
@@ -3173,9 +3175,36 @@
|
||||
.alpha-text-controls {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
border-top: 1px solid #27272a;
|
||||
background: #101018;
|
||||
}
|
||||
|
||||
.alpha-text-detail-heading {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
padding-right: 108px;
|
||||
}
|
||||
|
||||
.alpha-text-heading-main {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.alpha-text-heading-tools {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 420px) minmax(320px, 1fr);
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.alpha-text-controls--heading {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
padding: 14px;
|
||||
border: 1px solid #2f2f39;
|
||||
border-radius: 14px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(24, 24, 38, 0.98), rgba(12, 12, 18, 0.98));
|
||||
box-shadow: inset 0 0 0 1px rgba(99, 102, 241, 0.08);
|
||||
box-sizing: border-box;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.alpha-text-search-controls {
|
||||
@@ -3197,6 +3226,36 @@
|
||||
background:
|
||||
linear-gradient(180deg, rgba(24, 24, 38, 0.98), rgba(12, 12, 18, 0.98));
|
||||
box-shadow: inset 0 0 0 1px rgba(99, 102, 241, 0.08);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.alpha-text-search-controls--heading {
|
||||
align-content: start;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.alpha-text-search-inline {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.alpha-text-search-submit-inline {
|
||||
min-height: 42px;
|
||||
padding: 0 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.alpha-text-detail-heading .detail-toggle-inline {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.alpha-text-control {
|
||||
@@ -3212,6 +3271,7 @@
|
||||
|
||||
.alpha-text-select {
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
padding: 7px 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #3f3f46;
|
||||
@@ -3223,6 +3283,7 @@
|
||||
|
||||
.alpha-text-search-input {
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
padding: 9px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #4338ca;
|
||||
@@ -3269,6 +3330,31 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1040px) {
|
||||
.alpha-text-heading-tools {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.alpha-text-detail-heading {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.alpha-text-controls--heading {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.alpha-text-search-inline {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.alpha-text-detail-heading .detail-toggle-inline {
|
||||
position: static;
|
||||
justify-self: start;
|
||||
}
|
||||
}
|
||||
|
||||
.alpha-text-meta-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
@@ -3276,6 +3362,30 @@
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.alpha-text-extra-card {
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.alpha-text-extra-group {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.alpha-text-extra-group + .alpha-text-extra-group {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.alpha-text-extra-label {
|
||||
color: #a1a1aa;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.03em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.alpha-text-extra-actions {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.alpha-text-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -3289,6 +3399,24 @@
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.alpha-text-reader-card {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.alpha-text-reader-navigation {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #27272a;
|
||||
}
|
||||
|
||||
.alpha-text-reader-nav-btn--next {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.alpha-text-search-summary {
|
||||
margin: 0 0 10px;
|
||||
color: #a1a1aa;
|
||||
@@ -3410,6 +3538,16 @@
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.alpha-text-verse-text--original {
|
||||
color: #f5f3ff;
|
||||
font-family: var(--font-script-arabic), "Noto Serif", serif;
|
||||
}
|
||||
|
||||
.alpha-text-verse-text--transliteration {
|
||||
color: #cbd5f5;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.alpha-text-token-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -4629,11 +4767,11 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
.now-stats-sabian {
|
||||
font-size: clamp(17px, 2.5vmin, 23px);
|
||||
font-weight: 550;
|
||||
line-height: 1.32;
|
||||
font-size: clamp(13px, 1.75vmin, 16px);
|
||||
font-weight: 500;
|
||||
line-height: 1.42;
|
||||
color: #f8fafc;
|
||||
white-space: pre-line;
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
@@ -25,17 +25,16 @@
|
||||
searchLoading: false,
|
||||
searchError: "",
|
||||
searchRequestId: 0,
|
||||
highlightedVerseId: ""
|
||||
highlightedVerseId: "",
|
||||
displayPreferencesBySource: {}
|
||||
};
|
||||
|
||||
let sourceListEl;
|
||||
let sourceCountEl;
|
||||
let globalSearchFormEl;
|
||||
let globalSearchInputEl;
|
||||
let globalSearchClearEl;
|
||||
let localSearchFormEl;
|
||||
let localSearchInputEl;
|
||||
let localSearchClearEl;
|
||||
let workSelectEl;
|
||||
let sectionSelectEl;
|
||||
let detailNameEl;
|
||||
@@ -59,10 +58,8 @@
|
||||
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");
|
||||
workSelectEl = document.getElementById("alpha-text-work-select");
|
||||
sectionSelectEl = document.getElementById("alpha-text-section-select");
|
||||
detailNameEl = document.getElementById("alpha-text-detail-name");
|
||||
@@ -151,6 +148,82 @@
|
||||
return findById(work?.sections, state.selectedSectionId);
|
||||
}
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
function getSearchInput(scope) {
|
||||
return scope === "source" ? localSearchInputEl : globalSearchInputEl;
|
||||
}
|
||||
@@ -169,12 +242,131 @@
|
||||
}
|
||||
|
||||
function updateSearchControls() {
|
||||
if (globalSearchClearEl instanceof HTMLButtonElement) {
|
||||
globalSearchClearEl.disabled = !String(globalSearchInputEl?.value || state.globalSearchQuery || "").trim();
|
||||
return;
|
||||
}
|
||||
|
||||
if (localSearchClearEl instanceof HTMLButtonElement) {
|
||||
localSearchClearEl.disabled = !String(localSearchInputEl?.value || state.localSearchQuery || "").trim();
|
||||
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;
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +461,7 @@
|
||||
|
||||
function createCard(title) {
|
||||
const card = document.createElement("div");
|
||||
card.className = "planet-meta-card";
|
||||
card.className = "detail-meta-card planet-meta-card";
|
||||
if (title) {
|
||||
const heading = document.createElement("strong");
|
||||
heading.textContent = title;
|
||||
@@ -302,6 +494,25 @@
|
||||
detailBodyEl.appendChild(card);
|
||||
}
|
||||
|
||||
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 || "--"}`;
|
||||
}
|
||||
|
||||
function syncSelectionForSource(source) {
|
||||
const works = Array.isArray(source?.works) ? source.works : [];
|
||||
if (!works.length) {
|
||||
@@ -739,6 +950,7 @@
|
||||
const source = passage?.source || getSelectedSource();
|
||||
const work = passage?.work || getSelectedWork(source);
|
||||
const section = passage?.section || getSelectedSection(source, work);
|
||||
const displayPreferences = getSourceDisplayPreferences(source, passage);
|
||||
const metaGrid = document.createElement("div");
|
||||
metaGrid.className = "alpha-text-meta-grid";
|
||||
|
||||
@@ -755,49 +967,68 @@
|
||||
`;
|
||||
metaGrid.appendChild(overviewCard);
|
||||
|
||||
const navigationCard = createCard("Navigation");
|
||||
const toolbar = document.createElement("div");
|
||||
toolbar.className = "alpha-text-toolbar";
|
||||
if (displayPreferences.capabilities.hasAnyExtras) {
|
||||
const extraCard = createCard("Extra");
|
||||
extraCard.classList.add("alpha-text-extra-card");
|
||||
|
||||
const previousButton = document.createElement("button");
|
||||
previousButton.type = "button";
|
||||
previousButton.className = "alpha-nav-btn";
|
||||
previousButton.textContent = "← Previous";
|
||||
previousButton.disabled = !passage?.navigation?.previous;
|
||||
previousButton.addEventListener("click", () => {
|
||||
if (!passage?.navigation?.previous) {
|
||||
return;
|
||||
}
|
||||
state.selectedWorkId = passage.navigation.previous.workId;
|
||||
state.selectedSectionId = passage.navigation.previous.sectionId;
|
||||
state.lexiconEntry = null;
|
||||
renderSelectors();
|
||||
void loadSelectedPassage();
|
||||
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);
|
||||
});
|
||||
|
||||
const nextButton = document.createElement("button");
|
||||
nextButton.type = "button";
|
||||
nextButton.className = "alpha-nav-btn";
|
||||
nextButton.textContent = "Next →";
|
||||
nextButton.disabled = !passage?.navigation?.next;
|
||||
nextButton.addEventListener("click", () => {
|
||||
if (!passage?.navigation?.next) {
|
||||
return;
|
||||
displayGroup.append(displayLabel, displayButtons);
|
||||
extraCard.appendChild(displayGroup);
|
||||
}
|
||||
state.selectedWorkId = passage.navigation.next.workId;
|
||||
state.selectedSectionId = passage.navigation.next.sectionId;
|
||||
state.lexiconEntry = null;
|
||||
renderSelectors();
|
||||
void loadSelectedPassage();
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
const location = document.createElement("div");
|
||||
location.className = "planet-text";
|
||||
location.textContent = `${work?.title || "--"} · ${section?.title || "--"}`;
|
||||
interlinearButtons.appendChild(interlinearButton);
|
||||
interlinearGroup.append(interlinearLabel, interlinearButtons);
|
||||
extraCard.appendChild(interlinearGroup);
|
||||
}
|
||||
|
||||
toolbar.append(previousButton, nextButton);
|
||||
navigationCard.append(toolbar, location);
|
||||
metaGrid.appendChild(navigationCard);
|
||||
metaGrid.appendChild(extraCard);
|
||||
}
|
||||
|
||||
if (source?.features?.hasTokenAnnotations) {
|
||||
const noteCard = createCard("Reader Mode");
|
||||
@@ -809,6 +1040,8 @@
|
||||
}
|
||||
|
||||
function createPlainVerse(verse) {
|
||||
const source = getSelectedSource();
|
||||
const displayPreferences = getSourceDisplayPreferences(source, state.currentPassage);
|
||||
const article = document.createElement("article");
|
||||
article.className = "alpha-text-verse";
|
||||
article.classList.toggle("is-highlighted", isHighlightedVerse(verse));
|
||||
@@ -820,12 +1053,9 @@
|
||||
reference.className = "alpha-text-verse-reference";
|
||||
reference.textContent = verse.reference || (verse.number ? `Verse ${verse.number}` : "");
|
||||
|
||||
const text = document.createElement("p");
|
||||
text.className = "alpha-text-verse-text";
|
||||
appendHighlightedText(text, verse.text || "", isHighlightedVerse(verse) ? state.searchQuery : "");
|
||||
|
||||
head.append(reference);
|
||||
article.append(head, text);
|
||||
article.append(head);
|
||||
appendVerseTextLines(article, verse, source, displayPreferences, verse.text || "");
|
||||
return article;
|
||||
}
|
||||
|
||||
@@ -840,9 +1070,52 @@
|
||||
return glossText || String(fallbackText || "").trim();
|
||||
}
|
||||
|
||||
function createTokenVerse(verse, lexiconId) {
|
||||
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) {
|
||||
const article = document.createElement("article");
|
||||
article.className = "alpha-text-verse alpha-text-verse--interlinear";
|
||||
article.className = "alpha-text-verse";
|
||||
article.classList.toggle("alpha-text-verse--interlinear", Boolean(displayPreferences?.showInterlinear));
|
||||
article.classList.toggle("is-highlighted", isHighlightedVerse(verse));
|
||||
|
||||
const head = document.createElement("div");
|
||||
@@ -852,14 +1125,6 @@
|
||||
reference.className = "alpha-text-verse-reference";
|
||||
reference.textContent = verse.reference || (verse.number ? `Verse ${verse.number}` : "");
|
||||
|
||||
const gloss = document.createElement("p");
|
||||
gloss.className = "alpha-text-verse-text";
|
||||
appendHighlightedText(
|
||||
gloss,
|
||||
buildTokenTranslationText(verse?.tokens, verse?.text),
|
||||
isHighlightedVerse(verse) ? state.searchQuery : ""
|
||||
);
|
||||
|
||||
const tokenGrid = document.createElement("div");
|
||||
tokenGrid.className = "alpha-text-token-grid";
|
||||
|
||||
@@ -895,13 +1160,19 @@
|
||||
});
|
||||
|
||||
head.append(reference);
|
||||
article.append(head, gloss, tokenGrid);
|
||||
article.append(head);
|
||||
appendVerseTextLines(article, verse, source, displayPreferences, buildTokenTranslationText(verse?.tokens, verse?.text));
|
||||
if (displayPreferences?.showInterlinear) {
|
||||
article.appendChild(tokenGrid);
|
||||
}
|
||||
return article;
|
||||
}
|
||||
|
||||
function createReaderCard(passage) {
|
||||
const source = passage?.source || getSelectedSource();
|
||||
const card = createCard(`${source?.title || "Text"} Reader`);
|
||||
const displayPreferences = getSourceDisplayPreferences(source, passage);
|
||||
const card = createCard(getPassageLocationLabel(passage));
|
||||
card.classList.add("alpha-text-reader-card");
|
||||
const reader = document.createElement("div");
|
||||
reader.className = "alpha-text-reader";
|
||||
|
||||
@@ -920,12 +1191,42 @@
|
||||
|
||||
verses.forEach((verse) => {
|
||||
const verseEl = source?.features?.hasTokenAnnotations
|
||||
? createTokenVerse(verse, source.features.lexiconIds?.[0] || "")
|
||||
? createTokenVerse(verse, source.features.lexiconIds?.[0] || "", displayPreferences, source)
|
||||
: createPlainVerse(verse);
|
||||
reader.appendChild(verseEl);
|
||||
});
|
||||
|
||||
card.appendChild(reader);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
@@ -1168,6 +1469,8 @@
|
||||
renderSourceList();
|
||||
renderSelectors();
|
||||
await loadSelectedPassage();
|
||||
clearActiveSearchUi({ preserveHighlight: true });
|
||||
renderDetail();
|
||||
}
|
||||
|
||||
function bindControls() {
|
||||
@@ -1193,13 +1496,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (globalSearchClearEl instanceof HTMLButtonElement) {
|
||||
globalSearchClearEl.addEventListener("click", () => {
|
||||
clearScopedSearch("global");
|
||||
renderDetail();
|
||||
});
|
||||
}
|
||||
|
||||
if (localSearchFormEl instanceof HTMLFormElement) {
|
||||
localSearchFormEl.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
@@ -1218,13 +1514,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (localSearchClearEl instanceof HTMLButtonElement) {
|
||||
localSearchClearEl.addEventListener("click", () => {
|
||||
clearScopedSearch("source");
|
||||
renderDetail();
|
||||
});
|
||||
}
|
||||
|
||||
if (workSelectEl) {
|
||||
workSelectEl.addEventListener("change", () => {
|
||||
state.selectedWorkId = String(workSelectEl.value || "");
|
||||
|
||||
@@ -27,6 +27,18 @@
|
||||
let moonCountdownCache = null;
|
||||
let decanCountdownCache = null;
|
||||
|
||||
function joinSabianPhrases(phrases) {
|
||||
const parts = (Array.isArray(phrases) ? phrases : [])
|
||||
.map((phrase) => String(phrase || "").trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (!parts.length) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
function renderNowStatsFromSnapshot(elements, stats) {
|
||||
if (elements.nowStatsPlanetsEl) {
|
||||
elements.nowStatsPlanetsEl.replaceChildren();
|
||||
@@ -47,13 +59,10 @@
|
||||
if (elements.nowStatsSabianEl) {
|
||||
const sunSabianSymbol = stats?.sunSabianSymbol || null;
|
||||
const moonSabianSymbol = stats?.moonSabianSymbol || null;
|
||||
const sunLine = sunSabianSymbol?.phrase
|
||||
? `Sun Sabian ${sunSabianSymbol.absoluteDegree}: ${sunSabianSymbol.phrase}`
|
||||
: "Sun Sabian: --";
|
||||
const moonLine = moonSabianSymbol?.phrase
|
||||
? `Moon Sabian ${moonSabianSymbol.absoluteDegree}: ${moonSabianSymbol.phrase}`
|
||||
: "Moon Sabian: --";
|
||||
elements.nowStatsSabianEl.textContent = `${sunLine}\n${moonLine}`;
|
||||
elements.nowStatsSabianEl.textContent = joinSabianPhrases([
|
||||
moonSabianSymbol?.phrase,
|
||||
sunSabianSymbol?.phrase
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
42
index.html
42
index.html
@@ -16,7 +16,7 @@
|
||||
<link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-400.css">
|
||||
<link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-700.css">
|
||||
<link rel="stylesheet" href="node_modules/@fontsource/noto-naskh-arabic/arabic-400.css">
|
||||
<link rel="stylesheet" href="app/styles.css?v=20260310-text-search-split-05">
|
||||
<link rel="stylesheet" href="app/styles.css?v=20260310-now-stats-01">
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
@@ -714,15 +714,22 @@
|
||||
<form id="alpha-text-global-search-form" class="alpha-text-search-controls alpha-text-search-controls--sidebar">
|
||||
<label class="alpha-text-control" for="alpha-text-global-search-input">
|
||||
<span>Search All Texts</span>
|
||||
<input id="alpha-text-global-search-input" class="alpha-text-search-input" type="search" placeholder="Search across every text source" aria-label="Search all texts">
|
||||
</label>
|
||||
<div class="alpha-text-search-actions">
|
||||
<button id="alpha-text-global-search-submit" class="alpha-nav-btn" type="submit">Search All</button>
|
||||
<button id="alpha-text-global-search-clear" class="alpha-nav-btn alpha-nav-btn--ghost" type="button" disabled>Clear</button>
|
||||
<div class="alpha-text-search-inline">
|
||||
<input id="alpha-text-global-search-input" class="alpha-text-search-input" type="search" placeholder="Search across every text source" aria-label="Search all texts">
|
||||
<button id="alpha-text-global-search-submit" class="alpha-nav-btn alpha-text-search-submit-inline" type="submit">Search</button>
|
||||
</div>
|
||||
</form>
|
||||
<div id="alpha-text-source-list" class="planet-card-list" role="listbox" aria-label="Text sources"></div>
|
||||
<div class="alpha-text-controls">
|
||||
</aside>
|
||||
<section class="planet-detail-panel" aria-live="polite">
|
||||
<div class="planet-detail-heading alpha-text-detail-heading">
|
||||
<div class="alpha-text-heading-main">
|
||||
<h2 id="alpha-text-detail-name">--</h2>
|
||||
<div id="alpha-text-detail-sub" class="planet-detail-type">Select a text source to begin reading</div>
|
||||
</div>
|
||||
<div class="alpha-text-heading-tools">
|
||||
<div class="alpha-text-controls alpha-text-controls--heading">
|
||||
<label class="alpha-text-control" for="alpha-text-work-select">
|
||||
<span>Work</span>
|
||||
<select id="alpha-text-work-select" class="alpha-text-select" aria-label="Select text work"></select>
|
||||
@@ -732,22 +739,17 @@
|
||||
<select id="alpha-text-section-select" class="alpha-text-select" aria-label="Select text section"></select>
|
||||
</label>
|
||||
</div>
|
||||
</aside>
|
||||
<section class="planet-detail-panel" aria-live="polite">
|
||||
<div class="planet-detail-heading">
|
||||
<h2 id="alpha-text-detail-name">--</h2>
|
||||
<div id="alpha-text-detail-sub" class="planet-detail-type">Select a text source to begin reading</div>
|
||||
</div>
|
||||
<form id="alpha-text-local-search-form" class="alpha-text-search-controls alpha-text-search-controls--detail">
|
||||
<form id="alpha-text-local-search-form" class="alpha-text-search-controls alpha-text-search-controls--detail alpha-text-search-controls--heading">
|
||||
<label class="alpha-text-control" for="alpha-text-local-search-input">
|
||||
<span>Search This Source</span>
|
||||
<input id="alpha-text-local-search-input" class="alpha-text-search-input" type="search" placeholder="Search within the selected source" aria-label="Search current text source">
|
||||
</label>
|
||||
<div class="alpha-text-search-actions">
|
||||
<button id="alpha-text-local-search-submit" class="alpha-nav-btn" type="submit">Search Source</button>
|
||||
<button id="alpha-text-local-search-clear" class="alpha-nav-btn alpha-nav-btn--ghost" type="button" disabled>Clear</button>
|
||||
<div class="alpha-text-search-inline">
|
||||
<input id="alpha-text-local-search-input" class="alpha-text-search-input" type="search" placeholder="Search within the selected source" aria-label="Search current text source">
|
||||
<button id="alpha-text-local-search-submit" class="alpha-nav-btn alpha-text-search-submit-inline" type="submit">Search</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="alpha-text-detail-body" class="alpha-text-detail-body"></div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -937,9 +939,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="now-stats-section">
|
||||
<div class="now-stats-title">Current Planet Positions & Sabian Symbol</div>
|
||||
<div id="now-stats-sabian" class="now-stats-sabian">--</div>
|
||||
<div class="now-stats-title">Planet Positions</div>
|
||||
<div id="now-stats-planets" class="now-stats-planets">--</div>
|
||||
<div id="now-stats-sabian" class="now-stats-sabian">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -993,7 +995,7 @@
|
||||
<script src="app/ui-alphabet-detail.js?v=20260309-enochian-api"></script>
|
||||
<script src="app/ui-alphabet-kabbalah.js"></script>
|
||||
<script src="app/ui-alphabet.js?v=20260308b"></script>
|
||||
<script src="app/ui-alphabet-text.js?v=20260310-text-search-split-06"></script>
|
||||
<script src="app/ui-alphabet-text.js?v=20260310-text-search-split-10"></script>
|
||||
<script src="app/ui-zodiac-references.js"></script>
|
||||
<script src="app/ui-zodiac.js"></script>
|
||||
<script src="app/ui-quiz-bank-builtins-domains.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user