1document.addEventListener('DOMContentLoaded', function () { 2 jQuery(function ($) { 3 //------------------------------------------------------------------------------------------------ 4 //------------------------------------------------------------------------------------------------ 5 // Find all calendar instances 6 $('.frontend_kalender[data-instance]').each(function() { 7 if (!window.TerminKalender.ter_kal_calendars) { 8 window.TerminKalender.ter_kal_calendars = {}; 9 } 10 const $wrapper = $(this); 11 const instance_id = $wrapper.data('instance'); 12 const show_time = $wrapper.data('show_time') || 'start_end'; 13 // ter_kal_CHANGE: Initialize active filters from block settings 14 if (!window.TerminKalender.active_filters) { 15 window.TerminKalender.active_filters = {}; 16 } 17 const hiddenCategories = $wrapper.data('hidden-categories') || []; 18 window.TerminKalender.active_filters[instance_id] = new Set(hiddenCategories); 19 const hide_buttons = $wrapper.data('hide-buttons') === 1; // ter_kal_CHANGE: Read hide buttons flag 20 21 var ter_kal_lang = terminKalenderData.ter_kal_lang; 22 const INPUT_FORMAT = 'YYYY-MM-DDTHH:mm'; 23 if (terminKalenderData.TERMIN_KALENDER_PRO_ON == 'on' && terminKalenderData.is_user_logged_in == 1 ) { 24 var pro_button_login_link = ''; 25 var pro_button_login = ''; 26 } else if (terminKalenderData.is_user_logged_in == 0 ) { 27 var pro_button_login_link = terminKalenderData.calendar_login_link; 28 var proLoginButton = 'LoginButton'; 29 } 30 var calendarEl = document.getElementById('ter_kal_simple_calendar_' + instance_id); 31 if (!calendarEl) { 32 return; 33 } 34//------------------------------------------------------------------------------------------------ 35 var ter_kal_simple_calendar = new FullCalendar.Calendar(calendarEl, { 36 // Native FullCalendar responsive settings 37 height: 'auto', // Grows/shrinks based on content 38 contentHeight: 'auto', 39 handleWindowResize: true, // Automatically adjusts on window resize (default: true) 40 windowResizeDelay: 100, // Debounce resize (ms) 41 // For compact display: 42 expandRows: false, // Rows only as tall as needed 43 dayMaxEvents: 3, // Show max 3 events, rest as "+2 more" 44 // Responsive view switching: 45 /* windowResize: function(view) { 46 if (window.innerWidth < 500) { 47 ter_kal_simple_calendar.changeView('listWeek'); // Switch to list on mobile 48 } 49 },*/ 50 locale: ter_kal_lang, 51 customButtons: { 52 LoginButton: { 53 text: terminKalenderData.strings.calendar_login , 54 click: function() { window.location.href = pro_button_login_link; } 55 } 56 }, 57 footerToolbar: { 58 left: proLoginButton, 59 center: '', 60 right: '', 61 }, 62 headerToolbar: { 63 left: 'prev,next,today', 64 center: 'title', 65 right: 'multiMonthYear,dayGridMonth,timeGridWeek,listYear', 66 }, 67 editable: false, 68 timeZone: 'UTC', 69 eventDisplay: 'block', 70 firstDay: 1, 71 weekNumbers: true, 72 navLinks: true, 73 dayMaxEventRows: 4, 74 eventMaxStack: 3, 75 listDayFormat: { weekday: 'long', day: 'numeric', month: 'long' }, 76 listDaySideFormat: false, 77 events: terminKalenderData.TERMIN_KALENDER_DATEN , 78 eventDidMount: function(info) { 79 $(info.el).attr('title', info.event.title); 80 }, 81 viewDidMount: function(info) { 82 if (info.view.type === 'multiMonthYear') { 83 ter_kal_simple_calendar.setOption('dayMaxEventRows', 3); 84 } 85 if (info.view.type === 'dayGridMonth') { 86 ter_kal_simple_calendar.setOption('dayMaxEventRows', 4); 87 } 88 }, 89 eventClick: function (info) { 90 openEventDialog(info.event, instance_id, show_time); 91 } 92 }); 93//------------------------------------------------------------------------------------------------ 94 // window.ter_kal_simple_calendar = ter_kal_simple_calendar; 95 ter_kal_simple_calendar.render(); 96 // ter_kal_CHANGE: Store instance globally by instance_id 97 window.TerminKalender.ter_kal_calendars[instance_id] = ter_kal_simple_calendar; 98 // Detect if running in widget area 99/* const isInWidget = $wrapper.closest('.widget-area, .sidebar, aside').length > 0; 100 if (isInWidget) { 101 ter_kal_simple_calendar.setOption('height', 'auto'); 102 ter_kal_simple_calendar.setOption('contentHeight', 400); 103 }*/ 104//------------------------------------------------------------------------------------------------ 105 // Initialize event dialog native listeners 106 const dialogEl = document.getElementById('event-dialog-' + instance_id); 107 if (dialogEl) { 108 // Close button click 109 const closeBtn = dialogEl.querySelector('.ter_kal_dialog_close'); 110 if (closeBtn) { 111 closeBtn.addEventListener('click', () => dialogEl.close()); 112 } 113 // Backdrop click to close 114 dialogEl.addEventListener('click', (e) => { 115 const rect = dialogEl.getBoundingClientRect(); 116 // Fallback close check for backdrop click 117 const isInDialog = (rect.top <= e.clientY && e.clientY <= rect.top + rect.height && 118 rect.left <= e.clientX && e.clientX <= rect.left + rect.width); 119 if (!isInDialog) { 120 dialogEl.close(); 121 } 122 }); 123 // On close event, blur fullcalendar 124 dialogEl.addEventListener('close', () => { 125 $('.fc-view-outer *').blur(); 126 }); 127 } 128 129//------------------------------------------------------------------------------------------------ 130 window.TerminKalender.openEventDialogFrontend = window.TerminKalender.openEventDialogFrontend || {}; 131 window.TerminKalender.openEventDialogFrontend[instance_id] = function(event, id, time) { 132 openEventDialog(event, id, time); 133 }; 134 135 function openEventDialog(event, instance_id, show_time) { 136 const dialog_selector = '#event-dialog-' + instance_id; 137 const dialogEl = document.getElementById('event-dialog-' + instance_id); 138 if (!dialogEl) return; 139 140 const ter_kal_link = window.location.href; 141 142 // Generate buttons 143 const buttonsObj = window.TerminKalender.share_buttons_calendar_ical(event, 'frontent_calendar', ter_kal_link); 144 const footerEl = document.getElementById('event-footer-' + instance_id); 145 window.TerminKalender.render_dialog_footer_buttons(dialogEl, footerEl, event, 'frontent_calendar', ter_kal_link); 146 147 if (dialogEl.open) { 148 dialogEl.close(); 149 } 150 $('#event-form').attr('data-event-id', event ? event.id : ''); 151 152 var categoryData = event ? event.extendedProps.category : null; 153 var details = event ? event.extendedProps.details : ''; 154 var location = event ? event.extendedProps.location : ''; 155 var title = event ? event.title : ''; 156 var dayName = moment.utc(event.start).format('dddd'); 157 var utcStart_kurz = moment.utc(event.start).format('D MMMM YYYY');
158 var utcEnd_kurz = moment.utc(event.end).format('D MMMM YYYY'); 159 var utcStart_kurzStd = moment.utc(event.start).format('HH:mm'); 160 var utcEnd_kurzStd = ' - ' + moment.utc(event.end).format('HH:mm'); 161 var event_allday_string = terminKalenderData.strings.all_day ; 162 var freq = event.extendedProps.freq ? event.extendedProps.freq : '' ; 163 if (freq) { 164 var freq_translated = terminKalenderData.strings[freq] ; 165 var until = event.extendedProps.until ? event.extendedProps.until : ''; 166 var utcEnd_kurz = moment.utc(until).format('D MMMM YYYY'); 167 var utcEnd_kurzStd = ' - ' + moment.utc(until).format('HH:mm'); 168 } else { 169 var freq_translated = ' ' ; 170 } 171 //----------------------------------------------------------------------------- 172 // var isImplicitAllDay = event.end && moment.utc(event.end).diff(moment.utc(event.start), 'hours') === 24; 173 174 var isImplicitAllDay = utcStart_kurzStd === '00:00' && 175 moment.utc(event.end).format('HH:mm') === '00:00' && 176 moment.utc(event.end).diff(moment.utc(event.start), 'days') === 1; 177 178 if (event.allDay || isImplicitAllDay) { 179 utcStart_kurzStd = event_allday_string; 180 utcEnd_kurzStd = ''; 181 } 182 183 // Zeige nur Startdatum wenn Enddatum ungültig, leer, identisch oder implizites Ganztags-Event 184 var hasValidEnd = utcEnd_kurz && 185 utcEnd_kurz !== 'Invalid date' && 186 utcEnd_kurz.trim() !== '' && 187 utcEnd_kurz !== utcStart_kurz && 188 !isImplicitAllDay; 189 190 // Update instance-specific elements 191//----------------------------------------------------------------------------- 192 if (location) { // event_location 193 location ='<button type="button" class="bootstrap_button bootstrap_dark" onclick="window.open(\'https://www.google.com/maps/search/?api=1&query=' + 194 encodeURIComponent(location) + '\')">'+ terminKalenderData.strings.event_location + location + '</button>'; 195 $('#event-location_link-' + instance_id).html( location ); 196 } else { 197 $('#event-location_link-' + instance_id).html(''); 198 } 199//----------------------------------------------------------------------------- 200 var utcTage = hasValidEnd ? utcStart_kurz + ' - ' + utcEnd_kurz : utcStart_kurz; 201 //----------------------------------------------------------------------------- 202 $('#event-time-' + instance_id).html('<b>' + dayName + ',</b> ' + utcTage + 203 '<br>' + freq_translated + ' <b><span class="utc_std">' + utcStart_kurzStd + ' <span class="utc_std_end"> ' + utcEnd_kurzStd + '</span></span></b> ' ); 204//----------------------------------------------------------------------------- 205 $('#event-title-' + instance_id).text( title ); 206 $('#event-details-html-' + instance_id).html( details ); 207//----------------------------------------------------------------------------- 208 // Apply category styling 209 applyDialogStyles(categoryData, 'event', dialog_selector); 210//-------------------------------------------------------------------------- 211 // Handle show_time visibility 212 var $dialog = $(dialog_selector); 213 if (show_time == 'hide') { 214 $dialog.find('.utc_std').hide(); 215 } else if (show_time == 'start') { 216 $dialog.find('.utc_std_end').hide(); 217 } else { 218 $dialog.find('.utc_std').show(); 219 } 220//-------------------------------------------------------------------------- 221 dialogEl.showModal(); 222 } // end open event dialog 223 //--------------------------------------------------------------------------- 224 //--------------------------------------------------------------------------- 225//------------------------------------------------------------------------------------------------ 226//------------------------------------------------------------------------------------------------ 227 function applyDialogStyles(category, dialogType, dialog_selector) { 228 const dialogEl = document.querySelector(dialog_selector); 229 if (!dialogEl) return; 230 231 const header = dialogEl.querySelector('.ter_kal_dialog_header'); 232 const title = dialogEl.querySelector('.ter_kal_dialog_title'); 233 234 if (category) { 235 if (header) { 236 header.style.backgroundColor = category.backgroundColor; 237 header.style.color = category.textColor; 238 } 239 if (dialogType === 'event' && title) { 240 title.innerHTML = '<span class="dialog_title_icon dashicons ' + category.icon + '"></span>' + 241 '<span style="vertical-align:middle;">' + (category.label || terminKalenderData.strings.new_category) + '</span>'; 242 } 243 } else { 244 if (header) { 245 header.style.backgroundColor = ''; 246 header.style.color = ''; 247 } 248 if (dialogType === 'event' && title) { 249 title.innerHTML = '<span>' + terminKalenderData.strings.event_title + '</span>'; 250 } else if (dialogType === 'category' && title) { 251 title.innerHTML = '<span>' + terminKalenderData.strings.new_category + '</span>'; 252 } 253 } 254 } 255 256 loadCategories(instance_id); 257//------------------------------------------------------------------------------------------------ 258//------------------------------------------------------------------------------------------------ 259 // Add category filtering to frontend calendar 260 function loadCategories(instance_id) { 261 var categories = terminKalenderData.TERMIN_KALENDER_KATEGORIEN, 262 $catSelect = $('#event-category_' + instance_id), 263 $catRow = $('#kategoriezeile_' + instance_id); 264 265 $catSelect.empty(); 266 $catRow.empty(); 267 268 // ter_kal_CHANGE: Get initial hidden categories 269 var activeFilters = window.TerminKalender.active_filters[instance_id]; 270 271 272 $.each(categories, function (i, cat) { 273 // Ãberspringe die "To Do" Kategorie komplett 274 if (cat.label === 'To Do') { 275 return true; 276 } 277 $('<option>') 278 .val(cat.label) 279 .text(cat.label) 280 .data('category', cat) 281 .appendTo($catSelect); 282 283 var $btn = $('<div class="category-button">') 284 .css({ backgroundColor: cat.backgroundColor, color: cat.textColor }) 285 .data('category-label', cat.label) // ter_kal_CHANGE: Store label for filtering 286 .addClass(activeFilters.has(cat.label) ? 'ter_kal_filtered' : '') 287 .css('display', (hide_buttons && activeFilters.has(cat.label)) ? 'none' : '') 288 .append( 289 $('<span class="dashicons ' + cat.icon + '"></span>'), 290 $('<span class="tk_kalender_button_text">').text(cat.label) 291 ) 292 // ter_kal_CHANGE: Add click handler to toggle category filter 293 .on('click', function() { 294 var label = $(this).data('category-label'); 295 if (activeFilters.has(label)) { 296 activeFilters.delete(label); 297 $(this).removeClass('ter_kal_filtered'); 298 } else { 299 activeFilters.add(label); 300 $(this).addClass('ter_kal_filtered'); 301 } 302 303 ter_kal_applyEventFilter(instance_id); 304 }); 305 306 $catRow.append($btn); 307 }); 308 309 // ter_kal_CHANGE: Apply initial filter after calendar renders 310 if (activeFilters.size > 0) { 311 ter_kal_applyEventFilter(instance_id); 312 } 313 314 if (typeof callback === 'function') { 315 callback(); 316 } 317 318 localStorage.setItem('termin_kalender_kategorien', JSON.stringify(categories)); 319 } 320 321 322 // ter_kal_CHANGE: Use CSS classNames to preserve event styling (CORRECTED LOGIC) 323 function ter_kal_applyEventFilter(instance_id) { 324 var calendar = window.TerminKalender.ter_kal_calendars[instance_id]; 325 if (!calendar) return; 326 327 var activeFilters = window.TerminKalender.active_filters[instance_id]; 328
329 calendar.getEvents().forEach(function(event) { 330 var eventCategory = event.extendedProps.category ? event.extendedProps.category.label : null; 331 332 // ter_kal_CHANGE: Hide matched categories (clicked buttons are filtered OUT) 333 if (activeFilters.size === 0 || !activeFilters.has(eventCategory)) { 334 // Show: remove hidden class 335 event.setProp('classNames', event.classNames.filter(c => c !== 'ter-kal-hidden')); 336 } else { 337 // Hide: add hidden class when category IS in activeFilters 338 if (!event.classNames.includes('ter-kal-hidden')) { 339 event.setProp('classNames', [...event.classNames, 'ter-kal-hidden']); 340 } 341 } 342 }); 343 } 344 345 //--------------------------------------------------------------------------- 346 //--------------------------------------------------------------------------- 347 }); // end each calendar instance 348 //--------------------------------------------------------------------------- 349 //--------------------------------------------------------------------------- 350 351 }); // end jQuery 352 353}); // document.addEventListener 354//--------------------------------------------------------------------------- 355//---------------------------------------------------------------------------
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.