(() => { const rootSelector = "[data-story-calendar-root]"; const daySelector = "[data-story-calendar-day]"; const navSelector = "[data-story-calendar-nav]"; const filterSelector = "[data-story-calendar-filter]"; const calendarRoot = () => document.querySelector(rootSelector); const calendarDetail = () => calendarRoot()?.querySelector("[data-story-calendar-detail]"); let detailResizeObserver = null; const eventElement = (event) => event.target instanceof Element ? event.target : null; const isPlainPrimaryClick = (event) => event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey; const clearCalendarNotice = () => { calendarRoot()?.querySelectorAll("[data-story-calendar-notice]").forEach((notice) => notice.remove()); }; const showCalendarNotice = (message, state) => { clearCalendarNotice(); const target = calendarDetail() || calendarRoot(); if (!target) { return; } const notice = document.createElement("p"); notice.className = `story-calendar-detail-notice ${state === "error" ? "is-error" : ""}`.trim(); notice.dataset.storyCalendarNotice = "true"; notice.textContent = message; target.prepend(notice); }; const loadCalendarSurface = async (url, historyMode = "push") => { const root = calendarRoot(); if (!root || !window.fetch) { return false; } root.setAttribute("aria-busy", "true"); showCalendarNotice("Loading...", "loading"); try { const response = await fetch(url, { headers: { "X-Requested-With": "XMLHttpRequest" }, credentials: "same-origin" }); if (!response.ok) { throw new Error("Calendar load failed"); } root.innerHTML = await response.text(); root.removeAttribute("aria-busy"); watchCalendarHeight(); if (window.history?.pushState && historyMode !== "none") { const nextUrl = new URL(url, window.location.href); const state = { storyCalendarUrl: nextUrl.toString() }; if (historyMode === "replace") { window.history.replaceState(state, "", nextUrl.toString()); } else { window.history.pushState(state, "", nextUrl.toString()); } } return true; } catch { root.removeAttribute("aria-busy"); showCalendarNotice("The calendar could not be loaded. Use the link or Apply button to retry.", "error"); return false; } }; const buildFilterUrl = (form) => { const formData = new FormData(form); const url = new URL(form.dataset.storyCalendarIndexUrl || form.action || window.location.href, window.location.href); url.search = ""; for (const [key, value] of formData.entries()) { if (key === "__RequestVerificationToken" || value === "") { continue; } url.searchParams.set(key, value); } return url.toString(); }; const syncDetailHeight = () => { const month = calendarRoot()?.querySelector(".story-calendar-month"); const detail = calendarDetail(); if (!month || !detail) { return; } const monthHeight = Math.ceil(month.getBoundingClientRect().height); if (monthHeight > 0) { detail.style.height = `${monthHeight}px`; detail.style.maxHeight = `${monthHeight}px`; } }; const watchCalendarHeight = () => { detailResizeObserver?.disconnect(); detailResizeObserver = null; const month = calendarRoot()?.querySelector(".story-calendar-month"); if (window.ResizeObserver && month) { detailResizeObserver = new ResizeObserver(syncDetailHeight); detailResizeObserver.observe(month); } window.requestAnimationFrame(syncDetailHeight); }; const initialise = () => { if (!calendarRoot() || !window.fetch) { return; } window.history?.replaceState?.({ storyCalendarUrl: window.location.href }, "", window.location.href); document.addEventListener("click", async (event) => { if (!isPlainPrimaryClick(event)) { return; } const link = eventElement(event)?.closest(`${daySelector}, ${navSelector}`); if (!link || !link.isConnected || !calendarRoot()?.contains(link)) { return; } event.preventDefault(); await loadCalendarSurface(link.href, "push"); }, true); document.addEventListener("submit", async (event) => { const form = eventElement(event)?.closest(filterSelector); if (!form || !form.isConnected) { return; } event.preventDefault(); await loadCalendarSurface(buildFilterUrl(form), "push"); }, true); document.addEventListener("change", async (event) => { const select = eventElement(event)?.closest(`${filterSelector} select`); const form = select?.closest(filterSelector); if (!select || !form) { return; } await loadCalendarSurface(buildFilterUrl(form), "push"); }, true); window.addEventListener("popstate", async () => { await loadCalendarSurface(window.location.href, "none"); }); window.addEventListener("resize", syncDetailHeight); watchCalendarHeight(); }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initialise, { once: true }); } else { initialise(); } })();