From 6162ff595fa541ef6cf3521bd3f4de42eba1d760 Mon Sep 17 00:00:00 2001 From: luxick Date: Wed, 11 Mar 2026 13:31:49 +0100 Subject: [PATCH] Calendar Refinement --- README.md | 12 ++- action.php | 23 ++++- js/calendar-widget.js | 14 ++- src/CalendarService.php | 39 ++++++- src/CalendarSlot.php | 23 +++++ src/ChronologicalCalendarWidget.php | 153 ++++++++++++++++++++++++---- style.css | 131 +++++++++++++++++++++--- syntax/calendar.php | 25 ++++- 8 files changed, 371 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 450cd70..ab06dd8 100644 --- a/README.md +++ b/README.md @@ -289,21 +289,27 @@ Render a basic monthly calendar that links each day to canonical chronological p ``` {{calendar>}} {{calendar>2024-10}} +{{calendar>2026-03&size=small}} +{{calendar>2026-03&size=large&show_times=0}} ``` Notes: - `{{calendar>}}` renders the current month. - `{{calendar>YYYY-MM}}` renders a specific month. +- `size=large|small` controls the widget layout and defaults to `large`. +- `show_times=1|0` controls inline event times in `large` mode and defaults to `1`. - Day links target `chronological:YYYY:MM:DD`. - Header month/year links target `chronological:YYYY:MM` and `chronological:YYYY`. - Prev/next month buttons update the widget in-place without a full page reload. - Month switches fetch server-rendered widget HTML via AJAX and replace only the widget node. - Calendar output is marked as non-cacheable to keep missing/existing link styling and current-day highlighting up to date. -- Each day cell can show colored corner triangles for slots that have events on that day. - Indicator placement is configured per slot via the `Display` setting. - Indicator colors are taken from the slot's configured color. +- Small mode keeps the compact day-number-plus-indicator layout. +- Large mode renders inline day events in the month cells and suppresses the corner indicators. +- Only slots whose `Display` setting is not `None` participate in widget visibility. +- Indicator placement in small mode is configured per slot via the `Display` setting. +- Slot colors are reused for both indicators and inline event accents. ### 0.4) Virtual chronological day pages diff --git a/action.php b/action.php index 5c7d462..63782ae 100644 --- a/action.php +++ b/action.php @@ -155,6 +155,8 @@ class action_plugin_luxtools extends ActionPlugin if ($baseNs === '') { $baseNs = 'chronological'; } + $size = ChronologicalCalendarWidget::normalizeSize((string)$INPUT->str('size')); + $showTimes = ChronologicalCalendarWidget::normalizeShowTimes($INPUT->str('show_times')); if (!ChronologicalCalendarWidget::isValidMonth($year, $month)) { http_status(400); @@ -164,12 +166,21 @@ class action_plugin_luxtools extends ActionPlugin $this->sendNoStoreHeaders(); - // Load slot indicators and colors for the calendar widget $slots = CalendarSlot::loadEnabled($this); - $indicators = CalendarService::monthIndicators($slots, $year, $month); + $widgetSlots = CalendarSlot::filterWidgetVisible($slots); + $indicators = []; + $dayEvents = []; + if ($size === 'large') { + $widgetData = CalendarService::monthWidgetData($widgetSlots, $year, $month); + $indicators = $widgetData['indicators']; + $dayEvents = $widgetData['events']; + } else { + $indicators = CalendarService::monthIndicators($widgetSlots, $year, $month); + } + $slotColors = []; $slotDisplays = []; - foreach ($slots as $slot) { + foreach ($widgetSlots as $slot) { $color = $slot->getColor(); if ($color !== '') { $slotColors[$slot->getKey()] = $color; @@ -177,7 +188,11 @@ class action_plugin_luxtools extends ActionPlugin $slotDisplays[$slot->getKey()] = $slot->getDisplay(); } - $html = ChronologicalCalendarWidget::render($year, $month, $baseNs, $indicators, $slotColors, $slotDisplays); + $html = ChronologicalCalendarWidget::render($year, $month, $baseNs, $indicators, $slotColors, $slotDisplays, [ + 'size' => $size, + 'showTimes' => $showTimes, + 'dayEvents' => $dayEvents, + ]); if ($html === '') { http_status(500); echo 'Calendar rendering failed'; diff --git a/js/calendar-widget.js b/js/calendar-widget.js index a8cfc2f..032fc4a 100644 --- a/js/calendar-widget.js +++ b/js/calendar-widget.js @@ -54,7 +54,12 @@ return 'luxtools.calendar.month.' + baseNs; } + function shouldPersistCalendarMonth(calendar) { + return (calendar.getAttribute('data-luxtools-size') || 'large') === 'small'; + } + function readSavedCalendarMonth(calendar) { + if (!shouldPersistCalendarMonth(calendar)) return null; if (!window.localStorage) return null; try { @@ -73,6 +78,7 @@ } function saveCalendarMonth(calendar) { + if (!shouldPersistCalendarMonth(calendar)) return; if (!window.localStorage) return; var year = parseInt(calendar.getAttribute('data-current-year') || '', 10); @@ -90,6 +96,7 @@ } function clearSavedCalendarMonth(calendar) { + if (!shouldPersistCalendarMonth(calendar)) return; if (!window.localStorage) return; try { @@ -104,11 +111,15 @@ 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 + base: baseNs, + size: size, + show_times: showTimes }); var url = ajaxUrl + (ajaxUrl.indexOf('?') >= 0 ? '&' : '?') + params.toString(); @@ -212,6 +223,7 @@ } function restoreCalendarMonth(calendar) { + if (!shouldPersistCalendarMonth(calendar)) return; var saved = readSavedCalendarMonth(calendar); if (!saved) return; diff --git a/src/CalendarService.php b/src/CalendarService.php index 15ddc70..7482105 100644 --- a/src/CalendarService.php +++ b/src/CalendarService.php @@ -199,13 +199,11 @@ class CalendarService for ($day = 1; $day <= $daysInMonth; $day++) { $dateIso = sprintf('%04d-%02d-%02d', $year, $month, $day); $events = self::collectFromCalendar($expanded, $slot->getKey(), $dateIso); + $cacheKey = $slot->getKey() . '|' . $dateIso; + self::$dayCache[$cacheKey] = $events; + if ($events !== []) { $indicators[$dateIso][] = $slot->getKey(); - // Pre-populate the day cache - $cacheKey = $slot->getKey() . '|' . $dateIso; - if (!isset(self::$dayCache[$cacheKey])) { - self::$dayCache[$cacheKey] = $events; - } } } } catch (Throwable $e) { @@ -216,6 +214,37 @@ class CalendarService return $indicators; } + /** + * Prepare month data for the calendar widget in one pass. + * + * Uses monthIndicators() to warm the per-slot day cache, then reuses the + * normalized events already cached for each day. + * + * @param CalendarSlot[] $slots + * @param int $year + * @param int $month + * @return array{indicators: array, events: array} + */ + public static function monthWidgetData(array $slots, int $year, int $month): array + { + $indicators = self::monthIndicators($slots, $year, $month); + $eventsByDate = []; + $daysInMonth = (int)date('t', mktime(0, 0, 0, $month, 1, $year)); + + for ($day = 1; $day <= $daysInMonth; $day++) { + $dateIso = sprintf('%04d-%02d-%02d', $year, $month, $day); + $events = self::eventsForDate($slots, $dateIso); + if ($events !== []) { + $eventsByDate[$dateIso] = $events; + } + } + + return [ + 'indicators' => $indicators, + 'events' => $eventsByDate, + ]; + } + /** * Read and parse an ICS file, caching the parsed VCalendar per file path. * diff --git a/src/CalendarSlot.php b/src/CalendarSlot.php index 20bb2d4..7022729 100644 --- a/src/CalendarSlot.php +++ b/src/CalendarSlot.php @@ -122,6 +122,16 @@ class CalendarSlot return $this->display !== 'none'; } + /** + * Whether this slot should participate in calendar widget visibility. + * + * @return bool + */ + public function isVisibleInWidget(): bool + { + return $this->shouldDisplayIndicator(); + } + /** * A slot is enabled if it has a local file path or a CalDAV URL. * @@ -190,6 +200,19 @@ class CalendarSlot }); } + /** + * Keep only slots that should appear in the calendar widget. + * + * @param CalendarSlot[] $slots + * @return CalendarSlot[] + */ + public static function filterWidgetVisible(array $slots): array + { + return array_filter($slots, static function (CalendarSlot $slot): bool { + return $slot->isVisibleInWidget(); + }); + } + protected static function normalizeIndicatorDisplay(string $display): string { $display = strtolower(trim($display)); diff --git a/src/ChronologicalCalendarWidget.php b/src/ChronologicalCalendarWidget.php index 77e7ef7..0e80f94 100644 --- a/src/ChronologicalCalendarWidget.php +++ b/src/ChronologicalCalendarWidget.php @@ -7,6 +7,9 @@ namespace dokuwiki\plugin\luxtools; */ class ChronologicalCalendarWidget { + /** @var int Maximum number of inline events shown per day cell in large mode */ + protected const MAX_INLINE_EVENTS = 3; + /** * Render full calendar widget HTML for one month. * @@ -16,6 +19,7 @@ class ChronologicalCalendarWidget * @param array $indicators date => [slotKey, ...] from CalendarService::monthIndicators() * @param array $slotColors slotKey => CSS color * @param array $slotDisplays slotKey => configured indicator position + * @param array{size?:string,showTimes?:bool,dayEvents?:array} $options * @return string */ public static function render( @@ -24,10 +28,15 @@ class ChronologicalCalendarWidget string $baseNs = 'chronological', array $indicators = [], array $slotColors = [], - array $slotDisplays = [] + array $slotDisplays = [], + array $options = [] ): string { if (!self::isValidMonth($year, $month)) return ''; + $size = self::normalizeSize((string)($options['size'] ?? 'large')); + $showTimes = (bool)($options['showTimes'] ?? true); + $dayEvents = is_array($options['dayEvents'] ?? null) ? $options['dayEvents'] : []; + $firstDayTs = mktime(0, 0, 0, $month, 1, $year); $daysInMonth = (int)date('t', $firstDayTs); $firstWeekday = (int)date('N', $firstDayTs); // 1..7 (Mon..Sun) @@ -59,10 +68,12 @@ class ChronologicalCalendarWidget $yearUrlTemplate = $dayUrlTemplate; $ajaxUrl = defined('DOKU_BASE') ? (string)DOKU_BASE . 'lib/exe/ajax.php' : 'lib/exe/ajax.php'; - $html = '
'; - // Render slot indicators if any - $dayIndicators = $indicators[$date] ?? []; - if ($dayIndicators !== []) { - $indicatorHtml = ''; - foreach ($dayIndicators as $slotKey) { - $display = $slotDisplays[$slotKey] ?? 'none'; - if ($display === 'none') continue; + if ($size === 'small') { + $dayIndicators = $indicators[$date] ?? []; + if ($dayIndicators !== []) { + $indicatorHtml = ''; + foreach ($dayIndicators as $slotKey) { + $display = $slotDisplays[$slotKey] ?? 'none'; + if ($display === 'none') continue; - $color = $slotColors[$slotKey] ?? ''; - $style = ($color !== '') ? ' style="background-color:' . hsc($color) . '"' : ''; - $indicatorHtml .= ''; + $color = $slotColors[$slotKey] ?? ''; + $style = ($color !== '') ? ' style="background-color:' . hsc($color) . '"' : ''; + $indicatorHtml .= ''; + } + + if ($indicatorHtml !== '') { + $html .= '
' . $indicatorHtml . '
'; + } } - if ($indicatorHtml !== '') { - $html .= '
' . $indicatorHtml . '
'; - } - } - - if ($dayId !== null && function_exists('html_wikilink')) { - $html .= (string)html_wikilink($dayId, (string)$dayNumber); + $html .= self::renderDayLink($dayId, (string)$dayNumber); } else { - $html .= hsc((string)$dayNumber); + $html .= '
'; + $html .= '
' . self::renderDayLink($dayId, (string)$dayNumber) . '
'; + $html .= self::renderInlineEvents($events, $slotColors, $showTimes); + $html .= '
'; } $html .= ''; } @@ -155,6 +172,26 @@ class ChronologicalCalendarWidget return $html; } + /** + * @param string $size + * @return string + */ + public static function normalizeSize(string $size): string + { + $size = strtolower(trim($size)); + return $size === 'small' ? 'small' : 'large'; + } + + /** + * @param string|null $value + * @return bool + */ + public static function normalizeShowTimes(?string $value): bool + { + if ($value === null) return true; + return trim($value) !== '0'; + } + /** * @param int $year * @param int $month @@ -166,4 +203,80 @@ class ChronologicalCalendarWidget if ($month < 1 || $month > 12) return false; return true; } + + /** + * @param string|null $dayId + * @param string $label + * @return string + */ + protected static function renderDayLink(?string $dayId, string $label): string + { + if ($dayId !== null && function_exists('html_wikilink')) { + return (string)html_wikilink($dayId, $label); + } + + return hsc($label); + } + + /** + * @param CalendarEvent[] $events + * @param array $slotColors + * @param bool $showTimes + * @return string + */ + protected static function renderInlineEvents(array $events, array $slotColors, bool $showTimes): string + { + if ($events === []) { + return '
'; + } + + $html = '
    '; + + $visibleCount = min(count($events), self::MAX_INLINE_EVENTS); + for ($index = 0; $index < $visibleCount; $index++) { + $event = $events[$index]; + $color = $slotColors[$event->slotKey] ?? ''; + $style = $color !== '' ? ' style="--luxtools-slot-color:' . hsc($color) . '"' : ''; + $dataAttrs = self::renderEventDataAttributes($event); + + $html .= '
  • '; + if ($showTimes && !$event->allDay && $event->time !== '') { + $html .= '' . hsc($event->time) . ''; + } + $html .= '' . hsc($event->summary) . ''; + $html .= '
  • '; + } + + $remaining = count($events) - $visibleCount; + if ($remaining > 0) { + $html .= '
  • +' . hsc((string)$remaining) . ' more
  • '; + } + + $html .= '
'; + return $html; + } + + /** + * @param CalendarEvent $event + * @return string + */ + protected static function renderEventDataAttributes(CalendarEvent $event): string + { + $attrs = ' data-luxtools-event="1"'; + $attrs .= ' data-event-summary="' . hsc($event->summary) . '"'; + $attrs .= ' data-event-start="' . hsc($event->startIso) . '"'; + if ($event->endIso !== '') { + $attrs .= ' data-event-end="' . hsc($event->endIso) . '"'; + } + if ($event->location !== '') { + $attrs .= ' data-event-location="' . hsc($event->location) . '"'; + } + if ($event->description !== '') { + $attrs .= ' data-event-description="' . hsc($event->description) . '"'; + } + $attrs .= ' data-event-allday="' . ($event->allDay ? '1' : '0') . '"'; + $attrs .= ' data-event-slot="' . hsc($event->slotKey) . '"'; + + return $attrs; + } } diff --git a/style.css b/style.css index 7bff741..d0754ec 100644 --- a/style.css +++ b/style.css @@ -599,7 +599,8 @@ div.luxtools-calendar td.luxtools-calendar-day-today { background-color: @ini_highlight; } -div.luxtools-calendar td.luxtools-calendar-day a { +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > a, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a { display: flex; align-items: center; justify-content: center; @@ -612,27 +613,33 @@ div.luxtools-calendar td.luxtools-calendar-day a { padding: 0.1em 0; } -div.luxtools-calendar td.luxtools-calendar-day a.wikilink2:link, -div.luxtools-calendar td.luxtools-calendar-day a.wikilink2:visited { +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > a.wikilink2:link, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > a.wikilink2:visited, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a.wikilink2:link, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a.wikilink2:visited { color: @ini_missing; border-bottom: 0; } -div.luxtools-calendar td.luxtools-calendar-day a:hover, -div.luxtools-calendar td.luxtools-calendar-day a:focus, -div.luxtools-calendar td.luxtools-calendar-day a:active, -div.luxtools-calendar td.luxtools-calendar-day a:visited { +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > a:hover, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > a:focus, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > a:active, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > a:visited, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a:hover, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a:focus, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a:active, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a:visited { text-decoration: none; border-bottom: 0; box-shadow: none; } -div.luxtools-calendar td.luxtools-calendar-day span.curid > a, -div.luxtools-calendar td.luxtools-calendar-day span.curid > a:visited, -div.luxtools-calendar td.luxtools-calendar-day span.curid > a:hover, -div.luxtools-calendar td.luxtools-calendar-day span.curid > a:focus, -div.luxtools-calendar td.luxtools-calendar-day span.curid > a:active { +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a:visited, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a:hover, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a:focus, +div.luxtools-calendar.luxtools-calendar-size-small td.luxtools-calendar-day > span.curid > a:active { font-weight: bold; text-decoration: underline; border-bottom: 0; @@ -657,6 +664,106 @@ div.luxtools-calendar td.luxtools-calendar-day { position: relative; } +div.luxtools-calendar.luxtools-calendar-size-large table.luxtools-calendar-table td { + text-align: left; + vertical-align: top; +} + +div.luxtools-calendar.luxtools-calendar-size-large td.luxtools-calendar-day { + height: 8.25em; +} + +div.luxtools-calendar.luxtools-calendar-size-large td.luxtools-calendar-day-empty { + height: 8.25em; +} + +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-day-frame { + min-height: 8.25em; + padding: 0.35em 0.4em 0.4em 0.4em; + box-sizing: border-box; +} + +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-day-number { + text-align: right; + margin-bottom: 0.25em; + line-height: 1.1; +} + +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-day-number > a, +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-day-number > span.curid > a, +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-day-number span.curid > a { + display: inline; + min-height: 0; + padding: 0; + background: transparent; + text-decoration: none; + border-bottom: 0; + box-shadow: none; + font-weight: bold; +} + +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-day-number > a.wikilink2:link, +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-day-number > a.wikilink2:visited, +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-day-number span.curid > a.wikilink2:link, +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-day-number span.curid > a.wikilink2:visited { + color: @ini_missing; +} + +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-day-events { + overflow: hidden; +} + +div.luxtools-calendar.luxtools-calendar-size-large ul.luxtools-calendar-event-list { + list-style: none; + margin: 0; + padding: 0; +} + +div.luxtools-calendar.luxtools-calendar-size-large li.luxtools-calendar-event { + display: flex; + align-items: baseline; + gap: 0.35em; + margin: 0 0 0.2em 0; + padding: 0.1em 0.2em 0.1em 0.35em; + border-left: 3px solid var(--luxtools-slot-color, @ini_border); + background-color: @ini_background_alt; + overflow: hidden; + cursor: pointer; +} + +div.luxtools-calendar.luxtools-calendar-size-large li.luxtools-calendar-event:hover { + background-color: @ini_highlight; +} + +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-event-time { + flex: 0 0 auto; + font-weight: bold; + white-space: nowrap; +} + +div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-event-title { + flex: 1 1 auto; + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +div.luxtools-calendar.luxtools-calendar-size-large li.luxtools-calendar-event-more { + border-left-color: @ini_border; + justify-content: flex-end; + font-style: italic; +} + +@media (max-width: 800px) { + div.luxtools-calendar.luxtools-calendar-size-large td.luxtools-calendar-day, + div.luxtools-calendar.luxtools-calendar-size-large td.luxtools-calendar-day-empty, + div.luxtools-calendar.luxtools-calendar-size-large .luxtools-calendar-day-frame { + height: 7em; + min-height: 7em; + } +} + .luxtools-calendar-indicators { position: absolute; top: 0; diff --git a/syntax/calendar.php b/syntax/calendar.php index 0cc2505..5fac241 100644 --- a/syntax/calendar.php +++ b/syntax/calendar.php @@ -64,6 +64,8 @@ class syntax_plugin_luxtools_calendar extends SyntaxPlugin 'year' => $resolved['year'], 'month' => $resolved['month'], 'base' => $baseNs, + 'size' => ChronologicalCalendarWidget::normalizeSize((string)($params['size'] ?? 'large')), + 'show_times' => ChronologicalCalendarWidget::normalizeShowTimes($params['show_times'] ?? null), ]; } @@ -86,13 +88,24 @@ class syntax_plugin_luxtools_calendar extends SyntaxPlugin $year = (int)$data['year']; $month = (int)$data['month']; $baseNs = (string)$data['base']; + $size = ChronologicalCalendarWidget::normalizeSize((string)($data['size'] ?? 'large')); + $showTimes = (bool)($data['show_times'] ?? true); - // Load slot indicators and colors for the calendar widget $slots = CalendarSlot::loadEnabled($this); - $indicators = CalendarService::monthIndicators($slots, $year, $month); + $widgetSlots = CalendarSlot::filterWidgetVisible($slots); + $indicators = []; + $dayEvents = []; + if ($size === 'large') { + $widgetData = CalendarService::monthWidgetData($widgetSlots, $year, $month); + $indicators = $widgetData['indicators']; + $dayEvents = $widgetData['events']; + } else { + $indicators = CalendarService::monthIndicators($widgetSlots, $year, $month); + } + $slotColors = []; $slotDisplays = []; - foreach ($slots as $slot) { + foreach ($widgetSlots as $slot) { $color = $slot->getColor(); if ($color !== '') { $slotColors[$slot->getKey()] = $color; @@ -100,7 +113,11 @@ class syntax_plugin_luxtools_calendar extends SyntaxPlugin $slotDisplays[$slot->getKey()] = $slot->getDisplay(); } - $renderer->doc .= ChronologicalCalendarWidget::render($year, $month, $baseNs, $indicators, $slotColors, $slotDisplays); + $renderer->doc .= ChronologicalCalendarWidget::render($year, $month, $baseNs, $indicators, $slotColors, $slotDisplays, [ + 'size' => $size, + 'showTimes' => $showTimes, + 'dayEvents' => $dayEvents, + ]); return true; }