refraction almost completed
This commit is contained in:
@@ -3,6 +3,28 @@
|
||||
"use strict";
|
||||
|
||||
const { getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {};
|
||||
const holidayDataUi = window.HolidayDataUi || {};
|
||||
const holidayRenderUi = window.HolidayRenderUi || {};
|
||||
|
||||
if (
|
||||
typeof holidayDataUi.buildAllHolidays !== "function"
|
||||
|| typeof holidayDataUi.buildCalendarData !== "function"
|
||||
|| typeof holidayDataUi.buildGodsMap !== "function"
|
||||
|| typeof holidayDataUi.buildHebrewMap !== "function"
|
||||
|| typeof holidayDataUi.buildPlanetMap !== "function"
|
||||
|| typeof holidayDataUi.buildSignsMap !== "function"
|
||||
|| typeof holidayDataUi.calendarLabel !== "function"
|
||||
|| typeof holidayDataUi.formatCalendarDateFromGregorian !== "function"
|
||||
|| typeof holidayDataUi.formatGregorianReferenceDate !== "function"
|
||||
|| typeof holidayDataUi.monthLabelForCalendar !== "function"
|
||||
|| typeof holidayDataUi.normalizeSourceFilter !== "function"
|
||||
|| typeof holidayDataUi.resolveHolidayGregorianDate !== "function"
|
||||
|| typeof holidayRenderUi.holidaySearchText !== "function"
|
||||
|| typeof holidayRenderUi.renderList !== "function"
|
||||
|| typeof holidayRenderUi.renderHolidayDetail !== "function"
|
||||
) {
|
||||
throw new Error("HolidayDataUi and HolidayRenderUi modules must load before ui-holidays.js");
|
||||
}
|
||||
|
||||
const state = {
|
||||
initialized: false,
|
||||
@@ -69,52 +91,6 @@
|
||||
"the world": 21
|
||||
};
|
||||
|
||||
const HEBREW_MONTH_ALIAS_BY_ID = {
|
||||
nisan: ["nisan"],
|
||||
iyar: ["iyar"],
|
||||
sivan: ["sivan"],
|
||||
tammuz: ["tamuz", "tammuz"],
|
||||
av: ["av"],
|
||||
elul: ["elul"],
|
||||
tishrei: ["tishri", "tishrei"],
|
||||
cheshvan: ["heshvan", "cheshvan", "marcheshvan"],
|
||||
kislev: ["kislev"],
|
||||
tevet: ["tevet"],
|
||||
shvat: ["shevat", "shvat"],
|
||||
adar: ["adar", "adar i", "adar 1"],
|
||||
"adar-ii": ["adar ii", "adar 2"]
|
||||
};
|
||||
|
||||
const MONTH_NAME_TO_INDEX = {
|
||||
january: 0,
|
||||
february: 1,
|
||||
march: 2,
|
||||
april: 3,
|
||||
may: 4,
|
||||
june: 5,
|
||||
july: 6,
|
||||
august: 7,
|
||||
september: 8,
|
||||
october: 9,
|
||||
november: 10,
|
||||
december: 11
|
||||
};
|
||||
|
||||
const GREGORIAN_MONTH_ID_TO_ORDER = {
|
||||
january: 1,
|
||||
february: 2,
|
||||
march: 3,
|
||||
april: 4,
|
||||
may: 5,
|
||||
june: 6,
|
||||
july: 7,
|
||||
august: 8,
|
||||
september: 9,
|
||||
october: 10,
|
||||
november: 11,
|
||||
december: 12
|
||||
};
|
||||
|
||||
function getElements() {
|
||||
return {
|
||||
sourceSelectEl: document.getElementById("holiday-source-select"),
|
||||
@@ -180,583 +156,27 @@
|
||||
return getTarotCardDisplayName(cardName) || cardName;
|
||||
}
|
||||
|
||||
function normalizeCalendarText(value) {
|
||||
return String(value || "")
|
||||
.normalize("NFKD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.replace(/['`]/g, "")
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function readNumericPart(parts, partType) {
|
||||
const raw = parts.find((part) => part.type === partType)?.value;
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const digits = String(raw).replace(/[^0-9]/g, "");
|
||||
if (!digits) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsed = Number(digits);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
function getGregorianMonthOrderFromId(monthId) {
|
||||
if (!monthId) {
|
||||
return null;
|
||||
}
|
||||
const key = String(monthId).trim().toLowerCase();
|
||||
const value = GREGORIAN_MONTH_ID_TO_ORDER[key];
|
||||
return Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function parseMonthDayStartToken(token) {
|
||||
const match = String(token || "").match(/(\d{2})-(\d{2})/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const month = Number(match[1]);
|
||||
const day = Number(match[2]);
|
||||
if (!Number.isFinite(month) || !Number.isFinite(day)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { month, day };
|
||||
}
|
||||
|
||||
function createDateAtNoon(year, monthIndex, dayOfMonth) {
|
||||
return new Date(Math.trunc(year), monthIndex, Math.trunc(dayOfMonth), 12, 0, 0, 0);
|
||||
}
|
||||
|
||||
function computeWesternEasterDate(year) {
|
||||
const y = Math.trunc(Number(year));
|
||||
if (!Number.isFinite(y)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Meeus/Jones/Butcher Gregorian algorithm.
|
||||
const a = y % 19;
|
||||
const b = Math.floor(y / 100);
|
||||
const c = y % 100;
|
||||
const d = Math.floor(b / 4);
|
||||
const e = b % 4;
|
||||
const f = Math.floor((b + 8) / 25);
|
||||
const g = Math.floor((b - f + 1) / 3);
|
||||
const h = (19 * a + b - d - g + 15) % 30;
|
||||
const i = Math.floor(c / 4);
|
||||
const k = c % 4;
|
||||
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
||||
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
||||
const month = Math.floor((h + l - 7 * m + 114) / 31);
|
||||
const day = ((h + l - 7 * m + 114) % 31) + 1;
|
||||
return createDateAtNoon(y, month - 1, day);
|
||||
}
|
||||
|
||||
function computeNthWeekdayOfMonth(year, monthIndex, weekday, ordinal) {
|
||||
const y = Math.trunc(Number(year));
|
||||
if (!Number.isFinite(y)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const first = createDateAtNoon(y, monthIndex, 1);
|
||||
const firstWeekday = first.getDay();
|
||||
const offset = (weekday - firstWeekday + 7) % 7;
|
||||
const dayOfMonth = 1 + offset + (Math.trunc(ordinal) - 1) * 7;
|
||||
const daysInMonth = new Date(y, monthIndex + 1, 0).getDate();
|
||||
if (dayOfMonth > daysInMonth) {
|
||||
return null;
|
||||
}
|
||||
return createDateAtNoon(y, monthIndex, dayOfMonth);
|
||||
}
|
||||
|
||||
function resolveGregorianDateRule(rule) {
|
||||
const key = String(rule || "").trim().toLowerCase();
|
||||
if (!key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key === "gregorian-easter-sunday") {
|
||||
return computeWesternEasterDate(state.selectedYear);
|
||||
}
|
||||
|
||||
if (key === "gregorian-good-friday") {
|
||||
const easter = computeWesternEasterDate(state.selectedYear);
|
||||
if (!(easter instanceof Date) || Number.isNaN(easter.getTime())) {
|
||||
return null;
|
||||
}
|
||||
return createDateAtNoon(easter.getFullYear(), easter.getMonth(), easter.getDate() - 2);
|
||||
}
|
||||
|
||||
if (key === "gregorian-thanksgiving-us") {
|
||||
// US Thanksgiving: 4th Thursday of November.
|
||||
return computeNthWeekdayOfMonth(state.selectedYear, 10, 4, 4);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseFirstMonthDayFromText(dateText) {
|
||||
const text = String(dateText || "").replace(/~/g, " ");
|
||||
const firstSegment = text.split("/")[0] || text;
|
||||
const match = firstSegment.match(/(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2})/i);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const monthIndex = MONTH_NAME_TO_INDEX[String(match[1]).toLowerCase()];
|
||||
const day = Number(match[2]);
|
||||
if (!Number.isFinite(monthIndex) || !Number.isFinite(day)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { monthIndex, day };
|
||||
}
|
||||
|
||||
function findHebrewMonthDayInGregorianYear(monthId, day, year) {
|
||||
const aliases = HEBREW_MONTH_ALIAS_BY_ID[String(monthId || "").toLowerCase()] || [];
|
||||
const targetDay = Number(day);
|
||||
if (!aliases.length || !Number.isFinite(targetDay) || !Number.isFinite(year)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedAliases = aliases.map((alias) => normalizeCalendarText(alias)).filter(Boolean);
|
||||
const formatter = new Intl.DateTimeFormat("en-u-ca-hebrew", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric"
|
||||
});
|
||||
|
||||
const cursor = new Date(Math.trunc(year), 0, 1, 12, 0, 0, 0);
|
||||
const end = new Date(Math.trunc(year), 11, 31, 12, 0, 0, 0);
|
||||
|
||||
while (cursor.getTime() <= end.getTime()) {
|
||||
const parts = formatter.formatToParts(cursor);
|
||||
const currentDay = readNumericPart(parts, "day");
|
||||
const monthName = normalizeCalendarText(parts.find((part) => part.type === "month")?.value);
|
||||
if (currentDay === Math.trunc(targetDay) && normalizedAliases.includes(monthName)) {
|
||||
return new Date(cursor);
|
||||
}
|
||||
cursor.setDate(cursor.getDate() + 1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getIslamicMonthOrderById(monthId) {
|
||||
const month = (state.calendarData?.islamic || []).find((item) => item?.id === monthId);
|
||||
const order = Number(month?.order);
|
||||
return Number.isFinite(order) ? Math.trunc(order) : null;
|
||||
}
|
||||
|
||||
function findIslamicMonthDayInGregorianYear(monthId, day, year) {
|
||||
const monthOrder = getIslamicMonthOrderById(monthId);
|
||||
const targetDay = Number(day);
|
||||
if (!Number.isFinite(monthOrder) || !Number.isFinite(targetDay) || !Number.isFinite(year)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const formatter = new Intl.DateTimeFormat("en-u-ca-islamic", {
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
year: "numeric"
|
||||
});
|
||||
|
||||
const cursor = new Date(Math.trunc(year), 0, 1, 12, 0, 0, 0);
|
||||
const end = new Date(Math.trunc(year), 11, 31, 12, 0, 0, 0);
|
||||
|
||||
while (cursor.getTime() <= end.getTime()) {
|
||||
const parts = formatter.formatToParts(cursor);
|
||||
const currentDay = readNumericPart(parts, "day");
|
||||
const currentMonth = readNumericPart(parts, "month");
|
||||
if (currentDay === Math.trunc(targetDay) && currentMonth === monthOrder) {
|
||||
return new Date(cursor);
|
||||
}
|
||||
cursor.setDate(cursor.getDate() + 1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveHolidayGregorianDate(holiday) {
|
||||
if (!holiday || typeof holiday !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const calendarId = String(holiday.calendarId || "").trim().toLowerCase();
|
||||
const monthId = String(holiday.monthId || "").trim().toLowerCase();
|
||||
const day = Number(holiday.day);
|
||||
|
||||
if (calendarId === "gregorian") {
|
||||
if (holiday?.dateRule) {
|
||||
const ruledDate = resolveGregorianDateRule(holiday.dateRule);
|
||||
if (ruledDate) {
|
||||
return ruledDate;
|
||||
}
|
||||
}
|
||||
|
||||
const monthDay = parseMonthDayStartToken(holiday.monthDayStart) || parseMonthDayStartToken(holiday.dateText);
|
||||
if (monthDay) {
|
||||
return new Date(state.selectedYear, monthDay.month - 1, monthDay.day, 12, 0, 0, 0);
|
||||
}
|
||||
const order = getGregorianMonthOrderFromId(monthId);
|
||||
if (Number.isFinite(order) && Number.isFinite(day)) {
|
||||
return new Date(state.selectedYear, order - 1, Math.trunc(day), 12, 0, 0, 0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (calendarId === "hebrew") {
|
||||
return findHebrewMonthDayInGregorianYear(monthId, day, state.selectedYear);
|
||||
}
|
||||
|
||||
if (calendarId === "islamic") {
|
||||
return findIslamicMonthDayInGregorianYear(monthId, day, state.selectedYear);
|
||||
}
|
||||
|
||||
if (calendarId === "wheel-of-year") {
|
||||
const monthDay = parseMonthDayStartToken(holiday.monthDayStart) || parseFirstMonthDayFromText(holiday.dateText);
|
||||
if (monthDay?.month && monthDay?.day) {
|
||||
return new Date(state.selectedYear, monthDay.month - 1, monthDay.day, 12, 0, 0, 0);
|
||||
}
|
||||
if (monthDay?.monthIndex != null && monthDay?.day) {
|
||||
return new Date(state.selectedYear, monthDay.monthIndex, monthDay.day, 12, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatGregorianReferenceDate(date) {
|
||||
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return date.toLocaleDateString(undefined, {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric"
|
||||
});
|
||||
}
|
||||
|
||||
function formatCalendarDateFromGregorian(date, calendarId) {
|
||||
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
const locale = calendarId === "hebrew"
|
||||
? "en-u-ca-hebrew"
|
||||
: (calendarId === "islamic" ? "en-u-ca-islamic" : "en");
|
||||
|
||||
return new Intl.DateTimeFormat(locale, {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric"
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
function buildPlanetMap(planetsObj) {
|
||||
const map = new Map();
|
||||
if (!planetsObj || typeof planetsObj !== "object") {
|
||||
return map;
|
||||
}
|
||||
|
||||
Object.values(planetsObj).forEach((planet) => {
|
||||
if (!planet?.id) {
|
||||
return;
|
||||
}
|
||||
map.set(planet.id, planet);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function buildSignsMap(signs) {
|
||||
const map = new Map();
|
||||
if (!Array.isArray(signs)) {
|
||||
return map;
|
||||
}
|
||||
|
||||
signs.forEach((sign) => {
|
||||
if (!sign?.id) {
|
||||
return;
|
||||
}
|
||||
map.set(sign.id, sign);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function buildGodsMap(magickDataset) {
|
||||
const gods = magickDataset?.grouped?.gods?.gods;
|
||||
const map = new Map();
|
||||
|
||||
if (!Array.isArray(gods)) {
|
||||
return map;
|
||||
}
|
||||
|
||||
gods.forEach((god) => {
|
||||
if (!god?.id) {
|
||||
return;
|
||||
}
|
||||
map.set(god.id, god);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function buildHebrewMap(magickDataset) {
|
||||
const map = new Map();
|
||||
const letters = magickDataset?.grouped?.alphabets?.hebrew;
|
||||
if (!Array.isArray(letters)) {
|
||||
return map;
|
||||
}
|
||||
|
||||
letters.forEach((letter) => {
|
||||
if (!letter?.hebrewLetterId) {
|
||||
return;
|
||||
}
|
||||
map.set(letter.hebrewLetterId, letter);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function calendarLabel(calendarId) {
|
||||
const key = String(calendarId || "").trim().toLowerCase();
|
||||
if (key === "hebrew") return "Hebrew";
|
||||
if (key === "islamic") return "Islamic";
|
||||
if (key === "wheel-of-year") return "Wheel of the Year";
|
||||
return "Gregorian";
|
||||
}
|
||||
|
||||
function monthLabelForCalendar(calendarId, monthId) {
|
||||
const months = state.calendarData?.[calendarId];
|
||||
if (!Array.isArray(months)) {
|
||||
return monthId || "--";
|
||||
}
|
||||
|
||||
const month = months.find((entry) => String(entry?.id || "").toLowerCase() === String(monthId || "").toLowerCase());
|
||||
return month?.name || monthId || "--";
|
||||
}
|
||||
|
||||
function normalizeSourceFilter(value) {
|
||||
const key = String(value || "").trim().toLowerCase();
|
||||
if (key === "gregorian" || key === "hebrew" || key === "islamic" || key === "wheel-of-year") {
|
||||
return key;
|
||||
}
|
||||
return "all";
|
||||
}
|
||||
|
||||
function buildAllHolidays() {
|
||||
if (Array.isArray(state.referenceData?.calendarHolidays) && state.referenceData.calendarHolidays.length) {
|
||||
return [...state.referenceData.calendarHolidays].sort((left, right) => {
|
||||
const calCmp = calendarLabel(left?.calendarId).localeCompare(calendarLabel(right?.calendarId));
|
||||
if (calCmp !== 0) return calCmp;
|
||||
|
||||
const leftDay = Number(left?.day);
|
||||
const rightDay = Number(right?.day);
|
||||
if (Number.isFinite(leftDay) && Number.isFinite(rightDay) && leftDay !== rightDay) {
|
||||
return leftDay - rightDay;
|
||||
}
|
||||
|
||||
return String(left?.name || "").localeCompare(String(right?.name || ""));
|
||||
});
|
||||
}
|
||||
|
||||
const legacy = Array.isArray(state.referenceData?.celestialHolidays) ? state.referenceData.celestialHolidays : [];
|
||||
return legacy.map((holiday) => ({
|
||||
...holiday,
|
||||
calendarId: "gregorian",
|
||||
dateText: holiday?.date || holiday?.dateRange || ""
|
||||
}));
|
||||
}
|
||||
|
||||
function planetLabel(planetId) {
|
||||
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) {
|
||||
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) {
|
||||
if (godName) {
|
||||
return godName;
|
||||
}
|
||||
|
||||
if (!godId) {
|
||||
return "Deity";
|
||||
}
|
||||
|
||||
const god = state.godsById.get(godId);
|
||||
return god?.name || cap(godId);
|
||||
}
|
||||
|
||||
function hebrewLabel(hebrewLetterId) {
|
||||
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) {
|
||||
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)} -></button>`
|
||||
);
|
||||
}
|
||||
|
||||
if (associations.zodiacSignId) {
|
||||
buttons.push(
|
||||
`<button class="alpha-nav-btn" data-nav="zodiac" data-sign-id="${associations.zodiacSignId}">${zodiacLabel(associations.zodiacSignId)} -></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);
|
||||
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)} -></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) {
|
||||
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) {
|
||||
return normalizeSearchValue([
|
||||
holiday?.name,
|
||||
holiday?.kind,
|
||||
holiday?.date,
|
||||
holiday?.dateRange,
|
||||
holiday?.dateText,
|
||||
holiday?.monthDayStart,
|
||||
holiday?.calendarId,
|
||||
holiday?.description,
|
||||
associationSearchText(holiday?.associations)
|
||||
].filter(Boolean).join(" "));
|
||||
function getRenderContext(elements = getElements()) {
|
||||
return {
|
||||
elements,
|
||||
state,
|
||||
cap,
|
||||
normalizeSearchValue,
|
||||
getDisplayTarotName,
|
||||
resolveTarotTrumpNumber,
|
||||
getTarotCardSearchAliases,
|
||||
calendarLabel: holidayDataUi.calendarLabel,
|
||||
monthLabelForCalendar: (calendarId, monthId) => holidayDataUi.monthLabelForCalendar(state.calendarData, calendarId, monthId),
|
||||
normalizeSourceFilter: holidayDataUi.normalizeSourceFilter,
|
||||
filterBySource,
|
||||
resolveHolidayGregorianDate: (holiday) => holidayDataUi.resolveHolidayGregorianDate(holiday, {
|
||||
selectedYear: state.selectedYear,
|
||||
calendarData: state.calendarData
|
||||
}),
|
||||
formatGregorianReferenceDate: holidayDataUi.formatGregorianReferenceDate,
|
||||
formatCalendarDateFromGregorian: holidayDataUi.formatCalendarDateFromGregorian,
|
||||
selectByHolidayId
|
||||
};
|
||||
}
|
||||
|
||||
function getSelectedHoliday() {
|
||||
@@ -764,7 +184,7 @@
|
||||
}
|
||||
|
||||
function filterBySource(holidays) {
|
||||
const source = normalizeSourceFilter(state.selectedSource);
|
||||
const source = holidayDataUi.normalizeSourceFilter(state.selectedSource);
|
||||
if (source === "all") {
|
||||
return [...holidays];
|
||||
}
|
||||
@@ -773,7 +193,7 @@
|
||||
|
||||
function syncControls(elements) {
|
||||
if (elements.sourceSelectEl) {
|
||||
elements.sourceSelectEl.value = normalizeSourceFilter(state.selectedSource);
|
||||
elements.sourceSelectEl.value = holidayDataUi.normalizeSourceFilter(state.selectedSource);
|
||||
}
|
||||
if (elements.yearInputEl) {
|
||||
elements.yearInputEl.value = String(state.selectedYear);
|
||||
@@ -787,99 +207,11 @@
|
||||
}
|
||||
|
||||
function renderList(elements) {
|
||||
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}`;
|
||||
}
|
||||
holidayRenderUi.renderList(getRenderContext(elements));
|
||||
}
|
||||
|
||||
function renderHolidayDetail(holiday) {
|
||||
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)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return holidayRenderUi.renderHolidayDetail(holiday, getRenderContext());
|
||||
}
|
||||
|
||||
function renderDetail(elements) {
|
||||
@@ -897,7 +229,7 @@
|
||||
}
|
||||
|
||||
detailNameEl.textContent = holiday?.name || holiday?.id || "Holiday";
|
||||
detailSubEl.textContent = `${calendarLabel(holiday?.calendarId)} - ${monthLabelForCalendar(holiday?.calendarId, holiday?.monthId)}`;
|
||||
detailSubEl.textContent = `${holidayDataUi.calendarLabel(holiday?.calendarId)} - ${holidayDataUi.monthLabelForCalendar(state.calendarData, holiday?.calendarId, holiday?.monthId)}`;
|
||||
detailBodyEl.innerHTML = renderHolidayDetail(holiday);
|
||||
attachNavHandlers(detailBodyEl);
|
||||
}
|
||||
@@ -905,7 +237,7 @@
|
||||
function applyFilters(elements) {
|
||||
const sourceFiltered = filterBySource(state.holidays);
|
||||
state.filteredHolidays = state.searchQuery
|
||||
? sourceFiltered.filter((holiday) => holidaySearchText(holiday).includes(state.searchQuery))
|
||||
? sourceFiltered.filter((holiday) => holidayRenderUi.holidaySearchText(holiday, getRenderContext()).includes(state.searchQuery))
|
||||
: sourceFiltered;
|
||||
|
||||
if (!state.filteredHolidays.some((holiday) => holiday.id === state.selectedHolidayId)) {
|
||||
@@ -924,12 +256,12 @@
|
||||
}
|
||||
|
||||
const targetCalendar = String(target.calendarId || "").trim().toLowerCase();
|
||||
const activeFilter = normalizeSourceFilter(state.selectedSource);
|
||||
const activeFilter = holidayDataUi.normalizeSourceFilter(state.selectedSource);
|
||||
if (activeFilter !== "all" && activeFilter !== targetCalendar) {
|
||||
state.selectedSource = targetCalendar || "all";
|
||||
}
|
||||
|
||||
if (state.searchQuery && !holidaySearchText(target).includes(state.searchQuery)) {
|
||||
if (state.searchQuery && !holidayRenderUi.holidaySearchText(target, getRenderContext()).includes(state.searchQuery)) {
|
||||
state.searchQuery = "";
|
||||
}
|
||||
|
||||
@@ -941,7 +273,7 @@
|
||||
function bindControls(elements) {
|
||||
if (elements.sourceSelectEl) {
|
||||
elements.sourceSelectEl.addEventListener("change", () => {
|
||||
state.selectedSource = normalizeSourceFilter(elements.sourceSelectEl.value);
|
||||
state.selectedSource = holidayDataUi.normalizeSourceFilter(elements.sourceSelectEl.value);
|
||||
applyFilters(elements);
|
||||
});
|
||||
}
|
||||
@@ -1073,19 +405,13 @@
|
||||
|
||||
state.referenceData = referenceData;
|
||||
state.magickDataset = magickDataset || null;
|
||||
state.planetsById = buildPlanetMap(referenceData.planets);
|
||||
state.signsById = buildSignsMap(referenceData.signs);
|
||||
state.godsById = buildGodsMap(state.magickDataset);
|
||||
state.hebrewById = buildHebrewMap(state.magickDataset);
|
||||
state.planetsById = holidayDataUi.buildPlanetMap(referenceData.planets);
|
||||
state.signsById = holidayDataUi.buildSignsMap(referenceData.signs);
|
||||
state.godsById = holidayDataUi.buildGodsMap(state.magickDataset);
|
||||
state.hebrewById = holidayDataUi.buildHebrewMap(state.magickDataset);
|
||||
|
||||
state.calendarData = {
|
||||
gregorian: Array.isArray(referenceData.calendarMonths) ? referenceData.calendarMonths : [],
|
||||
hebrew: Array.isArray(referenceData.hebrewCalendar?.months) ? referenceData.hebrewCalendar.months : [],
|
||||
islamic: Array.isArray(referenceData.islamicCalendar?.months) ? referenceData.islamicCalendar.months : [],
|
||||
"wheel-of-year": Array.isArray(referenceData.wheelOfYear?.months) ? referenceData.wheelOfYear.months : []
|
||||
};
|
||||
|
||||
state.holidays = buildAllHolidays();
|
||||
state.calendarData = holidayDataUi.buildCalendarData(referenceData);
|
||||
state.holidays = holidayDataUi.buildAllHolidays(state.referenceData);
|
||||
if (!state.selectedHolidayId || !state.holidays.some((holiday) => holiday.id === state.selectedHolidayId)) {
|
||||
state.selectedHolidayId = state.holidays[0]?.id || null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user