[ 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                  if ( ! $adminmenu.data('wp-responsive') ) {
1829                      return;
1830                  }
1831                  let state = ( 'false' === $( this ).attr( 'aria-expanded' ) ) ? 'true' : 'false';
1832                  $( this ).parent( 'li' ).toggleClass( 'selected' );
1833                  $( this ).attr( 'aria-expanded', state );
1834                  $( this ).trigger( 'focus' );
1835                  event.preventDefault();
1836              });
1837  
1838              self.trigger();
1839              $document.on( 'wp-window-resized.wp-responsive', this.trigger.bind( this ) );
1840  
1841              // This needs to run later as UI Sortable may be initialized when the document is ready.
1842              $window.on( 'load.wp-responsive', this.maybeDisableSortables );
1843              $document.on( 'postbox-toggled', this.maybeDisableSortables );
1844  
1845              // When the screen columns are changed, potentially disable sortables.
1846              $( '#screen-options-wrap input:not(.meta-box-reordering-toggle)' ).on( 'click', this.maybeDisableSortables );
1847          },
1848  
1849          /**
1850           * Disable sortables if there is only one metabox, or the screen is in one column mode. Otherwise, enable sortables.
1851           *
1852           * @since 5.3.0
1853           *
1854           * @return {void}
1855           */
1856          maybeDisableSortables: function() {
1857              var width = navigator.userAgent.indexOf('AppleWebKit/') > -1 ? $window.width() : window.innerWidth;
1858  
1859              if (
1860                  $body.hasClass( 'meta-box-reordering-disabled' ) ||
1861                  ( width <= 782 ) ||
1862                  ( 1 >= $sortables.find( '.ui-sortable-handle:visible' ).length && jQuery( '.columns-prefs-1 input' ).prop( 'checked' ) )
1863              ) {
1864                  this.disableSortables();
1865              } else {
1866                  this.enableSortables();
1867              }
1868          },
1869  
1870          /**
1871           * Changes properties of body and admin menu.
1872           *
1873           * Pins and unpins the menu and adds the auto-fold class to the body.
1874           * Makes the admin menu responsive and disables the metabox sortables.
1875           *
1876           * @since 3.8.0
1877           *
1878           * @return {void}
1879           */
1880          activate: function() {
1881              setPinMenu();
1882  
1883              if ( ! $body.hasClass( 'auto-fold' ) ) {
1884                  $body.addClass( 'auto-fold' );
1885              }
1886  
1887              $adminmenu.data( 'wp-responsive', 1 );
1888              this.disableSortables();
1889          },
1890  
1891          /**
1892           * Changes properties of admin menu and enables metabox sortables.
1893           *
1894           * Pin and unpin the menu.
1895           * Removes the responsiveness of the admin menu and enables the metabox sortables.
1896           *
1897           * @since 3.8.0
1898           *
1899           * @return {void}
1900           */
1901          deactivate: function() {
1902              setPinMenu();
1903              $adminmenu.removeData('wp-responsive');
1904  
1905              this.maybeDisableSortables();
1906          },
1907  
1908          /**
1909           * Toggles the aria-haspopup attribute for the responsive admin menu.
1910           *
1911           * The aria-haspopup attribute is only necessary for the responsive menu.
1912           * See ticket https://core.trac.wordpress.org/ticket/43095
1913           *
1914           * @since 6.6.0
1915           *
1916           * @param {string} action Whether to add or remove the aria-haspopup attribute.
1917           *
1918           * @return {void}
1919           */
1920          toggleAriaHasPopup: function( action ) {
1921              var elements = $adminmenu.find( '[data-ariahaspopup]' );
1922  
1923              if ( action === 'add' ) {
1924                  elements.each( function() {
1925                      $( this ).attr( 'aria-haspopup', 'menu' ).attr( 'aria-expanded', 'false' );
1926                  } );
1927  
1928                  return;
1929              }
1930  
1931              elements.each( function() {
1932                  $( this ).removeAttr( 'aria-haspopup' ).removeAttr( 'aria-expanded' );
1933              } );
1934          },
1935  
1936          /**
1937           * Sets the responsiveness and enables the overlay based on the viewport width.
1938           *
1939           * @since 3.8.0
1940           *
1941           * @return {void}
1942           */
1943          trigger: function() {
1944              var viewportWidth = getViewportWidth();
1945  
1946              // Exclude IE < 9, it doesn't support @media CSS rules.
1947              if ( ! viewportWidth ) {
1948                  return;
1949              }
1950  
1951              if ( viewportWidth <= 782 ) {
1952                  if ( ! wpResponsiveActive ) {
1953                      $document.trigger( 'wp-responsive-activate' );
1954                      wpResponsiveActive = true;
1955                  }
1956              } else {
1957                  if ( wpResponsiveActive ) {
1958                      $document.trigger( 'wp-responsive-deactivate' );
1959                      wpResponsiveActive = false;
1960                  }
1961              }
1962  
1963              if ( viewportWidth <= 480 ) {
1964                  this.enableOverlay();
1965              } else {
1966                  this.disableOverlay();
1967              }
1968  
1969              this.maybeDisableSortables();
1970          },
1971  
1972          /**
1973           * Inserts a responsive overlay and toggles the window.
1974           *
1975           * @since 3.8.0
1976           *
1977           * @return {void}
1978           */
1979          enableOverlay: function() {
1980              if ( $overlay.length === 0 ) {
1981                  $overlay = $( '<div id="wp-responsive-overlay"></div>' )
1982                      .insertAfter( '#wpcontent' )
1983                      .hide()
1984                      .on( 'click.wp-responsive', function() {
1985                          $toolbar.find( '.menupop.hover' ).removeClass( 'hover' );
1986                          $( this ).hide();
1987                      });
1988              }
1989  
1990              $toolbarPopups.on( 'click.wp-responsive', function() {
1991                  $overlay.show();
1992              });
1993          },
1994  
1995          /**
1996           * Disables the responsive overlay and removes the overlay.
1997           *
1998           * @since 3.8.0
1999           *
2000           * @return {void}
2001           */
2002          disableOverlay: function() {
2003              $toolbarPopups.off( 'click.wp-responsive' );
2004              $overlay.hide();
2005          },
2006  
2007          /**
2008           * Disables sortables.
2009           *
2010           * @since 3.8.0
2011           *
2012           * @return {void}
2013           */
2014          disableSortables: function() {
2015              if ( $sortables.length ) {
2016                  try {
2017                      $sortables.sortable( 'disable' );
2018                      $sortables.find( '.ui-sortable-handle' ).addClass( 'is-non-sortable' );
2019                  } catch ( e ) {}
2020              }
2021          },
2022  
2023          /**
2024           * Enables sortables.
2025           *
2026           * @since 3.8.0
2027           *
2028           * @return {void}
2029           */
2030          enableSortables: function() {
2031              if ( $sortables.length ) {
2032                  try {
2033                      $sortables.sortable( 'enable' );
2034                      $sortables.find( '.ui-sortable-handle' ).removeClass( 'is-non-sortable' );
2035                  } catch ( e ) {}
2036              }
2037          }
2038      };
2039  
2040      /**
2041       * Add an ARIA role `button` to elements that behave like UI controls when JavaScript is on.
2042       *
2043       * @since 4.5.0
2044       *
2045       * @return {void}
2046       */
2047  	function aria_button_if_js() {
2048          $( '.aria-button-if-js' ).attr( 'role', 'button' );
2049      }
2050  
2051      $( document ).on( 'ajaxComplete', function() {
2052          aria_button_if_js();
2053      });
2054  
2055      /**
2056       * Get the viewport width.
2057       *
2058       * @since 4.7.0
2059       *
2060       * @return {number|boolean} The current viewport width or false if the
2061       *                          browser doesn't support innerWidth (IE < 9).
2062       */
2063  	function getViewportWidth() {
2064          var viewportWidth = false;
2065  
2066          if ( window.innerWidth ) {
2067              // On phones, window.innerWidth is affected by zooming.
2068              viewportWidth = Math.max( window.innerWidth, document.documentElement.clientWidth );
2069          }
2070  
2071          return viewportWidth;
2072      }
2073  
2074      /**
2075       * Sets the admin menu collapsed/expanded state.
2076       *
2077       * Sets the global variable `menuState` and triggers a custom event passing
2078       * the current menu state.
2079       *
2080       * @since 4.7.0
2081       *
2082       * @return {void}
2083       */
2084  	function setMenuState() {
2085          var viewportWidth = getViewportWidth() || 961;
2086  
2087          if ( viewportWidth <= 782  ) {
2088              menuState = 'responsive';
2089          } else if ( $body.hasClass( 'folded' ) || ( $body.hasClass( 'auto-fold' ) && viewportWidth <= 960 && viewportWidth > 782 ) ) {
2090              menuState = 'folded';
2091          } else {
2092              menuState = 'open';
2093          }
2094  
2095          $document.trigger( 'wp-menu-state-set', { state: menuState } );
2096      }
2097  
2098      // Set the menu state when the window gets resized.
2099      $document.on( 'wp-window-resized.set-menu-state', setMenuState );
2100  
2101      /**
2102       * Sets ARIA attributes on the collapse/expand menu button.
2103       *
2104       * When the admin menu is open or folded, updates the `aria-expanded` and
2105       * `aria-label` attributes of the button to give feedback to assistive
2106       * technologies. In the responsive view, the button is always hidden.
2107       *
2108       * @since 4.7.0
2109       *
2110       * @return {void}
2111       */
2112      $document.on( 'wp-menu-state-set wp-collapse-menu', function( event, eventData ) {
2113          var $collapseButton = $( '#collapse-button' ),
2114              ariaExpanded, ariaLabelText;
2115  
2116          if ( 'folded' === eventData.state ) {
2117              ariaExpanded = 'false';
2118              ariaLabelText = __( 'Expand Main menu' );
2119          } else {
2120              ariaExpanded = 'true';
2121              ariaLabelText = __( 'Collapse Main menu' );
2122          }
2123  
2124          $collapseButton.attr({
2125              'aria-expanded': ariaExpanded,
2126              'aria-label': ariaLabelText
2127          });
2128      });
2129  
2130      window.wpResponsive.init();
2131      setPinMenu();
2132      setMenuState();
2133      makeNoticesDismissible();
2134      aria_button_if_js();
2135  
2136      $document.on( 'wp-pin-menu wp-window-resized.pin-menu postboxes-columnchange.pin-menu postbox-toggled.pin-menu wp-collapse-menu.pin-menu wp-scroll-start.pin-menu', setPinMenu );
2137  
2138      // Set initial focus on a specific element.
2139      $( '.wp-initial-focus' ).trigger( 'focus' );
2140  
2141      // Toggle update details on update-core.php.
2142      $body.on( 'click', '.js-update-details-toggle', function() {
2143          var $updateNotice = $( this ).closest( '.js-update-details' ),
2144              $progressDiv = $( '#' + $updateNotice.data( 'update-details' ) );
2145  
2146          /*
2147           * When clicking on "Show details" move the progress div below the update
2148           * notice. Make sure it gets moved just the first time.
2149           */
2150          if ( ! $progressDiv.hasClass( 'update-details-moved' ) ) {
2151              $progressDiv.insertAfter( $updateNotice ).addClass( 'update-details-moved' );
2152          }
2153  
2154          // Toggle the progress div visibility.
2155          $progressDiv.toggle();
2156          // Toggle the Show Details button expanded state.
2157          $( this ).attr( 'aria-expanded', $progressDiv.is( ':visible' ) );
2158      });
2159  });
2160  
2161  /**
2162   * Hides the update button for expired plugin or theme uploads.
2163   *
2164   * On the "Update plugin/theme from uploaded zip" screen, once the upload has expired,
2165   * hides the "Replace current with uploaded" button and displays a warning.
2166   *
2167   * @since 5.5.0
2168   */
2169  $( function( $ ) {
2170      var $overwrite, $warning;
2171  
2172      if ( ! $body.hasClass( 'update-php' ) ) {
2173          return;
2174      }
2175  
2176      $overwrite = $( 'a.update-from-upload-overwrite' );
2177      $warning   = $( '.update-from-upload-expired' );
2178  
2179      if ( ! $overwrite.length || ! $warning.length ) {
2180          return;
2181      }
2182  
2183      window.setTimeout(
2184          function() {
2185              $overwrite.hide();
2186              $warning.removeClass( 'hidden' );
2187  
2188              if ( window.wp && window.wp.a11y ) {
2189                  window.wp.a11y.speak( $warning.text() );
2190              }
2191          },
2192          7140000 // 119 minutes. The uploaded file is deleted after 2 hours.
2193      );
2194  } );
2195  
2196  // Fire a custom jQuery event at the end of window resize.
2197  ( function() {
2198      var timeout;
2199  
2200      /**
2201       * Triggers the WP window-resize event.
2202       *
2203       * @since 3.8.0
2204       *
2205       * @return {void}
2206       */
2207  	function triggerEvent() {
2208          $document.trigger( 'wp-window-resized' );
2209      }
2210  
2211      /**
2212       * Fires the trigger event again after 200 ms.
2213       *
2214       * @since 3.8.0
2215       *
2216       * @return {void}
2217       */
2218  	function fireOnce() {
2219          window.clearTimeout( timeout );
2220          timeout = window.setTimeout( triggerEvent, 200 );
2221      }
2222  
2223      $window.on( 'resize.wp-fire-once', fireOnce );
2224  }());
2225  
2226  // Make Windows 8 devices play along nicely.
2227  (function(){
2228      if ( '-ms-user-select' in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/) ) {
2229          var msViewportStyle = document.createElement( 'style' );
2230          msViewportStyle.appendChild(
2231              document.createTextNode( '@-ms-viewport{width:auto!important}' )
2232          );
2233          document.getElementsByTagName( 'head' )[0].appendChild( msViewportStyle );
2234      }
2235  })();
2236  
2237  }( jQuery, window ));
2238  
2239  /**
2240   * Freeze animated plugin icons when reduced motion is enabled.
2241   *
2242   * When the user has enabled the 'prefers-reduced-motion' setting, this module
2243   * stops animations for all GIFs on the page with the class 'plugin-icon' or
2244   * plugin icon images in the update plugins table.
2245   *
2246   * @since 6.4.0
2247   *
2248   * @return {Object} Public methods.
2249   */
2250  (function() {
2251      // Private variables and methods.
2252      var priv = {},
2253          pub = {},
2254          mediaQuery;
2255  
2256      // Initialize pauseAll to false; it will be set to true if reduced motion is preferred.
2257      priv.pauseAll = false;
2258      if ( window.matchMedia ) {
2259          mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' );
2260          if ( ! mediaQuery || mediaQuery.matches ) {
2261              priv.pauseAll = true;
2262          }
2263      }
2264  
2265      // Method to replace animated GIFs with a static frame.
2266      priv.freezeAnimatedPluginIcons = function( img ) {
2267          var coverImage = function() {
2268              var width = img.width;
2269              var height = img.height;
2270              var canvas = document.createElement( 'canvas' );
2271  
2272              // Set canvas dimensions.
2273              canvas.width = width;
2274              canvas.height = height;
2275  
2276              // Copy classes from the image to the canvas.
2277              canvas.className = img.className;
2278  
2279              // Check if the image is inside a specific table.
2280              var isInsideUpdateTable = img.closest( '#update-plugins-table' );
2281  
2282              if ( isInsideUpdateTable ) {
2283                  // Transfer computed styles from image to canvas.
2284                  var computedStyles = window.getComputedStyle( img ),
2285                      i, max;
2286                  for ( i = 0, max = computedStyles.length; i < max; i++ ) {
2287                      var propName = computedStyles[ i ];
2288                      var propValue = computedStyles.getPropertyValue( propName );
2289                      canvas.style[ propName ] = propValue;
2290                  }
2291              }
2292  
2293              // Draw the image onto the canvas.
2294              canvas.getContext( '2d' ).drawImage( img, 0, 0, width, height );
2295  
2296              // Set accessibility attributes on canvas.
2297              canvas.setAttribute( 'aria-hidden', 'true' );
2298              canvas.setAttribute( 'role', 'presentation' );
2299  
2300              // Insert canvas before the image and set the image to be near-invisible.
2301              var parent = img.parentNode;
2302              parent.insertBefore( canvas, img );
2303              img.style.opacity = 0.01;
2304              img.style.width = '0px';
2305              img.style.height = '0px';
2306          };
2307  
2308          // If the image is already loaded, apply the coverImage function.
2309          if ( img.complete ) {
2310              coverImage();
2311          } else {
2312              // Otherwise, wait for the image to load.
2313              img.addEventListener( 'load', coverImage, true );
2314          }
2315      };
2316  
2317      // Public method to freeze all relevant GIFs on the page.
2318      pub.freezeAll = function() {
2319          var images = document.querySelectorAll( '.plugin-icon, #update-plugins-table img' );
2320          for ( var x = 0; x < images.length; x++ ) {
2321              if ( /\.gif(?:\?|$)/i.test( images[ x ].src ) ) {
2322                  priv.freezeAnimatedPluginIcons( images[ x ] );
2323              }
2324          }
2325      };
2326  
2327      // Only run the freezeAll method if the user prefers reduced motion.
2328      if ( true === priv.pauseAll ) {
2329          pub.freezeAll();
2330      }
2331  
2332      // Listen for jQuery AJAX events.
2333      ( function( $ ) {
2334          if ( window.pagenow === 'plugin-install' ) {
2335              // Only listen for ajaxComplete if this is the plugin-install.php page.
2336              $( document ).ajaxComplete( function( event, xhr, settings ) {
2337  
2338                  // Check if this is the 'search-install-plugins' request.
2339                  if ( settings.data && typeof settings.data === 'string' && settings.data.includes( 'action=search-install-plugins' ) ) {
2340                      // Recheck if the user prefers reduced motion.
2341                      if ( window.matchMedia ) {
2342                          var mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' );
2343                          if ( mediaQuery.matches ) {
2344                              pub.freezeAll();
2345                          }
2346                      } else {
2347                          // Fallback for browsers that don't support matchMedia.
2348                          if ( true === priv.pauseAll ) {
2349                              pub.freezeAll();
2350                          }
2351                      }
2352                  }
2353              } );
2354          }
2355      } )( jQuery );
2356  
2357      // Expose public methods.
2358      return pub;
2359  })();
2360  
2361  /**
2362   * Validate the delete-and-reassign users form and surface an accessible
2363   * error summary instead of disabling the submit button.
2364   *
2365   * Disabled buttons can't be discovered by assistive technology, so rather
2366   * than blocking submission we let the form submit, intercept it when content
2367   * decisions are still missing, and present a focusable error summary that
2368   * lists how many decisions remain and links straight to each one.
2369   *
2370   * Shared by both the single-site (wp-admin/users.php) and multisite/network
2371   * (confirm_delete_users() in wp-admin/includes/ms.php) deletion forms. The two
2372   * differ in markup: single site has one content decision per user, multisite
2373   * has one decision per site a user belongs to (several radio groups per
2374   * fieldset), and their reassign dropdowns use different "no selection" values.
2375   * The logic below works per radio group so it covers both.
2376   *
2377   * @since 7.1.0
2378   */
2379  (function(){
2380      const { _n, sprintf } = wp.i18n;
2381      const usersForm = document.querySelector( '.delete-and-reassign-users-form' );
2382  
2383      // Check if the form exists and contains any radio buttons.
2384      if ( ! usersForm || ! usersForm.querySelector( 'input[type="radio"]' ) ) {
2385          return;
2386      }
2387  
2388      const summaryId = 'delete-users-error-summary';
2389  
2390      /**
2391       * Whether a reassign dropdown has no user selected.
2392       *
2393       * The "Select a user" placeholder value differs between the forms: the
2394       * single-site dropdown uses an empty string, the multisite one uses the
2395       * wp_dropdown_users() default of '-1'.
2396       *
2397       * @param {HTMLSelectElement} select The reassign dropdown.
2398       * @return {boolean} True when no real user is selected.
2399       */
2400  	function hasNoSelectedUser( select ) {
2401          return '' === select.value || '-1' === select.value;
2402      }
2403  
2404      /**
2405       * Builds a human-readable label for a radio group's decision.
2406       *
2407       * Combines the fieldset legend (the user) with the site context that
2408       * precedes the group on multisite, so each summary entry is identifiable.
2409       *
2410       * @param {HTMLElement} group The radio group (<ul>) element.
2411       * @return {string} The composed label.
2412       */
2413  	function getDecisionLabel( group ) {
2414          const fieldset = group.closest( 'fieldset' );
2415          const legend   = fieldset ? fieldset.querySelector( 'legend' ) : null;
2416          const parts    = [];
2417  
2418          if ( legend ) {
2419              parts.push( legend.textContent.trim() );
2420          }
2421  
2422          // On multisite each radio group is preceded by a "Site: …" paragraph.
2423          const previous = group.previousElementSibling;
2424          if ( previous && previous !== legend && previous.textContent.trim() ) {
2425              parts.push( previous.textContent.trim() );
2426          }
2427  
2428          return parts.join( ' – ' );
2429      }
2430  
2431      // Keep the radio selection in sync with the reassign dropdown.
2432      usersForm.querySelectorAll( 'select' ).forEach( function( selectElement ) {
2433          selectElement.addEventListener( 'change', function( e ) {
2434              const item  = e.target.closest( 'li' );
2435              const radio = item ? item.querySelector( 'input[type="radio"]' ) : null;
2436              if ( radio ) {
2437                  radio.checked = ! hasNoSelectedUser( e.target );
2438              }
2439          });
2440      });
2441  
2442      /**
2443       * Returns the radio groups whose content decision is still incomplete.
2444       *
2445       * A decision unit is a single radio group (<ul>), which maps to one user on
2446       * single site and one site-per-user on multisite.
2447       *
2448       * @return {Array} Objects describing each incomplete decision.
2449       */
2450  	function getIncompleteDecisions() {
2451          const incomplete = [];
2452  
2453          usersForm.querySelectorAll( 'fieldset ul' ).forEach( function( group ) {
2454              const radios = group.querySelectorAll( 'input[type="radio"]' );
2455              if ( ! radios.length ) {
2456                  return;
2457              }
2458  
2459              const checked = group.querySelector( 'input[type="radio"]:checked' );
2460  
2461              // No option chosen yet.
2462              if ( ! checked ) {
2463                  incomplete.push( { target: radios[ 0 ], label: getDecisionLabel( group ) } );
2464                  return;
2465              }
2466  
2467              // "Attribute to another user" chosen, but no user selected.
2468              if ( 'reassign' === checked.value ) {
2469                  const select = group.querySelector( 'select' );
2470                  if ( select && hasNoSelectedUser( select ) ) {
2471                      incomplete.push( { target: select, label: getDecisionLabel( group ) } );
2472                  }
2473              }
2474          });
2475  
2476          return incomplete;
2477      }
2478  
2479      /**
2480       * Builds or refreshes the error summary markup.
2481       *
2482       * @param {Array} incomplete Incomplete decisions from getIncompleteDecisions().
2483       * @return {string} The summary title, for announcing to assistive technology.
2484       */
2485  	function renderErrorSummary( incomplete ) {
2486          let summary = document.getElementById( summaryId );
2487  
2488          if ( ! summary ) {
2489              summary = document.createElement( 'div' );
2490              summary.id = summaryId;
2491              summary.className = 'notice notice-error';
2492              summary.setAttribute( 'tabindex', '-1' );
2493  
2494              // The wrapper contains the form on single site and wraps it on
2495              // multisite; insert the summary right after the page heading.
2496              const wrap    = usersForm.querySelector( '.wrap' ) || usersForm.closest( '.wrap' ) || usersForm;
2497              const heading = wrap.querySelector( 'h1' );
2498              wrap.insertBefore( summary, heading ? heading.nextSibling : wrap.firstChild );
2499          }
2500  
2501          const count = incomplete.length;
2502          const title = sprintf(
2503              /* translators: %s: Number of content decisions still required. */
2504              _n(
2505                  '%s content decision is still required before you can delete.',
2506                  '%s content decisions are still required before you can delete.',
2507                  count
2508              ),
2509              count
2510          );
2511  
2512          // Clear any previous markup and invalid states before rebuilding,
2513          // so decisions resolved since the last render are no longer flagged.
2514          summary.textContent = '';
2515          usersForm.querySelectorAll( '[aria-invalid]' ).forEach( function( el ) {
2516              el.removeAttribute( 'aria-invalid' );
2517          });
2518  
2519          const titleEl = document.createElement( 'p' );
2520          const strong  = document.createElement( 'strong' );
2521          strong.textContent = title;
2522          titleEl.appendChild( strong );
2523          summary.appendChild( titleEl );
2524  
2525          const list = document.createElement( 'ul' );
2526          incomplete.forEach( function( item ) {
2527              const li   = document.createElement( 'li' );
2528              const link = document.createElement( 'a' );
2529              link.href        = '#' + item.target.id;
2530              link.textContent = item.label;
2531              link.addEventListener( 'click', function( e ) {
2532                  e.preventDefault();
2533                  item.target.focus();
2534              });
2535              li.appendChild( link );
2536              list.appendChild( li );
2537  
2538              item.target.setAttribute( 'aria-invalid', 'true' );
2539          });
2540          summary.appendChild( list );
2541  
2542          return title;
2543      }
2544  
2545      /**
2546       * Removes the error summary and clears invalid states.
2547       */
2548  	function clearErrorState() {
2549          const summary = document.getElementById( summaryId );
2550          if ( summary ) {
2551              summary.remove();
2552          }
2553          usersForm.querySelectorAll( '[aria-invalid]' ).forEach( function( el ) {
2554              el.removeAttribute( 'aria-invalid' );
2555          });
2556      }
2557  
2558      /**
2559       * Refreshes the error summary to match the current form state.
2560       *
2561       * @param {boolean} moveFocus Whether to move focus to the summary and
2562       *                            announce it (used on a failed submit).
2563       * @return {number} The number of incomplete decisions.
2564       */
2565  	function updateSummary( moveFocus ) {
2566          const incomplete = getIncompleteDecisions();
2567  
2568          if ( ! incomplete.length ) {
2569              clearErrorState();
2570              return 0;
2571          }
2572  
2573          const title = renderErrorSummary( incomplete );
2574  
2575          if ( moveFocus ) {
2576              document.getElementById( summaryId ).focus();
2577              if ( window.wp && window.wp.a11y ) {
2578                  window.wp.a11y.speak( title, 'assertive' );
2579              }
2580          }
2581  
2582          return incomplete.length;
2583      }
2584  
2585      usersForm.addEventListener( 'submit', function( e ) {
2586          if ( updateSummary( true ) > 0 ) {
2587              e.preventDefault();
2588          }
2589      });
2590  
2591      // Keep an existing summary current as decisions are resolved, without
2592      // stealing focus on every interaction.
2593      usersForm.addEventListener( 'change', function() {
2594          if ( document.getElementById( summaryId ) ) {
2595              updateSummary( false );
2596          }
2597      });
2598  })();


Generated : Thu Oct 15 08:20:32 2026 Cross-referenced by PHPXref