1(function ($) { 2 3 let calendar; 4 5 let Calendar = function ($container, options) { 6 let obj = this; 7 jQuery.extend(obj.options, options); 8 9 // Special locale for moment 10 moment.locale('bookly', { 11 months: obj.options.l10n.datePicker.monthNames, 12 monthsShort: obj.options.l10n.datePicker.monthNamesShort, 13 weekdays: obj.options.l10n.datePicker.dayNames, 14 weekdaysShort: obj.options.l10n.datePicker.dayNamesShort, 15 meridiem: function (hours, minutes, isLower) { 16 return hours < 12 17 ? obj.options.l10n.datePicker.meridiem[isLower ? 'am' : 'AM'] 18 : obj.options.l10n.datePicker.meridiem[isLower ? 'pm' : 'PM']; 19 }, 20 }); 21 22 let allowEditAppointment = typeof BooklyAppointmentDialog !== 'undefined' && (!options.hasOwnProperty('disable_editor') || !options.disable_editor); 23 24 let viewTypes = { 25 dayGridMonth: 'resourceTimelineMonth', 26 timeGridWeek: 'resourceTimelineWeek', 27 resourceTimeGridDay: 'resourceTimelineDay' 28 } 29 // Settings for Event Calendar 30 let settings = { 31 view: 'timeGridWeek', 32 customButtons: { 33 timeline: { 34 active: true, 35 text: {html: '<i class="far fa-fw fa-check-square"></i> ' + obj.options.l10n.timeline}, 36 click: function (e) { 37 let headerToolbar = calendar.getOption('headerToolbar'), 38 view = calendar.getOption('view'); 39 headerToolbar.end = 'dayGridMonth,timeGridWeek,resourceTimeGridDay,grid listWeek' 40 calendar.setOption('headerToolbar', headerToolbar); 41 let _viewTypes = Object.values(viewTypes); 42 if (_viewTypes.includes(view)) { 43 // Change view 44 Object.keys(viewTypes).forEach(function (key) { 45 if (viewTypes[key] === view) { 46 calendar.setOption('view', key); 47 } 48 }); 49 } else if (view === 'listWeek') { 50 setCookie('bookly_cal_view', 'listWeek'); 51 } 52 } 53 }, 54 grid: { 55 text: {html: '<i class="far fa-fw fa-square"></i> ' + obj.options.l10n.timeline}, 56 click: function () { 57 let headerToolbar = calendar.getOption('headerToolbar'), 58 view = calendar.getOption('view'); 59 headerToolbar.end = 'resourceTimelineMonth,resourceTimelineWeek,resourceTimelineDay,timeline listWeek' 60 calendar.setOption('headerToolbar', headerToolbar); 61 if (viewTypes.hasOwnProperty(view)) { 62 // Change view 63 calendar.setOption('view', viewTypes[view]); 64 } else if (view === 'listWeek') { 65 setCookie('bookly_cal_view', 'listWeekTimeline'); 66 } 67 } 68 }, 69 }, 70 views: { 71 dayGridMonth: { 72 dayHeaderFormat: function (date) { 73 return moment(date).locale('bookly').format('ddd'); 74 }, 75 displayEventEnd: true, 76 dayMaxEvents: obj.options.l10n.monthDayMaxEvents === '1', 77 theme: function (theme) { 78 if (obj.options.l10n.monthDayMaxEvents === '1') { 79 theme.view += ' ec-minimalistic'; 80 } 81 return theme; 82 } 83 }, 84 timeGridDay: { 85 dayHeaderFormat: function (date) { 86 return moment(date).locale('bookly').format('dddd'); 87 }, 88 pointer: true 89 }, 90 timeGridWeek: {pointer: true}, 91 resourceTimeGridDay: {pointer: true}, 92 resourceTimelineDay: {pointer: true, displayEventEnd: true, slotWidth: 180}, 93 resourceTimelineWeek: {pointer: true, displayEventEnd: true, slotWidth: 180}, 94 resourceTimelineMonth: {pointer: true, displayEventEnd: true, slotWidth: 180} 95 }, 96 nowIndicator: true, 97 hiddenDays: obj.options.l10n.hiddenDays, 98 slotDuration: obj.options.l10n.slotDuration, 99 slotMinTime: obj.options.l10n.slotMinTime, 100 slotMaxTime: obj.options.l10n.slotMaxTime, 101 scrollTime: obj.options.l10n.scrollTime, 102 moreLinkContent: function (arg) { 103 return obj.options.l10n.more.replace('%d', arg.num) 104 }, 105 flexibleSlotTimeLimits: true, 106 eventStartEditable: false, 107 eventDurationEditable: false, 108 allDaySlot: false, 109 allDayContent: obj.options.l10n.allDay, 110 111 slotLabelFormat: function (date) {
112 return moment(date).locale('bookly').format(obj.options.l10n.mjsTimeFormat); 113 }, 114 eventTimeFormat: function (start, end) { 115 if (start.getTime() === end.getTime()) { 116 return moment(start).locale('bookly').format(obj.options.l10n.mjsTimeFormat); 117 } else { 118 return moment(start).locale('bookly').format(obj.options.l10n.mjsTimeFormat) + ' - ' + moment(end).locale('bookly').format(obj.options.l10n.mjsTimeFormat); 119 } 120 }, 121 dayHeaderFormat: function (date) { 122 return moment(date).locale('bookly').format('ddd, D'); 123 }, 124 listDayFormat: function (date) { 125 return moment(date).locale('bookly').format('dddd'); 126 }, 127 firstDay: obj.options.l10n.datePicker.firstDay, 128 locale: obj.options.l10n.locale.replace('_', '-'), 129 buttonText: { 130 today: obj.options.l10n.today, 131 dayGridMonth: obj.options.l10n.month, 132 timeGridWeek: obj.options.l10n.week, 133 timeGridDay: obj.options.l10n.day, 134 resourceTimeGridDay: obj.options.l10n.day, 135 resourceTimelineMonth: obj.options.l10n.month, 136 resourceTimelineWeek: obj.options.l10n.week, 137 resourceTimelineDay: obj.options.l10n.day, 138 listWeek: obj.options.l10n.list 139 }, 140 noEventsContent: obj.options.l10n.noEvents, 141 eventSources: [{ 142 url: ajaxurl, 143 method: 'POST', 144 extraParams: function () { 145 return { 146 action: 'bookly_get_staff_appointments', 147 csrf_token: BooklyL10nGlobal.csrf_token, 148 staff_ids: obj.options.getStaffMemberIds(), 149 location_ids: obj.options.getLocationIds(), 150 service_ids: obj.options.getServiceIds() 151 }; 152 } 153 }], 154 eventFilter: function (info) { 155 return !['dayGridMonth', 'timeGridWeek', 'listWeek'].includes(info.view.type) || !info.event.extendedProps.resources_only; 156 }, 157 eventBackgroundColor: '#ccc', 158 eventMouseEnter: function (arg) { 159 arg.jsEvent.stopPropagation(); 160 if (arg.event.display === 'background') { 161 return ''; 162 } 163 let $event = $(arg.el); 164 if (arg.event.display === 'auto' && arg.view.type !== 'listWeek') { 165 let $existing_popover = $event.find('.bookly-ec-popover') 166 if (!$existing_popover.length) { 167 let $popover = $('<div class="bookly-popover bs-popover-top bookly-ec-popover">') 168 let $arrow = $('<div class="arrow"></div><div class="bookly-arrow-background"></div>'); 169 let $body = $('<div class="popover-body">'); 170 let $buttons = allowEditAppointment ? popoverButtons(arg) : ''; 171 172 $body.append(arg.event.extendedProps.tooltip).append($buttons).css({minWidth: '200px'}); 173 $popover.append($arrow).append($body); 174 $event.append($popover); 175 } 176 177 let $popover = $event.find('.bookly-ec-popover'), 178 $arrow = $popover.find('.arrow'), 179 offset = $event.offset(), 180 popover_height = $popover.outerHeight(), 181 $calendar_container = $event.closest('.ec-body').length ? $event.closest('.ec-body') : $event.closest('.ec-all-day'), 182 container_top = $calendar_container.offset().top, 183 event_width = $event.outerWidth(), 184 popover_right = offset.left > window.innerWidth / 2; 185 186 $popover.css('min-width', (Math.min(400, event_width - 2)) + 'px'); 187 188 if (container_top > offset.top - popover_height) { 189 // Popover onside of the event
190 $popover.css('top', (Math.max(container_top, offset.top) - $(document).scrollTop()) + 'px'); 191 if (popover_right) { 192 $popover.removeClass('bs-popover-top').removeClass('bs-popover-right').addClass('bs-popover-left'); 193 $popover.css('left', (offset.left - $popover.outerWidth()) + 'px'); 194 $arrow.css('right', '-8px'); 195 } else { 196 $popover.removeClass('bs-popover-top').removeClass('bs-popover-left').addClass('bs-popover-right'); 197 $popover.css('left', Math.min(offset.left - 7 + event_width, $calendar_container.offset().left + $calendar_container.outerWidth() - $popover.outerWidth() - 32) + 'px'); 198 $arrow.css('left', '-8px'); 199 } 200 } else { 201 // Popover on top of the event 202 let top = Math.max(popover_height + 40, Math.max(container_top, offset.top) - $(document).scrollTop()); 203 204 $popover.css('top', (top - popover_height - 5) + 'px') 205 $popover.removeClass('bs-popover-left').removeClass('bs-popover-right').addClass('bs-popover-top'); 206 if (popover_right) { 207 $popover.css('left', (offset.left + event_width - $popover.outerWidth()) + 'px'); 208 $arrow.css('right', '8px'); 209 } else { 210 $popover.css('left', (offset.left + 2) + 'px'); 211 $arrow.css('left', '8px'); 212 } 213 } 214 } 215 }, 216 eventContent: function (arg) { 217 if (arg.event.display === 'background') { 218 return ''; 219 } 220 let event = arg.event; 221 let props = event.extendedProps; 222 let nodes = []; 223 let $time = $('<div class="ec-event-time"/>'); 224 let $title = $('<div class="ec-event-title"/>'); 225 226 $time.append(props.header_text || arg.timeText); 227 nodes.push($time.get(0)); 228 if (arg.view.type === 'listWeek') { 229 let dot = $('<div class="ec-event-dot"></div>').css('border-color', event.backgroundColor); 230 nodes.push($('<div/>').append(dot).get(0)); 231 } 232 $title.append(props.desc || ''); 233 nodes.push($title.get(0)); 234 235 switch (props.overall_status) { 236 case 'pending': 237 $time.addClass('text-muted'); 238 $title.addClass('text-muted'); 239 break; 240 case 'rejected': 241 case 'cancelled': 242 $time.addClass('text-muted').wrapInner('<s>'); 243 $title.addClass('text-muted'); 244 break; 245 } 246 247 if (arg.view.type === 'listWeek' && allowEditAppointment) { 248 $title.append(popoverButtons(arg)); 249 } 250 251 return {domNodes: nodes}; 252 }, 253 eventClick: function (arg) { 254 if (arg.event.display === 'background') { 255 return; 256 } 257 arg.jsEvent.stopPropagation(); 258 259 if (allowEditAppointment) { 260 if (arg.event.extendedProps.type === 'event') { 261 BooklyAttendeesDialog.showDialog({id: arg.event.extendedProps.id}, function () { calendar.refetchEvents(); }) 262 } else { 263 let visible_staff_id; 264 if (arg.view.type === 'resourceTimeGridDay') { 265 visible_staff_id = 0; 266 } else { 267 visible_staff_id = obj.options.getCurrentStaffId(); 268 } 269 BooklyAppointmentDialog.showDialog(
270 arg.event.id, 271 null, 272 null, 273 function (event) { 274 if (event == 'refresh') { 275 calendar.refetchEvents(); 276 } else { 277 if (event.start === null) { 278 // Task 279 calendar.removeEventById(event.id); 280 } else { 281 if (visible_staff_id == event.resourceId || visible_staff_id == 0) { 282 // Update event in calendar. 283 calendar.removeEventById(event.id); 284 calendar.addEvent(event); 285 } else { 286 // Switch to the event owner tab. 287 jQuery('li > a[data-staff_id=' + event.resourceId + ']').click(); 288 } 289 } 290 } 291 292 if (locationChanged) { 293 calendar.refetchEvents(); 294 locationChanged = false; 295 } 296 } 297 ); 298 } 299 } 300 }, 301 dateClick: function (arg) { 302 let staff_id, visible_staff_id; 303 if (['resourceTimeGridDay', 'resourceTimelineMonth', 'resourceTimelineWeek', 'resourceTimelineDay'].includes(arg.view.type)) { 304 staff_id = arg.resource.id; 305 visible_staff_id = 0; 306 } else { 307 staff_id = visible_staff_id = obj.options.getCurrentStaffId(); 308 } 309 addAppointmentDialog(arg.date, staff_id, visible_staff_id); 310 }, 311 noEventsClick: function (arg) { 312 let staffId = obj.options.getCurrentStaffId(); 313 addAppointmentDialog(arg.view.activeStart, staffId, staffId); 314 }, 315 loading: function (isLoading) { 316 if (!calendar) { 317 return; 318 } 319 if (isLoading) { 320 if (allowEditAppointment) { 321 BooklyL10nAppDialog.refreshed = true; 322 } 323 if (dateSetFromDatePicker) { 324 dateSetFromDatePicker = false; 325 } else { 326 calendar.setOption('highlightedDates', []); 327 } 328 $('.bookly-ec-loading').show(); 329 } else { 330 let allDay = false; 331 if (calendar.getEvents().length) { 332 calendar.getEvents().forEach(function (event) { 333 if (event.allDay) { 334 allDay = true; 335 } 336 }) 337 } 338 calendar.setOption('allDaySlot', allDay); 339 $('.bookly-ec-loading').hide(); 340 obj.options.refresh(); 341 } 342 }, 343 viewDidMount: function (view) { 344 calendar.setOption('highlightedDates', []); 345 obj.options.viewChanged(view); 346 }, 347 theme: function (theme) { 348 theme.button = 'btn btn-default'; 349 theme.buttonGroup = 'btn-group'; 350 theme.active = 'active'; 351 return theme; 352 } 353 }; 354 355 function popoverButtons(arg) { 356 const $buttons = arg.view.type === 'listWeek' ? $('<div class="mt-2 d-flex"></div>') : $('<div class="mt-2 d-flex justify-content-end border-top pt-2"></div>'); 357 let props = arg.event.extendedProps; 358 if (props.hasOwnProperty('type') && props.type === 'event') { 359 $buttons.append( 360 $('<a class="btn btn-success btn-sm mr-1">').append('<i class="fas fa-fw fa-users">') 361 .attr('title', obj.options.l10n.events.attendees) 362 .on('click', function (e) { 363 e.stopPropagation(); 364 BooklyAttendeesDialog.showDialog({id: arg.event.extendedProps.id}, function () { calendar.refetchEvents(); }) 365 }) 366 ); 367 } else { 368 $buttons.append($('<button class="btn btn-success btn-sm mr-1">').append('<i class="far fa-fw fa-edit">')); 369 if (obj.options.l10n.recurring_appointments.active == '1' && props.series_id) { 370 $buttons.append( 371 $('<a class="btn btn-default btn-sm mr-1">').append('<i class="fas fa-fw fa-link">') 372 .attr('title', obj.options.l10n.recurring_appointments.title) 373 .on('click', function (e) { 374 e.stopPropagation(); 375 BooklySeriesDialog.showDialog({ 376 series_id: props.series_id, 377 done: function () { 378 calendar.refetchEvents(); 379 } 380 }); 381 }) 382 ); 383 } 384 if (obj.options.l10n.waiting_list.active == '1' && props.waitlisted > 0) { 385 $buttons.append( 386 $('<a class="btn btn-default btn-sm mr-1">').append('<i class="far fa-fw fa-list-alt">') 387 .attr('title', obj.options.l10n.waiting_list.title) 388 ); 389 } 390 if (obj.options.l10n.packages.active == '1' && props.package_id > 0) { 391 $buttons.append( 392 $('<a class="btn btn-default btn-sm mr-1">').append('<i class="far fa-fw fa-calendar-alt">') 393 .attr('title', obj.options.l10n.packages.title) 394 .on('click', function (e) { 395 e.stopPropagation(); 396 if (obj.options.l10n.packages.active == '1' && props.package_id) { 397 $(document.body).trigger('bookly_packages.schedule_dialog', [props.package_id, function () { 398 calendar.refetchEvents(); 399 }]); 400 } 401 }) 402 ); 403 } 404 $buttons.append( 405 $('<a class="btn btn-danger btn-sm text-white">
405').append('<i class="far fa-fw fa-trash-alt">') 406 .attr('title', obj.options.l10n.delete) 407 .on('click', function (e) { 408 e.stopPropagation(); 409 // Localize contains only string values 410 if (obj.options.l10n.recurring_appointments.active == '1' && props.series_id) { 411 $(document.body).trigger('recurring_appointments.delete_dialog', [calendar, arg.event]); 412 } else { 413 new BooklyConfirmDeletingAppointment({ 414 action: 'bookly_delete_appointment', 415 appointment_id: arg.event.id, 416 csrf_token: BooklyL10nGlobal.csrf_token 417 }, 418 function (response) { 419 calendar.removeEventById(arg.event.id); 420 } 421 ); 422 } 423 }) 424 ); 425 } 426 427 return $buttons; 428 } 429 430 function addAppointmentDialog(date, staffId, visibleStaffId) { 431 if (allowEditAppointment) { 432 BooklyAppointmentDialog.showDialog( 433 null, 434 parseInt(staffId), 435 moment(date), 436 function (event) { 437 if (event == 'refresh') { 438 calendar.refetchEvents(); 439 } else { 440 if (visibleStaffId == event.resourceId || visibleStaffId == 0) { 441 if (event.start !== null) { 442 if (event.id) { 443 // Create event in calendar. 444 calendar.addEvent(event); 445 } else { 446 calendar.refetchEvents(); 447 } 448 } 449 } else { 450 // Switch to the event owner tab. 451 jQuery('li[data-staff_id=' + event.resourceId + ']').click(); 452 } 453 } 454 455 if (locationChanged) { 456 calendar.refetchEvents(); 457 locationChanged = false; 458 } 459 } 460 ); 461 } 462 } 463 464 465 /** 466 * Set cookie. 467 * 468 * @param key 469 * @param value 470 */ 471 function setCookie(key, value) { 472 var expires = new Date(); 473 expires.setFullYear(expires.getFullYear() + 3); 474 document.cookie = key + '=' + value + ';expires=' + expires.toUTCString(); 475 } 476 477 let dateSetFromDatePicker = false; 478 479 calendar = EventCalendar.create($container.get(0), $.extend(true, {}, settings, obj.options.calendar)); 480 481 $('.ec-toolbar .ec-title', $container).on('click', function () { 482 let picker = $(this).data('daterangepicker'); 483 picker.setStartDate(calendar.getOption('date')); 484 picker.setEndDate(calendar.getOption('date')); 485 }); 486 // Init date picker for fast navigation in Event Calendar. 487 $('.ec-toolbar .ec-title', $container).daterangepicker({ 488 parentEl: '.bookly-js-calendar', 489 singleDatePicker: true, 490 showDropdowns: true, 491 autoUpdateInput: false, 492 locale: obj.options.l10n.datePicker 493 }, function (start) { 494 dateSetFromDatePicker = true; 495 calendar.setOption('highlightedDates', [start.toDate()]); 496 calendar.setOption('date', start.toDate()); 497 }); 498 499 // Export calendar 500 this.ec = calendar; 501 }; 502 503 var locationChanged = false; 504 $('body').on('change', '#bookly-appointment-location', function () { 505 locationChanged = true; 506 }); 507 508 Calendar.prototype.options = { 509 calendar: {}, 510 getCurrentStaffId: function () { return -1; }, 511 getStaffMemberIds: function () { return [this.getCurrentStaffId()]; }, 512 getServiceIds: function () { return ['all']; }, 513 getLocationIds: function () { return ['all']; }, 514 refresh: function () {}, 515 viewChanged: function () {}, 516 l10n: {} 517 }; 518 519 window.BooklyCalendar = Calendar; 520})(jQuery);
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.