403Webshell
Server IP : 81.2.241.106  /  Your IP : 216.73.216.165
Web Server : Apache/2.4.67 (Debian)
System : Linux tranq-f.bakta.org 5.10.0-45-amd64 #1 SMP Debian 5.10.259-1 (2026-07-02) x86_64
User : lisky ( 1002)
PHP Version : 7.4.33
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/lisky/wp-content/plugins/events-manager/includes/js/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/lisky/wp-content/plugins/events-manager/includes/js/events-manager-event-editor.js
(function() {
	'use strict';

	/**
	 * Timeranges Editor functionality
	 */
	function TimerangesEditor( container ) {
		this.init( container );
	}

	TimerangesEditor.prototype = {
		init: function( container = null ) {
			this.bindEvents( container );
		},

		bindEvents: function( container = null ) {
			let wrapper = container || document;
			let self = this;

			// Initialize preview if there are existing timeranges
			wrapper.querySelectorAll('.em-timeranges-editor').forEach( editor => {
				// Add timerange button
				editor.addEventListener('click', function(e) {
					if (e.target.matches('.em-timerange-add') || e.target.closest('.em-timerange-add')) {
						e.preventDefault();
						let button = e.target.matches('.em-timerange-add') ? e.target : e.target.closest('.em-timerange-add');
						self.addTimerange(button);
						self.updatePreview(e.target);
					}
				});

				// Delete timerange button
				editor.addEventListener('click', function(e) {
					if (e.target.matches('.em-timerange-delete') || e.target.closest('.em-timerange-delete')) {
						e.preventDefault();
						let button = e.target.matches('.em-timerange-delete') ? e.target : e.target.closest('.em-timerange-delete');
						let timeranges = button.closest('.em-timeranges');
						self.deleteTimerange(button);
						self.updatePreview( timeranges );
					}
					// handle the editor
					if ( e.target.matches('.em-timerange-editor-edit') ) {
						self.openUnlockModal( e.target );
					}
				});

				editor.querySelector('.em-timeranges-editor-modal')?.addEventListener('click', function(e) {
					if ( e.target.matches('button.unlock-confirm') ) {
						self.unlockEditor( e.target );
					} else if ( e.target.matches('button.unlock-cancel') ) {
						closeModal( e.currentTarget );
					}
				});

				// Time range validation and overlap detection
				editor.addEventListener('change', function(e) {
					if (e.target.matches('.em-time-start, .em-time-end, .em-time-all-day')) {
						self.validateTimeRanges(e.target);
						// Also update preview when time ranges change
						self.updatePreview(e.target);
					}
				});

				// Advanced options changes
				editor.addEventListener('change', function(e) {
					if (e.target.matches('.em-timerange-duration, .em-timerange-buffer, .em-timerange-frequency, .em-timerange-duration-unit, .em-timerange-buffer-unit, .em-timerange-frequency-unit, .em-timerange-overlap')) {
						self.updatePreview(e.target);
						if (e.target.matches('.em-timerange-duration-unit')) {
							self.syncFrequencyUnit(e.target);
						}
					}
				});

				// Real-time preview updates for time inputs
				editor.addEventListener('input', function(e) {
					if (e.target.matches('.em-time-start, .em-time-end')) {
						self.updatePreview(e.target);
					}
				});

				// Real-time duration changes to update frequency placeholder
				editor.addEventListener('input', function(e) {
					if (e.target.matches('.em-timerange-duration')) {
						self.syncFrequencyWithDuration(e.target);
						self.updatePreview(e.target);
					}
				});

				// Mutual exclusivity between advanced and all-day checkboxes
				editor.addEventListener('change', function(e) {
					if (e.target.matches('.em-timerange-timeslots-trigger input[type="checkbox"]')) {
						self.handleAdvancedToggle(e.target);
					} else if (e.target.matches('.em-timerange-allday input[type="checkbox"]')) {
						self.handleAllDayToggle(e.target);
						self.updatePreview(e.target);
					}
				});

				// Preview toggle
				editor.addEventListener('click', function(e) {
					if (e.target.matches('.em-timeranges-preview-toggle') || e.target.closest('.em-timeranges-preview-toggle')) {
						e.preventDefault();
						let toggle = e.target.matches('.em-timeranges-preview-toggle') ? e.target : e.target.closest('.em-timeranges-preview-toggle');
						self.togglePreview(toggle);
					}
				});

				editor.querySelectorAll('.em-timerange .em-time-start').forEach( input => {
					self.updatePreview( input );
				});
				// disable things if there's a disabled class (for recurring/repeating events)
				if ( editor.classList.contains('disabled') ) {
					let selector = 'input[name], .em-timeranges select, .em-timeranges button:not(.em-timerange-editor-edit), .em-timeranges-actions button';
					editor.querySelectorAll( selector ).forEach( el => { el.disabled = true; });
				}
			});
		},

		openUnlockModal: function( button ) {
			let editor = button.closest('.em-timeranges-editor');
			let modal = button.closest('.em-timeranges-editor').querySelector('.em-timeranges-editor-modal');
			modal.editor = editor;
			openModal( modal );
			modal.addEventListener( 'em_modal_close', function() {
				// re-append modal on close for submission
				editor.append(modal);
			}, { once: true });
		},

		unlockEditor: function( button ) {
			// modal actions confirming reschedule and re-appending modal upon close
			let modal  = button.closest('.em-timeranges-editor-modal');
			modal.editor.classList.remove('disabled')
			// remove all disabled properties
			modal.editor.querySelectorAll('[disabled]').forEach( el => { el.disabled = false; });
			// move the edit action option to the recurrence set to make it visible
			let timeranges = modal.editor.querySelector('.em-timeranges');
			timeranges.insertBefore( modal.querySelector('.timeranges-edit-action'), timeranges.firstElementChild.nextSibling );
			// set the edit nonce so we can update the timeranges on submission
			let nonce = modal.editor.querySelector('.em-timeranges-editor-nonce');
			if ( nonce ) {
				nonce.value = nonce.dataset.nonce;
			} else {
				alert('ERROR: No nonce found for timeranges edit, editing not possible and your changes will not be saved. Contact an administrator about this.');
			}
			closeModal(modal);
		},

		addTimerange: function(button) {
			let container = button.closest('.em-timeranges-editor').querySelector('.em-timeranges');
			let template = container.querySelector('template');
			let currentIndex = parseInt( container.dataset.index || '0', 10 );
			let newIndex = currentIndex + 1;

			if (template) {
				// Get template content
				let templateHtml = template.innerHTML;

				// Replace [X] placeholders with new index
				let newTimerangeHtml = templateHtml.replace(/\[X\]/g, `[${newIndex}]`);

				// Create new timerange element
				let tempDiv = document.createElement('div');
				tempDiv.innerHTML = newTimerangeHtml;
				let newTimerange = tempDiv.firstElementChild;

				// Insert before the add button
				template.before(newTimerange);
				em_setup_ui_elements(newTimerange);

				// Initialize frequency sync for new timerange
				let durationInput = newTimerange.querySelector('.em-timerange-duration');
				if (durationInput) {
					this.syncFrequencyWithDuration(durationInput);
				}

				// unset all day checkboxes
				container.querySelectorAll('.em-timerange-allday input[type="checkbox"]').forEach( checkbox => { if ( checkbox.checked ) { checkbox.click(); } } );

				// Update count
				container.dataset.index = newIndex;
				container.dataset.count = container.querySelectorAll('& > .em-timerange').length;

				// Update preview after adding new timerange
				let startInput = newTimerange.querySelector('.em-time-start');
				if (startInput) {
					this.updatePreview(startInput);
				}

				// Trigger custom event
				let event = new CustomEvent('timerange:added', {
					detail: { timerange: newTimerange, index: newIndex }
				});
				container.dispatchEvent(event);
			}
		},

		deleteTimerange: function(button) {
			let container = button.closest('.em-timeranges-editor').querySelector('.em-timeranges');
			let currentCount = container.querySelectorAll('& > .em-timerange').length;
			let timerange = button.closest('.em-timerange');

			// Only allow deletion if more than one timerange exists
			if ( currentCount > 1 ) {
				// move the delete meta input to the editor for submission
				let nonce = timerange.querySelector('.em-timerange-delete-nonce');
				if ( nonce ) {
					nonce.value = nonce.dataset.nonce;
					container.append(nonce);
				}
				// Remove the timerange
				timerange.remove();
				// Update count
				currentCount--;
				container.dataset.count = container.querySelectorAll('& > .em-timerange').length;
				// Trigger custom event
				let event = new CustomEvent('em_timerange:deleted', {
					detail: { count: currentCount }
				});
				container.dispatchEvent(event);
			}
		},

		handleAdvancedToggle: function(advancedCheckbox) {
			// Find the corresponding all-day checkbox in the same timerange
			let timerange = advancedCheckbox.closest('.em-timerange');
			if (advancedCheckbox.checked) {
				// unset all advanced text inputs
				timerange.querySelectorAll('.em-timerange-timeslots input[type="text"]').forEach( input => { input.value = ''; input.setAttribute('placeholder', '0'); });

				let allDayCheckbox = timerange.querySelector('.em-timerange-allday input[type="checkbox"]');

				if (allDayCheckbox && allDayCheckbox.checked) {
					allDayCheckbox.checked = false;
					// Trigger change event to ensure any other listeners are notified
					allDayCheckbox.dispatchEvent(new Event('change', { bubbles: true }));
				}
			} else {
				// unset all advanced text inputs
				timerange.querySelectorAll('.em-timerange-timeslots input[type="text"]').forEach( input => { input.value = ''; input.setAttribute('placeholder', '0'); });
				this.updatePreview( advancedCheckbox )
			}
		},

		handleAllDayToggle: function(allDayCheckbox) {
			if (allDayCheckbox.checked) {
				// Find the corresponding advanced checkbox in the same timerange
				let timerange = allDayCheckbox.closest('.em-timerange');
				// unset all advanced text inputs
				timerange.querySelectorAll('.em-timerange-timeslots input[type="text"]').forEach( input => { input.value = ''; input.setAttribute('placeholder', '0'); });

				let advancedCheckbox = timerange.querySelector('.em-timerange-timeslots-trigger input[type="checkbox"]');
				if (advancedCheckbox && advancedCheckbox.checked) {
					advancedCheckbox.checked = false;
					// Trigger change event to ensure any other listeners are notified
					advancedCheckbox.dispatchEvent(new Event('change', { bubbles: true }));
				}
			}
		},

		validateTimeRanges: function(changedInput) {
			let container = changedInput.closest('.em-timeranges');
			if (!container) return;

			let allTimeranges = container.querySelectorAll('.em-timerange');
			let hasErrors = false;

			// Clear existing errors
			allTimeranges.forEach(timerange => {
				timerange.querySelectorAll('.em-time-start, .em-time-end').forEach(input => {
					input.classList.remove('error');
				});
			});

			// Get all time ranges
			let timeRanges = [];
			allTimeranges.forEach((timerange, index) => {
				let startInput = timerange.querySelector('.em-time-start');
				let endInput = timerange.querySelector('.em-time-end');

				if (startInput && endInput && startInput.value && endInput.value) {
					timeRanges.push({
						index: index,
						start: this.timeToMinutes(startInput.value),
						end: this.timeToMinutes(endInput.value),
						startInput: startInput,
						endInput: endInput
					});
				}
			});

			// Check for overlaps
			for (let i = 0; i < timeRanges.length; i++) {
				for (let j = i + 1; j < timeRanges.length; j++) {
					let range1 = timeRanges[i];
					let range2 = timeRanges[j];

					// Check if ranges overlap
					if (this.rangesOverlap(range1, range2)) {
						range1.startInput.classList.add('error');
						range1.endInput.classList.add('error');
						range2.startInput.classList.add('error');
						range2.endInput.classList.add('error');
						hasErrors = true;
					}
				}
			}

			return !hasErrors;
		},

		timeToMinutes: function(timeString) {
			if (!timeString) return 0;

			let parts = timeString.toLowerCase().replace(/\s/g, '').match(/^(\d{1,2}):?(\d{2})?\s*(am|pm)?$/);
			if (!parts) return 0;

			let hours = parseInt(parts[1]);
			let minutes = parseInt(parts[2] || 0);
			let ampm = parts[3];

			if (ampm === 'pm' && hours !== 12) hours += 12;
			if (ampm === 'am' && hours === 12) hours = 0;

			return hours * 60 + minutes;
		},

		rangesOverlap: function(range1, range2) {
			// Handle cases where end time is next day (crosses midnight)
			let start1 = range1.start;
			let end1 = range1.end;
			let start2 = range2.start;
			let end2 = range2.end;

			if (end1 < start1) end1 += 24 * 60; // Add 24 hours if crossing midnight
			if (end2 < start2) end2 += 24 * 60; // Add 24 hours if crossing midnight

			return (start1 < end2) && (start2 < end1);
		},

		syncFrequencyWithDuration: function(durationInput) {
			let timerange = durationInput.closest('.em-timerange');
			if ( timerange ) {
				let frequencyInput = timerange.querySelector( '.em-timerange-frequency' );
				// Don't sync if frequency has a value
				if ( frequencyInput ) {
					// Update placeholder with duration value
					frequencyInput.placeholder = durationInput.value || '0';
				}
			}
		},

		syncFrequencyUnit: function(durationUnitSelect) {
			let frequencyUnitSelect = durationUnitSelect.closest('.em-timerange')?.querySelector('.em-timerange-frequency-unit');
			// Don't sync if frequency has a value
			if ( frequencyUnitSelect && !frequencyUnitSelect.previousElementSibling?.value ) {
				// Sync frequency unit with duration unit
				frequencyUnitSelect.value = durationUnitSelect.value;
			}
		},

		updatePreview: function(changedInput) {
			let editor = changedInput.closest('.em-timeranges-editor');
			let previewDiv = editor.querySelector('.em-timeranges-preview');
			let previewContent = previewDiv.querySelector('.em-timeranges-preview-content');

			if ( !previewContent ) return;

			// Get all timeranges and aggregate their times
			let timeranges = editor.querySelector( '.em-timeranges' );
			let allTimeranges = timeranges.querySelectorAll( '.em-timerange' );
			let allGeneratedTimes = [];
			let earliestTime = null;
			let latestTime = null;
			let latestTimeFormatted = null;
			let earliestTimeFormatted = null;
			let allDay = false;

			allTimeranges.forEach( timerange => {
				let startInput = timerange.querySelector( '.em-time-start' );
				let endInput = timerange.querySelector( '.em-time-end' );

				let duration = this.getTimeValue( timerange, 'duration' );
				let buffer = this.getTimeValue( timerange, 'buffer' );
				let frequency = this.getTimeValue( timerange, 'frequency' );

				// Track earliest and latest times
				if ( startInput.value ) {
					let startMinutes = this.timeToMinutes( startInput.value );
					if ( earliestTime === null || startMinutes < earliestTime ) {
						earliestTime = startMinutes;
						earliestTimeFormatted = startInput.value;
						startInput.classList.add('earliest-time');
					} else {
						startInput.classList.remove('earliest-time');
					}
				}
				if ( endInput.value ) {
					let endMinutes = this.timeToMinutes( endInput.value );
					if ( latestTime === null || endMinutes > latestTime ) {
						latestTime = endMinutes;
						latestTimeFormatted = endInput.value;
						endInput.classList.add('latest-time');
					} else {
						endInput.classList.remove('latest-time');
					}
				}
				if ( timerange.querySelector( '.em-timerange-allday input[type="checkbox"]' )?.checked ) {
					allDay = true;
				}

				if ( duration || frequency ) {
					let times = this.generateTimeranges( startInput.value, endInput.value, duration, buffer, frequency );
					allGeneratedTimes.push( ...times );
				} else {
					let startMinutes = this.timeToMinutes( startInput.value );
					allGeneratedTimes.push( startMinutes );
				}
			} );

			// Remove duplicates
			let uniqueTimes = [ ...new Set( allGeneratedTimes ) ];
			// reorder array by value
			uniqueTimes.sort( (a, b) => a - b );
			// get formatted times
			uniqueTimes = uniqueTimes.map( time => this.formatTimeRange( time ) );

			// Add earliest and latest times as data attributes
			timeranges.dataset.start = earliestTimeFormatted;
			timeranges.dataset.end = latestTimeFormatted;
			timeranges.dataset.allday = allDay ? 1:0;

			previewDiv.classList.toggle( 'hidden', uniqueTimes.length === 0 || ( uniqueTimes.length === 1 && allTimeranges.length === 1 ) );
			previewContent.innerHTML = uniqueTimes.join( '' );
			previewDiv.querySelector( '.em-timeranges-preview-count' ).textContent = uniqueTimes.length;
		},

		formatSingleTime: function ( timeString ) {
			if ( !timeString ) return '';
			let minutes = this.timeToMinutes( timeString );
			let hours = Math.floor( minutes / 60 );
			let mins = minutes % 60;

			let ampm = hours >= 12 ? 'PM' : 'AM';
			let displayHours = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours);

			return `${displayHours}:${mins.toString().padStart(2, '0')} ${ampm}`;
		},

		getTimeValue: function(timerange, type) {
			let qtyInput = timerange.querySelector(`.em-timerange-${type}`);
			let unitSelect = timerange.querySelector(`.em-timerange-${type}-unit`);

			if (!qtyInput || !unitSelect || !qtyInput.value) return 0;

			let qty = parseInt(qtyInput.value);
			let unit = unitSelect.value;

			// Convert to minutes
			switch (unit) {
				case 'H': return qty * 60; // Hours to minutes
				case 'I': return qty;      // Minutes
				case 'S': return Math.round(qty / 60); // Seconds to minutes (rounded)
				default: return qty;
			}
		},

		generateTimeranges: function(startTime, endTime, duration, buffer, frequency) {
			let startMinutes = this.timeToMinutes(startTime);
			let endMinutes = this.timeToMinutes(endTime);
			let slots = [];
			// Handle crossing midnight

			if (endMinutes >= startMinutes) {
				let currentStart = startMinutes;

				if ( currentStart === endMinutes ) {
					slots.push( currentStart );
				} else {
					let interval = frequency > 0 ? frequency : duration + ( buffer || 0 );
					// Generate overlapping slots based on frequency
					for ( let i = 0; currentStart <= endMinutes; i++ ) {
						//let slotEnd = i ? null : currentStart + duration;
						slots.push( currentStart );
						currentStart += interval;
					}
				}
			}

			return slots;
		},

		formatTimeRange: function( startMinutes, endMinutes = null ) {
			let formatTime = (minutes) => {
				// Handle crossing midnight
				minutes = minutes % (24 * 60);
				if (minutes < 0) minutes += 24 * 60;

				let hours = Math.floor(minutes / 60);
				let mins = minutes % 60;

				let ampm = hours >= 12 ? 'PM' : 'AM';
				let displayHours = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours);

				return `${displayHours}:${mins.toString().padStart(2, '0')} ${ampm}`;
			};

			let result = `<span class="em-timeranges-preview-time">${formatTime(startMinutes)}`;
			if ( endMinutes ) {
				result += ` - ${formatTime(endMinutes)}`;
			}
			result += '</span>'
			return result;
		},

		togglePreview: function(toggle) {
			let previewDiv = toggle.closest('.em-timeranges-preview');
			let previewContent = previewDiv.querySelector('.em-timeranges-preview-content');
			let chevron = toggle.querySelector('.em-icon-chevron-down, .em-icon-chevron-up');

			if (previewContent.classList.contains('hidden')) {
				previewContent.classList.remove('hidden');
				if (chevron) chevron.className = chevron.className.replace('chevron-down', 'chevron-up');

				// Update preview when expanding
				let container = toggle.closest('.em-timeranges-editor');
				if (container) {
					// Use any timerange input to trigger update, since we now aggregate all
					let anyInput = container.querySelector('.em-timerange .em-time-start');
					if (anyInput) {
						this.updatePreview(anyInput);
					}
				}
			} else {
				previewContent.classList.add('hidden');
				if (chevron) chevron.className = chevron.className.replace('chevron-up', 'chevron-down');
			}
		},
	};

	document.addEventListener('em_event_editor_ready', () => {
		window.EM_TimerangesEditor = new TimerangesEditor();
	});
	// add listener for ui setup
	document.addEventListener('em_setup_ui_elements', function (e) {
		if (e.detail.container !== document) {
			window.EM_TimerangesEditor = new TimerangesEditor( e.detail.container );
		}
	});

})();

document.addEventListener('em_event_editor_ready', function() {

	// load event recurrence data
	document.querySelectorAll('.em-recurrence-sets').forEach( function( recurrenceSets ) {
		recurrenceSets.querySelector('.em-recurrence-set[data-type="include"]:first-child').dataset.primary = "1"; // set primary recurrence flag for JS (CSS uses selector instead)
		document.dispatchEvent( new CustomEvent('em_event_editor_recurrences', { detail: { recurrenceSets : recurrenceSets } } ) );
	});

	// Event Status Warning
	document.querySelectorAll('select[name="event_active_status"]').forEach(select => {
		select.addEventListener('change', function (event) {
			if ( select.value === '0' && !confirm( EM.event_cancellations.warning.replace(/\\n/g, '\n') ) ) { 
				event.preventDefault();
			}
		});
	});

	// disable recurrence meta box selection since we must always show it
	document.getElementById('em-event-recurring-hide')?.setAttribute('disabled', '');

	// Handle the recurring/repeating event selection and initialize showing/hiding relevant recurring sections
	document.querySelectorAll( '.event_type' ).forEach( eventType => {
		const form = eventType.closest( 'form' );
		eventType.addEventListener( 'change', function () {
			// When set to recurring or repeating, sync the main event data to primary recurrence set
			if ( handleRecurring() ) {
				let eventDateTimes = form.querySelector('.event-form-when');
				let eventDatePicker = eventDateTimes.querySelector('.em-datepicker.em-event-dates');
				let selectedDates;
				if ( eventDatePicker.classList.contains('em-datepicker-until') ) {
					selectedDates = [
						eventDatePicker.querySelector('.em-date-input-start.flatpickr-input')._flatpickr.selectedDates[0],
						eventDatePicker.querySelector('.em-date-input-end.flatpickr-input')._flatpickr.selectedDates[0]
					];
				} else {
					selectedDates = eventDatePicker.querySelector('.em-date-input.flatpickr-input')._flatpickr.selectedDates;
				}
				let eventTimeRange = eventDateTimes.querySelector('.em-time-range');
				// we need to get jQuery elements to handle the timepicker
				let eventStartTime = eventTimeRange.querySelector('.em-time-input.em-time-start');
				let eventEndTime = eventTimeRange.querySelector('.em-time-input.em-time-end');
				let eventAllDay = eventTimeRange.querySelector('input.em-time-all-day');
				let timezone = eventDateTimes.querySelector('select.event_timezone')?.value;
				let eventStatus = eventDateTimes.querySelector('select.event_active_status')?.value;
				let setDateTimes = false;

				// here, we're copying over all the regular event date/time/timezone data into the primary recurrence so there's some UI continuity in case user started adding dates before selecting recurring type
				let recurrenceSet = form.querySelector('.em-recurrence-set[data-primary="1"]');
				if ( recurrenceSet ) {
					// copy over the times first - if not the datepicker triggers a change loop and sets the 12am times back onto the event
					let timeRange = recurrenceSet.querySelector( '.em-recurrence-advanced .em-time-range' );
					if ( timeRange ) {
						let recurrenceStartTime = timeRange.querySelector( '.em-time-input.em-time-start' );
						let recurrenceEndTime = timeRange.querySelector( '.em-time-input.em-time-end' );
						let recurrenceAllDay = timeRange.querySelector( '.em-time-all-day' );
						if ( recurrenceStartTime ) {
							recurrenceStartTime.value = eventStartTime?.value;
						}
						if ( recurrenceEndTime ) {
							recurrenceEndTime.value = eventEndTime?.value;
						}
						if ( recurrenceAllDay ) {
							recurrenceAllDay.checked = eventAllDay?.checked;
						}
					}
					// get equivalent datepicker, timepicker and timezone
					if ( recurrenceSet.querySelector('select.recurrence_freq')?.value === 'on' ) {
						// we set the selected dates to those we selected in the datepicker, for now
						let recurrenceDatePicker = recurrenceSet.querySelector('.em-on-selector .em-date-input.flatpickr-input')?._flatpickr
						if ( !recurrenceDatePicker?.selectedDates ) {
							recurrenceDatePicker?.setDate( selectedDates, true );
							setDateTimes = true; // setting date triggers the setDateTimes event already
						}
					} else {
						// get the regular date range
						recurrenceSet.querySelectorAll('.em-recurrence-advanced .em-datepicker .em-date-input.flatpickr-input').forEach( input => {
							// add if we don't have dates set already
							if ( !input._flatpickr?.selectedDates.length ) {
								if (input.closest('.em-datepicker-until')) {
									input._flatpickr.setDate( input.classList.contains('em-date-input-start') ? selectedDates[0] : selectedDates[1], true );
									setDateTimes = true; // setting date triggers the setDateTimes event already
								} else if (input.closest('.em-datepicker-range')) {
									input._flatpickr.setDate( selectedDates, true );
									setDateTimes = true; // setting date triggers the setDateTimes event already
								}
							}
						});
					}
					// copy over the timezone
					[ 'select.recurrence_timezone', 'select.recurrence_status' ].forEach( selector => {
						let select = recurrenceSet.querySelector( selector );
						let value = selector === 'select.recurrence_timezone' ? timezone : eventStatus;
						if ( value && select ) {
							if ( select.selectize ) {
								select.selectize.setValue( value, true );
							} else {
								select.value = value;
							}
						}
					});
					// trigger a recurrence summary refresh
					if ( !setDateTimes ) {
						recurrenceSet.closest( '.em-recurrence-sets' ).dispatchEvent( new CustomEvent( 'setDateTimes' ) );
					}
				}
			}
		});
		/**
		 * Handles toggling of recurring event settings for a form element.
		 * Determines if the event is recurring based on the specified event type and updates the form element's classes accordingly.
		 */
		let handleRecurring = function() {
			// check now if it's recurring as well
			let isRecurring = false;
			if ( form ) {
				// it's recurring/repeating if the checkbox is checked, we add is-recurring regardless
				isRecurring = eventType.type === 'checkbox' ? eventType.checked : eventType.value !== 'single';
				form.classList.toggle( 'em-is-recurring', isRecurring );
				if ( eventType.type === 'checkbox' ) {
					// checkboxes are now only for recurring
					form.classList.toggle( 'em-type-recurring', eventType.checked );
				}
				// remove the em-type- classes and add the current selected type, defaulting to single
				form.classList.remove( ...[ ...form.classList ].filter( className => className.startsWith( 'em-type-' ) ) );
				form.classList.add( 'em-type-' + eventType.value );
			}
			return isRecurring;
		}
		handleRecurring();
	});

	document.dispatchEvent( new CustomEvent('em_event_editor_loaded') );
});

document.addEventListener('em_event_editor_recurrences', function( e ) {
	let recurrenceSets = e.detail.recurrenceSets;

	// shortcuts for functions in recurrences/ui-functions.js
	let updateRecurrenceSummary = ( recurrenceSet ) => recurrenceSet.dispatchEvent( new CustomEvent('updateRecurrenceSummary', { bubbles: true }) );
	let updateSetsCount = () => recurrenceSets.dispatchEvent( new CustomEvent('updateSetsCount') );
	let updateRecurrenceOrder = () => recurrenceSets.dispatchEvent( new CustomEvent('updateRecurrenceOrder') );


	/* ------------------------------------------------------------
	 Add/Remove Recurrnces
	 ------------------------------------------------------------ */

	// ADD NEW RECURRENCE RULE
	let addRecurrence = function ( recurrenceType ) {
		let recurrenceTypeSets = recurrenceSets.querySelector('.em-recurrence-type-' + recurrenceType + ' .em-recurrence-type-sets');

		// Count existing recurrence sets to determine the new index.
		let index = recurrenceTypeSets.querySelectorAll('.em-recurrence-set').length + 1;

		// Copy template HTML
		let templateHtml = recurrenceSets.querySelector('.em-recurrence-set-template')?.innerHTML;
		let recurrenceSet;
		if ( templateHtml === null ) {
			// legacy template which didn't use the template element to enclose the recurrence set type
			let recurrenceSet = recurrenceSets.querySelector('.em-recurrence-set-template').cloneNode(true);
			recurrenceSet.classList.remove('em-recurrence-set-template', 'hidden');
			recurrenceSet.innerHTML = recurrenceSet.innerHTML.replace(/T%/g, `${recurrenceType}`).replace(/N%/g, `${index}`);
		} else {
			// create a blank div which we'll add classes etc. to
			recurrenceSet = document.createElement('div');
			// Replace all occurrences of "[N%]" with the new index.
			recurrenceSet.innerHTML = templateHtml.replace(/T%/g, `${recurrenceType}`).replace(/N%/g, `${index}`);
		}

		// Remove the 'hidden' class and template-specific class; add the active class.
		recurrenceSet.classList.add('em-recurrence-set', 'new-recurrence-set');
		recurrenceSet.querySelector('.em-recurrence-set-type').value = recurrenceType;
		recurrenceSet.dataset.type = recurrenceType;
		recurrenceSet.dataset.index = index;

		// remove include/exclude specific elements depending on type
		if ( recurrenceType === 'exclude' ) {
			recurrenceSet.querySelectorAll('.em-recurrence-advanced .only-include-type').forEach(el => el.remove() );
		}
		if ( recurrenceType === 'include' ) {
			recurrenceSet.querySelectorAll('.em-recurrence-advanced .only-exclude-type').forEach(el => el.remove() );
		}

		// anything more before we append
		let result = { recurrenceSet: recurrenceSet, index: index, success: true };
		recurrenceTypeSets.dispatchEvent( new CustomEvent('beforeAddRecurrence', { detail: result  }) );

		if ( result.success ) {
			// Insert the new recurrence set above the template.
			recurrenceTypeSets.append(recurrenceSet);
			em_setup_ui_elements( recurrenceSet );
			// run all checks
			updateSetsCount();
			emRecurrenceEditor.updateIntervalDescriptor( recurrenceSet );
			emRecurrenceEditor.updateIntervalSelectors( recurrenceSet );
			emRecurrenceEditor.updateDurationDescriptor( recurrenceSet );
			updateRecurrenceSummary( recurrenceSet );
		}

		return recurrenceSet;
	}
	// add include recurrence
	recurrenceSets.querySelectorAll('.em-add-recurrence-set[data-type="include"]').forEach( function( addButton ){
		addButton.addEventListener( 'click', () => addRecurrence('include') );
	});
	// set up listner to add recurrences, exclude and include, the exclude trigger is in reschedule.js
	recurrenceSets.addEventListener( 'addRecurrence', ( e ) => addRecurrence( e.detail.type ) );

	// REMOVE A RECURRENCE RULE
	recurrenceSets.addEventListener('click', function ( e ) {
		if ( e.target.matches('.em-recurrence-set-action-remove') ) {
			let removeButton = e.target;
			e.preventDefault();

			// Locate the recurrence set container for this remove action.
			let recurrenceSet = removeButton.closest('.em-recurrence-set');

			// Find the hidden delete field (an input whose name contains "[delete]").
			let setId = recurrenceSet.querySelector('input.em-recurrence-set-id');
			let deleteField = recurrenceSet.querySelector('input.em-recurrence-set-delete-field');
			if (setId && deleteField) {
				// Transfer the data-nonce value to the hidden input's value.
				deleteField.value = deleteField.getAttribute('data-nonce');

				// Move the delete field out of the recurrence set into the recurrence container.
				recurrenceSets.appendChild(deleteField);
				recurrenceSets.appendChild(setId);
			}

			// Remove the entire recurrence set from the DOM.
			recurrenceSet.remove();
			// redo all checks
			updateSetsCount();
			updateRecurrenceOrder();
		}
	});
});

document.addEventListener('em_event_editor_recurrences', function( e ) {
	let recurrenceSets = e.detail.recurrenceSets;
	recurrenceSets.querySelectorAll('.em-recurrence-type-sets').forEach( function( recurrenceTypeSets ) {
		let draggedElem = null;
		let placeholder = null;
		let offsetX = 0, offsetY = 0;
		let originalParent = null, originalNextSibling = null;

		recurrenceTypeSets.addEventListener('mousedown', function(e) {
			recurrenceTypeSets.querySelectorAll('.em-recurrence-set:not(:first-child)').forEach( (recurrenceSet) => recurrenceSet.classList.add('reordering') );
			const handle = e.target.closest('.em-recurrence-set-action-order');
			if (!handle) return;
			const set = handle.closest('.em-recurrence-set');
			if (!set) return;
			e.preventDefault();

			// Store original parent and next sibling for later restoration
			originalParent = set.parentNode;
			originalNextSibling = set.nextSibling;

			const rect = set.getBoundingClientRect();
			// Calculate offsets using page coordinates
			offsetX = e.pageX - (rect.left + window.pageXOffset);
			offsetY = e.pageY - (rect.top + window.pageYOffset);

			// wrap set into div for styling preservation via .em
			draggedElem = document.createElement('div');
			draggedElem.classList.add('em', 'em-recurrence-sets');
			draggedElem.append(set);

			// Create a placeholder with the same dimensions
			placeholder = document.createElement('div');
			placeholder.classList.add('drop-placeholder');
			placeholder.style.height = rect.height + 'px';
			placeholder.style.width = rect.width + 'px';
			originalParent.insertBefore(placeholder, originalNextSibling);

			// Move the dragged element to document.body so its absolute positioning is relative to the document
			document.body.appendChild(draggedElem);
			draggedElem.style.position = 'absolute';
			draggedElem.style.width = "100%";
			draggedElem.style.left = (rect.left + window.pageXOffset) + 'px';
			draggedElem.style.top = (rect.top + window.pageYOffset) + 'px';
			draggedElem.style.zIndex = '1000';

			document.addEventListener('mousemove', onMouseMove);
			document.addEventListener('mouseup', onMouseUp);
		});

		function onMouseMove(e) {
			if (!draggedElem) return;
			// Update dragged element's position so the cursor stays at the same offset
			draggedElem.style.left = (e.pageX - offsetX) + 'px';
			draggedElem.style.top = (e.pageY - offsetY) + 'px';

			// Determine where to place the placeholder in the container
			let sets = Array.from(recurrenceTypeSets.querySelectorAll('.em-recurrence-set'));
			let inserted = false;
			for (let set of sets) {
				let rect = set.getBoundingClientRect();
				let setTop = rect.top + window.pageYOffset;
				if (e.pageY < setTop + rect.height / 2) {
					recurrenceTypeSets.insertBefore(placeholder, set);
					inserted = true;
					break;
				}
			}
			if (!inserted) {
				recurrenceTypeSets.appendChild(placeholder);
			}
		}

		function onMouseUp(e) {
			document.removeEventListener('mousemove', onMouseMove);
			document.removeEventListener('mouseup', onMouseUp);

			// Reinsert the dragged element back into its original container before the placeholder
			recurrenceTypeSets.insertBefore(draggedElem.firstElementChild, placeholder);
			draggedElem.remove();
			placeholder.remove();
			placeholder = null;
			draggedElem = null;
			recurrenceTypeSets.querySelectorAll('.em-recurrence-set').forEach( function( recurrenceSet ) {
				if ( recurrenceSet.matches(':first-child') ) {
					recurrenceSet.dataset.primary = '1';
					recurrenceSet.querySelectorAll('.em-time-all-day').forEach( el => { el.indeterminate = false; } );
				} else {
					delete recurrenceSet.dataset.primary;
				}
				recurrenceSet.classList.remove('reordering')
			});
			recurrenceSets.dispatchEvent( new CustomEvent('updateRecurrenceOrder') ); // we could also bubble this on recurrenceTypeSet
		}
	});
});

document.addEventListener('em_event_editor_recurrences', function( e ) {
	let recurrenceSets = e.detail.recurrenceSets;

	// exclude references used throughout here for rescheduling logic
	let recurrenceExcludeSets = recurrenceSets.querySelector('.em-recurrence-type-exclude');
	let recurrenceExcludeModal = recurrenceExcludeSets.querySelector('& > .em-recurrence-set-reshedule-modal');
	let rescheduleExcludeAction = recurrenceExcludeModal.querySelector('.recurrence-reschedule-action');

	/* ------------------------------------------------------------
	 UNDO FUNCTIONALITY
	 ------------------------------------------------------------ */

	// trigger the undo event when clicked, whether in scope of a recurrent set, set type or all sets
	recurrenceSets.querySelectorAll('button.undo').forEach( function( button ) {
		button.addEventListener('click', () => {
			button.closest('.em-recurrence-set, .em-recurrence-type, .em-recurrence-sets')?.dispatchEvent( new CustomEvent( 'undo', { detail: { button: button }, bubbles: true } ) )
		});
	});
	// listen for an undo event to a single set, set types, or all sets and undo the relevant recurrences.
	recurrenceSets.addEventListener('undo', function( e ) {
		e.stopPropagation();
		let recurrences;
		if ( e.target.matches('.em-recurrence-set') ) {
			recurrences = [e.target];
		} else if ( e.target.matches('.em-recurrence-type') ) {
			recurrences = e.target.querySelectorAll('.em-recurrence-set');
		} else {
			recurrences = recurrenceSets.querySelectorAll('.em-recurrence-set');
		}
		recurrences.forEach( function( recurrenceSet ) {
			// set back previous values and re-disable previously disabled elements
			recurrenceSet.querySelectorAll('[data-undo]:not([type="checkbox"]):not(.selectized)').forEach( input => {
				input.value = input.dataset.undo;
				input.readonly = false;
				input.disabled = false;
				if ( input.classList.contains('em-recurrence-frequency') ) {
					input.dispatchEvent( new Event('change', { bubbles: false }) );
				}
			});
			recurrenceSet.querySelectorAll('input[data-undo][type="checkbox"]').forEach( input => { input.checked = input.dataset.undo === '1'; } );
			//recurrenceSet.querySelectorAll('.em-time-input').forEach( input => { jQuery(input).em_timepicker('setTime', new Date('2020-01-01 ' + input.dataset.undo)) } );
			recurrenceSet.querySelectorAll('.em-datepicker').forEach( function( datePicker ) {
				if ( datePicker.closest('.reschedulable') ) {
					datePicker.querySelectorAll('input.em-date-input').forEach( el => { el.disabled = true; el.value = ''; } );
					datePicker.querySelectorAll('.em-date-input.disabled, .em-datepicker-dates').forEach( el => { el.classList.add('disabled') } );
				}
			});
			em_setup_datepicker_dates( recurrenceSet );
			recurrenceSet.querySelectorAll('select.em-selectize.selectized').forEach( el => {
				if ( el.dataset.undo !== undefined ) {
					el.selectize?.setValue( el.dataset.undo.split(',') );
				}
				// disable rescheduling datepickers
				if ( el.closest('.reschedulable') ) {
					el.selectize?.disable();
				}
			});
			// undo the timepicker, by replacing the stored template
			let timeRangeEditor = recurrenceSet.querySelector('.em-recurrence-timeranges');
			if ( timeRangeEditor ) {
				timeRangeEditor.querySelector('.recurrence-timeranges-editor').innerHTML = timeRangeEditor.querySelector('.recurrence-timeranges-undo').innerHTML;
			}
			// disable other rechedulable items
			recurrenceSet.querySelectorAll('.reschedulable [name]:not(.selectized), .reschedulable button').forEach( input => { input.disabled = true; } );
			// disable the nonces to reschedule this button type
			recurrenceSet.querySelector( 'input[type="hidden"][data-nonce]' ).disabled = true;
			// re-enable the reschedule buttons and set flag to false
			recurrenceSet.querySelectorAll('.reschedule-trigger').forEach( button => { button.disabled = false } );
			delete recurrenceSet.dataset.rescheduled;
			// unset modified flag for advanced icon
			recurrenceSet.classList.remove('advanced-modified', 'advanced-modified-dates');
			// update descriptors and selectors
			emRecurrenceEditor.updateIntervalDescriptor( recurrenceSet );
			emRecurrenceEditor.updateIntervalSelectors( recurrenceSet );
			emRecurrenceEditor.updateDurationDescriptor( recurrenceSet );
		});
		// exclude logic
		if ( e.target.matches('.em-recurrence-type-exclude') ) {
			recurrenceExcludeSets.querySelectorAll('.em-recurrence-set.new-recurrence-set').forEach( recurrenceSet => recurrenceSet.querySelector('.em-recurrence-set-action-remove')?.click() );
			recurrenceExcludeModal.querySelector('.em-modal-content')?.append( rescheduleExcludeAction );
			rescheduleExcludeAction.querySelector('[data-nonce]').disabled = true;
		} else if ( e.target.matches('.em-recurrence-set[data-type="exclude"]') ) {
			// check if there are more reschedulable sets, if not then remove the reshedulable notice
			if ( e.target.closest('.em-recurrence-type-exclude').querySelectorAll('.em-recurrence-set[data-rescheduled]').length === 0 ) {
				recurrenceExcludeModal.querySelector('.em-modal-content')?.append( rescheduleExcludeAction );
			}
		}
		recurrenceSets.dispatchEvent( new CustomEvent('updateSetsCount') );
		recurrenceSets.dispatchEvent( new CustomEvent('setAdvancedDefaults', { bubbles: true }) );
	});


	/* ------------------------------------------------------------
	 RESCHEDULING FUNCTIONALITY
	 ------------------------------------------------------------ */

	// RESCHEDULING BUTTONS
	// recurrence rescheduling buttons & modals - we don't need to listen to delegated events since this only applies to previously-created recurrences
	recurrenceSets.querySelectorAll('.em-recurrence-set').forEach( function( recurrenceSet ){
		delete recurrenceSet.dataset.rescheduled;
		let modal = recurrenceSet.dataset.type === 'exclude' ? recurrenceExcludeModal : recurrenceSet.querySelector('.em-recurrence-set-reshedule-modal');

		// trigger reschedule
		recurrenceSet.querySelectorAll('button.reschedule-trigger').forEach( function( button ) {
			button.addEventListener('click', function( e ){
				if ( recurrenceSet.dataset.rescheduled !== undefined ) {
					unlockReschedule( button );
				} else {
					modal.rescheduleButton = button;
					modal.classList.toggle( 'primary-recurrence', recurrenceSet.dataset.primary );
					openModal( modal );
				}
			});
		});

		if ( recurrenceSet.dataset.type === 'include' ) {
			// modal actions confirming reschedule and re-appending modal upon close
			modal.addEventListener( 'em_modal_close', function() {
				// re-append modal on close for submission
				recurrenceSet.append(modal);
				delete modal.rescheduleButton;
			});
			modal.querySelector('button.reschedule-cancel')?.addEventListener( 'click', () => closeModal(modal) );
			modal.querySelector('button.reschedule-confirm')?.addEventListener( 'click', function( e ) {
				unlockReschedule( modal.rescheduleButton );
				// move the reschedule action option to the recurrence set to make it visible
				recurrenceSet.querySelector('.em-recurrence-pattern')?.prepend( modal.querySelector('.recurrence-reschedule-action') );
				// close modal and mark this as rescheduled
				recurrenceSet.dataset.rescheduled = '1';
				closeModal(modal);
			});
			recurrenceSet.addEventListener('undo', function(){
				modal.querySelector('.em-modal-content')?.append( recurrenceSet.querySelector('.recurrence-reschedule-action') );
			});
		}
	});
	// unlock rescheduling inputs and store undoable values
	let unlockReschedule = function ( rescheduleButton ) {
		// remove disabled property from inputs contained in button container
		let reschedulable = rescheduleButton.closest('.reschedulable');
		reschedulable.querySelectorAll('[disabled]').forEach( el => { el.disabled = false } ); // TODO the label of ON datepicker not enabling
		reschedulable.querySelectorAll('.disabled').forEach( el => { el.classList.remove('disabled') } ); // TODO the label of ON datepicker not enabling
		// save the current date/day selection settings so we can re-establish it
		reschedulable.querySelectorAll('.em-datepicker-data input').forEach( input => { input.dataset.undo = input.value; } );
		reschedulable.querySelectorAll('select.em-selectize.selectized').forEach( function( el ) {
			el.selectize?.enable();
			let days = el.selectize?.getValue();
			if ( days ) {
				el.dataset.undo = days.join();
			}
		});
		// enable the nonce to reschedule this button type
		reschedulable.closest('.em-recurrence-set').querySelector( rescheduleButton.dataset.nonce ).disabled = false;
		reschedulable.querySelectorAll('.reschedule-trigger').forEach( button => { button.disabled = true } );
	};


	// EXCLUDE RESCHEDULING
	// add warning for currently created recurrences and adding a new exclusion
	recurrenceSets.querySelectorAll('.em-add-recurrence-set[data-type="exclude"]').forEach( function( addButton ) {
		addButton.addEventListener('click', function (e) {
			if ( recurrenceExcludeSets.querySelectorAll('[data-rescheduled]').length > 0 || !recurrenceSets.dataset.event_id ) {
				let event = new CustomEvent('addRecurrence', { bubbles: true, detail: { type : 'exclude' } });
				recurrenceSets.dispatchEvent( event );
			} else {
				openModal( recurrenceExcludeModal );
			}
		});
	});
	// modal actions - confirming reschedule and re-appending modal upon close
	recurrenceExcludeModal.addEventListener( 'em_modal_close', function() {
		// re-append modal on close for submission
		recurrenceExcludeSets.append(recurrenceExcludeModal);
		delete recurrenceExcludeModal.rescheduleButton;
	});
	recurrenceExcludeModal.querySelector('button.reschedule-cancel')?.addEventListener( 'click', () => closeModal(recurrenceExcludeModal) );
	recurrenceExcludeModal.querySelector('button.reschedule-confirm')?.addEventListener( 'click', function(e ) {
		let recurrenceSet;
		if ( recurrenceExcludeModal.rescheduleButton ) {
			unlockReschedule( recurrenceExcludeModal.rescheduleButton  )
			recurrenceSet = recurrenceExcludeModal.rescheduleButton.closest('.em-recurrence-set');
		} else {
			// pass a detail so it is populated by reference
			let recurrenceTypeSets = recurrenceSets.querySelector('.em-recurrence-type-exclude');
			recurrenceTypeSets?.dispatchEvent( new CustomEvent('addRecurrence', { bubbles: true }) );
			recurrenceSet = recurrenceTypeSets?.querySelector('.em-recurrence-set:last-child');
		}
		// mark rescheduled, even if it's new because it essentially can reschedule previously created recurrences by negating them
		if ( recurrenceSet ) {
			recurrenceSet.dataset.rescheduled = '1';
		}
		// move the reschedule action option to the recurrence set to make it visible
		recurrenceExcludeSets.firstElementChild.after( rescheduleExcludeAction );
		rescheduleExcludeAction.querySelector('[data-nonce]').disabled = false;
		// close modeal and mark this as rescheduled
		closeModal(recurrenceExcludeModal);
	});

	// move the cancel warning back to reschedule modal if removing an event results in no exclusions
	recurrenceSets.addEventListener('updateSetsCount', function() {
		if ( recurrenceExcludeSets.dataset.count === '0' ) {
			recurrenceExcludeModal.querySelector('.em-modal-content')?.append( rescheduleExcludeAction );
		}
	});
});

// This file deals with dates and times of the main event, determining the overall recurrence duration in dates and times of the recurring event for display purposes.
document.addEventListener('em_event_editor_recurrences', function( e ) {
	let recurrenceSets = e.detail.recurrenceSets;

	document.addEventListener('em_luxon_ready', function(){
		// Sets the overal event dates, times and timezone based on the earliest and latest recurrence date/time, and the primary recurrence timezone.
		// PHP will always handle the real date ranges.
		// This is somewhat redundant and needs a review. The reason is because this doesn't account for timezones, for example we could have an earlier string date/time than another, but the later one is in a TZ that's makes it earlier in UTC time.
		// For this reason, we need a library like Luxon to accurately calculate and use this for displaying estimated start/end date/times.
		// TODO add TimeZone-aware libary to calculate real start/end dates and provide an accurate recurrence summary for all recurrences grouped together.
		
		recurrenceSets.addEventListener('setDateTimes', function() {
			let eventDateTimes = recurrenceSets.closest('form').querySelector('.event-form-when');
			if ( eventDateTimes ) {
				// COLLECT ALL DATES FROM RECURRENCE SETS, update earliest/latest date as we go
				/** @type {luxon.DateTime} */
				let startDateTime;
				/** @type {luxon.DateTime} */
				let endDateTime;
				/** @type {Element} */
				let eventDatePicker = eventDateTimes.querySelector('.em-datepicker.em-event-dates');
				/** @type {Element} */
				let eventTimeRange = eventDateTimes.querySelector('.em-timeranges');
				// we need to get jQuery elements to handle the timepicker
				/** @type {jQuery} */
				let $eventStartTime = jQuery(eventTimeRange.querySelector('.em-time-input.em-time-start'));
				/** @type {jQuery} */
				let $eventEndTime = jQuery(eventTimeRange.querySelector('.em-time-input.em-time-end'));
				let DateTime = luxon.DateTime;
				let timezone = recurrenceSets.querySelector('.em-recurrence-set[data-primary="1"] select.recurrence_timezone')?.value;
				// Replace .5 with :30 in timezone string (for half-hour offsets like UTC+5.5)
				timezone = timezone?.replace(/\.5/g, ':30') || timezone;
				
				// get primary values for other recurrences
				let defaultStartTimeSeconds, defaultEndTimeSeconds;
				let defaultStartDate, defaultEndDate;
				
				// Track if all recurrence sets are marked as all-day
				let allRecurrencesAllDay = true;
				// Track all unique timezones used across recurrence sets
				let uniqueTimezones = new Set();

				// go through each recurring set to get the start time and date
				recurrenceSets.querySelectorAll('.em-recurrence-type-include .em-recurrence-set').forEach( recurrenceSet => {
					let recurrenceTimezone = recurrenceSet.querySelector('.em-recurrence-timezone select')?.value || timezone;
					// Replace .5 with :30 in timezone string (for half-hour offsets like UTC+5.5)
					recurrenceTimezone = recurrenceTimezone?.replace(/\.5/g, ':30') || recurrenceTimezone;
					// Add the timezone to our set of unique timezones
					if (recurrenceTimezone) {
						uniqueTimezones.add(recurrenceTimezone);
					}
					/** @type {luxon.DateTime} */
					let recurrenceStart;
					/** @type {luxon.DateTime} */
					let	recurrenceEnd;
					// build the start and end datetime from the recurrence set, starting by getting the date and creating luxon.DateTime objects
					if ( recurrenceSet.querySelector('select.recurrence_freq')?.value === 'on' ) {
						// get the ON dates rather than the date range
						let selectedDates = recurrenceSet.querySelector('.em-on-selector .em-date-input')?._flatpickr?.selectedDates;
						if ( selectedDates ) {
							selectedDates.sort(function(a, b) { return a - b; });
							// get the first and last dates from the selected dates
							recurrenceStart = DateTime.fromJSDate( selectedDates[0] );
							recurrenceEnd = DateTime.fromJSDate( selectedDates[selectedDates.length - 1] );
						}
					} else {
						// get the regular date range
						recurrenceSet.querySelectorAll('.em-recurrence-advanced .em-datepicker .em-date-input.flatpickr-input').forEach( input => {
							if ( input._flatpickr?.selectedDates.length ) {
								if (input.closest('.em-datepicker-until')) {
									if (input.classList.contains('em-date-input-start')) {
										recurrenceStart = DateTime.fromJSDate( input._flatpickr.selectedDates[0] );
									} else if (input.classList.contains('em-date-input-end')) {
										recurrenceEnd = DateTime.fromJSDate( input._flatpickr.selectedDates[0] );
									}
								} else if (input.closest('.em-datepicker-range')) {
									recurrenceStart = DateTime.fromJSDate( input._flatpickr.selectedDates[0] );
									if ( input._flatpickr.selectedDates.length >= 2 ) {
										recurrenceEnd = DateTime.fromJSDate( input._flatpickr.selectedDates[1] );
									} else {
										recurrenceEnd = recurrenceStart;
									}
								}
								defaultStartDate ??= recurrenceStart;
								defaultEndDate ??= recurrenceEnd;
							}
						});
					}
					// make sure we have recurrence dates and the timezones are correctly set
					recurrenceStart ??= defaultStartDate;
					recurrenceEnd ??= defaultEndDate || defaultStartDate;
					recurrenceStart = recurrenceStart?.setZone( recurrenceTimezone, { keepLocalTime: true } );
					recurrenceEnd = recurrenceEnd?.setZone( recurrenceTimezone, { keepLocalTime: true } );

					// proceed if we have start/end dates
					if ( recurrenceStart && recurrenceEnd ) {
						// add the time to the start/end dates
						let timeRange = recurrenceSet.querySelector( '.em-timeranges' );
						if ( timeRange && ( recurrenceSet.dataset.primary || recurrenceSet.querySelector( '.recurrences-timeranges-default-trigger')?.checked ) ) {
							let $recurrenceStartTime = jQuery( timeRange.querySelector( '.em-time-input.earliest-time' ) );
							let $recurrenceEndTime = jQuery( timeRange.querySelector( '.em-time-input.latest-time' ) );

							// Get times based on whether all day or normal
							if ( timeRange.dataset.allday === "1" ) {
								recurrenceEnd = recurrenceEnd.endOf( 'day' );
								// set default start/end times first time for the timepicker for future recurrences
								defaultStartTimeSeconds |= 0;
								defaultEndTimeSeconds |= 86399; // 23:59:59
							} else {
								allRecurrencesAllDay = false;
								let secondsFromMidnight = $recurrenceStartTime.em_timepicker( 'getSecondsFromMidnight' );
								if ( $recurrenceStartTime.val() ) {
									recurrenceStart = recurrenceStart.plus( { seconds: secondsFromMidnight } );
								} else {
									recurrenceStart = recurrenceStart.plus( { seconds: defaultStartTimeSeconds || 0 } );
								}
								if ( $recurrenceEndTime.val() ) {
									let secondsFromMidnight = $recurrenceEndTime.em_timepicker( 'getSecondsFromMidnight' );
									recurrenceEnd = recurrenceEnd.plus( { seconds: secondsFromMidnight } );
								} else {
									recurrenceEnd = recurrenceEnd.plus( { seconds: defaultEndTimeSeconds || 0 } );
								}
								// set default start/end times first time for the timepicker for future recurrences
								defaultStartTimeSeconds |= $recurrenceStartTime.em_timepicker( 'getSecondsFromMidnight' );
								defaultEndTimeSeconds |= $recurrenceEndTime.em_timepicker( 'getSecondsFromMidnight' );
							}

							// account for duration
							let duration = recurrenceSet.querySelector( '.recurrence_duration' )?.value;
							if ( duration ) {
								recurrenceEnd = recurrenceEnd.plus( { days: duration } );
							}
							// Now we have the luxon.DateTime dates/times in correct timezone, we can compare them accurately
							if ( recurrenceStart.isValid && ( !startDateTime || recurrenceStart < startDateTime ) ) {
								startDateTime = recurrenceStart.setZone( timezone );
							}
							if ( recurrenceEnd.isValid && (!endDateTime || recurrenceEnd > endDateTime) ) {
								endDateTime = recurrenceEnd.setZone( timezone );
							}
						}
					}
				});
				if ( startDateTime?.isValid && endDateTime?.isValid ) {

					// set the datepicker and timepickers of the main event form
					let startDate = startDateTime.setZone( "system", { keepLocalTime: true } ).toJSDate();
					let endDate = endDateTime.setZone( "system", { keepLocalTime: true } ).toJSDate();
					if ( eventDatePicker.classList.contains('em-datepicker-range') ) {
						// set the date range with both dates, even if endDate didn't change
						eventDatePicker.querySelector( '.em-date-input.flatpickr-input' )?._flatpickr?.setDate( [ startDate, endDate ], true );
					} else {
						eventDatePicker.querySelector( '.em-date-input-start.flatpickr-input' )?._flatpickr?.setDate( startDate, true );
						eventDatePicker.querySelector( '.em-date-input-end.flatpickr-input' )?._flatpickr?.setDate( endDate, true );
					}
					$eventStartTime.em_timepicker( 'setTime', startDate );
					$eventEndTime.em_timepicker( 'setTime', endDate );
					
					// Update recurring summary dates
					let recurringSection = eventDateTimes.querySelector('.recurring-summary-dates');
					if (recurringSection) {
						// Remove 'hidden' class if present
						recurringSection.classList.remove('hidden');
						
						// Update start date and time - use the right format from EM settings
						let datePickerFormat = 'D';
						let timeFormat = EM.show24hours == 1 ? 'H:mm':'h:mm a';
						
						// Update start date element
						let startDateElem = recurringSection.querySelector('.date.start-date');
						if (startDateElem) {
							startDateElem.textContent = startDateTime.toFormat(datePickerFormat);
						}
						
						// Update start time element
						let startTimeElem = recurringSection.querySelector('.time.start-time');
						if (startTimeElem) {
							startTimeElem.textContent = ' @ ' + startDateTime.toFormat(timeFormat);
						}
						
						// Update end date element
						let endDateElem = recurringSection.querySelector('.date.end-date');
						if (endDateElem) {
							endDateElem.textContent = endDateTime.toFormat(datePickerFormat);
						}
						
						// Update end time element
						let endTimeElem = recurringSection.querySelector('.time.end-time');
						if (endTimeElem) {
							endTimeElem.textContent = ' @ ' + endDateTime.toFormat(timeFormat);
						}
						
						// Update classes based on all-day status
						recurringSection.classList.remove('is-all-day', 'has-all-day');
						
						if ( eventTimeRange.dataset.allday === '1' ) {
							// True all-day event (all checkboxes checked and times match pattern)
							recurringSection.classList.add('is-all-day');
						} else if ( allRecurrencesAllDay ) {
							// All checkboxes are checked, but start/end times don't match all-day times in the primary timezone
							recurringSection.classList.add('has-all-day');
						}
						
						// Update timezone
						let timezoneElem = recurringSection.querySelector('.recurring-timezone .timezone');
						if (timezoneElem && timezone) {
							timezoneElem.textContent = timezone;
						}
						
						 // Add 'has-multiple-timezones' class if multiple timezones are detected
						if (uniqueTimezones.size > 1) {
							recurringSection.classList.add('has-multiple-timezones');
						} else {
							recurringSection.classList.remove('has-multiple-timezones');
						}
						
						// Hide the missing info message
						let missingInfoElem = eventDateTimes.querySelector('.recurring-summary-missing');
						if (missingInfoElem) {
							missingInfoElem.classList.add('hidden');
						}
					}
				}
			}
		});
	});

	let breakpoints = { 'small' : 500, 'large' : false, };
	let recurringSummaries = document.querySelectorAll('.em-recurring-summary .recurring-summary-dates');
	if ( recurringSummaries.length > 0 ) {
		EM_ResizeObserver( breakpoints, recurringSummaries );
	}
});

// this section deals with functions that handle display of text, counting sets and redisplaying forms
let emRecurrenceEditor = {
	updateIntervalDescriptor : function( container ) {
		let sets = container.matches('.em-recurrence-sets') ? container.querySelectorAll('.em-recurrence-set') : [ container.closest('.em-recurrence-set') ];
		sets.forEach( function( set ) {
			set.querySelectorAll(".interval-desc").forEach( el => el.classList.add('hidden') );
			let number = "-plural";
			let input = set.querySelector('input.em-recurrence-interval');
			if ( input ) {
				if ( input.value === "1" || input.value === "" ) {
					number = "-singular";
				}
			}
			let select = set.querySelector("select.em-recurrence-frequency");
			let freq = select ? select.value : "";
			let descriptorSelector = "span.interval-desc.interval-" + freq + number;
			set.querySelectorAll( descriptorSelector ).forEach( el => el.classList.remove('hidden') );
			set.querySelectorAll('.interval-desc-intro').forEach( el => el.classList.toggle('hidden', freq === 'on') );
		});
	},

	updateDurationDescriptor : function( container ) {
		let sets = container.matches('.em-recurrence-sets') ? container.querySelectorAll('.em-recurrence-set') : [ container.closest('.em-recurrence-set') ];
		sets.forEach( function( set ) {
			set.querySelectorAll(".recurrence-days-desc").forEach( el => el.classList.add('hidden') );
			let input = set.querySelector('input.em-recurrence-duration');
			let number = input && (input.value === "1" || (input.value === '' && input.placeholder === '1')) ? 'singular' : 'plural';
			set.querySelectorAll( ".recurrence-days-desc.em-" + number ).forEach( el => el.classList.remove('hidden') );
		});
	},

	updateIntervalSelectors : function ( container ) {
		let sets = container.matches('.em-recurrence-sets') ? container.querySelectorAll('.em-recurrence-set') : [ container.closest('.em-recurrence-set') ];
		sets.forEach( function( set ) {
			set.querySelectorAll('.alternate-selector').forEach( el => el.classList.add('hidden') );
			let select = set.querySelector("select.em-recurrence-frequency");
			let freq = select ? select.value : "";
			set.querySelectorAll('.em-' + freq + '-selector').forEach( el => el.classList.remove('hidden') );
			set.querySelectorAll('.em-recurrence-interval').forEach( el => el.classList.toggle('hidden', freq === 'on') );
		});
	}
}

document.addEventListener('em_event_editor_recurrences', function( e ) {
	let recurrenceSets = e.detail.recurrenceSets;
	/**
	 * Sets placeholders for advanced fields in non-main recurrences so as to show informative recurrence set defaults and descriptions if left blank, since the main recurrence is the default values.
	 */
	recurrenceSets.addEventListener('setAdvancedDefaults', function () {
		// get the first recurrence set
		let recurrenceSetPrimary = recurrenceSets.querySelector('.em-recurrence-type-include .em-recurrence-set:first-child');
		let selector = '.em-recurrence-type-include .em-recurrence-set:not(:first-child) .em-recurrence-advanced';

		// set all recurrence sets to have a 0 value flag to detect modifications of primary
		recurrenceSets.querySelectorAll('.em-recurrence-set').forEach( recurrenceSet => { recurrenceSet.dataset.primaryModified = '0'; });

		// check for modified values and if so set flag that this overrides primary set
		recurrenceSets.querySelectorAll('.em-recurrence-set').forEach(recurrenceSet => {
			if (recurrenceSet !== recurrenceSetPrimary) {
				if ( recurrenceSet.querySelector('.recurrences-timeranges-default-trigger')?.checked ) {
					recurrenceSet.dataset.primaryModified = '1';
				}
			}
		});

		// Recurse recurrenceSets by recurrence set (.em-recurrence-set) to begin with
		recurrenceSets.querySelectorAll('.em-recurrence-set').forEach( function ( recurrenceSet ) {
			if ( !recurrenceSet.matches('.em-recurrence-type-include .em-recurrence-set:first-child') ) {
				// loop each flatpickr input and set placeholders and detect if default value has changed
				recurrenceSetPrimary.querySelectorAll('.em-recurrence-advanced .em-datepicker .em-date-input.flatpickr-input').forEach(function (input) {
					let datePicker = input.closest('.em-datepicker');
					let value = input._flatpickr.altInput.value;
					let modifiedDefault = input._flatpickr._inputData.some( input => input.value !== input.dataset.undo );

					// get the text format directly, we assume it's the same as the datepicker type for events, it's an EM setting (if dev customizations change the template, they'll need to account for it here)
					let datesSelector = '.em-recurrence-dates .em-date-input.form-control';
					if (datePicker.classList.contains('em-datepicker-range')) {
						recurrenceSet.querySelectorAll(datesSelector).forEach(function (dp) {
							dp.previousElementSibling.placeholder ||= dp.placeholder;
							dp.placeholder = value ? value : dp.previousElementSibling.placeholder;
							if ( dp.value === '' && modifiedDefault ) {
								dp.closest('.em-recurrence-set').dataset.primaryModified = '1';
							}
						});
					} else if (datePicker.classList.contains('em-datepicker-until')) {
						datesSelector += input.classList.contains('em-date-input-start') ? '.em-date-input-start' : '.em-date-input-end';
						recurrenceSet.querySelectorAll(datesSelector).forEach(function (dp) {
							dp.placeholder = value ? value : dp.previousElementSibling.placeholder;
							if ( dp.value === '' && modifiedDefault ) {
								dp.closest('.em-recurrence-set').dataset.primaryModified = '1';
							}
						});
					}
				});
			}
		});

		// Handle timezone and status dropdowns using a shared function
		['timezone', 'status'].forEach( function( selectType ) {
			const classPrefix = '.em-recurrence-' + selectType;
			let select = recurrenceSetPrimary.querySelector(classPrefix + ' select');
			if (select) {
				// Set placeholder for other recurrences - timezones will also affect the exclude section
				let selectors = selectType === 'timezone' ? [ selector, '.em-recurrence-type-exclude .em-recurrence-set .em-recurrence-advanced'] : [ selector ];
				selectors.forEach( function( selector ) {
					recurrenceSets.querySelectorAll(selector + ' ' + classPrefix + ' select').forEach( function( otherSelect ) {
						if ( otherSelect.selectize ) {
							if ( select.value ) {
								otherSelect.selectize.settings.placeholder = select.querySelector(`option[value="${select.value}"]`)?.textContent || select.value;
								otherSelect.selectize.updatePlaceholder();
								// no value selected therefore overriden by primary
								if ( otherSelect.value === '' && select.value !== select.dataset.undo ) {
									otherSelect.closest('.em-recurrence-set').dataset.primaryModified = '1';
								}
							} else {
								otherSelect.selectize.settings.placeholder = select.selectize?.settings.placeholder;
								otherSelect.selectize.updatePlaceholder();
							}
						}
					});
				});
			}
		});

		// Handle recurrence duration input
		let durationInput = recurrenceSetPrimary.querySelector('input.em-recurrence-duration');
		if (durationInput && durationInput.value.trim()) {
			recurrenceSets.querySelectorAll(selector + ' input.em-recurrence-duration').forEach(function(otherInput) {
				otherInput.placeholder = durationInput.value.trim();
				if ( otherInput.value === '' && durationInput.value !== durationInput.dataset.undo ) {
					otherInput.closest('.em-recurrence-set').dataset.primaryModified = '1';
				}
			});
		}

		// check primary overriding values and set additional flags for date ranges which we can reference and detect overriding changes
		recurrenceSets.querySelectorAll('.em-recurrence-set').forEach( function( recurrenceSet ) {
			// check if each recurrence set is affected by primary modifications
			recurrenceSet.classList.toggle('advanced-modified-primary', recurrenceSet.dataset.primaryModified === '1');
			if ( recurrenceSet.dataset.primaryModified ) {
				recurrenceSet.dispatchEvent( new Event('change', { bubbles: true }) );
			}
			delete recurrenceSet.dataset.primaryModified;

			// check the start/end dates specifically, if they are both set then we need to set a flag so we know they were modified too
			let hasDates = 0;
			let hasStartDate = false, hasStartDateModified = false;
			let hasEndDate = false, hasEndDateModified = false;
			let hasModifiedDates;
			if ( recurrenceSet.querySelector('select.em-recurrence-frequency')?.value === 'on' ) {
				hasDates = 2; // fake this as we don't need a date range for 'on' frequency
			} else {
				// check if there is a complete date range set (i.e. two dates) and if any of the dates were modified
				recurrenceSet.querySelectorAll(`.em-recurrence-dates .em-datepicker-data input[name]`).forEach(function ( input ) {
					hasDates += input.value ? 1 : 0;
					if ( input.matches(':first-of-type') ) {
						hasStartDate = !!input.value;
						hasStartDateModified = input.value !== input.dataset.undo;
					}
					if ( input.matches(':last-of-type') ) {
						hasEndDate = !!input.value;
						hasEndDateModified = input.value !== input.dataset.undo;
					}
					hasModifiedDates ||= input.value !== input.dataset.undo;
				});
			}
			recurrenceSet.classList.toggle('has-date-range', hasDates >= 2);
			if ( hasDates < 2 ) {
				recurrenceSet.classList.toggle( 'has-date-range-start', hasStartDate );
				recurrenceSet.classList.toggle( 'has-date-range-end', hasEndDate );
			}
			if ( hasStartDateModified && hasEndDateModified ) {
				recurrenceSet.classList.toggle('has-modified-date-range', true);
				recurrenceSet.classList.toggle('has-modified-date-range-start', false);
				recurrenceSet.classList.toggle('has-modified-date-range-end', false);
			} else {
				recurrenceSet.classList.toggle('has-modified-date-range', false);
				recurrenceSet.classList.toggle('has-modified-date-range-start', hasStartDateModified);
				recurrenceSet.classList.toggle('has-modified-date-range-end', hasEndDateModified);
			}
		});

		// update the recurrence summary
		recurrenceSets.dispatchEvent( new Event('updateRecurrenceSummary', { bubbles: true }) );
	});

	// update the count elements so CSS can do its thing
	recurrenceSets.addEventListener('updateSetsCount', function() {
		['include', 'exclude'].forEach( function ( recurrenceType ) {
			// show or hide remove button
			let recurrenceTypeSets = recurrenceSets.querySelector('.em-recurrence-type-' + recurrenceType);
			if ( recurrenceTypeSets ) {
				let count = recurrenceTypeSets.querySelectorAll('.em-recurrence-set').length;
				recurrenceSets.setAttribute('data-' + recurrenceType + '-count', count);
				recurrenceTypeSets.dataset.count = count; // CSS will hide things
			}
		});
	});

	// reset order of items as per reordering
	recurrenceSets.addEventListener('updateRecurrenceOrder', function() {
		let primaryRecurrence;
		recurrenceSets.querySelectorAll('.em-recurrence-type-include .em-recurrence-set').forEach( function( recurrenceSet, index) {
			let order_input = recurrenceSet.querySelector('.em-recurrence-order');
			if (order_input) {
				order_input.value = index + 1;
			}
			recurrenceSet.classList.toggle('show-advanced', index === 0);
			if ( recurrenceSet !== primaryRecurrence && index === 0 ) {
				// copy all the date/time/duration advanced values from primaryRecurrence to here
				primaryRecurrence = recurrenceSet;
				// set default placehodlers
				primaryRecurrence.querySelectorAll('[data-placeholder]').forEach( el => { el.placeholder = el.dataset.placeholder } );
				primaryRecurrence.querySelectorAll('.em-datepicker .em-date-input-end.form-control').forEach( el => { el.placeholder = el.previousElementSibling.placeholder });
				primaryRecurrence.querySelectorAll('.em-datepicker .em-date-input-start.form-control').forEach( el => { el.placeholder = el.previousElementSibling.placeholder });
				recurrenceSets.dispatchEvent( new CustomEvent('updateSetsCount') );
				recurrenceSets.dispatchEvent( new CustomEvent('setAdvancedDefaults') );
				recurrenceSets.dispatchEvent( new CustomEvent('updateRecurrenceSummary') );
			}
			if ( recurrenceSet.matches(':first-child') ) {
				recurrenceSet.dataset.primary = '1';
				recurrenceSet.querySelectorAll('.em-time-all-day').forEach( el => { el.indeterminate = false; } );
			} else {
				delete recurrenceSet.dataset.primary;
			}
		});
	} );

	// show/hide remove button
	recurrenceSets.dispatchEvent( new CustomEvent('updateSetsCount') );
	// Initialize recurrence descriptor and selectors for this recurrenceSets container
	emRecurrenceEditor.updateIntervalDescriptor(recurrenceSets);
	emRecurrenceEditor.updateIntervalSelectors(recurrenceSets);
	emRecurrenceEditor.updateDurationDescriptor(recurrenceSets);

});


document.addEventListener('em_event_editor_recurrences', function( e ) {
	let recurrenceSets = e.detail.recurrenceSets;

	// Attach delegated listeners for interval/duration inputs and frequency select within this container
	recurrenceSets.addEventListener('keyup', function (e) {
		if ( e.target.matches('input.em-recurrence-interval') ) {
			emRecurrenceEditor.updateIntervalDescriptor( e.target.closest('.em-recurrence-set') );
		} else if (e.target.matches('input.em-recurrence-duration')) {
			emRecurrenceEditor.updateDurationDescriptor( e.target.closest('.em-recurrence-set') );
		}
	});

	// recurrency descriptors and selectors that change upon frequency changes
	recurrenceSets.addEventListener('change', function (e) {
		if (e.target.matches('select.em-recurrence-frequency')) {
			let recurrenceSet = e.target.closest('.em-recurrence-set');
			emRecurrenceEditor.updateIntervalDescriptor( recurrenceSet );
			emRecurrenceEditor.updateIntervalSelectors( recurrenceSet );
		}
	});
});

//Event Editor
// Recurrence Warnings
document.querySelectorAll('form.em-event-admin-recurring').forEach(form => {
	form.addEventListener('submit', function (event) {
		let warning_text;
		let recreateInput = form.querySelector('input[name="event_recreate_tickets"]');

		if (recreateInput && recreateInput.value === "1") {
			warning_text = EM.event_recurrence_bookings;
		}

		if ( warning_text && !confirm(warning_text) ) {
			event.preventDefault();
		}
	});
});

//Buttons for recurrence warnings within event editor forms
document.querySelectorAll('.em-reschedule-trigger, .em-reschedule-cancel').forEach(trigger => {
	trigger.addEventListener('click', e => {
		e.preventDefault();
		const el = e.currentTarget;
		const show = el.matches('.em-reschedule-trigger');
		el.closest('.em-recurrence-reschedule')?.querySelector(el.dataset.target)?.classList.toggle('reschedule-hidden', !show);
		el.parentElement.querySelectorAll('[data-nonce]').forEach( el => { el.disabled = !show } );
		el.parentElement.querySelectorAll('button').forEach( link => link.classList.remove('reschedule-hidden') );
		el.classList.add('reschedule-hidden');
	});
});

document.addEventListener('em_event_editor_recurrences', function( e ) {
	let recurrenceSets = e.detail.recurrenceSets;

	let setAdvancedDefaults = () => recurrenceSets.dispatchEvent( new CustomEvent('setAdvancedDefaults') );
	let updateRecurrenceSummary = ( recurrenceSet ) => recurrenceSet.dispatchEvent( new CustomEvent('updateRecurrenceSummary', { bubbles: true }) )
	let setDateTimes = () => recurrenceSets.dispatchEvent( new CustomEvent('setDateTimes') );

	// ADVANCED TOGGLE/SECTION

	// Open/Close Advanced Section
	recurrenceSets.addEventListener('click', function(e) {
		const toggleButton = e.target.closest('.em-recurrence-set-action-advanced');
		if (!toggleButton) return;

		const recurrenceSet = toggleButton.closest('.em-recurrence-set');
		recurrenceSet.classList.toggle('show-advanced');
		if( '_tippy' in toggleButton ){
			if ( recurrenceSet.classList.contains('show-advanced') ) {
				toggleButton._tippy.setContent( toggleButton.getAttribute('data-label-hide') );
			} else {
				toggleButton._tippy.setContent( toggleButton.getAttribute('data-label-show') );
			}
		}
	});

	// Function to check advanced inputs and set icon color
	let updateAdvancedIcon = function( recurrenceSet ) {
		const advancedSection = recurrenceSet.querySelector('.em-recurrence-advanced');
		const advancedIcon = recurrenceSet.querySelector('.em-recurrence-set-action-advanced');

		if ( !advancedSection || !advancedIcon) return;

		const inputs = advancedSection.querySelectorAll('input, select, textarea');
		let hasValue = Array.from(inputs).some( input => {
			if ( input.type === 'checkbox' ) {
				return input.checked;
			} else {
				return input.value.trim() !== '';
			}
		});

		recurrenceSet.classList.toggle('has-advanced-value', hasValue);
	};

	// Event listener scoped to current recurrenceSets container
	recurrenceSets.addEventListener('change', function(e) {
		if ( e.target.closest('.em-recurrence-advanced') ) {
			updateAdvancedIcon( e.target.closest('.em-recurrence-set') );
		}
	});

	// Initialize advanced icons on page load for current recurrenceSets container
	recurrenceSets.querySelectorAll('.em-recurrence-set').forEach( recurrenceSet => updateAdvancedIcon( recurrenceSet ));

	// Track the first recurrence set and update placeholders accordingly
	recurrenceSets.querySelectorAll('.em-recurrence-type').forEach( function( recurrenceSetType ) {
		recurrenceSetType.addEventListener('change', function(e ){
			let recurrenceSet = e.target.closest('.em-recurrence-set');
			if ( e.target.closest('.em-recurrence-advanced') ) {
				// check changes to main/first recurrence
				if ( recurrenceSetType.classList.contains('em-recurrence-type-include') ) {
					if ( recurrenceSet === recurrenceSet.parentElement?.firstElementChild ) {
						setAdvancedDefaults();
					}
					setDateTimes();
				}
				updateRecurrenceSummary( recurrenceSet );
			} else if ( recurrenceSet?.querySelector('select.recurrence_freq')?.value === 'on' ) {
				// account for 'on' frequency changes
				if ( e.target.closest('.em-datepicker.em-on-selector') ) {
					// update the regular range datepicker to reflect date range from the 'on' selector datepicker
					let selectedDates = e.target.closest('.em-date-input')?._flatpickr?.selectedDates;
					if ( selectedDates ) {
						selectedDates.sort(function(a, b) { return a - b; });
						// set the dates

						let datepickerDates = recurrenceSet.querySelector('.em-recurrence-dates.em-datepicker');
						if ( datepickerDates ) {
							if ( datepickerDates.classList.contains( 'em-datepicker-until' ) ) {
								// we set start and end datepickers individually
								datepickerDates.querySelector( `.em-date-input-start` )?._flatpickr?.setDate( selectedDates[0] );
								datepickerDates.querySelector( `.em-date-input-end` )?._flatpickr?.setDate( selectedDates[selectedDates.length - 1] );
							} else if ( datepickerDates.classList.contains( 'em-datepicker-range' ) ) {
								// set an array of first/last selectedDates
								let selectedDatesArray = [ selectedDates[0], selectedDates[selectedDates.length - 1] ];
								datepickerDates.querySelector( `.em-date-input` )?._flatpickr?.setDate( selectedDatesArray );
							}
						}
						if ( recurrenceSetType.classList.contains('em-recurrence-type-include') ) {
							if ( recurrenceSet === recurrenceSet.parentElement?.firstElementChild ) {
								setAdvancedDefaults();
							}
						}
					}
				}
				setDateTimes();
			}
		});
	});

	// Track changes to the advanced section, for undo logic and other validation
	recurrenceSets.addEventListener('change', function(e) {
		if ( e.target.closest('.em-recurrence-advanced') ) {
			let recurrenceSet = e.target.closest('.em-recurrence-set');
			// go through each input with a name property and check if it different to the data-undo property (which may not be set)
			let isModified = false;
			recurrenceSet.querySelectorAll('.em-recurrence-advanced [name]:not([disabled]):not([data-nonce]').forEach( input => {
				if ( input.name && input.dataset.undo ) {
					if ( input.type === 'checkbox' ) {
						if ( input.checked && input.dataset.undo !== '1' ) {
							isModified = true;
						}
					} else {
						if ( input.value !== input.dataset.undo ) {
							isModified = true;
						}
					}
				} else if ( recurrenceSets.dataset.event_id && input.value ) {
					isModified = true;
				}
			});
			recurrenceSet.classList.toggle('advanced-modified', isModified);
		}

		// Listen for changes to .em-time-range on non-primary recurrence sets
		if ( e.target.matches('.em-recurrence-timeranges *') ) {
			let recurrenceSet = e.target.closest('.em-recurrence-set');
			let timeranges = recurrenceSet.querySelector('.em-recurrence-timeranges .em.timeranges');
			let startTimeInput = recurrenceSet.querySelector('.em-time-input.em-time-start.earliest-time');
			let endTimeInput = recurrenceSet.querySelector('.em-time-input.em-time-end.latest-time');
			let durationInput = recurrenceSet.querySelector('input.em-recurrence-duration');
			let isMultiDay = durationInput?.value > 0 || durationInput?.placeholder > 0 || false;

			if ( !isMultiDay ) {
				let startTime = startTimeInput.dataset.seconds ? parseInt(startTimeInput.dataset.seconds) : null;
				let endTime = endTimeInput.dataset.seconds ? parseInt(endTimeInput.dataset.seconds) : null;

				if ( !startTime || !endTime ) {
					// Select the first recurrence set of type "include" within recurrenceSets
					let recurrenceSetPrimary = recurrenceSets.querySelector('.em-recurrence-set[data-type="include"]:first-child');
					if ( recurrenceSetPrimary ) {
						if ( startTime === null ) {
							let seconds = recurrenceSetPrimary.querySelector('.em-time-input.em-time-start')?.dataset.seconds;
							startTime = seconds === undefined ? null : parseInt( recurrenceSetPrimary.querySelector('.em-time-input.em-time-start')?.dataset.seconds || 0 );
						}
						if ( endTime === null ) {
							let seconds = recurrenceSetPrimary.querySelector('.em-time-input.em-time-end')?.dataset.seconds;
							endTime = seconds === undefined ? null : parseInt( recurrenceSetPrimary.querySelector('.em-time-input.em-time-end')?.dataset.seconds || 0 );
						}
					}
				}

				// Ensure end time is not earlier than start time
				if ( startTime !== null && endTime !== null ) {
					if ( e.target.matches('.em-time-start') && startTime > endTime ) {
						endTimeInput.value = startTimeInput.value;
						endTimeInput.dispatchEvent(new Event('change'));
					}

					// Ensure start time is not later than end time
					if ( e.target.matches('.em-time-end') && startTime > endTime ) {
						startTimeInput.value = endTimeInput.value;
						startTimeInput.dispatchEvent(new Event('change'));
					}
				}
			}
		}

		// if duration is changed, trigger a change for the end time and make sure we're not at 0 with bad start/end times
		if ( e.target.matches('input.em-recurrence-duration') ) {
			if ( e.target.value === '0' || ( e.target.value === '' && e.target.placeholder === '0' ) ) {
				let recurrenceSet = e.target.closest('.em-recurrence-set');
				let sets = recurrenceSet.dataset.primary ? recurrenceSet : recurrenceSets;
				sets.querySelectorAll('.em-recurrence-times .em-time-end').forEach( el => el.dispatchEvent(new Event('change')));
			}
		}

		// listen for all-day checkbox changes within the non-primary recurrences
		let primaryCb = recurrenceSets.querySelector('.em-recurrence-set[data-primary] .em-time-all-day');
		if ( e.target.matches('.em-time-all-day') ) {
			let cb = e.target;
			if ( cb.matches('.em-recurrence-set[data-primary] .em-time-all-day') ) {
				if ( cb.readOnly ) {
					cb.checked = true;
					cb.readOnly = false;
				}
			} else {
				if ( cb.readOnly ) {
					cb.checked = true;
					cb.readOnly = false;
				} else if ( cb.checked && primaryCb.checked ) {
					cb.readOnly = true
					cb.indeterminate = true;
					// unset both times
					cb.closest('.em-time-range').querySelectorAll('.em-time-input').forEach( el => { el.value = '' } );
				}
			}
		}
	});

	// Update the recurrence summary of recurrences
	recurrenceSets.addEventListener('updateRecurrenceSummary', function( e ) {
		let sets = e.target.matches('.em-recurrence-set') ? [ e.target ] : e.target.querySelectorAll('.em-recurrence-set');
		let timerangeDefault = recurrenceSets.querySelector('.em-recurrence-set[data-primary] .em-recurrence-timeranges .em-timeranges');

		sets.forEach( function ( recurrenceSet ){
			let advancedSummary = recurrenceSet.querySelector('.advanced-summary');
			if ( advancedSummary ) {
				// Initialize objects for values as one-liners
				let dateValues = { start: '', end: '', startIsSet: false, endIsSet: false };
				let timeValues = { start: timerangeDefault.dataset.start, end: timerangeDefault.dataset.end, startIsSet: false, endIsSet: false };

				// Get date values with a loop
				if ( recurrenceSet.querySelector('select.recurrence_freq')?.value === 'on' ) {
					// get the ON dates rather than the date range
					let selectedDates = recurrenceSet.querySelector('.em-on-selector .em-date-input')?._flatpickr?.selectedDates;
					if ( selectedDates ) {
						selectedDates.sort(function(a, b) { return a - b; });
						// get the first and last dates from the selected dates
						dateValues['start'] = selectedDates[0];
						dateValues['startIsSet'] = true;
						dateValues['end'] = selectedDates[selectedDates.length - 1];
						dateValues['endIsSet'] = true;
					}
				} else {
					// not On dates so we look at traditional date range
					let datepickerDates = recurrenceSet.querySelector('.em-recurrence-dates.em-datepicker');
					if ( datepickerDates.classList.contains('em-datepicker-until') ) {
						['start', 'end'].forEach(function(type) {
							let dateInput = datepickerDates.querySelector(`.em-date-input-${type}`);
							if (dateInput) {
								if (dateInput._flatpickr && dateInput._flatpickr.altInput && dateInput._flatpickr.selectedDates.length) {
									// If flatpickr has a selected date, use that
									dateValues[type] = dateInput._flatpickr.altInput.value;
									dateValues[type + 'IsSet'] = true;
								} else if (dateInput.nextElementSibling) {
									// Otherwise use the visible input's value or placeholder
									dateValues[type] = dateInput.nextElementSibling.value ||
									dateInput.nextElementSibling.placeholder;
								}
							}
						});
					} else if ( datepickerDates.classList.contains('em-datepicker-range') ) {
						let dateInput = datepickerDates.querySelector(`.em-date-input`);
						if ( dateInput ) {
							// get the dates from flatpickr, formatted into the altinput format
							if (dateInput._flatpickr && dateInput._flatpickr.altInput && dateInput._flatpickr.selectedDates.length) {
								// If flatpickr has a selected date, use that
								dateValues['start'] = dateInput._flatpickr.altInput.value;
								dateValues['startIsSet'] = true;
							} else if (dateInput.nextElementSibling) {
								// Otherwise use the visible input's value or placeholder
								dateValues['start'] = dateInput.nextElementSibling.value ||
									dateInput.nextElementSibling.placeholder;
							}
						}
					}
				}

				// Get time values with a loop
				if ( recurrenceSet.querySelector('.em-recurrence-timeranges .recurrence-timeranges-default input:checked') ) {
					timeranges = recurrenceSet.querySelector('.em-recurrence-timeranges .em.timeranges');
					['start', 'end'].forEach(function(type) {
						let timeInput = recurrenceSet.querySelector(`.em-recurrence-times .em-time-${type}`);
						if (timeInput) {
							timeValues[type] = timeranges.dataset[type];
							if ( timeInput.value ) {
								timeValues[type + 'IsSet'] = !!timeValues[type];
							}
						}
					});
				}

				// Get timezone from select
				let timezoneSelect = recurrenceSet.querySelector('.em-recurrence-timezone select');
				let timezoneValue = '';

				if ( timezoneSelect ) {
					let value = timezoneSelect.value;

					if (value) {
						// If there's a value, get the text of the selected option (using null-coalescing)
						timezoneValue = timezoneSelect.querySelector(`option[value="${value}"]`)?.textContent || '';
					} else {
						// If no value, try to get the placeholder (using null-coalescing)
						timezoneValue = recurrenceSet.querySelector('.em-recurrence-timezone .selectize-input input')?.placeholder || '';
					}
				}

				// Get duration
				let durationInput = recurrenceSet.querySelector('.em-recurrence-duration input.em-recurrence-duration');
				let durationValue = durationInput ? (durationInput.value.trim() || durationInput.placeholder || '0') : '0';
				emRecurrenceEditor.updateDurationDescriptor( recurrenceSet );

				// Update elements with direct one-liners
				if ( Object.entries(dateValues).length === 4 ) {
					advancedSummary.querySelectorAll('.start-date').forEach(el => { el.textContent = dateValues.start; el.classList.toggle('is-set', dateValues.startIsSet); });
					advancedSummary.querySelectorAll('.end-date').forEach(el => { el.textContent = dateValues.end; el.classList.toggle('is-set', dateValues.endIsSet); });
				} else {
					advancedSummary.querySelectorAll('.dates').forEach(el => { el.textContent = dateValues.start; el.classList.toggle('is-set', dateValues.startIsSet); });
				}
				advancedSummary.querySelectorAll('.times').forEach( function( el ) {
					el.innerHTML = `<span class="start-time">${timeValues.start}</span> - <span class="end-time">${timeValues.end}</span>`;
					el.firstElementChild.classList.toggle('is-set', timeValues.startIsSet);
					el.lastElementChild.classList.toggle('is-set', timeValues.endIsSet);
				});
				advancedSummary.querySelector('.all-day')?.classList.toggle('is-set', recurrenceSet.querySelector('.em-time-all-day')?.checked );
				advancedSummary.querySelectorAll('.timezone').forEach(el => { el.textContent = timezoneValue; el.classList.toggle('is-set', timezoneSelect?.value); });
				advancedSummary.querySelectorAll('.duration').forEach(el => { el.textContent = durationValue; el.classList.toggle('is-set', durationInput && durationInput.value !== ''); });
			}
		});
	});
});

document.addEventListener('em_event_editor_recurrences', function( e ) {
	let recurrenceSets = e.detail.recurrenceSets;

	let setup_ui_elements = function( container ) {
		// clean up template of UI elements so they can be rebuilt when cloned
		if ( container === document ) {
			let template = recurrenceSets.querySelector('.em-recurrence-set-template');
			em_unsetup_ui_elements( template );
		}

		recurrenceSets.dispatchEvent( new CustomEvent('setAdvancedDefaults') );

		let eventType = recurrenceSets.closest('form').querySelector('input[name="event_type"]')?.value;

		// Add change handlers for selectize dropdowns in first recurrence set
		// track selectize changes assuming recurrences are enabled
		if ( container === document && ( eventType === 'recurring' || eventType === 'repeating' ) ) {
			// Get the first recurrence set
			let firstRecurrenceSet = recurrenceSets.querySelector('.em-recurrence-type-include .em-recurrence-set:first-child');

			// Map of recurrence field selectors to event field selectors
			const fieldMappings = {
				'.em-recurrence-timezone select': 'select[name="event_timezone"]',
				'.em-recurrence-status select': 'select[name="event_active_status"]'
			};

			// Handle each field type
			Object.entries(fieldMappings).forEach( function([recurrenceSelector, eventSelector]) {
				let recurrenceField = firstRecurrenceSet.querySelector(recurrenceSelector);

				// Find the corresponding event field
				let eventFormWhen = recurrenceSets.closest('form').querySelector('.event-form-when');
				let eventField = eventFormWhen?.querySelector(eventSelector);

				if ( recurrenceField && eventField ) {
					// Set up change handler using selectize API if available
					if (recurrenceField.selectize) {
						// For selectize fields
						recurrenceField.selectize.on('change', function (value) {
							if (eventField.selectize) {
								// If event field is also selectize
								eventField.selectize.setValue(value, true);
							} else {
								// If event field is a regular select
								eventField.value = value;
								eventField.dispatchEvent(new Event('change', {bubbles: true}));
							}
						});
					} else {
						// Fallback for regular select fields
						recurrenceField.addEventListener('change', function () {
							if (eventField.selectize) {
								eventField.selectize.setValue(recurrenceField.value, true);
							} else {
								eventField.value = recurrenceField.value;
								eventField.dispatchEvent(new Event('change', {bubbles: true}));
							}
						});
					}

					// Also handle initial sync (if recurrence field already has a value)
					let initialValue = recurrenceField.selectize ? recurrenceField.selectize.getValue() : recurrenceField.value;
					if (initialValue) {
						if (eventField.selectize) {
							eventField.selectize.setValue(initialValue, true);
						} else {
							eventField.value = initialValue;
						}
					}
				}
			});
		}
	}

	document.addEventListener('em_setup_ui_elements', function(e) {
		setup_ui_elements( e.detail.container );
	});

	// If already loaded, we trigger a first-time setup
	if (document.readyState === 'complete' || document.readyState === 'interactive') {
		setup_ui_elements( document );
	}
});

//Tickets & Bookings - legacy stuff needing some rewrites
document.addEventListener('em_event_editor_loaded', function(e){
	const $ = jQuery.noConflict();
	if ( $( "#em-tickets-form" ).length > 0 ) {
		//Enable/Disable Bookings
		document.getElementById('event-rsvp').addEventListener('click', function (event) {
			const nonceInput = this.parentElement.querySelector('input.event_rsvp_delete[data-nonce]');
			const rsvpOptions = document.getElementById('event-rsvp-options');
			if (!this.checked) {
				const confirmation = confirm(EM.disable_bookings_warning);
				if (!confirmation) {
					event.preventDefault();
				} else {
					rsvpOptions.classList.add('hidden');
					nonceInput.disabled = false;
				}
			} else {
				rsvpOptions.classList.remove('hidden');
				nonceInput.disabled = true;
			}
		});
		if ( $( 'input#event-rsvp' ).is( ":checked" ) ) {
			$( "div#rsvp-data" ).fadeIn();
		} else {
			$( "div#rsvp-data" ).hide();
		}
		//Ticket(s) UI
		var reset_ticket_forms = function () {
			$( '#em-tickets-form table tbody tr.em-tickets-row' ).show();
			$( '#em-tickets-form table tbody tr.em-tickets-row-form' ).hide();
		};
		// handle indeterminate checkboxes and the hidden inputs they associate with
		document.querySelectorAll('#em-tickets-form input.possibly-indeterminate[type="checkbox"], #em-tickets-form input[type="checkbox"][indeterminate]').forEach( cb => {
			if ( cb.hasAttribute('indeterminate') && cb.readOnly ) {
				cb.indeterminate = true;
			}
			cb.addEventListener('click', () => {
				if ( cb.hasAttribute('indeterminate') && !cb.classList.contains('determinate') ) {
					if ( cb.readOnly ) {
						cb.checked = true;
						cb.readOnly = false;
					} else if ( cb.checked ) {
						cb.readOnly = true
						cb.indeterminate = true;
					}
					if ( cb.classList.contains('possibly-indeterminate') ) {
						cb.nextElementSibling.value = cb.indeterminate ? 'default' : ( cb.checked ? 1 : 0 );
					}
				} else {
					cb.nextElementSibling.value = cb.checked ? 1 : 0;
				}
			});
		});
		//Add a new ticket
		$( "#em-tickets-add" ).on( 'click', function ( e ) {
			e.preventDefault();
			reset_ticket_forms();
			//create copy of template slot, insert so ready for population
			var tickets = $( '#em-tickets-form table tbody' );
			tickets.first( '.em-ticket-template' ).find( 'input.em-date-input.flatpickr-input' ).each( function () {
				if ( '_flatpickr' in this ) {
					this._flatpickr.destroy();
				}
			} ); //clear all datepickers, should be done first time only, next times it'd be ignored
			var rowNo = tickets.length + 1;
			var slot = tickets.first( '.em-ticket-template' ).clone( true ).attr( 'id', 'em-ticket-' + rowNo ).removeClass( 'em-ticket-template' ).addClass( 'em-ticket' ).appendTo( $( '#em-tickets-form table' ) );
			//change the index of the form element names
			slot.find( '*[name]' ).each( function ( index, el ) {
				el = $( el );
				el.attr( 'name', el.attr( 'name' ).replace( 'em_tickets[0]', 'em_tickets[' + rowNo + ']' ) );
			} );
			// sort out until datepicker ids
			let start_datepicker = slot.find( '.ticket-dates-from-normal' ).first();
			if ( start_datepicker.attr( 'data-until-id' ) ) {
				let until_id = start_datepicker.attr( 'data-until-id' ).replace( '-0', '-' + rowNo );
				start_datepicker.attr( 'data-until-id', until_id );
				slot.find( '.ticket-dates-to-normal' ).attr( 'id', start_datepicker.attr( 'data-until-id' ) );

			}
			//show ticket and switch to editor
			slot.show().find( '.ticket-actions-edit' ).trigger( 'click' );
			//refresh datepicker and values
			slot.find( '.em-time-input' ).off().each( function ( index, el ) {
				if ( typeof this.em_timepickerObj == 'object' ) {
					this.em_timepicker( 'remove' );
				}
			} ); //clear all em_timepickers - consequently, also other click/blur/change events, recreate the further down
			em_setup_ui_elements( slot );
			$( 'html, body' ).animate( { scrollTop: slot.offset().top - 30 } ); //sends user to form
			check_ticket_sortability();
			// set up a UUID for this ticket
			slot.find('.ticket_uuid').val(
				"10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
					(+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
				)
			)
		} );
		//Edit a Ticket
		$( document ).on( 'click', '.ticket-actions-edit', function ( e ) {
			e.preventDefault();
			reset_ticket_forms();
			var tbody = $( this ).closest( 'tbody' );
			tbody.find( 'tr.em-tickets-row' ).hide();
			tbody.find( 'tr.em-tickets-row-form' ).fadeIn();
			return false;
		} );
		$( document ).on( 'click', '.ticket-actions-edited', function ( e ) {
			e.preventDefault();
			var tbody = $( this ).closest( 'tbody' );
			var rowNo = tbody.attr( 'id' ).replace( 'em-ticket-', '' );
			tbody.find( '.em-tickets-row' ).fadeIn();
			tbody.find( '.em-tickets-row-form' ).hide();
			tbody.find( '*[name]' ).each( function ( index, el ) {
				el = $( el );
				if ( el.attr( 'name' ) == 'ticket_start_pub' ) {
					tbody.find( 'span.ticket_start' ).text( el.val() );
				} else if ( el.attr( 'name' ) == 'ticket_end_pub' ) {
					tbody.find( 'span.ticket_end' ).text( el.val() );
				} else if ( el.attr( 'name' ) == 'em_tickets[' + rowNo + '][ticket_type]' ) {
					if ( el.find( ':selected' ).val() == 'members' ) {
						tbody.find( 'span.ticket_name' ).prepend( '* ' );
					}
				} else if ( el.attr( 'name' ) == 'em_tickets[' + rowNo + '][ticket_start_recurring_days]' ) {
					var text = tbody.find( 'select.ticket-dates-from-recurring-when' ).val() == 'before' ? '-' + el.val() : el.val();
					if ( el.val() != '' ) {
						tbody.find( 'span.ticket_start_recurring_days' ).text( text );
						tbody.find( 'span.ticket_start_recurring_days_text, span.ticket_start_time' ).removeClass( 'hidden' ).show();
					} else {
						tbody.find( 'span.ticket_start_recurring_days' ).text( ' - ' );
						tbody.find( 'span.ticket_start_recurring_days_text, span.ticket_start_time' ).removeClass( 'hidden' ).hide();
					}
				} else if ( el.attr( 'name' ) == 'em_tickets[' + rowNo + '][ticket_end_recurring_days]' ) {
					var text = tbody.find( 'select.ticket-dates-to-recurring-when' ).val() == 'before' ? '-' + el.val() : el.val();
					if ( el.val() != '' ) {
						tbody.find( 'span.ticket_end_recurring_days' ).text( text );
						tbody.find( 'span.ticket_end_recurring_days_text, span.ticket_end_time' ).removeClass( 'hidden' ).show();
					} else {
						tbody.find( 'span.ticket_end_recurring_days' ).text( ' - ' );
						tbody.find( 'span.ticket_end_recurring_days_text, span.ticket_end_time' ).removeClass( 'hidden' ).hide();
					}
				} else {
					var classname = el.attr( 'name' ).replace( 'em_tickets[' + rowNo + '][', '' ).replace( ']', '' ).replace( '[]', '' );
					tbody.find( '.em-tickets-row .' + classname ).text( el.val() );
				}
			} );
			//allow for others to hook into this
			$( document ).triggerHandler( 'em_maps_tickets_edit', [ tbody, rowNo, true ] );
			$( 'html, body' ).animate( { scrollTop: tbody.parent().offset().top - 30 } ); //sends user back to top of form
			return false;
		} );
		$( document ).on( 'change', '.em-ticket-form select.ticket_type', function ( e ) {
			//check if ticket is for all users or members, if members, show roles to limit the ticket to
			var el = $( this );
			let ticketForm = el.closest( '.em-ticket-form' );
			if ( this.value === 'members' || ( this.value === '-1' && this.dataset.default === 'members' ) ) {
				el.closest( '.em-ticket-form' ).find( '.ticket-roles' ).fadeIn();
			} else {
				el.closest( '.em-ticket-form' ).find( '.ticket-roles' ).hide();
			}
			if ( this.value === '-1' && this.dataset.default === 'members' ) {
				// set all checkboxes with indeterminate prop to indeterminate
				ticketForm[0].querySelectorAll( '.ticket-roles input[type="checkbox"][indeterminate]' ).forEach( el => {
					el.indeterminate = true;
					el.classList.remove( 'determinate' )
				} );
				ticketForm[0].querySelectorAll( '.ticket-roles input[type="checkbox"]:not([indeterminate])' ).forEach( el => { el.checked = false; } );
			}else if ( this.value === 'members' ) {
				// remove indeterminate prop from all checkboxes
				ticketForm[0].querySelectorAll( '.ticket-roles input[type="checkbox"][indeterminate]' ).forEach( el => {
					el.indeterminate = false;
					el.readOnly = false;
					el.checked = true;
					el.classList.add( 'determinate' )
				} );
			}
		});
		$('.em-ticket-form select.ticket_type').trigger('change');
		$( document ).on( 'change', '.em-ticket-form .ticket-roles input[type="checkbox"]', function ( e ) {
			let ticketForm = this.closest( '.em-ticket-form' );
			let select = ticketForm.querySelector( '.em-ticket-form select.ticket_type' )
			if ( select.dataset.default === 'members' && select.value === '-1' ) {
				select.value = 'members';
				ticketForm.querySelectorAll( '.ticket-roles input[type="checkbox"][indeterminate]' ).forEach( el => {
					el.indeterminate = false;
					el.readOnly = false;
					el.checked = true;
					el.classList.add( 'determinate' )
				} );
			}
		});
		$( document ).on( 'click', '.em-ticket-form .ticket-options-advanced', function ( e ) {
			//show or hide advanced tickets, hidden by default
			e.preventDefault();
			var el = $( this );
			if ( el.hasClass( 'show' ) ) {
				el.closest( '.em-ticket-form' ).find( '.em-ticket-form-advanced' ).fadeIn();
				el.find( '.show,.show-advanced' ).hide();
				el.find( '.hide,.hide-advanced' ).show();
			} else {
				el.closest( '.em-ticket-form' ).find( '.em-ticket-form-advanced' ).hide();
				el.find( '.show,.show-advanced' ).show();
				el.find( '.hide,.hide-advanced' ).hide();
			}
			el.toggleClass( 'show' );
		} );
		$( '.em-ticket-form' ).each( function () {
			//check whether to show advanced options or not by default for each ticket
			var show_advanced = false;
			var el = $( this );
			el.find( '.em-ticket-form-advanced input[type="text"]' ).each( function () {
				if ( this.value != '' ) show_advanced = true;
			} );
			if ( el.find( '.em-ticket-form-advanced input[type="checkbox"]:checked' ).length > 0 ) {
				show_advanced = true;
			}
			el.find( '.em-ticket-form-advanced option:selected' ).each( function () {
				if ( this.value != '' ) show_advanced = true;
			} );
			if ( show_advanced ) el.find( '.ticket-options-advanced' ).trigger( 'click' );
		} );
		//Delete a ticket
		$( document ).on( 'click', '.ticket-actions-delete', function ( e ) {
			e.preventDefault();
			var el = $( this );
			var tbody = el.closest( 'tbody' );
			if ( tbody.find( 'input.ticket_id' ).val() > 0 ) {
				//only will happen if no bookings made, we set the ticket as deleted and enable the delete nonce
				let warning = this.classList.contains( 'parent-ticket' ) ? EM.eventEditor.deleteTicketParentWarning : EM.eventEditor.deleteTicketWarning;
				if ( confirm( warning ) ) {
					tbody.find( 'input.delete[data-nonce]' ).prop('disabled', false);
					tbody.closest( '.em-ticket' ).addClass( 'ticket-deleted' );
				}
			} else {
				//not saved to db yet, so just remove
				tbody.remove();
			}
			check_ticket_sortability();
			return false;
		} );
		//Sort Tickets
		$( '#em-tickets-form.em-tickets-sortable table' ).sortable( {
			items: '> tbody',
			placeholder: "em-ticket-sortable-placeholder",
			handle: '.ticket-status',
			helper: function ( event, el ) {
				var helper = $( el ).clone().addClass( 'em-ticket-sortable-helper' );
				var tds = helper.find( '.em-tickets-row td' ).length;
				helper.children().remove();
				helper.append( '<tr class="em-tickets-row"><td colspan="' + tds + '" style="text-align:left; padding-left:15px;"><span class="dashicons dashicons-tickets-alt"></span></td></tr>' );
				return helper;
			},
		} );
		var check_ticket_sortability = function () {
			var em_tickets = $( '#em-tickets-form table tbody.em-ticket' );
			if ( em_tickets.length == 1 ) {
				em_tickets.find( '.ticket-status' ).addClass( 'single' );
				$( '#em-tickets-form.em-tickets-sortable table' ).sortable( "option", "disabled", true );
			} else {
				em_tickets.find( '.ticket-status' ).removeClass( 'single' );
				$( '#em-tickets-form.em-tickets-sortable table' ).sortable( "option", "disabled", false );
			}
		};
		check_ticket_sortability();
	}
});
//# sourceMappingURL=events-manager-event-editor.js.map

Youez - 2016 - github.com/yon3zu
LinuXploit