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


Generated : Fri Nov 13 08:20:01 2026 Cross-referenced by PHPXref