PageSourceSearch

https://www.knkv.nl/app/themes/saas-theme/assets/js/theme.js?ver=5.1.3-200

js knkv.nl collected 2026-09-24 21:31:33 UTC 27,221 bytes, 783 lines download raw bytes

1
2/**
3 * Save the sidebar menu collapse state.
4 */
5const sidebar_collapse_toggler = document.getElementById( 'sidebar-collapse-toggler' );
6if ( sidebar_collapse_toggler ) {
7
8	sidebar_collapse_toggler.addEventListener( 'change', ( event ) => {
9		if ( sidebar_collapse_toggler.checked === true ) {
10			localStorage.setItem( 'menu-sidebar-state', 'closed' );
11		} else {
12			localStorage.setItem( 'menu-sidebar-state', 'opened' );
13		}
14	} );
15
16	document.addEventListener( 'DOMContentLoaded', function() {
17		const menu_sidebar_state = localStorage.getItem( 'menu-sidebar-state' );
18		if ( menu_sidebar_state === 'opened' ) {
19			sidebar_collapse_toggler.checked = false;
20		} else if ( menu_sidebar_state === 'closed' ) {
21			sidebar_collapse_toggler.checked = true;
22		}
23	} );
24
25}
26
27/**
28 * Confimation message trigger.
29 *
30 * Usage: <a href="#" data-confirmation="Are you sure?">
31 * Usage: <button type="submit" data-confirmation="Are you sure?"> (untested)
32 */
33document.addEventListener( 'click', function( e ) {
34	if ( e.target.hasAttribute( 'data-confirmation' ) && e.target.dataset.confirmation.length ) {
35		if ( ! window.confirm( e.target.dataset.confirmation ) ) {
36			e.preventDefault();
37		}
38	}
39} );
40
41/**
42 * Enable tooltips
43 */
44
45function saas_enable_tooltips () {
46	const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
47	if ( tooltipTriggerList ) {
48		[ ...tooltipTriggerList ].map( tooltipTriggerEl => new Tooltip( tooltipTriggerEl, {
49			html: true,
50			container: tooltipTriggerEl
51		} ) );
52	}
53}
54
55document.addEventListener( 'DOMContentLoaded', function() {
56
57	// Page load
58	saas_enable_tooltips();
59
60	// Gravity forms ajax reload
61	if ( typeof jQuery !== 'undefined' ) {
62		jQuery( document ).on( 'gform_post_render', saas_enable_tooltips );
63	}
64
65	// On get-template-part response
66	document.body.addEventListener( 'ajax-template-loaded', saas_enable_tooltips );
67
68	// On tab change (if they were invisible before)
69	document.addEventListener( 'shown.bs.tab', saas_enable_tooltips );
70
71} );
72
73// Loop through all dropdowns inside datatables and set their boundary to the wrapper div instead of the first parent with relative positioning
74document.addEventListener( 'DOMContentLoaded', ( e ) => {
75	if ( typeof jQuery !== 'undefined' ) {
76
77		jQuery( document ).on( 'draw.dt', 'table', function( e ) {
78			const dropdownElementList = document.querySelectorAll( '.dataTables_wrapper .dropdown-toggle' );
79			const dropdownList = [...dropdownElementList].map( ( dropdownToggleEl ) => {
80				dropdownToggleEl.classList.add( '-fixed' );
81				new Dropdown( dropdownToggleEl, {
82					popperConfig: function( obj ) {
83						obj.strategy = 'fixed';
84						return obj;
85					}
86				} );
87			} );
88		} );
89
90	}
91} );
92
93// Confirmation submit modal logic
94//todo Only load where needed? See class-gf-field-saas-tab.php -> $saas_form_tab_is_inited
95document.addEventListener( 'DOMContentLoaded', function() {
96
97	var saas_confirmation_submit_modal = document.querySelector( '.saas-confirmation-submit-modal' );
98	if ( saas_confirmation_submit_modal ) {
99
100		var confirmationModalInstance = new Modal( saas_confirmation_submit_modal, {});
101		var form = saas_confirmation_submit_modal.closest( 'form' );
102		var modal_submit_btn = saas_confirmation_submit_modal.querySelector( '.submit-btn' );
103		var form_submit_btn = form.querySelector( '#gform_submit_button_' + modal_submit_btn.getAttribute( 'data-form_id' ) );
104		var required_modal_checkboxes = saas_confirmation_submit_modal.querySelectorAll( 'input[required]' );
105		var form_has_tabs = form.querySelector( '.tab-pane' );
106		var modal_tab_id = form_has_tabs ? saas_confirmation_submit_modal.closest( '.tab-pane' ).getAttribute( 'aria-labelledby' ) : null;
107		var save_draft_buttons = form.querySelector( '[name="is-modds-quick-draft"], [name="save-draft"], .btn.no-submit' );
108
109		// Initial state of submit button
110		var checked_required_checkboxes = saas_confirmation_submit_modal.querySelectorAll( 'input[required]:checked' );
111		if ( checked_required_checkboxes.length === required_modal_checkboxes.length ) {
112			modal_submit_btn.removeAttribute( 'disabled' );
113		}
114
115		var submit_check = function( e ) {
116			e.preventDefault();
117			e.stopPropagation();
118			if ( form_has_tabs ) {
119				jQuery( '#' + modal_tab_id ).tab( 'show' );
120			}
121			confirmationModalInstance.show();
122		};
123
124		// Clicking submit outside of modal = open modal
125		form.addEventListener( 'submit', submit_check );
126
127		// Exclude the draft button
128		if ( save_draft_buttons ) {
129			save_draft_buttons.addEventListener( 'click', function() {
130				form.removeEventListener( 'submit', submit_check );
131			} );
132		}
133
134		// Clicking from modal = submit
135		modal_submit_btn.addEventListener( 'click', ( e ) => {
136			form.removeEventListener( 'submit', submit_check );
137			confirmationModalInstance.hide(); // Otherwise layout will break on failed validation
138			form_submit_btn.click();
139		} );
140
141		// Update submit button disabled state according to required checkboxes
142		required_modal_checkboxes.forEach( ( checkbox ) => {
143			checkbox.addEventListener('change', ( e ) => {
144				checked_required_checkboxes = saas_confirmation_submit_modal.querySelectorAll( 'input[required]:checked' );
145				if ( checked_required_checkboxes.length === required_modal_checkboxes.length ) {
146					modal_submit_btn.removeAttribute( 'disabled' );
147				} else {
148					modal_submit_btn.setAttribute( 'disabled', '' );
149				}
150			});
151		});
152
153	}
154
155} );
156
157// Reload GF form after submit (only applicable to modal forms)
158//todo Combine with toast trigger script
159document.addEventListener( 'DOMContentLoaded', function() {
160	if ( typeof jQuery !== 'undefined' ) {
161
162		var $forms = jQuery( '.saas-form-modal .gform_wrapper' );
163		if ( $forms.length ) {
164			$forms.each( function() {
165
166				var $form = jQuery( this );
167				var $parent = $form.parent();
168				var form_id = $form.attr( 'id' ).replace( 'gform_wrapper_', '' );
169				var form_html = jQuery( '<div />' ).append( $form.clone() ).html();
170
171				jQuery( document ).on( 'gform_confirmation_loaded', function( event, formId ) {
172					if ( formId == form_id ) {
173
174						$parent.find( '#gform_confirmation_wrapper_' + form_id ).replaceWith( form_html );
175						$parent.find( '.gform_anchor + .gform_anchor' ).remove();
176						jQuery( document ).trigger( 'gform_post_render', [ form_id, 0 ] );
177
178					}
179				} );
180
181			} );
182		}
183
184	}
185} );
186
187// Hide arrows on non-looped carousels
188document.addEventListener( 'DOMContentLoaded', ( e ) => {
189
190	// Toggle (show/hide) next/prev buttons when relevant.
191	const toggle_navigation = function( carousel ) {
192
193		const prev_buttons = document.querySelectorAll( '[data-bs-target="#' + carousel.id + '"][data-bs-slide="prev"]' );
194		const next_buttons = document.querySelectorAll( '[data-bs-target="#' + carousel.id + '"][data-bs-slide="next"]' );
195
196		// Hide prev button if the first slide is active
197		if ( prev_buttons ) {
198			prev_buttons.forEach( prev_button => {
199				prev_button.classList.toggle( '-hidden', carousel.querySelector( '.carousel-item:first-child.active' ) );
200			} );
201		}
202
203		// Hide next button if the first slide is active
204		if ( next_buttons ) {
205			next_buttons.forEach( next_button => {
206				next_button.classList.toggle( '-hidden', carousel.querySelector( '.carousel-item:last-child.active' ) );
207			} );
208		}
209
210	}
211
212	// Set initial navigation state
213	const init = function() {
214		const carousels_with_hard_stops = document.querySelectorAll( '.carousel[data-bs-wrap="false"]' );
215		if ( carousels_with_hard_stops ) {
216			carousels_with_hard_stops.forEach( carousel => {
217				toggle_navigation( carousel );
218			} );
219		}
220	}
221
222	// On pageload
223	init();
224
225	// On ajax load event
226	document.body.addEventListener( 'ajax-template-loaded', function() {
227		init();
228	} );
229
230	// On slide event (of any carousel)
231	document.addEventListener( 'slid.bs.carousel', function( e ) {
232
233		// Toggle navigation arrows
234		const carousel = e.target;
235		const wrap = carousel.getAttribute( 'data-bs-wrap' );
236		if ( ! wrap || wrap === '0' || wrap === 'false' ) {
237			toggle_navigation( carousel );
238		}
239
240		// Carousel page info component
241		const carousel_page_infos = document.querySelectorAll( '.carousel-page-info[data-bs-target="#' + e.target.id + '"]' );
242		if ( carousel_page_infos ) {
243			carousel_page_infos.forEach( el => {
244				el.querySelector( '.-num' ).textContent = e.to + 1;
245			} );
246		}
247
248	} );
249
250} );
251
252// Tab anchor functionality
253function saas_activate_matching_tab( tab_trigger ) {
254
255	// Exceptions
256	if ( ! tab_trigger ) {
257		return true;
258	}
259	if ( window.getComputedStyle( tab_trigger, null ).display === 'none' ) {
260		return true;
261	}
262	if ( ! document.getElementById( tab_trigger.getAttribute( 'aria-controls' ) ) ) {
263		return true;
264	}
265
266	// Activate the matching tab
267	const tab = new Tab( tab_trigger );
268	tab.show();
269
270	// Scroll to the tab content on page load
271	// Hacky timeout: doesn't scroll on form page unless delayed for 250ms or more
272	setTimeout( () => {
273		tab_trigger.parentElement.scrollIntoView( { behavior: 'smooth', block: 'start' } );
274	}, 250 );
275
276}
277
278function saas_tab_anchor_init() {
279	const tabs_container = document.querySelector( '.nav-tabs' );
280	if ( tabs_container ) {
281
282		// Open the tab based on the ID from the URL and scroll to the tab content
283		let tab_id_from_url = window.location.hash ? window.location.hash.slice(1) : '';
284		if ( tab_id_from_url ) {
285			tab_id_from_url = tab_id_from_url.replace( 'tab_anchor_', '');
286			saas_activate_matching_tab( document.querySelector( `[data-bs-target="#${tab_id_from_url}"]` ) );
287		}
288
289		// Update the URL with the active tab's ID on tab activation
290		document.addEventListener( 'show.bs.tab', function( e ) {
291			const tab_id = 'tab_anchor_' + e.target.getAttribute( 'data-bs-target' ).slice(1);
292			history.replaceState( null, null, `#${tab_id}` );
293		} );
294
295		// Add click event listener to document to check for matching anchors
296		document.addEventListener('click', e => {
297			const clickedElement = e.target;
298			let anchor = clickedElement.getAttribute('href');
299			if (anchor && anchor.includes( 'tab_anchor_' ) ) {
300				let tab_id = anchor.split( 'tab_anchor_' )[1];
301				saas_activate_matching_tab( document.querySelector( `[data-bs-target="#${tab_id}"]` ) );
302			}
303		});
304
305	}
306}
307window.addEventListener( 'load', saas_tab_anchor_init );
308
309document.addEventListener( 'click', function( e ) {
310	if ( e.target.closest( '.saas-expand-text' ) ) {
311		e.stopPropagation();
312		e.preventDefault();
313		e.target.closest( '.saas-expand-text' ).previousSibling.classList.toggle( 'text-truncate-2' );
314	}
315} );
316
317// Gravity Forms JS filters
318if ( typeof gform !== 'undefined' ) {
319	gform.addFilter('gform_file_upload_markup', function (html, file, up, strings, imagesUrl) {
320		var formId = up.settings.multipart_params.form_id,
321			fieldId = up.settings.multipart_params.field_id;
322		html = `<button class='btn gform_delete_file p-0 pb-1' onclick='gformDeleteUploadedFile(${formId},${fieldId}, this);'>
323				<i class='fa-solid fa-trash text-danger'></i>
324				<span class='screen-reader-text'>${strings.delete_file}</span>
325				<span class="text-body fw-normal text-lowercase ps-1">${file.name}</span>
326			</button>`;
327
328		return html;
329	});
330}
331
332// Gravity Forms repeater2 customization
333// window.addEventListener( 'load', () => {
334document.addEventListener( 'gf-repeater2-ready', ( event ) => {
335
336	let repeater_index = 1;
337	let message;
338	while ( document.querySelector( '[data-repeater2-parentid="' + repeater_index + '"]' ) ) {
339
340		let repeater_child_fields = document.querySelectorAll( '[data-repeater2-parentid="' + repeater_index + '"][data-repeater2-repeatid="1"]' );
341		let have_more = document.querySelectorAll( '[data-repeater2-parentid="' + repeater_index + '"][data-repeater2-repeatid="2"]' ).length;
342		if ( repeater_child_fields.length ) {
343
344			let last_field = repeater_child_fields[ repeater_child_fields.length - 1 ];
345			let gf_repeater2_remove = last_field.nextSibling.querySelector( '.gf_repeater2_remove' );
346
347			// If only 1 item is available on page load, make the remove button disabled
348			if ( ! have_more ) {
349				last_field.querySelector( 'label' ).innerHTML += `<a href="#" class="saas_repeater2_remove float-end text-danger pe-none opacity-50">${gf_repeater2_remove.innerHTML}</a>`;
350			}
351			else {
352				last_field.querySelector( 'label' ).innerHTML += `<a href="#" class="saas_repeater2_remove float-end text-danger">${gf_repeater2_remove.innerHTML}</a>`;
353			}
354
355			// Add repeater limit message to be displayed after max item count is met or exceeded
356			message = document.createElement( 'div' );
357			message.innerHTML = theme_ajax_object.translations.gf_repeater2_max_items_message;
358			message.classList.add( 'saas_repeater_limit_message', 'd-none', 'text-danger' );
359			gf_repeater2_remove.closest( '.ginput_container' ).appendChild( message );
360
361		}
362
363		repeater_index += 1;
364
365	}
366
367}, { once : true } );
368
369// GF repeater2 add-row event
370document.addEventListener( 'DOMContentLoaded', function() {
371	if ( typeof jQuery !== 'undefined' ) {
372		jQuery( 'form' ).on( 'gform_repeater2_after_repeat', function( e, repeater_index, repeatId ) {
373
374			const form_id = this.id.match(/\d+/)[0];
375			const form_body = this.querySelector( '.gform_body' );
376			const repeater = gfRepeater_repeater2s[ form_id ][ repeater_index ];
377			const subfields = form_body.querySelectorAll( '[data-repeater2-parentid="' + repeater_index + '"]' );
378			const last_subfield = subfields[ subfields.length - 1 ];
379			const add_button = last_subfield.nextSibling.querySelector( '.gf_repeater2_add' );
380			const remove_buttons = form_body.querySelectorAll( '[data-repeater2-parentid="' + repeater_index + '"] .saas_repeater2_remove' );
381			const limit_message = last_subfield.nextSibling.querySelector( '.saas_repeater_limit_message' );
382
383			// Loop through all the repeater fields and toggle the remove buttons depending on the total item count
384			remove_buttons.forEach( field => {
385				if ( remove_buttons.length === 1 ) {
386					field.classList.add( 'pe-none', 'opacity-50' );
387				} else {
388					field.classList.remove( 'pe-none', 'opacity-50' );
389				}
390			} );
391
392			// Toggle the add button and limit message visibility
393			if ( repeater['settings']['max'] && ( repeater['data']['repeatCount'] >= repeater['settings']['max'] ) ) {
394				add_button.classList.remove( 'd-block' );
395				add_button.classList.add( 'd-none' );
396				limit_message.classList.remove( 'd-none' );
397			}
398			else {
399				limit_message.classList.add( 'd-none' );
400			}
401
402			// Add separators
403			saas_repeater2_redraw_separators( form_id, form_body, repeater_index );
404
405			// Enable tooltips/icons for cloned item
406			saas_enable_tooltips();
407			if ( typeof window.FontAwesome !== 'undefined' ) {
408				//todo Specify element to check
409				window.FontAwesome.dom.i2svg();
410			}
411
412		} );
413	}
414} );
415
416// GF repeater2 remove-row event
417document.addEventListener( 'click', event => {
418
419	const target = event.target;
420	let form_body, form_id, add_button, remove_buttons, limit_message, repeater_index, last_subfield;
421
422	// Remove
423	if ( target.classList.contains( 'saas_repeater2_remove' ) || target.closest( '.saas_repeater2_remove' ) ) {
424
425		event.preventDefault();
426
427		form_body = target.closest( '.gform_body' );
428		form_id = target.closest( 'form' ).id.match(/\d+/)[0];
429		repeater_index = target.closest( '.gfield' ).getAttribute( 'data-repeater2-parentid' );
430		last_subfield = form_body.querySelectorAll( '[data-repeater2-parentid="' + repeater_index + '"]' );
431		last_subfield = last_subfield[ last_subfield.length - 1 ];
432		add_button = last_subfield.nextSibling.querySelector( '.gf_repeater2_add' );
433		limit_message = last_subfield.nextSibling.querySelector( '.saas_repeater_limit_message' );
434		remove_buttons = form_body.querySelectorAll( '[data-repeater2-parentid="' + repeater_index + '"] .saas_repeater2_remove' );
435
436		// Loop through all the repeater fields and toggle the remove buttons depending on the total item count
437		remove_buttons.forEach( field => {
438			if ( remove_buttons.length === 2 ) {
439				field.classList.add( 'pe-none', 'opacity-50' );
440			}
441			else {
442				field.classList.remove( 'pe-none', 'opacity-50' );
443			}
444		} );
445
446		// Remove current item
447		gfRepeater_unrepeatRepeater( form_id, repeater_index, target.closest( '.gfield' ).getAttribute( 'data-repeater2-repeatid' ) );
448
449		// Toggle the add button and limit message visibility
450		add_button.classList.add( 'd-block' );
451		add_button.classList.remove( 'd-none' );
452		limit_message.classList.add( 'd-none' );
453
454		// Enable tooltips/icons for cloned item
455		saas_enable_tooltips();
456		if ( typeof window.FontAwesome !== 'undefined' ) {
457			//todo Specify element to check
458			window.FontAwesome.dom.i2svg();
459		}
460
461		// Redraw separators
462		saas_repeater2_redraw_separators( form_id, form_body, repeater_index );
463
464	}
465
466} );
467
468/**
469 * Remove+add line-separators between repeater rows.
470 *
471 * @param {int} form_id
472 * @param {Node} form_body
473 * @param {int} repeater_index
474 */
475function saas_repeater2_redraw_separators( form_id, form_body, repeater_index ) {
476
477	// If separators are enabled
478	const repeater = gfRepeater_repeater2s[ form_id ][ repeater_index ];
479	if ( repeater.controllers.start[0].classList.contains( '-separator' ) ) {
480
481		const subfields = form_body.querySelectorAll( '[data-repeater2-parentid="' + repeater_index + '"]' );
482		const last_subfields_except_last = Array.from( subfields ).filter( field => {
483			return field.getAttribute( 'data-repeater2-childid' ) == repeater.data.childrenCount && field !== subfields[ subfields.length - 1 ];
484		} );
485
486		// Remove all existing separators
487		const separators = form_body.querySelectorAll( '.saas_repeater2_separator--' + repeater_index );
488		separators.forEach( separator => {
489			separator.remove();
490		} );
491
492		// Add new ones
493		const separator = document.createElement( 'div' );
494		separator.classList.add( 'col-12', 'saas_repeater2_separator--' + repeater_index );
495		separator.innerHTML = '<hr class="-saas_repeater2-separator">';
496		last_subfields_except_last.forEach( field => {
497			field.after( separator.cloneNode( true ) );
498		} );
499
500	}
501
502}
503
504// Disable the Gravity Forms button on click
505document.addEventListener('DOMContentLoaded', function () {
506	const submitButtons = document.querySelectorAll('.saas-form-modal [id^="gform_submit_button_"]');
507
508	if (submitButtons.length > 0) {
509		// Disable the submit button on click
510		submitButtons.forEach(function (submitButton) {
511			submitButton.addEventListener('click', function () {
512				// Wait for the Gravity Forms validation to start
513				setTimeout( () => {
514					submitButton.setAttribute('disabled', 'disabled');
515				}, 100 );
516			});
517		});
518	}
519});
520
521// Max-1 checkbox group
522// You are allowed to check another box in the same group but it will uncheck all others. You can still uncheck all, which differentiates it from a radio group.
523document.addEventListener( 'click', function( e ) {
524	if ( e.target.getAttribute( 'data-max1-group' ) ) {
525
526		const input = e.target;
527		const group = input.getAttribute( 'data-max1-group' );
528		const siblings = document.querySelectorAll( '[data-max1-group="' + group + '"]' );
529		siblings.forEach( ( sibling ) => {
530			if ( ! sibling.isSameNode( input ) ) {
531				sibling.checked = false;
532			}
533		} );
534
535	}
536} );
537
538// Un-hide element from first scroll event
539window.addEventListener( 'scroll', ( event ) => {
540	const els = document.querySelectorAll( '.-show-me-on-scroll' );
541	els.forEach( ( el ) => {
542		el.classList.add( '-showing' );
543	} );
544}, { once: true } );
545
546// Maintain megamenu visibility while touching inside it on mobile
547const SAAS_MEGAMENU_TOUCH_CLASS = '-touch-active';
548
549const saasHandleMegaTap = ( event ) => {
550	const activeItems = document.querySelectorAll( '.owmenu-li-has-mega.' + SAAS_MEGAMENU_TOUCH_CLASS );
551	const target = event.target instanceof Element ? event.target : event.target?.parentElement;
552	const innerLink = target ? target.closest( '.owmenu-megamenu a[href]' ) : null;
553	const inMegaMenu = target ? target.closest( '.owmenu-megamenu' ) : null;
554	const menuItem = target ? target.closest( '.owmenu-li-has-mega' ) : null;
555
556	// Allow real links inside megamenu to navigate normally.
557	if ( inMegaMenu && innerLink && innerLink.getAttribute( 'href' ) && innerLink.getAttribute( 'href' ) !== '#' ) {
558		return;
559	}
560	// Keep megamenu open when tapping non-link content inside it.
561	if ( inMegaMenu && ! innerLink ) {
562		event.preventDefault();
563		return;
564	}
565
566	activeItems.forEach( ( item ) => {
567		if ( item !== menuItem && ! item.contains( target ) ) {
568			item.classList.remove( SAAS_MEGAMENU_TOUCH_CLASS );
569		}
570	} );
571
572	if ( menuItem ) {
573
574		event.preventDefault();
575
576
577		if ( menuItem.classList.contains( SAAS_MEGAMENU_TOUCH_CLASS ) ) {
578			menuItem.classList.remove( SAAS_MEGAMENU_TOUCH_CLASS );
579			return;
580		}
581
582		menuItem.classList.add( SAAS_MEGAMENU_TOUCH_CLASS );
583	}
584};
585
586let saasLastTouchTs = 0;
587
588document.addEventListener( 'click', ( event ) => {
589	// Ignore synthetic clicks that follow a touch.
590	if ( Date.now() - saasLastTouchTs < 800 ) {
591		return;
592	}
593
594	saasHandleMegaTap( event );
595} );
596
597document.addEventListener( 'keyup', ( event ) => {
598	if ( event.key !== 'Escape' ) {
599		return;
600	}
601
602	document.querySelectorAll( '.owmenu-li-has-mega.' + SAAS_MEGAMENU_TOUCH_CLASS ).forEach( ( item ) => {
603		item.classList.remove( SAAS_MEGAMENU_TOUCH_CLASS );
604	} );
605} );
606
607// Trigger event for scripts that are waiting for these methods to be available.
608document.dispatchEvent( new CustomEvent( 'saas-themejs-ready' ) );
609
610// Download file with a token
611//
612// Delegated handler for `.download-pending` anchors: the API download routes
613// sit behind auth:api, but a plain link navigation cannot carry the bearer
614// token — so the click is intercepted here and the href is fetched with the
615// Authorization header, then served to the browser as a blob.
616//
617// Anchors that also carry `.download-once` are throwaways (created dynamically,
618// e.g. from an API `urls` response). For those we skip the restore/re-add
619// cleanup and remove the element after the download fires.
620document.addEventListener( 'click', ( event ) => {
621	if ( event.target.classList.contains( 'download-pending' ) ) {
622
623		let anchor = event.target;
624		let is_once = anchor.classList.contains( 'download-once' );
625		let file_path = anchor.href;
626
627		// Only send the bearer token to our own endpoints (allowlist). Exter
627nal
628		// URLs — e.g. presigned object-storage links — are self-authenticating
629		// via their query string and force the download via their
630		// Content-Disposition header. Sending our token to them would both
631		// trigger a CORS preflight the storage host rejects and leak the token to
632		// a third-party origin, so let the browser download them natively. The
633		// list of bases is provided by theme_ajax_object.bearer_token_base_urls
634		// (filterable in PHP via the `saas_bearer_token_base_urls` filter).
635		let bearer_token_base_urls = ( window.theme_ajax_object && theme_ajax_object.bearer_token_base_urls ) || [];
636		if ( ! Array.isArray( bearer_token_base_urls ) ) {
637			bearer_token_base_urls = [ bearer_token_base_urls ];
638		}
639		let attach_token = bearer_token_base_urls.some( function ( base ) {
640			return base && file_path.indexOf( base ) === 0;
641		} );
642
643		if ( ! attach_token ) {
644			// Do not preventDefault: allow the native download to proceed.
645			if ( is_once ) {
646				setTimeout( function () { anchor.remove(); }, 0 );
647			}
648			return;
649		}
650
651		event.preventDefault();
652
653		let filename = anchor.innerText;
654		let headers = new Headers();
655		headers.append( 'Authorization', 'Bearer ' + User_Token.get_token() );
656		anchor.disabled = true;
657
658		fetch( file_path, { headers } )
659			.then( response => {
660
661				// Prefer the server-provided filename (Content-Disposition);
662				// fall back to the link text for static, pre-rendered links.
663				let disposition = response.headers.get( 'Content-Disposition' );
664				if ( disposition && disposition.indexOf( 'filename' ) !== -1 ) {
665					let matches = disposition.match( /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i );
666					if ( matches && matches[ 1 ] ) {
667						filename = matches[ 1 ].trim();
668					}
669				}
670
671				return response.blob();
672			} )
673			.then( blobby => {
674
675				let objectUrl = window.URL.createObjectURL( blobby );
676
677				// Drop the trigger class before the synthetic click so this
678				// delegated handler does not re-enter on the blob navigation.
679				anchor.classList.remove( 'download-pending' );
680				anchor.href = objectUrl;
681				anchor.download = filename;
682				anchor.click();
683
684				window.URL.revokeObjectURL( objectUrl );
685
686				if ( is_once ) {
687					anchor.remove();
688				} else {
689					anchor.href = file_path;
690					anchor.disabled = false;
691					anchor.classList.add( 'download-pending' );
692				}
693
694			} )
695			.catch( () => {
696				// Never leave a throwaway anchor stuck in the DOM on failure.
697				if ( is_once ) {
698					anchor.remove();
699				} else {
700					anchor.disabled = false;
701				}
702			} );
703
704	}
705} );
706
707// Reload fontawesome on gravity forms ajax response.
708document.addEventListener( 'DOMContentLoaded', function() {
709	if ( typeof jQuery !== 'undefined' ) {
710		jQuery( document ).on( 'gform_post_render', function() {
711			if ( typeof window.FontAwesome !== 'undefined' ) {
712				//todo Specify element to check
713				window.FontAwesome.dom.i2svg();
714			}
715		} );
716	}
717} );
718
719/* Safari: Prevent megamenu from closing when clicking links inside it on mobile */
720// On mobile Safari, clicking a link inside a megamenu can cause :focus-within to become false before navigation, causing the menu to close. This handler ensures the click proceeds.
721/*
722function handleMegamenuClick( e ) {
723console.log( 'event start', screen.width, theme_ajax_object.owmenu.breakpoint );
724	// if ( screen.width < theme_ajax_object.owmenu.breakpoint ) {
725// console.log( 'Screen width ok' );
726
727		let megamenu = null;
728		let link = null;
729		var path = e.composedPath ? e.composedPath() : [];
730		for ( var i = 0; i < path.length; i++ ) {
731			if ( path[i] && path[i].classList && path[i].classList.contains( 'owmenu-megamenu' ) ) {
732				megamenu = path[i];
733				break;
734			}
735		}
736console.log( 'Megamenu', megamenu, path );
737
738		if ( megamenu ) {
739
740			for ( var i = 0; i < path.length; i++ ) {
741				if ( path[i] && path[i].tagName === 'A' ) {
742					// Check if this link is inside the megamenu we found
743					if ( megamenu.contains( path[i] ) || path[i].closest( '.owmenu-megamenu' ) === megamenu ) {
744						link = path[i];
745						break;
746					}
747				}
748			}
749			if ( ! link ) {
750				var target = e.target;
751				if ( target.tagName === 'A' ) {
752					link = target;
753				} else {
754					// Try to find a link that contains the target or is near it
755					var potentialLink = target.closest( 'a' );
756					if ( potentialLink && megamenu.contains( potentialLink ) ) {
757						link = potentialLink;
758					}
759				}
760			}
761console.log( 'Link', link, path );
762
763			// if ( link && link.href && link.href !== '#' && link.getAttribute( 'href' ) !== '' ) {
764			if ( link && link.href && link.href !== '#' && link.getAttribute( 'href' ) !== '' && link.href !== window.location.href + '#' ) {
765				const megamenuParent = link.closest( '.owmenu-li-has-mega' );
766console.log( 'Megamenu parent', megamenuParent );
767				if ( megamenuParent ) {
768					megamenuParent.classList.add( '-focus-within' );
769					console.log( 'Added the class!' );
770					setTimeout( function() {
771						megamenuParent.classList.remove( '-focus-within' );
772					}, 500 );
773				}
774			}
775
776		}
777	// }
778}
779
780document.addEventListener( 'touchstart', handleMegamenuClick, true );
781document.addEventListener( 'click', handleMegamenuClick, true );
782console.log( 'vcheck' );
783*/

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.