diff --git a/js/calendar-widget.js b/js/calendar-widget.js index 0c5fbd2..a8cfc2f 100644 --- a/js/calendar-widget.js +++ b/js/calendar-widget.js @@ -43,12 +43,62 @@ calendar.removeAttribute('data-luxtools-loading'); } - var buttons = calendar.querySelectorAll('button.luxtools-calendar-nav-button'); + 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; + } + + function readSavedCalendarMonth(calendar) { + if (!window.localStorage) return null; + + try { + var raw = window.localStorage.getItem(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 (!window.localStorage) 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; + + try { + window.localStorage.setItem(getCalendarStateKey(calendar), JSON.stringify({ + year: year, + month: month + })); + } catch (e) { + // ignore storage failures + } + } + + function clearSavedCalendarMonth(calendar) { + if (!window.localStorage) return; + + try { + window.localStorage.removeItem(getCalendarStateKey(calendar)); + } catch (e) { + // ignore storage failures + } + } + function fetchCalendarMonth(calendar, year, month) { var ajaxUrl = calendar.getAttribute('data-luxtools-ajax-url') || ''; if (!ajaxUrl) return Promise.reject(new Error('Missing calendar ajax url')); @@ -105,53 +155,99 @@ } } - function navigateCalendarMonth(calendar, direction) { - var year = parseInt(calendar.getAttribute('data-current-year') || '', 10); - var month = parseInt(calendar.getAttribute('data-current-month') || '', 10); - if (!year || !month) return; + function replaceCalendar(calendar, replacement, persistState) { + if (persistState !== false) { + saveCalendarMonth(replacement); + } + syncCalendarToday(replacement); + calendar.replaceWith(replacement); + } - var next = getNextMonth(year, month, direction); + function loadCalendarMonth(calendar, targetYear, targetMonth, persistState) { setCalendarBusy(calendar, true); - fetchCalendarMonth(calendar, next.year, next.month) + fetchCalendarMonth(calendar, targetYear, targetMonth) .then(function (html) { var replacement = parseCalendarFromHtml(html); if (!replacement) { throw new Error('Calendar markup missing in response'); } - syncCalendarToday(replacement); - calendar.replaceWith(replacement); + replaceCalendar(calendar, replacement, persistState); }) .catch(function () { - var fallbackLink = calendar.querySelector('a.luxtools-calendar-month-link'); - if (fallbackLink && fallbackLink.href) { - window.location.href = fallbackLink.href; - } + 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) { + var saved = readSavedCalendarMonth(calendar); + if (!saved) return; + + var year = parseInt(calendar.getAttribute('data-current-year') || '', 10); + var month = parseInt(calendar.getAttribute('data-current-month') || '', 10); + 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 || !target.classList.contains('luxtools-calendar-nav-button')) return; + 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; - var direction = parseInt(target.getAttribute('data-luxtools-dir') || '0', 10); - if (direction !== -1 && direction !== 1) return; - event.preventDefault(); - navigateCalendarMonth(calendar, direction); + + 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 initCalendarWidgets() { 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); diff --git a/src/ChronologicalCalendarWidget.php b/src/ChronologicalCalendarWidget.php index e86263d..6236cda 100644 --- a/src/ChronologicalCalendarWidget.php +++ b/src/ChronologicalCalendarWidget.php @@ -64,6 +64,7 @@ class ChronologicalCalendarWidget $html .= '
'; $html .= '
'; + $html .= ''; $html .= ''; $html .= '
'; @@ -84,6 +85,7 @@ class ChronologicalCalendarWidget $html .= '
'; $html .= ''; + $html .= ''; $html .= '
'; $html .= '
'; @@ -118,7 +120,10 @@ class ChronologicalCalendarWidget if ($cell % 7 === 6) $html .= ''; } - $html .= ''; + $html .= ''; + $html .= ''; return $html; } diff --git a/style.css b/style.css index fe2b38b..8e15cc4 100644 --- a/style.css +++ b/style.css @@ -534,11 +534,15 @@ div.luxtools-calendar .luxtools-calendar-nav { } div.luxtools-calendar .luxtools-calendar-nav-prev { - text-align: left; + display: flex; + justify-content: flex-start; + gap: 0.25em; } div.luxtools-calendar .luxtools-calendar-nav-next { - text-align: right; + display: flex; + justify-content: flex-end; + gap: 0.25em; } div.luxtools-calendar .luxtools-calendar-nav-prev a, @@ -562,6 +566,11 @@ div.luxtools-calendar .luxtools-calendar-nav-button:focus { outline: none; } +div.luxtools-calendar .luxtools-calendar-footer { + margin-top: 0.35em; + text-align: right; +} + div.luxtools-calendar table.luxtools-calendar-table { width: 100%; border-collapse: collapse;