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


Generated : Thu Sep 24 08:20:34 2026 Cross-referenced by PHPXref