[ 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       * 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              currentPageSelector = form.querySelector( '#current-page-selector' );
1317  
1318          if ( currentPageSelector && currentPageSelector.defaultValue !== currentPageSelector.value ) {
1319              return; // Pagination form submission.
1320          }
1321  
1322          // Observe submissions from posts lists for 'bulk_action' or users lists for 'new_role'.
1323          var bulkFieldRelations = {
1324              'bulk_action' : 'action',
1325              'changeit' : 'new_role'
1326          };
1327          if ( ! Object.keys( bulkFieldRelations ).includes( submitterName ) ) {
1328              return;
1329          }
1330  
1331          var values = new FormData(form);
1332          var value = values.get( bulkFieldRelations[ submitterName ] ) || '-1';
1333  
1334          // Check that the action is not the default one.
1335          if ( value !== '-1' ) {
1336              // Check that at least one item is selected.
1337              var itemsSelected = form.querySelectorAll( '.wp-list-table tbody .check-column input[type="checkbox"]:checked' );
1338  
1339              if ( itemsSelected.length > 0 ) {
1340                  return;
1341              }
1342          }
1343          event.preventDefault();
1344          event.stopPropagation();
1345          $( 'html, body' ).animate( { scrollTop: 0 } );
1346  
1347          var errorMessage = __( 'Please select at least one item to perform this action on.' );
1348          addAdminNotice( {
1349              id: 'no-items-selected',
1350              type: 'error',
1351              message: errorMessage,
1352              dismissible: true,
1353          } );
1354  
1355          wp.a11y.speak( errorMessage );
1356      });
1357  
1358      /**
1359       * Shows row actions on focus of its parent container element or any other elements contained within.
1360       *
1361       * @return {void}
1362       */
1363      $( '#wpbody-content' ).on({
1364          focusin: function() {
1365              clearTimeout( transitionTimeout );
1366              focusedRowActions = $( this ).find( '.row-actions' );
1367              // transitionTimeout is necessary for Firefox, but Chrome won't remove the CSS class without a little help.
1368              $( '.row-actions' ).not( this ).removeClass( 'visible' );
1369              focusedRowActions.addClass( 'visible' );
1370          },
1371          focusout: function() {
1372              // Tabbing between post title and .row-actions links needs a brief pause, otherwise
1373              // the .row-actions div gets hidden in transit in some browsers (ahem, Firefox).
1374              transitionTimeout = setTimeout( function() {
1375                  focusedRowActions.removeClass( 'visible' );
1376              }, 30 );
1377          }
1378      }, '.table-view-list .has-row-actions' );
1379  
1380      // Toggle list table rows on small screens.
1381      $( 'tbody' ).on( 'click', '.toggle-row', function() {
1382          $( this ).closest( 'tr' ).toggleClass( 'is-expanded' );
1383      });
1384  
1385      $('#default-password-nag-no').on( 'click', function() {
1386          setUserSetting('default_password_nag', 'hide');
1387          $('div.default-password-nag').hide();
1388          return false;
1389      });
1390  
1391      /**
1392       * Handles tab keypresses in theme and plugin file editor textareas.
1393       *
1394       * @param {Event} e The event object.
1395       *
1396       * @return {void}
1397       */
1398      $('#newcontent').on('keydown.wpevent_InsertTab', function(e) {
1399          var el = e.target, selStart, selEnd, val, scroll, sel;
1400  
1401          // After pressing escape key (keyCode: 27), the tab key should tab out of the textarea.
1402          if ( e.keyCode == 27 ) {
1403              // When pressing Escape: Opera 12 and 27 blur form fields, IE 8 clears them.
1404              e.preventDefault();
1405              $(el).data('tab-out', true);
1406              return;
1407          }
1408  
1409          // Only listen for plain tab key (keyCode: 9) without any modifiers.
1410          if ( e.keyCode != 9 || e.ctrlKey || e.altKey || e.shiftKey )
1411              return;
1412  
1413          // After tabbing out, reset it so next time the tab key can be used again.
1414          if ( $(el).data('tab-out') ) {
1415              $(el).data('tab-out', false);
1416              return;
1417          }
1418  
1419          selStart = el.selectionStart;
1420          selEnd = el.selectionEnd;
1421          val = el.value;
1422  
1423          // If any text is selected, replace the selection with a tab character.
1424          if ( document.selection ) {
1425              el.focus();
1426              sel = document.selection.createRange();
1427              sel.text = '\t';
1428          } else if ( selStart >= 0 ) {
1429              scroll = this.scrollTop;
1430              el.value = val.substring(0, selStart).concat('\t', val.substring(selEnd) );
1431              el.selectionStart = el.selectionEnd = selStart + 1;
1432              this.scrollTop = scroll;
1433          }
1434  
1435          // Cancel the regular tab functionality, to prevent losing focus of the textarea.
1436          if ( e.stopPropagation )
1437              e.stopPropagation();
1438          if ( e.preventDefault )
1439              e.preventDefault();
1440      });
1441  
1442      // Reset page number variable for new filters/searches but not for bulk actions. See #17685.
1443      if ( pageInput.length ) {
1444  
1445          /**
1446           * Handles pagination variable when filtering the list table.
1447           *
1448           * Set the pagination argument to the first page when the post-filter form is submitted.
1449           * This happens when pressing the 'filter' button on the list table page.
1450           *
1451           * The pagination argument should not be touched when the bulk action dropdowns are set to do anything.
1452           *
1453           * The form closest to the pageInput is the post-filter form.
1454           *
1455           * @return {void}
1456           */
1457          pageInput.closest('form').on( 'submit', function() {
1458              /*
1459               * action = bulk action dropdown at the top of the table
1460               */
1461              if ( $('select[name="action"]').val() == -1 && pageInput.val() == currentPage )
1462                  pageInput.val('1');
1463          });
1464      }
1465  
1466      /**
1467       * Resets the bulk actions when the search button is clicked.
1468       *
1469       * @return {void}
1470       */
1471      $('.search-box input[type="search"], .search-box input[type="submit"]').on( 'mousedown', function () {
1472          $('select[name^="action"]').val('-1');
1473      });
1474  
1475      /**
1476       * Scrolls into view when focus.scroll-into-view is triggered.
1477       *
1478       * @param {Event} e The event object.
1479       *
1480       * @return {void}
1481        */
1482      $('#contextual-help-link, #show-settings-link').on( 'focus.scroll-into-view', function(e){
1483          if ( e.target.scrollIntoViewIfNeeded )
1484              e.target.scrollIntoViewIfNeeded(false);
1485      });
1486  
1487      /**
1488       * Disables the submit upload buttons when no data is entered.
1489       *
1490       * @return {void}
1491       */
1492      (function(){
1493          var button, input, form = $('form.wp-upload-form');
1494  
1495          // Exit when no upload form is found.
1496          if ( ! form.length )
1497              return;
1498  
1499          button = form.find('input[type="submit"]');
1500          input = form.find('input[type="file"]');
1501  
1502          /**
1503           * Determines if any data is entered in any file upload input.
1504           *
1505           * @since 3.5.0
1506           *
1507           * @return {void}
1508           */
1509  		function toggleUploadButton() {
1510              // When no inputs have a value, disable the upload buttons.
1511              button.prop('disabled', '' === input.map( function() {
1512                  return $(this).val();
1513              }).get().join(''));
1514          }
1515  
1516          // Update the status initially.
1517          toggleUploadButton();
1518          // Update the status when any file input changes.
1519          input.on('change', toggleUploadButton);
1520      })();
1521  
1522      /**
1523       * Pins the menu while distraction-free writing is enabled.
1524       *
1525       * @param {Event} event Event data.
1526       *
1527       * @since 4.1.0
1528       *
1529       * @return {void}
1530       */
1531  	function pinMenu( event ) {
1532          var windowPos = $window.scrollTop(),
1533              resizing = ! event || event.type !== 'scroll';
1534  
1535          if ( isIOS || $adminmenu.data( 'wp-responsive' ) ) {
1536              return;
1537          }
1538  
1539          /*
1540           * When the menu is higher than the window and smaller than the entire page.
1541           * It should be adjusted to be able to see the entire menu.
1542           *
1543           * Otherwise it can be accessed normally.
1544           */
1545          if ( height.menu + height.adminbar < height.window ||
1546              height.menu + height.adminbar + 20 > height.wpwrap ) {
1547              unpinMenu();
1548              return;
1549          }
1550  
1551          menuIsPinned = true;
1552  
1553          // If the menu is higher than the window, compensate on scroll.
1554          if ( height.menu + height.adminbar > height.window ) {
1555              // Check for overscrolling, this happens when swiping up at the top of the document in modern browsers.
1556              if ( windowPos < 0 ) {
1557                  // Stick the menu to the top.
1558                  if ( ! pinnedMenuTop ) {
1559                      pinnedMenuTop = true;
1560                      pinnedMenuBottom = false;
1561  
1562                      $adminMenuWrap.css({
1563                          position: 'fixed',
1564                          top: '',
1565                          bottom: ''
1566                      });
1567                  }
1568  
1569                  return;
1570              } else if ( windowPos + height.window > $document.height() - 1 ) {
1571                  // When overscrolling at the bottom, stick the menu to the bottom.
1572                  if ( ! pinnedMenuBottom ) {
1573                      pinnedMenuBottom = true;
1574                      pinnedMenuTop = false;
1575  
1576                      $adminMenuWrap.css({
1577                          position: 'fixed',
1578                          top: '',
1579                          bottom: 0
1580                      });
1581                  }
1582  
1583                  return;
1584              }
1585  
1586              if ( windowPos > lastScrollPosition ) {
1587                  // When a down scroll has been detected.
1588  
1589                  // If it was pinned to the top, unpin and calculate relative scroll.
1590                  if ( pinnedMenuTop ) {
1591                      pinnedMenuTop = false;
1592                      // Calculate new offset position.
1593                      menuTop = $adminMenuWrap.offset().top - height.adminbar - ( windowPos - lastScrollPosition );
1594  
1595                      if ( menuTop + height.menu + height.adminbar < windowPos + height.window ) {
1596                          menuTop = windowPos + height.window - height.menu - height.adminbar;
1597                      }
1598  
1599                      $adminMenuWrap.css({
1600                          position: 'absolute',
1601                          top: menuTop,
1602                          bottom: ''
1603                      });
1604                  } else if ( ! pinnedMenuBottom && $adminMenuWrap.offset().top + height.menu < windowPos + height.window ) {
1605                      // Pin it to the bottom.
1606                      pinnedMenuBottom = true;
1607  
1608                      $adminMenuWrap.css({
1609                          position: 'fixed',
1610                          top: '',
1611                          bottom: 0
1612                      });
1613                  }
1614              } else if ( windowPos < lastScrollPosition ) {
1615                  // When a scroll up is detected.
1616  
1617                  // If it was pinned to the bottom, unpin and calculate relative scroll.
1618                  if ( pinnedMenuBottom ) {
1619                      pinnedMenuBottom = false;
1620  
1621                      // Calculate new offset position.
1622                      menuTop = $adminMenuWrap.offset().top - height.adminbar + ( lastScrollPosition - windowPos );
1623  
1624                      if ( menuTop + height.menu > windowPos + height.window ) {
1625                          menuTop = windowPos;
1626                      }
1627  
1628                      $adminMenuWrap.css({
1629                          position: 'absolute',
1630                          top: menuTop,
1631                          bottom: ''
1632                      });
1633                  } else if ( ! pinnedMenuTop && $adminMenuWrap.offset().top >= windowPos + height.adminbar ) {
1634  
1635                      // Pin it to the top.
1636                      pinnedMenuTop = true;
1637  
1638                      $adminMenuWrap.css({
1639                          position: 'fixed',
1640                          top: '',
1641                          bottom: ''
1642                      });
1643                  }
1644              } else if ( resizing ) {
1645                  // Window is being resized.
1646  
1647                  pinnedMenuTop = pinnedMenuBottom = false;
1648  
1649                  // Calculate the new offset.
1650                  menuTop = windowPos + height.window - height.menu - height.adminbar - 1;
1651  
1652                  if ( menuTop > 0 ) {
1653                      $adminMenuWrap.css({
1654                          position: 'absolute',
1655                          top: menuTop,
1656                          bottom: ''
1657                      });
1658                  } else {
1659                      unpinMenu();
1660                  }
1661              }
1662          }
1663  
1664          lastScrollPosition = windowPos;
1665      }
1666  
1667      /**
1668       * Determines the height of certain elements.
1669       *
1670       * @since 4.1.0
1671       *
1672       * @return {void}
1673       */
1674  	function resetHeights() {
1675          height = {
1676              window: $window.height(),
1677              wpwrap: $wpwrap.height(),
1678              adminbar: $adminbar.height(),
1679              menu: $adminMenuWrap.height()
1680          };
1681      }
1682  
1683      /**
1684       * Unpins the menu.
1685       *
1686       * @since 4.1.0
1687       *
1688       * @return {void}
1689       */
1690  	function unpinMenu() {
1691          if ( isIOS || ! menuIsPinned ) {
1692              return;
1693          }
1694  
1695          pinnedMenuTop = pinnedMenuBottom = menuIsPinned = false;
1696          $adminMenuWrap.css({
1697              position: '',
1698              top: '',
1699              bottom: ''
1700          });
1701      }
1702  
1703      /**
1704       * Pins and unpins the menu when applicable.
1705       *
1706       * @since 4.1.0
1707       *
1708       * @return {void}
1709       */
1710  	function setPinMenu() {
1711          resetHeights();
1712  
1713          if ( $adminmenu.data('wp-responsive') ) {
1714              $body.removeClass( 'sticky-menu' );
1715              unpinMenu();
1716          } else if ( height.menu + height.adminbar > height.window ) {
1717              pinMenu();
1718              $body.removeClass( 'sticky-menu' );
1719          } else {
1720              $body.addClass( 'sticky-menu' );
1721              unpinMenu();
1722          }
1723      }
1724  
1725      if ( ! isIOS ) {
1726          $window.on( 'scroll.pin-menu', pinMenu );
1727          $document.on( 'tinymce-editor-init.pin-menu', function( event, editor ) {
1728              editor.on( 'wp-autoresize', resetHeights );
1729          });
1730      }
1731  
1732      /**
1733       * Changes the sortables and responsiveness of metaboxes.
1734       *
1735       * @since 3.8.0
1736       *
1737       * @return {void}
1738       */
1739      window.wpResponsive = {
1740  
1741          /**
1742           * Initializes the wpResponsive object.
1743           *
1744           * @since 3.8.0
1745           *
1746           * @return {void}
1747           */
1748          init: function() {
1749              var self = this;
1750  
1751              this.maybeDisableSortables = this.maybeDisableSortables.bind( this );
1752  
1753              // Modify functionality based on custom activate/deactivate event.
1754              $document.on( 'wp-responsive-activate.wp-responsive', function() {
1755                  self.activate();
1756                  self.toggleAriaHasPopup( 'add' );
1757              }).on( 'wp-responsive-deactivate.wp-responsive', function() {
1758                  self.deactivate();
1759                  self.toggleAriaHasPopup( 'remove' );
1760              });
1761  
1762              $( '#wp-admin-bar-menu-toggle a' ).attr( 'aria-expanded', 'false' );
1763  
1764              // Toggle sidebar when toggle is clicked.
1765              $( '#wp-admin-bar-menu-toggle' ).on( 'click.wp-responsive', function( event ) {
1766                  event.preventDefault();
1767  
1768                  // Close any open toolbar submenus.
1769                  $adminbar.find( '.hover' ).removeClass( 'hover' );
1770  
1771                  $wpwrap.toggleClass( 'wp-responsive-open' );
1772                  if ( $wpwrap.hasClass( 'wp-responsive-open' ) ) {
1773                      $(this).find('a').attr( 'aria-expanded', 'true' );
1774                      $( '#adminmenu a:first' ).trigger( 'focus' );
1775                  } else {
1776                      $(this).find('a').attr( 'aria-expanded', 'false' );
1777                  }
1778              } );
1779  
1780              // Close sidebar when target moves outside of toggle and sidebar.
1781              $( document ).on( 'click', function( event ) {
1782                  if ( ! $wpwrap.hasClass( 'wp-responsive-open' ) || ! document.hasFocus() ) {
1783                      return;
1784                  }
1785  
1786                  var focusIsInToggle  = $.contains( $( '#wp-admin-bar-menu-toggle' )[0], event.target );
1787                  var focusIsInSidebar = $.contains( $( '#adminmenuwrap' )[0], event.target );
1788  
1789                  if ( ! focusIsInToggle && ! focusIsInSidebar ) {
1790                      $( '#wp-admin-bar-menu-toggle' ).trigger( 'click.wp-responsive' );
1791                  }
1792              } );
1793  
1794              // Close sidebar when a keypress completes outside of toggle and sidebar.
1795              $( document ).on( 'keyup', function( event ) {
1796                  var toggleButton   = $( '#wp-admin-bar-menu-toggle' )[0];
1797                  if ( ! $wpwrap.hasClass( 'wp-responsive-open' ) ) {
1798                      return;
1799                  }
1800                  if ( 27 === event.keyCode ) {
1801                      $( toggleButton ).trigger( 'click.wp-responsive' );
1802                      $( toggleButton ).find( 'a' ).trigger( 'focus' );
1803                  } else {
1804                      if ( 9 === event.keyCode ) {
1805                          var sidebar        = $( '#adminmenuwrap' )[0];
1806                          var focusedElement = event.relatedTarget || document.activeElement;
1807                          // A brief delay is required to allow focus to switch to another element.
1808                          setTimeout( function() {
1809                              var focusIsInToggle  = $.contains( toggleButton, focusedElement );
1810                              var focusIsInSidebar = $.contains( sidebar, focusedElement );
1811  
1812                              if ( ! focusIsInToggle && ! focusIsInSidebar ) {
1813                                  $( toggleButton ).trigger( 'click.wp-responsive' );
1814                              }
1815                          }, 10 );
1816                      }
1817                  }
1818              });
1819  
1820              // Add menu events.
1821              $adminmenu.on( 'click.wp-responsive', 'li.wp-has-submenu > a', function( event ) {
1822                  if ( ! $adminmenu.data('wp-responsive') ) {
1823                      return;
1824                  }
1825                  let state = ( 'false' === $( this ).attr( 'aria-expanded' ) ) ? 'true' : 'false';
1826                  $( this ).parent( 'li' ).toggleClass( 'selected' );
1827                  $( this ).attr( 'aria-expanded', state );
1828                  $( this ).trigger( 'focus' );
1829                  event.preventDefault();
1830              });
1831  
1832              self.trigger();
1833              $document.on( 'wp-window-resized.wp-responsive', this.trigger.bind( this ) );
1834  
1835              // This needs to run later as UI Sortable may be initialized when the document is ready.
1836              $window.on( 'load.wp-responsive', this.maybeDisableSortables );
1837              $document.on( 'postbox-toggled', this.maybeDisableSortables );
1838  
1839              // When the screen columns are changed, potentially disable sortables.
1840              $( '#screen-options-wrap input' ).on( 'click', this.maybeDisableSortables );
1841          },
1842  
1843          /**
1844           * Disable sortables if there is only one metabox, or the screen is in one column mode. Otherwise, enable sortables.
1845           *
1846           * @since 5.3.0
1847           *
1848           * @return {void}
1849           */
1850          maybeDisableSortables: function() {
1851              var width = navigator.userAgent.indexOf('AppleWebKit/') > -1 ? $window.width() : window.innerWidth;
1852  
1853              if (
1854                  ( width <= 782 ) ||
1855                  ( 1 >= $sortables.find( '.ui-sortable-handle:visible' ).length && jQuery( '.columns-prefs-1 input' ).prop( 'checked' ) )
1856              ) {
1857                  this.disableSortables();
1858              } else {
1859                  this.enableSortables();
1860              }
1861          },
1862  
1863          /**
1864           * Changes properties of body and admin menu.
1865           *
1866           * Pins and unpins the menu and adds the auto-fold class to the body.
1867           * Makes the admin menu responsive and disables the metabox sortables.
1868           *
1869           * @since 3.8.0
1870           *
1871           * @return {void}
1872           */
1873          activate: function() {
1874              setPinMenu();
1875  
1876              if ( ! $body.hasClass( 'auto-fold' ) ) {
1877                  $body.addClass( 'auto-fold' );
1878              }
1879  
1880              $adminmenu.data( 'wp-responsive', 1 );
1881              this.disableSortables();
1882          },
1883  
1884          /**
1885           * Changes properties of admin menu and enables metabox sortables.
1886           *
1887           * Pin and unpin the menu.
1888           * Removes the responsiveness of the admin menu and enables the metabox sortables.
1889           *
1890           * @since 3.8.0
1891           *
1892           * @return {void}
1893           */
1894          deactivate: function() {
1895              setPinMenu();
1896              $adminmenu.removeData('wp-responsive');
1897  
1898              this.maybeDisableSortables();
1899          },
1900  
1901          /**
1902           * Toggles the aria-haspopup attribute for the responsive admin menu.
1903           *
1904           * The aria-haspopup attribute is only necessary for the responsive menu.
1905           * See ticket https://core.trac.wordpress.org/ticket/43095
1906           *
1907           * @since 6.6.0
1908           *
1909           * @param {string} action Whether to add or remove the aria-haspopup attribute.
1910           *
1911           * @return {void}
1912           */
1913          toggleAriaHasPopup: function( action ) {
1914              var elements = $adminmenu.find( '[data-ariahaspopup]' );
1915  
1916              if ( action === 'add' ) {
1917                  elements.each( function() {
1918                      $( this ).attr( 'aria-haspopup', 'menu' ).attr( 'aria-expanded', 'false' );
1919                  } );
1920  
1921                  return;
1922              }
1923  
1924              elements.each( function() {
1925                  $( this ).removeAttr( 'aria-haspopup' ).removeAttr( 'aria-expanded' );
1926              } );
1927          },
1928  
1929          /**
1930           * Sets the responsiveness and enables the overlay based on the viewport width.
1931           *
1932           * @since 3.8.0
1933           *
1934           * @return {void}
1935           */
1936          trigger: function() {
1937              var viewportWidth = getViewportWidth();
1938  
1939              // Exclude IE < 9, it doesn't support @media CSS rules.
1940              if ( ! viewportWidth ) {
1941                  return;
1942              }
1943  
1944              if ( viewportWidth <= 782 ) {
1945                  if ( ! wpResponsiveActive ) {
1946                      $document.trigger( 'wp-responsive-activate' );
1947                      wpResponsiveActive = true;
1948                  }
1949              } else {
1950                  if ( wpResponsiveActive ) {
1951                      $document.trigger( 'wp-responsive-deactivate' );
1952                      wpResponsiveActive = false;
1953                  }
1954              }
1955  
1956              if ( viewportWidth <= 480 ) {
1957                  this.enableOverlay();
1958              } else {
1959                  this.disableOverlay();
1960              }
1961  
1962              this.maybeDisableSortables();
1963          },
1964  
1965          /**
1966           * Inserts a responsive overlay and toggles the window.
1967           *
1968           * @since 3.8.0
1969           *
1970           * @return {void}
1971           */
1972          enableOverlay: function() {
1973              if ( $overlay.length === 0 ) {
1974                  $overlay = $( '<div id="wp-responsive-overlay"></div>' )
1975                      .insertAfter( '#wpcontent' )
1976                      .hide()
1977                      .on( 'click.wp-responsive', function() {
1978                          $toolbar.find( '.menupop.hover' ).removeClass( 'hover' );
1979                          $( this ).hide();
1980                      });
1981              }
1982  
1983              $toolbarPopups.on( 'click.wp-responsive', function() {
1984                  $overlay.show();
1985              });
1986          },
1987  
1988          /**
1989           * Disables the responsive overlay and removes the overlay.
1990           *
1991           * @since 3.8.0
1992           *
1993           * @return {void}
1994           */
1995          disableOverlay: function() {
1996              $toolbarPopups.off( 'click.wp-responsive' );
1997              $overlay.hide();
1998          },
1999  
2000          /**
2001           * Disables sortables.
2002           *
2003           * @since 3.8.0
2004           *
2005           * @return {void}
2006           */
2007          disableSortables: function() {
2008              if ( $sortables.length ) {
2009                  try {
2010                      $sortables.sortable( 'disable' );
2011                      $sortables.find( '.ui-sortable-handle' ).addClass( 'is-non-sortable' );
2012                  } catch ( e ) {}
2013              }
2014          },
2015  
2016          /**
2017           * Enables sortables.
2018           *
2019           * @since 3.8.0
2020           *
2021           * @return {void}
2022           */
2023          enableSortables: function() {
2024              if ( $sortables.length ) {
2025                  try {
2026                      $sortables.sortable( 'enable' );
2027                      $sortables.find( '.ui-sortable-handle' ).removeClass( 'is-non-sortable' );
2028                  } catch ( e ) {}
2029              }
2030          }
2031      };
2032  
2033      /**
2034       * Add an ARIA role `button` to elements that behave like UI controls when JavaScript is on.
2035       *
2036       * @since 4.5.0
2037       *
2038       * @return {void}
2039       */
2040  	function aria_button_if_js() {
2041          $( '.aria-button-if-js' ).attr( 'role', 'button' );
2042      }
2043  
2044      $( document ).on( 'ajaxComplete', function() {
2045          aria_button_if_js();
2046      });
2047  
2048      /**
2049       * Get the viewport width.
2050       *
2051       * @since 4.7.0
2052       *
2053       * @return {number|boolean} The current viewport width or false if the
2054       *                          browser doesn't support innerWidth (IE < 9).
2055       */
2056  	function getViewportWidth() {
2057          var viewportWidth = false;
2058  
2059          if ( window.innerWidth ) {
2060              // On phones, window.innerWidth is affected by zooming.
2061              viewportWidth = Math.max( window.innerWidth, document.documentElement.clientWidth );
2062          }
2063  
2064          return viewportWidth;
2065      }
2066  
2067      /**
2068       * Sets the admin menu collapsed/expanded state.
2069       *
2070       * Sets the global variable `menuState` and triggers a custom event passing
2071       * the current menu state.
2072       *
2073       * @since 4.7.0
2074       *
2075       * @return {void}
2076       */
2077  	function setMenuState() {
2078          var viewportWidth = getViewportWidth() || 961;
2079  
2080          if ( viewportWidth <= 782  ) {
2081              menuState = 'responsive';
2082          } else if ( $body.hasClass( 'folded' ) || ( $body.hasClass( 'auto-fold' ) && viewportWidth <= 960 && viewportWidth > 782 ) ) {
2083              menuState = 'folded';
2084          } else {
2085              menuState = 'open';
2086          }
2087  
2088          $document.trigger( 'wp-menu-state-set', { state: menuState } );
2089      }
2090  
2091      // Set the menu state when the window gets resized.
2092      $document.on( 'wp-window-resized.set-menu-state', setMenuState );
2093  
2094      /**
2095       * Sets ARIA attributes on the collapse/expand menu button.
2096       *
2097       * When the admin menu is open or folded, updates the `aria-expanded` and
2098       * `aria-label` attributes of the button to give feedback to assistive
2099       * technologies. In the responsive view, the button is always hidden.
2100       *
2101       * @since 4.7.0
2102       *
2103       * @return {void}
2104       */
2105      $document.on( 'wp-menu-state-set wp-collapse-menu', function( event, eventData ) {
2106          var $collapseButton = $( '#collapse-button' ),
2107              ariaExpanded, ariaLabelText;
2108  
2109          if ( 'folded' === eventData.state ) {
2110              ariaExpanded = 'false';
2111              ariaLabelText = __( 'Expand Main menu' );
2112          } else {
2113              ariaExpanded = 'true';
2114              ariaLabelText = __( 'Collapse Main menu' );
2115          }
2116  
2117          $collapseButton.attr({
2118              'aria-expanded': ariaExpanded,
2119              'aria-label': ariaLabelText
2120          });
2121      });
2122  
2123      window.wpResponsive.init();
2124      setPinMenu();
2125      setMenuState();
2126      makeNoticesDismissible();
2127      aria_button_if_js();
2128  
2129      $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 );
2130  
2131      // Set initial focus on a specific element.
2132      $( '.wp-initial-focus' ).trigger( 'focus' );
2133  
2134      // Toggle update details on update-core.php.
2135      $body.on( 'click', '.js-update-details-toggle', function() {
2136          var $updateNotice = $( this ).closest( '.js-update-details' ),
2137              $progressDiv = $( '#' + $updateNotice.data( 'update-details' ) );
2138  
2139          /*
2140           * When clicking on "Show details" move the progress div below the update
2141           * notice. Make sure it gets moved just the first time.
2142           */
2143          if ( ! $progressDiv.hasClass( 'update-details-moved' ) ) {
2144              $progressDiv.insertAfter( $updateNotice ).addClass( 'update-details-moved' );
2145          }
2146  
2147          // Toggle the progress div visibility.
2148          $progressDiv.toggle();
2149          // Toggle the Show Details button expanded state.
2150          $( this ).attr( 'aria-expanded', $progressDiv.is( ':visible' ) );
2151      });
2152  });
2153  
2154  /**
2155   * Hides the update button for expired plugin or theme uploads.
2156   *
2157   * On the "Update plugin/theme from uploaded zip" screen, once the upload has expired,
2158   * hides the "Replace current with uploaded" button and displays a warning.
2159   *
2160   * @since 5.5.0
2161   */
2162  $( function( $ ) {
2163      var $overwrite, $warning;
2164  
2165      if ( ! $body.hasClass( 'update-php' ) ) {
2166          return;
2167      }
2168  
2169      $overwrite = $( 'a.update-from-upload-overwrite' );
2170      $warning   = $( '.update-from-upload-expired' );
2171  
2172      if ( ! $overwrite.length || ! $warning.length ) {
2173          return;
2174      }
2175  
2176      window.setTimeout(
2177          function() {
2178              $overwrite.hide();
2179              $warning.removeClass( 'hidden' );
2180  
2181              if ( window.wp && window.wp.a11y ) {
2182                  window.wp.a11y.speak( $warning.text() );
2183              }
2184          },
2185          7140000 // 119 minutes. The uploaded file is deleted after 2 hours.
2186      );
2187  } );
2188  
2189  // Fire a custom jQuery event at the end of window resize.
2190  ( function() {
2191      var timeout;
2192  
2193      /**
2194       * Triggers the WP window-resize event.
2195       *
2196       * @since 3.8.0
2197       *
2198       * @return {void}
2199       */
2200  	function triggerEvent() {
2201          $document.trigger( 'wp-window-resized' );
2202      }
2203  
2204      /**
2205       * Fires the trigger event again after 200 ms.
2206       *
2207       * @since 3.8.0
2208       *
2209       * @return {void}
2210       */
2211  	function fireOnce() {
2212          window.clearTimeout( timeout );
2213          timeout = window.setTimeout( triggerEvent, 200 );
2214      }
2215  
2216      $window.on( 'resize.wp-fire-once', fireOnce );
2217  }());
2218  
2219  // Make Windows 8 devices play along nicely.
2220  (function(){
2221      if ( '-ms-user-select' in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/) ) {
2222          var msViewportStyle = document.createElement( 'style' );
2223          msViewportStyle.appendChild(
2224              document.createTextNode( '@-ms-viewport{width:auto!important}' )
2225          );
2226          document.getElementsByTagName( 'head' )[0].appendChild( msViewportStyle );
2227      }
2228  })();
2229  
2230  }( jQuery, window ));
2231  
2232  /**
2233   * Freeze animated plugin icons when reduced motion is enabled.
2234   *
2235   * When the user has enabled the 'prefers-reduced-motion' setting, this module
2236   * stops animations for all GIFs on the page with the class 'plugin-icon' or
2237   * plugin icon images in the update plugins table.
2238   *
2239   * @since 6.4.0
2240   */
2241  (function() {
2242      // Private variables and methods.
2243      var priv = {},
2244          pub = {},
2245          mediaQuery;
2246  
2247      // Initialize pauseAll to false; it will be set to true if reduced motion is preferred.
2248      priv.pauseAll = false;
2249      if ( window.matchMedia ) {
2250          mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' );
2251          if ( ! mediaQuery || mediaQuery.matches ) {
2252              priv.pauseAll = true;
2253          }
2254      }
2255  
2256      // Method to replace animated GIFs with a static frame.
2257      priv.freezeAnimatedPluginIcons = function( img ) {
2258          var coverImage = function() {
2259              var width = img.width;
2260              var height = img.height;
2261              var canvas = document.createElement( 'canvas' );
2262  
2263              // Set canvas dimensions.
2264              canvas.width = width;
2265              canvas.height = height;
2266  
2267              // Copy classes from the image to the canvas.
2268              canvas.className = img.className;
2269  
2270              // Check if the image is inside a specific table.
2271              var isInsideUpdateTable = img.closest( '#update-plugins-table' );
2272  
2273              if ( isInsideUpdateTable ) {
2274                  // Transfer computed styles from image to canvas.
2275                  var computedStyles = window.getComputedStyle( img ),
2276                      i, max;
2277                  for ( i = 0, max = computedStyles.length; i < max; i++ ) {
2278                      var propName = computedStyles[ i ];
2279                      var propValue = computedStyles.getPropertyValue( propName );
2280                      canvas.style[ propName ] = propValue;
2281                  }
2282              }
2283  
2284              // Draw the image onto the canvas.
2285              canvas.getContext( '2d' ).drawImage( img, 0, 0, width, height );
2286  
2287              // Set accessibility attributes on canvas.
2288              canvas.setAttribute( 'aria-hidden', 'true' );
2289              canvas.setAttribute( 'role', 'presentation' );
2290  
2291              // Insert canvas before the image and set the image to be near-invisible.
2292              var parent = img.parentNode;
2293              parent.insertBefore( canvas, img );
2294              img.style.opacity = 0.01;
2295              img.style.width = '0px';
2296              img.style.height = '0px';
2297          };
2298  
2299          // If the image is already loaded, apply the coverImage function.
2300          if ( img.complete ) {
2301              coverImage();
2302          } else {
2303              // Otherwise, wait for the image to load.
2304              img.addEventListener( 'load', coverImage, true );
2305          }
2306      };
2307  
2308      // Public method to freeze all relevant GIFs on the page.
2309      pub.freezeAll = function() {
2310          var images = document.querySelectorAll( '.plugin-icon, #update-plugins-table img' );
2311          for ( var x = 0; x < images.length; x++ ) {
2312              if ( /\.gif(?:\?|$)/i.test( images[ x ].src ) ) {
2313                  priv.freezeAnimatedPluginIcons( images[ x ] );
2314              }
2315          }
2316      };
2317  
2318      // Only run the freezeAll method if the user prefers reduced motion.
2319      if ( true === priv.pauseAll ) {
2320          pub.freezeAll();
2321      }
2322  
2323      // Listen for jQuery AJAX events.
2324      ( function( $ ) {
2325          if ( window.pagenow === 'plugin-install' ) {
2326              // Only listen for ajaxComplete if this is the plugin-install.php page.
2327              $( document ).ajaxComplete( function( event, xhr, settings ) {
2328  
2329                  // Check if this is the 'search-install-plugins' request.
2330                  if ( settings.data && typeof settings.data === 'string' && settings.data.includes( 'action=search-install-plugins' ) ) {
2331                      // Recheck if the user prefers reduced motion.
2332                      if ( window.matchMedia ) {
2333                          var mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' );
2334                          if ( mediaQuery.matches ) {
2335                              pub.freezeAll();
2336                          }
2337                      } else {
2338                          // Fallback for browsers that don't support matchMedia.
2339                          if ( true === priv.pauseAll ) {
2340                              pub.freezeAll();
2341                          }
2342                      }
2343                  }
2344              } );
2345          }
2346      } )( jQuery );
2347  
2348      // Expose public methods.
2349      return pub;
2350  })();


Generated : Tue Feb 18 08:20:02 2025 Cross-referenced by PHPXref