1const { __ } = wp.i18n; 2 3let piecalJS = (function() { 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 || ( !info.event._def.extendedProps.isRecurringInstance && piecalVars.useAdaptiveTimezones && Alpine.store('calendarEngine').appendOffset ) ) { 85 // Construct the URL with parameters 86 const baseUrl = info.event._def.extendedProps.permalink; 87 const eventStart = new Date( info.event.start ); 88 const eventEnd = new Date( info.event.end ); 89 const viewerTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; 90 91 92 const url = new URL( baseUrl ); 93 url.searchParams.append( 'eventstart', Math.floor( eventStart.getTime() / 1000 ) ); 94 url.searchParams.append( 'eventend', Math.floor( eventEnd.getTime() / 1000 ) ); 95 url.searchParams.append( 'timezone', viewerTimezone ); 96 97 // Assign the constructed URL to the store 98 Alpine.store("calendarEngine").eventUrl = url.toString(); 99 } 100 101 if( info.jsEvent.type == "keydown" ) { 102 setTimeout( () => { 103 document.querySelector('.piecal-popover__inner > button').focus(); 104 }, 100); 105 } 106 107 return info; 108 } 109 110 function eventDidMount( info, options = {} ) { 111 let link = info.el; 112 113 const locale = info.view.dateEnv.locale.codeArg; 114 115 const formattedTime = new Intl.DateTimeFormat(locale, { 116 hour: 'numeric', 117 minute: 'numeric', 118 hour12: true 119 }); 120 121 const formattedDate = new Intl.DateTimeFormat(locale, { 122 day: 'numeric', 123 month: 'numeric', 124 year: 'numeric' 125 }); 126 127 if( link.tagName == 'TR' ) { 128 link = info.el.querySelector('a'); 129 } 130 131 if( !link || link.tagName != "A" ) return; 132 133 link.setAttribute('role', 'button'); 134 link.setAttribute('href', 'javascript:void(0)'); 135 136 if( info.event.allDay ) { 137 const allDayDescriptionText = __('All-day event', 'piecal'); 138 139 link.setAttribute('aria-label', `${allDayDescriptionText} - ${info.event.title}`); 140 } 141 142 // Handle multi-day event aria label to let screen readers know the event spans multiple days 143 if( info.event.end && (info.event.end - info.event.start) > (24 * 60 * 60 * 1000) ) { 144 const startDate = formattedDate.format(info.event.start); 145 const startTime = info.event.allDay ? '' : formattedTime.format(info.event.start); 146
147 const endDate = formattedDate.format(info.event.end); 148 const endTime = info.event.allDay ? '' :formattedTime.format(info.event.end); 149 150 /* Translators: Text describing span of multi-day event. */ 151 const spanText = __('to', 'piecal'); 152 153 /* Translators: Text for multi-day event description. */ 154 const multiDayDescriptionText = __('Multi-day event running from', 'piecal'); // <?php _e("'Multi-day event running from'", 'piecal'); ?>; 155 156 /* Translators: Text for multi-day all-day event description. */ 157 const multiDayAllDayDescriptionText = __('Multi-day, all-day event running from', 'piecal'); // <?php _e("'Multi-day, all-day event running from'", 'piecal'); ?>; 158 159 const descriptionText = info.event.allDay ? multiDayAllDayDescriptionText : multiDayDescriptionText; 160 161 /* Translators: Text describing span of multi-day event. */ 162 link.setAttribute('aria-label', `${descriptionText} ${startDate} ${startTime} ${spanText} ${endDate} ${endTime} - ${info.event.title}`); 163 } 164 165 return info; 166 } 167 168 function dayCellDidMount( info, options = {} ) { 169 let dayLink = info.el.querySelector('.fc-daygrid-day-top a'); 170 171 if( !dayLink ) return; 172 173 dayLink.setAttribute('role', 'button'); 174 dayLink.setAttribute('href', 'javascript:void(0)'); 175 176 // Prevent double read out of button label 177 dayLink.closest('td').removeAttribute('aria-labelledby'); 178 179 setTimeout( () => { 180 if( info.el.querySelector('.fc-daygrid-day-events .fc-daygrid-event-harness') ) { 181 dayLink.setAttribute('aria-label', dayLink.getAttribute('aria-label') + ', has events.'); 182 } 183 }, 100); 184 185 dayLink.addEventListener('keydown', (event) => { 186 if( event.key == "Enter" || event.key == ' ' ) { 187 event.preventDefault(); 188 window.calendar.gotoDate(info.date); 189 piecalChangeView('listDay'); 190 191 setTimeout( () => { 192 let focusTarget = document.querySelector('.fc-list-day-text'); 193 focusTarget?.setAttribute('tabindex', '0'); 194 focusTarget?.focus(); 195 }, 100); 196 } 197 }) 198 199 return info; 200 } 201 202 function eventDataTransform( event, options = {} ) { 203 // Safely decode encoded HTML entities for output as titles 204 let scrubber = document.createElement('textarea'); 205 scrubber.innerHTML = event.title; 206 event.title = scrubber.value; 207 208 // Extend end date for all day events that span multiple days 209 let { actualEnd, end } = piecalUtils.getAlldayMultidayEventEnd( event ) ?? {}; 210 211 if( actualEnd && end ) { 212 event.actualEnd = actualEnd; 213 event.end = end; 214 } 215 216 return event; 217 } 218 219 function dateClick( info, options = {} ) { 220 if( info.jsEvent.target.tagName != 'A' ) return; 221 222 if( !options.calendarIndex ) { 223 window.calendar.gotoDate(info.dateStr); 224 } else { 225 window.calendarInstances[options.calendarIndex].gotoDate(info.dateStr); 226 } 227 228 piecalChangeView('listDay'); 229 230 return info; 231 } 232 233 function dayHeaderContent( info, options = {} ) { 234 let overriddenDayHeaderViews = ['dayGridMonth', 'timeGridWeek', 'dayGridWeek']; 235 236 if( overriddenDayHeaderViews.includes(info.view.type) ) { 237 return ''; 238 } 239 240 return info; 241 } 242 243 function dayHeaderDidMount( info, options = {} ) { 244 let dayHeaderLink = info.el.querySelector('a'); 245 246 let fullDayName = piecalUtils.getShortenedDayNames(info.text, 'full'); 247 let shortDayName = piecalUtils.getShortenedDayNames(info.text, 'short'); 248 let singleLetterDayName = piecalUtils.getShortenedDayNames(info.text, 'single'); 249 250 let shortenableViews = ['dayGridMonth', 'timeGridWeek', 'dayGridWeek']; 251 252 if( shortenableViews.includes(info.view.type) ) { 253 dayHeaderLink.innerHTML = `<span class="piecal-grid-day-header-text piecal-grid-day-header-text--full">${fullDayName}</span>
254 <span class="piecal-grid-day-header-text piecal-grid-day-header-text--short">${shortDayName}</span> 255 <span class="piecal-grid-day-header-text piecal-grid-day-header-text--single-letter">${singleLetterDayName}</span>`; 256 } 257 258 return info; 259 } 260 261 // Return methods that should be accessible when invoking piecalJS. 262 return {init, eventDidMount, dayCellDidMount, eventClick, eventDataTransform, dateClick, dayHeaderContent, dayHeaderDidMount}; 263})();
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.