759 lines
21 KiB
JavaScript
759 lines
21 KiB
JavaScript
/* ui-calendar.js — Month and celestial holiday browser */
|
|
(function () {
|
|
"use strict";
|
|
|
|
const { getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {};
|
|
const calendarDatesUi = window.TarotCalendarDates || {};
|
|
const calendarDetailUi = window.TarotCalendarDetail || {};
|
|
const calendarDataUi = window.CalendarDataUi || {};
|
|
const {
|
|
addDays,
|
|
buildSignDateBounds,
|
|
formatCalendarDateFromGregorian,
|
|
formatDateLabel,
|
|
formatGregorianReferenceDate,
|
|
formatIsoDate,
|
|
getDaysInMonth,
|
|
getGregorianMonthStartDate,
|
|
getGregorianReferenceDateForCalendarMonth,
|
|
getMonthStartWeekday,
|
|
intersectDateRanges,
|
|
isMonthDayInRange,
|
|
isoToDateAtNoon,
|
|
normalizeCalendarText,
|
|
parseDayRangeFromText,
|
|
parseMonthDayStartToken,
|
|
parseMonthDayToken,
|
|
parseMonthDayTokensFromText,
|
|
parseMonthRange,
|
|
resolveCalendarDayToGregorian,
|
|
resolveHolidayGregorianDate
|
|
} = calendarDatesUi;
|
|
|
|
if (
|
|
typeof calendarDataUi.getMonthDayLinkRows !== "function"
|
|
|| typeof calendarDataUi.buildDecanTarotRowsForMonth !== "function"
|
|
|| typeof calendarDataUi.eventSearchText !== "function"
|
|
|| typeof calendarDataUi.holidaySearchText !== "function"
|
|
|| typeof calendarDataUi.buildHolidayList !== "function"
|
|
|| typeof calendarDataUi.buildMonthSearchText !== "function"
|
|
) {
|
|
throw new Error("CalendarDataUi module must load before ui-calendar.js");
|
|
}
|
|
|
|
const state = {
|
|
initialized: false,
|
|
referenceData: null,
|
|
magickDataset: null,
|
|
selectedCalendar: "gregorian",
|
|
calendarData: {},
|
|
months: [],
|
|
filteredMonths: [],
|
|
holidays: [],
|
|
calendarHolidays: [],
|
|
selectedMonthId: null,
|
|
searchQuery: "",
|
|
selectedYear: new Date().getFullYear(),
|
|
selectedDayMonthId: null,
|
|
selectedDayCalendarId: null,
|
|
selectedDayEntries: [],
|
|
planetsById: new Map(),
|
|
signsById: new Map(),
|
|
godsById: new Map(),
|
|
hebrewById: new Map(),
|
|
dayLinksCache: new Map()
|
|
};
|
|
|
|
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
|
"the fool": 0,
|
|
fool: 0,
|
|
"the magus": 1,
|
|
magus: 1,
|
|
magician: 1,
|
|
"the high priestess": 2,
|
|
"high priestess": 2,
|
|
"the empress": 3,
|
|
empress: 3,
|
|
"the emperor": 4,
|
|
emperor: 4,
|
|
"the hierophant": 5,
|
|
hierophant: 5,
|
|
"the lovers": 6,
|
|
lovers: 6,
|
|
"the chariot": 7,
|
|
chariot: 7,
|
|
strength: 8,
|
|
lust: 8,
|
|
"the hermit": 9,
|
|
hermit: 9,
|
|
fortune: 10,
|
|
"wheel of fortune": 10,
|
|
justice: 11,
|
|
"the hanged man": 12,
|
|
"hanged man": 12,
|
|
death: 13,
|
|
temperance: 14,
|
|
art: 14,
|
|
"the devil": 15,
|
|
devil: 15,
|
|
"the tower": 16,
|
|
tower: 16,
|
|
"the star": 17,
|
|
star: 17,
|
|
"the moon": 18,
|
|
moon: 18,
|
|
"the sun": 19,
|
|
sun: 19,
|
|
aeon: 20,
|
|
judgement: 20,
|
|
judgment: 20,
|
|
universe: 21,
|
|
world: 21,
|
|
"the world": 21
|
|
};
|
|
|
|
function normalizeText(value) {
|
|
return String(value || "").trim();
|
|
}
|
|
|
|
function normalizeSearchValue(value) {
|
|
return normalizeText(value).toLowerCase();
|
|
}
|
|
|
|
function cap(value) {
|
|
const text = normalizeText(value);
|
|
return text ? text.charAt(0).toUpperCase() + text.slice(1) : text;
|
|
}
|
|
|
|
function normalizeTarotName(value) {
|
|
return normalizeText(value)
|
|
.toLowerCase()
|
|
.replace(/\s+/g, " ");
|
|
}
|
|
|
|
function resolveTarotTrumpNumber(cardName) {
|
|
const key = normalizeTarotName(cardName);
|
|
if (!key) {
|
|
return null;
|
|
}
|
|
|
|
if (Object.prototype.hasOwnProperty.call(TAROT_TRUMP_NUMBER_BY_NAME, key)) {
|
|
return TAROT_TRUMP_NUMBER_BY_NAME[key];
|
|
}
|
|
|
|
const withoutLeadingThe = key.replace(/^the\s+/, "");
|
|
if (Object.prototype.hasOwnProperty.call(TAROT_TRUMP_NUMBER_BY_NAME, withoutLeadingThe)) {
|
|
return TAROT_TRUMP_NUMBER_BY_NAME[withoutLeadingThe];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getDisplayTarotName(cardName, trumpNumber) {
|
|
if (!cardName) {
|
|
return "";
|
|
}
|
|
|
|
if (typeof getTarotCardDisplayName !== "function") {
|
|
return cardName;
|
|
}
|
|
|
|
if (Number.isFinite(Number(trumpNumber))) {
|
|
return getTarotCardDisplayName(cardName, { trumpNumber: Number(trumpNumber) }) || cardName;
|
|
}
|
|
|
|
return getTarotCardDisplayName(cardName) || cardName;
|
|
}
|
|
|
|
function normalizeMinorTarotCardName(value) {
|
|
return normalizeTarotName(value)
|
|
.split(" ")
|
|
.filter(Boolean)
|
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
.join(" ");
|
|
}
|
|
|
|
function normalizeDayFilterEntry(dayNumber, gregorianIso) {
|
|
const nextDayNumber = Math.trunc(Number(dayNumber));
|
|
const nextIso = normalizeText(gregorianIso);
|
|
if (!Number.isFinite(nextDayNumber) || nextDayNumber <= 0 || !nextIso) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
dayNumber: nextDayNumber,
|
|
gregorianIso: nextIso
|
|
};
|
|
}
|
|
|
|
function sortDayFilterEntries(entries) {
|
|
const deduped = new Map();
|
|
(Array.isArray(entries) ? entries : []).forEach((entry) => {
|
|
const normalized = normalizeDayFilterEntry(entry?.dayNumber, entry?.gregorianIso);
|
|
if (normalized) {
|
|
deduped.set(normalized.dayNumber, normalized);
|
|
}
|
|
});
|
|
|
|
return [...deduped.values()].sort((left, right) => left.dayNumber - right.dayNumber);
|
|
}
|
|
|
|
function getElements() {
|
|
return {
|
|
monthListEl: document.getElementById("calendar-month-list"),
|
|
monthCountEl: document.getElementById("calendar-month-count"),
|
|
listTitleEl: document.getElementById("calendar-list-title"),
|
|
calendarTypeEl: document.getElementById("calendar-type-select"),
|
|
calendarYearWrapEl: document.getElementById("calendar-year-wrap"),
|
|
yearInputEl: document.getElementById("calendar-year-input"),
|
|
searchInputEl: document.getElementById("calendar-search-input"),
|
|
searchClearEl: document.getElementById("calendar-search-clear"),
|
|
detailNameEl: document.getElementById("calendar-detail-name"),
|
|
detailSubEl: document.getElementById("calendar-detail-sub"),
|
|
detailBodyEl: document.getElementById("calendar-detail-body")
|
|
};
|
|
}
|
|
|
|
function ensureDayFilterContext(month) {
|
|
if (!month) {
|
|
return;
|
|
}
|
|
|
|
const sameContext = state.selectedDayMonthId === month.id
|
|
&& state.selectedDayCalendarId === state.selectedCalendar;
|
|
|
|
if (!sameContext) {
|
|
state.selectedDayMonthId = month.id;
|
|
state.selectedDayCalendarId = state.selectedCalendar;
|
|
state.selectedDayEntries = [];
|
|
}
|
|
}
|
|
|
|
function clearSelectedDayFilter() {
|
|
state.selectedDayMonthId = null;
|
|
state.selectedDayCalendarId = null;
|
|
state.selectedDayEntries = [];
|
|
}
|
|
|
|
function toggleDayFilterEntry(month, dayNumber, gregorianIso) {
|
|
ensureDayFilterContext(month);
|
|
|
|
const next = normalizeDayFilterEntry(dayNumber, gregorianIso);
|
|
if (!next) {
|
|
return;
|
|
}
|
|
|
|
const entries = [...state.selectedDayEntries];
|
|
const existingIndex = entries.findIndex((entry) => entry.dayNumber === next.dayNumber);
|
|
if (existingIndex >= 0) {
|
|
entries.splice(existingIndex, 1);
|
|
} else {
|
|
entries.push(next);
|
|
}
|
|
|
|
state.selectedDayEntries = sortDayFilterEntries(entries);
|
|
}
|
|
|
|
function toggleDayRangeFilter(month, startDay, endDay) {
|
|
ensureDayFilterContext(month);
|
|
|
|
const start = Math.trunc(Number(startDay));
|
|
const end = Math.trunc(Number(endDay));
|
|
if (!Number.isFinite(start) || !Number.isFinite(end) || start <= 0 || end <= 0) {
|
|
return;
|
|
}
|
|
|
|
const minDay = Math.min(start, end);
|
|
const maxDay = Math.max(start, end);
|
|
const rows = getMonthDayLinkRows(month)
|
|
.filter((row) => row.isResolved && row.day >= minDay && row.day <= maxDay)
|
|
.map((row) => normalizeDayFilterEntry(row.day, row.gregorianDate))
|
|
.filter(Boolean);
|
|
|
|
if (!rows.length) {
|
|
return;
|
|
}
|
|
|
|
const existingSet = new Set(state.selectedDayEntries.map((entry) => entry.dayNumber));
|
|
const allSelected = rows.every((row) => existingSet.has(row.dayNumber));
|
|
|
|
if (allSelected) {
|
|
const removeSet = new Set(rows.map((row) => row.dayNumber));
|
|
state.selectedDayEntries = state.selectedDayEntries.filter((entry) => !removeSet.has(entry.dayNumber));
|
|
return;
|
|
}
|
|
|
|
rows.forEach((row) => {
|
|
if (!existingSet.has(row.dayNumber)) {
|
|
state.selectedDayEntries.push(row);
|
|
}
|
|
});
|
|
|
|
state.selectedDayEntries = sortDayFilterEntries(state.selectedDayEntries);
|
|
}
|
|
|
|
function getSelectedDayFilterContext(month) {
|
|
if (!month) {
|
|
return null;
|
|
}
|
|
if (state.selectedDayMonthId !== month.id) {
|
|
return null;
|
|
}
|
|
if (state.selectedDayCalendarId !== state.selectedCalendar) {
|
|
return null;
|
|
}
|
|
if (!Array.isArray(state.selectedDayEntries) || !state.selectedDayEntries.length) {
|
|
return null;
|
|
}
|
|
|
|
const entries = state.selectedDayEntries
|
|
.map((entry) => {
|
|
const normalized = normalizeDayFilterEntry(entry.dayNumber, entry.gregorianIso);
|
|
if (!normalized) {
|
|
return null;
|
|
}
|
|
return {
|
|
...normalized,
|
|
gregorianDate: isoToDateAtNoon(normalized.gregorianIso)
|
|
};
|
|
})
|
|
.filter(Boolean);
|
|
|
|
if (!entries.length) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
entries,
|
|
dayNumbers: new Set(entries.map((entry) => entry.dayNumber))
|
|
};
|
|
}
|
|
|
|
function buildPlanetMap(planetsObj) {
|
|
const map = new Map();
|
|
if (!planetsObj || typeof planetsObj !== "object") {
|
|
return map;
|
|
}
|
|
|
|
Object.values(planetsObj).forEach((planet) => {
|
|
if (planet?.id) {
|
|
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) {
|
|
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) {
|
|
map.set(god.id, god);
|
|
}
|
|
});
|
|
|
|
return map;
|
|
}
|
|
|
|
function findGodIdByName(name) {
|
|
if (!name) {
|
|
return null;
|
|
}
|
|
|
|
const normalized = normalizeText(name).toLowerCase().replace(/^the\s+/, "");
|
|
for (const [id, god] of state.godsById) {
|
|
const godName = normalizeText(god?.name).toLowerCase().replace(/^the\s+/, "");
|
|
if (godName === normalized || id.toLowerCase() === normalized) {
|
|
return id;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
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) {
|
|
map.set(letter.hebrewLetterId, letter);
|
|
}
|
|
});
|
|
|
|
return map;
|
|
}
|
|
|
|
function sortMonths(months) {
|
|
return [...months].sort((left, right) => Number(left?.order || 0) - Number(right?.order || 0));
|
|
}
|
|
|
|
function getSelectedMonth() {
|
|
return state.months.find((month) => month.id === state.selectedMonthId) || null;
|
|
}
|
|
|
|
function getMonthSubtitle(month) {
|
|
if (state.selectedCalendar === "hebrew" || state.selectedCalendar === "islamic") {
|
|
const native = month.nativeName ? ` · ${month.nativeName}` : "";
|
|
const days = month.days ? ` · ${month.days} days` : "";
|
|
return `${month.season || ""}${native}${days}`;
|
|
}
|
|
if (state.selectedCalendar === "wheel-of-year") {
|
|
return [month.date, month.type, month.season].filter(Boolean).join(" · ");
|
|
}
|
|
return parseMonthRange(month);
|
|
}
|
|
|
|
function renderList(elements) {
|
|
const { monthListEl, monthCountEl, listTitleEl } = elements;
|
|
if (!monthListEl) {
|
|
return;
|
|
}
|
|
|
|
monthListEl.innerHTML = "";
|
|
|
|
state.filteredMonths.forEach((month) => {
|
|
const isSelected = month.id === state.selectedMonthId;
|
|
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.monthId = month.id;
|
|
itemEl.innerHTML = `
|
|
<div class="planet-list-name">${month.name || month.id}</div>
|
|
<div class="planet-list-meta">${getMonthSubtitle(month)}</div>
|
|
`;
|
|
itemEl.addEventListener("click", () => {
|
|
selectByMonthId(month.id, elements);
|
|
});
|
|
monthListEl.appendChild(itemEl);
|
|
});
|
|
|
|
if (monthCountEl) {
|
|
monthCountEl.textContent = state.searchQuery
|
|
? `${state.filteredMonths.length} of ${state.months.length} months`
|
|
: `${state.months.length} months`;
|
|
}
|
|
|
|
if (listTitleEl) {
|
|
listTitleEl.textContent = "Calendar > Months";
|
|
}
|
|
}
|
|
|
|
|
|
function getCalendarDataContext() {
|
|
return {
|
|
state,
|
|
normalizeText,
|
|
normalizeSearchValue,
|
|
normalizeMinorTarotCardName,
|
|
getTarotCardSearchAliases,
|
|
addDays,
|
|
buildSignDateBounds,
|
|
formatDateLabel,
|
|
formatIsoDate,
|
|
getDaysInMonth,
|
|
resolveCalendarDayToGregorian,
|
|
resolveHolidayGregorianDate
|
|
};
|
|
}
|
|
|
|
function buildDecanTarotRowsForMonth(month) {
|
|
return calendarDataUi.buildDecanTarotRowsForMonth(getCalendarDataContext(), month);
|
|
}
|
|
|
|
function getMonthDayLinkRows(month) {
|
|
return calendarDataUi.getMonthDayLinkRows(getCalendarDataContext(), month);
|
|
}
|
|
|
|
function eventSearchText(event) {
|
|
return calendarDataUi.eventSearchText(getCalendarDataContext(), event);
|
|
}
|
|
|
|
function holidaySearchText(holiday) {
|
|
return calendarDataUi.holidaySearchText(getCalendarDataContext(), holiday);
|
|
}
|
|
|
|
function buildHolidayList(month) {
|
|
return calendarDataUi.buildHolidayList(getCalendarDataContext(), month);
|
|
}
|
|
|
|
function buildMonthSearchText(month) {
|
|
return calendarDataUi.buildMonthSearchText(getCalendarDataContext(), month);
|
|
}
|
|
|
|
function matchesSearch(searchText) {
|
|
if (!state.searchQuery) {
|
|
return true;
|
|
}
|
|
return searchText.includes(state.searchQuery);
|
|
}
|
|
|
|
function syncSearchControls(elements) {
|
|
if (elements.searchInputEl) {
|
|
elements.searchInputEl.value = state.searchQuery;
|
|
}
|
|
if (elements.searchClearEl) {
|
|
elements.searchClearEl.disabled = !state.searchQuery;
|
|
}
|
|
}
|
|
|
|
function renderDetail(elements) {
|
|
calendarDetailUi.renderDetail?.(elements);
|
|
}
|
|
|
|
function applySearchFilter(elements) {
|
|
state.filteredMonths = state.searchQuery
|
|
? state.months.filter((month) => matchesSearch(buildMonthSearchText(month)))
|
|
: [...state.months];
|
|
|
|
if (!state.filteredMonths.some((month) => month.id === state.selectedMonthId)) {
|
|
state.selectedMonthId = state.filteredMonths[0]?.id || null;
|
|
}
|
|
|
|
syncSearchControls(elements);
|
|
renderList(elements);
|
|
renderDetail(elements);
|
|
}
|
|
|
|
function selectByMonthId(monthId, elements = getElements()) {
|
|
const target = state.months.find((month) => month.id === monthId);
|
|
if (!target) {
|
|
return false;
|
|
}
|
|
|
|
if (state.searchQuery && !state.filteredMonths.some((month) => month.id === target.id)) {
|
|
state.searchQuery = "";
|
|
state.filteredMonths = [...state.months];
|
|
}
|
|
|
|
if (state.selectedMonthId !== target.id) {
|
|
clearSelectedDayFilter();
|
|
}
|
|
|
|
state.selectedMonthId = target.id;
|
|
syncSearchControls(elements);
|
|
renderList(elements);
|
|
renderDetail(elements);
|
|
return true;
|
|
}
|
|
|
|
function selectCalendarType(calendarId, elements = getElements()) {
|
|
if (!state.calendarData || !Array.isArray(state.calendarData[calendarId])) {
|
|
return false;
|
|
}
|
|
|
|
if (elements.calendarTypeEl) {
|
|
elements.calendarTypeEl.value = calendarId;
|
|
}
|
|
|
|
loadCalendarType(calendarId, elements);
|
|
return true;
|
|
}
|
|
|
|
function bindYearInput(elements) {
|
|
if (!elements.yearInputEl) {
|
|
return;
|
|
}
|
|
|
|
elements.yearInputEl.value = String(state.selectedYear);
|
|
elements.yearInputEl.addEventListener("change", () => {
|
|
const nextYear = Number(elements.yearInputEl.value);
|
|
if (!Number.isFinite(nextYear)) {
|
|
elements.yearInputEl.value = String(state.selectedYear);
|
|
return;
|
|
}
|
|
|
|
state.selectedYear = Math.min(2500, Math.max(1900, Math.round(nextYear)));
|
|
elements.yearInputEl.value = String(state.selectedYear);
|
|
state.dayLinksCache = new Map();
|
|
clearSelectedDayFilter();
|
|
renderDetail(elements);
|
|
});
|
|
}
|
|
|
|
function bindSearchInput(elements) {
|
|
if (elements.searchInputEl) {
|
|
elements.searchInputEl.addEventListener("input", () => {
|
|
state.searchQuery = normalizeSearchValue(elements.searchInputEl.value);
|
|
applySearchFilter(elements);
|
|
});
|
|
}
|
|
|
|
if (elements.searchClearEl && elements.searchInputEl) {
|
|
elements.searchClearEl.addEventListener("click", () => {
|
|
state.searchQuery = "";
|
|
elements.searchInputEl.value = "";
|
|
applySearchFilter(elements);
|
|
elements.searchInputEl.focus();
|
|
});
|
|
}
|
|
}
|
|
|
|
function loadCalendarType(calendarId, elements) {
|
|
const months = state.calendarData[calendarId];
|
|
if (!Array.isArray(months)) {
|
|
return;
|
|
}
|
|
|
|
state.selectedCalendar = calendarId;
|
|
state.dayLinksCache = new Map();
|
|
clearSelectedDayFilter();
|
|
state.months = sortMonths(months);
|
|
state.filteredMonths = [...state.months];
|
|
state.selectedMonthId = state.months[0]?.id || null;
|
|
state.searchQuery = "";
|
|
|
|
if (elements.calendarYearWrapEl) {
|
|
elements.calendarYearWrapEl.hidden = false;
|
|
}
|
|
|
|
syncSearchControls(elements);
|
|
applySearchFilter(elements);
|
|
}
|
|
|
|
function bindCalendarTypeSelect(elements) {
|
|
if (!elements.calendarTypeEl) {
|
|
return;
|
|
}
|
|
|
|
elements.calendarTypeEl.value = state.selectedCalendar;
|
|
elements.calendarTypeEl.addEventListener("change", () => {
|
|
loadCalendarType(String(elements.calendarTypeEl.value || "gregorian"), elements);
|
|
});
|
|
}
|
|
|
|
function ensureCalendarSection(referenceData, magickDataset) {
|
|
if (!referenceData) {
|
|
return;
|
|
}
|
|
|
|
calendarDatesUi.init?.({
|
|
getSelectedYear: () => state.selectedYear,
|
|
getSelectedCalendar: () => state.selectedCalendar,
|
|
getIslamicMonths: () => state.calendarData?.islamic || []
|
|
});
|
|
|
|
calendarDetailUi.init?.({
|
|
getState: () => state,
|
|
getElements,
|
|
getSelectedMonth,
|
|
getSelectedDayFilterContext,
|
|
clearSelectedDayFilter,
|
|
toggleDayFilterEntry,
|
|
toggleDayRangeFilter,
|
|
getMonthSubtitle,
|
|
getMonthDayLinkRows,
|
|
buildDecanTarotRowsForMonth,
|
|
buildHolidayList,
|
|
matchesSearch,
|
|
eventSearchText,
|
|
holidaySearchText,
|
|
getDisplayTarotName,
|
|
cap,
|
|
formatGregorianReferenceDate,
|
|
getDaysInMonth,
|
|
getMonthStartWeekday,
|
|
getGregorianMonthStartDate,
|
|
formatCalendarDateFromGregorian,
|
|
parseMonthDayToken,
|
|
parseMonthDayTokensFromText,
|
|
parseMonthDayStartToken,
|
|
parseDayRangeFromText,
|
|
parseMonthRange,
|
|
formatIsoDate,
|
|
resolveHolidayGregorianDate,
|
|
isMonthDayInRange,
|
|
intersectDateRanges,
|
|
getGregorianReferenceDateForCalendarMonth,
|
|
normalizeCalendarText,
|
|
findGodIdByName
|
|
});
|
|
|
|
state.referenceData = referenceData;
|
|
state.magickDataset = magickDataset || null;
|
|
state.dayLinksCache = new Map();
|
|
clearSelectedDayFilter();
|
|
state.holidays = Array.isArray(referenceData.celestialHolidays) ? referenceData.celestialHolidays : [];
|
|
state.calendarHolidays = Array.isArray(referenceData.calendarHolidays) ? referenceData.calendarHolidays : [];
|
|
state.planetsById = buildPlanetMap(referenceData.planets);
|
|
state.signsById = buildSignsMap(referenceData.signs);
|
|
state.godsById = buildGodsMap(state.magickDataset);
|
|
state.hebrewById = 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 : []
|
|
};
|
|
|
|
const currentCalMonths = state.calendarData[state.selectedCalendar] || state.calendarData.gregorian || [];
|
|
state.months = sortMonths(currentCalMonths);
|
|
state.filteredMonths = [...state.months];
|
|
|
|
const elements = getElements();
|
|
if (elements.calendarYearWrapEl) {
|
|
elements.calendarYearWrapEl.hidden = false;
|
|
}
|
|
|
|
if (!state.months.length) {
|
|
if (elements.detailNameEl) {
|
|
elements.detailNameEl.textContent = "Calendar";
|
|
}
|
|
if (elements.detailSubEl) {
|
|
elements.detailSubEl.textContent = "No month data available.";
|
|
}
|
|
if (elements.detailBodyEl) {
|
|
elements.detailBodyEl.innerHTML = "";
|
|
}
|
|
if (elements.monthListEl) {
|
|
elements.monthListEl.innerHTML = "";
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!state.selectedMonthId || !state.months.some((month) => month.id === state.selectedMonthId)) {
|
|
state.selectedMonthId = state.months[0].id;
|
|
}
|
|
|
|
if (!state.initialized) {
|
|
state.initialized = true;
|
|
bindYearInput(elements);
|
|
bindSearchInput(elements);
|
|
bindCalendarTypeSelect(elements);
|
|
}
|
|
|
|
applySearchFilter(elements);
|
|
}
|
|
|
|
window.CalendarSectionUi = {
|
|
ensureCalendarSection,
|
|
selectByMonthId,
|
|
selectCalendarType
|
|
};
|
|
})();
|