[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/js/ -> common.js (source)

   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       * Handles the `aria-haspopup` attribute on the current menu item when it has a submenu.
 907       *
 908       * @since 4.4.0
 909       *
 910       * @return {void}
 911       */
 912  	function currentMenuItemHasPopup() {
 913          var $current = $( 'a.wp-has-current-submenu' );
 914  
 915          if ( 'folded' === menuState ) {
 916              // When folded or auto-folded and not responsive view, the current menu item does have a fly-out sub-menu.
 917              $current.attr( 'aria-haspopup', 'true' );
 918          } else {
 919              // When expanded or in responsive view, reset aria-haspopup.
 920              $current.attr( 'aria-haspopup', 'false' );
 921          }
 922      }
 923  
 924      $document.on( 'wp-menu-state-set wp-collapse-menu wp-responsive-activate wp-responsive-deactivate', currentMenuItemHasPopup );
 925  
 926      /**
 927       * Ensures an admin submenu is within the visual viewport.
 928       *
 929       * @since 4.1.0
 930       *
 931       * @param {jQuery} $menuItem The parent menu item containing the submenu.
 932       *
 933       * @return {void}
 934       */
 935  	function adjustSubmenu( $menuItem ) {
 936          var bottomOffset, pageHeight, adjustment, theFold, menutop, wintop, maxtop,
 937              $submenu = $menuItem.find( '.wp-submenu' );
 938  
 939          menutop = $menuItem.offset().top;
 940          wintop = $window.scrollTop();
 941          maxtop = menutop - wintop - 30; // max = make the top of the sub almost touch admin bar.
 942  
 943          bottomOffset = menutop + $submenu.height() + 1; // Bottom offset of the menu.
 944          pageHeight = $wpwrap.height();                  // Height of the entire page.
 945          adjustment = 60 + bottomOffset - pageHeight;
 946          theFold = $window.height() + wintop - 50;       // The fold.
 947  
 948          if ( theFold < ( bottomOffset - adjustment ) ) {
 949              adjustment = bottomOffset - theFold;
 950          }
 951  
 952          if ( adjustment > maxtop ) {
 953              adjustment = maxtop;
 954          }
 955  
 956          if ( adjustment > 1 && $('#wp-admin-bar-menu-toggle').is(':hidden') ) {
 957              $submenu.css( 'margin-top', '-' + adjustment + 'px' );
 958          } else {
 959              $submenu.css( 'margin-top', '' );
 960          }
 961      }
 962  
 963      if ( 'ontouchstart' in window || /IEMobile\/[1-9]/.test(navigator.userAgent) ) { // Touch screen device.
 964          // iOS Safari works with touchstart, the rest work with click.
 965          mobileEvent = isIOS ? 'touchstart' : 'click';
 966  
 967          /**
 968           * Closes any open submenus when touch/click is not on the menu.
 969           *
 970           * @param {Event} e The event object.
 971           *
 972           * @return {void}
 973           */
 974          $body.on( mobileEvent+'.wp-mobile-hover', function(e) {
 975              if ( $adminmenu.data('wp-responsive') ) {
 976                  return;
 977              }
 978  
 979              if ( ! $( e.target ).closest( '#adminmenu' ).length ) {
 980                  $adminmenu.find( 'li.opensub' ).removeClass( 'opensub' );
 981              }
 982          });
 983  
 984          /**
 985           * Handles the opening or closing the submenu based on the mobile click|touch event.
 986           *
 987           * @param {Event} event The event object.
 988           *
 989           * @return {void}
 990           */
 991          $adminmenu.find( 'a.wp-has-submenu' ).on( mobileEvent + '.wp-mobile-hover', function( event ) {
 992              var $menuItem = $(this).parent();
 993  
 994              if ( $adminmenu.data( 'wp-responsive' ) ) {
 995                  return;
 996              }
 997  
 998              /*
 999               * Show the sub instead of following the link if:
1000               *     - the submenu is not open.
1001               *     - the submenu is not shown inline or the menu is not folded.
1002               */
1003              if ( ! $menuItem.hasClass( 'opensub' ) && ( ! $menuItem.hasClass( 'wp-menu-open' ) || $menuItem.width() < 40 ) ) {
1004                  event.preventDefault();
1005                  adjustSubmenu( $menuItem );
1006                  $adminmenu.find( 'li.opensub' ).removeClass( 'opensub' );
1007                  $menuItem.addClass('opensub');
1008              }
1009          });
1010      }
1011  
1012      if ( ! isIOS && ! isAndroid ) {
1013          $adminmenu.find( 'li.wp-has-submenu' ).hoverIntent({
1014  
1015              /**
1016               * Opens the submenu when hovered over the menu item for desktops.
1017               *
1018               * @return {void}
1019               */
1020              over: function() {
1021                  var $menuItem = $( this ),
1022                      $submenu = $menuItem.find( '.wp-submenu' ),
1023                      top = parseInt( $submenu.css( 'top' ), 10 );
1024  
1025                  if ( isNaN( top ) || top > -5 ) { // The submenu is visible.
1026                      return;
1027                  }
1028  
1029                  if ( $adminmenu.data( 'wp-responsive' ) ) {
1030                      // The menu is in responsive mode, bail.
1031                      return;
1032                  }
1033  
1034                  adjustSubmenu( $menuItem );
1035                  $adminmenu.find( 'li.opensub' ).removeClass( 'opensub' );
1036                  $menuItem.addClass( 'opensub' );
1037              },
1038  
1039              /**
1040               * Closes the submenu when no longer hovering the menu item.
1041               *
1042               * @return {void}
1043               */
1044              out: function(){
1045                  if ( $adminmenu.data( 'wp-responsive' ) ) {
1046                      // The menu is in responsive mode, bail.
1047                      return;
1048                  }
1049  
1050                  $( this ).removeClass( 'opensub' ).find( '.wp-submenu' ).css( 'margin-top', '' );
1051              },
1052              timeout: 200,
1053              sensitivity: 7,
1054              interval: 90
1055          });
1056  
1057          /**
1058           * Opens the submenu on when focused on the menu item.
1059           *
1060           * @param {Event} event The event object.
1061           *
1062           * @return {void}
1063           */
1064          $adminmenu.on( 'focus.adminmenu', '.wp-submenu a', function( event ) {
1065              if ( $adminmenu.data( 'wp-responsive' ) ) {
1066                  // The menu is in responsive mode, bail.
1067                  return;
1068              }
1069  
1070              $( event.target ).closest( 'li.menu-top' ).addClass( 'opensub' );
1071  
1072              /**
1073               * Closes the submenu on blur from the menu item.
1074               *
1075               * @param {Event} event The event object.
1076               *
1077               * @return {void}
1078               */
1079          }).on( 'blur.adminmenu', '.wp-submenu a', function( event ) {
1080              if ( $adminmenu.data( 'wp-responsive' ) ) {
1081                  return;
1082              }
1083  
1084              $( event.target ).closest( 'li.menu-top' ).removeClass( 'opensub' );
1085  
1086              /**
1087               * Adjusts the size for the submenu.
1088               *
1089               * @return {void}
1090               */
1091          }).find( 'li.wp-has-submenu.wp-not-current-submenu' ).on( 'focusin.adminmenu', function() {
1092              adjustSubmenu( $( this ) );
1093          });
1094      }
1095  
1096      /*
1097       * The `.below-h2` class is here just for backward compatibility with plugins
1098       * that are (incorrectly) using it. Do not use. Use `.inline` instead. See #34570.
1099       * If '.wp-header-end' is found, append the notices after it otherwise
1100       * after the first h1 or h2 heading found within the main content.
1101       */
1102      if ( ! $headerEnd.length ) {
1103          $headerEnd = $( '.wrap h1, .wrap h2' ).first();
1104      }
1105      $( 'div.updated, div.error, div.notice' ).not( '.inline, .below-h2' ).insertAfter( $headerEnd );
1106  
1107      /**
1108       * Makes notices dismissible.
1109       *
1110       * @since 4.4.0
1111       *
1112       * @return {void}
1113       */
1114  	function makeNoticesDismissible() {
1115          $( '.notice.is-dismissible' ).each( function() {
1116              var $el = $( this ),
1117                  $button = $( '<button type="button" class="notice-dismiss"><span class="screen-reader-text"></span></button>' );
1118  
1119              if ( $el.find( '.notice-dismiss' ).length ) {
1120                  return;
1121              }
1122  
1123              // Ensure plain text.
1124              $button.find( '.screen-reader-text' ).text( __( 'Dismiss this notice.' ) );
1125              $button.on( 'click.wp-dismiss-notice', function( event ) {
1126                  event.preventDefault();
1127                  $el.fadeTo( 100, 0, function() {
1128                      $el.slideUp( 100, function() {
1129                          $el.remove();
1130                      });
1131                  });
1132              });
1133  
1134              $el.append( $button );
1135          });
1136      }
1137  
1138      $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', makeNoticesDismissible );
1139  
1140      // Init screen meta.
1141      screenMeta.init();
1142  
1143      /**
1144       * Checks a checkbox.
1145       *
1146       * This event needs to be delegated. Ticket #37973.
1147       *
1148       * @return {boolean} Returns whether a checkbox is checked or not.
1149       */
1150      $body.on( 'click', 'tbody > tr > .check-column :checkbox', function( event ) {
1151          // Shift click to select a range of checkboxes.
1152          if ( 'undefined' == event.shiftKey ) { return true; }
1153          if ( event.shiftKey ) {
1154              if ( !lastClicked ) { return true; }
1155              checks = $( lastClicked ).closest( 'form' ).find( ':checkbox' ).filter( ':visible:enabled' );
1156              first = checks.index( lastClicked );
1157              last = checks.index( this );
1158              checked = $(this).prop('checked');
1159              if ( 0 < first && 0 < last && first != last ) {
1160                  sliced = ( last > first ) ? checks.slice( first, last ) : checks.slice( last, first );
1161                  sliced.prop( 'checked', function() {
1162                      if ( $(this).closest('tr').is(':visible') )
1163                          return checked;
1164  
1165                      return false;
1166                  });
1167              }
1168          }
1169          lastClicked = this;
1170  
1171          // Toggle the "Select all" checkboxes depending if the other ones are all checked or not.
1172          var unchecked = $(this).closest('tbody').find('tr.iedit').find(':checkbox').filter(':visible:enabled').not(':checked');
1173  
1174          /**
1175           * Determines if all checkboxes are checked.
1176           *
1177           * @return {boolean} Returns true if there are no unchecked checkboxes.
1178           */
1179          $(this).closest('table').children('thead, tfoot').find(':checkbox').prop('checked', function() {
1180              return ( 0 === unchecked.length );
1181          });
1182  
1183          return true;
1184      });
1185  
1186      /**
1187       * Controls all the toggles on bulk toggle change.
1188       *
1189       * When the bulk checkbox is changed, all the checkboxes in the tables are changed accordingly.
1190       * When the shift-button is pressed while changing the bulk checkbox the checkboxes in the table are inverted.
1191       *
1192       * This event needs to be delegated. Ticket #37973.
1193       *
1194       * @param {Event} event The event object.
1195       *
1196       * @return {boolean}
1197       */
1198      $body.on( 'click.wp-toggle-checkboxes', 'thead .check-column :checkbox, tfoot .check-column :checkbox', function( event ) {
1199          var $this = $(this),
1200              $table = $this.closest( 'table' ),
1201              controlChecked = $this.prop('checked'),
1202              toggle = event.shiftKey || $this.data('wp-toggle');
1203  
1204          $table.children( 'tbody' ).filter(':visible')
1205              .children().children('.check-column').find(':checkbox')
1206              /**
1207               * Updates the checked state on the checkbox in the table.
1208               *
1209               * @return {boolean} True checks the checkbox, False unchecks the checkbox.
1210               */
1211              .prop('checked', function() {
1212                  if ( $(this).is(':hidden,:disabled') ) {
1213                      return false;
1214                  }
1215  
1216                  if ( toggle ) {
1217                      return ! $(this).prop( 'checked' );
1218                  } else if ( controlChecked ) {
1219                      return true;
1220                  }
1221  
1222                  return false;
1223              });
1224  
1225          $table.children('thead,  tfoot').filter(':visible')
1226              .children().children('.check-column').find(':checkbox')
1227  
1228              /**
1229               * Syncs the bulk checkboxes on the top and bottom of the table.
1230               *
1231               * @return {boolean} True checks the checkbox, False unchecks the checkbox.
1232               */
1233              .prop('checked', function() {
1234                  if ( toggle ) {
1235                      return false;
1236                  } else if ( controlChecked ) {
1237                      return true;
1238                  }
1239  
1240                  return false;
1241              });
1242      });
1243  
1244      /**
1245       * Marries a secondary control to its primary control.
1246       *
1247       * @param {jQuery} topSelector    The top selector element.
1248       * @param {jQuery} topSubmit      The top submit element.
1249       * @param {jQuery} bottomSelector The bottom selector element.
1250       * @param {jQuery} bottomSubmit   The bottom submit element.
1251       * @return {void}
1252       */
1253  	function marryControls( topSelector, topSubmit, bottomSelector, bottomSubmit ) {
1254          /**
1255           * Updates the primary selector when the secondary selector is changed.
1256           *
1257           * @since 5.7.0
1258           *
1259           * @return {void}
1260           */
1261  		function updateTopSelector() {
1262              topSelector.val($(this).val());
1263          }
1264          bottomSelector.on('change', updateTopSelector);
1265  
1266          /**
1267           * Updates the secondary selector when the primary selector is changed.
1268           *
1269           * @since 5.7.0
1270           *
1271           * @return {void}
1272           */
1273  		function updateBottomSelector() {
1274              bottomSelector.val($(this).val());
1275          }
1276          topSelector.on('change', updateBottomSelector);
1277  
1278          /**
1279           * Triggers the primary submit when then secondary submit is clicked.
1280           *
1281           * @since 5.7.0
1282           *
1283           * @return {void}
1284           */
1285  		function triggerSubmitClick(e) {
1286              e.preventDefault();
1287              e.stopPropagation();
1288  
1289              topSubmit.trigger('click');
1290          }
1291          bottomSubmit.on('click', triggerSubmitClick);
1292      }
1293  
1294      // Marry the secondary "Bulk actions" controls to the primary controls:
1295      marryControls( $('#bulk-action-selector-top'), $('#doaction'), $('#bulk-action-selector-bottom'), $('#doaction2') );
1296  
1297      // Marry the secondary "Change role to" controls to the primary controls:
1298      marryControls( $('#new_role'), $('#changeit'), $('#new_role2'), $('#changeit2') );
1299  
1300      /**
1301       * Shows row actions on focus of its parent container element or any other elements contained within.
1302       *
1303       * @return {void}
1304       */
1305      $( '#wpbody-content' ).on({
1306          focusin: function() {
1307              clearTimeout( transitionTimeout );
1308              focusedRowActions = $( this ).find( '.row-actions' );
1309              // transitionTimeout is necessary for Firefox, but Chrome won't remove the CSS class without a little help.
1310              $( '.row-actions' ).not( this ).removeClass( 'visible' );
1311              focusedRowActions.addClass( 'visible' );
1312          },
1313          focusout: function() {
1314              // Tabbing between post title and .row-actions links needs a brief pause, otherwise
1315              // the .row-actions div gets hidden in transit in some browsers (ahem, Firefox).
1316              transitionTimeout = setTimeout( function() {
1317                  focusedRowActions.removeClass( 'visible' );
1318              }, 30 );
1319          }
1320      }, '.table-view-list .has-row-actions' );
1321  
1322      // Toggle list table rows on small screens.
1323      $( 'tbody' ).on( 'click', '.toggle-row', function() {
1324          $( this ).closest( 'tr' ).toggleClass( 'is-expanded' );
1325      });
1326  
1327      $('#default-password-nag-no').on( 'click', function() {
1328          setUserSetting('default_password_nag', 'hide');
1329          $('div.default-password-nag').hide();
1330          return false;
1331      });
1332  
1333      /**
1334       * Handles tab keypresses in theme and plugin file editor textareas.
1335       *
1336       * @param {Event} e The event object.
1337       *
1338       * @return {void}
1339       */
1340      $('#newcontent').on('keydown.wpevent_InsertTab', function(e) {
1341          var el = e.target, selStart, selEnd, val, scroll, sel;
1342  
1343          // After pressing escape key (keyCode: 27), the tab key should tab out of the textarea.
1344          if ( e.keyCode == 27 ) {
1345              // When pressing Escape: Opera 12 and 27 blur form fields, IE 8 clears them.
1346              e.preventDefault();
1347              $(el).data('tab-out', true);
1348              return;
1349          }
1350  
1351          // Only listen for plain tab key (keyCode: 9) without any modifiers.
1352          if ( e.keyCode != 9 || e.ctrlKey || e.altKey || e.shiftKey )
1353              return;
1354  
1355          // After tabbing out, reset it so next time the tab key can be used again.
1356          if ( $(el).data('tab-out') ) {
1357              $(el).data('tab-out', false);
1358              return;
1359          }
1360  
1361          selStart = el.selectionStart;
1362          selEnd = el.selectionEnd;
1363          val = el.value;
1364  
1365          // If any text is selected, replace the selection with a tab character.
1366          if ( document.selection ) {
1367              el.focus();
1368              sel = document.selection.createRange();
1369              sel.text = '\t';
1370          } else if ( selStart >= 0 ) {
1371              scroll = this.scrollTop;
1372              el.value = val.substring(0, selStart).concat('\t', val.substring(selEnd) );
1373              el.selectionStart = el.selectionEnd = selStart + 1;
1374              this.scrollTop = scroll;
1375          }
1376  
1377          // Cancel the regular tab functionality, to prevent losing focus of the textarea.
1378          if ( e.stopPropagation )
1379              e.stopPropagation();
1380          if ( e.preventDefault )
1381              e.preventDefault();
1382      });
1383  
1384      // Reset page number variable for new filters/searches but not for bulk actions. See #17685.
1385      if ( pageInput.length ) {
1386  
1387          /**
1388           * Handles pagination variable when filtering the list table.
1389           *
1390           * Set the pagination argument to the first page when the post-filter form is submitted.
1391           * This happens when pressing the 'filter' button on the list table page.
1392           *
1393           * The pagination argument should not be touched when the bulk action dropdowns are set to do anything.
1394           *
1395           * The form closest to the pageInput is the post-filter form.
1396           *
1397           * @return {void}
1398           */
1399          pageInput.closest('form').on( 'submit', function() {
1400              /*
1401               * action = bulk action dropdown at the top of the table
1402               */
1403              if ( $('select[name="action"]').val() == -1 && pageInput.val() == currentPage )
1404                  pageInput.val('1');
1405          });
1406      }
1407  
1408      /**
1409       * Resets the bulk actions when the search button is clicked.
1410       *
1411       * @return {void}
1412       */
1413      $('.search-box input[type="search"], .search-box input[type="submit"]').on( 'mousedown', function () {
1414          $('select[name^="action"]').val('-1');
1415      });
1416  
1417      /**
1418       * Scrolls into view when focus.scroll-into-view is triggered.
1419       *
1420       * @param {Event} e The event object.
1421       *
1422       * @return {void}
1423        */
1424      $('#contextual-help-link, #show-settings-link').on( 'focus.scroll-into-view', function(e){
1425          if ( e.target.scrollIntoViewIfNeeded )
1426              e.target.scrollIntoViewIfNeeded(false);
1427      });
1428  
1429      /**
1430       * Disables the submit upload buttons when no data is entered.
1431       *
1432       * @return {void}
1433       */
1434      (function(){
1435          var button, input, form = $('form.wp-upload-form');
1436  
1437          // Exit when no upload form is found.
1438          if ( ! form.length )
1439              return;
1440  
1441          button = form.find('input[type="submit"]');
1442          input = form.find('input[type="file"]');
1443  
1444          /**
1445           * Determines if any data is entered in any file upload input.
1446           *
1447           * @since 3.5.0
1448           *
1449           * @return {void}
1450           */
1451  		function toggleUploadButton() {
1452              // When no inputs have a value, disable the upload buttons.
1453              button.prop('disabled', '' === input.map( function() {
1454                  return $(this).val();
1455              }).get().join(''));
1456          }
1457  
1458          // Update the status initially.
1459          toggleUploadButton();
1460          // Update the status when any file input changes.
1461          input.on('change', toggleUploadButton);
1462      })();
1463  
1464      /**
1465       * Pins the menu while distraction-free writing is enabled.
1466       *
1467       * @param {Event} event Event data.
1468       *
1469       * @since 4.1.0
1470       *
1471       * @return {void}
1472       */
1473  	function pinMenu( event ) {
1474          var windowPos = $window.scrollTop(),
1475              resizing = ! event || event.type !== 'scroll';
1476  
1477          if ( isIOS || $adminmenu.data( 'wp-responsive' ) ) {
1478              return;
1479          }
1480  
1481          /*
1482           * When the menu is higher than the window and smaller than the entire page.
1483           * It should be adjusted to be able to see the entire menu.
1484           *
1485           * Otherwise it can be accessed normally.
1486           */
1487          if ( height.menu + height.adminbar < height.window ||
1488              height.menu + height.adminbar + 20 > height.wpwrap ) {
1489              unpinMenu();
1490              return;
1491          }
1492  
1493          menuIsPinned = true;
1494  
1495          // If the menu is higher than the window, compensate on scroll.
1496          if ( height.menu + height.adminbar > height.window ) {
1497              // Check for overscrolling, this happens when swiping up at the top of the document in modern browsers.
1498              if ( windowPos < 0 ) {
1499                  // Stick the menu to the top.
1500                  if ( ! pinnedMenuTop ) {
1501                      pinnedMenuTop = true;
1502                      pinnedMenuBottom = false;
1503  
1504                      $adminMenuWrap.css({
1505                          position: 'fixed',
1506                          top: '',
1507                          bottom: ''
1508                      });
1509                  }
1510  
1511                  return;
1512              } else if ( windowPos + height.window > $document.height() - 1 ) {
1513                  // When overscrolling at the bottom, stick the menu to the bottom.
1514                  if ( ! pinnedMenuBottom ) {
1515                      pinnedMenuBottom = true;
1516                      pinnedMenuTop = false;
1517  
1518                      $adminMenuWrap.css({
1519                          position: 'fixed',
1520                          top: '',
1521                          bottom: 0
1522                      });
1523                  }
1524  
1525                  return;
1526              }
1527  
1528              if ( windowPos > lastScrollPosition ) {
1529                  // When a down scroll has been detected.
1530  
1531                  // If it was pinned to the top, unpin and calculate relative scroll.
1532                  if ( pinnedMenuTop ) {
1533                      pinnedMenuTop = false;
1534                      // Calculate new offset position.
1535                      menuTop = $adminMenuWrap.offset().top - height.adminbar - ( windowPos - lastScrollPosition );
1536  
1537                      if ( menuTop + height.menu + height.adminbar < windowPos + height.window ) {
1538                          menuTop = windowPos + height.window - height.menu - height.adminbar;
1539                      }
1540  
1541                      $adminMenuWrap.css({
1542                          position: 'absolute',
1543                          top: menuTop,
1544                          bottom: ''
1545                      });
1546                  } else if ( ! pinnedMenuBottom && $adminMenuWrap.offset().top + height.menu < windowPos + height.window ) {
1547                      // Pin it to the bottom.
1548                      pinnedMenuBottom = true;
1549  
1550                      $adminMenuWrap.css({
1551                          position: 'fixed',
1552                          top: '',
1553                          bottom: 0
1554                      });
1555                  }
1556              } else if ( windowPos < lastScrollPosition ) {
1557                  // When a scroll up is detected.
1558  
1559                  // If it was pinned to the bottom, unpin and calculate relative scroll.
1560                  if ( pinnedMenuBottom ) {
1561                      pinnedMenuBottom = false;
1562  
1563                      // Calculate new offset position.
1564                      menuTop = $adminMenuWrap.offset().top - height.adminbar + ( lastScrollPosition - windowPos );
1565  
1566                      if ( menuTop + height.menu > windowPos + height.window ) {
1567                          menuTop = windowPos;
1568                      }
1569  
1570                      $adminMenuWrap.css({
1571                          position: 'absolute',
1572                          top: menuTop,
1573                          bottom: ''
1574                      });
1575                  } else if ( ! pinnedMenuTop && $adminMenuWrap.offset().top >= windowPos + height.adminbar ) {
1576  
1577                      // Pin it to the top.
1578                      pinnedMenuTop = true;
1579  
1580                      $adminMenuWrap.css({
1581                          position: 'fixed',
1582                          top: '',
1583                          bottom: ''
1584                      });
1585                  }
1586              } else if ( resizing ) {
1587                  // Window is being resized.
1588  
1589                  pinnedMenuTop = pinnedMenuBottom = false;
1590  
1591                  // Calculate the new offset.
1592                  menuTop = windowPos + height.window - height.menu - height.adminbar - 1;
1593  
1594                  if ( menuTop > 0 ) {
1595                      $adminMenuWrap.css({
1596                          position: 'absolute',
1597                          top: menuTop,
1598                          bottom: ''
1599                      });
1600                  } else {
1601                      unpinMenu();
1602                  }
1603              }
1604          }
1605  
1606          lastScrollPosition = windowPos;
1607      }
1608  
1609      /**
1610       * Determines the height of certain elements.
1611       *
1612       * @since 4.1.0
1613       *
1614       * @return {void}
1615       */
1616  	function resetHeights() {
1617          height = {
1618              window: $window.height(),
1619              wpwrap: $wpwrap.height(),
1620              adminbar: $adminbar.height(),
1621              menu: $adminMenuWrap.height()
1622          };
1623      }
1624  
1625      /**
1626       * Unpins the menu.
1627       *
1628       * @since 4.1.0
1629       *
1630       * @return {void}
1631       */
1632  	function unpinMenu() {
1633          if ( isIOS || ! menuIsPinned ) {
1634              return;
1635          }
1636  
1637          pinnedMenuTop = pinnedMenuBottom = menuIsPinned = false;
1638          $adminMenuWrap.css({
1639              position: '',
1640              top: '',
1641              bottom: ''
1642          });
1643      }
1644  
1645      /**
1646       * Pins and unpins the menu when applicable.
1647       *
1648       * @since 4.1.0
1649       *
1650       * @return {void}
1651       */
1652  	function setPinMenu() {
1653          resetHeights();
1654  
1655          if ( $adminmenu.data('wp-responsive') ) {
1656              $body.removeClass( 'sticky-menu' );
1657              unpinMenu();
1658          } else if ( height.menu + height.adminbar > height.window ) {
1659              pinMenu();
1660              $body.removeClass( 'sticky-menu' );
1661          } else {
1662              $body.addClass( 'sticky-menu' );
1663              unpinMenu();
1664          }
1665      }
1666  
1667      if ( ! isIOS ) {
1668          $window.on( 'scroll.pin-menu', pinMenu );
1669          $document.on( 'tinymce-editor-init.pin-menu', function( event, editor ) {
1670              editor.on( 'wp-autoresize', resetHeights );
1671          });
1672      }
1673  
1674      /**
1675       * Changes the sortables and responsiveness of metaboxes.
1676       *
1677       * @since 3.8.0
1678       *
1679       * @return {void}
1680       */
1681      window.wpResponsive = {
1682  
1683          /**
1684           * Initializes the wpResponsive object.
1685           *
1686           * @since 3.8.0
1687           *
1688           * @return {void}
1689           */
1690          init: function() {
1691              var self = this;
1692  
1693              this.maybeDisableSortables = this.maybeDisableSortables.bind( this );
1694  
1695              // Modify functionality based on custom activate/deactivate event.
1696              $document.on( 'wp-responsive-activate.wp-responsive', function() {
1697                  self.activate();
1698              }).on( 'wp-responsive-deactivate.wp-responsive', function() {
1699                  self.deactivate();
1700              });
1701  
1702              $( '#wp-admin-bar-menu-toggle a' ).attr( 'aria-expanded', 'false' );
1703  
1704              // Toggle sidebar when toggle is clicked.
1705              $( '#wp-admin-bar-menu-toggle' ).on( 'click.wp-responsive', function( event ) {
1706                  event.preventDefault();
1707  
1708                  // Close any open toolbar submenus.
1709                  $adminbar.find( '.hover' ).removeClass( 'hover' );
1710  
1711                  $wpwrap.toggleClass( 'wp-responsive-open' );
1712                  if ( $wpwrap.hasClass( 'wp-responsive-open' ) ) {
1713                      $(this).find('a').attr( 'aria-expanded', 'true' );
1714                      $( '#adminmenu a:first' ).trigger( 'focus' );
1715                  } else {
1716                      $(this).find('a').attr( 'aria-expanded', 'false' );
1717                  }
1718              } );
1719  
1720              // Close sidebar when target moves outside of toggle and sidebar.
1721              $( document ).on( 'click', function( event ) {
1722                  if ( ! $wpwrap.hasClass( 'wp-responsive-open' ) || ! document.hasFocus() ) {
1723                      return;
1724                  }
1725  
1726                  var focusIsInToggle  = $.contains( $( '#wp-admin-bar-menu-toggle' )[0], event.target );
1727                  var focusIsInSidebar = $.contains( $( '#adminmenuwrap' )[0], event.target );
1728  
1729                  if ( ! focusIsInToggle && ! focusIsInSidebar ) {
1730                      $( '#wp-admin-bar-menu-toggle' ).trigger( 'click.wp-responsive' );
1731                  }
1732              } );
1733  
1734              // Close sidebar when a keypress completes outside of toggle and sidebar.
1735              $( document ).on( 'keyup', function( event ) {
1736                  var toggleButton   = $( '#wp-admin-bar-menu-toggle' )[0];
1737                  if ( ! $wpwrap.hasClass( 'wp-responsive-open' ) ) {
1738                      return;
1739                  }
1740                  if ( 27 === event.keyCode ) {
1741                      $( toggleButton ).trigger( 'click.wp-responsive' );
1742                      $( toggleButton ).find( 'a' ).trigger( 'focus' );
1743                  } else {
1744                      if ( 9 === event.keyCode ) {
1745                          var sidebar        = $( '#adminmenuwrap' )[0];
1746                          var focusedElement = event.relatedTarget || document.activeElement;
1747                          // A brief delay is required to allow focus to switch to another element.
1748                          setTimeout( function() {
1749                              var focusIsInToggle  = $.contains( toggleButton, focusedElement );
1750                              var focusIsInSidebar = $.contains( sidebar, focusedElement );
1751                              
1752                              if ( ! focusIsInToggle && ! focusIsInSidebar ) {
1753                                  $( toggleButton ).trigger( 'click.wp-responsive' );
1754                              }
1755                          }, 10 );
1756                      }
1757                  }
1758              });
1759  
1760              // Add menu events.
1761              $adminmenu.on( 'click.wp-responsive', 'li.wp-has-submenu > a', function( event ) {
1762                  if ( ! $adminmenu.data('wp-responsive') ) {
1763                      return;
1764                  }
1765  
1766                  $( this ).parent( 'li' ).toggleClass( 'selected' );
1767                  $( this ).trigger( 'focus' );
1768                  event.preventDefault();
1769              });
1770  
1771              self.trigger();
1772              $document.on( 'wp-window-resized.wp-responsive', this.trigger.bind( this ) );
1773  
1774              // This needs to run later as UI Sortable may be initialized when the document is ready.
1775              $window.on( 'load.wp-responsive', this.maybeDisableSortables );
1776              $document.on( 'postbox-toggled', this.maybeDisableSortables );
1777  
1778              // When the screen columns are changed, potentially disable sortables.
1779              $( '#screen-options-wrap input' ).on( 'click', this.maybeDisableSortables );
1780          },
1781  
1782          /**
1783           * Disable sortables if there is only one metabox, or the screen is in one column mode. Otherwise, enable sortables.
1784           *
1785           * @since 5.3.0
1786           *
1787           * @return {void}
1788           */
1789          maybeDisableSortables: function() {
1790              var width = navigator.userAgent.indexOf('AppleWebKit/') > -1 ? $window.width() : window.innerWidth;
1791  
1792              if (
1793                  ( width <= 782 ) ||
1794                  ( 1 >= $sortables.find( '.ui-sortable-handle:visible' ).length && jQuery( '.columns-prefs-1 input' ).prop( 'checked' ) )
1795              ) {
1796                  this.disableSortables();
1797              } else {
1798                  this.enableSortables();
1799              }
1800          },
1801  
1802          /**
1803           * Changes properties of body and admin menu.
1804           *
1805           * Pins and unpins the menu and adds the auto-fold class to the body.
1806           * Makes the admin menu responsive and disables the metabox sortables.
1807           *
1808           * @since 3.8.0
1809           *
1810           * @return {void}
1811           */
1812          activate: function() {
1813              setPinMenu();
1814  
1815              if ( ! $body.hasClass( 'auto-fold' ) ) {
1816                  $body.addClass( 'auto-fold' );
1817              }
1818  
1819              $adminmenu.data( 'wp-responsive', 1 );
1820              this.disableSortables();
1821          },
1822  
1823          /**
1824           * Changes properties of admin menu and enables metabox sortables.
1825           *
1826           * Pin and unpin the menu.
1827           * Removes the responsiveness of the admin menu and enables the metabox sortables.
1828           *
1829           * @since 3.8.0
1830           *
1831           * @return {void}
1832           */
1833          deactivate: function() {
1834              setPinMenu();
1835              $adminmenu.removeData('wp-responsive');
1836  
1837              this.maybeDisableSortables();
1838          },
1839  
1840          /**
1841           * Sets the responsiveness and enables the overlay based on the viewport width.
1842           *
1843           * @since 3.8.0
1844           *
1845           * @return {void}
1846           */
1847          trigger: function() {
1848              var viewportWidth = getViewportWidth();
1849  
1850              // Exclude IE < 9, it doesn't support @media CSS rules.
1851              if ( ! viewportWidth ) {
1852                  return;
1853              }
1854  
1855              if ( viewportWidth <= 782 ) {
1856                  if ( ! wpResponsiveActive ) {
1857                      $document.trigger( 'wp-responsive-activate' );
1858                      wpResponsiveActive = true;
1859                  }
1860              } else {
1861                  if ( wpResponsiveActive ) {
1862                      $document.trigger( 'wp-responsive-deactivate' );
1863                      wpResponsiveActive = false;
1864                  }
1865              }
1866  
1867              if ( viewportWidth <= 480 ) {
1868                  this.enableOverlay();
1869              } else {
1870                  this.disableOverlay();
1871              }
1872  
1873              this.maybeDisableSortables();
1874          },
1875  
1876          /**
1877           * Inserts a responsive overlay and toggles the window.
1878           *
1879           * @since 3.8.0
1880           *
1881           * @return {void}
1882           */
1883          enableOverlay: function() {
1884              if ( $overlay.length === 0 ) {
1885                  $overlay = $( '<div id="wp-responsive-overlay"></div>' )
1886                      .insertAfter( '#wpcontent' )
1887                      .hide()
1888                      .on( 'click.wp-responsive', function() {
1889                          $toolbar.find( '.menupop.hover' ).removeClass( 'hover' );
1890                          $( this ).hide();
1891                      });
1892              }
1893  
1894              $toolbarPopups.on( 'click.wp-responsive', function() {
1895                  $overlay.show();
1896              });
1897          },
1898  
1899          /**
1900           * Disables the responsive overlay and removes the overlay.
1901           *
1902           * @since 3.8.0
1903           *
1904           * @return {void}
1905           */
1906          disableOverlay: function() {
1907              $toolbarPopups.off( 'click.wp-responsive' );
1908              $overlay.hide();
1909          },
1910  
1911          /**
1912           * Disables sortables.
1913           *
1914           * @since 3.8.0
1915           *
1916           * @return {void}
1917           */
1918          disableSortables: function() {
1919              if ( $sortables.length ) {
1920                  try {
1921                      $sortables.sortable( 'disable' );
1922                      $sortables.find( '.ui-sortable-handle' ).addClass( 'is-non-sortable' );
1923                  } catch ( e ) {}
1924              }
1925          },
1926  
1927          /**
1928           * Enables sortables.
1929           *
1930           * @since 3.8.0
1931           *
1932           * @return {void}
1933           */
1934          enableSortables: function() {
1935              if ( $sortables.length ) {
1936                  try {
1937                      $sortables.sortable( 'enable' );
1938                      $sortables.find( '.ui-sortable-handle' ).removeClass( 'is-non-sortable' );
1939                  } catch ( e ) {}
1940              }
1941          }
1942      };
1943  
1944      /**
1945       * Add an ARIA role `button` to elements that behave like UI controls when JavaScript is on.
1946       *
1947       * @since 4.5.0
1948       *
1949       * @return {void}
1950       */
1951  	function aria_button_if_js() {
1952          $( '.aria-button-if-js' ).attr( 'role', 'button' );
1953      }
1954  
1955      $( document ).on( 'ajaxComplete', function() {
1956          aria_button_if_js();
1957      });
1958  
1959      /**
1960       * Get the viewport width.
1961       *
1962       * @since 4.7.0
1963       *
1964       * @return {number|boolean} The current viewport width or false if the
1965       *                          browser doesn't support innerWidth (IE < 9).
1966       */
1967  	function getViewportWidth() {
1968          var viewportWidth = false;
1969  
1970          if ( window.innerWidth ) {
1971              // On phones, window.innerWidth is affected by zooming.
1972              viewportWidth = Math.max( window.innerWidth, document.documentElement.clientWidth );
1973          }
1974  
1975          return viewportWidth;
1976      }
1977  
1978      /**
1979       * Sets the admin menu collapsed/expanded state.
1980       *
1981       * Sets the global variable `menuState` and triggers a custom event passing
1982       * the current menu state.
1983       *
1984       * @since 4.7.0
1985       *
1986       * @return {void}
1987       */
1988  	function setMenuState() {
1989          var viewportWidth = getViewportWidth() || 961;
1990  
1991          if ( viewportWidth <= 782  ) {
1992              menuState = 'responsive';
1993          } else if ( $body.hasClass( 'folded' ) || ( $body.hasClass( 'auto-fold' ) && viewportWidth <= 960 && viewportWidth > 782 ) ) {
1994              menuState = 'folded';
1995          } else {
1996              menuState = 'open';
1997          }
1998  
1999          $document.trigger( 'wp-menu-state-set', { state: menuState } );
2000      }
2001  
2002      // Set the menu state when the window gets resized.
2003      $document.on( 'wp-window-resized.set-menu-state', setMenuState );
2004  
2005      /**
2006       * Sets ARIA attributes on the collapse/expand menu button.
2007       *
2008       * When the admin menu is open or folded, updates the `aria-expanded` and
2009       * `aria-label` attributes of the button to give feedback to assistive
2010       * technologies. In the responsive view, the button is always hidden.
2011       *
2012       * @since 4.7.0
2013       *
2014       * @return {void}
2015       */
2016      $document.on( 'wp-menu-state-set wp-collapse-menu', function( event, eventData ) {
2017          var $collapseButton = $( '#collapse-button' ),
2018              ariaExpanded, ariaLabelText;
2019  
2020          if ( 'folded' === eventData.state ) {
2021              ariaExpanded = 'false';
2022              ariaLabelText = __( 'Expand Main menu' );
2023          } else {
2024              ariaExpanded = 'true';
2025              ariaLabelText = __( 'Collapse Main menu' );
2026          }
2027  
2028          $collapseButton.attr({
2029              'aria-expanded': ariaExpanded,
2030              'aria-label': ariaLabelText
2031          });
2032      });
2033  
2034      window.wpResponsive.init();
2035      setPinMenu();
2036      setMenuState();
2037      currentMenuItemHasPopup();
2038      makeNoticesDismissible();
2039      aria_button_if_js();
2040  
2041      $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 );
2042  
2043      // Set initial focus on a specific element.
2044      $( '.wp-initial-focus' ).trigger( 'focus' );
2045  
2046      // Toggle update details on update-core.php.
2047      $body.on( 'click', '.js-update-details-toggle', function() {
2048          var $updateNotice = $( this ).closest( '.js-update-details' ),
2049              $progressDiv = $( '#' + $updateNotice.data( 'update-details' ) );
2050  
2051          /*
2052           * When clicking on "Show details" move the progress div below the update
2053           * notice. Make sure it gets moved just the first time.
2054           */
2055          if ( ! $progressDiv.hasClass( 'update-details-moved' ) ) {
2056              $progressDiv.insertAfter( $updateNotice ).addClass( 'update-details-moved' );
2057          }
2058  
2059          // Toggle the progress div visibility.
2060          $progressDiv.toggle();
2061          // Toggle the Show Details button expanded state.
2062          $( this ).attr( 'aria-expanded', $progressDiv.is( ':visible' ) );
2063      });
2064  });
2065  
2066  /**
2067   * Hides the update button for expired plugin or theme uploads.
2068   *
2069   * On the "Update plugin/theme from uploaded zip" screen, once the upload has expired,
2070   * hides the "Replace current with uploaded" button and displays a warning.
2071   *
2072   * @since 5.5.0
2073   */
2074  $( function( $ ) {
2075      var $overwrite, $warning;
2076  
2077      if ( ! $body.hasClass( 'update-php' ) ) {
2078          return;
2079      }
2080  
2081      $overwrite = $( 'a.update-from-upload-overwrite' );
2082      $warning   = $( '.update-from-upload-expired' );
2083  
2084      if ( ! $overwrite.length || ! $warning.length ) {
2085          return;
2086      }
2087  
2088      window.setTimeout(
2089          function() {
2090              $overwrite.hide();
2091              $warning.removeClass( 'hidden' );
2092  
2093              if ( window.wp && window.wp.a11y ) {
2094                  window.wp.a11y.speak( $warning.text() );
2095              }
2096          },
2097          7140000 // 119 minutes. The uploaded file is deleted after 2 hours.
2098      );
2099  } );
2100  
2101  // Fire a custom jQuery event at the end of window resize.
2102  ( function() {
2103      var timeout;
2104  
2105      /**
2106       * Triggers the WP window-resize event.
2107       *
2108       * @since 3.8.0
2109       *
2110       * @return {void}
2111       */
2112  	function triggerEvent() {
2113          $document.trigger( 'wp-window-resized' );
2114      }
2115  
2116      /**
2117       * Fires the trigger event again after 200 ms.
2118       *
2119       * @since 3.8.0
2120       *
2121       * @return {void}
2122       */
2123  	function fireOnce() {
2124          window.clearTimeout( timeout );
2125          timeout = window.setTimeout( triggerEvent, 200 );
2126      }
2127  
2128      $window.on( 'resize.wp-fire-once', fireOnce );
2129  }());
2130  
2131  // Make Windows 8 devices play along nicely.
2132  (function(){
2133      if ( '-ms-user-select' in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/) ) {
2134          var msViewportStyle = document.createElement( 'style' );
2135          msViewportStyle.appendChild(
2136              document.createTextNode( '@-ms-viewport{width:auto!important}' )
2137          );
2138          document.getElementsByTagName( 'head' )[0].appendChild( msViewportStyle );
2139      }
2140  })();
2141  
2142  }( jQuery, window ));
2143  
2144  /**
2145   * Freeze animated plugin icons when reduced motion is enabled.
2146   *
2147   * When the user has enabled the 'prefers-reduced-motion' setting, this module
2148   * stops animations for all GIFs on the page with the class 'plugin-icon' or
2149   * plugin icon images in the update plugins table.
2150   *
2151   * @since 6.4.0
2152   */
2153  (function() {
2154      // Private variables and methods.
2155      var priv = {},
2156          pub = {},
2157          mediaQuery;
2158  
2159      // Initialize pauseAll to false; it will be set to true if reduced motion is preferred.
2160      priv.pauseAll = false;
2161      if ( window.matchMedia ) {
2162          mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' );
2163          if ( ! mediaQuery || mediaQuery.matches ) {
2164              priv.pauseAll = true;
2165          }
2166      }
2167  
2168      // Method to replace animated GIFs with a static frame.
2169      priv.freezeAnimatedPluginIcons = function( img ) {
2170          var coverImage = function() {
2171              var width = img.width;
2172              var height = img.height;
2173              var canvas = document.createElement( 'canvas' );
2174  
2175              // Set canvas dimensions.
2176              canvas.width = width;
2177              canvas.height = height;
2178  
2179              // Copy classes from the image to the canvas.
2180              canvas.className = img.className;
2181  
2182              // Check if the image is inside a specific table.
2183              var isInsideUpdateTable = img.closest( '#update-plugins-table' );
2184  
2185              if ( isInsideUpdateTable ) {
2186                  // Transfer computed styles from image to canvas.
2187                  var computedStyles = window.getComputedStyle( img ),
2188                      i, max;
2189                  for ( i = 0, max = computedStyles.length; i < max; i++ ) {
2190                      var propName = computedStyles[ i ];
2191                      var propValue = computedStyles.getPropertyValue( propName );
2192                      canvas.style[ propName ] = propValue;
2193                  }
2194              }
2195  
2196              // Draw the image onto the canvas.
2197              canvas.getContext( '2d' ).drawImage( img, 0, 0, width, height );
2198  
2199              // Set accessibility attributes on canvas.
2200              canvas.setAttribute( 'aria-hidden', 'true' );
2201              canvas.setAttribute( 'role', 'presentation' );
2202  
2203              // Insert canvas before the image and set the image to be near-invisible.
2204              var parent = img.parentNode;
2205              parent.insertBefore( canvas, img );
2206              img.style.opacity = 0.01;
2207              img.style.width = '0px';
2208              img.style.height = '0px';
2209          };
2210  
2211          // If the image is already loaded, apply the coverImage function.
2212          if ( img.complete ) {
2213              coverImage();
2214          } else {
2215              // Otherwise, wait for the image to load.
2216              img.addEventListener( 'load', coverImage, true );
2217          }
2218      };
2219  
2220      // Public method to freeze all relevant GIFs on the page.
2221      pub.freezeAll = function() {
2222          var images = document.querySelectorAll( '.plugin-icon, #update-plugins-table img' );
2223          for ( var x = 0; x < images.length; x++ ) {
2224              if ( /\.gif(?:\?|$)/i.test( images[ x ].src ) ) {
2225                  priv.freezeAnimatedPluginIcons( images[ x ] );
2226              }
2227          }
2228      };
2229  
2230      // Only run the freezeAll method if the user prefers reduced motion.
2231      if ( true === priv.pauseAll ) {
2232          pub.freezeAll();
2233      }
2234  
2235      // Listen for jQuery AJAX events.
2236      ( function( $ ) {
2237          if ( window.pagenow === 'plugin-install' ) {
2238              // Only listen for ajaxComplete if this is the plugin-install.php page.
2239              $( document ).ajaxComplete( function( event, xhr, settings ) {
2240  
2241                  // Check if this is the 'search-install-plugins' request.
2242                  if ( settings.data && typeof settings.data === 'string' && settings.data.includes( 'action=search-install-plugins' ) ) {
2243                      // Recheck if the user prefers reduced motion.
2244                      if ( window.matchMedia ) {
2245                          var mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' );
2246                          if ( mediaQuery.matches ) {
2247                              pub.freezeAll();
2248                          }
2249                      } else {
2250                          // Fallback for browsers that don't support matchMedia.
2251                          if ( true === priv.pauseAll ) {
2252                              pub.freezeAll();
2253                          }
2254                      }
2255                  }
2256              } );
2257          }
2258      } )( jQuery );
2259  
2260      // Expose public methods.
2261      return pub;
2262  })();


Generated : Thu Mar 28 08:20:01 2024 Cross-referenced by PHPXref