1let piecalJS = (function() { 2 const { __ } = wp.i18n; 3 4 function init( options ) { 5 if( typeof options !== 'object' ) { 6 throw new Error('Options must be an object'); 7 } 8 9 if( !options.mode || !getAllowedModes().includes(options.mode) ) { 10 throw new Error('Mode must be one of: ' + getAllowedModes().join(', ')); 11 } 12 13 if( !options.container ) { 14 throw new Error('Container must be provided'); 15 } 16 17 window.calendarInstances = window.calendarInstances || []; 18 19 let containers = document.querySelectorAll('[data-piecal-instance]'); 20 let instance = -1; 21 22 for( let container of containers ) { 23 instance++; 24 container.dataset.piecalInstance = instance; 25 26 let calendar = new FullCalendar.Calendar(container, { 27 headerToolbar: false, 28 initialView: getModeInitialView(options.mode), 29 editable: false, 30 locale: options.locale, 31 contentHeight: 'auto', 32 events: options.events, 33 views: { 34 listMonth: { 35 duration: { 36 days: options.lookahead || 30 37 } 38 } 39 }, 40 eventClick: options.eventClick 41 }); 42 43 calendar.render(); 44 45 window.calendarInstances.push(calendar); 46 } 47 } 48 49 function getAllowedModes() { 50 return ['recurrence-list', 'full']; 51 } 52 53 function getModeInitialView( mode ) { 54 let modeViewMap = { 55 'recurrence-list': 'listMonth', 56 'full': 'dayGridMonth' 57 }; 58 59 return modeViewMap[mode]; 60 } 61 62 function validateOptions( options ) { 63 let validOptions = { 64 calendarIndex: 'int', 65 appendOffset: 'boolean', 66 useAdaptiveTimezones: 'boolean' 67 }; 68 } 69 70 function eventClick( info, options = {} ) { 71 Alpine.store("calendarEngine").eventTitle = info.event._def.title; 72 Alpine.store("calendarEngine").eventStart = info.event.start; 73 Alpine.store("calendarEngine").eventEnd = info.event.end; 74 Alpine.store("calendarEngine").eventDetails = info.event._def.extendedProps.details ?? ''; 75 Alpine.store("calendarEngine").eventUrl = info.event._def.extendedProps.permalink; 76 Alpine.store("calendarEngine").eventAllDay = info.event.allDay; 77 Alpine.store("calendarEngine").eventType = info.event._def.extendedProps.postType; 78 Alpine.store('calendarEngine').showPopover = true; 79 Alpine.store('calendarEngine').eventActualEnd = info.event._def.extendedProps.actualEnd; 80 Alpine.store('calendarEngine').appendOffset = options.appendOffset; 81 82 // Always pass through event data via the URL if it's a recurring instance, or if adaptive timezones are enabled. 83 // Do not pass through event data via the URL if it's a non-recurring instance and adaptive timezones are disabled. 84 if( info.event._def.extendedProps.isRecurringInstance || 85 ( !info.event._def.extendedProps.isRecurringInstance && piecalVars.useAdaptiveTimezones && Alpine.store('calendarEngine').appendOffset ) && 86 info.event._def.extendedProps.permalink ) { 87 88 // Construct the URL with parameters 89 const baseUrl = info.event._def.extendedProps.permalink; 90 const eventStart = new Date( info.event.start ); 91 const eventEnd = new Date( info.event.end ); 92 const viewerTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; 93 94 95 const url = new URL( baseUrl ); 96 url.searchParams.append( 'eventstart', Math.floor( eventStart.getTime() / 1000 ) ); 97 url.searchParams.append( 'eventend', Math.floor( eventEnd.getTime() / 1000 ) ); 98 url.searchParams.append( 'timezone', viewerTimezone ); 99 100 // Assign the constructed URL to the store 101 Alpine.store("calendarEngine").eventUrl = url.toString(); 102 } 103 104 if( info.jsEvent.type == "keydown" ) { 105 setTimeout( () => { 106 document.querySelector('.piecal-popover__inner > button').focus(); 107 }, 100); 108 } 109 110 return info; 111 } 112 113 function eventDidMount( info, options = {} ) { 114 let link = info.el; 115 116 const locale = info.view.dateEnv.locale.codeArg; 117 118 const formattedTime = new Intl.DateTimeFormat(locale, { 119 hour: 'numeric', 120 minute: 'numeric', 121 hour12: true 122 }); 123 124 const formattedDate = new Intl.DateTimeFormat(locale, { 125 day: 'numeric', 126 month: 'numeric', 127 year: 'numeric' 128 }); 129 130 if( link.tagName == 'TR' ) { 131 link = info.el.querySelector('a'); 132 } 133 134 if( !link || link.tagName != "A" ) return; 135 136 link.setAttribute('role', 'button'); 137 link.setAttribute('href', 'javascript:void(0)'); 138 139 if( info.event.allDay ) { 140 const allDayDescriptionText = __('All-day event', 'piecal'); 141 142 link.setAttribute('aria-label', `${allDayDescriptionText} - ${info.event.title}`); 143 } 144 145 // Handle multi-day event aria label to let screen readers know the event spans multiple days 146 if( info.event.end && (info.event.end - info.event.start) > (24 * 60 * 60 * 1000) ) { 147 const startDate = formattedDate.format(info.event.start); 148 const startTime = info.event.allDay ? '' : formattedTime.format(info.event.start); 149
150 const endDate = formattedDate.format(info.event.end); 151 const endTime = info.event.allDay ? '' :formattedTime.format(info.event.end); 152 153 /* Translators: Text describing span of multi-day event. */ 154 const spanText = __('to', 'piecal'); 155 156 /* Translators: Text for multi-day event description. */ 157 const multiDayDescriptionText = __('Multi-day event running from', 'piecal'); // <?php _e("'Multi-day event running from'", 'piecal'); ?>; 158 159 /* Translators: Text for multi-day all-day event description. */ 160 const multiDayAllDayDescriptionText = __('Multi-day, all-day event running from', 'piecal'); // <?php _e("'Multi-day, all-day event running from'", 'piecal'); ?>; 161 162 const descriptionText = info.event.allDay ? multiDayAllDayDescriptionText : multiDayDescriptionText; 163 164 /* Translators: Text describing span of multi-day event. */ 165 link.setAttribute('aria-label', `${descriptionText} ${startDate} ${startTime} ${spanText} ${endDate} ${endTime} - ${info.event.title}`); 166 } 167 168 return info; 169 } 170 171 function dayCellDidMount( info, options = {} ) { 172 let dayLink = info.el.querySelector('.fc-daygrid-day-top a'); 173 174 if( !dayLink ) return; 175 176 dayLink.setAttribute('role', 'button'); 177 dayLink.setAttribute('href', 'javascript:void(0)'); 178 179 // Prevent double read out of button label 180 dayLink.closest('td').removeAttribute('aria-labelledby'); 181 182 setTimeout( () => { 183 if( info.el.querySelector('.fc-daygrid-day-events .fc-daygrid-event-harness') ) { 184 dayLink.setAttribute('aria-label', dayLink.getAttribute('aria-label') + ', has events.'); 185 } 186 }, 100); 187 188 dayLink.addEventListener('keydown', (event) => { 189 if( event.key == "Enter" || event.key == ' ' ) { 190 event.preventDefault(); 191 window.calendar.gotoDate(info.date); 192 piecalChangeView('listDay'); 193 194 setTimeout( () => { 195 let focusTarget = document.querySelector('.fc-list-day-text'); 196 focusTarget?.setAttribute('tabindex', '0'); 197 focusTarget?.focus(); 198 }, 100); 199 } 200 }) 201 202 return info; 203 } 204 205 function eventDataTransform( event, options = {} ) { 206 // Safely decode encoded HTML entities for output as titles 207 let scrubber = document.createElement('textarea'); 208 scrubber.innerHTML = event.title; 209 event.title = scrubber.value; 210 211 // Extend end date for all day events that span multiple days 212 let { actualEnd, end } = piecalUtils.getAlldayMultidayEventEnd( event ) ?? {}; 213 214 if( actualEnd && end ) { 215 event.actualEnd = actualEnd; 216 event.end = end; 217 } 218 219 return event; 220 } 221 222 function dateClick( info, options = {} ) { 223 if( info.jsEvent.target.tagName != 'A' ) return; 224 225 if( !options.calendarIndex ) { 226 window.calendar.gotoDate(info.dateStr); 227 } else { 228 window.calendarInstances[options.calendarIndex].gotoDate(info.dateStr); 229 } 230 231 piecalChangeView('listDay'); 232 233 return info; 234 } 235 236 function dayHeaderContent( info, options = {} ) { 237 let overriddenDayHeaderViews = ['dayGridMonth', 'timeGridWeek', 'dayGridWeek']; 238 239 if( overriddenDayHeaderViews.includes(info.view.type) ) { 240 return ''; 241 } 242 243 return info; 244 } 245 246 function dayHeaderDidMount( info, options = {} ) { 247 let dayHeaderLink = info.el.querySelector('a'); 248 249 if( !dayHeaderLink ) return; 250 251 let fullDayName = piecalUtils.getLocalizedDayNames(info.text, 'full', options.locale ); 252 let shortDayName = piecalUtils.getLocalizedDayNames(info.text, 'short', options.locale ); 253 let singleLetterDayName = piecalUtils.getLocalizedDayNames(info.text, 'single', options.locale ); 254 255 let shortenableViews = ['dayGridMonth', 'timeGridWeek', 'dayGridWeek', 'listWeek']; 256 let viewsWithDates = ['timeGridWeek', 'listWeek', 'dayGridWeek']; 257 258 if( shortenableViews.includes(info.view.type) ) { 259 if( viewsWithDates.includes(info.view.type) && options.showDates != false ) { 260 // Format the date from info.date 261 const dateFormatter = new Intl.DateTimeFormat(info.view.dateEnv.locale.codeArg, { 262 month: 'numeric', 263 day: 'numeric' 264 }); 265 const formattedDate = dateFormatter.format(info.date); 266 267 dayHeaderLink.innerHTML = `<span class="piecal-grid-day-header-text piecal-grid-day-header-text--full">${fullDayName} ${formattedDate}</span>
268 <span class="piecal-grid-day-header-text piecal-grid-day-header-text--short">${shortDayName} ${formattedDate}</span> 269 <span class="piecal-grid-day-header-text piecal-grid-day-header-text--single-letter">${singleLetterDayName} ${formattedDate}</span>`; 270 } else { 271 dayHeaderLink.innerHTML = `<span class="piecal-grid-day-header-text piecal-grid-day-header-text--full">${fullDayName}</span> 272 <span class="piecal-grid-day-header-text piecal-grid-day-header-text--short">${shortDayName}</span> 273 <span class="piecal-grid-day-header-text piecal-grid-day-header-text--single-letter">${singleLetterDayName}</span>`; 274 } 275 } 276 277 return info; 278 } 279 280 // Return methods that should be accessible when invoking piecalJS. 281 return {init, eventDidMount, dayCellDidMount, eventClick, eventDataTransform, dateClick, dayHeaderContent, dayHeaderDidMount}; 282})();
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.