PageSourceSearch

https://menstherapyhub.co.uk/wp-admin/js/common.js?ver=7.1.2

js menstherapyhub.co.uk collected 2026-09-29 00:56:32 UTC 70,421 bytes, 2,597 lines download raw bytes

1/**
2 * @output wp-admin/js/common.js
3 */
4
5/* global setUserSetting, ajaxurl, alert, confirm, pagenow */
6/* global columns, screenMeta */
7
8/**
9 *  Adds common WordPress functionality to the window.
10 *
11 *  @param {jQuery} $        jQuery object.
12 *  @param {Object} window   The window object.
13 *  @param {mixed} undefined Unused.
14 */
15( function( $, window, undefined ) {
16	var $document = $( document ),
17		$window = $( window ),
18		$body = $( document.body ),
19		__ = wp.i18n.__,
20		sprintf = wp.i18n.sprintf;
21
22/**
23 * Throws an error for a deprecated property.
24 *
25 * @since 5.5.1
26 *
27 * @param {string} propName    The property that was used.
28 * @param {string} version     The version of WordPress that deprecated the property.
29 * @param {string} replacement The property that should have been used.
30 */
31function deprecatedProperty( propName, version, replacement ) {
32	var message;
33
34	if ( 'undefined' !== typeof replacement ) {
35		message = sprintf(
36			/* translators: 1: Deprecated property name, 2: Version number, 3: Alternative property name. */
37			__( '%1$s is deprecated since version %2$s! Use %3$s instead.' ),
38			propName,
39			version,
40			replacement
41		);
42	} else {
43		message = sprintf(
44			/* translators: 1: Deprecated property name, 2: Version number. */
45			__( '%1$s is deprecated since version %2$s with no alternative available.' ),
46			propName,
47			version
48		);
49	}
50
51	window.console.warn( message );
52}
53
54/**
55 * Deprecate all properties on an object.
56 *
57 * @since 5.5.1
58 * @since 5.6.0 Added the `version` parameter.
59 *
60 * @param {string} name       The name of the object, i.e. commonL10n.
61 * @param {object} l10nObject The object to deprecate the properties on.
62 * @param {string} version    The version of WordPress that deprecated the property.
63 *
64 * @return {object} The object with all its properties deprecated.
65 */
66function deprecateL10nObject( name, l10nObject, version ) {
67	var deprecatedObject = {};
68
69	Object.keys( l10nObject ).forEach( function( key ) {
70		var prop = l10nObject[ key ];
71		var propName = name + '.' + key;
72
73		if ( 'object' === typeof prop ) {
74			Object.defineProperty( deprecatedObject, key, { get: function() {
75				deprecatedProperty( propName, version, prop.alternative );
76				return prop.func();
77			} } );
78		} else {
79			Object.defineProperty( deprecatedObject, key, { get: function() {
80				deprecatedProperty( propName, version, 'wp.i18n' );
81				return prop;
82			} } );
83		}
84	} );
85
86	return deprecatedObject;
87}
88
89window.wp.deprecateL10nObject = deprecateL10nObject;
90
91/**
92 * Removed in 5.5.0, needed for back-compatibility.
93 *
94 * @since 2.6.0
95 * @deprecated 5.5.0
96 */
97window.commonL10n = window.commonL10n || {
98	warnDelete: '',
99	dismiss: '',
100	collapseMenu: '',
101	expandMenu: ''
102};
103
104window.commonL10n = deprecateL10nObject( 'commonL10n', window.commonL10n, '5.5.0' );
105
106/**
107 * Removed in 5.5.0, needed for back-compatibility.
108 *
109 * @since 3.3.0
110 * @deprecated 5.5.0
111 */
112window.wpPointerL10n = window.wpPointerL10n || {
113	dismiss: ''
114};
115
116window.wpPointerL10n = deprecateL10nObject( 'wpPointerL10n', window.wpPointerL10n, '5.5.0' );
117
118/**
119 * Removed in 5.5.0, needed for back-compatibility.
120 *
121 * @since 4.3.0
122 * @deprecated 5.5.0
123 */
124window.userProfileL10n = window.userProfileL10n || {
125	warn: '',
126	warnWeak: '',
127	show: '',
128	hide: '',
129	cancel: '',
130	ariaShow: '',
131	ariaHide: ''
132};
133
134window.userProfileL10n = deprecateL10nObject( 'userProfileL10n', window.userProfileL10n, '5.5.0' );
135
136/**
137 * Removed in 5.5.0, needed for back-compatibility.
138 *
139 * @since 4.9.6
140 * @deprecated 5.5.0
141 */
142window.privacyToolsL10n = window.privacyToolsL10n || {
143	noDataFound: '',
144	foundAndRemoved: '',
145	noneRemoved: '',
146	someNotRemoved: '',
147	removalError: '',
148	emailSent: '',
149	noExportFile: '',
150	exportError: ''
151};
152
153window.privacyToolsL10n = deprecateL10nObject( 'privacyToolsL10n', window.privacyToolsL10n, '5.5.0' );
154
155/**
156 * Removed in 5.5.0, needed for back-compatibility.
157 *
158 * @since 3.6.0
159 * @deprecated 5.5.0
160 */
161window.authcheckL10n = {
162	beforeunload: ''
163};
164
165window.authcheckL10n = window.authcheckL10n || deprecateL10nObject( 'authcheckL10n', window.authcheckL10n, '5.5.0' );
166
167/**
168 * Removed in 5.5.0, needed for back-compatibility.
169 *
170 * @since 2.8.0
171 * @deprecated 5.5.0
172 */
173window.tagsl10n = {
174	noPerm: '',
175	broken: ''
176};
177
178window.tagsl10n = window.tagsl10n || deprecateL10nObject( 'tagsl10n', window.tagsl10n, '5.5.0' );
179
180/**
181 * Removed in 5.5.0, needed for back-compatibility.
182 *
183 * @since 2.5.0
184 * @deprecated 5.5.0
185 */
186window.adminCommentsL10n = window.adminCommentsL10n || {
187	hotkeys_highlight_first: {
188		alternative: 'window.adminCommentsSettings.hotkeys_highlight_first',
189		func: function() { return window.adminCommentsSettings.hotkeys_highlight_first; }
190	},
191	hotkeys_highlight_last: {
192		alternative: 'window.adminCommentsSettings.hotkeys_highlight_last',
193		func: function() { return window.adminCommentsSettings.hotkeys_highlight_last; }
194	},
195	replyApprove: '',
196	reply: '',
197	warnQuickEdit: '',
198	warnCommentChanges: '',
199	docTitleComments: '',
200	docTitleCommentsCount: ''
201};
202
203window.adminCommentsL10n = deprecateL10nObject( 'adminCommentsL10n', window.adminCommentsL10n, '5.5.0' );
204
205/**
206 * Removed in 5.5.0, needed for back-compatibility.
207 *
208 * @since 2.5.0
209 * @deprecated 5.5.0
210 */
211window.tagsSuggestL10n = window.tagsSuggestL10n || {
212	tagDelimiter: '',
213	removeTerm: '',
214	termSelected: '',
215	termAdded: '',
216	termRemoved: ''
217};
218
219window.tagsSuggestL10n = deprecateL10nObject( 'tagsSuggestL10n', window.tagsSuggestL10n, '5.5.0' );
220
221/**
222 * Removed in 5.5.0, needed for back-compatibility.
223 *
224 * @since 3.5.0
225 * @deprecated 5.5.0
226 */
227window.wpColorPickerL10n = window.wpColorPickerL10n || {
228	clear: '',
229	clearAriaLabel: '',
230	defaultString: '',
231	defaultAriaLabel: '',
232	pick: '',
233	defaultLabel: ''
234};
235
236window.wpColorPickerL10n = deprecateL10nObject( 'wpColorPickerL10n', window.wpColorPickerL10n, '5.5.0' );
237
238/**
239 * Removed in 5.5.0, needed for back-compatibility.
240 *
241 * @since 2.7.0
242 * @deprecated 5.5.0
243 */
244window.attachMediaBoxL10n = window.attachMediaBoxL10n || {
245	error: ''
246};
247
248window.attachMediaBoxL10n = deprecateL10nObject( 'attachMediaBoxL10n', window.attachMediaBoxL10n, '5.5.0' );
249
250/**
251 * Removed in 5.5.0, needed for back-compatibility.
252 *
253 * @since 2.5.0
254 * @deprecated 5.5.0
255 */
256window.postL10n = window.postL10n || {
257	ok: '',
258	cancel: '',
259	publishOn: '',
260	publishOnFuture: '',
261	publishOnPast: '',
262	dateFormat: '',
263	showcomm: '',
264	endcomm: '',
265	publish: '',
266	schedule: '',
267	update: '',
268	savePending: '',
269	saveDraft: '',
270	'private': '',
271	'public': '',
272	publicSticky: '',
273	password: '',
274	privatelyPublished: '',
275	published: '',
276	saveAlert: '',
277	savingText: '',
278	permalinkSaved: ''
279};
280
281window.postL10n = deprecateL10nObject( 'postL10n', window.postL10n, '5.5.0' );
282
283/**
284 * Removed in 5.5.0, needed for back-compatibility.
285 *
286 * @since 2.7.0
287 * @deprecated 5.5.0
288 */
289window.inlineEditL10n = window.inlineEditL10n || {
290	error: '',
291	ntdeltitle: '',
292	notitle: '',
293	comma: '',
294	saved: ''
295};
296
297window.inlineEditL10n = deprecateL10nObject( 'inlineEditL10n', window.inlineEditL10n, '5.5.0' );
298
299/**
300 * Removed in 5.5.0, needed for back-compatibility.
301 *
302 * @since 2.7.0
303 * @deprecated 5.5.0
304 */
305window.plugininstallL10n = window.plugininstallL10n || {
306	plugin_information: '',
307	plugin_modal_label: '',
308	ays: ''
309};
310
311window.plugininstallL10n = deprecateL10nObject( 'plugininstallL10n', window.plugininstallL10n, '5.5.0' );
312
313/**
314 * Removed in 5.5.0, needed for back-compatibility.
315 *
316 * @since 3.0.0
317 * @deprecated 5.5.0
318 */
319window.navMenuL10n = window.navMenuL10n || {
320	noResultsFound: '',
321	warnDeleteMenu: '',
322	saveAlert: '',
323	untitled: ''
324};
325
326window.navMenuL10n = deprecateL10nObject( 'navMenuL10n', window.navMenuL10n, '5.5.0' );
327
328/**
329 * Removed in 5.5.0, needed for back-compatibility.
330 *
331 * @since 2.5.0
332 * @deprecated 5.5.0
333 */
334window.commentL10n = window.commentL10n || {
335	submittedOn: '',
336	dateFormat: ''
337};
338
339window.commentL10n = deprecateL10nObject( 'commentL10n', window.commentL10n, '5.5.0' );
340
341/**
342 * Removed in 5.5.0, needed for back-compatibility.
343 *
344 * @since 2.9.0
345 * @deprecated 5.5.0
346 */
347window.setPostThumbnailL10n = window.setPostThumbnailL10n || {
348	setThumbnail: '',
349	saving: '',
350	error: '',
351	done: ''
352};
353
354window.setPostThumbnailL10n = deprecateL10nObject( 'setPostThumbnailL10n', window.setPostThumbnailL10n, '5.5.0' );
355
356/**
357 * Removed in 6.5.0, needed for back-compatibility.
358 *
359 * @since 4.5.0
360 * @deprecated 6.5.0
361 */
362window.uiAutocompleteL10n = window.uiAutocompleteL10n || {
363	noResults: '',
364	oneResult: '',
365	manyResults: '',
366	itemSelected: ''
367};
368
369window.uiAutocompleteL10n = deprecateL10nObject( 'uiAutocompleteL10n', window.uiAutocompleteL10n, '6.5.0' );
370
371/**
372 * Removed in 3.3.0, needed for back-compatibility.
373 *
374 * @since 2.7.0
375 * @deprecated 3.3.0
376 */
377window.adminMenu = {
378	init : function() {},
379	fold : function() {},
380	restoreMenuState : function() {},
381	toggle : function() {},
382	favorites : function() {}
383};
384
385// Show/hide/save table columns.
386window.columns = {
387
388	/**
389	 * Initializes the column toggles in the screen options.
390	 *
391	 * Binds an onClick event to the checkboxes to show or hide the table columns
392	 * based on their toggled state. And persists the toggled state.
393	 *
394	 * @since 2.7.0
395	 *
396	 * @return {void}
397	 */
398	init : function() {
399		var that = this;
400		$('.hide-column-tog', '#adv-settings').on( 'click', function() {
401			var $t = $(this), column = $t.val();
402			if ( $t.prop('checked') )
403				that.checked(column);
404			else
405				that.unchecked(column);
406
407			columns.saveManageColumnsState();
408		});
409	},
410
411	/**
412	 * Saves the toggled state for the columns.
413	 *
414	 * Saves whether the columns should be shown or hidden on a page.
415	 *
416	 * @since 3.0.0
417	 *
418	 * @return {void}
419	 */
420	saveManageColumnsState : function() {
421		var hidden = this.hidden();
422		$.post(
423			ajaxurl,
424			{
425				action: 'hidden-columns',
426				hidden: hidden,
427				screenoptionnonce: $('#screenoptionnonce').val(),
428				page: pagenow
429			},
430			function() {
431				wp.a11y.speak( __( 'Screen Options updated.' ) );
432			}
433		);
434	},
435
436	/**
437	 * Makes a column visible and adjusts the column span for the table.
438	 *
439	 * @since 3.0.0
440	 * @param {string} column The column name.
441	 *
442	 * @return {void}
443	 */
444	checked : function(column) {
445		$('.column-' + column).removeClass( 'hidden' );
446		this.colSpanChange(+1);
447	},
448
449	/**
450	 * Hides a column and adjusts the column span for the table.
451	 *
452	 * @since 3.0.0
453	 * @param {string} column The column name.
454	 *
455	 * @return {void}
456	 */
457	unchecked : function(column) {
458		$('.column-' + column).addClass( 'hidden' );
459		this.colSpanChange(-1);
460	},
461
462	/**
463	 * Gets all hidden columns.
464	 *
465	 * @since 3.0.0
466	 *
467	 * @return {string} The hidden column names separated by a comma.
468	 */
469	hidden : function() {
470		return $( '.manage-column[id]' ).filter( '.hidden' ).map(function() {
471			return this.id;
472		}).get().join( ',' );
473	},
474
475	/**
476	 * Gets the checked column toggles from the screen options.
477	 *
478	 * @since 3.0.0
479	 *
480	 * @return {string} String containing the checked column names.
481	 */
482	useCheckboxesForHidden : function() {
483		this.hidden = function(){
484			return $('.hide-column-tog').not(':checked').map(function() {
485				var id = this.id;
486				return id.substring( id, id.length - 5 );
487			}).get().join(',');
488		};
489	},
490
491	/**
492	 * Adjusts the column span for the table.
493	 *
494	 * @since 3.1.0
495	 *
496	 * @param {number} diff The modifier for the column span.
497	 */
498	colSpanChange : function(diff) {
499		var $t = $('table').find('.colspanchange'), n;
500		if ( !$t.length )
501			return;
502		n = parseInt( $t.attr('colspan'), 10 ) + diff;
503		$t.attr('colspan', n.toString());
504	}
505};
506
507$( function() { columns.init(); } );
508
509/**
510 * Validates that the required form fields are not empty.
511 *
512 * @since 2.9.0
513 *
514 * @param {jQuery} form The form to validate.
515 *
516 * @return {boolean} Returns true if all required fields are not an empty string.
517 */
518window.validateForm = function( form ) {
519	return !$( form )
520		.find( '.form-required' )
521		.filter( function() { return $( ':input:visible', this ).val() === ''; } )
522		.addClass( 'form-invalid' )
523		.find( ':input:visible' )
524		.on( 'change', function() { $( this ).closest( '.form-invalid' ).removeClass( 'form-invalid' ); } )
525		.length;
526};
527
528// Stub for doing better warnings.
529/**
530 * Shows message pop-up notice or confirmation message.
531 *
532 * @since 2.7.0
533 *
534 * @type {{warn: showNotice.warn, note: showNotice.note}}
535 *
536 * @return {void}
537 */
538window.showNotice = {
539
540	/**
541	 * Shows a delete confirmation pop-up message.
542	 *
543	 * @since 2.7.0
544	 *
545	 * @return {boolean} Returns true if the message is confirmed.
546	 */
547	warn : function() {
548		if ( confirm( __( 'You are about to permanently delete these items from your site.\nThis action cannot be undone.\n\'Cancel\' to stop, \'OK\' to delete.' ) ) ) {
549			return true;
550		}
551
552		return false;
553	},
554
555	/**
556	 * Shows an alert message.
557	 *
558	 * @since 2.7.0
559	 *
560	 * @param text The text to display in the message.
561	 */
562	note : function(text) {
563		alert(text);
564	}
565};
566
567/**
568 * Represents the functions for the meta screen options panel.
569 *
570 * @since 3.2.0
571 *
572 * @type {{element: null, toggles: null, page: null, init: screenMeta.init,
573 *         toggleEvent: screenMeta.toggleEvent, open: screenMeta.open,
574 *         close: screenMeta.close}}
575 *
576 * @return {void}
577 */
578window.screenMeta = {
579	element: null, // #screen-meta
580	toggles: null, // .screen-meta-toggle
581	page:    null, // #wpcontent
582
583	/**
584	 * Initializes the screen meta options panel.
585	 *
586	 * @since 3.2.0
587	 *
588	 * @return {void}
589	 */
590	init: function() {
591		this.element = $('#screen-meta');
592		this.toggles = $( '#screen-meta-links' ).find( '.show-settings' );
593		this.page    = $('#wpcontent');
594
595		this.toggles.on( 'click', this.toggleEvent );
596	},
597
598	/**
599	 * Toggles the screen meta options panel.
600	 *
601	 * @since 3.2.0
602	 *
603	 * @return {void}
604	 */
605	toggleEvent: function() {
606		var panel = $( '#' + $( this ).attr( 'aria-controls' ) );
607
608		if ( !panel.length )
609			return;
610
611		if ( panel.is(':visible') )
612			screenMeta.close( panel, $(this) );
613		else
614			screenMeta.open( panel, $(this) );
615	},
616
617	/**
618	 * Opens the screen meta options panel.
619	 *
620	 * @since 3.2.0
621	 *
622	 * @param {jQuery} panel  The screen meta options panel div.
623	 * @param {jQuery} button The toggle button.
624	 *
625	 * @return {void}
626	 */
627	open: function( panel, button ) {
628
629		$( '#screen-meta-links' ).find( '.screen-meta-toggle' ).not( button.parent() ).css( 'visibility', 'hidden' );
630
631		panel.parent().show();
632
633		/**
634		 * Sets the focus to the meta options panel and adds the necessary CSS classes.
635		 *
636		 * @since 3.2.0
637		 *
638		 * @return {void}
639		 */
640		panel.slideDown( 'fast', function() {
641			panel.removeClass( 'hidden' ).trigger( 'focus' );
642			button.addClass( 'screen-meta-active' ).attr( 'aria-expanded', true );
643		});
644
645		$document.trigger( 'screen:options:open' );
646	},
647
648	/**
649	 * Closes the screen meta options panel.
650	 *
651	 * @since 3.2.0
652	 *
653	 * @param {jQuery} panel  The screen meta options panel div.
654	 * @param {jQuery} button The toggle button.
655	 *
656	 * @return {void}
657	 */
658	close: function( panel, button ) {
659		/**
660		 * Hides the screen meta options panel.
661		 *
662		 * @since 3.2.0
663		 *
664		 * @return {void}
665		 */
666		panel.slideUp( 'fast', function() {
667			button.removeClass( 'screen-meta-active' ).attr( 'aria-expanded', false );
668			$('.screen-meta-toggle').css('visibility', '');
669			panel.parent().hide();
670			panel.addClass( 'hidden' );
671		});
672
673		$document.trigger( 'screen:options:close' );
674	}
675};
676
677/**
678 * Initializes the help tabs in the help panel.
679 *
680 * @param {Event} e The event object.
681 *
682 * @return {void}
683 */
684$('.contextual-help-tabs').on( 'click', 'a', function(e) {
685	var link = $(this),
686		panel;
687
688	e.preventDefault();
689
690	// Don't do anything if the click is for the tab already showing.
691	if ( link.is('.active a') )
692		return false;
693
694	// Links.
695	$('.contextual-help-tabs .active').removeClass('active');
696	link.parent('li').addClass('active');
697
698	panel = $( link.attr('href') );
699
700	// Panels.
701	$('.help-tab-content').not( panel ).removeClass('active').hide();
702	panel.addClass('active').show();
703});
704
705/**
706 * Update custom permalink structure via buttons.
707 */
708var permalinkStructureFocused = false,
709    $permalinkStructure       = $( '#permalink_structure' ),
710    $permalinkStructureInputs = $( '.permalink-structure input:radio' ),
711    $permalinkCustomSelection = $( '#custom_selection' ),
712    $availableStructureTags   = $( '.form-table.permalink-structure .available-structure-tags button' );
713
714// Change permalink structure input when selecting one of the common structures.
715$permalinkStructureInputs.on( 'change', function() {
716	if ( 'custom' === this.value ) {
717		return;
718	}
719
720	$permalinkStructure.val( this.value );
721
722	// Update button states after selection.
723	$availableStructureTags.each( function() {
724		changeStructureTagButtonState( $( this ) );
725	} );
726} );
727
728$permalinkStructure.on( 'click input', function() {
729	$permalinkCustomSelection.prop( 'checked', true );
730} );
731
732// Check if the permalink structure input field has had focus at least once.
733$permalinkStructure.on( 'focus', function( event ) {
734	permalinkStructureFocused = true;
735	$( this ).off( event );
736} );
737
738/**
739 * Enables or disables a structure tag button depending on its usage.
740 *
741 * If the structure is already used in the custom permalink structure,
742 * it will be disabled.
743 *
744 * @param {Object} button Button jQuery object.
745 */
746function changeStructureTagButtonState( button ) {
747	if ( -1 !== $permalinkStructure.val().indexOf( button.text().trim() ) ) {
748		button.attr( 'data-label', button.attr( 'aria-label' ) );
749		button.attr( 'aria-label', button.attr( 'data-used' ) );
750		button.attr( 'aria-pressed', true );
751		button.addClass( 'active' );
752	} else if ( button.attr( 'data-label' ) ) {
753		button.attr( 'aria-label', button.attr( 'data-label' ) );
754		button.attr( 'aria-pressed', false );
755		button.removeClass( 'active' );
756	}
757}
758
759// Check initial button state.
760$availableStructureTags.each( function() {
761	changeStructureTagButtonState( $( this ) );
762} );
763
764// Observe permalink structure field and disable buttons of tags that are already present.
765$permalinkStructure.on( 'change', function() {
766	$availableStructureTags.each( function() {
767		changeStructureTagButtonState( $( this ) );
768	} );
769} );
770
771$availableStructureTags.on( 'click', function() {
772	var permalinkStructureValue = $permalinkStructure.val(),
773	    selectionStart          = $permalinkStructure[ 0 ].selectionStart,
774	    selectionEnd            = $permalinkStructure[ 0 ].selectionEnd,
775	    textToAppend            = $( this ).text().trim(),
776	    textToAnnounce,
777	    newSelectionStart;
778
779	if ( $( this ).hasClass( 'active' ) ) {
780		textToAnnounce = $( this ).attr( 'data-removed' );
781	} else {
782		textToAnnounce = $( this ).attr( 'data-added' );
783	}
784
785	// Remove structure tag if already part of the structure.
786	if ( -1 !== permalinkStructureValue.indexOf( textToAppend ) ) {
787		permalinkStructureValue = permalinkStructureValue.replace( textToAppend + '/', '' );
788
789		$permalinkStructure.val( '/' === permalinkStructureValue ? '' : permalinkStructureValue );
790
791		// Announce change to screen readers.
792		$( '#custom_selection_updated' ).text( textToAnnounce );
793
794		// Disable button.
795		changeStructureTagButtonState( $( this ) );
796
797		return;
798	}
799
800	// Input field never had focus, move selection to end of input.
801	if ( ! permalinkStructureFocused && 0 === selectionStart && 0 === selectionEnd ) {
802		selectionStart = selectionEnd = permalinkStructureValue.length;
803	}
804
805	$permalinkCustomSelection.prop( 'checked', true );
806
807	// Prepend and append slashes if necessary.
808	if ( '/' !== permalinkStructureValue.substr( 0, selectionStart ).substr( -1 ) ) {
809		textToAppend = '/' + textToAppend;
810	}
811
812	if ( '/' !== permalinkStructureValue.substr( selectionEnd, 1 ) ) {
813		textToAppend = textToAppend + '/';
814	}
815
816	// Insert structure tag at the specified position.
817	$permalinkStructure.val( permalinkStructureValue.substr( 0, selectionStart ) + textToAppend + permalinkStructureValue.substr( selectionEnd ) );
818
819	// Announce change to screen readers.
820	$( '#custom_selection_updated' ).text( textToAnnounce );
821
822	// Disable button.
823	changeStructureTagButtonState( $( this ) );
824
825	// If input had focus give it back with cursor right after appended text.
826	if ( permalinkStructureFocused && $permalinkStructure[0].setSelectionRange ) {
827		newSelectionStart = ( permalinkStructureValue.substr( 0, selectionStart ) + textToAppend ).length;
828		$permalinkStructure[0].setSelectionRange( newSelectionStart, newSelectionStart );
829		$permalinkStructure.trigger( 'focus' );
830	}
831} );
832
833$( function() {
834	var checks, first, last, checked, sliced, mobileEvent, transitionTimeout, focusedRowActions,
835		lastClicked = false,
836		pageInput = $('input.current-page'),
837		currentPage = pageInput.val(),
838		isIOS = /iPhone|iPad|iPod/.test( navigator.userAgent ),
839		isAndroid = navigator.userAgent.indexOf( 'Android' ) !== -1,
840		$adminMenuWrap = $( '#adminmenuwrap' ),
841		$wpwrap = $( '#wpwrap' ),
842		$adminmenu = $( '#adminmenu' ),
843		$overlay = $( '#wp-responsive-overlay' ),
844		$toolbar = $( '#wp-toolbar' ),
845		$toolbarPopups = $toolbar.find( 'a[aria-haspopup="true"]' ),
846		$sortables = $('.meta-box-sortables'),
847		wpResponsiveActive = false,
848		$adminbar = $( '#wpadminbar' ),
849		lastScrollPosition = 0,
850		pinnedMenuTop = false,
851		pinnedMenuBottom = false,
852		menuTop = 0,
853		menuState,
854		menuIsPinned = false,
855		height = {
856			window: $window.height(),
857			wpwrap: $wpwrap.height(),
858			adminbar: $adminbar.height(),
859			menu: $adminMenuWrap.height()
860		},
861		$headerEnd = $( '.wp-header-end' );
862
863	/**
864	 * Makes the fly-out submenu header clickable, when the menu is folded.
865	 *
866	 * @param {Event} e The event object.
867	 *
868	 * @return {void}
869	 */
870	$adminmenu.on('click.wp-submenu-head', '.wp-submenu-head', function(e){
871		$(e.target).parent().siblings('a').get(0).click();
872	});
873
874	/**
875	 * Collapses the admin menu.
876	 *
877	 * @return {void}
878	 */
879	$( '#collapse-button' ).on( 'click.collapse-menu', function() {
880		var viewportWidth = getViewportWidth() || 961;
881
882		// Reset any compensation for submenus near the bottom of the screen.
883		$('#adminmenu div.wp-submenu').css('margin-top', '');
884
885		if ( viewportWidth <= 960 ) {
886			if ( $body.hasClass('auto-fold') ) {
887				$body.removeClass('auto-fold').removeClass('folded');
888				setUserSetting('unfold', 1);
889				setUserSetting('mfold', 'o');
890				menuState = 'open';
891			} else {
892				$body.addClass('auto-fold');
893				setUserSetting('unfold', 0);
894				menuState = 'folded';
895			}
896		} else {
897			if ( $body.hasClass('folded') ) {
898				$body.removeClass('folded');
899				setUserSetting('mfold', 'o');
900				menuState = 'open';
901			} else {
902				$body.addClass('folded');
903				setUserSetting('mfold', 'f');
904				menuState = 'folded';
905			}
906		}
907
908		$document.trigger( 'wp-collapse-menu', { state: menuState } );
909	});
910
911	/**
912	 * Ensures an admin submenu is within the visual viewport.
913	 *
914	 * @since 4.1.0
915	 *
916	 * @param {jQuery} $menuItem The parent menu item containing the submenu.
917	 *
918	 * @return {void}
919	 */
920	function adjustSubmenu( $menuItem ) {
921		var bottomOffset, pageHeight, adjustment, theFold, menutop, wintop, maxtop,
922			$submenu = $menuItem.find( '.wp-submenu' );
923
924		menutop = $menuItem.offset().top;
925		wintop = $window.scrollTop();
926		maxtop = menutop - wintop - 30; // max = make the top of the sub almost touch admin bar.
927
928		bottomOffset = menutop + $submenu.height() + 1; // Bottom offset of the menu.
929		pageHeight = $wpwrap.height();                  // Height of the entire page.
930		adjustment = 60 + bottomOffset - pageHeight;
931		theFold = $window.height() + wintop - 50;       // The fold.
932
933		if ( theFold < ( bottomOffset - adjustment ) ) {
934			adjustment = bottomOffset - theFold;
935		}
936
937		if ( adjustment > maxtop ) {
938			adjustment = maxtop;
939		}
940
941		if ( adjustment > 1 && $('#wp-admin-bar-menu-toggle').is(':hidden') ) {
942			$submenu.css( 'margin-top', '-' + adjustment + 'px' );
943		} else {
944			$submenu.css( 'margin-top', '' );
945		}
946	}
947
948	if ( 'ontouchstart' in window || /IEMobile\/[1-9]/.test(navigator.userAgent) ) { // Touch screen device.
949		// iOS Safari works with touchstart, the rest work with click.
950		mobileEvent = isIOS ? 'touchstart' : 'click';
951
952		/**
953		 * Closes any open submenus when touch/click is not on the menu.
954		 *
955		 * @param {Event} e The event object.
956		 *
957		 * @return {void}
958		 */
959		$body.on( mobileEvent+'.wp-mobile-hover', function(e) {
960			if ( $adminmenu.data('wp-responsive') ) {
961				return;
962			}
963
964			if ( ! $( e.target ).closest( '#adminmenu' ).length ) {
965				$adminmenu.find( 'li.opensub' ).removeClass( 'opensub' );
966			}
967		});
968
969		/**
970		 * Handles the opening or closing the submenu based on the mobile click|touch event.
971		 *
972		 * @param {Event} event The event object.
973		 *
974		 * @return {void}
975		 */
976		$adminmenu.find( 'a.wp-has-submenu' ).on( mobileEvent + '.wp-mobile-hover', function( event ) {
977			var $menuItem = $(this).parent();
978
979			if ( $adminmenu.data( 'wp-responsive' ) ) {
980				return;
981			}
982
983			/*
984			 * Show the sub instead of following the link if:
985			 * 	- the submenu is not open.
986			 * 	- the submenu is not shown inline or the menu is not folded.
987			 */
988			if ( ! $menuItem.hasClass( 'opensub' ) && ( ! $menuItem.hasClass( 'wp-menu-open' ) || $menuItem.width() < 40 ) ) {
989				event.preventDefault();
990				adjustSubmenu( $menuItem );
991				$adminmenu.find( 'li.opensub' ).removeClass( 'opensub' );
992				$menuItem.addClass('opensub');
993			}
994		});
995	}
996
997	if ( ! isIOS && ! isAndroid ) {
998		$adminmenu.find( 'li.wp-has-submenu' ).hoverIntent({
999
1000			/**
1001			 * Opens the submenu when hovered over the menu item for desktops.
1002			 *
1003			 * @return {void}
1004			 */
1005			over: function() {
1006				var $menuItem = $( this ),
1007					$submenu = $menuItem.find( '.wp-submenu' ),
1008					top = parseInt( $submenu.css( 'top' ), 10 );
1009
1010				if ( isNaN( top ) || top > -5 ) { // The submenu is visible.
1011					return;
1012				}
1013
1014				if ( $adminmenu.data( 'wp-responsive' ) ) {
1015					// The menu is in responsive mode, bail.
1016					return;
1017				}
1018
1019				adjustSubmenu( $menuItem );
1020				$adminmenu.find( 'li.opensub' ).removeClass( 'opensub' );
1021				$menuItem.addClass( 'opensub' );
1022			},
1023
1024			/**
1025			 * Closes the submenu when no longer hovering the menu item.
1026			 *
1027			 * @return {void}
1028			 */
1029			out: function(){
1030				if ( $adminmenu.data( 'wp-responsive' ) ) {
1031					// The menu is in responsive mode, bail.
1032					return;
1033				}
1034
1035				$( this ).removeClass( 'opensub' ).find( '.wp-submenu' ).css( 'margin-top', '' );
1036			},
1037			timeout: 200,
1038			sensitivity: 7,
1039			interval: 90
1040		});
1041
1042		/**
1043		 * Opens the submenu on when focused on the menu item.
1044		 *
1045		 * @param {Event} event The event object.
1046		 *
1047		 * @return {void}
1048		 */
1049		$adminmenu.on( 'focus.adminmenu', '.wp-submenu a', function( event ) {
1050			if ( $adminmenu.data( 'wp-responsive' ) ) {
1051				// The menu is in responsive mode, bail.
1052				return;
1053			}
1054
1055			$( event.target ).closest( 'li.menu-top' ).addClass( 'opensub' );
1056
1057			/**
1058			 * Closes the submenu on blur from the menu item.
1059			 *
1060			 * @param {Event} event The event object.
1061			 *
1062			 * @return {void}
1063			 */
1064		}).on( 'blur.adminmenu', '.wp-submenu a', function( event ) {
1065			if ( $adminmenu.data( 'wp-responsive' ) ) {
1066				return;
1067			}
1068
1069			$( event.target ).closest( 'li.menu-top' ).removeClass( 'opensub' );
1070
1071			/**
1072			 * Adjusts the size for the submenu.
1073			 *
1074			 * @return {void}
1075			 */
1076		}).find( 'li.wp-has-submenu.wp-not-current-submenu' ).on( 'focusin.adminmenu', function() {
1077			adjustSubmenu( $( this ) );
1078		});
1079	}
1080
1081	/*
1082	 * The `.below-h2` class is here just for backward compatibility with plugins
1083	 * that are (incorrectly) using it. Do not use. Use `.inline` instead. See #34570.
1084	 * If '.wp-header-end' is found, append the notices after it otherwise
1085	 * after the first h1 or h2 heading found within the main content.
1086	 */
1087	if ( ! $headerEnd.length ) {
1088		$headerEnd = $( '.wrap h1, .wrap h2' ).first();
1089	}
1090	$( 'div.updated, div.error, div.notice' ).not( '.inline, .below-h2' ).insertAfter( $headerEnd );
1091
1092	/**
1093	 * Makes notices dismissible.
1094	 *
1095	 * @since 4.4.0
1096	 *
1097	 * @return {void}
1098	 */
1099	function makeNoticesDismissible() {
1100		$( '.notice.is-dismissible' ).each( function() {
1101			var $el = $( this ),
1102				$button = $( '<button type="button" class="notice-dismiss"><span class="screen-reader-text"></span></button>' );
1103
1104			if ( $el.find( '.notice-dismiss' ).length ) {
1105				return;
1106			}
1107
1108			// Ensure plain text.
1109			$button.find( '.screen-reader-text' ).text( __( 'Dismiss this notice.' ) );
1110			$button.on( 'click.wp-dismiss-notice', function( event ) {
1111				event.preventDefault();
1112				$el.fadeTo( 100, 0, function() {
1113					$el.slideUp( 100, function() {
1114						$el.remove();
1115					});
1116				});
1117			});
1118
1119			$el.append( $button );
1120		});
1121	}
1122
1123	$document.on( 'wp-updates-notice-added wp-plugin-install-error wp-plugin-update-error wp-plugin-delete-error wp-theme-install-error wp-theme-delete-error wp-notice-added', makeNoticesDismissible );
1124
1125	// Init screen meta.
1126	screenMeta.init();
1127
1128	/**
1129	 * Checks a checkbox.
1130	 *
1131	 * This event needs to be delegated. Ticket #37973.
1132	 *
1133	 * @return {boolean} Returns whether a checkbox is checked or not.
1134	 */
1135	$body.on( 'click', 'tbody > tr > .check-column :checkbox', function( event ) {
1136		// Shift click to select a range of checkboxes.
1137		if ( 'undefined' == event.shiftKey ) { return true; }
1138		if ( event.shiftKey ) {
1139			if ( !lastClicked ) { return true; }
1140			checks = $( lastClicked ).closest( 'form' ).find( ':checkbox' ).filter( ':visible:enabled' );
1141			first = checks.index( lastClicked );
1142			last = checks.index( this );
1143			checked = $(this).prop('checked');
1144			if ( 0 < first && 0 < last && first != last ) {
1145				sliced = ( last > first ) ? checks.slice( first, last ) : checks.slice( last, first );
1146				sliced.prop( 'checked', function() {
1147					if ( $(this).closest('tr').is(':visible') )
1148						return checked;
1149
1150					return false;
1151				});
1152			}
1153		}
1154		lastClicked = this;
1155
1156		// Toggle the "Select all" checkboxes depending if the other ones are all checked or not.
1157		var unchecked = $(this).closest('tbody').find('tr').find(':checkbox').filter(':visible:enabled').not(':checked');
1158
1159		/**
1160		 * Determines if all checkboxes are checked.
1161		 *
1162		 * @return {boolean} Returns true if there are no unchecked checkboxes.
1163		 */
1164		$(this).closest('table').children('thead, tfoot').find(':checkbox').prop('checked', function() {
1165			return ( 0 === unchecked.length );
1166		});
1167
1168		return true;
1169	});
1170
1171	/**
1172	 * Controls all the toggles on bulk toggle change.
1173	 *
1174	 * When the bulk checkbox is changed, all the checkboxes in the tables are changed accordingly.
1175	 * When the shift-button is pressed while changing the bulk checkbox the checkboxes in the table are inverted.
1176	 *
1177	 * This event needs to be delegated. Ticket #37973.
1178	 *
1179	 * @param {Event} event The event object.
1180	 *
1181	 * @return {boolean}
1182	 */
1183	$body.on( 'click.wp-toggle-checkboxes', 'thead .check-column :checkbox, tfoot .check-column :checkbox', function( event ) {
1184		var $this = $(this),
1185			$table = $this.closest( 'table' ),
1186			controlChecked = $this.prop('checked'),
1187			toggle = event.shiftKey || $this.data('wp-toggle');
1188
1189		$table.children( 'tbody' ).filter(':visible')
1190			.children().children('.check-column').find(':checkbox')
1191			/**
1192			 * Updates the checked state on the checkbox in the table.
1193			 *
1194			 * @return {boolean} True checks the checkbox, False unchecks the checkbox.
1195			 */
1196			.prop('checked', function() {
1197				if ( $(this).is(':hidden,:disabled') ) {
1198					return false;
1199				}
1200
1201				if ( toggle ) {
1202					return ! $(this).prop( 'checked' );
1203				} else if ( controlChecked ) {
1204					return true;
1205				}
1206
1207				return false;
1208			});
1209
1210		$table.children('thead,  tfoot').filter(':visible')
1211			.children().children('.check-column').find(':checkbox')
1212
1213			/**
1214			 * Syncs the bulk checkboxes on the top and bottom of the table.
1215			 *
1216			 * @return {boolean} True checks the checkbox, False unchecks the checkbox.
1217			 */
1218			.prop('checked', function() {
1219				if ( toggle ) {
1220					return false;
1221				} else if ( controlChecked ) {
1222					return true;
1223				}
1224
1225				return false;
1226			});
1227	});
1228
1229	/**
1230	 * Marries a secondary control to its primary control.
1231	 *
1232	 * @param {jQuery} topSelector    The top selector element.
1233	 * @param {jQuery} topSubmit      The top submit element.
1234	 * @param {jQuery} bottomSelector The bottom selector element.
1235	 * @param {jQuery} bottomSubmit   The bottom submit element.
1236	 * @return {void}
1237	 */
1238	function marryControls( topSelector, topSubmit, bottomSelector, bottomSubmit ) {
1239		/**
1240		 * Updates the primary selector when the secondary selector is changed.
1241		 *
1242		 * @since 5.7.0
1243		 *
1244		 * @return {void}
1245		 */
1246		function updateTopSelector() {
1247			topSelector.val($(this).val());
1248		}
1249		bottomSelector.on('change', updateTopSelector);
1250
1251		/**
1252		 * Updates the secondary selector when the primary selector is changed.
1253		 *
1254		 * @since 5.7.0
1255		 *
1256		 * @return {void}
1257		 */
1258		function updateBottomSelector() {
1259			bottomSelector.val($(this).val());
1260		}
1261		topSelector.on('change', updateBottomSelector);
1262
1263		/**
1264		 * Triggers the primary submit when then secondary submit is clicked.
1265		 *
1266		 * @since 5.7.0
1267		 *
1268		 * @return {void}
1269		 */
1270		function triggerSubmitClick(e) {
1271			e.preventDefault();
1272			e.stopPropagation();
1273
1274			topSubmit.trigger('click');
1275		}
1276		bottomSubmit.on('click', triggerSubmitClick);
1277	}
1278
1279	// Marry the secondary "Bulk actions" controls to the primary controls:
1280	marryControls( $('#bulk-action-selector-top'), $('#doaction'), $('#bulk-action-selector-bottom'), $('#doaction2') );
1281
1282	// Marry the secondary "Change role to" controls to the primary controls:
1283	marryControls( $('#new_role'), $('#changeit'), $('#new_role2'), $('#changeit2') );
1284
1285	var addAdminNotice = function( data ) {
1286		var $notice = $( data.selector ),
1287			$headerEnd = $( '.wp-header-end' ),
1288			type,
1289			dismissible,
1290			$adminNotice;
1291
1292		delete data.selector;
1293
1294		dismissible = ( data.dismissible && data.dismissible === true ) ? ' is-dismissible' : '';
1295		type        = ( data.type ) ? data.type : 'info';
1296
1297		$adminNotice = '<div id="' + data.id + '" class="notice notice-' + data.type + dismissible + '"><p>' + data.message + '</p></div>';
1298
1299		// Check if this admin notice already exists.
1300		if ( ! $notice.length ) {
1301			$notice = $( '#' + data.id );
1302		}
1303
1304		if ( $notice.length ) {
1305			$notice.replaceWith( $adminNotice );
1306		} else if ( $headerEnd.length ) {
1307			$headerEnd.after( $adminNotice );
1308		} else {
1309			if ( 'customize' === pagenow ) {
1310				$( '.customize-themes-notifications' ).append( $adminNotice );
1311			} else {
1312				$( '.wrap' ).find( '> h1' ).after( $adminNotice );
1313			}
1314		}
1315
1316		$document.trigger( 'wp-notice-added' );
1317	};
1318
1319	$( '.bulkactions' ).parents( 'form' ).on( 'submit', function( event ) {
1320		var form = this,
1321			submitterName = event.originalEvent && event.originalEvent.submitter ? event.originalEvent.submitter.name : false,
1322			currentPageSelector = form.querySelector( '#current-page-selector' );
1323
1324		if ( currentPageSelector && currentPageSelector.defaultValue !== currentPageSelector.value ) {
1325			return; // Pagination form submission.
1326		}
1327
1328		// Observe submissions from posts lists for 'bulk_action' or users lists for 'new_role'.
1329		var bulkFieldRelations = {
1330			'bulk_action' : window.bulkActionObserverIds.bulk_action,
1331			'changeit' : window.bulkActionObserverIds.changeit
1332		};
1333		if ( ! Object.keys( bulkFieldRelations ).includes( submitterName ) ) {
1334			return;
1335		}
1336
1337		var values = new FormData(form);
1338		var value = values.get( bulkFieldRelations[ submitterName ] ) || '-1';
1339
1340		// Check that the action is not the default one.
1341		if ( value !== '-1' ) {
1342			// Check that at least one item is selected.
1343			var itemsSelected = form.querySelectorAll( '.wp-list-table tbody .check-column input[type="checkbox"]:checked' );
1344
1345			if ( itemsSelected.length > 0 ) {
1346				return;
1347			}
1348		}
1349		event.preventDefault();
1350		event.stopPropagation();
1351		$( 'html, body' ).animate( { scrollTop: 0 } );
1352
1353		var errorMessage = value !== '-1' ?
1354			__( 'Please select at least one item to perform this action on.' ) :
1355			__( 'Please select a bulk action to perform.' );
1356		addAdminNotice( {
1357			id: value !== '-1' ? 'no-items-selected' : 'no-bulk-action-selected',
1358			type: 'error',
1359			message: errorMessage,
1360			dismissible: true,
1361		} );
1362
1363		wp.a11y.speak( errorMessage );
1364	});
1365
1366	/**
1367	 * Shows row actions on focus of its parent container element or any other elements contained within.
1368	 *
1369	 * @return {void}
1370	 */
1371	$( '#wpbody-content' ).on({
1372		focusin: function() {
1373			clearTimeout( transitionTimeout );
1374			focusedRowActions = $( this ).find( '.row-actions' );
1375			// transitionTimeout is necessary for Firefox, but Chrome won't remove the CSS class without a little help.
1376			$( '.row-actions' ).not( this ).removeClass( 'visible' );
1377			focusedRowActions.addClass( 'visible' );
1378		},
1379		focusout: function() {
1380			// Tabbing between post title and .row-actions links needs a brief pause, otherwise
1381			// the .row-actions div gets hidden in transit in some browsers (ahem, Firefox).
1382			transitionTimeout = setTimeout( function() {
1383				focusedRowActions.removeClass( 'visible' );
1384			}, 30 );
1385		}
1386	}
1386, '.table-view-list .has-row-actions' );
1387
1388	// Toggle list table rows on small screens.
1389	$( 'tbody' ).on( 'click', '.toggle-row', function() {
1390		$( this ).closest( 'tr' ).toggleClass( 'is-expanded' );
1391	});
1392
1393	$('#default-password-nag-no').on( 'click', function() {
1394		setUserSetting('default_password_nag', 'hide');
1395		$('div.default-password-nag').hide();
1396		return false;
1397	});
1398
1399	/**
1400	 * Handles tab keypresses in theme and plugin file editor textareas.
1401	 *
1402	 * @param {Event} e The event object.
1403	 *
1404	 * @return {void}
1405	 */
1406	$('#newcontent').on('keydown.wpevent_InsertTab', function(e) {
1407		var el = e.target, selStart, selEnd, val, scroll, sel;
1408
1409		// After pressing escape key (keyCode: 27), the tab key should tab out of the textarea.
1410		if ( e.keyCode == 27 ) {
1411			// When pressing Escape: Opera 12 and 27 blur form fields, IE 8 clears them.
1412			e.preventDefault();
1413			$(el).data('tab-out', true);
1414			return;
1415		}
1416
1417		// Only listen for plain tab key (keyCode: 9) without any modifiers.
1418		if ( e.keyCode != 9 || e.ctrlKey || e.altKey || e.shiftKey )
1419			return;
1420
1421		// After tabbing out, reset it so next time the tab key can be used again.
1422		if ( $(el).data('tab-out') ) {
1423			$(el).data('tab-out', false);
1424			return;
1425		}
1426
1427		selStart = el.selectionStart;
1428		selEnd = el.selectionEnd;
1429		val = el.value;
1430
1431		// If any text is selected, replace the selection with a tab character.
1432		if ( document.selection ) {
1433			el.focus();
1434			sel = document.selection.createRange();
1435			sel.text = '\t';
1436		} else if ( selStart >= 0 ) {
1437			scroll = this.scrollTop;
1438			el.value = val.substring(0, selStart).concat('\t', val.substring(selEnd) );
1439			el.selectionStart = el.selectionEnd = selStart + 1;
1440			this.scrollTop = scroll;
1441		}
1442
1443		// Cancel the regular tab functionality, to prevent losing focus of the textarea.
1444		if ( e.stopPropagation )
1445			e.stopPropagation();
1446		if ( e.preventDefault )
1447			e.preventDefault();
1448	});
1449
1450	// Reset page number variable for new filters/searches but not for bulk actions. See #17685.
1451	if ( pageInput.length ) {
1452
1453		/**
1454		 * Handles pagination variable when filtering the list table.
1455		 *
1456		 * Set the pagination argument to the first page when the post-filter form is submitted.
1457		 * This happens when pressing the 'filter' button on the list table page.
1458		 *
1459		 * The pagination argument should not be touched when the bulk action dropdowns are set to do anything.
1460		 *
1461		 * The form closest to the pageInput is the post-filter form.
1462		 *
1463		 * @return {void}
1464		 */
1465		pageInput.closest('form').on( 'submit', function() {
1466			/*
1467			 * action = bulk action dropdown at the top of the table
1468			 */
1469			if ( $('select[name="action"]').val() == -1 && pageInput.val() == currentPage )
1470				pageInput.val('1');
1471		});
1472	}
1473
1474	/**
1475	 * Resets the bulk actions when the search button is clicked.
1476	 *
1477	 * @return {void}
1478	 */
1479	$('.search-box input[type="search"], .search-box input[type="submit"]').on( 'mousedown', function () {
1480		$('select[name^="action"]').val('-1');
1481	});
1482
1483	/**
1484	 * Scrolls into view when focus.scroll-into-view is triggered.
1485	 *
1486	 * @param {Event} e The event object.
1487	 *
1488	 * @return {void}
1489 	 */
1490	$('#contextual-help-link, #show-settings-link').on( 'focus.scroll-into-view', function(e){
1491		if ( e.target.scrollIntoViewIfNeeded )
1492			e.target.scrollIntoViewIfNeeded(false);
1493	});
1494
1495	/**
1496	 * Disables the submit upload buttons when no data is entered.
1497	 *
1498	 * @return {void}
1499	 */
1500	(function(){
1501		var button, input, form = $('form.wp-upload-form');
1502
1503		// Exit when no upload form is found.
1504		if ( ! form.length )
1505			return;
1506
1507		button = form.find('input[type="submit"]');
1508		input = form.find('input[type="file"]');
1509
1510		/**
1511		 * Determines if any data is entered in any file upload input.
1512		 *
1513		 * @since 3.5.0
1514		 *
1515		 * @return {void}
1516		 */
1517		function toggleUploadButton() {
1518			// When no inputs have a value, disable the upload buttons.
1519			button.prop('disabled', '' === input.map( function() {
1520				return $(this).val();
1521			}).get().join(''));
1522		}
1523
1524		// Update the status initially.
1525		toggleUploadButton();
1526		// Update the status when any file input changes.
1527		input.on('change', toggleUploadButton);
1528	})();
1529
1530	/**
1531	 * Pins the menu while distraction-free writing is enabled.
1532	 *
1533	 * @param {Event} event Event data.
1534	 *
1535	 * @since 4.1.0
1536	 *
1537	 * @return {void}
1538	 */
1539	function pinMenu( event ) {
1540		var windowPos = $window.scrollTop(),
1541			resizing = ! event || event.type !== 'scroll';
1542
1543		if ( isIOS || $adminmenu.data( 'wp-responsive' ) ) {
1544			return;
1545		}
1546
1547		/*
1548		 * When the menu is higher than the window and smaller than the entire page.
1549		 * It should be adjusted to be able to see the entire menu.
1550		 *
1551		 * Otherwise it can be accessed normally.
1552		 */
1553		if ( height.menu + height.adminbar < height.window ||
1554			height.menu + height.adminbar + 20 > height.wpwrap ) {
1555			unpinMenu();
1556			return;
1557		}
1558
1559		menuIsPinned = true;
1560
1561		// If the menu is higher than the window, compensate on scroll.
1562		if ( height.menu + height.adminbar > height.window ) {
1563			// Check for overscrolling, this happens when swiping up at the top of the document in modern browsers.
1564			if ( windowPos < 0 ) {
1565				// Stick the menu to the top.
1566				if ( ! pinnedMenuTop ) {
1567					pinnedMenuTop = true;
1568					pinnedMenuBottom = false;
1569
1570					$adminMenuWrap.css({
1571						position: 'fixed',
1572						top: '',
1573						bottom: ''
1574					});
1575				}
1576
1577				return;
1578			} else if ( windowPos + height.window > $document.height() - 1 ) {
1579				// When overscrolling at the bottom, stick the menu to the bottom.
1580				if ( ! pinnedMenuBottom ) {
1581					pinnedMenuBottom = true;
1582					pinnedMenuTop = false;
1583
1584					$adminMenuWrap.css({
1585						position: 'fixed',
1586						top: '',
1587						bottom: 0
1588					});
1589				}
1590
1591				return;
1592			}
1593
1594			if ( windowPos > lastScrollPosition ) {
1595				// When a down scroll has been detected.
1596
1597				// If it was pinned to the top, unpin and calculate relative scroll.
1598				if ( pinnedMenuTop ) {
1599					pinnedMenuTop = false;
1600					// Calculate new offset position.
1601					menuTop = $adminMenuWrap.offset().top - height.adminbar - ( windowPos - lastScrollPosition );
1602
1603					if ( menuTop + height.menu + height.adminbar < windowPos + height.window ) {
1604						menuTop = windowPos + height.window - height.menu - height.adminbar;
1605					}
1606
1607					$adminMenuWrap.css({
1608						position: 'absolute',
1609						top: menuTop,
1610						bottom: ''
1611					});
1612				} else if ( ! pinnedMenuBottom && $adminMenuWrap.offset().top + height.menu < windowPos + height.window ) {
1613					// Pin it to the bottom.
1614					pinnedMenuBottom = true;
1615
1616					$adminMenuWrap.css({
1617						position: 'fixed',
1618						top: '',
1619						bottom: 0
1620					});
1621				}
1622			} else if ( windowPos < lastScrollPosition ) {
1623				// When a scroll up is detected.
1624
1625				// If it was pinned to the bottom, unpin and calculate relative scroll.
1626				if ( pinnedMenuBottom ) {
1627					pinnedMenuBottom = false;
1628
1629					// Calculate new offset position.
1630					menuTop = $adminMenuWrap.offset().top - height.adminbar + ( lastScrollPosition - windowPos );
1631
1632					if ( menuTop + height.menu > windowPos + height.window ) {
1633						menuTop = windowPos;
1634					}
1635
1636					$adminMenuWrap.css({
1637						position: 'absolute',
1638						top: menuTop,
1639						bottom: ''
1640					});
1641				} else if ( ! pinnedMenuTop && $adminMenuWrap.offset().top >= windowPos + height.adminbar ) {
1642
1643					// Pin it to the top.
1644					pinnedMenuTop = true;
1645
1646					$adminMenuWrap.css({
1647						position: 'fixed',
1648						top: '',
1649						bottom: ''
1650					});
1651				}
1652			} else if ( resizing ) {
1653				// Window is being resized.
1654
1655				pinnedMenuTop = pinnedMenuBottom = false;
1656
1657				// Calculate the new offset.
1658				menuTop = windowPos + height.window - height.menu - height.adminbar - 1;
1659
1660				if ( menuTop > 0 ) {
1661					$adminMenuWrap.css({
1662						position: 'absolute',
1663						top: menuTop,
1664						bottom: ''
1665					});
1666				} else {
1667					unpinMenu();
1668				}
1669			}
1670		}
1671
1672		lastScrollPosition = windowPos;
1673	}
1674
1675	/**
1676	 * Determines the height of certain elements.
1677	 *
1678	 * @since 4.1.0
1679	 *
1680	 * @return {void}
1681	 */
1682	function resetHeights() {
1683		height = {
1684			window: $window.height(),
1685			wpwrap: $wpwrap.height(),
1686			adminbar: $adminbar.height(),
1687			menu: $adminMenuWrap.height()
1688		};
1689	}
1690
1691	/**
1692	 * Unpins the menu.
1693	 *
1694	 * @since 4.1.0
1695	 *
1696	 * @return {void}
1697	 */
1698	function unpinMenu() {
1699		if ( isIOS || ! menuIsPinned ) {
1700			return;
1701		}
1702
1703		pinnedMenuTop = pinnedMenuBottom = menuIsPinned = false;
1704		$adminMenuWrap.css({
1705			position: '',
1706			top: '',
1707			bottom: ''
1708		});
1709	}
1710
1711	/**
1712	 * Pins and unpins the menu when applicable.
1713	 *
1714	 * @since 4.1.0
1715	 *
1716	 * @return {void}
1717	 */
1718	function setPinMenu() {
1719		resetHeights();
1720
1721		if ( $adminmenu.data('wp-responsive') ) {
1722			$body.removeClass( 'sticky-menu' );
1723			unpinMenu();
1724		} else if ( height.menu + height.adminbar > height.window ) {
1725			pinMenu();
1726			$body.removeClass( 'sticky-menu' );
1727		} else {
1728			$body.addClass( 'sticky-menu' );
1729			unpinMenu();
1730		}
1731	}
1732
1733	if ( ! isIOS ) {
1734		$window.on( 'scroll.pin-menu', pinMenu );
1735		$document.on( 'tinymce-editor-init.pin-menu', function( event, editor ) {
1736			editor.on( 'wp-autoresize', resetHeights );
1737		});
1738	}
1739
1740	/**
1741	 * Changes the sortables and responsiveness of metaboxes.
1742	 *
1743	 * @since 3.8.0
1744	 *
1745	 * @return {void}
1746	 */
1747	window.wpResponsive = {
1748
1749		/**
1750		 * Initializes the wpResponsive object.
1751		 *
1752		 * @since 3.8.0
1753		 *
1754		 * @return {void}
1755		 */
1756		init: function() {
1757			var self = this;
1758
1759			this.maybeDisableSortables = this.maybeDisableSortables.bind( this );
1760
1761			// Modify functionality based on custom activate/deactivate event.
1762			$document.on( 'wp-responsive-activate.wp-responsive', function() {
1763				self.activate();
1764				self.toggleAriaHasPopup( 'add' );
1765			}).on( 'wp-responsive-deactivate.wp-responsive', function() {
1766				self.deactivate();
1767				self.toggleAriaHasPopup( 'remove' );
1768			});
1769
1770			$( '#wp-admin-bar-menu-toggle a' ).attr( 'aria-expanded', 'false' );
1771
1772			// Toggle sidebar when toggle is clicked.
1773			$( '#wp-admin-bar-menu-toggle' ).on( 'click.wp-responsive', function( event ) {
1774				event.preventDefault();
1775
1776				// Close any open toolbar submenus.
1777				$adminbar.find( '.hover' ).removeClass( 'hover' );
1778
1779				$wpwrap.toggleClass( 'wp-responsive-open' );
1780				if ( $wpwrap.hasClass( 'wp-responsive-open' ) ) {
1781					$(this).find('a').attr( 'aria-expanded', 'true' );
1782					$( '#adminmenu a:first' ).trigger( 'focus' );
1783				} else {
1784					$(this).find('a').attr( 'aria-expanded', 'false' );
1785				}
1786			} );
1787
1788			// Close sidebar when target moves outside of toggle and sidebar.
1789			$( document ).on( 'click', function( event ) {
1790				if ( ! $wpwrap.hasClass( 'wp-responsive-open' ) || ! document.hasFocus() ) {
1791					return;
1792				}
1793
1794				var focusIsInToggle  = $.contains( $( '#wp-admin-bar-menu-toggle' )[0], event.target );
1795				var focusIsInSidebar = $.contains( $( '#adminmenuwrap' )[0], event.target );
1796
1797				if ( ! focusIsInToggle && ! focusIsInSidebar ) {
1798					$( '#wp-admin-bar-menu-toggle' ).trigger( 'click.wp-responsive' );
1799				}
1800			} );
1801
1802			// Close sidebar when a keypress completes outside of toggle and sidebar.
1803			$( document ).on( 'keyup', function( event ) {
1804				var toggleButton   = $( '#wp-admin-bar-menu-toggle' )[0];
1805				if ( ! $wpwrap.hasClass( 'wp-responsive-open' ) ) {
1806				    return;
1807				}
1808				if ( 27 === event.keyCode ) {
1809					$( toggleButton ).trigger( 'click.wp-responsive' );
1810					$( toggleButton ).find( 'a' ).trigger( 'focus' );
1811				} else {
1812					if ( 9 === event.keyCode ) {
1813						var sidebar        = $( '#adminmenuwrap' )[0];
1814						var focusedElement = event.relatedTarget || document.activeElement;
1815						// A brief delay is required to allow focus to switch to another element.
1816						setTimeout( function() {
1817							var focusIsInToggle  = $.contains( toggleButton, focusedElement );
1818							var focusIsInSidebar = $.contains( sidebar, focusedElement );
1819
1820							if ( ! focusIsInToggle && ! focusIsInSidebar ) {
1821								$( toggleButton ).trigger( 'click.wp-responsive' );
1822							}
1823						}, 10 );
1824					}
1825				}
1826			});
1827
1828			// Add menu events.
1829			$adminmenu.on( 'click.wp-responsive', 'li.wp-has-submenu > a', function( event ) {
1830				if ( ! $adminmenu.data('wp-responsive') ) {
1831					return;
1832				}
1833				let state = ( 'false' === $( this ).attr( 'aria-expanded' ) ) ? 'true' : 'false';
1834				$( this ).parent( 'li' ).toggleClass( 'selected' );
1835				$( this ).attr( 'aria-expanded', state );
1836				$( this ).trigger( 'focus' );
1837				event.preventDefault();
1838			});
1839
1840			self.trigger();
1841			$document.on( 'wp-window-resized.wp-responsive', this.trigger.bind( this ) );
1842
1843			// This needs to run later as UI Sortable may be initialized when the document is ready.
1844			$window.on( 'load.wp-responsive', this.maybeDisableSortables );
1845			$document.on( 'postbox-toggled', this.maybeDisableSortables );
1846
1847			// When the screen columns are changed, potentially disable sortables.
1848			$( '#screen-options-wrap input' ).on( 'click', this.maybeDisableSortables );
1849		},
1850
1851		/**
1852		 * Disable sortables if there is only one metabox, or the screen is in one column mode. Otherwise, enable sortables.
1853		 *
1854		 * @since 5.3.0
1855		 *
1856		 * @return {void}
1857		 */
1858		maybeDisableSortables: function() {
1859			var width = navigator.userAgent.indexOf('AppleWebKit/') > -1 ? $window.width() : window.innerWidth;
1860
1861			if (
1862				( width <= 782 ) ||
1863				( 1 >= $sortables.find( '.ui-sortable-handle:visible' ).length && jQuery( '.columns-prefs-1 input' ).prop( 'checked' ) )
1864			) {
1865				this.disableSortables();
1866			} else {
1867				this.enableSortables();
1868			}
1869		},
1870
1871		/**
1872		 * Changes properties of body and admin menu.
1873		 *
1874		 * Pins and unpins the menu and adds the auto-fold class to the body.
1875		 * Makes the admin menu responsive and disables the metabox sortables.
1876		 *
1877		 * @since 3.8.0
1878		 *
1879		 * @return {void}
1880		 */
1881		activate: function() {
1882			setPinMenu();
1883
1884			if ( ! $body.hasClass( 'auto-fold' ) ) {
1885				$body.addClass( 'auto-fold' );
1886			}
1887
1888			$adminmenu.data( 'wp-responsive', 1 );
1889			this.disableSortables();
1890		},
1891
1892		/**
1893		 * Changes properties of admin menu and enables metabox sortables.
1894		 *
1895		 * Pin and unpin the menu.
1896		 * Removes the responsiveness of the admin menu and enables the metabox sortables.
1897		 *
1898		 * @since 3.8.0
1899		 *
1900		 * @return {void}
1901		 */
1902		deactivate: function() {
1903			setPinMenu();
1904			$adminmenu.removeData('wp-responsive');
1905
1906			this.maybeDisableSortables();
1907		},
1908
1909		/**
1910		 * Toggles the aria-haspopup attribute for the responsive admin menu.
1911		 *
1912		 * The aria-haspopup attribute is only necessary for the responsive menu.
1913		 * See ticket https://core.trac.wordpress.org/ticket/43095
1914		 *
1915		 * @since 6.6.0
1916		 *
1917		 * @param {string} action Whether to add or remove the aria-haspopup attribute.
1918		 *
1919		 * @return {void}
1920		 */
1921		toggleAriaHasPopup: function( action ) {
1922			var elements = $adminmenu.find( '[data-ariahaspopup]' );
1923
1924			if ( action === 'add' ) {
1925				elements.each( function() {
1926					$( this ).attr( 'aria-haspopup', 'menu' ).attr( 'aria-expanded', 'false' );
1927				} );
1928
1929				return;
1930			}
1931
1932			elements.each( function() {
1933				$( this ).removeAttr( 'aria-haspopup' ).removeAttr( 'aria-expanded' );
1934			} );
1935		},
1936
1937		/**
1938		 * Sets the responsiveness and enables the overlay based on the viewport width.
1939		 *
1940		 * @since 3.8.0
1941		 *
1942		 * @return {void}
1943		 */
1944		trigger: function() {
1945			var viewportWidth = getViewportWidth();
1946
1947			// Exclude IE < 9, it doesn't support @media CSS rules.
1948			if ( ! viewportWidth ) {
1949				return;
1950			}
1951
1952			if ( viewportWidth <= 782 ) {
1953				if ( ! wpResponsiveActive ) {
1954					$document.trigger( 'wp-responsive-activate' );
1955					wpResponsiveActive = true;
1956				}
1957			} else {
1958				if ( wpResponsiveActive ) {
1959					$document.trigger( 'wp-responsive-deactivate' );
1960					wpResponsiveActive = false;
1961				}
1962			}
1963
1964			if ( viewportWidth <= 480 ) {
1965				this.enableOverlay();
1966			} else {
1967				this.disableOverlay();
1968			}
1969
1970			this.maybeDisableSortables();
1971		},
1972
1973		/**
1974		 * Inserts a responsive overlay and toggles the window.
1975		 *
1976		 * @since 3.8.0
1977		 *
1978		 * @return {void}
1979		 */
1980		enableOverlay: function() {
1981			if ( $overlay.length === 0 ) {
1982				$overlay = $( '<div id="wp-responsive-overlay"></div>' )
1983					.insertAfter( '#wpcontent' )
1984					.hide()
1985					.on( 'click.wp-responsive', function() {
1986						$toolbar.find( '.menupop.hover' ).removeClass( 'hover' );
1987						$( this ).hide();
1988					});
1989			}
1990
1991			$toolbarPopups.on( 'click.wp-responsive', function() {
1992				$overlay.show();
1993			});
1994		},
1995
1996		/**
1997		 * Disables the responsive overlay and removes the overlay.
1998		 *
1999		 * @since 3.8.0
2000		 *
2001		 * @return {void}
2002		 */
2003		disableOverlay: function() {
2004			$toolbarPopups.off( 'click.wp-responsive' );
2005			$overlay.hide();
2006		},
2007
2008		/**
2009		 * Disables sortables.
2010		 *
2011		 * @since 3.8.0
2012		 *
2013		 * @return {void}
2014		 */
2015		disableSortables: function() {
2016			if ( $sortables.length ) {
2017				try {
2018					$sortables.sortable( 'disable' );
2019					$sortables.find( '.ui-sortable-handle' ).addClass( 'is-non-sortable' );
2020				} catch ( e ) {}
2021			}
2022		},
2023
2024		/**
2025		 * Enables sortables.
2026		 *
2027		 * @since 3.8.0
2028		 *
2029		 * @return {void}
2030		 */
2031		enableSortables: function() {
2032			if ( $sortables.length ) {
2033				try {
2034					$sortables.sortable( 'enable' );
2035					$sortables.find( '.ui-sortable-handle' ).removeClass( 'is-non-sortable' );
2036				} catch ( e ) {}
2037			}
2038		}
2039	};
2040
2041	/**
2042	 * Add an ARIA role `button` to elements that behave like UI controls when JavaScript is on.
2043	 *
2044	 * @since 4.5.0
2045	 *
2046	 * @return {void}
2047	 */
2048	function aria_button_if_js() {
2049		$( '.aria-button-if-js' ).attr( 'role', 'button' );
2050	}
2051
2052	$( document ).on( 'ajaxComplete', function() {
2053		aria_button_if_js();
2054	});
2055
2056	/**
2057	 * Get the viewport width.
2058	 *
2059	 * @since 4.7.0
2060	 *
2061	 * @return {number|boolean} The current viewport width or false if the
2062	 *                          browser doesn't support innerWidth (IE < 9).
2063	 */
2064	function getViewportWidth() {
2065		var viewportWidth = false;
2066
2067		if ( window.innerWidth ) {
2068			// On phones, window.innerWidth is affected by zooming.
2069			viewportWidth = Math.max( window.innerWidth, document.documentElement.clientWidth );
2070		}
2071
2072		return viewportWidth;
2073	}
2074
2075	/**
2076	 * Sets the admin menu collapsed/expanded state.
2077	 *
2078	 * Sets the global variable `menuState` and triggers a custom event passing
2079	 * the current menu state.
2080	 *
2081	 * @since 4.7.0
2082	 *
2083	 * @return {void}
2084	 */
2085	function setMenuState() {
2086		var viewportWidth = getViewportWidth() || 961;
2087
2088		if ( viewportWidth <= 782  ) {
2089			menuState = 'responsive';
2090		} else if ( $body.hasClass( 'folded' ) || ( $body.hasClass( 'auto-fold' ) && viewportWidth <= 960 && viewportWidth > 782 ) ) {
2091			menuState = 'folded';
2092		} else {
2093			menuState = 'open';
2094		}
2095
2096		$document.trigger( 'wp-menu-state-set', { state: menuState } );
2097	}
2098
2099	// Set the menu state when the window gets resized.
2100	$document.on( 'wp-window-resized.set-menu-state', setMenuState );
2101
2102	/**
2103	 * Sets ARIA attributes on the collapse/expand menu button.
2104	 *
2105	 * When the admin menu is open or folded, updates the `aria-expanded` and
2106	 * `aria-label` attributes of the button to give feedback to assistive
2107	 * technologies. In the responsive view, the button is always hidden.
2108	 *
2109	 * @since 4.7.0
2110	 *
2111	 * @return {void}
2112	 */
2113	$document.on( 'wp-menu-state-set wp-collapse-menu', function( event, eventData ) {
2114		var $collapseButton = $( '#collapse-button' ),
2115			ariaExpanded, ariaLabelText;
2116
2117		if ( 'folded' === eventData.state ) {
2118			ariaExpanded = 'false';
2119			ariaLabelText = __( 'Expand Main menu' );
2120		} else {
2121			ariaExpanded = 'true';
2122			ariaLabelText = __( 'Collapse Main menu' );
2123		}
2124
2125		$collapseButton.attr({
2126			'aria-expanded': ariaExpanded,
2127			'aria-label': ariaLabelText
2128		});
2129	});
2130
2131	window.wpResponsive.init();
2132	setPinMenu();
2133	setMenuState();
2134	makeNoticesDismissible();
2135	aria_button_if_js();
2136
2137	$document.on( 'wp-pin-menu wp-window-resized.pin-menu postboxes-columnchange.pin-menu postbox-toggled.pin-menu wp-collapse-menu.pin-menu wp-scroll-start.pin-menu', setPinMenu );
2138
2139	// Set initial focus on a specific element.
2140	$( '.wp-initial-focus' ).trigger( 'focus' );
2141
2142	// Toggle update details on update-core.php.
2143	$body.on( 'click', '.js-update-details-toggle', function() {
2144		var $updateNotice = $( this ).closest( '.js-update-details' ),
2145			$progressDiv = $( '#' + $updateNotice.data( 'update-details' ) );
2146
2147		/*
2148		 * When clicking on "Show details" move the progress div below the update
2149		 * notice. Make sure it gets moved just the first time.
2150		 */
2151		if ( ! $progressDiv.hasClass( 'update-details-moved' ) ) {
2152			$progressDiv.insertAfter( $updateNotice ).addClass( 'update-details-moved' );
2153		}
2154
2155		// Toggle the progress div visibility.
2156		$progressDiv.toggle();
2157		// Toggle the Show Details button expanded state.
2158		$( this ).attr( 'aria-expanded', $progressDiv.is( ':visible' ) );
2159	});
2160});
2161
2162/**
2163 * Hides the update button for expired plugin or theme uploads.
2164 *
2165 * On the "Update plugin/theme from uploaded zip" screen, once the upload has expired,
2166 * hides the "Replace current with uploaded" button and displays a warning.
2167 *
2168 * @since 5.5.0
2169 */
2170$( function( $ ) {
2171	var $overwrite, $warning;
2172
2173	if ( ! $body.hasClass( 'update-php' ) ) {
2174		return;
2175	}
2176
2177	$overwrite = $( 'a.update-from-upload-overwrite' );
2178	$warning   = $( '.update-from-upload-expired' );
2179
2180	if ( ! $overwrite.length || ! $warning.length ) {
2181		return;
2182	}
2183
2184	window.setTimeout(
2185		function() {
2186			$overwrite.hide();
2187			$warning.removeClass( 'hidden' );
2188
2189			if ( window.wp && window.wp.a11y ) {
2190				window.wp.a11y.speak( $warning.text() );
2191			}
2192		},
2193		7140000 // 119 minutes. The uploaded file is deleted after 2 hours.
2194	);
2195} );
2196
2197// Fire a custom jQuery event at the end of window resize.
2198( function() {
2199	var timeout;
2200
2201	/**
2202	 * Triggers the WP window-resize event.
2203	 *
2204	 * @since 3.8.0
2205	 *
2206	 * @return {void}
2207	 */
2208	function triggerEvent() {
2209		$document.trigger( 'wp-window-resized' );
2210	}
2211
2212	/**
2213	 * Fires the trigger event again after 200 ms.
2214	 *
2215	 * @since 3.8.0
2216	 *
2217	 * @return {void}
2218	 */
2219	function fireOnce() {
2220		window.clearTimeout( timeout );
2221		timeout = window.setTimeout( triggerEvent, 200 );
2222	}
2223
2224	$window.on( 'resize.wp-fire-once', fireOnce );
2225}());
2226
2227// Make Windows 8 devices play along nicely.
2228(function(){
2229	if ( '-ms-user-select' in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/) ) {
2230		var msViewportStyle = document.createElement( 'style' );
2231		msViewportStyle.appendChild(
2232			document.createTextNode( '@-ms-viewport{width:auto!important}' )
2233		);
2234		document.getElementsByTagName( 'head' )[0].appendChild( msViewportStyle );
2235	}
2236})();
2237
2238}( jQuery, window ));
2239
2240/**
2241 * Freeze animated plugin icons when reduced motion is enabled.
2242 *
2243 * When the user has enabled the 'prefers-reduced-motion' setting, this module
2244 * stops animations for all GIFs on the page with the class 'plugin-icon' or
2245 * plugin icon images in the update plugins table.
2246 *
2247 * @since 6.4.0
2248 */
2249(function() {
2250	// Private variables and methods.
2251	var priv = {},
2252		pub = {},
2253		mediaQuery;
2254
2255	// Initialize pauseAll to false; it will be set to true if reduced motion is preferred.
2256	priv.pauseAll = false;
2257	if ( window.matchMedia ) {
2258		mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' );
2259		if ( ! mediaQuery || mediaQuery.matches ) {
2260			priv.pauseAll = true;
2261		}
2262	}
2263
2264	// Method to replace animated GIFs with a static frame.
2265	priv.freezeAnimatedPluginIcons = function( img ) {
2266		var coverImage = function() {
2267			var width = img.width;
2268			var height = img.height;
2269			var canvas = document.createElement( 'canvas' );
2270
2271			// Set canvas dimensions.
2272			canvas.width = width;
2273			canvas.height = height;
2274
2275			// Copy classes from the image to the canvas.
2276			canvas.className = img.className;
2277
2278			// Check if the image is inside a specific table.
2279			var isInsideUpdateTable = img.closest( '#update-plugins-table' );
2280
2281			if ( isInsideUpdateTable ) {
2282				// Transfer computed styles from image to canvas.
2283				var computedStyles = window.getComputedStyle( img ),
2284					i, max;
2285				for ( i = 0, max = computedStyles.length; i < max; i++ ) {
2286					var propName = computedStyles[ i ];
2287					var propValue = computedStyles.getPropertyValue( propName );
2288					canvas.style[ propName ] = propValue;
2289				}
2290			}
2291
2292			// Draw the image onto the canvas.
2293			canvas.getContext( '2d' ).drawImage( img, 0, 0, width, height );
2294
2295			// Set accessibility attributes on canvas.
2296			canvas.setAttribute( 'aria-hidden', 'true' );
2297			canvas.setAttribute( 'role', 'presentation' );
2298
2299			// Insert canvas before the image and set the image to be near-invisible.
2300			var parent = img.parentNode;
2301			parent.insertBefore( canvas, img );
2302			img.style.opacity = 0.01;
2303			img.style.width = '0px';
2304			img.style.height = '0px';
2305		};
2306
2307		// If the image is already loaded, apply the coverImage function.
2308		if ( img.complete ) {
2309			coverImage();
2310		} else {
2311			// Otherwise, wait for the image to load.
2312			img.addEventListener( 'load', coverImage, true );
2313		}
2314	};
2315
2316	// Public method to freeze all relevant GIFs on the page.
2317	pub.freezeAll = function() {
2318		var images = document.querySelectorAll( '.plugin-icon, #update-plugins-table img' );
2319		for ( var x = 0; x < images.length; x++ ) {
2320			if ( /\.gif(?:\?|$)/i.test( images[ x ].src ) ) {
2321				priv.freezeAnimatedPluginIcons( images[ x ] );
2322			}
2323		}
2324	};
2325
2326	// Only run the freezeAll method if the user prefers reduced motion.
2327	if ( true === priv.pauseAll ) {
2328		pub.freezeAll();
2329	}
2330
2331	// Listen for jQuery AJAX events.
2332	( function( $ ) {
2333		if ( window.pagenow === 'plugin-install' ) {
2334			// Only listen for ajaxComplete if this is the plugin-install.php page.
2335			$( document ).ajaxComplete( function( event, xhr, settings ) {
2336
2337				// Check if this is the 'search-install-plugins' request.
2338				if ( settings.data && typeof settings.data === 'string' && settings.data.includes( 'action=search-install-plugins' ) ) {
2339					// Recheck if the user prefers reduced motion.
2340					if ( window.matchMedia ) {
2341						var mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' );
2342						if ( mediaQuery.matches ) {
2343							pub.freezeAll();
2344						}
2345					} else {
2346						// Fallback for browsers that don't support matchMedia.
2347						if ( true === priv.pauseAll ) {
2348							pub.freezeAll();
2349						}
2350					}
2351				}
2352			} );
2353		}
2354	} )( jQuery );
2355
2356	// Expose public methods.
2357	return pub;
2358})();
2359
2360/**
2361 * Validate the delete-and-reassign users form and surface an accessible
2362 * error summary instead of disabling the submit button.
2363 *
2364 * Disabled buttons can't be discovered by assistive technology, so rather
2365 * than blocking submission we let the form submit, intercept it when content
2366 * decisions are still missing, and present a focusable error summary that
2367 * lists how many decisions remain and links straight to each one.
2368 *
2369 * Shared by both the single-site (wp-admin/users.php) and multisite/network
2370 * (confirm_delete_users() in wp-admin/includes/ms.php) deletion forms. The two
2371 * differ in markup: single site has one content decision per user, multisite
2372 * has one decision per site a user belongs to (several radio groups per
2373 * fieldset), and their reassign dropdowns use different "no selection" values.
2374 * The logic below works per radio group so it covers both.
2375 *
2376 * @since 7.1.0
2377 */
2378(function(){
2379	const { _n, sprintf } = wp.i18n;
2380	const usersForm = document.querySelector( '.delete-and-reassign-users-form' );
2381
2382	// Check if the form exists and contains any radio buttons.
2383	if ( ! usersForm || ! usersForm.querySelector( 'input[type="radio"]' ) ) {
2384		return;
2385	}
2386
2387	const summaryId = 'delete-users-error-summary';
2388
2389	/**
2390	 * Whether a reassign dropdown has no user selected.
2391	 *
2392	 * The "Select a user" placeholder value differs between the forms: the
2393	 * single-site dropdown uses an empty string, the multisite one uses the
2394	 * wp_dropdown_users() default of '-1'.
2395	 *
2396	 * @param {HTMLSelectElement} select The reassign dropdown.
2397	 * @return {boolean} True when no real user is selected.
2398	 */
2399	function hasNoSelectedUser( select ) {
2400		return '' === select.value || '-1' === select.value;
2401	}
2402
2403	/**
2404	 * Builds a human-readable label for a radio group's decision.
2405	 *
2406	 * Combines the fieldset legend (the user) with the site context that
2407	 * precedes the group on multisite, so each summary entry is identifiable.
2408	 *
2409	 * @param {HTMLElement} group The radio group (<ul>) element.
2410	 * @return {string} The composed label.
2411	 */
2412	function getDecisionLabel( group ) {
2413		const fieldset = group.closest( 'fieldset' );
2414		const legend   = fieldset ? fieldset.querySelector( 'legend' ) : null;
2415		const parts    = [];
2416
2417		if ( legend ) {
2418			parts.push( legend.textContent.trim() );
2419		}
2420
2421		// On multisite each radio group is preceded by a "Site: …" paragraph.
2422		const previous = group.previousElementSibling;
2423		if ( previous && previous !== legend && previous.textContent.trim() ) {
2424			parts.push( previous.textContent.trim() );
2425		}
2426
2427		return parts.join( ' – ' );
2428	}
2429
2430	// Keep the radio selection in sync with the reassign dropdown.
2431	usersForm.querySelectorAll( 'select' ).forEach( function( selectElement ) {
2432		selectElement.addEventListener( 'change', function( e ) {
2433			const item  = e.target.closest( 'li' );
2434			const radio = item ? item.querySelector( 'input[type="radio"]' ) : null;
2435			if ( radio ) {
2436				radio.checked = ! hasNoSelectedUser( e.target );
2437			}
2438		});
2439	});
2440
2441	/**
2442	 * Returns the radio groups whose content decision is still incomplete.
2443	 *
2444	 * A decision unit is a single radio group (<ul>), which maps to one user on
2445	 * single site and one site-per-user on multisite.
2446	 *
2447	 * @return {Array} Objects describing each incomplete decision.
2448	 */
2449	function getIncompleteDecisions() {
2450		const incomplete = [];
2451
2452		usersForm.querySelectorAll( 'fieldset ul' ).forEach( function( group ) {
2453			const radios = group.querySelectorAll( 'input[type="radio"]' );
2454			if ( ! radios.length ) {
2455				return;
2456			}
2457
2458			const checked = group.querySelector( 'input[type="radio"]:checked' );
2459
2460			// No option chosen yet.
2461			if ( ! checked ) {
2462				incomplete.push( { target: radios[ 0 ], label: getDecisionLabel( group ) } );
2463				return;
2464			}
2465
2466			// "Attribute to another user" chosen, but no user selected.
2467			if ( 'reassign' === checked.value ) {
2468				const select = group.querySelector( 'select' );
2469				if ( select && hasNoSelectedUser( select ) ) {
2470					incomplete.push( { target: select, label: getDecisionLabel( group ) } );
2471				}
2472			}
2473		});
2474
2475		return incomplete;
2476	}
2477
2478	/**
2479	 * Builds or refreshes the error summary markup.
2480	 *
2481	 * @param {Array} incomplete Incomplete decisions from getIncompleteDecisions().
2482	 * @return {string} The summary title, for announcing to assistive technology.
2483	 */
2484	function renderErrorSummary( incomplete ) {
2485		let summary = document.getElementById( summaryId );
2486
2487		if ( ! summary ) {
2488			summary = document.createElement( 'div' );
2489			summary.id = summaryId;
2490			summary.className = 'notice notice-error';
2491			summary.setAttribute( 'tabindex', '-1' );
2492
2493			// The wrapper contains the form on single site and wraps it on
2494			// multisite; insert the summary right after the page heading.
2495			const wrap    = usersForm.querySelector( '.wrap' ) || usersForm.closest( '.wrap' ) || usersForm;
2496			const heading = wrap.querySelector( 'h1' );
2497			wrap.insertBefore( summary, heading ? heading.nextSibling : wrap.firstChild );
2498		}
2499
2500		const count = incomplete.length;
2501		const title = sprintf(
2502			/* translators: %s: Number of content decisions still required. */
2503			_n(
2504				'%s content decision is still required before you can delete.',
2505				'%s content decisions are still required before you can delete.',
2506				count
2507			),
2508			count
2509		);
2510
2511		// Clear any previous markup and invalid states before rebuilding,
2512		// so decisions resolved since the last render are no longer flagged.
2513		summary.textContent = '';
2514		usersForm.querySelectorAll( '[aria-invalid]' ).forEach( function( el ) {
2515			el.removeAttribute( 'aria-invalid' );
2516		});
2517
2518		const titleEl = document.createElement( 'p' );
2519		const strong  = document.createElement( 'strong' );
2520		strong.textContent = title;
2521		titleEl.appendChild( strong );
2522		summary.appendChild( titleEl );
2523
2524		const list = document.createElement( 'ul' );
2525		incomplete.forEach( function( item ) {
2526			const li   = document.createElement( 'li' );
2527			const link = document.createElement( 'a' );
2528			link.href        = '#' + item.target.id;
2529			link.textContent = item.label;
2530			link.addEventListener( 'click', function( e ) {
2531				e.preventDefault();
2532				item.target.focus();
2533			});
2534			li.appendChild( link );
2535			list.appendChild( li );
2536
2537			item.target.setAttribute( 'aria-invalid', 'true' );
2538		});
2539		summary.appendChild( list );
2540
2541		return title;
2542	}
2543
2544	/**
2545	 * Removes the error summary and clears invalid states.
2546	 */
2547	function clearErrorState() {
2548		const summary = document.getElementById( summaryId );
2549		if ( summary ) {
2550			summary.remove();
2551		}
2552		usersForm.querySelectorAll( '[aria-invalid]' ).forEach( function( el ) {
2553			el.removeAttribute( 'aria-invalid' );
2554		});
2555	}
2556
2557	/**
2558	 * Refreshes the error summary to match the current form state.
2559	 *
2560	 * @param {boolean} moveFocus Whether to move focus to the summary and
2561	 *                            announce it (used on a failed submit).
2562	 * @return {number} The number of incomplete decisions.
2563	 */
2564	function updateSummary( moveFocus ) {
2565		const incomplete = getIncompleteDecisions();
2566
2567		if ( ! incomplete.length ) {
2568			clearErrorState();
2569			return 0;
2570		}
2571
2572		const title = renderErrorSummary( incomplete );
2573
2574		if ( moveFocus ) {
2575			document.getElementById( summaryId ).focus();
2576			if ( window.wp && window.wp.a11y ) {
2577				window.wp.a11y.speak( title, 'assertive' );
2578			}
2579		}
2580
2581		return incomplete.length;
2582	}
2583
2584	usersForm.addEventListener( 'submit', function( e ) {
2585		if ( updateSummary( true ) > 0 ) {
2586			e.preventDefault();
2587		}
2588	});
2589
2590	// Keep an existing summary current as decisions are resolved, without
2591	// stealing focus on every interaction.
2592	usersForm.addEventListener( 'change', function() {
2593		if ( document.getElementById( summaryId ) ) {
2594			updateSummary( false );
2595		}
2596	});
2597})();

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.