311 lines
10 KiB
JavaScript
311 lines
10 KiB
JavaScript
/* ui-holidays-render.js - Render/search helpers for the holiday repository */
|
|
(function () {
|
|
"use strict";
|
|
|
|
function planetLabel(planetId, context) {
|
|
const { state, cap } = context;
|
|
if (!planetId) {
|
|
return "Planet";
|
|
}
|
|
|
|
const planet = state.planetsById.get(planetId);
|
|
if (!planet) {
|
|
return cap(planetId);
|
|
}
|
|
|
|
return `${planet.symbol || ""} ${planet.name || cap(planetId)}`.trim();
|
|
}
|
|
|
|
function zodiacLabel(signId, context) {
|
|
const { state, cap } = context;
|
|
if (!signId) {
|
|
return "Zodiac";
|
|
}
|
|
|
|
const sign = state.signsById.get(signId);
|
|
if (!sign) {
|
|
return cap(signId);
|
|
}
|
|
|
|
return `${sign.symbol || ""} ${sign.name || cap(signId)}`.trim();
|
|
}
|
|
|
|
function godLabel(godId, godName, context) {
|
|
const { state, cap } = context;
|
|
if (godName) {
|
|
return godName;
|
|
}
|
|
|
|
if (!godId) {
|
|
return "Deity";
|
|
}
|
|
|
|
const god = state.godsById.get(godId);
|
|
return god?.name || cap(godId);
|
|
}
|
|
|
|
function hebrewLabel(hebrewLetterId, context) {
|
|
const { state, cap } = context;
|
|
if (!hebrewLetterId) {
|
|
return "Hebrew Letter";
|
|
}
|
|
|
|
const letter = state.hebrewById.get(hebrewLetterId);
|
|
if (!letter) {
|
|
return cap(hebrewLetterId);
|
|
}
|
|
|
|
return `${letter.char || ""} ${letter.name || cap(hebrewLetterId)}`.trim();
|
|
}
|
|
|
|
function computeDigitalRoot(value) {
|
|
let current = Math.abs(Math.trunc(Number(value)));
|
|
if (!Number.isFinite(current)) {
|
|
return null;
|
|
}
|
|
|
|
while (current >= 10) {
|
|
current = String(current)
|
|
.split("")
|
|
.reduce((sum, digit) => sum + Number(digit), 0);
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
function buildAssociationButtons(associations, context) {
|
|
const { getDisplayTarotName, resolveTarotTrumpNumber } = context;
|
|
if (!associations || typeof associations !== "object") {
|
|
return '<div class="planet-text">--</div>';
|
|
}
|
|
|
|
const buttons = [];
|
|
|
|
if (associations.planetId) {
|
|
buttons.push(
|
|
`<button class="alpha-nav-btn" data-nav="planet" data-planet-id="${associations.planetId}">${planetLabel(associations.planetId, context)} -></button>`
|
|
);
|
|
}
|
|
|
|
if (associations.zodiacSignId) {
|
|
buttons.push(
|
|
`<button class="alpha-nav-btn" data-nav="zodiac" data-sign-id="${associations.zodiacSignId}">${zodiacLabel(associations.zodiacSignId, context)} -></button>`
|
|
);
|
|
}
|
|
|
|
if (Number.isFinite(Number(associations.numberValue))) {
|
|
const rawNumber = Math.trunc(Number(associations.numberValue));
|
|
if (rawNumber >= 0) {
|
|
const numberValue = computeDigitalRoot(rawNumber);
|
|
if (numberValue != null) {
|
|
const label = rawNumber === numberValue
|
|
? `Number ${numberValue}`
|
|
: `Number ${numberValue} (from ${rawNumber})`;
|
|
buttons.push(
|
|
`<button class="alpha-nav-btn" data-nav="number" data-number-value="${numberValue}">${label} -></button>`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (associations.tarotCard) {
|
|
const trumpNumber = resolveTarotTrumpNumber(associations.tarotCard);
|
|
const explicitTrumpNumber = Number(associations.tarotTrumpNumber);
|
|
const tarotTrumpNumber = Number.isFinite(explicitTrumpNumber) ? explicitTrumpNumber : trumpNumber;
|
|
const tarotLabel = getDisplayTarotName(associations.tarotCard, tarotTrumpNumber);
|
|
buttons.push(
|
|
`<button class="alpha-nav-btn" data-nav="tarot-card" data-card-name="${associations.tarotCard}" data-trump-number="${tarotTrumpNumber ?? ""}">${tarotLabel} -></button>`
|
|
);
|
|
}
|
|
|
|
if (associations.godId || associations.godName) {
|
|
const label = godLabel(associations.godId, associations.godName, context);
|
|
buttons.push(
|
|
`<button class="alpha-nav-btn" data-nav="god" data-god-id="${associations.godId || ""}" data-god-name="${associations.godName || label}">${label} -></button>`
|
|
);
|
|
}
|
|
|
|
if (associations.hebrewLetterId) {
|
|
buttons.push(
|
|
`<button class="alpha-nav-btn" data-nav="alphabet" data-alphabet="hebrew" data-hebrew-letter-id="${associations.hebrewLetterId}">${hebrewLabel(associations.hebrewLetterId, context)} -></button>`
|
|
);
|
|
}
|
|
|
|
if (associations.kabbalahPathNumber != null) {
|
|
buttons.push(
|
|
`<button class="alpha-nav-btn" data-nav="kabbalah" data-path-no="${associations.kabbalahPathNumber}">Path ${associations.kabbalahPathNumber} -></button>`
|
|
);
|
|
}
|
|
|
|
if (associations.iChingPlanetaryInfluence) {
|
|
buttons.push(
|
|
`<button class="alpha-nav-btn" data-nav="iching" data-planetary-influence="${associations.iChingPlanetaryInfluence}">I Ching - ${associations.iChingPlanetaryInfluence} -></button>`
|
|
);
|
|
}
|
|
|
|
if (!buttons.length) {
|
|
return '<div class="planet-text">--</div>';
|
|
}
|
|
|
|
return `<div class="alpha-nav-btns">${buttons.join("")}</div>`;
|
|
}
|
|
|
|
function associationSearchText(associations, context) {
|
|
const { getTarotCardSearchAliases } = context;
|
|
if (!associations || typeof associations !== "object") {
|
|
return "";
|
|
}
|
|
|
|
const tarotAliases = associations.tarotCard && typeof getTarotCardSearchAliases === "function"
|
|
? getTarotCardSearchAliases(associations.tarotCard, { trumpNumber: associations.tarotTrumpNumber })
|
|
: [];
|
|
|
|
return [
|
|
associations.planetId,
|
|
associations.zodiacSignId,
|
|
associations.numberValue,
|
|
associations.tarotCard,
|
|
associations.tarotTrumpNumber,
|
|
...tarotAliases,
|
|
associations.godId,
|
|
associations.godName,
|
|
associations.hebrewLetterId,
|
|
associations.kabbalahPathNumber,
|
|
associations.iChingPlanetaryInfluence
|
|
].filter(Boolean).join(" ");
|
|
}
|
|
|
|
function holidaySearchText(holiday, context) {
|
|
const { normalizeSearchValue } = context;
|
|
return normalizeSearchValue([
|
|
holiday?.name,
|
|
holiday?.kind,
|
|
holiday?.date,
|
|
holiday?.dateRange,
|
|
holiday?.dateText,
|
|
holiday?.monthDayStart,
|
|
holiday?.calendarId,
|
|
holiday?.description,
|
|
associationSearchText(holiday?.associations, context)
|
|
].filter(Boolean).join(" "));
|
|
}
|
|
|
|
function renderList(context) {
|
|
const {
|
|
elements,
|
|
state,
|
|
filterBySource,
|
|
normalizeSourceFilter,
|
|
calendarLabel,
|
|
monthLabelForCalendar,
|
|
selectByHolidayId
|
|
} = context;
|
|
const { listEl, countEl } = elements;
|
|
if (!listEl) {
|
|
return;
|
|
}
|
|
|
|
listEl.innerHTML = "";
|
|
|
|
state.filteredHolidays.forEach((holiday) => {
|
|
const isSelected = holiday.id === state.selectedHolidayId;
|
|
const itemEl = document.createElement("div");
|
|
itemEl.className = `planet-list-item${isSelected ? " is-selected" : ""}`;
|
|
itemEl.setAttribute("role", "option");
|
|
itemEl.setAttribute("aria-selected", isSelected ? "true" : "false");
|
|
itemEl.dataset.holidayId = holiday.id;
|
|
|
|
const sourceCalendar = calendarLabel(holiday.calendarId);
|
|
const sourceMonth = monthLabelForCalendar(holiday.calendarId, holiday.monthId);
|
|
const sourceDate = holiday?.dateText || holiday?.date || holiday?.dateRange || "--";
|
|
|
|
itemEl.innerHTML = `
|
|
<div class="planet-list-name">${holiday?.name || holiday?.id || "Holiday"}</div>
|
|
<div class="planet-list-meta">${sourceCalendar} - ${sourceMonth} - ${sourceDate}</div>
|
|
`;
|
|
|
|
itemEl.addEventListener("click", () => {
|
|
selectByHolidayId(holiday.id, elements);
|
|
});
|
|
|
|
listEl.appendChild(itemEl);
|
|
});
|
|
|
|
if (countEl) {
|
|
const sourceFiltered = filterBySource(state.holidays);
|
|
const activeFilter = normalizeSourceFilter(state.selectedSource);
|
|
const sourceLabel = activeFilter === "all"
|
|
? ""
|
|
: ` (${calendarLabel(activeFilter)})`;
|
|
countEl.textContent = state.searchQuery
|
|
? `${state.filteredHolidays.length} of ${sourceFiltered.length} holidays${sourceLabel}`
|
|
: `${sourceFiltered.length} holidays${sourceLabel}`;
|
|
}
|
|
}
|
|
|
|
function renderHolidayDetail(holiday, context) {
|
|
const {
|
|
state,
|
|
calendarLabel,
|
|
monthLabelForCalendar,
|
|
resolveHolidayGregorianDate,
|
|
formatGregorianReferenceDate,
|
|
formatCalendarDateFromGregorian
|
|
} = context;
|
|
const gregorianDate = resolveHolidayGregorianDate(holiday);
|
|
const gregorianRef = formatGregorianReferenceDate(gregorianDate);
|
|
const hebrewRef = formatCalendarDateFromGregorian(gregorianDate, "hebrew");
|
|
const islamicRef = formatCalendarDateFromGregorian(gregorianDate, "islamic");
|
|
const confidence = String(holiday?.conversionConfidence || holiday?.datePrecision || "approximate").toLowerCase();
|
|
const confidenceLabel = (!(gregorianDate instanceof Date) || Number.isNaN(gregorianDate.getTime()))
|
|
? "unresolved"
|
|
: (confidence === "exact" ? "exact" : "approximate");
|
|
const monthName = monthLabelForCalendar(holiday?.calendarId, holiday?.monthId);
|
|
const holidayDate = holiday?.dateText || holiday?.date || holiday?.dateRange || "--";
|
|
const sourceMonthLink = holiday?.monthId
|
|
? `<div class="alpha-nav-btns"><button class="alpha-nav-btn" data-nav="calendar-month" data-calendar-id="${holiday.calendarId || ""}" data-month-id="${holiday.monthId}">Open ${calendarLabel(holiday?.calendarId)} ${monthName} -></button></div>`
|
|
: "";
|
|
|
|
return `
|
|
<div class="planet-meta-grid">
|
|
<div class="planet-meta-card">
|
|
<strong>Holiday Facts</strong>
|
|
<div class="planet-text">
|
|
<dl class="alpha-dl">
|
|
<dt>Source Calendar</dt><dd>${calendarLabel(holiday?.calendarId)}</dd>
|
|
<dt>Source Month</dt><dd>${monthName}</dd>
|
|
<dt>Source Date</dt><dd>${holidayDate}</dd>
|
|
<dt>Reference Year</dt><dd>${state.selectedYear}</dd>
|
|
<dt>Conversion</dt><dd>${confidenceLabel}</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<div class="planet-meta-card">
|
|
<strong>Cross-Calendar Dates</strong>
|
|
<div class="planet-text">
|
|
<dl class="alpha-dl">
|
|
<dt>Gregorian</dt><dd>${gregorianRef}</dd>
|
|
<dt>Hebrew</dt><dd>${hebrewRef}</dd>
|
|
<dt>Islamic</dt><dd>${islamicRef}</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<div class="planet-meta-card">
|
|
<strong>Description</strong>
|
|
<div class="planet-text">${holiday?.description || "--"}</div>
|
|
${sourceMonthLink}
|
|
</div>
|
|
<div class="planet-meta-card">
|
|
<strong>Associations</strong>
|
|
${buildAssociationButtons(holiday?.associations, context)}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
window.HolidayRenderUi = {
|
|
holidaySearchText,
|
|
renderList,
|
|
renderHolidayDetail
|
|
};
|
|
})(); |