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