/* global window, document, fetch, URLSearchParams */ (function () { 'use strict'; var Luxtools = window.Luxtools || (window.Luxtools = {}); function findCalendarRoot(target) { var el = target; while (el && el !== document) { if (el.classList && el.classList.contains('luxtools-calendar') && el.getAttribute('data-luxtools-calendar') === '1') { return el; } el = el.parentNode; } return null; } function getNextMonth(year, month, direction) { var cursor = new Date(year, month - 1, 1); cursor.setMonth(cursor.getMonth() + direction); return { year: cursor.getFullYear(), month: cursor.getMonth() + 1 }; } function parseCalendarFromHtml(html) { if (!html) return null; var wrapper = document.createElement('div'); wrapper.innerHTML = html; return wrapper.querySelector('div.luxtools-calendar[data-luxtools-calendar="1"]'); } function setCalendarBusy(calendar, busy) { if (!calendar) return; if (busy) { calendar.setAttribute('data-luxtools-loading', '1'); } else { calendar.removeAttribute('data-luxtools-loading'); } var buttons = calendar.querySelectorAll('button.luxtools-calendar-nav-button, button.luxtools-calendar-today-button'); for (var i = 0; i < buttons.length; i++) { buttons[i].disabled = !!busy; } } function getCalendarStateKey(calendar) { var baseNs = calendar.getAttribute('data-base-ns') || 'chronological'; return 'luxtools_calendar_month_' + baseNs.replace(/[^a-zA-Z0-9]/g, '_'); } function shouldPersistCalendarMonth(calendar) { return (calendar.getAttribute('data-luxtools-size') || 'large') === 'small'; } function getCookieValue(name) { var match = document.cookie.match('(^|;)\\s*' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\s*=\\s*([^;]+)'); return match ? decodeURIComponent(match[2]) : null; } function setCookie(name, value) { var date = new Date(); date.setFullYear(date.getFullYear() + 1); document.cookie = name + '=' + encodeURIComponent(value) + '; expires=' + date.toUTCString() + '; path=/; SameSite=Lax'; } function deleteCookie(name) { document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Lax'; } function readSavedCalendarMonth(calendar) { if (!shouldPersistCalendarMonth(calendar)) return null; try { var raw = getCookieValue(getCalendarStateKey(calendar)); if (!raw) return null; var parsed = JSON.parse(raw); var year = parseInt(parsed && parsed.year, 10); var month = parseInt(parsed && parsed.month, 10); if (!year || month < 1 || month > 12) return null; return { year: year, month: month }; } catch (e) { return null; } } function saveCalendarMonth(calendar) { if (!shouldPersistCalendarMonth(calendar)) return; var year = parseInt(calendar.getAttribute('data-current-year') || '', 10); var month = parseInt(calendar.getAttribute('data-current-month') || '', 10); if (!year || month < 1 || month > 12) return; setCookie(getCalendarStateKey(calendar), JSON.stringify({ year: year, month: month })); } function clearSavedCalendarMonth(calendar) { if (!shouldPersistCalendarMonth(calendar)) return; deleteCookie(getCalendarStateKey(calendar)); } function fetchCalendarMonth(calendar, year, month) { var ajaxUrl = calendar.getAttribute('data-luxtools-ajax-url') || ''; if (!ajaxUrl) return Promise.reject(new Error('Missing calendar ajax url')); var baseNs = calendar.getAttribute('data-base-ns') || 'chronological'; var size = calendar.getAttribute('data-luxtools-size') || 'large'; var showTimes = calendar.getAttribute('data-luxtools-show-times') || '1'; var params = new URLSearchParams({ call: 'luxtools_calendar_month', year: String(year), month: String(month), base: baseNs, size: size, show_times: showTimes }); var url = ajaxUrl + (ajaxUrl.indexOf('?') >= 0 ? '&' : '?') + params.toString(); return fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } }).then(function (response) { if (!response.ok) { throw new Error('Calendar request failed: ' + response.status); } return response.text(); }); } function pad2(num) { return num < 10 ? '0' + num : String(num); } function syncCalendarToday(calendar) { if (!calendar) return; var todayCells = calendar.querySelectorAll('td.luxtools-calendar-day-today'); for (var i = 0; i < todayCells.length; i++) { todayCells[i].classList.remove('luxtools-calendar-day-today'); } var now = new Date(); var year = parseInt(calendar.getAttribute('data-current-year') || '', 10); var month = parseInt(calendar.getAttribute('data-current-month') || '', 10); if (!year || !month) return; var currentYear = now.getFullYear(); var currentMonth = now.getMonth() + 1; if (year !== currentYear || month !== currentMonth) return; var dateIso = String(currentYear) + '-' + pad2(currentMonth) + '-' + pad2(now.getDate()); var todayCell = calendar.querySelector('td.luxtools-calendar-day[data-luxtools-date="' + dateIso + '"]'); if (todayCell) { todayCell.classList.add('luxtools-calendar-day-today'); } } function replaceCalendar(calendar, replacement, persistState) { if (persistState !== false) { saveCalendarMonth(replacement); } syncCalendarToday(replacement); calendar.replaceWith(replacement); if (Luxtools.initChronologicalEventTimes) { Luxtools.initChronologicalEventTimes(); } } function loadCalendarMonth(calendar, targetYear, targetMonth, persistState) { setCalendarBusy(calendar, true); fetchCalendarMonth(calendar, targetYear, targetMonth) .then(function (html) { var replacement = parseCalendarFromHtml(html); if (!replacement) { throw new Error('Calendar markup missing in response'); } replaceCalendar(calendar, replacement, persistState); }) .catch(function () { window.location.reload(); }) .finally(function () { setCalendarBusy(calendar, false); }); } function navigateCalendarMonth(calendar, direction, persistState) { var year = parseInt(calendar.getAttribute('data-current-year') || '', 10); var month = parseInt(calendar.getAttribute('data-current-month') || '', 10); if (!year || !month) return; var next = getNextMonth(year, month, direction); loadCalendarMonth(calendar, next.year, next.month, persistState); } function navigateCalendarToday(calendar) { clearSavedCalendarMonth(calendar); var now = new Date(); var year = parseInt(calendar.getAttribute('data-current-year') || '', 10); var month = parseInt(calendar.getAttribute('data-current-month') || '', 10); if (!year || !month) return; var targetYear = now.getFullYear(); var targetMonth = now.getMonth() + 1; var direction = ((targetYear - year) * 12) + (targetMonth - month); if (!direction) { syncCalendarToday(calendar); return; } navigateCalendarMonth(calendar, direction, false); } function restoreCalendarMonth(calendar) { if (!shouldPersistCalendarMonth(calendar)) return; var year = parseInt(calendar.getAttribute('data-current-year') || '', 10); var month = parseInt(calendar.getAttribute('data-current-month') || '', 10); if (!year || !month) return; var saved = readSavedCalendarMonth(calendar); if (!saved) { var now = new Date(); var todayYear = now.getFullYear(); var todayMonth = now.getMonth() + 1; if (year !== todayYear || month !== todayMonth) { loadCalendarMonth(calendar, todayYear, todayMonth, false); } return; } if (saved.year === year && saved.month === month) return; loadCalendarMonth(calendar, saved.year, saved.month, true); } function onCalendarClick(event) { var target = event.target; if (!target || !target.classList) return; if (!target.classList.contains('luxtools-calendar-nav-button') && !target.classList.contains('luxtools-calendar-today-button')) return; var calendar = findCalendarRoot(target); if (!calendar) return; event.preventDefault(); if (target.getAttribute('data-luxtools-today') === '1') { navigateCalendarToday(calendar); return; } var direction = parseInt(target.getAttribute('data-luxtools-dir') || '0', 10); if (!direction) return; navigateCalendarMonth(calendar, direction, true); } function updateClientDateCookie() { var now = new Date(); setCookie('luxtools_client_month', JSON.stringify({ year: now.getFullYear(), month: now.getMonth() + 1 })); } function initCalendarWidgets() { updateClientDateCookie(); var calendars = document.querySelectorAll('div.luxtools-calendar[data-luxtools-calendar="1"]'); for (var i = 0; i < calendars.length; i++) { syncCalendarToday(calendars[i]); restoreCalendarMonth(calendars[i]); } document.addEventListener('click', onCalendarClick, false); } Luxtools.CalendarWidget = { init: initCalendarWidgets }; })();