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


Generated : Fri Sep 4 08:20:24 2026 Cross-referenced by PHPXref