[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/ -> media-views.js (source)

   1  /******/ (() => { // webpackBootstrap
   2  /******/     var __webpack_modules__ = ({
   3  
   4  /***/ 7145
   5  (module) {
   6  
   7  var Selection = wp.media.model.Selection,
   8      Library = wp.media.controller.Library,
   9      CollectionAdd;
  10  
  11  /**
  12   * wp.media.controller.CollectionAdd
  13   *
  14   * A state for adding attachments to a collection (e.g. video playlist).
  15   *
  16   * @memberOf wp.media.controller
  17   *
  18   * @class
  19   * @augments wp.media.controller.Library
  20   * @augments wp.media.controller.State
  21   * @augments Backbone.Model
  22   *
  23   * @param {Object}                     [attributes]                         The attributes hash passed to the state.
  24   * @param {string}                     [attributes.id=library]              Unique identifier.
  25   * @param {string}                     attributes.title                     Title for the state. Displays in the frame's title region.
  26   * @param {boolean|string}             [attributes.multiple=add]            Whether multi-select is enabled. Accepts 'add' or true.
  27   *                                                                          When set to true, requires Shift or Cmd/Ctrl to select multiple items.
  28   *                                                                          When set to 'add', allows selecting multiple items by clicking thumbnails.
  29   * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
  30   *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
  31   * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
  32   *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
  33   * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
  34   * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
  35   *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
  36   * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
  37   * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
  38   * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
  39   * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
  40   * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
  41   * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
  42   * @param {number}                     [attributes.priority=100]            The priority for the state link in the media menu.
  43   * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
  44   *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
  45   * @param {string}                     attributes.type                      The collection's media type. (e.g. 'video').
  46   * @param {string}                     attributes.collectionType            The collection type. (e.g. 'playlist').
  47   */
  48  CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{
  49      defaults: _.defaults( {
  50          // Selection defaults. @see media.model.Selection
  51          multiple:      'add',
  52          // Attachments browser defaults. @see media.view.AttachmentsBrowser
  53          filterable:    'uploaded',
  54  
  55          priority:      100,
  56          syncSelection: false
  57      }, Library.prototype.defaults ),
  58  
  59      /**
  60       * Initializes the CollectionAdd controller.
  61       *
  62       * @since 3.9.0
  63       */
  64      initialize: function() {
  65          var collectionType = this.get('collectionType');
  66  
  67          if ( 'video' === this.get( 'type' ) ) {
  68              collectionType = 'video-' + collectionType;
  69          }
  70  
  71          this.set( 'id', collectionType + '-library' );
  72          this.set( 'toolbar', collectionType + '-add' );
  73          this.set( 'menu', collectionType );
  74  
  75          // If we haven't been provided a `library`, create a `Selection`.
  76          if ( ! this.get('library') ) {
  77              this.set( 'library', wp.media.query({ type: this.get('type') }) );
  78          }
  79          Library.prototype.initialize.apply( this, arguments );
  80      },
  81  
  82      /**
  83       * Activates the CollectionAdd controller.
  84       *
  85       * @since 3.9.0
  86       */
  87      activate: function() {
  88          var library = this.get('library'),
  89              editLibrary = this.get('editLibrary'),
  90              edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
  91  
  92          if ( editLibrary && editLibrary !== edit ) {
  93              library.unobserve( editLibrary );
  94          }
  95  
  96          // Accepts attachments that exist in the original library and
  97          // that do not exist in gallery's library.
  98          library.validator = function( attachment ) {
  99              return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
 100          };
 101  
 102          /*
 103           * Reset the library to ensure that all attachments are re-added
 104           * to the collection. Do so silently, as calling `observe` will
 105           * trigger the `reset` event.
 106           */
 107          library.reset( library.mirroring.models, { silent: true });
 108          library.observe( edit );
 109          this.set('editLibrary', edit);
 110  
 111          Library.prototype.activate.apply( this, arguments );
 112      }
 113  });
 114  
 115  module.exports = CollectionAdd;
 116  
 117  
 118  /***/ },
 119  
 120  /***/ 8612
 121  (module) {
 122  
 123  var Library = wp.media.controller.Library,
 124      l10n = wp.media.view.l10n,
 125      $ = jQuery,
 126      CollectionEdit;
 127  
 128  /**
 129   * wp.media.controller.CollectionEdit
 130   *
 131   * A state for editing a collection, which is used by audio and video playlists,
 132   * and can be used for other collections.
 133   *
 134   * @memberOf wp.media.controller
 135   *
 136   * @class
 137   * @augments wp.media.controller.Library
 138   * @augments wp.media.controller.State
 139   * @augments Backbone.Model
 140   *
 141   * @param {Object}                     [attributes]                      The attributes hash passed to the state.
 142   * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
 143   * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
 144   *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
 145   * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
 146   * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
 147   * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
 148   * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
 149   * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
 150   * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
 151   * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
 152   * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
 153   * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
 154   * @param {number}                     [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
 155   * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
 156   * @param {number}                     [attributes.priority=60]          The priority for the state link in the media menu.
 157   * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
 158   *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
 159   * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
 160   * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
 161   *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
 162   * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
 163   * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
 164   */
 165  CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{
 166      defaults: {
 167          multiple:         false,
 168          sortable:         true,
 169          date:             false,
 170          searchable:       false,
 171          content:          'browse',
 172          describe:         true,
 173          dragInfo:         true,
 174          idealColumnWidth: 170,
 175          editing:          false,
 176          priority:         60,
 177          SettingsView:     false,
 178          syncSelection:    false
 179      },
 180  
 181      /**
 182       * Initializes the CollectionEdit controller.
 183       *
 184       * @since 3.9.0
 185       */
 186      initialize: function() {
 187          var collectionType = this.get('collectionType');
 188  
 189          if ( 'video' === this.get( 'type' ) ) {
 190              collectionType = 'video-' + collectionType;
 191          }
 192  
 193          this.set( 'id', collectionType + '-edit' );
 194          this.set( 'toolbar', collectionType + '-edit' );
 195  
 196          // If we haven't been provided a `library`, create a `Selection`.
 197          if ( ! this.get('library') ) {
 198              this.set( 'library', new wp.media.model.Selection() );
 199          }
 200          // The single `Attachment` view to be used in the `Attachments` view.
 201          if ( ! this.get('AttachmentView') ) {
 202              this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
 203          }
 204          Library.prototype.initialize.apply( this, arguments );
 205      },
 206  
 207      /**
 208       * Activates the CollectionEdit controller.
 209       *
 210       * @since 3.9.0
 211       */
 212      activate: function() {
 213          var library = this.get('library');
 214  
 215          // Limit the library to images only.
 216          library.props.set( 'type', this.get( 'type' ) );
 217  
 218          // Watch for uploaded attachments.
 219          this.get('library').observe( wp.Uploader.queue );
 220  
 221          this.frame.on( 'content:render:browse', this.renderSettings, this );
 222  
 223          Library.prototype.activate.apply( this, arguments );
 224      },
 225  
 226      /**
 227       * Deactivates the CollectionEdit controller.
 228       *
 229       * @since 3.9.0
 230       */
 231      deactivate: function() {
 232          // Stop watching for uploaded attachments.
 233          this.get('library').unobserve( wp.Uploader.queue );
 234  
 235          this.frame.off( 'content:render:browse', this.renderSettings, this );
 236  
 237          Library.prototype.deactivate.apply( this, arguments );
 238      },
 239  
 240      /**
 241       * Render the collection embed settings view in the browser sidebar.
 242       *
 243       * @todo This is against the pattern elsewhere in media. Typically the frame
 244       *       is responsible for adding region mode callbacks. Explain.
 245       *
 246       * @since 3.9.0
 247       *
 248       * @param {wp.media.view.attachmentsBrowser} attachmentsBrowserView The attachments browser view.
 249       */
 250      renderSettings: function( attachmentsBrowserView ) {
 251          var library = this.get('library'),
 252              collectionType = this.get('collectionType'),
 253              dragInfoText = this.get('dragInfoText'),
 254              SettingsView = this.get('SettingsView'),
 255              obj = {};
 256  
 257          if ( ! library || ! attachmentsBrowserView ) {
 258              return;
 259          }
 260  
 261          library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
 262  
 263          obj[ collectionType ] = new SettingsView({
 264              controller: this,
 265              model:      library[ collectionType ],
 266              priority:   40
 267          });
 268  
 269          attachmentsBrowserView.sidebar.set( obj );
 270  
 271          if ( dragInfoText ) {
 272              attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
 273                  el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
 274                  priority: -40
 275              }) );
 276          }
 277  
 278          // Add the 'Reverse order' button to the toolbar.
 279          attachmentsBrowserView.toolbar.set( 'reverse', {
 280              text:     l10n.reverseOrder,
 281              priority: 80,
 282  
 283              click: function() {
 284                  library.reset( library.toArray().reverse() );
 285              }
 286          });
 287      }
 288  });
 289  
 290  module.exports = CollectionEdit;
 291  
 292  
 293  /***/ },
 294  
 295  /***/ 5422
 296  (module) {
 297  
 298  var l10n = wp.media.view.l10n,
 299      Cropper;
 300  
 301  /**
 302   * wp.media.controller.Cropper
 303   *
 304   * A class for cropping an image when called from the header media customization panel.
 305   *
 306   * @memberOf wp.media.controller
 307   *
 308   * @class
 309   * @augments wp.media.controller.State
 310   * @augments Backbone.Model
 311   */
 312  Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{
 313      defaults: {
 314          id:          'cropper',
 315          title:       l10n.cropImage,
 316          // Region mode defaults.
 317          toolbar:     'crop',
 318          content:     'crop',
 319          router:      false,
 320          canSkipCrop: false,
 321  
 322          // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state.
 323          doCropArgs: {}
 324      },
 325  
 326      /**
 327       * Shows the crop image window when called from the Add new image button.
 328       *
 329       * @since 4.2.0
 330       *
 331       * @return {void}
 332       */
 333      activate: function() {
 334          this.frame.on( 'content:create:crop', this.createCropContent, this );
 335          this.frame.on( 'close', this.removeCropper, this );
 336          this.set('selection', new Backbone.Collection(this.frame._selection.single));
 337      },
 338  
 339      /**
 340       * Changes the state of the toolbar window to browse mode.
 341       *
 342       * @since 4.2.0
 343       *
 344       * @return {void}
 345       */
 346      deactivate: function() {
 347          this.frame.toolbar.mode('browse');
 348      },
 349  
 350      /**
 351       * Creates the crop image window.
 352       *
 353       * Initialized when clicking on the Select and Crop button.
 354       *
 355       * @since 4.2.0
 356       *
 357       * @fires crop window
 358       *
 359       * @return {void}
 360       */
 361      createCropContent: function() {
 362          this.cropperView = new wp.media.view.Cropper({
 363              controller: this,
 364              attachment: this.get('selection').first()
 365          });
 366          this.cropperView.on('image-loaded', this.createCropToolbar, this);
 367          this.frame.content.set(this.cropperView);
 368  
 369      },
 370  
 371      /**
 372       * Removes the image selection and closes the cropping window.
 373       *
 374       * @since 4.2.0
 375       *
 376       * @return {void}
 377       */
 378      removeCropper: function() {
 379          this.imgSelect.cancelSelection();
 380          this.imgSelect.setOptions({remove: true});
 381          this.imgSelect.update();
 382          this.cropperView.remove();
 383      },
 384  
 385      /**
 386       * Checks if cropping can be skipped and creates crop toolbar accordingly.
 387       *
 388       * @since 4.2.0
 389       *
 390       * @return {void}
 391       */
 392      createCropToolbar: function() {
 393          var canSkipCrop, hasRequiredAspectRatio, suggestedCropSize, toolbarOptions;
 394  
 395          suggestedCropSize      = this.get( 'suggestedCropSize' );
 396          hasRequiredAspectRatio = this.get( 'hasRequiredAspectRatio' );
 397          canSkipCrop            = this.get( 'canSkipCrop' ) || false;
 398  
 399          toolbarOptions = {
 400              controller: this.frame,
 401              items: {
 402                  insert: {
 403                      style:    'primary',
 404                      text:     l10n.cropImage,
 405                      priority: 80,
 406                      requires: { library: false, selection: false },
 407  
 408                      click: function() {
 409                          var controller = this.controller,
 410                              selection;
 411  
 412                          selection = controller.state().get('selection').first();
 413                          selection.set({cropDetails: controller.state().imgSelect.getSelection()});
 414  
 415                          this.$el.text(l10n.cropping);
 416                          this.$el.prop( 'disabled', true );
 417  
 418                          controller.state().doCrop( selection ).done( function( croppedImage ) {
 419                              controller.trigger('cropped', croppedImage );
 420                              controller.close();
 421                          }).fail( function() {
 422                              controller.trigger('content:error:crop');
 423                          });
 424                      }
 425                  }
 426              }
 427          };
 428  
 429          if ( canSkipCrop || hasRequiredAspectRatio ) {
 430              _.extend( toolbarOptions.items, {
 431                  skip: {
 432                      style:      'secondary',
 433                      text:       l10n.skipCropping,
 434                      priority:   70,
 435                      requires:   { library: false, selection: false },
 436                      click:      function() {
 437                          var controller = this.controller,
 438                              selection = controller.state().get( 'selection' ).first();
 439  
 440                          controller.state().cropperView.remove();
 441  
 442                          // Apply the suggested crop size.
 443                          if ( hasRequiredAspectRatio && !canSkipCrop ) {
 444                              selection.set({cropDetails: suggestedCropSize});
 445                              controller.state().doCrop( selection ).done( function( croppedImage ) {
 446                                  controller.trigger( 'cropped', croppedImage );
 447                                  controller.close();
 448                              }).fail( function() {
 449                                  controller.trigger( 'content:error:crop' );
 450                              });
 451                              return;
 452                          }
 453  
 454                          // Skip the cropping process.
 455                          controller.trigger( 'skippedcrop', selection );
 456                          controller.close();
 457                      }
 458                  }
 459              });
 460          }
 461  
 462          this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
 463      },
 464  
 465      /**
 466       * Creates an object with the image attachment and crop properties.
 467       *
 468       * @since 4.2.0
 469       *
 470       * @param {wp.media.model.Attachment} attachment The image attachment.
 471       * @return {$.promise} A jQuery promise with the custom header crop details.
 472       */
 473      doCrop: function( attachment ) {
 474          return wp.ajax.post( 'custom-header-crop', _.extend(
 475              {},
 476              this.defaults.doCropArgs,
 477              {
 478                  nonce: attachment.get( 'nonces' ).edit,
 479                  id: attachment.get( 'id' ),
 480                  cropDetails: attachment.get( 'cropDetails' )
 481              }
 482          ) );
 483      }
 484  });
 485  
 486  module.exports = Cropper;
 487  
 488  
 489  /***/ },
 490  
 491  /***/ 9660
 492  (module) {
 493  
 494  var Controller = wp.media.controller,
 495      CustomizeImageCropper;
 496  
 497  /**
 498   * A state for cropping an image in the customizer.
 499   *
 500   * @since 4.3.0
 501   *
 502   * @constructs wp.media.controller.CustomizeImageCropper
 503   * @memberOf wp.media.controller
 504   * @augments wp.media.controller.CustomizeImageCropper.Cropper
 505   * @inheritDoc
 506   */
 507  CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{
 508      /**
 509       * Posts the crop details to the admin.
 510       *
 511       * Uses crop measurements when flexible in both directions.
 512       * Constrains flexible side based on image ratio and size of the fixed side.
 513       *
 514       * @since 4.3.0
 515       *
 516       * @param {Object} attachment The attachment to crop.
 517       *
 518       * @return {$.promise} A jQuery promise that represents the crop image request.
 519       */
 520      doCrop: function( attachment ) {
 521          var cropDetails = attachment.get( 'cropDetails' ),
 522              control = this.get( 'control' ),
 523              ratio = cropDetails.width / cropDetails.height;
 524  
 525          // Use crop measurements when flexible in both directions.
 526          if ( control.params.flex_width && control.params.flex_height ) {
 527              cropDetails.dst_width  = cropDetails.width;
 528              cropDetails.dst_height = cropDetails.height;
 529  
 530          // Constrain flexible side based on image ratio and size of the fixed side.
 531          } else {
 532              cropDetails.dst_width  = control.params.flex_width  ? control.params.height * ratio : control.params.width;
 533              cropDetails.dst_height = control.params.flex_height ? control.params.width  / ratio : control.params.height;
 534          }
 535  
 536          return wp.ajax.post( 'crop-image', {
 537              wp_customize: 'on',
 538              nonce: attachment.get( 'nonces' ).edit,
 539              id: attachment.get( 'id' ),
 540              context: control.id,
 541              cropDetails: cropDetails
 542          } );
 543      }
 544  });
 545  
 546  module.exports = CustomizeImageCropper;
 547  
 548  
 549  /***/ },
 550  
 551  /***/ 5663
 552  (module) {
 553  
 554  var l10n = wp.media.view.l10n,
 555      EditImage;
 556  
 557  /**
 558   * wp.media.controller.EditImage
 559   *
 560   * A state for editing (cropping, etc.) an image.
 561   *
 562   * @memberOf wp.media.controller
 563   *
 564   * @class
 565   * @augments wp.media.controller.State
 566   * @augments Backbone.Model
 567   *
 568   * @param {Object}                    attributes                      The attributes hash passed to the state.
 569   * @param {wp.media.model.Attachment} attributes.model                The attachment.
 570   * @param {string}                    [attributes.id=edit-image]      Unique identifier.
 571   * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
 572   * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
 573   * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
 574   * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
 575   * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
 576   */
 577  EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{
 578      defaults: {
 579          id:      'edit-image',
 580          title:   l10n.editImage,
 581          menu:    false,
 582          toolbar: 'edit-image',
 583          content: 'edit-image',
 584          url:     ''
 585      },
 586  
 587      /**
 588       * Activates a frame for editing a featured image.
 589       *
 590       * @since 3.9.0
 591       *
 592       * @return {void}
 593       */
 594      activate: function() {
 595          this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
 596      },
 597  
 598      /**
 599       * Deactivates a frame for editing a featured image.
 600       *
 601       * @since 3.9.0
 602       *
 603       * @return {void}
 604       */
 605      deactivate: function() {
 606          this.frame.off( 'toolbar:render:edit-image' );
 607      },
 608  
 609      /**
 610       * Adds a toolbar with a back button.
 611       *
 612       * When the back button is pressed it checks whether there is a previous state.
 613       * In case there is a previous state it sets that previous state otherwise it
 614       * closes the frame.
 615       *
 616       * @since 3.9.0
 617       *
 618       * @return {void}
 619       */
 620      toolbar: function() {
 621          var frame = this.frame,
 622              lastState = frame.lastState(),
 623              previous = lastState && lastState.id;
 624  
 625          frame.toolbar.set( new wp.media.view.Toolbar({
 626              controller: frame,
 627              items: {
 628                  back: {
 629                      style: 'primary',
 630                      text:     l10n.back,
 631                      priority: 20,
 632                      click:    function() {
 633                          if ( previous ) {
 634                              frame.setState( previous );
 635                          } else {
 636                              frame.close();
 637                          }
 638                      }
 639                  }
 640              }
 641          }) );
 642      }
 643  });
 644  
 645  module.exports = EditImage;
 646  
 647  
 648  /***/ },
 649  
 650  /***/ 4910
 651  (module) {
 652  
 653  var l10n = wp.media.view.l10n,
 654      $ = Backbone.$,
 655      Embed;
 656  
 657  /**
 658   * wp.media.controller.Embed
 659   *
 660   * A state for embedding media from a URL.
 661   *
 662   * @memberOf wp.media.controller
 663   *
 664   * @class
 665   * @augments wp.media.controller.State
 666   * @augments Backbone.Model
 667   *
 668   * @param {Object} attributes                         The attributes hash passed to the state.
 669   * @param {string} [attributes.id=embed]              Unique identifier.
 670   * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
 671   * @param {string} [attributes.content=embed]         Initial mode for the content region.
 672   * @param {string} [attributes.menu=default]          Initial mode for the menu region.
 673   * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
 674   * @param {string} [attributes.menu=false]            Initial mode for the menu region.
 675   * @param {number} [attributes.priority=120]          The priority for the state link in the media menu.
 676   * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
 677   * @param {string} [attributes.url]                   The embed URL.
 678   * @param {Object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
 679   */
 680  Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{
 681      defaults: {
 682          id:       'embed',
 683          title:    l10n.insertFromUrlTitle,
 684          content:  'embed',
 685          menu:     'default',
 686          toolbar:  'main-embed',
 687          priority: 120,
 688          type:     'link',
 689          url:      '',
 690          metadata: {}
 691      },
 692  
 693      // The amount of time used when debouncing the scan.
 694      sensitivity: 400,
 695  
 696      initialize: function(options) {
 697          this.metadata = options.metadata;
 698          this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
 699          this.props = new Backbone.Model( this.metadata || { url: '' });
 700          this.props.on( 'change:url', this.debouncedScan, this );
 701          this.props.on( 'change:url', this.refresh, this );
 702          this.on( 'scan', this.scanImage, this );
 703      },
 704  
 705      /**
 706       * Trigger a scan of the embedded URL's content for metadata required to embed.
 707       *
 708       * @fires wp.media.controller.Embed#scan
 709       */
 710      scan: function() {
 711          var scanners,
 712              embed = this,
 713              attributes = {
 714                  type: 'link',
 715                  scanners: []
 716              };
 717  
 718          /*
 719           * Scan is triggered with the list of `attributes` to set on the
 720           * state, useful for the 'type' attribute and 'scanners' attribute,
 721           * an array of promise objects for asynchronous scan operations.
 722           */
 723          if ( this.props.get('url') ) {
 724              this.trigger( 'scan', attributes );
 725          }
 726  
 727          if ( attributes.scanners.length ) {
 728              scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
 729              scanners.always( function() {
 730                  if ( embed.get('scanners') === scanners ) {
 731                      embed.set( 'loading', false );
 732                  }
 733              });
 734          } else {
 735              attributes.scanners = null;
 736          }
 737  
 738          attributes.loading = !! attributes.scanners;
 739          this.set( attributes );
 740      },
 741      /**
 742       * Try scanning the embed as an image to discover its dimensions.
 743       *
 744       * @param {Object} attributes The attributes for the embed scan.
 745       */
 746      scanImage: function( attributes ) {
 747          var frame = this.frame,
 748              state = this,
 749              url = this.props.get('url'),
 750              image = new Image(),
 751              deferred = $.Deferred();
 752  
 753          attributes.scanners.push( deferred.promise() );
 754  
 755          // Try to load the image and find its width/height.
 756          image.onload = function() {
 757              deferred.resolve();
 758  
 759              if ( state !== frame.state() || url !== state.props.get('url') ) {
 760                  return;
 761              }
 762  
 763              state.set({
 764                  type: 'image'
 765              });
 766  
 767              state.props.set({
 768                  width:  image.width,
 769                  height: image.height
 770              });
 771          };
 772  
 773          image.onerror = deferred.reject;
 774          image.src = url;
 775      },
 776  
 777      refresh: function() {
 778          this.frame.toolbar.get().refresh();
 779      },
 780  
 781      reset: function() {
 782          this.props.clear().set({ url: '' });
 783  
 784          if ( this.active ) {
 785              this.refresh();
 786          }
 787      }
 788  });
 789  
 790  module.exports = Embed;
 791  
 792  
 793  /***/ },
 794  
 795  /***/ 1169
 796  (module) {
 797  
 798  var Attachment = wp.media.model.Attachment,
 799      Library = wp.media.controller.Library,
 800      l10n = wp.media.view.l10n,
 801      FeaturedImage;
 802  
 803  /**
 804   * wp.media.controller.FeaturedImage
 805   *
 806   * A state for selecting a featured image for a post.
 807   *
 808   * @memberOf wp.media.controller
 809   *
 810   * @class
 811   * @augments wp.media.controller.Library
 812   * @augments wp.media.controller.State
 813   * @augments Backbone.Model
 814   *
 815   * @param {Object}                     [attributes]                          The attributes hash passed to the state.
 816   * @param {string}                     [attributes.id=featured-image]        Unique identifier.
 817   * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
 818   * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
 819   *                                                                           If one is not supplied, a collection of all images will be created.
 820   * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
 821   * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
 822   *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
 823   * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
 824   * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
 825   * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
 826   * @param {number}                     [attributes.priority=60]              The priority for the state link in the media menu.
 827   * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
 828   * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
 829   *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
 830   * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
 831   * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
 832   * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
 833   * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
 834   * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
 835   */
 836  FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{
 837      defaults: _.defaults({
 838          id:            'featured-image',
 839          title:         l10n.setFeaturedImageTitle,
 840          multiple:      false,
 841          filterable:    'uploaded',
 842          toolbar:       'featured-image',
 843          priority:      60,
 844          syncSelection: true
 845      }, Library.prototype.defaults ),
 846  
 847      /**
 848       * Initializes the FeaturedImage controller.
 849       *
 850       * @since 3.5.0
 851       */
 852      initialize: function() {
 853          var library, comparator;
 854  
 855          // If we haven't been provided a `library`, create a `Selection`.
 856          if ( ! this.get('library') ) {
 857              this.set( 'library', wp.media.query({ type: 'image' }) );
 858          }
 859  
 860          Library.prototype.initialize.apply( this, arguments );
 861  
 862          library    = this.get('library');
 863          comparator = library.comparator;
 864  
 865          // Overload the library's comparator to push items that are not in
 866          // the mirrored query to the front of the aggregate collection.
 867          library.comparator = function( a, b ) {
 868              var aInQuery = !! this.mirroring.get( a.cid ),
 869                  bInQuery = !! this.mirroring.get( b.cid );
 870  
 871              if ( ! aInQuery && bInQuery ) {
 872                  return -1;
 873              } else if ( aInQuery && ! bInQuery ) {
 874                  return 1;
 875              } else {
 876                  return comparator.apply( this, arguments );
 877              }
 878          };
 879  
 880          // Add all items in the selection to the library, so any featured
 881          // images that are not initially loaded still appear.
 882          library.observe( this.get('selection') );
 883      },
 884  
 885      /**
 886       * Activates the FeaturedImage controller.
 887       *
 888       * @since 3.5.0
 889       */
 890      activate: function() {
 891          this.frame.on( 'open', this.updateSelection, this );
 892  
 893          Library.prototype.activate.apply( this, arguments );
 894      },
 895  
 896      /**
 897       * Deactivates the FeaturedImage controller.
 898       *
 899       * @since 3.5.0
 900       */
 901      deactivate: function() {
 902          this.frame.off( 'open', this.updateSelection, this );
 903  
 904          Library.prototype.deactivate.apply( this, arguments );
 905      },
 906  
 907      /**
 908       * Updates the selection to match the current featured image.
 909       *
 910       * @since 3.5.0
 911       */
 912      updateSelection: function() {
 913          var selection = this.get('selection'),
 914              id = wp.media.view.settings.post.featuredImageId,
 915              attachment;
 916  
 917          if ( '' !== id && -1 !== id ) {
 918              attachment = Attachment.get( id );
 919              attachment.fetch();
 920          }
 921  
 922          selection.reset( attachment ? [ attachment ] : [] );
 923      }
 924  });
 925  
 926  module.exports = FeaturedImage;
 927  
 928  
 929  /***/ },
 930  
 931  /***/ 7127
 932  (module) {
 933  
 934  var Selection = wp.media.model.Selection,
 935      Library = wp.media.controller.Library,
 936      l10n = wp.media.view.l10n,
 937      GalleryAdd;
 938  
 939  /**
 940   * wp.media.controller.GalleryAdd
 941   *
 942   * A state for selecting more images to add to a gallery.
 943   *
 944   * @since 3.5.0
 945   *
 946   * @class
 947   * @augments wp.media.controller.Library
 948   * @augments wp.media.controller.State
 949   * @augments Backbone.Model
 950   *
 951   * @memberof wp.media.controller
 952   *
 953   * @param {Object}                     [attributes]                         The attributes hash passed to the state.
 954   * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
 955   * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
 956   * @param {boolean|string}             [attributes.multiple=add]            Whether multi-select is enabled. Accepts 'add' or true.
 957   *                                                                          When set to true, requires Shift or Cmd/Ctrl to select multiple items.
 958   *                                                                          When set to 'add', allows selecting multiple items by clicking thumbnails.
 959   * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
 960   *                                                                          If one is not supplied, a collection of all images will be created.
 961   * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
 962   *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
 963   * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
 964   * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
 965   *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
 966   * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
 967   * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
 968   * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
 969   * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
 970   * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
 971   * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
 972   * @param {number}                     [attributes.priority=100]            The priority for the state link in the media menu.
 973   * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
 974   *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
 975   */
 976  GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{
 977      defaults: _.defaults({
 978          id:            'gallery-library',
 979          title:         l10n.addToGalleryTitle,
 980          multiple:      'add',
 981          filterable:    'uploaded',
 982          menu:          'gallery',
 983          toolbar:       'gallery-add',
 984          priority:      100,
 985          syncSelection: false
 986      }, Library.prototype.defaults ),
 987  
 988      /**
 989       * Initializes the library. Creates a library of images if a library isn't supplied.
 990       *
 991       * @since 3.5.0
 992       *
 993       * @return {void}
 994       */
 995      initialize: function() {
 996          if ( ! this.get('library') ) {
 997              this.set( 'library', wp.media.query({ type: 'image' }) );
 998          }
 999  
1000          Library.prototype.initialize.apply( this, arguments );
1001      },
1002  
1003      /**
1004       * Activates the library.
1005       *
1006       * Removes all event listeners if in edit mode. Creates a validator to check an attachment.
1007       * Resets library and re-enables event listeners. Activates edit mode. Calls the parent's activate method.
1008       *
1009       * @since 3.5.0
1010       *
1011       * @return {void}
1012       */
1013      activate: function() {
1014          var library = this.get('library'),
1015              edit    = this.frame.state('gallery-edit').get('library');
1016  
1017          if ( this.editLibrary && this.editLibrary !== edit ) {
1018              library.unobserve( this.editLibrary );
1019          }
1020  
1021          /*
1022           * Accept attachments that exist in the original library but
1023           * that do not exist in gallery's library yet.
1024           */
1025          library.validator = function( attachment ) {
1026              return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
1027          };
1028  
1029          /*
1030           * Reset the library to ensure that all attachments are re-added
1031           * to the collection. Do so silently, as calling `observe` will
1032           * trigger the `reset` event.
1033           */
1034          library.reset( library.mirroring.models, { silent: true });
1035          library.observe( edit );
1036          this.editLibrary = edit;
1037  
1038          Library.prototype.activate.apply( this, arguments );
1039      }
1040  });
1041  
1042  module.exports = GalleryAdd;
1043  
1044  
1045  /***/ },
1046  
1047  /***/ 2038
1048  (module) {
1049  
1050  var Library = wp.media.controller.Library,
1051      l10n = wp.media.view.l10n,
1052      GalleryEdit;
1053  
1054  /**
1055   * wp.media.controller.GalleryEdit
1056   *
1057   * A state for editing a gallery's images and settings.
1058   *
1059   * @since 3.5.0
1060   *
1061   * @class
1062   * @augments wp.media.controller.Library
1063   * @augments wp.media.controller.State
1064   * @augments Backbone.Model
1065   *
1066   * @memberOf wp.media.controller
1067   *
1068   * @param {Object}                     [attributes]                       The attributes hash passed to the state.
1069   * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
1070   * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
1071   * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
1072   *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
1073   * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
1074   * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
1075   * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1076   * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
1077   * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
1078   * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
1079   * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1080   * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
1081   * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
1082   * @param {number}                     [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
1083   * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
1084   * @param {number}                     [attributes.priority=60]           The priority for the state link in the media menu.
1085   * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
1086   *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
1087   * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
1088   *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
1089   */
1090  GalleryEdit = Library.extend(/** @lends wp.media.controller.GalleryEdit.prototype */{
1091      defaults: {
1092          id:               'gallery-edit',
1093          title:            l10n.editGalleryTitle,
1094          multiple:         false,
1095          searchable:       false,
1096          sortable:         true,
1097          date:             false,
1098          display:          false,
1099          content:          'browse',
1100          toolbar:          'gallery-edit',
1101          describe:         true,
1102          displaySettings:  true,
1103          dragInfo:         true,
1104          idealColumnWidth: 170,
1105          editing:          false,
1106          priority:         60,
1107          syncSelection:    false
1108      },
1109  
1110      /**
1111       * Initializes the library.
1112       *
1113       * Creates a selection if a library isn't supplied and creates an attachment
1114       * view if no attachment view is supplied.
1115       *
1116       * @since 3.5.0
1117       *
1118       * @return {void}
1119       */
1120      initialize: function() {
1121          // If we haven't been provided a `library`, create a `Selection`.
1122          if ( ! this.get('library') ) {
1123              this.set( 'library', new wp.media.model.Selection() );
1124          }
1125  
1126          // The single `Attachment` view to be used in the `Attachments` view.
1127          if ( ! this.get('AttachmentView') ) {
1128              this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
1129          }
1130  
1131          Library.prototype.initialize.apply( this, arguments );
1132      },
1133  
1134      /**
1135       * Activates the library.
1136       *
1137       * Limits the library to images, watches for uploaded attachments. Watches for
1138       * the browse event on the frame and binds it to gallerySettings.
1139       *
1140       * @since 3.5.0
1141       *
1142       * @return {void}
1143       */
1144      activate: function() {
1145          var library = this.get('library');
1146  
1147          // Limit the library to images only.
1148          library.props.set( 'type', 'image' );
1149  
1150          // Watch for uploaded attachments.
1151          this.get('library').observe( wp.Uploader.queue );
1152  
1153          this.frame.on( 'content:render:browse', this.gallerySettings, this );
1154  
1155          Library.prototype.activate.apply( this, arguments );
1156      },
1157  
1158      /**
1159       * Deactivates the library.
1160       *
1161       * Stops watching for uploaded attachments and browse events.
1162       *
1163       * @since 3.5.0
1164       *
1165       * @return {void}
1166       */
1167      deactivate: function() {
1168          // Stop watching for uploaded attachments.
1169          this.get('library').unobserve( wp.Uploader.queue );
1170  
1171          this.frame.off( 'content:render:browse', this.gallerySettings, this );
1172  
1173          Library.prototype.deactivate.apply( this, arguments );
1174      },
1175  
1176      /**
1177       * Adds the gallery settings to the sidebar and adds a reverse button to the
1178       * toolbar.
1179       *
1180       * @since 3.5.0
1181       *
1182       * @param {wp.media.view.Frame} browser The file browser.
1183       *
1184       * @return {void}
1185       */
1186      gallerySettings: function( browser ) {
1187          if ( ! this.get('displaySettings') ) {
1188              return;
1189          }
1190  
1191          var library = this.get('library');
1192  
1193          if ( ! library || ! browser ) {
1194              return;
1195          }
1196  
1197          library.gallery = library.gallery || new Backbone.Model();
1198  
1199          browser.sidebar.set({
1200              gallery: new wp.media.view.Settings.Gallery({
1201                  controller: this,
1202                  model:      library.gallery,
1203                  priority:   40
1204              })
1205          });
1206  
1207          browser.toolbar.set( 'reverse', {
1208              text:     l10n.reverseOrder,
1209              priority: 80,
1210  
1211              click: function() {
1212                  library.reset( library.toArray().reverse() );
1213              }
1214          });
1215      }
1216  });
1217  
1218  module.exports = GalleryEdit;
1219  
1220  
1221  /***/ },
1222  
1223  /***/ 705
1224  (module) {
1225  
1226  var State = wp.media.controller.State,
1227      Library = wp.media.controller.Library,
1228      l10n = wp.media.view.l10n,
1229      ImageDetails;
1230  
1231  /**
1232   * wp.media.controller.ImageDetails
1233   *
1234   * A state for editing the attachment display settings of an image that's been
1235   * inserted into the editor.
1236   *
1237   * @memberOf wp.media.controller
1238   *
1239   * @class
1240   * @augments wp.media.controller.State
1241   * @augments Backbone.Model
1242   *
1243   * @param {Object}                    [attributes]                       The attributes hash passed to the state.
1244   * @param {string}                    [attributes.id=image-details]      Unique identifier.
1245   * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
1246   * @param {wp.media.model.Attachment} attributes.image                   The image's model.
1247   * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
1248   * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
1249   * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
1250   * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
1251   * @param {boolean}                   [attributes.editing=false]         Unused.
1252   * @param {number}                    [attributes.priority=60]           Unused.
1253   *
1254   * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
1255   *       however this may not do anything.
1256   */
1257  ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototype */{
1258      defaults: _.defaults({
1259          id:       'image-details',
1260          title:    l10n.imageDetailsTitle,
1261          content:  'image-details',
1262          menu:     false,
1263          router:   false,
1264          toolbar:  'image-details',
1265          editing:  false,
1266          priority: 60
1267      }, Library.prototype.defaults ),
1268  
1269      /**
1270       * Initializes the ImageDetails controller.
1271       *
1272       * @since 3.9.0
1273       *
1274       * @param {Object} options Attributes.
1275       */
1276      initialize: function( options ) {
1277          this.image = options.image;
1278          State.prototype.initialize.apply( this, arguments );
1279      },
1280  
1281      /**
1282       * Activates the ImageDetails controller.
1283       *
1284       * @since 3.9.0
1285       */
1286      activate: function() {
1287          this.frame.modal.$el.addClass('image-details');
1288      }
1289  });
1290  
1291  module.exports = ImageDetails;
1292  
1293  
1294  /***/ },
1295  
1296  /***/ 472
1297  (module) {
1298  
1299  var l10n = wp.media.view.l10n,
1300      getUserSetting = window.getUserSetting,
1301      setUserSetting = window.setUserSetting,
1302      Library;
1303  
1304  /**
1305   * wp.media.controller.Library
1306   *
1307   * A state for choosing an attachment or group of attachments from the media library.
1308   *
1309   * @memberOf wp.media.controller
1310   *
1311   * @class
1312   * @augments wp.media.controller.State
1313   * @augments Backbone.Model
1314   * @mixes media.selectionSync
1315   *
1316   * @param {Object}                          [attributes]                         The attributes hash passed to the state.
1317   * @param {string}                          [attributes.id=library]              Unique identifier.
1318   * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
1319   * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
1320   *                                                                               If one is not supplied, a collection of all attachments will be created.
1321   * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
1322   *                                                                               If the 'selection' attribute is a plain JS object,
1323   *                                                                               a Selection will be created using its values as the selection instance's `props` model.
1324   *                                                                               Otherwise, it will copy the library's `props` model.
1325   * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
1326   * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
1327   *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
1328   * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
1329   * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
1330   * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
1331   * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
1332   * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
1333   *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
1334   * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1335   * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
1336   * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1337   * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
1338   * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
1339   */
1340  Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{
1341      defaults: {
1342          id:                 'library',
1343          title:              l10n.mediaLibraryTitle,
1344          multiple:           false,
1345          content:            'upload',
1346          menu:               'default',
1347          router:             'browse',
1348          toolbar:            'select',
1349          searchable:         true,
1350          filterable:         false,
1351          sortable:           true,
1352          autoSelect:         true,
1353          describe:           false,
1354          contentUserSetting: true,
1355          syncSelection:      true
1356      },
1357  
1358      /**
1359       * Initializes the Library controller.
1360       *
1361       * If a library isn't provided, query all media items.
1362       * If a selection instance isn't provided, create one.
1363       *
1364       * @since 3.5.0
1365       */
1366      initialize: function() {
1367          var selection = this.get('selection'),
1368              props;
1369  
1370          if ( ! this.get('library') ) {
1371              this.set( 'library', wp.media.query() );
1372          }
1373  
1374          if ( ! ( selection instanceof wp.media.model.Selection ) ) {
1375              props = selection;
1376  
1377              if ( ! props ) {
1378                  props = this.get('library').props.toJSON();
1379                  props = _.omit( props, 'orderby', 'query' );
1380              }
1381  
1382              this.set( 'selection', new wp.media.model.Selection( null, {
1383                  multiple: this.get('multiple'),
1384                  props: props
1385              }) );
1386          }
1387  
1388          this.resetDisplays();
1389      },
1390  
1391      /**
1392       * Activates the Library controller.
1393       *
1394       * @since 3.5.0
1395       */
1396      activate: function() {
1397          this.syncSelection();
1398  
1399          wp.Uploader.queue.on( 'add', this.uploading, this );
1400  
1401          this.get('selection').on( 'add remove reset', this.refreshContent, this );
1402  
1403          if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
1404              this.frame.on( 'content:activate', this.saveContentMode, this );
1405              this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
1406          }
1407      },
1408  
1409      /**
1410       * Deactivates the Library controller.
1411       *
1412       * @since 3.5.0
1413       */
1414      deactivate: function() {
1415          this.recordSelection();
1416  
1417          this.frame.off( 'content:activate', this.saveContentMode, this );
1418  
1419          // Unbind all event handlers that use this state as the context
1420          // from the selection.
1421          this.get('selection').off( null, null, this );
1422  
1423          wp.Uploader.queue.off( null, null, this );
1424      },
1425  
1426      /**
1427       * Resets the library to its initial state.
1428       *
1429       * @since 3.5.0
1430       */
1431      reset: function() {
1432          this.get('selection').reset();
1433          this.resetDisplays();
1434          this.refreshContent();
1435      },
1436  
1437      /**
1438       * Resets the attachment display settings defaults to the site options.
1439       *
1440       * If site options don't define them, fall back to a persistent user setting.
1441       *
1442       * @since 3.5.0
1443       */
1444      resetDisplays: function() {
1445          var defaultProps = wp.media.view.settings.defaultProps;
1446          this._displays = [];
1447          this._defaultDisplaySettings = {
1448              align: getUserSetting( 'align', defaultProps.align ) || 'none',
1449              size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
1450              link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
1451          };
1452      },
1453  
1454      /**
1455       * Creates a model to represent display settings (alignment, etc.) for an attachment.
1456       *
1457       * @since 3.5.0
1458       *
1459       * @param {wp.media.model.Attachment} attachment The attachment for which to create display settings.
1460       * @return {Backbone.Model} A model representing the display settings for the attachment.
1461       */
1462      display: function( attachment ) {
1463          var displays = this._displays;
1464  
1465          if ( ! displays[ attachment.cid ] ) {
1466              displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
1467          }
1468          return displays[ attachment.cid ];
1469      },
1470  
1471      /**
1472       * Given an attachment, creates attachment display settings properties.
1473       *
1474       * @since 3.6.0
1475       *
1476       * @param {wp.media.model.Attachment} attachment The attachment for which to get the default display settings.
1477       * @return {Object} The default display settings for the attachment.
1478       */
1479      defaultDisplaySettings: function( attachment ) {
1480          var settings = _.clone( this._defaultDisplaySettings );
1481  
1482          settings.canEmbed = this.canEmbed( attachment );
1483          if ( settings.canEmbed ) {
1484              settings.link = 'embed';
1485          } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
1486              settings.link = 'file';
1487          }
1488  
1489          return settings;
1490      },
1491  
1492      /**
1493       * Determines whether an attachment is an image.
1494       *
1495       * @since 4.4.1
1496       *
1497       * @param {wp.media.model.Attachment} attachment The attachment to check.
1498       * @return {boolean} True if the attachment is an image, false otherwise.
1499       */
1500      isImageAttachment: function( attachment ) {
1501          // If uploading, we know the filename but not the mime type.
1502          if ( attachment.get('uploading') ) {
1503              return /\.(jpe?g|png|gif|webp|avif|heic|heif)$/i.test( attachment.get('filename') );
1504          }
1505  
1506          return attachment.get('type') === 'image';
1507      },
1508  
1509      /**
1510       * Determines whether an attachment can be embedded (audio or video).
1511       *
1512       * @since 3.6.0
1513       *
1514       * @param {wp.media.model.Attachment} attachment The attachment to check.
1515       * @return {boolean} True if the attachment can be embedded, false otherwise.
1516       */
1517      canEmbed: function( attachment ) {
1518          // If uploading, we know the filename but not the mime type.
1519          if ( ! attachment.get('uploading') ) {
1520              var type = attachment.get('type');
1521              if ( type !== 'audio' && type !== 'video' ) {
1522                  return false;
1523              }
1524          }
1525  
1526          return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
1527      },
1528  
1529  
1530      /**
1531       * Resets the content mode to the default.
1532       *
1533       * If the state is active, no items are selected, and the current
1534       * content mode is not an option in the state's router (provided
1535       * the state has a router), reset the content mode to the default.
1536       *
1537       * @since 3.5.0
1538       */
1539      refreshContent: function() {
1540          var selection = this.get('selection'),
1541              frame = this.frame,
1542              router = frame.router.get(),
1543              mode = frame.content.mode();
1544  
1545          if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
1546              this.frame.content.render( this.get('content') );
1547          }
1548      },
1549  
1550      /**
1551       * Callback handler when an attachment is uploaded.
1552       *
1553       * Switch to the Media Library if uploaded from the 'Upload Files' tab.
1554       *
1555       * Adds any uploading attachments to the selection.
1556       *
1557       * If the state only supports one attachment to be selected and multiple
1558       * attachments are uploaded, the last attachment in the upload queue will
1559       * be selected.
1560       *
1561       * @since 3.5.0
1562       *
1563       * @param {wp.media.model.Attachment} attachment The attachment that was uploaded.
1564       */
1565      uploading: function( attachment ) {
1566          var content = this.frame.content;
1567  
1568          if ( 'upload' === content.mode() ) {
1569              this.frame.content.mode('browse');
1570          }
1571  
1572          if ( this.get( 'autoSelect' ) ) {
1573              this.get('selection').add( attachment );
1574              this.frame.trigger( 'library:selection:add' );
1575          }
1576      },
1577  
1578      /**
1579       * Persists the mode of the content region as a user setting.
1580       *
1581       * @since 3.5.0
1582       */
1583      saveContentMode: function() {
1584          if ( 'browse' !== this.get('router') ) {
1585              return;
1586          }
1587  
1588          var mode = this.frame.content.mode(),
1589              view = this.frame.router.get();
1590  
1591          if ( view && view.get( mode ) ) {
1592              setUserSetting( 'libraryContent', mode );
1593          }
1594      }
1595  
1596  });
1597  
1598  // Make selectionSync available on any Media Library state.
1599  _.extend( Library.prototype, wp.media.selectionSync );
1600  
1601  module.exports = Library;
1602  
1603  
1604  /***/ },
1605  
1606  /***/ 8065
1607  (module) {
1608  
1609  /**
1610   * wp.media.controller.MediaLibrary
1611   *
1612   * @memberOf wp.media.controller
1613   *
1614   * @class
1615   * @augments wp.media.controller.Library
1616   * @augments wp.media.controller.State
1617   * @augments Backbone.Model
1618   */
1619  var Library = wp.media.controller.Library,
1620      MediaLibrary;
1621  
1622  MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{
1623      defaults: _.defaults({
1624          // Attachments browser defaults. @see media.view.AttachmentsBrowser
1625          filterable:      'uploaded',
1626  
1627          displaySettings: false,
1628          priority:        80,
1629          syncSelection:   false
1630      }, Library.prototype.defaults ),
1631  
1632      /**
1633       * Initializes the MediaLibrary controller.
1634       *
1635       * @since 3.9.0
1636       *
1637       * @param {Object} options Attributes.
1638       */
1639      initialize: function( options ) {
1640          this.media = options.media;
1641          this.type = options.type;
1642          this.set( 'library', wp.media.query({ type: this.type }) );
1643  
1644          Library.prototype.initialize.apply( this, arguments );
1645      },
1646  
1647      /**
1648       * Activates the MediaLibrary controller.
1649       *
1650       * @since 3.9.0
1651       */
1652      activate: function() {
1653          // @todo this should use this.frame.
1654          if ( wp.media.frame.lastMime ) {
1655              this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
1656              delete wp.media.frame.lastMime;
1657          }
1658          Library.prototype.activate.apply( this, arguments );
1659      }
1660  });
1661  
1662  module.exports = MediaLibrary;
1663  
1664  
1665  /***/ },
1666  
1667  /***/ 9875
1668  (module) {
1669  
1670  /**
1671   * wp.media.controller.Region
1672   *
1673   * A region is a persistent application layout area.
1674   *
1675   * A region assumes one mode at any time, and can be switched to another.
1676   *
1677   * When mode changes, events are triggered on the region's parent view.
1678   * The parent view will listen to specific events and fill the region with an
1679   * appropriate view depending on mode. For example, a frame listens for the
1680   * 'browse' mode t be activated on the 'content' view and then fills the region
1681   * with an AttachmentsBrowser view.
1682   *
1683   * @memberOf wp.media.controller
1684   *
1685   * @class
1686   *
1687   * @param {Object}        options          Options hash for the region.
1688   * @param {string}        options.id       Unique identifier for the region.
1689   * @param {Backbone.View} options.view     A parent view the region exists within.
1690   * @param {string}        options.selector jQuery selector for the region within the parent view.
1691   */
1692  var Region = function( options ) {
1693      _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
1694  };
1695  
1696  // Use Backbone's self-propagating `extend` inheritance method.
1697  Region.extend = Backbone.Model.extend;
1698  
1699  _.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{
1700      /**
1701       * Activate a mode.
1702       *
1703       * @since 3.5.0
1704       *
1705       * @param {string} mode The mode to activate on the region.
1706       *
1707       * @fires Region#activate
1708       * @fires Region#deactivate
1709       *
1710       * @return {wp.media.controller.Region} Returns itself to allow chaining.
1711       */
1712      mode: function( mode ) {
1713          if ( ! mode ) {
1714              return this._mode;
1715          }
1716          // Bail if we're trying to change to the current mode.
1717          if ( mode === this._mode ) {
1718              return this;
1719          }
1720  
1721          /**
1722           * Region mode deactivation event.
1723           *
1724           * @event wp.media.controller.Region#deactivate
1725           */
1726          this.trigger('deactivate');
1727  
1728          this._mode = mode;
1729          this.render( mode );
1730  
1731          /**
1732           * Region mode activation event.
1733           *
1734           * @event wp.media.controller.Region#activate
1735           */
1736          this.trigger('activate');
1737          return this;
1738      },
1739      /**
1740       * Render a mode.
1741       *
1742       * @since 3.5.0
1743       *
1744       * @param {string} mode The mode to render on the region.
1745       *
1746       * @fires Region#create
1747       * @fires Region#render
1748       *
1749       * @return {wp.media.controller.Region} Returns itself to allow chaining.
1750       */
1751      render: function( mode ) {
1752          // If the mode isn't active, activate it.
1753          if ( mode && mode !== this._mode ) {
1754              return this.mode( mode );
1755          }
1756  
1757          var set = { view: null },
1758              view;
1759  
1760          /**
1761           * Create region view event.
1762           *
1763           * Region view creation takes place in an event callback on the frame.
1764           *
1765           * @event wp.media.controller.Region#create
1766           * @type {Object}
1767           * @property {Object} view The view object for the region.
1768           */
1769          this.trigger( 'create', set );
1770          view = set.view;
1771  
1772          /**
1773           * Render region view event.
1774           *
1775           * Region view creation takes place in an event callback on the frame.
1776           *
1777           * @event wp.media.controller.Region#render
1778           * @type {Object}
1779           */
1780          this.trigger( 'render', view );
1781          if ( view ) {
1782              this.set( view );
1783          }
1784          return this;
1785      },
1786  
1787      /**
1788       * Get the region's view.
1789       *
1790       * @since 3.5.0
1791       *
1792       * @return {wp.media.View} Returns the region's view.
1793       */
1794      get: function() {
1795          return this.view.views.first( this.selector );
1796      },
1797  
1798      /**
1799       * Set the region's view as a subview of the frame.
1800       *
1801       * @since 3.5.0
1802       *
1803       * @param {Array|Object} views        The views to set on the region.
1804       * @param {Object}       [options={}] Optional. Additional options for setting the views on the region.
1805       * @return {wp.Backbone.Subviews} Subviews is returned to allow chaining.
1806       */
1807      set: function( views, options ) {
1808          if ( options ) {
1809              options.add = false;
1810          }
1811          return this.view.views.set( this.selector, views, options );
1812      },
1813  
1814      /**
1815       * Trigger regional view events on the frame.
1816       *
1817       * @since 3.5.0
1818       *
1819       * @param {string} event The event to trigger on the region.
1820       * @return {undefined|wp.media.controller.Region} Returns itself to allow chaining.
1821       */
1822      trigger: function( event ) {
1823          var base, args;
1824  
1825          if ( ! this._mode ) {
1826              return;
1827          }
1828  
1829          args = _.toArray( arguments );
1830          base = this.id + ':' + event;
1831  
1832          // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
1833          args[0] = base + ':' + this._mode;
1834          this.view.trigger.apply( this.view, args );
1835  
1836          // Trigger `{this.id}:{event}` event on the frame.
1837          args[0] = base;
1838          this.view.trigger.apply( this.view, args );
1839          return this;
1840      }
1841  });
1842  
1843  module.exports = Region;
1844  
1845  
1846  /***/ },
1847  
1848  /***/ 2275
1849  (module) {
1850  
1851  var Library = wp.media.controller.Library,
1852      l10n = wp.media.view.l10n,
1853      ReplaceImage;
1854  
1855  /**
1856   * wp.media.controller.ReplaceImage
1857   *
1858   * A state for replacing an image.
1859   *
1860   * @memberOf wp.media.controller
1861   *
1862   * @class
1863   * @augments wp.media.controller.Library
1864   * @augments wp.media.controller.State
1865   * @augments Backbone.Model
1866   *
1867   * @param {Object}                     [attributes]                         The attributes hash passed to the state.
1868   * @param {string}                     [attributes.id=replace-image]        Unique identifier.
1869   * @param {string}                     [attributes.title=Replace Image]     Title for the state. Displays in the media menu and the frame's title region.
1870   * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
1871   *                                                                          If one is not supplied, a collection of all images will be created.
1872   * @param {boolean}                    [attributes.multiple=false]          Whether multi-select is enabled.
1873   * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
1874   *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
1875   * @param {string}                     [attributes.menu=default]            Initial mode for the menu region.
1876   * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
1877   * @param {string}                     [attributes.toolbar=replace]         Initial mode for the toolbar region.
1878   * @param {number}                     [attributes.priority=60]             The priority for the state link in the media menu.
1879   * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
1880   * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
1881   *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
1882   * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1883   * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
1884   * @param {boolean}                    [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1885   * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
1886   * @param {boolean}                    [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
1887   */
1888  ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.prototype */{
1889      defaults: _.defaults({
1890          id:            'replace-image',
1891          title:         l10n.replaceImageTitle,
1892          multiple:      false,
1893          filterable:    'uploaded',
1894          toolbar:       'replace',
1895          menu:          false,
1896          priority:      60,
1897          syncSelection: true
1898      }, Library.prototype.defaults ),
1899  
1900      /**
1901       * Initializes the ReplaceImage controller.
1902       *
1903       * @since 3.9.0
1904       *
1905       * @param {Object} options Attributes.
1906       */
1907      initialize: function( options ) {
1908          var library, comparator;
1909  
1910          this.image = options.image;
1911          // If we haven't been provided a `library`, create a `Selection`.
1912          if ( ! this.get('library') ) {
1913              this.set( 'library', wp.media.query({ type: 'image' }) );
1914          }
1915  
1916          Library.prototype.initialize.apply( this, arguments );
1917  
1918          library    = this.get('library');
1919          comparator = library.comparator;
1920  
1921          // Overload the library's comparator to push items that are not in
1922          // the mirrored query to the front of the aggregate collection.
1923          library.comparator = function( a, b ) {
1924              var aInQuery = !! this.mirroring.get( a.cid ),
1925                  bInQuery = !! this.mirroring.get( b.cid );
1926  
1927              if ( ! aInQuery && bInQuery ) {
1928                  return -1;
1929              } else if ( aInQuery && ! bInQuery ) {
1930                  return 1;
1931              } else {
1932                  return comparator.apply( this, arguments );
1933              }
1934          };
1935  
1936          // Add all items in the selection to the library, so any featured
1937          // images that are not initially loaded still appear.
1938          library.observe( this.get('selection') );
1939      },
1940  
1941      /**
1942       * Activates the ReplaceImage controller.
1943       *
1944       * @since 3.9.0
1945       */
1946      activate: function() {
1947          this.frame.on( 'content:render:browse', this.updateSelection, this );
1948  
1949          Library.prototype.activate.apply( this, arguments );
1950      },
1951  
1952      /**
1953       * Deactivates the ReplaceImage controller.
1954       *
1955       * @since 5.9.0
1956       */
1957      deactivate: function() {
1958          this.frame.off( 'content:render:browse', this.updateSelection, this );
1959  
1960          Library.prototype.deactivate.apply( this, arguments );
1961      },
1962  
1963      /**
1964       * Updates the selection to match the current image.
1965       *
1966       * @since 3.9.0
1967       */
1968      updateSelection: function() {
1969          var selection = this.get('selection'),
1970              attachment = this.image.attachment;
1971  
1972          selection.reset( attachment ? [ attachment ] : [] );
1973      }
1974  });
1975  
1976  module.exports = ReplaceImage;
1977  
1978  
1979  /***/ },
1980  
1981  /***/ 6172
1982  (module) {
1983  
1984  var Controller = wp.media.controller,
1985      SiteIconCropper;
1986  
1987  /**
1988   * wp.media.controller.SiteIconCropper
1989   *
1990   * A state for cropping a Site Icon.
1991   *
1992   * @memberOf wp.media.controller
1993   *
1994   * @class
1995   * @augments wp.media.controller.Cropper
1996   * @augments wp.media.controller.State
1997   * @augments Backbone.Model
1998   */
1999  SiteIconCropper = Controller.Cropper.extend(/** @lends wp.media.controller.SiteIconCropper.prototype */{
2000      activate: function() {
2001          this.frame.on( 'content:create:crop', this.createCropContent, this );
2002          this.frame.on( 'close', this.removeCropper, this );
2003          this.set('selection', new Backbone.Collection(this.frame._selection.single));
2004      },
2005  
2006      createCropContent: function() {
2007          this.cropperView = new wp.media.view.SiteIconCropper({
2008              controller: this,
2009              attachment: this.get('selection').first()
2010          });
2011          this.cropperView.on('image-loaded', this.createCropToolbar, this);
2012          this.frame.content.set(this.cropperView);
2013  
2014      },
2015  
2016      doCrop: function( attachment ) {
2017          var cropDetails = attachment.get( 'cropDetails' ),
2018              control = this.get( 'control' );
2019  
2020          cropDetails.dst_width  = control.params.width;
2021          cropDetails.dst_height = control.params.height;
2022  
2023          return wp.ajax.post( 'crop-image', {
2024              nonce: attachment.get( 'nonces' ).edit,
2025              id: attachment.get( 'id' ),
2026              context: 'site-icon',
2027              cropDetails: cropDetails
2028          } );
2029      }
2030  });
2031  
2032  module.exports = SiteIconCropper;
2033  
2034  
2035  /***/ },
2036  
2037  /***/ 6150
2038  (module) {
2039  
2040  /**
2041   * wp.media.controller.StateMachine
2042   *
2043   * A state machine keeps track of state. It is in one state at a time,
2044   * and can change from one state to another.
2045   *
2046   * States are stored as models in a Backbone collection.
2047   *
2048   * @memberOf wp.media.controller
2049   *
2050   * @since 3.5.0
2051   *
2052   * @class
2053   * @augments Backbone.Model
2054   * @mixin
2055   * @mixes Backbone.Events
2056   */
2057  var StateMachine = function() {
2058      return {
2059          // Use Backbone's self-propagating `extend` inheritance method.
2060          extend: Backbone.Model.extend
2061      };
2062  };
2063  
2064  _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{
2065      /**
2066       * Fetch a state.
2067       *
2068       * If no `id` is provided, returns the active state.
2069       *
2070       * Implicitly creates states.
2071       *
2072       * Ensure that the `states` collection exists so the `StateMachine`
2073       * can be used as a mixin.
2074       *
2075       * @since 3.5.0
2076       *
2077       * @param {string} id The ID of the state to fetch. If omitted, the active state is returned.
2078       * @return {wp.media.controller.State} Returns a State model from
2079       *                                     the StateMachine collection.
2080       */
2081      state: function( id ) {
2082          this.states = this.states || new Backbone.Collection();
2083  
2084          // Default to the active state.
2085          id = id || this._state;
2086  
2087          if ( id && ! this.states.get( id ) ) {
2088              this.states.add({ id: id });
2089          }
2090          return this.states.get( id );
2091      },
2092  
2093      /**
2094       * Sets the active state.
2095       *
2096       * Bail if we're trying to select the current state, if we haven't
2097       * created the `states` collection, or are trying to select a state
2098       * that does not exist.
2099       *
2100       * @since 3.5.0
2101       *
2102       * @param {string} id The ID of the state to set as active.
2103       *
2104       * @fires wp.media.controller.State#deactivate
2105       * @fires wp.media.controller.State#activate
2106       *
2107       * @return {wp.media.controller.StateMachine} Returns itself to allow chaining.
2108       */
2109      setState: function( id ) {
2110          var previous = this.state();
2111  
2112          if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
2113              return this;
2114          }
2115  
2116          if ( previous ) {
2117              previous.trigger('deactivate');
2118              this._lastState = previous.id;
2119          }
2120  
2121          this._state = id;
2122          this.state().trigger('activate');
2123  
2124          return this;
2125      },
2126  
2127      /**
2128       * Returns the previous active state.
2129       *
2130       * Call the `state()` method with no parameters to retrieve the current
2131       * active state.
2132       *
2133       * @since 3.5.0
2134       *
2135       * @return {void|wp.media.controller.State} Returns a State model from
2136       *                                          the StateMachine collection.
2137       */
2138      lastState: function() {
2139          if ( this._lastState ) {
2140              return this.state( this._lastState );
2141          }
2142      }
2143  });
2144  
2145  // Map all event binding and triggering on a StateMachine to its `states` collection.
2146  _.each([ 'on', 'off', 'trigger' ], function( method ) {
2147      /**
2148       * Binds an event listener to events in the StateMachine's states collection.
2149       *
2150       * @function on
2151       * @memberOf wp.media.controller.StateMachine
2152       * @instance
2153       * @return {wp.media.controller.StateMachine} Returns itself to allow chaining.
2154       */
2155      /**
2156       * Unbinds an event listener from the StateMachine's states collection.
2157       *
2158       * @function off
2159       * @memberOf wp.media.controller.StateMachine
2160       * @instance
2161       * @return {wp.media.controller.StateMachine} Returns itself to allow chaining.
2162       */
2163      /**
2164       * Triggers an event on the StateMachine's states collection.
2165       *
2166       * @function trigger
2167       * @memberOf wp.media.controller.StateMachine
2168       * @instance
2169       * @return {wp.media.controller.StateMachine} Returns itself to allow chaining.
2170       */
2171      StateMachine.prototype[ method ] = function() {
2172          // Ensure that the `states` collection exists so the `StateMachine`
2173          // can be used as a mixin.
2174          this.states = this.states || new Backbone.Collection();
2175          // Forward the method to the `states` collection.
2176          this.states[ method ].apply( this.states, arguments );
2177          return this;
2178      };
2179  });
2180  
2181  module.exports = StateMachine;
2182  
2183  
2184  /***/ },
2185  
2186  /***/ 5694
2187  (module) {
2188  
2189  /**
2190   * wp.media.controller.State
2191   *
2192   * A state is a step in a workflow that when set will trigger the controllers
2193   * for the regions to be updated as specified in the frame.
2194   *
2195   * A state has an event-driven lifecycle:
2196   *
2197   * 'ready'      triggers when a state is added to a state machine's collection.
2198   * 'activate'   triggers when a state is activated by a state machine.
2199   * 'deactivate' triggers when a state is deactivated by a state machine.
2200   * 'reset'      is not triggered automatically. It should be invoked by the
2201   * proper controller to reset the state to its default.
2202   *
2203   * @memberOf wp.media.controller
2204   *
2205   * @class
2206   * @augments Backbone.Model
2207   */
2208  var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{
2209      /**
2210       * Constructor.
2211       *
2212       * @since 3.5.0
2213       */
2214      constructor: function() {
2215          this.on( 'activate', this._preActivate, this );
2216          this.on( 'activate', this.activate, this );
2217          this.on( 'activate', this._postActivate, this );
2218          this.on( 'deactivate', this._deactivate, this );
2219          this.on( 'deactivate', this.deactivate, this );
2220          this.on( 'reset', this.reset, this );
2221          this.on( 'ready', this._ready, this );
2222          this.on( 'ready', this.ready, this );
2223          /**
2224           * Call parent constructor with passed arguments
2225           */
2226          Backbone.Model.apply( this, arguments );
2227          this.on( 'change:menu', this._updateMenu, this );
2228      },
2229      /**
2230       * Ready event callback.
2231       *
2232       * @abstract
2233       * @since 3.5.0
2234       */
2235      ready: function() {},
2236  
2237      /**
2238       * Activate event callback.
2239       *
2240       * @abstract
2241       * @since 3.5.0
2242       */
2243      activate: function() {},
2244  
2245      /**
2246       * Deactivate event callback.
2247       *
2248       * @abstract
2249       * @since 3.5.0
2250       */
2251      deactivate: function() {},
2252  
2253      /**
2254       * Reset event callback.
2255       *
2256       * @abstract
2257       * @since 3.5.0
2258       */
2259      reset: function() {},
2260  
2261      /**
2262       * Ready event callback.
2263       *
2264       * @since 3.5.0
2265       * @access private
2266       */
2267      _ready: function() {
2268          this._updateMenu();
2269      },
2270  
2271      /**
2272       * Pre-activate event callback.
2273       *
2274       * @since 3.5.0
2275       * @access private
2276       */
2277      _preActivate: function() {
2278          this.active = true;
2279      },
2280  
2281      /**
2282       * Post-activate event callback.
2283       *
2284       * @since 3.5.0
2285       * @access private
2286       */
2287      _postActivate: function() {
2288          this.on( 'change:menu', this._menu, this );
2289          this.on( 'change:titleMode', this._title, this );
2290          this.on( 'change:content', this._content, this );
2291          this.on( 'change:toolbar', this._toolbar, this );
2292  
2293          this.frame.on( 'title:render:default', this._renderTitle, this );
2294  
2295          this._title();
2296          this._menu();
2297          this._toolbar();
2298          this._content();
2299          this._router();
2300      },
2301  
2302      /**
2303       * Deactivate event callback.
2304       *
2305       * @since 3.5.0
2306       * @access private
2307       */
2308      _deactivate: function() {
2309          this.active = false;
2310  
2311          this.frame.off( 'title:render:default', this._renderTitle, this );
2312  
2313          this.off( 'change:menu', this._menu, this );
2314          this.off( 'change:titleMode', this._title, this );
2315          this.off( 'change:content', this._content, this );
2316          this.off( 'change:toolbar', this._toolbar, this );
2317      },
2318  
2319      /**
2320       * Renders the frame's title using the titleMode property.
2321       *
2322       *
2323       * @since 3.5.0
2324       * @access private
2325       */
2326      _title: function() {
2327          this.frame.title.render( this.get('titleMode') || 'default' );
2328      },
2329  
2330      /**
2331       * Renders the title in the media frame.
2332       *
2333       * @param {media.view.Title} view The title view.
2334       * @since 3.5.0
2335       * @access private
2336       */
2337      _renderTitle: function( view ) {
2338          view.$el.text( this.get('title') || '' );
2339      },
2340  
2341      /**
2342       * Renders and manages the router region.
2343       *
2344       * @since 3.5.0
2345       * @access private
2346       */
2347      _router: function() {
2348          var router = this.frame.router,
2349              mode = this.get('router'),
2350              view;
2351  
2352          this.frame.$el.toggleClass( 'hide-router', ! mode );
2353          if ( ! mode ) {
2354              return;
2355          }
2356  
2357          this.frame.router.render( mode );
2358  
2359          view = router.get();
2360          if ( view && view.select ) {
2361              view.select( this.frame.content.mode() );
2362          }
2363      },
2364  
2365      /**
2366       * Renders and manages the menu region.
2367       *
2368       * @since 3.5.0
2369       * @access private
2370       */
2371      _menu: function() {
2372          var menu = this.frame.menu,
2373              mode = this.get('menu'),
2374              actionMenuItems,
2375              actionMenuLength,
2376              view;
2377  
2378          if ( this.frame.menu ) {
2379              actionMenuItems = this.frame.menu.get('views'),
2380              actionMenuLength = actionMenuItems ? actionMenuItems.views.get().length : 0,
2381              // Show action menu only if it is active and has more than one default element.
2382              this.frame.$el.toggleClass( 'hide-menu', ! mode || actionMenuLength < 2 );
2383          }
2384          if ( ! mode ) {
2385              return;
2386          }
2387  
2388          menu.mode( mode );
2389  
2390          view = menu.get();
2391          if ( view && view.select ) {
2392              view.select( this.id );
2393          }
2394      },
2395  
2396      /**
2397       * Updates the menu.
2398       *
2399       * @since 3.5.0
2400       * @access private
2401       */
2402      _updateMenu: function() {
2403          var previous = this.previous('menu'),
2404              menu = this.get('menu');
2405  
2406          if ( previous ) {
2407              this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
2408          }
2409  
2410          if ( menu ) {
2411              this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
2412          }
2413      },
2414  
2415      /**
2416       * Creates a view in the media menu for the state.
2417       *
2418       * @since 3.5.0
2419       * @access private
2420       *
2421       * @param {media.view.Menu} view The menu view.
2422       */
2423      _renderMenu: function( view ) {
2424          var menuItem = this.get('menuItem'),
2425              title = this.get('title'),
2426              priority = this.get('priority');
2427  
2428          if ( ! menuItem && title ) {
2429              menuItem = { text: title };
2430  
2431              if ( priority ) {
2432                  menuItem.priority = priority;
2433              }
2434          }
2435  
2436          if ( ! menuItem ) {
2437              return;
2438          }
2439  
2440          view.set( this.id, menuItem );
2441      }
2442  });
2443  
2444  /**
2445   * Creates render methods for frame regions.
2446   */
2447  _.each(['toolbar','content'], function( region ) {
2448      /**
2449       * Renders the region in the media frame.
2450       *
2451       * @access private
2452       */
2453      State.prototype[ '_' + region ] = function() {
2454          var mode = this.get( region );
2455          if ( mode ) {
2456              this.frame[ region ].render( mode );
2457          }
2458      };
2459  });
2460  
2461  module.exports = State;
2462  
2463  
2464  /***/ },
2465  
2466  /***/ 4181
2467  (module) {
2468  
2469  /**
2470   * wp.media.selectionSync
2471   *
2472   * Sync an attachments selection in a state with another state.
2473   *
2474   * Allows for selecting multiple images in the Add Media workflow, and then
2475   * switching to the Insert Gallery workflow while preserving the attachments selection.
2476   *
2477   * @memberOf wp.media
2478   *
2479   * @mixin
2480   */
2481  var selectionSync = {
2482      /**
2483       * Syncs the selection in this state with the master selection.
2484       *
2485       * @since 3.5.0
2486       */
2487      syncSelection: function() {
2488          var selection = this.get('selection'),
2489              manager = this.frame._selection;
2490  
2491          if ( ! this.get('syncSelection') || ! manager || ! selection ) {
2492              return;
2493          }
2494  
2495          /*
2496           * If the selection supports multiple items, validate the stored
2497           * attachments based on the new selection's conditions. Record
2498           * the attachments that are not included; we'll maintain a
2499           * reference to those. Other attachments are considered in flux.
2500           */
2501          if ( selection.multiple ) {
2502              selection.reset( [], { silent: true });
2503              selection.validateAll( manager.attachments );
2504              manager.difference = _.difference( manager.attachments.models, selection.models );
2505          }
2506  
2507          // Sync the selection's single item with the master.
2508          selection.single( manager.single );
2509      },
2510  
2511      /**
2512       * Record the currently active attachments, which is a combination
2513       * of the selection's attachments and the set of selected
2514       * attachments that this specific selection considered invalid.
2515       * Reset the difference and record the single attachment.
2516       *
2517       * @since 3.5.0
2518       */
2519      recordSelection: function() {
2520          var selection = this.get('selection'),
2521              manager = this.frame._selection;
2522  
2523          if ( ! this.get('syncSelection') || ! manager || ! selection ) {
2524              return;
2525          }
2526  
2527          if ( selection.multiple ) {
2528              manager.attachments.reset( selection.toArray().concat( manager.difference ) );
2529              manager.difference = [];
2530          } else {
2531              manager.attachments.add( selection.toArray() );
2532          }
2533  
2534          manager.single = selection._single;
2535      }
2536  };
2537  
2538  module.exports = selectionSync;
2539  
2540  
2541  /***/ },
2542  
2543  /***/ 2982
2544  (module) {
2545  
2546  var View = wp.media.View,
2547      AttachmentCompat;
2548  
2549  /**
2550   * wp.media.view.AttachmentCompat
2551   *
2552   * A view to display fields added via the `attachment_fields_to_edit` filter.
2553   *
2554   * @memberOf wp.media.view
2555   *
2556   * @class
2557   * @augments wp.media.View
2558   * @augments wp.Backbone.View
2559   * @augments Backbone.View
2560   */
2561  AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{
2562      tagName:   'form',
2563      className: 'compat-item',
2564  
2565      events: {
2566          'submit':          'preventDefault',
2567          'change input':    'save',
2568          'change select':   'save',
2569          'change textarea': 'save'
2570      },
2571  
2572      initialize: function() {
2573          // Render the view when a new item is added.
2574          this.listenTo( this.model, 'add', this.render );
2575      },
2576  
2577      /**
2578       * Disposes of the view and its children.
2579       *
2580       * @return {wp.media.view.AttachmentCompat} Returns itself to allow chaining.
2581       */
2582      dispose: function() {
2583          if ( this.$(':focus').length ) {
2584              this.save();
2585          }
2586          /**
2587           * call 'dispose' directly on the parent class
2588           */
2589          return View.prototype.dispose.apply( this, arguments );
2590      },
2591      /**
2592       * Renders the view.
2593       *
2594       * @return {void|wp.media.view.AttachmentCompat} Returns itself to allow chaining.
2595       */
2596      render: function() {
2597          var compat = this.model.get('compat');
2598          if ( ! compat || ! compat.item ) {
2599              return;
2600          }
2601  
2602          this.views.detach();
2603          this.$el.html( compat.item );
2604          this.views.render();
2605          return this;
2606      },
2607      /**
2608       * Prevents the default action of the event.
2609       *
2610       * @param {JQuery.Event} event The jQuery event object associated with the action.
2611       */
2612      preventDefault: function( event ) {
2613          event.preventDefault();
2614      },
2615      /**
2616       * Saves the attachment compat data.
2617       *
2618       * @param {JQuery.Event} event The jQuery event object associated with the action.
2619       */
2620      save: function( event ) {
2621          var data = {};
2622  
2623          if ( event ) {
2624              event.preventDefault();
2625          }
2626  
2627          _.each( this.$el.serializeArray(), function( pair ) {
2628              data[ pair.name ] = pair.value;
2629          });
2630  
2631          this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
2632          this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
2633      },
2634  
2635      /**
2636       * Triggers the `attachment:compat:ready` event on the controller after saving the compat data.
2637       */
2638      postSave: function() {
2639          this.controller.trigger( 'attachment:compat:ready', ['ready'] );
2640      }
2641  });
2642  
2643  module.exports = AttachmentCompat;
2644  
2645  
2646  /***/ },
2647  
2648  /***/ 7709
2649  (module) {
2650  
2651  var $ = jQuery,
2652      AttachmentFilters;
2653  
2654  /**
2655   * wp.media.view.AttachmentFilters
2656   *
2657   * @memberOf wp.media.view
2658   *
2659   * @class
2660   * @augments wp.media.View
2661   * @augments wp.Backbone.View
2662   * @augments Backbone.View
2663   */
2664  AttachmentFilters = wp.media.View.extend(/** @lends wp.media.view.AttachmentFilters.prototype */{
2665      tagName:   'select',
2666      className: 'attachment-filters',
2667      id:        'media-attachment-filters',
2668  
2669      events: {
2670          change: 'change'
2671      },
2672  
2673      keys: [],
2674  
2675      initialize: function() {
2676          this.createFilters();
2677          _.extend( this.filters, this.options.filters );
2678  
2679          // Build `<option>` elements.
2680          this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
2681              return {
2682                  el: $( '<option></option>' ).val( value ).html( filter.text )[0],
2683                  priority: filter.priority || 50
2684              };
2685          }, this ).sortBy('priority').pluck('el').value() );
2686  
2687          this.listenTo( this.model, 'change', this.select );
2688          this.select();
2689      },
2690  
2691      /**
2692       * Creates the filters for the view.
2693       *
2694       * @abstract
2695       */
2696      createFilters: function() {
2697          this.filters = {};
2698      },
2699  
2700      /**
2701       * Updates the Attachment Query properties to match when the selected filter changes.
2702       */
2703      change: function() {
2704          var filter = this.filters[ this.el.value ];
2705          if ( filter ) {
2706              this.model.set( filter.props );
2707          }
2708      },
2709  
2710      /**
2711       * Selects the filter based on the Attachment Query properties.
2712       */
2713      select: function() {
2714          var model = this.model,
2715              value = 'all',
2716              props = model.toJSON();
2717  
2718          _.find( this.filters, function( filter, id ) {
2719              var equal = _.all( filter.props, function( prop, key ) {
2720                  return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
2721              });
2722  
2723              if ( equal ) {
2724                  return value = id;
2725              }
2726          });
2727  
2728          this.$el.val( value );
2729      }
2730  });
2731  
2732  module.exports = AttachmentFilters;
2733  
2734  
2735  /***/ },
2736  
2737  /***/ 7349
2738  (module) {
2739  
2740  var l10n = wp.media.view.l10n,
2741      All;
2742  
2743  /**
2744   * wp.media.view.AttachmentFilters.All
2745   *
2746   * @memberOf wp.media.view.AttachmentFilters
2747   *
2748   * @class
2749   * @augments wp.media.view.AttachmentFilters
2750   * @augments wp.media.View
2751   * @augments wp.Backbone.View
2752   * @augments Backbone.View
2753   */
2754  All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{
2755      createFilters: function() {
2756          var filters = {},
2757              uid = window.userSettings ? parseInt( window.userSettings.uid, 10 ) : 0;
2758  
2759          _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
2760              filters[ key ] = {
2761                  text: text,
2762                  props: {
2763                      status:  null,
2764                      type:    key,
2765                      uploadedTo: null,
2766                      orderby: 'date',
2767                      order:   'DESC',
2768                      author:  null
2769                  }
2770              };
2771          });
2772  
2773          filters.all = {
2774              text:  l10n.allMediaItems,
2775              props: {
2776                  status:  null,
2777                  type:    null,
2778                  uploadedTo: null,
2779                  orderby: 'date',
2780                  order:   'DESC',
2781                  author:  null
2782              },
2783              priority: 10
2784          };
2785  
2786          if ( wp.media.view.settings.post.id ) {
2787              filters.uploaded = {
2788                  text:  l10n.uploadedToThisPost,
2789                  props: {
2790                      status:  null,
2791                      type:    null,
2792                      uploadedTo: wp.media.view.settings.post.id,
2793                      orderby: 'menuOrder',
2794                      order:   'ASC',
2795                      author:  null
2796                  },
2797                  priority: 20
2798              };
2799          }
2800  
2801          filters.unattached = {
2802              text:  l10n.unattached,
2803              props: {
2804                  status:     null,
2805                  uploadedTo: 0,
2806                  type:       null,
2807                  orderby:    'menuOrder',
2808                  order:      'ASC',
2809                  author:     null
2810              },
2811              priority: 50
2812          };
2813  
2814          if ( uid ) {
2815              filters.mine = {
2816                  text:  l10n.mine,
2817                  props: {
2818                      status:        null,
2819                      type:        null,
2820                      uploadedTo:    null,
2821                      orderby:    'date',
2822                      order:        'DESC',
2823                      author:        uid
2824                  },
2825                  priority: 50
2826              };
2827          }
2828  
2829          if ( wp.media.view.settings.mediaTrash &&
2830              this.controller.isModeActive( 'grid' ) ) {
2831  
2832              filters.trash = {
2833                  text:  l10n.trash,
2834                  props: {
2835                      uploadedTo: null,
2836                      status:     'trash',
2837                      type:       null,
2838                      orderby:    'date',
2839                      order:      'DESC',
2840                      author:     null
2841                  },
2842                  priority: 50
2843              };
2844          }
2845  
2846          this.filters = filters;
2847      }
2848  });
2849  
2850  module.exports = All;
2851  
2852  
2853  /***/ },
2854  
2855  /***/ 6472
2856  (module) {
2857  
2858  var l10n = wp.media.view.l10n,
2859      DateFilter;
2860  
2861  /**
2862   * A filter dropdown for month/dates.
2863   *
2864   * @memberOf wp.media.view.AttachmentFilters
2865   *
2866   * @class
2867   * @augments wp.media.view.AttachmentFilters
2868   * @augments wp.media.View
2869   * @augments wp.Backbone.View
2870   * @augments Backbone.View
2871   */
2872  DateFilter = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Date.prototype */{
2873      id: 'media-attachment-date-filters',
2874  
2875      createFilters: function() {
2876          var filters = {};
2877          _.each( wp.media.view.settings.months || {}, function( value, index ) {
2878              filters[ index ] = {
2879                  text: value.text,
2880                  props: {
2881                      year: value.year,
2882                      monthnum: value.month
2883                  }
2884              };
2885          });
2886          filters.all = {
2887              text:  l10n.allDates,
2888              props: {
2889                  monthnum: false,
2890                  year:  false
2891              },
2892              priority: 10
2893          };
2894          this.filters = filters;
2895      }
2896  });
2897  
2898  module.exports = DateFilter;
2899  
2900  
2901  /***/ },
2902  
2903  /***/ 1368
2904  (module) {
2905  
2906  var l10n = wp.media.view.l10n,
2907      Uploaded;
2908  
2909  /**
2910   * wp.media.view.AttachmentFilters.Uploaded
2911   *
2912   * @memberOf wp.media.view.AttachmentFilters
2913   *
2914   * @class
2915   * @augments wp.media.view.AttachmentFilters
2916   * @augments wp.media.View
2917   * @augments wp.Backbone.View
2918   * @augments Backbone.View
2919   */
2920  Uploaded = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Uploaded.prototype */{
2921      createFilters: function() {
2922          var type = this.model.get('type'),
2923              types = wp.media.view.settings.mimeTypes,
2924              uid = window.userSettings ? parseInt( window.userSettings.uid, 10 ) : 0,
2925              text;
2926  
2927          if ( types && type ) {
2928              text = types[ type ];
2929          }
2930  
2931          this.filters = {
2932              all: {
2933                  text:  text || l10n.allMediaItems,
2934                  props: {
2935                      uploadedTo: null,
2936                      orderby: 'date',
2937                      order:   'DESC',
2938                      author:     null
2939                  },
2940                  priority: 10
2941              },
2942  
2943              uploaded: {
2944                  text:  l10n.uploadedToThisPost,
2945                  props: {
2946                      uploadedTo: wp.media.view.settings.post.id,
2947                      orderby: 'menuOrder',
2948                      order:   'ASC',
2949                      author:     null
2950                  },
2951                  priority: 20
2952              },
2953  
2954              unattached: {
2955                  text:  l10n.unattached,
2956                  props: {
2957                      uploadedTo: 0,
2958                      orderby: 'menuOrder',
2959                      order:   'ASC',
2960                      author:     null
2961                  },
2962                  priority: 50
2963              }
2964          };
2965  
2966          if ( uid ) {
2967              this.filters.mine = {
2968                  text:  l10n.mine,
2969                  props: {
2970                      orderby: 'date',
2971                      order:   'DESC',
2972                      author:  uid
2973                  },
2974                  priority: 50
2975              };
2976          }
2977      }
2978  });
2979  
2980  module.exports = Uploaded;
2981  
2982  
2983  /***/ },
2984  
2985  /***/ 4075
2986  (module) {
2987  
2988  var View = wp.media.View,
2989      $ = jQuery,
2990      Attachment;
2991  
2992  /**
2993   * wp.media.view.Attachment
2994   *
2995   * @memberOf wp.media.view
2996   *
2997   * @class
2998   * @augments wp.media.View
2999   * @augments wp.Backbone.View
3000   * @augments Backbone.View
3001   */
3002  Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{
3003      tagName:   'li',
3004      className: 'attachment',
3005      template:  wp.template('attachment'),
3006  
3007      /**
3008       * Compute the accessible name for an attachment tile.
3009       *
3010       * Factored out of attributes() so render() can refresh the label when a
3011       * model finishes loading after the view was created. See #65852.
3012       *
3013       * @since 7.2.0
3014       *
3015       * @return {string} Accessible name for the attachment.
3016       */
3017      getAriaLabel: function() {
3018          var ariaLabel = this.model.get( 'title' );
3019  
3020          if ( ! ariaLabel ) {
3021              if ( this.model.get( 'uploading' ) ) {
3022                  ariaLabel = wp.i18n.__( 'uploading…' );
3023              } else {
3024                  ariaLabel = wp.i18n.__( '(no title)' );
3025              }
3026          }
3027  
3028          return ariaLabel;
3029      },
3030  
3031      attributes: function() {
3032          return {
3033              'tabIndex':     0,
3034              'role':         'checkbox',
3035              'aria-label':   this.getAriaLabel(),
3036              'aria-checked': false,
3037              'data-id':      this.model.get( 'id' )
3038          };
3039      },
3040  
3041      events: {
3042          'click':                          'toggleSelectionHandler',
3043          'change [data-setting]':          'updateSetting',
3044          'change [data-setting] input':    'updateSetting',
3045          'change [data-setting] select':   'updateSetting',
3046          'change [data-setting] textarea': 'updateSetting',
3047          'click .attachment-close':        'removeFromLibrary',
3048          'click .check':                   'checkClickHandler',
3049          'keydown':                        'toggleSelectionHandler'
3050      },
3051  
3052      buttons: {},
3053  
3054      initialize: function() {
3055          var selection = this.options.selection,
3056              options = _.defaults( this.options, {
3057                  rerenderOnModelChange: true
3058              } );
3059  
3060          if ( options.rerenderOnModelChange ) {
3061              this.listenTo( this.model, 'change', this.render );
3062          } else {
3063              this.listenTo( this.model, 'change:percent', this.progress );
3064          }
3065          this.listenTo( this.model, 'change:title', this._syncTitle );
3066          this.listenTo( this.model, 'change:caption', this._syncCaption );
3067          this.listenTo( this.model, 'change:artist', this._syncArtist );
3068          this.listenTo( this.model, 'change:album', this._syncAlbum );
3069  
3070          // Update the selection.
3071          this.listenTo( this.model, 'add', this.select );
3072          this.listenTo( this.model, 'remove', this.deselect );
3073          if ( selection ) {
3074              selection.on( 'reset', this.updateSelect, this );
3075              // Update the model's details view.
3076              this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
3077              this.details( this.model, this.controller.state().get('selection') );
3078          }
3079  
3080          this.listenTo( this.controller.states, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
3081      },
3082      /**
3083       * Update the view after the model has been saved.
3084       *
3085       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3086       */
3087      dispose: function() {
3088          var selection = this.options.selection;
3089  
3090          // Make sure all settings are saved before removing the view.
3091          this.updateAll();
3092  
3093          if ( selection ) {
3094              selection.off( null, null, this );
3095          }
3096          /**
3097           * call 'dispose' directly on the parent class
3098           */
3099          View.prototype.dispose.apply( this, arguments );
3100          return this;
3101      },
3102      /**
3103       * Renders the attachment view.
3104       *
3105       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3106       */
3107      render: function() {
3108          var options = _.defaults( this.model.toJSON(), {
3109                  orientation:   'landscape',
3110                  uploading:     false,
3111                  type:          '',
3112                  subtype:       '',
3113                  icon:          '',
3114                  filename:      '',
3115                  caption:       '',
3116                  title:         '',
3117                  dateFormatted: '',
3118                  width:         '',
3119                  height:        '',
3120                  compat:        false,
3121                  alt:           '',
3122                  description:   ''
3123              }, this.options );
3124  
3125          options.buttons  = this.buttons;
3126          options.describe = this.controller.state().get('describe');
3127  
3128          if ( 'image' === options.type ) {
3129              options.size = this.imageSize();
3130          }
3131  
3132          options.can = {};
3133          if ( options.nonces ) {
3134              options.can.remove = !! options.nonces['delete'];
3135              options.can.save = !! options.nonces.update;
3136          }
3137  
3138          if ( this.controller.state().get('allowLocalEdits') && ! options.uploading ) {
3139              options.allowLocalEdits = true;
3140          }
3141  
3142          if ( options.uploading && ! options.percent ) {
3143              options.percent = 0;
3144          }
3145  
3146          this.views.detach();
3147          this.$el.html( this.template( options ) );
3148  
3149          this.$el.toggleClass( 'uploading', options.uploading );
3150  
3151          if ( options.uploading ) {
3152              this.$bar = this.$('.media-progress-bar div');
3153          } else {
3154              delete this.$bar;
3155          }
3156  
3157          // Check if the model is selected.
3158          this.updateSelect();
3159  
3160          // Update the save status.
3161          this.updateSave();
3162  
3163          this.views.render();
3164  
3165          /*
3166           * Backbone only evaluates attributes() when the element is created.
3167           * When a tile is built for an id-only model that is still fetching,
3168           * the aria-label is computed from empty data and never refreshes.
3169           * Re-apply it here once the model has data. Skip subclasses that
3170           * reset attributes (e.g. Attachment.Details). See #65852, #47458.
3171           */
3172          if ( undefined !== _.result( this, 'attributes' )['aria-label'] ) {
3173              this.$el.attr( 'aria-label', this.getAriaLabel() );
3174          }
3175  
3176          return this;
3177      },
3178  
3179      progress: function() {
3180          if ( this.$bar && this.$bar.length ) {
3181              this.$bar.width( this.model.get('percent') + '%' );
3182          }
3183      },
3184  
3185      /**
3186       * Toggles the selection state of the attachment.
3187       *
3188       * @param {JQuery.Event} event The jQuery event object that triggered the selection toggle.
3189       */
3190      toggleSelectionHandler: function( event ) {
3191          var method;
3192  
3193          // Don't do anything inside inputs and on the attachment check and remove buttons.
3194          if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
3195              return;
3196          }
3197  
3198          // Catch arrow events.
3199          if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
3200              this.controller.trigger( 'attachment:keydown:arrow', event );
3201              return;
3202          }
3203  
3204          // Catch enter and space events.
3205          if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
3206              return;
3207          }
3208  
3209          event.preventDefault();
3210  
3211          // In the grid view, bubble up an edit:attachment event to the controller.
3212          if ( this.controller.isModeActive( 'grid' ) ) {
3213              if ( this.controller.isModeActive( 'edit' ) ) {
3214                  // Pass the current target to restore focus when closing.
3215                  this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
3216                  return;
3217              }
3218  
3219              if ( this.controller.isModeActive( 'select' ) ) {
3220                  method = 'toggle';
3221              }
3222          }
3223  
3224          if ( event.shiftKey ) {
3225              method = 'between';
3226          } else if ( event.ctrlKey || event.metaKey ) {
3227              method = 'toggle';
3228          }
3229  
3230          // Avoid toggles when the command or control key is pressed with the enter key to prevent deselecting the last selected attachment.
3231          if ( ( event.metaKey || event.ctrlKey ) && ( 13 === event.keyCode || 10 === event.keyCode ) ) {
3232              return;
3233          }
3234  
3235          this.toggleSelection({
3236              method: method
3237          });
3238  
3239          this.controller.trigger( 'selection:toggle' );
3240      },
3241      /**
3242       * Toggles the selection state of the attachment.
3243       *
3244       * @param {Object} options The options for toggling the selection state of the attachment.
3245       */
3246      toggleSelection: function( options ) {
3247          var collection = this.collection,
3248              selection = this.options.selection,
3249              model = this.model,
3250              method = options && options.method,
3251              single, models, singleIndex, modelIndex;
3252  
3253          if ( ! selection ) {
3254              return;
3255          }
3256  
3257          single = selection.single();
3258          method = _.isUndefined( method ) ? selection.multiple : method;
3259  
3260          // If the `method` is set to `between`, select all models that
3261          // exist between the current and the selected model.
3262          if ( 'between' === method && single && selection.multiple ) {
3263              // If the models are the same, short-circuit.
3264              if ( single === model ) {
3265                  return;
3266              }
3267  
3268              singleIndex = collection.indexOf( single );
3269              modelIndex  = collection.indexOf( this.model );
3270  
3271              if ( singleIndex < modelIndex ) {
3272                  models = collection.models.slice( singleIndex, modelIndex + 1 );
3273              } else {
3274                  models = collection.models.slice( modelIndex, singleIndex + 1 );
3275              }
3276  
3277              selection.add( models );
3278              selection.single( model );
3279              return;
3280  
3281          // If the `method` is set to `toggle`, just flip the selection
3282          // status, regardless of whether the model is the single model.
3283          } else if ( 'toggle' === method ) {
3284              selection[ this.selected() ? 'remove' : 'add' ]( model );
3285              selection.single( model );
3286              return;
3287          } else if ( 'add' === method ) {
3288              selection.add( model );
3289              selection.single( model );
3290              return;
3291          }
3292  
3293          // Fixes bug that loses focus when selecting a featured image.
3294          if ( ! method ) {
3295              method = 'add';
3296          }
3297  
3298          if ( method !== 'add' ) {
3299              method = 'reset';
3300          }
3301  
3302          if ( this.selected() ) {
3303              /*
3304               * If the model is the single model, remove it.
3305               * If it is not the same as the single model,
3306               * it now becomes the single model.
3307               */
3308              selection[ single === model ? 'remove' : 'single' ]( model );
3309          } else {
3310              /*
3311               * If the model is not selected, run the `method` on the
3312               * selection. By default, we `reset` the selection, but the
3313               * `method` can be set to `add` the model to the selection.
3314               */
3315              selection[ method ]( model );
3316              selection.single( model );
3317          }
3318      },
3319  
3320      updateSelect: function() {
3321          this[ this.selected() ? 'select' : 'deselect' ]();
3322      },
3323      /**
3324       * Checks if the model is selected in the selection.
3325       *
3326       * @return {void|boolean} True if the model is selected in the selection, false otherwise.
3327       */
3328      selected: function() {
3329          var selection = this.options.selection;
3330          if ( selection ) {
3331              return !! selection.get( this.model.cid );
3332          }
3333      },
3334      /**
3335       * Selects the model in the selection.
3336       *
3337       * @param {Backbone.Model}      model      The model to select.
3338       * @param {Backbone.Collection} collection The collection containing the model.
3339       */
3340      select: function( model, collection ) {
3341          var selection = this.options.selection,
3342              controller = this.controller;
3343  
3344          /*
3345           * Check if a selection exists and if it's the collection provided.
3346           * If they're not the same collection, bail; we're in another
3347           * selection's event loop.
3348           */
3349          if ( ! selection || ( collection && collection !== selection ) ) {
3350              return;
3351          }
3352  
3353          // Bail if the model is already selected.
3354          if ( this.$el.hasClass( 'selected' ) ) {
3355              return;
3356          }
3357  
3358          // Add 'selected' class to model, set aria-checked to true.
3359          this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
3360          //  Make the checkbox tabable, except in media grid (bulk select mode).
3361          if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
3362              this.$( '.check' ).attr( 'tabindex', '0' );
3363          }
3364      },
3365      /**
3366       * Deselects the model in the selection.
3367       *
3368       * @param {Backbone.Model}      model      The model to deselect.
3369       * @param {Backbone.Collection} collection The collection containing the model.
3370       */
3371      deselect: function( model, collection ) {
3372          var selection = this.options.selection;
3373  
3374          /*
3375           * Check if a selection exists and if it's the collection provided.
3376           * If they're not the same collection, bail; we're in another
3377           * selection's event loop.
3378           */
3379          if ( ! selection || ( collection && collection !== selection ) ) {
3380              return;
3381          }
3382          this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
3383              .find( '.check' ).attr( 'tabindex', '-1' );
3384      },
3385      /**
3386       * Updates the view to reflect whether the model is the single model in the selection.
3387       *
3388       * @param {Backbone.Model}      model      The model to check for details.
3389       * @param {Backbone.Collection} collection The collection containing the model.
3390       */
3391      details: function( model, collection ) {
3392          var selection = this.options.selection,
3393              details;
3394  
3395          if ( selection !== collection ) {
3396              return;
3397          }
3398  
3399          details = selection.single();
3400          this.$el.toggleClass( 'details', details === this.model );
3401      },
3402      /**
3403       * Gets the image size object for the specified size.
3404       *
3405       * @param {string} size The desired image size.
3406       * @return {Object} Returns an object containing the image size information.
3407       */
3408      imageSize: function( size ) {
3409          var sizes = this.model.get('sizes'), matched = false;
3410  
3411          size = size || 'medium';
3412  
3413          // Use the provided image size if possible.
3414          if ( sizes ) {
3415              if ( sizes[ size ] ) {
3416                  matched = sizes[ size ];
3417              } else if ( sizes.large ) {
3418                  matched = sizes.large;
3419              } else if ( sizes.thumbnail ) {
3420                  matched = sizes.thumbnail;
3421              } else if ( sizes.full ) {
3422                  matched = sizes.full;
3423              }
3424  
3425              if ( matched ) {
3426                  return _.clone( matched );
3427              }
3428          }
3429  
3430          return {
3431              url:         this.model.get('url'),
3432              width:       this.model.get('width'),
3433              height:      this.model.get('height'),
3434              orientation: this.model.get('orientation')
3435          };
3436      },
3437      /**
3438       * Update the model's setting with the value from the input.
3439       *
3440       * @param {JQuery.Event} event The jQuery event object that triggered the setting update.
3441       */
3442      updateSetting: function( event ) {
3443          var $setting = $( event.target ).closest('[data-setting]'),
3444              setting, value;
3445  
3446          if ( ! $setting.length ) {
3447              return;
3448          }
3449  
3450          setting = $setting.data('setting');
3451          value   = event.target.value;
3452  
3453          if ( this.model.get( setting ) !== value ) {
3454              this.save( setting, value );
3455          }
3456      },
3457  
3458      /**
3459       * Pass all the arguments to the model's save method.
3460       *
3461       * Records the aggregate status of all save requests and updates the
3462       * view's classes accordingly.
3463       */
3464      save: function() {
3465          var view = this,
3466              save = this._save = this._save || { status: 'ready' },
3467              request = this.model.save.apply( this.model, arguments ),
3468              requests = save.requests ? $.when( request, save.requests ) : request;
3469  
3470          // If we're waiting to remove 'Saved.', stop.
3471          if ( save.savedTimer ) {
3472              clearTimeout( save.savedTimer );
3473          }
3474  
3475          this.updateSave('waiting');
3476          save.requests = requests;
3477          requests.always( function() {
3478              // If we've performed another request since this one, bail.
3479              if ( save.requests !== requests ) {
3480                  return;
3481              }
3482  
3483              view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
3484              save.savedTimer = setTimeout( function() {
3485                  view.updateSave('ready');
3486                  delete save.savedTimer;
3487              }, 2000 );
3488          });
3489      },
3490      /**
3491       * Updates the view's save status.
3492       *
3493       * @param {string} status The new save status to apply to the view.
3494       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3495       */
3496      updateSave: function( status ) {
3497          var save = this._save = this._save || { status: 'ready' };
3498  
3499          if ( status && status !== save.status ) {
3500              this.$el.removeClass( 'save-' + save.status );
3501              save.status = status;
3502          }
3503  
3504          this.$el.addClass( 'save-' + save.status );
3505          return this;
3506      },
3507  
3508      updateAll: function() {
3509          var $settings = this.$('[data-setting]'),
3510              model = this.model,
3511              changed;
3512  
3513          changed = _.chain( $settings ).map( function( el ) {
3514              var $input = $('input, textarea, select, [value]', el ),
3515                  setting, value;
3516  
3517              if ( ! $input.length ) {
3518                  return;
3519              }
3520  
3521              setting = $(el).data('setting');
3522              value = $input.val();
3523  
3524              // Record the value if it changed.
3525              if ( model.get( setting ) !== value ) {
3526                  return [ setting, value ];
3527              }
3528          }).compact().object().value();
3529  
3530          if ( ! _.isEmpty( changed ) ) {
3531              model.save( changed );
3532          }
3533      },
3534      /**
3535       * Removes the model from the collection.
3536       *
3537       * @param {JQuery.Event} event The jQuery event object that triggered the removal from the library.
3538       */
3539      removeFromLibrary: function( event ) {
3540          // Catch enter and space events.
3541          if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
3542              return;
3543          }
3544  
3545          // Stop propagation so the model isn't selected.
3546          event.stopPropagation();
3547  
3548          this.collection.remove( this.model );
3549      },
3550  
3551      /**
3552       * Adds the model if it isn't in the selection, if it is in the selection,
3553       * removes it.
3554       *
3555       * @param {JQuery.Event} event The jQuery event object that triggered the selection toggle.
3556       * @return {void}
3557       */
3558      checkClickHandler: function ( event ) {
3559          var selection = this.options.selection;
3560          if ( ! selection ) {
3561              return;
3562          }
3563          event.stopPropagation();
3564          if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
3565              selection.remove( this.model );
3566              // Move focus back to the attachment tile (from the check).
3567              this.$el.focus();
3568          } else {
3569              selection.add( this.model );
3570          }
3571  
3572          // Trigger an action button update.
3573          this.controller.trigger( 'selection:toggle' );
3574      }
3575  });
3576  
3577  // Ensure settings remain in sync between attachment views.
3578  _.each({
3579      caption: '_syncCaption',
3580      title:   '_syncTitle',
3581      artist:  '_syncArtist',
3582      album:   '_syncAlbum'
3583  }, function( method, setting ) {
3584      /**
3585       * Updates the DOM when the model's caption changes.
3586       *
3587       * @function _syncCaption
3588       * @memberOf wp.media.view.Attachment
3589       * @instance
3590       *
3591       * @param {Backbone.Model} model The model containing the caption.
3592       * @param {string}         value The new caption value.
3593       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3594       */
3595      /**
3596       * Updates the DOM when the model's title changes.
3597       *
3598       * @function _syncTitle
3599       * @memberOf wp.media.view.Attachment
3600       * @instance
3601       *
3602       * @param {Backbone.Model} model The model containing the title.
3603       * @param {string}         value The new title value.
3604       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3605       */
3606      /**
3607       * Updates the DOM when the model's artist changes.
3608       *
3609       * @function _syncArtist
3610       * @memberOf wp.media.view.Attachment
3611       * @instance
3612       *
3613       * @param {Backbone.Model} model The model containing the artist.
3614       * @param {string}         value The new artist value.
3615       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3616       */
3617      /**
3618       * Updates the DOM when the model's album changes.
3619       *
3620       * @function _syncAlbum
3621       * @memberOf wp.media.view.Attachment
3622       * @instance
3623       *
3624       * @param {Backbone.Model} model The model containing the album.
3625       * @param {string}         value The new album value.
3626       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3627       */
3628      Attachment.prototype[ method ] = function( model, value ) {
3629          var $setting = this.$('[data-setting="' + setting + '"]');
3630  
3631          if ( ! $setting.length ) {
3632              return this;
3633          }
3634  
3635          /*
3636           * If the updated value is in sync with the value in the DOM, there
3637           * is no need to re-render. If we're currently editing the value,
3638           * it will automatically be in sync, suppressing the re-render for
3639           * the view we're editing, while updating any others.
3640           */
3641          if ( value === $setting.find('input, textarea, select, [value]').val() ) {
3642              return this;
3643          }
3644  
3645          return this.render();
3646      };
3647  });
3648  
3649  module.exports = Attachment;
3650  
3651  
3652  /***/ },
3653  
3654  /***/ 6090
3655  (module) {
3656  
3657  /* global ClipboardJS */
3658  var Attachment = wp.media.view.Attachment,
3659      l10n = wp.media.view.l10n,
3660      $ = jQuery,
3661      Details,
3662      __ = wp.i18n.__;
3663  
3664  Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{
3665      tagName:   'div',
3666      className: 'attachment-details',
3667      template:  wp.template('attachment-details'),
3668  
3669      /*
3670       * Reset all the attributes inherited from Attachment including role=checkbox,
3671       * tabindex, etc., as they are inappropriate for this view. See #47458 and [30483] / #30390.
3672       */
3673      attributes: {},
3674  
3675      events: {
3676          'change [data-setting]':          'updateSetting',
3677          'change [data-setting] input':    'updateSetting',
3678          'change [data-setting] select':   'updateSetting',
3679          'change [data-setting] textarea': 'updateSetting',
3680          'click .delete-attachment':       'deleteAttachment',
3681          'click .trash-attachment':        'trashAttachment',
3682          'click .untrash-attachment':      'untrashAttachment',
3683          'click .edit-attachment':         'editAttachment',
3684          'keydown':                        'toggleSelectionHandler'
3685      },
3686  
3687      /**
3688       * Copies the attachment URL to the clipboard.
3689       *
3690       * @since 5.5.0
3691       *
3692       * @return {void}
3693       */
3694       copyAttachmentDetailsURLClipboard: function() {
3695          var clipboard = new ClipboardJS( '.copy-attachment-url' ),
3696              successTimeout;
3697  
3698          clipboard.on( 'success', function( event ) {
3699              var triggerElement = $( event.trigger ),
3700                  successElement = $( '.success', triggerElement.closest( '.copy-to-clipboard-container' ) );
3701  
3702              // Clear the selection and move focus back to the trigger.
3703              event.clearSelection();
3704  
3705              // Show success visual feedback.
3706              clearTimeout( successTimeout );
3707              successElement.removeClass( 'hidden' );
3708  
3709              // Hide success visual feedback after 3 seconds since last success.
3710              successTimeout = setTimeout( function() {
3711                  successElement.addClass( 'hidden' );
3712              }, 3000 );
3713  
3714              // Handle success audible feedback.
3715              wp.a11y.speak( __( 'The file URL has been copied to your clipboard' ) );
3716          } );
3717       },
3718  
3719      /**
3720       * Shows the details of an attachment.
3721       *
3722       * @since 3.5.0
3723       *
3724       * @constructs wp.media.view.Attachment.Details
3725       * @augments wp.media.view.Attachment
3726       *
3727       * @return {void}
3728       */
3729      initialize: function() {
3730          this.options = _.defaults( this.options, {
3731              rerenderOnModelChange: false
3732          });
3733  
3734          // Call 'initialize' directly on the parent class.
3735          Attachment.prototype.initialize.apply( this, arguments );
3736  
3737          this.copyAttachmentDetailsURLClipboard();
3738      },
3739  
3740      /**
3741       * Gets the focusable elements to move focus to.
3742       *
3743       * @since 5.3.0
3744       */
3745      getFocusableElements: function() {
3746          var editedAttachment = $( 'li[data-id="' + this.model.id + '"]' );
3747  
3748          this.previousAttachment = editedAttachment.prev();
3749          this.nextAttachment = editedAttachment.next();
3750      },
3751  
3752      /**
3753       * Moves focus to the previous or next attachment in the grid.
3754       * Fallbacks to the upload button or media frame when there are no attachments.
3755       *
3756       * @since 5.3.0
3757       */
3758      moveFocus: function() {
3759          if ( this.previousAttachment.length ) {
3760              this.previousAttachment.trigger( 'focus' );
3761              return;
3762          }
3763  
3764          if ( this.nextAttachment.length ) {
3765              this.nextAttachment.trigger( 'focus' );
3766              return;
3767          }
3768  
3769          // Fallback: move focus to the "Select Files" button in the media modal.
3770          if ( this.controller.uploader && this.controller.uploader.$browser ) {
3771              this.controller.uploader.$browser.trigger( 'focus' );
3772              return;
3773          }
3774  
3775          // Last fallback.
3776          this.moveFocusToLastFallback();
3777      },
3778  
3779      /**
3780       * Moves focus to the media frame as last fallback.
3781       *
3782       * @since 5.3.0
3783       */
3784      moveFocusToLastFallback: function() {
3785          // Last fallback: make the frame focusable and move focus to it.
3786          $( '.media-frame' )
3787              .attr( 'tabindex', '-1' )
3788              .trigger( 'focus' );
3789      },
3790  
3791      /**
3792       * Deletes an attachment.
3793       *
3794       * Deletes an attachment after asking for confirmation. After deletion,
3795       * keeps focus in the modal.
3796       *
3797       * @since 3.5.0
3798       *
3799       * @param {MouseEvent} event A click event.
3800       *
3801       * @return {void}
3802       */
3803      deleteAttachment: function( event ) {
3804          event.preventDefault();
3805  
3806          this.getFocusableElements();
3807  
3808          if ( window.confirm( l10n.warnDelete ) ) {
3809              this.model.destroy( {
3810                  wait: true,
3811                  error: function() {
3812                      window.alert( l10n.errorDeleting );
3813                  }
3814              } );
3815  
3816              this.moveFocus();
3817          }
3818      },
3819  
3820      /**
3821       * Sets the Trash state on an attachment, or destroys the model itself.
3822       *
3823       * If the mediaTrash setting is set to true, trashes the attachment.
3824       * Otherwise, the model itself is destroyed.
3825       *
3826       * @since 3.9.0
3827       *
3828       * @param {MouseEvent} event A click event.
3829       *
3830       * @return {void}
3831       */
3832      trashAttachment: function( event ) {
3833          var library = this.controller.library,
3834              self = this;
3835          event.preventDefault();
3836  
3837          this.getFocusableElements();
3838  
3839          // When in the Media Library and the Media Trash is enabled.
3840          if ( wp.media.view.settings.mediaTrash &&
3841              'edit-metadata' === this.controller.content.mode() ) {
3842  
3843              this.model.set( 'status', 'trash' );
3844              this.model.save().done( function() {
3845                  library._requery( true );
3846                  /*
3847                   * @todo We need to move focus back to the previous, next, or first
3848                   * attachment but the library gets re-queried and refreshed.
3849                   * Thus, the references to the previous attachments are lost.
3850                   * We need an alternate method.
3851                   */
3852                  self.moveFocusToLastFallback();
3853              } );
3854          } else {
3855              this.model.destroy();
3856              this.moveFocus();
3857          }
3858      },
3859  
3860      /**
3861       * Untrashes an attachment.
3862       *
3863       * @since 4.0.0
3864       *
3865       * @param {MouseEvent} event A click event.
3866       *
3867       * @return {void}
3868       */
3869      untrashAttachment: function( event ) {
3870          var library = this.controller.library;
3871          event.preventDefault();
3872  
3873          this.model.set( 'status', 'inherit' );
3874          this.model.save().done( function() {
3875              library._requery( true );
3876          } );
3877      },
3878  
3879      /**
3880       * Opens the edit page for a specific attachment.
3881       *
3882       * @since 3.5.0
3883       *
3884       * @param {MouseEvent} event A click event.
3885       *
3886       * @return {void}
3887       */
3888      editAttachment: function( event ) {
3889          var editState = this.controller.states.get( 'edit-image' );
3890          if ( window.imageEdit && editState ) {
3891              event.preventDefault();
3892  
3893              editState.set( 'image', this.model );
3894              this.controller.setState( 'edit-image' );
3895          } else {
3896              this.$el.addClass('needs-refresh');
3897          }
3898      },
3899  
3900      /**
3901       * Triggers an event on the controller when reverse tabbing (shift+tab).
3902       *
3903       * This event can be used to make sure to move the focus correctly.
3904       *
3905       * Fires `attachment:details:shift-tab` and `attachment:keydown:arrow` events on the controller.
3906       *
3907       * @since 4.0.0
3908       *
3909       * @param {KeyboardEvent} event A keyboard event.
3910       *
3911       * @return {boolean|void} Returns false or undefined.
3912       */
3913      toggleSelectionHandler: function( event ) {
3914          if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
3915              this.controller.trigger( 'attachment:details:shift-tab', event );
3916              return false;
3917          }
3918      },
3919  
3920      render: function() {
3921          Attachment.prototype.render.apply( this, arguments );
3922  
3923          wp.media.mixin.removeAllPlayers();
3924          this.$( 'audio, video' ).each( function (i, elem) {
3925              var el = wp.media.view.MediaDetails.prepareSrc( elem );
3926              new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
3927          } );
3928      }
3929  });
3930  
3931  module.exports = Details;
3932  
3933  
3934  /***/ },
3935  
3936  /***/ 5232
3937  (module) {
3938  
3939  /**
3940   * wp.media.view.Attachment.EditLibrary
3941   *
3942   * @memberOf wp.media.view.Attachment
3943   *
3944   * @class
3945   * @augments wp.media.view.Attachment
3946   * @augments wp.media.View
3947   * @augments wp.Backbone.View
3948   * @augments Backbone.View
3949   */
3950  var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{
3951      buttons: {
3952          close: true
3953      }
3954  });
3955  
3956  module.exports = EditLibrary;
3957  
3958  
3959  /***/ },
3960  
3961  /***/ 4593
3962  (module) {
3963  
3964  /**
3965   * wp.media.view.Attachment.EditSelection
3966   *
3967   * @memberOf wp.media.view.Attachment
3968   *
3969   * @class
3970   * @augments wp.media.view.Attachment.Selection
3971   * @augments wp.media.view.Attachment
3972   * @augments wp.media.View
3973   * @augments wp.Backbone.View
3974   * @augments Backbone.View
3975   */
3976  var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{
3977      buttons: {
3978          close: true
3979      }
3980  });
3981  
3982  module.exports = EditSelection;
3983  
3984  
3985  /***/ },
3986  
3987  /***/ 3443
3988  (module) {
3989  
3990  /**
3991   * wp.media.view.Attachment.Library
3992   *
3993   * @memberOf wp.media.view.Attachment
3994   *
3995   * @class
3996   * @augments wp.media.view.Attachment
3997   * @augments wp.media.View
3998   * @augments wp.Backbone.View
3999   * @augments Backbone.View
4000   */
4001  var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{
4002      buttons: {
4003          check: true
4004      }
4005  });
4006  
4007  module.exports = Library;
4008  
4009  
4010  /***/ },
4011  
4012  /***/ 3962
4013  (module) {
4014  
4015  /**
4016   * wp.media.view.Attachment.Selection
4017   *
4018   * @memberOf wp.media.view.Attachment
4019   *
4020   * @class
4021   * @augments wp.media.view.Attachment
4022   * @augments wp.media.View
4023   * @augments wp.Backbone.View
4024   * @augments Backbone.View
4025   */
4026  var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{
4027      className: 'attachment selection',
4028  
4029      // On click, just select the model, instead of removing the model from
4030      // the selection.
4031      toggleSelection: function() {
4032          this.options.selection.single( this.model );
4033      }
4034  });
4035  
4036  module.exports = Selection;
4037  
4038  
4039  /***/ },
4040  
4041  /***/ 8142
4042  (module) {
4043  
4044  var View = wp.media.View,
4045      $ = jQuery,
4046      Attachments,
4047      infiniteScrolling = wp.media.view.settings.infiniteScrolling;
4048  
4049  Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
4050      tagName:   'ul',
4051      className: 'attachments',
4052  
4053      attributes: {
4054          role:     'group',
4055          tabIndex: -1
4056      },
4057  
4058      /**
4059       * Represents the overview of attachments in the Media Library.
4060       *
4061       * The constructor binds events to the collection this view represents when
4062       * adding or removing attachments or resetting the entire collection.
4063       *
4064       * Listens for `collection:add`, `collection:remove`, `collection:reset`,
4065       * `controller:library:selection:add`, `scrollElement:scroll`, `this:ready`,
4066       * and `controller:open` events.
4067       *
4068       * @since 3.5.0
4069       *
4070       * @constructs
4071       * @memberof wp.media.view
4072       *
4073       * @augments wp.media.View
4074       */
4075      initialize: function() {
4076          this.el.id = _.uniqueId('__attachments-view-');
4077  
4078          /**
4079           * @since 5.8.0 Added the `infiniteScrolling` parameter.
4080           *
4081           * @param infiniteScrolling  Whether to enable infinite scrolling or use
4082           *                           the default "load more" button.
4083           * @param refreshSensitivity The time in milliseconds to throttle the scroll
4084           *                           handler.
4085           * @param refreshThreshold   The amount of pixels that should be scrolled before
4086           *                           loading more attachments from the server.
4087           * @param AttachmentView     The view class to be used for models in the
4088           *                           collection.
4089           * @param sortable           A jQuery sortable options object
4090           *                           ( http://api.jqueryui.com/sortable/ ).
4091           * @param resize             A boolean indicating whether or not to listen to
4092           *                           resize events.
4093           * @param idealColumnWidth   The width in pixels which a column should have when
4094           *                           calculating the total number of columns.
4095           */
4096          _.defaults( this.options, {
4097              infiniteScrolling:  infiniteScrolling || false,
4098              refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
4099              refreshThreshold:   3,
4100              AttachmentView:     wp.media.view.Attachment,
4101              sortable:           false,
4102              resize:             true,
4103              idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
4104          });
4105  
4106          this._viewsByCid = {};
4107          this.$window = $( window );
4108          this.resizeEvent = 'resize.media-modal-columns';
4109  
4110          this.collection.on( 'add', function( attachment ) {
4111              this.views.add( this.createAttachmentView( attachment ), {
4112                  at: this.collection.indexOf( attachment )
4113              });
4114          }, this );
4115  
4116          /*
4117           * Find the view to be removed, delete it and call the remove function to clear
4118           * any set event handlers.
4119           */
4120          this.collection.on( 'remove', function( attachment ) {
4121              var view = this._viewsByCid[ attachment.cid ];
4122              delete this._viewsByCid[ attachment.cid ];
4123  
4124              if ( view ) {
4125                  view.remove();
4126              }
4127          }, this );
4128  
4129          this.collection.on( 'reset', this.render, this );
4130  
4131          this.controller.on( 'library:selection:add', this.attachmentFocus, this );
4132  
4133          if ( this.options.infiniteScrolling ) {
4134              // Throttle the scroll handler and bind this.
4135              this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
4136  
4137              this.options.scrollElement = this.options.scrollElement || this.el;
4138              $( this.options.scrollElement ).on( 'scroll', this.scroll );
4139          }
4140  
4141          this.initSortable();
4142  
4143          _.bindAll( this, 'setColumns' );
4144  
4145          if ( this.options.resize ) {
4146              this.on( 'ready', this.bindEvents );
4147              this.controller.on( 'open', this.setColumns );
4148  
4149              /*
4150               * Call this.setColumns() after this view has been rendered in the
4151               * DOM so attachments get proper width applied.
4152               */
4153              _.defer( this.setColumns, this );
4154          }
4155      },
4156  
4157      /**
4158       * Listens to the resizeEvent on the window.
4159       *
4160       * Adjusts the amount of columns accordingly. First removes any existing event
4161       * handlers to prevent duplicate listeners.
4162       *
4163       * Listens for the `window:resize` event.
4164       *
4165       * @since 4.0.0
4166       *
4167       * @return {void}
4168       */
4169      bindEvents: function() {
4170          this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
4171      },
4172  
4173      /**
4174       * Focuses the first item in the collection.
4175       *
4176       * @since 4.0.0
4177       *
4178       * @return {void}
4179       */
4180      attachmentFocus: function() {
4181          /*
4182           * @todo When uploading new attachments, this tries to move focus to
4183           * the attachments grid. Actually, a progress bar gets initially displayed
4184           * and then updated when uploading completes, so focus is lost.
4185           * Additionally: this view is used for both the attachments list and
4186           * the list of selected attachments in the bottom media toolbar. Thus, when
4187           * uploading attachments, it is called twice and returns two different `this`.
4188           * `this.columns` is truthy within the modal.
4189           */
4190          if ( this.columns ) {
4191              // Move focus to the grid list within the modal.
4192              this.$el.focus();
4193          }
4194      },
4195  
4196      /**
4197       * Restores focus to the selected item in the collection.
4198       *
4199       * Moves focus back to the first selected attachment in the grid. Used when
4200       * tabbing backwards from the attachment details sidebar.
4201       * See media.view.AttachmentsBrowser.
4202       *
4203       * @since 4.0.0
4204       *
4205       * @return {void}
4206       */
4207      restoreFocus: function() {
4208          this.$( 'li.selected:first' ).focus();
4209      },
4210  
4211      /**
4212       * Handles events for arrow key presses.
4213       *
4214       * Focuses the attachment in the direction of the used arrow key if it exists.
4215       *
4216       * @since 4.0.0
4217       *
4218       * @param {KeyboardEvent} event The keyboard event that triggered this function.
4219       *
4220       * @return {void}
4221       */
4222      arrowEvent: function( event ) {
4223          var attachments = this.$el.children( 'li' ),
4224              perRow = this.columns,
4225              index = attachments.filter( ':focus' ).index(),
4226              row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
4227  
4228          if ( index === -1 ) {
4229              return;
4230          }
4231  
4232          // Left arrow = 37.
4233          if ( 37 === event.keyCode ) {
4234              if ( 0 === index ) {
4235                  return;
4236              }
4237              attachments.eq( index - 1 ).focus();
4238          }
4239  
4240          // Up arrow = 38.
4241          if ( 38 === event.keyCode ) {
4242              if ( 1 === row ) {
4243                  return;
4244              }
4245              attachments.eq( index - perRow ).focus();
4246          }
4247  
4248          // Right arrow = 39.
4249          if ( 39 === event.keyCode ) {
4250              if ( attachments.length === index ) {
4251                  return;
4252              }
4253              attachments.eq( index + 1 ).focus();
4254          }
4255  
4256          // Down arrow = 40.
4257          if ( 40 === event.keyCode ) {
4258              if ( Math.ceil( attachments.length / perRow ) === row ) {
4259                  return;
4260              }
4261              attachments.eq( index + perRow ).focus();
4262          }
4263      },
4264  
4265      /**
4266       * Clears any set event handlers.
4267       *
4268       * @since 3.5.0
4269       *
4270       * @return {void}
4271       */
4272      dispose: function() {
4273          this.collection.props.off( null, null, this );
4274          if ( this.options.resize ) {
4275              this.$window.off( this.resizeEvent );
4276          }
4277  
4278          // Call 'dispose' directly on the parent class.
4279          View.prototype.dispose.apply( this, arguments );
4280      },
4281  
4282      /**
4283       * Calculates the amount of columns.
4284       *
4285       * Calculates the amount of columns and sets it on the data-columns attribute
4286       * of .media-frame-content.
4287       *
4288       * @since 4.0.0
4289       *
4290       * @return {void}
4291       */
4292      setColumns: function() {
4293          var prev = this.columns,
4294              width = this.$el.width();
4295  
4296          if ( width ) {
4297              this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
4298  
4299              if ( ! prev || prev !== this.columns ) {
4300                  this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
4301              }
4302          }
4303      },
4304  
4305      /**
4306       * Initializes jQuery sortable on the attachment list.
4307       *
4308       * Fails gracefully if jQuery sortable doesn't exist or isn't passed
4309       * in the options.
4310       *
4311       * Fires `collection:reset`.
4312       *
4313       * @since 3.5.0
4314       *
4315       * @return {void}
4316       */
4317      initSortable: function() {
4318          var collection = this.collection;
4319  
4320          if ( ! this.options.sortable || ! $.fn.sortable ) {
4321              return;
4322          }
4323  
4324          this.$el.sortable( _.extend({
4325              // If the `collection` has a `comparator`, disable sorting.
4326              disabled: !! collection.comparator,
4327  
4328              /*
4329               * Change the position of the attachment as soon as the mouse pointer
4330               * overlaps a thumbnail.
4331               */
4332              tolerance: 'pointer',
4333  
4334              // Record the initial `index` of the dragged model.
4335              start: function( event, ui ) {
4336                  ui.item.data('sortableIndexStart', ui.item.index());
4337              },
4338  
4339              /*
4340               * Update the model's index in the collection. Do so silently, as the view
4341               * is already accurate.
4342               */
4343              update: function( event, ui ) {
4344                  var model = collection.at( ui.item.data('sortableIndexStart') ),
4345                      comparator = collection.comparator;
4346  
4347                  // Temporarily disable the comparator to prevent `add`
4348                  // from re-sorting.
4349                  delete collection.comparator;
4350  
4351                  // Silently shift the model to its new index.
4352                  collection.remove( model, {
4353                      silent: true
4354                  });
4355                  collection.add( model, {
4356                      silent: true,
4357                      at:     ui.item.index()
4358                  });
4359  
4360                  // Restore the comparator.
4361                  collection.comparator = comparator;
4362  
4363                  // Fire the `reset` event to ensure other collections sync.
4364                  collection.trigger( 'reset', collection );
4365  
4366                  // If the collection is sorted by menu order, update the menu order.
4367                  collection.saveMenuOrder();
4368              }
4369          }, this.options.sortable ) );
4370  
4371          /*
4372           * If the `orderby` property is changed on the `collection`,
4373           * check to see if we have a `comparator`. If so, disable sorting.
4374           */
4375          collection.props.on( 'change:orderby', function() {
4376              this.$el.sortable( 'option', 'disabled', !! collection.comparator );
4377          }, this );
4378  
4379          this.collection.props.on( 'change:orderby', this.refreshSortable, this );
4380          this.refreshSortable();
4381      },
4382  
4383      /**
4384       * Disables jQuery sortable if collection has a comparator or collection.orderby
4385       * equals menuOrder.
4386       *
4387       * @since 3.5.0
4388       *
4389       * @return {void}
4390       */
4391      refreshSortable: function() {
4392          if ( ! this.options.sortable || ! $.fn.sortable ) {
4393              return;
4394          }
4395  
4396          var collection = this.collection,
4397              orderby = collection.props.get('orderby'),
4398              enabled = 'menuOrder' === orderby || ! collection.comparator;
4399  
4400          this.$el.sortable( 'option', 'disabled', ! enabled );
4401      },
4402  
4403      /**
4404       * Creates a new view for an attachment and adds it to _viewsByCid.
4405       *
4406       * @since 3.5.0
4407       *
4408       * @param {wp.media.model.Attachment} attachment The attachment model for which to create a view.
4409       *
4410       * @return {wp.media.View} The created view.
4411       */
4412      createAttachmentView: function( attachment ) {
4413          var view = new this.options.AttachmentView({
4414              controller:           this.controller,
4415              model:                attachment,
4416              collection:           this.collection,
4417              selection:            this.options.selection
4418          });
4419  
4420          return this._viewsByCid[ attachment.cid ] = view;
4421      },
4422  
4423      /**
4424       * Prepares view for display.
4425       *
4426       * Creates views for every attachment in collection if the collection is not
4427       * empty, otherwise clears all views and loads more attachments.
4428       *
4429       * @since 3.5.0
4430       *
4431       * @return {void}
4432       */
4433      prepare: function() {
4434          if ( this.collection.length ) {
4435              this.views.set( this.collection.map( this.createAttachmentView, this ) );
4436          } else {
4437              this.views.unset();
4438              if ( this.options.infiniteScrolling ) {
4439                  this.collection.more().done( this.scroll );
4440              }
4441          }
4442      },
4443  
4444      /**
4445       * Triggers the scroll function to check if we should query for additional
4446       * attachments right away.
4447       *
4448       * @since 3.5.0
4449       *
4450       * @return {void}
4451       */
4452      ready: function() {
4453          if ( this.options.infiniteScrolling ) {
4454              this.scroll();
4455          }
4456      },
4457  
4458      /**
4459       * Handles scroll events.
4460       *
4461       * Shows the spinner if we're close to the bottom. Loads more attachments from
4462       * server if we're {refreshThreshold} times away from the bottom.
4463       *
4464       * @since 3.5.0
4465       *
4466       * @return {void}
4467       */
4468      scroll: function() {
4469          var view = this,
4470              el = this.options.scrollElement,
4471              scrollTop = el.scrollTop,
4472              toolbar;
4473  
4474          /*
4475           * The scroll event occurs on the document, but the element that should be
4476           * checked is the document body.
4477           */
4478          if ( el === document ) {
4479              el = document.body;
4480              scrollTop = $(document).scrollTop();
4481          }
4482  
4483          if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
4484              return;
4485          }
4486  
4487          toolbar = this.views.parent.toolbar;
4488  
4489          // Show the spinner only if we are close to the bottom.
4490          if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
4491              toolbar.get('spinner').show();
4492          }
4493  
4494          if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
4495              this.collection.more().done(function() {
4496                  view.scroll();
4497                  toolbar.get('spinner').hide();
4498              });
4499          }
4500      }
4501  });
4502  
4503  module.exports = Attachments;
4504  
4505  
4506  /***/ },
4507  
4508  /***/ 6829
4509  (module) {
4510  
4511  var View = wp.media.View,
4512      mediaTrash = wp.media.view.settings.mediaTrash,
4513      l10n = wp.media.view.l10n,
4514      $ = jQuery,
4515      AttachmentsBrowser,
4516      infiniteScrolling = wp.media.view.settings.infiniteScrolling,
4517      __ = wp.i18n.__,
4518      sprintf = wp.i18n.sprintf;
4519  
4520  /**
4521   * wp.media.view.AttachmentsBrowser
4522   *
4523   * @memberOf wp.media.view
4524   *
4525   * @class
4526   * @augments wp.media.View
4527   * @augments wp.Backbone.View
4528   * @augments Backbone.View
4529   *
4530   * @param {Object}         [options]               The options hash passed to the view.
4531   * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar.
4532   *                                                 Accepts 'uploaded' and 'all'.
4533   * @param {boolean}        [options.search=true]   Whether to show the search interface in the
4534   *                                                 browser's toolbar.
4535   * @param {boolean}        [options.date=true]     Whether to show the date filter in the
4536   *                                                 browser's toolbar.
4537   * @param {boolean}        [options.display=false] Whether to show the attachments display settings
4538   *                                                 view in the sidebar.
4539   * @param {boolean|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
4540   *                                                 Accepts true, false, and 'errors'.
4541   */
4542  AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{
4543      tagName:   'div',
4544      className: 'attachments-browser',
4545  
4546      /**
4547       * Initializes the AttachmentsBrowser view.
4548       */
4549      initialize: function() {
4550          _.defaults( this.options, {
4551              filters: false,
4552              search:  true,
4553              date:    true,
4554              display: false,
4555              sidebar: true,
4556              AttachmentView: wp.media.view.Attachment.Library
4557          });
4558  
4559          this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this );
4560          this.controller.on( 'edit:selection', this.editSelection );
4561  
4562          // In the Media Library, the sidebar is used to display errors before the attachments grid.
4563          if ( this.options.sidebar && 'errors' === this.options.sidebar ) {
4564              this.createSidebar();
4565          }
4566  
4567          /*
4568           * In the grid mode (the Media Library), place the Inline Uploader before
4569           * other sections so that the visual order and the DOM order match. This way,
4570           * the Inline Uploader in the Media Library is right after the "Add New"
4571           * button, see ticket #37188.
4572           */
4573          if ( this.controller.isModeActive( 'grid' ) ) {
4574              this.createUploader();
4575  
4576              /*
4577               * Create a multi-purpose toolbar. Used as main toolbar in the Media Library
4578               * and also for other things, for example the "Drag and drop to reorder" and
4579               * "Suggested dimensions" info in the media modal.
4580               */
4581              this.createToolbar();
4582          } else {
4583              this.createToolbar();
4584              this.createUploader();
4585          }
4586  
4587          // Add a heading before the attachments list.
4588          this.createAttachmentsHeading();
4589  
4590          // Create the attachments wrapper view.
4591          this.createAttachmentsWrapperView();
4592  
4593          if ( ! infiniteScrolling ) {
4594              this.$el.addClass( 'has-load-more' );
4595              this.createLoadMoreView();
4596          }
4597  
4598          // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909.
4599          if ( this.options.sidebar && 'errors' !== this.options.sidebar ) {
4600              this.createSidebar();
4601          }
4602  
4603          this.updateContent();
4604  
4605          if ( ! infiniteScrolling ) {
4606              this.updateLoadMoreView();
4607          }
4608  
4609          if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
4610              this.$el.addClass( 'hide-sidebar' );
4611  
4612              if ( 'errors' === this.options.sidebar ) {
4613                  this.$el.addClass( 'sidebar-for-errors' );
4614              }
4615          }
4616  
4617          this.collection.on( 'add remove reset', this.updateContent, this );
4618  
4619          if ( ! infiniteScrolling ) {
4620              this.collection.on( 'add remove reset', this.updateLoadMoreView, this );
4621          }
4622  
4623          // The non-cached or cached attachments query has completed.
4624          this.collection.on( 'attachments:received', this.announceSearchResults, this );
4625      },
4626  
4627      /**
4628       * Updates the `wp.a11y.speak()` ARIA live region with a message to communicate
4629       * the number of search results to screen reader users. This function is
4630       * debounced because the collection updates multiple times.
4631       *
4632       * @since 5.3.0
4633       *
4634       * @return {void}
4635       */
4636      announceSearchResults: _.debounce( function() {
4637          var count,
4638              /* translators: Accessibility text. %d: Number of attachments found in a search. */
4639              mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Click load more for more results.' );
4640  
4641          if ( infiniteScrolling ) {
4642              /* translators: Accessibility text. %d: Number of attachments found in a search. */
4643              mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Scroll the page for more results.' );
4644          }
4645  
4646          if ( this.collection.mirroring && this.collection.mirroring.args.s ) {
4647              count = this.collection.length;
4648  
4649              if ( 0 === count ) {
4650                  wp.a11y.speak( l10n.noMediaTryNewSearch );
4651                  return;
4652              }
4653  
4654              if ( this.collection.hasMore() ) {
4655                  wp.a11y.speak( mediaFoundHasMoreResultsMessage.replace( '%d', count ) );
4656                  return;
4657              }
4658  
4659              wp.a11y.speak( l10n.mediaFound.replace( '%d', count ) );
4660          }
4661      }, 200 ),
4662  
4663      /**
4664       * Edits the selection in the modal. This is used when the user clicks the "Edit" button in the modal.
4665       *
4666       * @param {wp.media.view.Modal} modal The modal view.
4667       */
4668      editSelection: function( modal ) {
4669          // When editing a selection, move focus to the "Go to library" button.
4670          modal.$( '.media-button-backToLibrary' ).focus();
4671      },
4672  
4673      /**
4674       * Disposes of the view and its children.
4675       *
4676       * @return {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining.
4677       */
4678      dispose: function() {
4679          this.options.selection.off( null, null, this );
4680          View.prototype.dispose.apply( this, arguments );
4681          return this;
4682      },
4683  
4684      /**
4685       * Creates the toolbar view.
4686       */
4687      createToolbar: function() {
4688          var LibraryViewSwitcher, Filters, toolbarOptions,
4689              showFilterByType = -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] );
4690  
4691          toolbarOptions = {
4692              controller: this.controller
4693          };
4694  
4695          if ( this.controller.isModeActive( 'grid' ) ) {
4696              toolbarOptions.className = 'media-toolbar wp-filter';
4697          }
4698  
4699          /**
4700           * @member {wp.media.view.Toolbar}
4701           */
4702          this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
4703  
4704          this.views.add( this.toolbar );
4705  
4706          this.toolbar.set( 'spinner', new wp.media.view.Spinner({
4707              priority: -20
4708          }) );
4709  
4710          if ( showFilterByType || this.options.date ) {
4711              /*
4712               * Create a h2 heading before the select elements that filter attachments.
4713               * This heading is visible in the modal and visually hidden in the grid.
4714               */
4715              this.toolbar.set( 'filters-heading', new wp.media.view.Heading( {
4716                  priority:   -100,
4717                  text:       l10n.filterAttachments,
4718                  level:      'h2',
4719                  className:  'media-attachments-filter-heading screen-reader-text'
4720              }).render() );
4721          }
4722  
4723          if ( showFilterByType ) {
4724              // "Filters" is a <select>, a label element needs to be rendered before.
4725              this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
4726                  value: l10n.filterByType,
4727                  attributes: {
4728                      'for':  'media-attachment-filters'
4729                  },
4730                  priority:   -80
4731              }).render() );
4732  
4733              if ( 'uploaded' === this.options.filters ) {
4734                  this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({
4735                      controller: this.controller,
4736                      model:      this.collection.props,
4737                      priority:   -80
4738                  }).render() );
4739              } else {
4740                  Filters = new wp.media.view.AttachmentFilters.All({
4741                      controller: this.controller,
4742                      model:      this.collection.props,
4743                      priority:   -80
4744                  });
4745  
4746                  this.toolbar.set( 'filters', Filters.render() );
4747              }
4748          }
4749  
4750          /*
4751           * Feels odd to bring the global media library switcher into the Attachment browser view.
4752           * Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
4753           * which the controller can tap into and add this view?
4754           */
4755          if ( this.controller.isModeActive( 'grid' ) ) {
4756              LibraryViewSwitcher = View.extend({
4757                  className: 'view-switch media-grid-view-switch',
4758                  template: wp.template( 'media-library-view-switcher')
4759              });
4760  
4761              this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
4762                  controller: this.controller,
4763                  priority: -90
4764              }).render() );
4765  
4766              // DateFilter is a <select>, a label element needs to be rendered before.
4767              this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
4768                  value: l10n.filterByDate,
4769                  attributes: {
4770                      'for': 'media-attachment-date-filters'
4771                  },
4772                  priority: -75
4773              }).render() );
4774              this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
4775                  controller: this.controller,
4776                  model:      this.collection.props,
4777                  priority:   -75,
4778              }).render() );
4779  
4780              // BulkSelection is a <div> with subviews, including screen reader text.
4781              this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
4782                  text: l10n.bulkSelect,
4783                  controller: this.controller,
4784                  priority: -70
4785              }).render() );
4786  
4787              this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
4788                  filters: Filters,
4789                  style: 'primary',
4790                  disabled: true,
4791                  text: mediaTrash ? l10n.trashSelected : l10n.deletePermanently,
4792                  controller: this.controller,
4793                  priority: -80,
4794                  click: function() {
4795                      var changed = [], removed = [],
4796                          selection = this.controller.state().get( 'selection' ),
4797                          library = this.controller.state().get( 'library' );
4798  
4799                      if ( ! selection.length ) {
4800                          return;
4801                      }
4802  
4803                      if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
4804                          return;
4805                      }
4806  
4807                      if ( mediaTrash &&
4808                          'trash' !== selection.at( 0 ).get( 'status' ) &&
4809                          ! window.confirm( l10n.warnBulkTrash ) ) {
4810  
4811                          return;
4812                      }
4813  
4814                      selection.each( function( model ) {
4815                          if ( ! model.get( 'nonces' )['delete'] ) {
4816                              removed.push( model );
4817                              return;
4818                          }
4819  
4820                          if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
4821                              model.set( 'status', 'inherit' );
4822                              changed.push( model.save() );
4823                              removed.push( model );
4824                          } else if ( mediaTrash ) {
4825                              model.set( 'status', 'trash' );
4826                              changed.push( model.save() );
4827                              removed.push( model );
4828                          } else {
4829                              model.destroy({wait: true});
4830                          }
4831                      } );
4832  
4833                      if ( changed.length ) {
4834                          selection.remove( removed );
4835  
4836                          $.when.apply( null, changed ).then( _.bind( function() {
4837                              library._requery( true );
4838                              this.controller.trigger( 'selection:action:done' );
4839                          }, this ) );
4840                      } else {
4841                          this.controller.trigger( 'selection:action:done' );
4842                      }
4843                  }
4844              }).render() );
4845  
4846              if ( mediaTrash ) {
4847                  this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
4848                      filters: Filters,
4849                      style: 'link button-link-delete',
4850                      disabled: true,
4851                      text: l10n.deletePermanently,
4852                      controller: this.controller,
4853                      priority: -55,
4854                      size: '',
4855                      click: function() {
4856                          var removed = [],
4857                              destroy = [],
4858                              selection = this.controller.state().get( 'selection' );
4859  
4860                          if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
4861                              return;
4862                          }
4863  
4864                          selection.each( function( model ) {
4865                              if ( ! model.get( 'nonces' )['delete'] ) {
4866                                  removed.push( model );
4867                                  return;
4868                              }
4869  
4870                              destroy.push( model );
4871                          } );
4872  
4873                          if ( removed.length ) {
4874                              selection.remove( removed );
4875                          }
4876  
4877                          if ( destroy.length ) {
4878                              $.when.apply( null, destroy.map( function (item) {
4879                                  return item.destroy();
4880                              } ) ).then( _.bind( function() {
4881                                  this.controller.trigger( 'selection:action:done' );
4882                              }, this ) );
4883                          }
4884                      }
4885                  }).render() );
4886              }
4887  
4888          } else if ( this.options.date ) {
4889              // DateFilter is a <select>, a label element needs to be rendered before.
4890              this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
4891                  value: l10n.filterByDate,
4892                  attributes: {
4893                      'for': 'media-attachment-date-filters'
4894                  },
4895                  priority: -75
4896              }).render() );
4897              this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
4898                  controller: this.controller,
4899                  model:      this.collection.props,
4900                  priority:   -75
4901              }).render() );
4902          }
4903  
4904          if ( this.options.search ) {
4905              // Search is an input, a label element needs to be rendered before.
4906              this.toolbar.set( 'searchLabel', new wp.media.view.Label({
4907                  value: l10n.searchLabel,
4908                  className: 'media-search-input-label',
4909                  attributes: {
4910                      'for': 'media-search-input'
4911                  },
4912                  priority:   60
4913              }).render() );
4914              this.toolbar.set( 'search', new wp.media.view.Search({
4915                  controller: this.controller,
4916                  model:      this.collection.props,
4917                  priority:   60
4918              }).render() );
4919          }
4920  
4921          if ( this.options.dragInfo ) {
4922              this.toolbar.set( 'dragInfo', new View({
4923                  el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
4924                  priority: -40
4925              }) );
4926          }
4927  
4928          if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
4929              this.toolbar.set( 'suggestedDimensions', new View({
4930                  el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0],
4931                  priority: -40
4932              }) );
4933          }
4934      },
4935  
4936      /**
4937       * Updates the content of the attachments browser.
4938       */
4939      updateContent: function() {
4940          var view = this,
4941              noItemsView;
4942  
4943          if ( this.controller.isModeActive( 'grid' ) ) {
4944              // Usually the media library.
4945              noItemsView = view.attachmentsNoResults;
4946          } else {
4947              // Usually the media modal.
4948              noItemsView = view.uploader;
4949          }
4950  
4951          if ( ! this.collection.length ) {
4952              this.toolbar.get( 'spinner' ).show();
4953              this.toolbar.$( '.media-bg-overlay' ).show();
4954              this.dfd = this.collection.more().done( function() {
4955                  if ( ! view.collection.length ) {
4956                      noItemsView.$el.removeClass( 'hidden' );
4957                  } else {
4958                      noItemsView.$el.addClass( 'hidden' );
4959                  }
4960                  view.toolbar.get( 'spinner' ).hide();
4961                  view.toolbar.$( '.media-bg-overlay' ).hide();
4962              } );
4963          } else {
4964              noItemsView.$el.addClass( 'hidden' );
4965              view.toolbar.get( 'spinner' ).hide();
4966              this.toolbar.$( '.media-bg-overlay' ).hide();
4967          }
4968      },
4969  
4970      /**
4971       * Creates the uploader view.
4972       */
4973      createUploader: function() {
4974          this.uploader = new wp.media.view.UploaderInline({
4975              controller: this.controller,
4976              status:     false,
4977              message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
4978              canClose:   this.controller.isModeActive( 'grid' )
4979          });
4980  
4981          this.uploader.$el.addClass( 'hidden' );
4982          this.views.add( this.uploader );
4983      },
4984  
4985      /**
4986       * Toggles the uploader view.
4987       */
4988      toggleUploader: function() {
4989          if ( this.uploader.$el.hasClass( 'hidden' ) ) {
4990              this.uploader.show();
4991          } else {
4992              this.uploader.hide();
4993          }
4994      },
4995  
4996      /**
4997       * Creates the Attachments wrapper view.
4998       *
4999       * @since 5.8.0
5000       *
5001       * @return {void}
5002       */
5003      createAttachmentsWrapperView: function() {
5004          this.attachmentsWrapper = new wp.media.View( {
5005              className: 'attachments-wrapper'
5006          } );
5007  
5008          // Create the list of attachments.
5009          this.views.add( this.attachmentsWrapper );
5010          this.createAttachments();
5011      },
5012  
5013      /**
5014       * Creates the attachments view.
5015       */
5016      createAttachments: function() {
5017          this.attachments = new wp.media.view.Attachments({
5018              controller:           this.controller,
5019              collection:           this.collection,
5020              selection:            this.options.selection,
5021              model:                this.model,
5022              sortable:             this.options.sortable,
5023              scrollElement:        this.options.scrollElement,
5024              idealColumnWidth:     this.options.idealColumnWidth,
5025  
5026              // The single `Attachment` view to be used in the `Attachments` view.
5027              AttachmentView: this.options.AttachmentView
5028          });
5029  
5030          // Add keydown listener to the instance of the Attachments view.
5031          this.controller.on( 'attachment:keydown:arrow',     _.bind( this.attachments.arrowEvent, this.attachments ) );
5032          this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) );
5033  
5034          this.views.add( '.attachments-wrapper', this.attachments );
5035  
5036          if ( this.controller.isModeActive( 'grid' ) ) {
5037              this.attachmentsNoResults = new View({
5038                  controller: this.controller,
5039                  tagName: 'p'
5040              });
5041  
5042              this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
5043              this.attachmentsNoResults.$el.html( l10n.noMedia );
5044  
5045              this.views.add( this.attachmentsNoResults );
5046          }
5047      },
5048  
5049      /**
5050       * Creates the load more button and attachments counter view.
5051       *
5052       * @since 5.8.0
5053       *
5054       * @return {void}
5055       */
5056      createLoadMoreView: function() {
5057          var view = this;
5058  
5059          this.loadMoreWrapper = new View( {
5060              controller: this.controller,
5061              className: 'load-more-wrapper'
5062          } );
5063  
5064          this.loadMoreCount = new View( {
5065              controller: this.controller,
5066              tagName: 'p',
5067              className: 'load-more-count hidden'
5068          } );
5069  
5070          this.loadMoreButton = new wp.media.view.Button( {
5071              text: __( 'Load more' ),
5072              className: 'load-more hidden',
5073              style: 'primary',
5074              size: '',
5075              click: function() {
5076                  view.loadMoreAttachments();
5077              }
5078          } );
5079  
5080          this.loadMoreSpinner = new wp.media.view.Spinner();
5081  
5082          this.loadMoreJumpToFirst = new wp.media.view.Button( {
5083              text: __( 'Jump to first loaded item' ),
5084              className: 'load-more-jump hidden',
5085              size: '',
5086              click: function() {
5087                  view.jumpToFirstAddedItem();
5088              }
5089          } );
5090  
5091          this.views.add( '.attachments-wrapper', this.loadMoreWrapper );
5092          this.views.add( '.load-more-wrapper', this.loadMoreSpinner );
5093          this.views.add( '.load-more-wrapper', this.loadMoreCount );
5094          this.views.add( '.load-more-wrapper', this.loadMoreButton );
5095          this.views.add( '.load-more-wrapper', this.loadMoreJumpToFirst );
5096      },
5097  
5098      /**
5099       * Updates the Load More view. This function is debounced because the
5100       * collection updates multiple times at the add, remove, and reset events.
5101       * We need it to run only once, after all attachments are added or removed.
5102       *
5103       * @since 5.8.0
5104       *
5105       * @return {void}
5106       */
5107      updateLoadMoreView: _.debounce( function() {
5108          // Ensure the load more view elements are initially hidden at each update.
5109          this.loadMoreButton.$el.addClass( 'hidden' );
5110          this.loadMoreCount.$el.addClass( 'hidden' );
5111          this.loadMoreJumpToFirst.$el.addClass( 'hidden' ).prop( 'disabled', true );
5112  
5113          if ( ! this.collection.getTotalAttachments() ) {
5114              return;
5115          }
5116  
5117          if ( this.collection.length ) {
5118              this.loadMoreCount.$el.text(
5119                  /* translators: 1: Number of displayed attachments, 2: Number of total attachments. */
5120                  sprintf(
5121                      __( 'Showing %1$s of %2$s media items' ),
5122                      this.collection.length,
5123                      this.collection.getTotalAttachments()
5124                  )
5125              );
5126  
5127              this.loadMoreCount.$el.removeClass( 'hidden' );
5128          }
5129  
5130          /*
5131           * Notice that while the collection updates multiple times hasMore() may
5132           * return true when it's actually not true.
5133           */
5134          if ( this.collection.hasMore() ) {
5135              this.loadMoreButton.$el.removeClass( 'hidden' );
5136          }
5137  
5138          // Find the media item to move focus to. The jQuery `eq()` index is zero-based.
5139          this.firstAddedMediaItem = this.$el.find( '.attachment' ).eq( this.firstAddedMediaItemIndex );
5140  
5141          // If there's a media item to move focus to, make the "Jump to" button available.
5142          if ( this.firstAddedMediaItem.length ) {
5143              this.firstAddedMediaItem.addClass( 'new-media' );
5144              this.loadMoreJumpToFirst.$el.removeClass( 'hidden' ).prop( 'disabled', false );
5145          }
5146  
5147          // If there are new items added, but no more to be added, move focus to Jump button.
5148          if ( this.firstAddedMediaItem.length && ! this.collection.hasMore() ) {
5149              this.loadMoreJumpToFirst.$el.trigger( 'focus' );
5150          }
5151      }, 10 ),
5152  
5153      /**
5154       * Loads more attachments.
5155       *
5156       * @since 5.8.0
5157       *
5158       * @return {void}
5159       */
5160      loadMoreAttachments: function() {
5161          var view = this;
5162  
5163          if ( ! this.collection.hasMore() ) {
5164              return;
5165          }
5166  
5167          /*
5168           * The collection index is zero-based while the length counts the actual
5169           * amount of items. Thus the length is equivalent to the position of the
5170           * first added item.
5171           */
5172          this.firstAddedMediaItemIndex = this.collection.length;
5173  
5174          this.$el.addClass( 'more-loaded' );
5175          this.collection.each( function( attachment ) {
5176              var attach_id = attachment.attributes.id;
5177              $( '[data-id="' + attach_id + '"]' ).addClass( 'found-media' );
5178          });
5179  
5180          view.loadMoreSpinner.show();
5181          this.collection.once( 'attachments:received', function() {
5182              view.loadMoreSpinner.hide();
5183          } );
5184          this.collection.more();
5185      },
5186  
5187      /**
5188       * Moves focus to the first new added item.    .
5189       *
5190       * @since 5.8.0
5191       *
5192       * @return {void}
5193       */
5194      jumpToFirstAddedItem: function() {
5195          // Set focus on first added item.
5196          this.firstAddedMediaItem.focus();
5197      },
5198  
5199      /**
5200       * Creates the attachments heading view.
5201       */
5202      createAttachmentsHeading: function() {
5203          this.attachmentsHeading = new wp.media.view.Heading( {
5204              text: l10n.attachmentsList,
5205              level: 'h2',
5206              className: 'media-views-heading screen-reader-text'
5207          } );
5208          this.views.add( this.attachmentsHeading );
5209      },
5210  
5211      /**
5212       * Creates the sidebar view.
5213       */
5214      createSidebar: function() {
5215          var options = this.options,
5216              selection = options.selection,
5217              sidebar = this.sidebar = new wp.media.view.Sidebar({
5218                  controller: this.controller
5219              });
5220  
5221          this.views.add( sidebar );
5222  
5223          if ( this.controller.uploader ) {
5224              sidebar.set( 'uploads', new wp.media.view.UploaderStatus({
5225                  controller: this.controller,
5226                  priority:   40
5227              }) );
5228          }
5229  
5230          selection.on( 'selection:single', this.createSingle, this );
5231          selection.on( 'selection:unsingle', this.disposeSingle, this );
5232  
5233          if ( selection.single() ) {
5234              this.createSingle();
5235          }
5236      },
5237  
5238      /**
5239       * Creates the single attachment view.
5240       */
5241      createSingle: function() {
5242          var sidebar = this.sidebar,
5243              single = this.options.selection.single();
5244  
5245          sidebar.set( 'details', new wp.media.view.Attachment.Details({
5246              controller: this.controller,
5247              model:      single,
5248              priority:   80
5249          }) );
5250  
5251          sidebar.set( 'compat', new wp.media.view.AttachmentCompat({
5252              controller: this.controller,
5253              model:      single,
5254              priority:   120
5255          }) );
5256  
5257          if ( this.options.display ) {
5258              sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({
5259                  controller:   this.controller,
5260                  model:        this.model.display( single ),
5261                  attachment:   single,
5262                  priority:     160,
5263                  userSettings: this.model.get('displayUserSettings')
5264              }) );
5265          }
5266  
5267          // Show the sidebar on mobile.
5268          if ( this.model.id === 'insert' ) {
5269              sidebar.$el.addClass( 'visible' );
5270          }
5271      },
5272  
5273      /**
5274       * Disposes of the single attachment view.
5275       */
5276      disposeSingle: function() {
5277          var sidebar = this.sidebar;
5278          sidebar.unset('details');
5279          sidebar.unset('compat');
5280          sidebar.unset('display');
5281          // Hide the sidebar on mobile.
5282          sidebar.$el.removeClass( 'visible' );
5283      }
5284  });
5285  
5286  module.exports = AttachmentsBrowser;
5287  
5288  
5289  /***/ },
5290  
5291  /***/ 3479
5292  (module) {
5293  
5294  var Attachments = wp.media.view.Attachments,
5295      Selection;
5296  
5297  /**
5298   * wp.media.view.Attachments.Selection
5299   *
5300   * @memberOf wp.media.view.Attachments
5301   *
5302   * @class
5303   * @augments wp.media.view.Attachments
5304   * @augments wp.media.View
5305   * @augments wp.Backbone.View
5306   * @augments Backbone.View
5307   */
5308  Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{
5309      events: {},
5310      initialize: function() {
5311          _.defaults( this.options, {
5312              sortable:   false,
5313              resize:     false,
5314  
5315              // The single `Attachment` view to be used in the `Attachments` view.
5316              AttachmentView: wp.media.view.Attachment.Selection
5317          });
5318          // Call 'initialize' directly on the parent class.
5319          return Attachments.prototype.initialize.apply( this, arguments );
5320      }
5321  });
5322  
5323  module.exports = Selection;
5324  
5325  
5326  /***/ },
5327  
5328  /***/ 168
5329  (module) {
5330  
5331  var $ = Backbone.$,
5332      ButtonGroup;
5333  
5334  /**
5335   * wp.media.view.ButtonGroup
5336   *
5337   * @memberOf wp.media.view
5338   *
5339   * @class
5340   * @augments wp.media.View
5341   * @augments wp.Backbone.View
5342   * @augments Backbone.View
5343   */
5344  ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{
5345      tagName:   'div',
5346      className: 'button-group button-large media-button-group',
5347  
5348      initialize: function() {
5349          /**
5350           * @member {wp.media.view.Button[]}
5351           */
5352          this.buttons = _.map( this.options.buttons || [], function( button ) {
5353              if ( button instanceof Backbone.View ) {
5354                  return button;
5355              } else {
5356                  return new wp.media.view.Button( button ).render();
5357              }
5358          });
5359  
5360          delete this.options.buttons;
5361  
5362          if ( this.options.classes ) {
5363              this.$el.addClass( this.options.classes );
5364          }
5365      },
5366  
5367      /**
5368       * Renders the button group.
5369       *
5370       * @return {wp.media.view.ButtonGroup} The button group.
5371       */
5372      render: function() {
5373          this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
5374          return this;
5375      }
5376  });
5377  
5378  module.exports = ButtonGroup;
5379  
5380  
5381  /***/ },
5382  
5383  /***/ 846
5384  (module) {
5385  
5386  /**
5387   * wp.media.view.Button
5388   *
5389   * @memberOf wp.media.view
5390   *
5391   * @class
5392   * @augments wp.media.View
5393   * @augments wp.Backbone.View
5394   * @augments Backbone.View
5395   */
5396  var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{
5397      tagName:    'button',
5398      className:  'media-button',
5399      attributes: { type: 'button' },
5400  
5401      events: {
5402          'click': 'click'
5403      },
5404  
5405      defaults: {
5406          text:     '',
5407          style:    '',
5408          size:     'large',
5409          disabled: false
5410      },
5411  
5412      initialize: function() {
5413          /**
5414           * Create a model with the provided `defaults`.
5415           *
5416           * @member {Backbone.Model}
5417           */
5418          this.model = new Backbone.Model( this.defaults );
5419  
5420          // If any of the `options` have a key from `defaults`, apply its
5421          // value to the `model` and remove it from the `options` object.
5422          _.each( this.defaults, function( def, key ) {
5423              var value = this.options[ key ];
5424              if ( _.isUndefined( value ) ) {
5425                  return;
5426              }
5427  
5428              this.model.set( key, value );
5429              delete this.options[ key ];
5430          }, this );
5431  
5432          this.listenTo( this.model, 'change', this.render );
5433      },
5434      /**
5435       * Renders the button.
5436       *
5437       * @return {wp.media.view.Button} Returns itself to allow chaining.
5438       */
5439      render: function() {
5440          var classes = [ 'button', this.className ],
5441              model = this.model.toJSON();
5442  
5443          if ( model.style ) {
5444              classes.push( 'button-' + model.style );
5445          }
5446  
5447          if ( model.size ) {
5448              classes.push( 'button-' + model.size );
5449          }
5450  
5451          classes = _.uniq( classes.concat( this.options.classes ) );
5452          this.el.className = classes.join(' ');
5453  
5454          this.$el.prop( 'disabled', model.disabled );
5455          this.$el.text( this.model.get('text') );
5456  
5457          return this;
5458      },
5459      /**
5460       * Handles the click event.
5461       *
5462       * @param {JQuery.Event} event The jQuery event object for the click action.
5463       */
5464      click: function( event ) {
5465          if ( '#' === this.attributes.href ) {
5466              event.preventDefault();
5467          }
5468  
5469          if ( this.options.click && ! this.model.get('disabled') ) {
5470              this.options.click.apply( this, arguments );
5471          }
5472      }
5473  });
5474  
5475  module.exports = Button;
5476  
5477  
5478  /***/ },
5479  
5480  /***/ 7637
5481  (module) {
5482  
5483  var View = wp.media.View,
5484      UploaderStatus = wp.media.view.UploaderStatus,
5485      l10n = wp.media.view.l10n,
5486      $ = jQuery,
5487      Cropper;
5488  
5489  /**
5490   * wp.media.view.Cropper
5491   *
5492   * Uses the imgAreaSelect plugin to allow a user to crop an image.
5493   *
5494   * Takes imgAreaSelect options from
5495   * wp.customize.HeaderControl.calculateImageSelectOptions via
5496   * wp.customize.HeaderControl.openMM.
5497   *
5498   * @memberOf wp.media.view
5499   *
5500   * @class
5501   * @augments wp.media.View
5502   * @augments wp.Backbone.View
5503   * @augments Backbone.View
5504   */
5505  Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{
5506      className: 'crop-content',
5507      template: wp.template('crop-content'),
5508      initialize: function() {
5509          _.bindAll(this, 'onImageLoad');
5510      },
5511      ready: function() {
5512          this.controller.frame.on('content:error:crop', this.onError, this);
5513          this.$image = this.$el.find('.crop-image');
5514          this.$image.on('load', this.onImageLoad);
5515          $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
5516      },
5517      remove: function() {
5518          $(window).off('resize.cropper');
5519          this.$el.remove();
5520          this.$el.off();
5521          View.prototype.remove.apply(this, arguments);
5522      },
5523      prepare: function() {
5524          return {
5525              title: l10n.cropYourImage,
5526              url: this.options.attachment.get('url')
5527          };
5528      },
5529      onImageLoad: function() {
5530          var imgOptions = this.controller.get('imgSelectOptions'),
5531              imgSelect;
5532  
5533          if (typeof imgOptions === 'function') {
5534              imgOptions = imgOptions(this.options.attachment, this.controller);
5535          }
5536  
5537          imgOptions = _.extend(imgOptions, {
5538              parent: this.$el,
5539              onInit: function() {
5540  
5541                  // Store the set ratio.
5542                  var setRatio = imgSelect.getOptions().aspectRatio;
5543  
5544                  // On mousedown, if no ratio is set and the Shift key is down, use a 1:1 ratio.
5545                  this.parent.children().on( 'mousedown touchstart', function( e ) {
5546  
5547                      // If no ratio is set and the shift key is down, use a 1:1 ratio.
5548                      if ( ! setRatio && e.shiftKey ) {
5549                          imgSelect.setOptions( {
5550                              aspectRatio: '1:1'
5551                          } );
5552                      }
5553                  } );
5554  
5555                  this.parent.children().on( 'mouseup touchend', function() {
5556  
5557                      // Restore the set ratio.
5558                      imgSelect.setOptions( {
5559                          aspectRatio: setRatio ? setRatio : false
5560                      } );
5561                  } );
5562              }
5563          } );
5564          this.trigger('image-loaded');
5565          imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
5566      },
5567      onError: function() {
5568          var filename = this.options.attachment.get('filename');
5569  
5570          this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
5571              filename: UploaderStatus.prototype.filename(filename),
5572              message: window._wpMediaViewsL10n.cropError
5573          }), { at: 0 });
5574      }
5575  });
5576  
5577  module.exports = Cropper;
5578  
5579  
5580  /***/ },
5581  
5582  /***/ 6126
5583  (module) {
5584  
5585  var View = wp.media.View,
5586      EditImage;
5587  
5588  /**
5589   * wp.media.view.EditImage
5590   *
5591   * @memberOf wp.media.view
5592   *
5593   * @class
5594   * @augments wp.media.View
5595   * @augments wp.Backbone.View
5596   * @augments Backbone.View
5597   */
5598  EditImage = View.extend(/** @lends wp.media.view.EditImage.prototype */{
5599      className: 'image-editor',
5600      template: wp.template('image-editor'),
5601  
5602      initialize: function( options ) {
5603          this.editor = window.imageEdit;
5604          this.controller = options.controller;
5605          View.prototype.initialize.apply( this, arguments );
5606      },
5607  
5608      prepare: function() {
5609          return this.model.toJSON();
5610      },
5611  
5612      loadEditor: function() {
5613          this.editor.open( this.model.get( 'id' ), this.model.get( 'nonces' ).edit, this );
5614      },
5615  
5616      back: function() {
5617          var lastState = this.controller.lastState();
5618          this.controller.setState( lastState );
5619      },
5620  
5621      refresh: function() {
5622          this.model.fetch();
5623      },
5624  
5625      save: function() {
5626          var lastState = this.controller.lastState();
5627  
5628          this.model.fetch().done( _.bind( function() {
5629              this.controller.setState( lastState );
5630          }, this ) );
5631      }
5632  
5633  });
5634  
5635  module.exports = EditImage;
5636  
5637  
5638  /***/ },
5639  
5640  /***/ 5741
5641  (module) {
5642  
5643  /**
5644   * wp.media.view.Embed
5645   *
5646   * @memberOf wp.media.view
5647   *
5648   * @class
5649   * @augments wp.media.View
5650   * @augments wp.Backbone.View
5651   * @augments Backbone.View
5652   */
5653  var Embed = wp.media.View.extend(/** @lends wp.media.view.Embed.prototype */{
5654      className: 'media-embed',
5655  
5656      /**
5657       * Initializes the embed view.
5658       */
5659      initialize: function() {
5660          /**
5661           * @member {wp.media.view.EmbedUrl}
5662           */
5663          this.url = new wp.media.view.EmbedUrl({
5664              controller: this.controller,
5665              model:      this.model.props
5666          }).render();
5667  
5668          this.views.set([ this.url ]);
5669          this.refresh();
5670          this.listenTo( this.model, 'change:type', this.refresh );
5671          this.listenTo( this.model, 'change:loading', this.loading );
5672      },
5673  
5674      /**
5675       * Sets the settings for the embed view.
5676       *
5677       * @param {Object} view The view object to be set as the settings for the embed view.
5678       */
5679      settings: function( view ) {
5680          if ( this._settings ) {
5681              this._settings.remove();
5682          }
5683          this._settings = view;
5684          this.views.add( view );
5685      },
5686  
5687      /**
5688       * Refreshes the embed view based on the type of embed.
5689       */
5690      refresh: function() {
5691          var type = this.model.get('type'),
5692              constructor;
5693  
5694          if ( 'image' === type ) {
5695              constructor = wp.media.view.EmbedImage;
5696          } else if ( 'link' === type ) {
5697              constructor = wp.media.view.EmbedLink;
5698          } else {
5699              return;
5700          }
5701  
5702          this.settings( new constructor({
5703              controller: this.controller,
5704              model:      this.model.props,
5705              priority:   40
5706          }) );
5707      },
5708  
5709      /**
5710       * Toggles the loading state of the embed view.
5711       */
5712      loading: function() {
5713          this.$el.toggleClass( 'embed-loading', this.model.get('loading') );
5714      }
5715  });
5716  
5717  module.exports = Embed;
5718  
5719  
5720  /***/ },
5721  
5722  /***/ 2395
5723  (module) {
5724  
5725  var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
5726      EmbedImage;
5727  
5728  /**
5729   * wp.media.view.EmbedImage
5730   *
5731   * @memberOf wp.media.view
5732   *
5733   * @class
5734   * @augments wp.media.view.Settings.AttachmentDisplay
5735   * @augments wp.media.view.Settings
5736   * @augments wp.media.View
5737   * @augments wp.Backbone.View
5738   * @augments Backbone.View
5739   */
5740  EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{
5741      className: 'embed-media-settings',
5742      template:  wp.template('embed-image-settings'),
5743  
5744      initialize: function() {
5745          /**
5746           * Call `initialize` directly on parent class with passed arguments
5747           */
5748          AttachmentDisplay.prototype.initialize.apply( this, arguments );
5749          this.listenTo( this.model, 'change:url', this.updateImage );
5750      },
5751  
5752      updateImage: function() {
5753          this.$('img').attr( 'src', this.model.get('url') );
5754      }
5755  });
5756  
5757  module.exports = EmbedImage;
5758  
5759  
5760  /***/ },
5761  
5762  /***/ 8232
5763  (module) {
5764  
5765  var $ = jQuery,
5766      EmbedLink;
5767  
5768  /**
5769   * wp.media.view.EmbedLink
5770   *
5771   * @memberOf wp.media.view
5772   *
5773   * @class
5774   * @augments wp.media.view.Settings
5775   * @augments wp.media.View
5776   * @augments wp.Backbone.View
5777   * @augments Backbone.View
5778   */
5779  EmbedLink = wp.media.view.Settings.extend(/** @lends wp.media.view.EmbedLink.prototype */{
5780      className: 'embed-link-settings',
5781      template:  wp.template('embed-link-settings'),
5782  
5783      initialize: function() {
5784          this.listenTo( this.model, 'change:url', this.updateoEmbed );
5785      },
5786  
5787      updateoEmbed: _.debounce( function() {
5788          var url = this.model.get( 'url' );
5789  
5790          // Clear out previous results.
5791          this.$('.embed-container').hide().find('.embed-preview').empty();
5792          this.$( '.setting' ).hide();
5793  
5794          // Only proceed with embed if the field contains more than 11 characters.
5795          // Example: http://a.io is 11 chars
5796          if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {
5797              return;
5798          }
5799  
5800          this.fetch();
5801      }, wp.media.controller.Embed.sensitivity ),
5802  
5803      fetch: function() {
5804          var url = this.model.get( 'url' ), re, youTubeEmbedMatch;
5805  
5806          // Check if they haven't typed in 500 ms.
5807          if ( $('#embed-url-field').val() !== url ) {
5808              return;
5809          }
5810  
5811          if ( this.dfd && 'pending' === this.dfd.state() ) {
5812              this.dfd.abort();
5813          }
5814  
5815          // Support YouTube embed urls, since they work once in the editor.
5816          re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/;
5817          youTubeEmbedMatch = re.exec( url );
5818          if ( youTubeEmbedMatch ) {
5819              url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ];
5820          }
5821  
5822          this.dfd = wp.apiRequest({
5823              url: wp.media.view.settings.oEmbedProxyUrl,
5824              data: {
5825                  url: url,
5826                  maxwidth: this.model.get( 'width' ),
5827                  maxheight: this.model.get( 'height' )
5828              },
5829              type: 'GET',
5830              dataType: 'json',
5831              context: this
5832          })
5833              .done( function( response ) {
5834                  this.renderoEmbed( {
5835                      data: {
5836                          body: response.html || ''
5837                      }
5838                  } );
5839              } )
5840              .fail( this.renderFail );
5841      },
5842  
5843      renderFail: function ( response, status ) {
5844          if ( 'abort' === status ) {
5845              return;
5846          }
5847          this.$( '.link-text' ).show();
5848      },
5849  
5850      renderoEmbed: function( response ) {
5851          var html = ( response && response.data && response.data.body ) || '';
5852  
5853          if ( html ) {
5854              this.$('.embed-container').show().find('.embed-preview').html( html );
5855          } else {
5856              this.renderFail();
5857          }
5858      }
5859  });
5860  
5861  module.exports = EmbedLink;
5862  
5863  
5864  /***/ },
5865  
5866  /***/ 7327
5867  (module) {
5868  
5869  var View = wp.media.View,
5870      $ = jQuery,
5871      l10n = wp.media.view.l10n,
5872      EmbedUrl;
5873  
5874  /**
5875   * wp.media.view.EmbedUrl
5876   *
5877   * @memberOf wp.media.view
5878   *
5879   * @class
5880   * @augments wp.media.View
5881   * @augments wp.Backbone.View
5882   * @augments Backbone.View
5883   */
5884  EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{
5885      tagName:   'span',
5886      className: 'embed-url',
5887  
5888      events: {
5889          'input': 'url'
5890      },
5891  
5892      initialize: function() {
5893          this.$input = $( '<input id="embed-url-field" type="url" />' )
5894              .attr( 'aria-label', l10n.insertFromUrlTitle )
5895              .val( this.model.get('url') );
5896          this.input = this.$input[0];
5897  
5898          this.spinner = $('<span class="spinner" />')[0];
5899          this.$el.append([ this.input, this.spinner ]);
5900  
5901          this.listenTo( this.model, 'change:url', this.render );
5902  
5903          if ( this.model.get( 'url' ) ) {
5904              _.delay( _.bind( function () {
5905                  this.model.trigger( 'change:url' );
5906              }, this ), 500 );
5907          }
5908      },
5909      /**
5910       * Renders the view.
5911       *
5912       * @return {void|wp.media.view.EmbedUrl} Returns itself to allow chaining.
5913       */
5914      render: function() {
5915          var $input = this.$input;
5916  
5917          if ( $input.is(':focus') ) {
5918              return;
5919          }
5920  
5921          if ( this.model.get( 'url' ) ) {
5922              this.input.value = this.model.get('url');
5923          } else {
5924              this.input.setAttribute( 'placeholder', 'https://' );
5925          }
5926  
5927          /**
5928           * Call `render` directly on parent class with passed arguments
5929           */
5930          View.prototype.render.apply( this, arguments );
5931          return this;
5932      },
5933  
5934      url: function( event ) {
5935          var url = event.target.value || '';
5936          this.model.set( 'url', url.trim() );
5937      }
5938  });
5939  
5940  module.exports = EmbedUrl;
5941  
5942  
5943  /***/ },
5944  
5945  /***/ 718
5946  (module) {
5947  
5948  var $ = jQuery;
5949  
5950  /**
5951   * wp.media.view.FocusManager
5952   *
5953   * @memberOf wp.media.view
5954   *
5955   * @class
5956   * @augments wp.media.View
5957   * @augments wp.Backbone.View
5958   * @augments Backbone.View
5959   */
5960  var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{
5961  
5962      events: {
5963          'keydown': 'focusManagementMode'
5964      },
5965  
5966      /**
5967       * Initializes the Focus Manager.
5968       *
5969       * @param {Object} options The Focus Manager options.
5970       *
5971       * @since 5.3.0
5972       *
5973       * @return {void}
5974       */
5975      initialize: function( options ) {
5976          this.mode                    = options.mode || 'constrainTabbing';
5977          this.tabsAutomaticActivation = options.tabsAutomaticActivation || false;
5978      },
5979  
5980       /**
5981     * Determines which focus management mode to use.
5982     *
5983     * @since 5.3.0
5984     *
5985     * @param {Object} event jQuery event object.
5986     *
5987     * @return {void}
5988     */
5989      focusManagementMode: function( event ) {
5990          if ( this.mode === 'constrainTabbing' ) {
5991              this.constrainTabbing( event );
5992          }
5993  
5994          if ( this.mode === 'tabsNavigation' ) {
5995              this.tabsNavigation( event );
5996          }
5997      },
5998  
5999      /**
6000       * Gets all the tabbable elements.
6001       *
6002       * @since 5.3.0
6003       *
6004       * @return {Object} A jQuery collection of tabbable elements.
6005       */
6006      getTabbables: function() {
6007          // Skip the file input added by Plupload.
6008          return this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
6009      },
6010  
6011      /**
6012       * Moves focus to the modal dialog.
6013       *
6014       * @since 3.5.0
6015       *
6016       * @return {void}
6017       */
6018      focus: function() {
6019          this.$( '.media-modal' ).trigger( 'focus' );
6020      },
6021  
6022      /**
6023       * Constrains navigation with the Tab key within the media view element.
6024       *
6025       * @since 4.0.0
6026       *
6027       * @param {Object} event A keydown jQuery event.
6028       *
6029       * @return {void}
6030       */
6031      constrainTabbing: function( event ) {
6032          var tabbables;
6033  
6034          // Look for the tab key.
6035          if ( 9 !== event.keyCode ) {
6036              return;
6037          }
6038  
6039          tabbables = this.getTabbables();
6040  
6041          // Keep tab focus within media modal while it's open.
6042          if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
6043              tabbables.first().focus();
6044              return false;
6045          } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
6046              tabbables.last().focus();
6047              return false;
6048          }
6049      },
6050  
6051      /**
6052       * Hides from assistive technologies all the body children.
6053       *
6054       * Sets an `aria-hidden="true"` attribute on all the body children except
6055       * the provided element and other elements that should not be hidden.
6056       *
6057       * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy
6058       * in Safari 11.1 and support is spotty in other browsers. Also, `aria-modal="true"`
6059       * prevents the `wp.a11y.speak()` ARIA live regions to work as they're outside
6060       * of the modal dialog and get hidden from assistive technologies.
6061       *
6062       * @since 5.2.3
6063       *
6064       * @param {Object} visibleElement The jQuery object representing the element that should not be hidden.
6065       *
6066       * @return {void}
6067       */
6068      setAriaHiddenOnBodyChildren: function( visibleElement ) {
6069          var bodyChildren,
6070              self = this;
6071  
6072          if ( this.isBodyAriaHidden ) {
6073              return;
6074          }
6075  
6076          // Get all the body children.
6077          bodyChildren = document.body.children;
6078  
6079          // Loop through the body children and hide the ones that should be hidden.
6080          _.each( bodyChildren, function( element ) {
6081              // Don't hide the modal element.
6082              if ( element === visibleElement[0] ) {
6083                  return;
6084              }
6085  
6086              // Determine the body children to hide.
6087              if ( self.elementShouldBeHidden( element ) ) {
6088                  element.setAttribute( 'aria-hidden', 'true' );
6089                  // Store the hidden elements.
6090                  self.ariaHiddenElements.push( element );
6091              }
6092          } );
6093  
6094          this.isBodyAriaHidden = true;
6095      },
6096  
6097      /**
6098       * Unhides from assistive technologies all the body children.
6099       *
6100       * Makes visible again to assistive technologies all the body children
6101       * previously hidden and stored in this.ariaHiddenElements.
6102       *
6103       * @since 5.2.3
6104       *
6105       * @return {void}
6106       */
6107      removeAriaHiddenFromBodyChildren: function() {
6108          _.each( this.ariaHiddenElements, function( element ) {
6109              element.removeAttribute( 'aria-hidden' );
6110          } );
6111  
6112          this.ariaHiddenElements = [];
6113          this.isBodyAriaHidden   = false;
6114      },
6115  
6116      /**
6117       * Determines if the passed element should not be hidden from assistive technologies.
6118       *
6119       * @since 5.2.3
6120       *
6121       * @param {Object} element The DOM element that should be checked.
6122       *
6123       * @return {boolean} Whether the element should not be hidden from assistive technologies.
6124       */
6125      elementShouldBeHidden: function( element ) {
6126          var role = element.getAttribute( 'role' ),
6127              liveRegionsRoles = [ 'alert', 'status', 'log', 'marquee', 'timer' ];
6128  
6129          /*
6130           * Don't hide scripts, elements that already have `aria-hidden`, and
6131           * ARIA live regions.
6132           */
6133          return ! (
6134              element.tagName === 'SCRIPT' ||
6135              element.hasAttribute( 'aria-hidden' ) ||
6136              element.hasAttribute( 'aria-live' ) ||
6137              liveRegionsRoles.indexOf( role ) !== -1
6138          );
6139      },
6140  
6141      /**
6142       * Whether the body children are hidden from assistive technologies.
6143       *
6144       * @since 5.2.3
6145       */
6146      isBodyAriaHidden: false,
6147  
6148      /**
6149       * Stores an array of DOM elements that should be hidden from assistive
6150       * technologies, for example when the media modal dialog opens.
6151       *
6152       * @since 5.2.3
6153       */
6154      ariaHiddenElements: [],
6155  
6156      /**
6157       * Holds the jQuery collection of ARIA tabs.
6158       *
6159       * @since 5.3.0
6160       */
6161      tabs: $(),
6162  
6163      /**
6164       * Sets up tabs in an ARIA tabbed interface.
6165       *
6166       * @since 5.3.0
6167       *
6168       * @return {void}
6169       */
6170      setupAriaTabs: function() {
6171          this.tabs = this.$( '[role="tab"]' );
6172  
6173          // Set up initial attributes.
6174          this.tabs.attr( {
6175              'aria-selected': 'false',
6176              tabIndex: '-1'
6177          } );
6178  
6179          // Set up attributes on the initially active tab.
6180          this.tabs.filter( '.active' )
6181              .removeAttr( 'tabindex' )
6182              .attr( 'aria-selected', 'true' );
6183      },
6184  
6185      /**
6186       * Enables arrows navigation within the ARIA tabbed interface.
6187       *
6188       * @since 5.3.0
6189       *
6190       * @param {Object} event jQuery event object.
6191       *
6192       * @return {void}
6193       */
6194      tabsNavigation: function( event ) {
6195          var orientation = 'horizontal',
6196              keys = [ 32, 35, 36, 37, 38, 39, 40 ];
6197  
6198          // Return if not Spacebar, End, Home, or Arrow keys.
6199          if ( keys.indexOf( event.which ) === -1 ) {
6200              return;
6201          }
6202  
6203          // Determine navigation direction.
6204          if ( this.$el.attr( 'aria-orientation' ) === 'vertical' ) {
6205              orientation = 'vertical';
6206          }
6207  
6208          // Make Up and Down arrow keys do nothing with horizontal tabs.
6209          if ( orientation === 'horizontal' && [ 38, 40 ].indexOf( event.which ) !== -1 ) {
6210              return;
6211          }
6212  
6213          // Make Left and Right arrow keys do nothing with vertical tabs.
6214          if ( orientation === 'vertical' && [ 37, 39 ].indexOf( event.which ) !== -1 ) {
6215              return;
6216          }
6217  
6218          this.switchTabs( event, this.tabs );
6219      },
6220  
6221      /**
6222       * Switches tabs in the ARIA tabbed interface.
6223       *
6224       * @since 5.3.0
6225       *
6226       * @param {Object} event jQuery event object.
6227       *
6228       * @return {void}
6229       */
6230      switchTabs: function( event ) {
6231          var key   = event.which,
6232              index = this.tabs.index( $( event.target ) ),
6233              newIndex;
6234  
6235          switch ( key ) {
6236              // Space bar: Activate current targeted tab.
6237              case 32: {
6238                  this.activateTab( this.tabs[ index ] );
6239                  break;
6240              }
6241              // End key: Activate last tab.
6242              case 35: {
6243                  event.preventDefault();
6244                  this.activateTab( this.tabs[ this.tabs.length - 1 ] );
6245                  break;
6246              }
6247              // Home key: Activate first tab.
6248              case 36: {
6249                  event.preventDefault();
6250                  this.activateTab( this.tabs[ 0 ] );
6251                  break;
6252              }
6253              // Left and up keys: Activate previous tab.
6254              case 37:
6255              case 38: {
6256                  event.preventDefault();
6257                  newIndex = ( index - 1 ) < 0 ? this.tabs.length - 1 : index - 1;
6258                  this.activateTab( this.tabs[ newIndex ] );
6259                  break;
6260              }
6261              // Right and down keys: Activate next tab.
6262              case 39:
6263              case 40: {
6264                  event.preventDefault();
6265                  newIndex = ( index + 1 ) === this.tabs.length ? 0 : index + 1;
6266                  this.activateTab( this.tabs[ newIndex ] );
6267                  break;
6268              }
6269          }
6270      },
6271  
6272      /**
6273       * Sets a single tab to be focusable and semantically selected.
6274       *
6275       * @since 5.3.0
6276       *
6277       * @param {Object} tab The tab DOM element.
6278       *
6279       * @return {void}
6280       */
6281      activateTab: function( tab ) {
6282          if ( ! tab ) {
6283              return;
6284          }
6285  
6286          // The tab is a DOM element: no need for jQuery methods.
6287          tab.focus();
6288  
6289          // Handle automatic activation.
6290          if ( this.tabsAutomaticActivation ) {
6291              tab.removeAttribute( 'tabindex' );
6292              tab.setAttribute( 'aria-selected', 'true' );
6293              tab.click();
6294  
6295              return;
6296          }
6297  
6298          // Handle manual activation.
6299          $( tab ).on( 'click', function() {
6300              tab.removeAttribute( 'tabindex' );
6301              tab.setAttribute( 'aria-selected', 'true' );
6302          } );
6303       }
6304  });
6305  
6306  module.exports = FocusManager;
6307  
6308  
6309  /***/ },
6310  
6311  /***/ 1061
6312  (module) {
6313  
6314  /**
6315   * wp.media.view.Frame
6316   *
6317   * A frame is a composite view consisting of one or more regions and one or more
6318   * states.
6319   *
6320   * @memberOf wp.media.view
6321   *
6322   * @see wp.media.controller.State
6323   * @see wp.media.controller.Region
6324   *
6325   * @class
6326   * @augments wp.media.View
6327   * @augments wp.Backbone.View
6328   * @augments Backbone.View
6329   * @mixes wp.media.controller.StateMachine
6330   */
6331  var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{
6332      initialize: function() {
6333          _.defaults( this.options, {
6334              mode: [ 'select' ]
6335          });
6336          this._createRegions();
6337          this._createStates();
6338          this._createModes();
6339      },
6340  
6341      _createRegions: function() {
6342          // Clone the regions array.
6343          this.regions = this.regions ? this.regions.slice() : [];
6344  
6345          // Initialize regions.
6346          _.each( this.regions, function( region ) {
6347              this[ region ] = new wp.media.controller.Region({
6348                  view:     this,
6349                  id:       region,
6350                  selector: '.media-frame-' + region
6351              });
6352          }, this );
6353      },
6354      /**
6355       * Create the frame's states.
6356       *
6357       * @see wp.media.controller.State
6358       * @see wp.media.controller.StateMachine
6359       *
6360       * @fires wp.media.controller.State#ready
6361       */
6362      _createStates: function() {
6363          // Create the default `states` collection.
6364          this.states = new Backbone.Collection( null, {
6365              model: wp.media.controller.State
6366          });
6367  
6368          // Ensure states have a reference to the frame.
6369          this.states.on( 'add', function( model ) {
6370              model.frame = this;
6371              model.trigger('ready');
6372          }, this );
6373  
6374          if ( this.options.states ) {
6375              this.states.add( this.options.states );
6376          }
6377      },
6378  
6379      /**
6380       * A frame can be in a mode or multiple modes at one time.
6381       *
6382       * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
6383       */
6384      _createModes: function() {
6385          // Store active "modes" that the frame is in. Unrelated to region modes.
6386          this.activeModes = new Backbone.Collection();
6387          this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
6388  
6389          _.each( this.options.mode, function( mode ) {
6390              this.activateMode( mode );
6391          }, this );
6392      },
6393      /**
6394       * Reset all states on the frame to their defaults.
6395       *
6396       * @return {wp.media.view.Frame} Returns itself to allow chaining.
6397       */
6398      reset: function() {
6399          this.states.invoke( 'trigger', 'reset' );
6400          return this;
6401      },
6402      /**
6403       * Map activeMode collection events to the frame.
6404       *
6405       * @param {Backbone.Model}      model      The model that triggered the event.
6406       * @param {Backbone.Collection} collection The collection to which the model belongs.
6407       * @param {Object}              options    The options object associated with the event.
6408       */
6409      triggerModeEvents: function( model, collection, options ) {
6410          var collectionEvent,
6411              modeEventMap = {
6412                  add: 'activate',
6413                  remove: 'deactivate'
6414              },
6415              eventToTrigger;
6416          // Probably a better way to do this.
6417          _.each( options, function( value, key ) {
6418              if ( value ) {
6419                  collectionEvent = key;
6420              }
6421          } );
6422  
6423          if ( ! _.has( modeEventMap, collectionEvent ) ) {
6424              return;
6425          }
6426  
6427          eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
6428          this.trigger( eventToTrigger );
6429      },
6430      /**
6431       * Activate a mode on the frame.
6432       *
6433       * @param {string} mode Mode ID.
6434       * @return {void|this} Returns itself to allow chaining.
6435       */
6436      activateMode: function( mode ) {
6437          // Bail if the mode is already active.
6438          if ( this.isModeActive( mode ) ) {
6439              return;
6440          }
6441          this.activeModes.add( [ { id: mode } ] );
6442          // Add a CSS class to the frame so elements can be styled for the mode.
6443          this.$el.addClass( 'mode-' + mode );
6444  
6445          return this;
6446      },
6447      /**
6448       * Deactivate a mode on the frame.
6449       *
6450       * @param {string} mode Mode ID.
6451       * @return {this} Returns itself to allow chaining.
6452       */
6453      deactivateMode: function( mode ) {
6454          // Bail if the mode isn't active.
6455          if ( ! this.isModeActive( mode ) ) {
6456              return this;
6457          }
6458          this.activeModes.remove( this.activeModes.where( { id: mode } ) );
6459          this.$el.removeClass( 'mode-' + mode );
6460  
6461          // Fires a `#{mode}:deactivate` event on the frame.
6462          this.trigger( mode + ':deactivate' );
6463  
6464          return this;
6465      },
6466      /**
6467       * Check if a mode is enabled on the frame.
6468       *
6469       * @param {string} mode Mode ID.
6470       * @return {boolean} True if the mode is active, false otherwise.
6471       */
6472      isModeActive: function( mode ) {
6473          return Boolean( this.activeModes.where( { id: mode } ).length );
6474      }
6475  });
6476  
6477  // Make the `Frame` a `StateMachine`.
6478  _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
6479  
6480  module.exports = Frame;
6481  
6482  
6483  /***/ },
6484  
6485  /***/ 5424
6486  (module) {
6487  
6488  var Select = wp.media.view.MediaFrame.Select,
6489      l10n = wp.media.view.l10n,
6490      ImageDetails;
6491  
6492  /**
6493   * wp.media.view.MediaFrame.ImageDetails
6494   *
6495   * A media frame for manipulating an image that's already been inserted
6496   * into a post.
6497   *
6498   * @memberOf wp.media.view.MediaFrame
6499   *
6500   * @class
6501   * @augments wp.media.view.MediaFrame.Select
6502   * @augments wp.media.view.MediaFrame
6503   * @augments wp.media.view.Frame
6504   * @augments wp.media.View
6505   * @augments wp.Backbone.View
6506   * @augments Backbone.View
6507   * @mixes wp.media.controller.StateMachine
6508   */
6509  ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{
6510      defaults: {
6511          id:      'image',
6512          url:     '',
6513          menu:    'image-details',
6514          content: 'image-details',
6515          toolbar: 'image-details',
6516          type:    'link',
6517          title:    l10n.imageDetailsTitle,
6518          priority: 120
6519      },
6520  
6521      initialize: function( options ) {
6522          this.image = new wp.media.model.PostImage( options.metadata );
6523          this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
6524          Select.prototype.initialize.apply( this, arguments );
6525      },
6526  
6527      bindHandlers: function() {
6528          Select.prototype.bindHandlers.apply( this, arguments );
6529          this.on( 'menu:create:image-details', this.createMenu, this );
6530          this.on( 'content:create:image-details', this.imageDetailsContent, this );
6531          this.on( 'content:render:edit-image', this.editImageContent, this );
6532          this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
6533          // Override the select toolbar.
6534          this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
6535      },
6536  
6537      createStates: function() {
6538          this.states.add([
6539              new wp.media.controller.ImageDetails({
6540                  image: this.image,
6541                  editable: false
6542              }),
6543              new wp.media.controller.ReplaceImage({
6544                  id: 'replace-image',
6545                  library: wp.media.query( { type: 'image' } ),
6546                  image: this.image,
6547                  multiple:  false,
6548                  title:     l10n.imageReplaceTitle,
6549                  toolbar: 'replace',
6550                  priority:  80,
6551                  displaySettings: true
6552              }),
6553              new wp.media.controller.EditImage( {
6554                  image: this.image,
6555                  selection: this.options.selection
6556              } )
6557          ]);
6558      },
6559  
6560      imageDetailsContent: function( options ) {
6561          options.view = new wp.media.view.ImageDetails({
6562              controller: this,
6563              model: this.state().image,
6564              attachment: this.state().image.attachment
6565          });
6566      },
6567  
6568      editImageContent: function() {
6569          var state = this.state(),
6570              model = state.get('image'),
6571              view;
6572  
6573          if ( ! model ) {
6574              return;
6575          }
6576  
6577          view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
6578  
6579          this.content.set( view );
6580  
6581          // After bringing in the frame, load the actual editor via an Ajax call.
6582          view.loadEditor();
6583  
6584      },
6585  
6586      renderImageDetailsToolbar: function() {
6587          this.toolbar.set( new wp.media.view.Toolbar({
6588              controller: this,
6589              items: {
6590                  select: {
6591                      style:    'primary',
6592                      text:     l10n.update,
6593                      priority: 80,
6594  
6595                      click: function() {
6596                          var controller = this.controller,
6597                              state = controller.state();
6598  
6599                          controller.close();
6600  
6601                          // Not sure if we want to use wp.media.string.image which will create a shortcode or
6602                          // perhaps wp.html.string to at least to build the <img />.
6603                          state.trigger( 'update', controller.image.toJSON() );
6604  
6605                          // Restore and reset the default state.
6606                          controller.setState( controller.options.state );
6607                          controller.reset();
6608                      }
6609                  }
6610              }
6611          }) );
6612      },
6613  
6614      renderReplaceImageToolbar: function() {
6615          var frame = this,
6616              lastState = frame.lastState(),
6617              previous = lastState && lastState.id;
6618  
6619          this.toolbar.set( new wp.media.view.Toolbar({
6620              controller: this,
6621              items: {
6622                  back: {
6623                      text:     l10n.back,
6624                      priority: 80,
6625                      click:    function() {
6626                          if ( previous ) {
6627                              frame.setState( previous );
6628                          } else {
6629                              frame.close();
6630                          }
6631                      }
6632                  },
6633  
6634                  replace: {
6635                      style:    'primary',
6636                      text:     l10n.replace,
6637                      priority: 20,
6638                      requires: { selection: true },
6639  
6640                      click: function() {
6641                          var controller = this.controller,
6642                              state = controller.state(),
6643                              selection = state.get( 'selection' ),
6644                              attachment = selection.single();
6645  
6646                          controller.close();
6647  
6648                          controller.image.changeAttachment( attachment, state.display( attachment ) );
6649  
6650                          // Not sure if we want to use wp.media.string.image which will create a shortcode or
6651                          // perhaps wp.html.string to at least to build the <img />.
6652                          state.trigger( 'replace', controller.image.toJSON() );
6653  
6654                          // Restore and reset the default state.
6655                          controller.setState( controller.options.state );
6656                          controller.reset();
6657                      }
6658                  }
6659              }
6660          }) );
6661      }
6662  
6663  });
6664  
6665  module.exports = ImageDetails;
6666  
6667  
6668  /***/ },
6669  
6670  /***/ 4274
6671  (module) {
6672  
6673  var Select = wp.media.view.MediaFrame.Select,
6674      Library = wp.media.controller.Library,
6675      l10n = wp.media.view.l10n,
6676      Post;
6677  
6678  /**
6679   * wp.media.view.MediaFrame.Post
6680   *
6681   * The frame for manipulating media on the Edit Post page.
6682   *
6683   * @memberOf wp.media.view.MediaFrame
6684   *
6685   * @class
6686   * @augments wp.media.view.MediaFrame.Select
6687   * @augments wp.media.view.MediaFrame
6688   * @augments wp.media.view.Frame
6689   * @augments wp.media.View
6690   * @augments wp.Backbone.View
6691   * @augments Backbone.View
6692   * @mixes wp.media.controller.StateMachine
6693   */
6694  Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{
6695      initialize: function() {
6696          this.counts = {
6697              audio: {
6698                  count: wp.media.view.settings.attachmentCounts.audio,
6699                  state: 'playlist'
6700              },
6701              video: {
6702                  count: wp.media.view.settings.attachmentCounts.video,
6703                  state: 'video-playlist'
6704              }
6705          };
6706  
6707          _.defaults( this.options, {
6708              multiple:  true,
6709              editing:   false,
6710              state:    'insert',
6711              metadata:  {}
6712          });
6713  
6714          // Call 'initialize' directly on the parent class.
6715          Select.prototype.initialize.apply( this, arguments );
6716          this.createIframeStates();
6717  
6718      },
6719  
6720      /**
6721       * Create the default states.
6722       */
6723      createStates: function() {
6724          var options = this.options;
6725  
6726          this.states.add([
6727              // Main states.
6728              new Library({
6729                  id:         'insert',
6730                  title:      l10n.insertMediaTitle,
6731                  priority:   20,
6732                  toolbar:    'main-insert',
6733                  filterable: 'all',
6734                  library:    wp.media.query( options.library ),
6735                  multiple:   options.multiple ? 'reset' : false,
6736                  editable:   true,
6737  
6738                  // If the user isn't allowed to edit fields,
6739                  // can they still edit it locally?
6740                  allowLocalEdits: true,
6741  
6742                  // Show the attachment display settings.
6743                  displaySettings: true,
6744                  // Update user settings when users adjust the
6745                  // attachment display settings.
6746                  displayUserSettings: true
6747              }),
6748  
6749              new Library({
6750                  id:         'gallery',
6751                  title:      l10n.createGalleryTitle,
6752                  priority:   40,
6753                  toolbar:    'main-gallery',
6754                  filterable: 'uploaded',
6755                  multiple:   'add',
6756                  editable:   false,
6757  
6758                  library:  wp.media.query( _.defaults({
6759                      type: 'image'
6760                  }, options.library ) )
6761              }),
6762  
6763              // Embed states.
6764              new wp.media.controller.Embed( { metadata: options.metadata } ),
6765  
6766              new wp.media.controller.EditImage( { model: options.editImage } ),
6767  
6768              // Gallery states.
6769              new wp.media.controller.GalleryEdit({
6770                  library: options.selection,
6771                  editing: options.editing,
6772                  menu:    'gallery'
6773              }),
6774  
6775              new wp.media.controller.GalleryAdd(),
6776  
6777              new Library({
6778                  id:         'playlist',
6779                  title:      l10n.createPlaylistTitle,
6780                  priority:   60,
6781                  toolbar:    'main-playlist',
6782                  filterable: 'uploaded',
6783                  multiple:   'add',
6784                  editable:   false,
6785  
6786                  library:  wp.media.query( _.defaults({
6787                      type: 'audio'
6788                  }, options.library ) )
6789              }),
6790  
6791              // Playlist states.
6792              new wp.media.controller.CollectionEdit({
6793                  type: 'audio',
6794                  collectionType: 'playlist',
6795                  title:          l10n.editPlaylistTitle,
6796                  SettingsView:   wp.media.view.Settings.Playlist,
6797                  library:        options.selection,
6798                  editing:        options.editing,
6799                  menu:           'playlist',
6800                  dragInfoText:   l10n.playlistDragInfo,
6801                  dragInfo:       false
6802              }),
6803  
6804              new wp.media.controller.CollectionAdd({
6805                  type: 'audio',
6806                  collectionType: 'playlist',
6807                  title: l10n.addToPlaylistTitle
6808              }),
6809  
6810              new Library({
6811                  id:         'video-playlist',
6812                  title:      l10n.createVideoPlaylistTitle,
6813                  priority:   60,
6814                  toolbar:    'main-video-playlist',
6815                  filterable: 'uploaded',
6816                  multiple:   'add',
6817                  editable:   false,
6818  
6819                  library:  wp.media.query( _.defaults({
6820                      type: 'video'
6821                  }, options.library ) )
6822              }),
6823  
6824              new wp.media.controller.CollectionEdit({
6825                  type: 'video',
6826                  collectionType: 'playlist',
6827                  title:          l10n.editVideoPlaylistTitle,
6828                  SettingsView:   wp.media.view.Settings.Playlist,
6829                  library:        options.selection,
6830                  editing:        options.editing,
6831                  menu:           'video-playlist',
6832                  dragInfoText:   l10n.videoPlaylistDragInfo,
6833                  dragInfo:       false
6834              }),
6835  
6836              new wp.media.controller.CollectionAdd({
6837                  type: 'video',
6838                  collectionType: 'playlist',
6839                  title: l10n.addToVideoPlaylistTitle
6840              })
6841          ]);
6842  
6843          if ( wp.media.view.settings.post.featuredImageId ) {
6844              this.states.add( new wp.media.controller.FeaturedImage() );
6845          }
6846      },
6847  
6848      bindHandlers: function() {
6849          var handlers, checkCounts;
6850  
6851          Select.prototype.bindHandlers.apply( this, arguments );
6852  
6853          this.on( 'activate', this.activate, this );
6854  
6855          // Only bother checking media type counts if one of the counts is zero.
6856          checkCounts = _.find( this.counts, function( type ) {
6857              return type.count === 0;
6858          } );
6859  
6860          if ( typeof checkCounts !== 'undefined' ) {
6861              this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
6862          }
6863  
6864          this.on( 'menu:create:gallery', this.createMenu, this );
6865          this.on( 'menu:create:playlist', this.createMenu, this );
6866          this.on( 'menu:create:video-playlist', this.createMenu, this );
6867          this.on( 'toolbar:create:main-insert', this.createToolbar, this );
6868          this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
6869          this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
6870          this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
6871          this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
6872          this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
6873  
6874          handlers = {
6875              menu: {
6876                  'default': 'mainMenu',
6877                  'gallery': 'galleryMenu',
6878                  'playlist': 'playlistMenu',
6879                  'video-playlist': 'videoPlaylistMenu'
6880              },
6881  
6882              content: {
6883                  'embed':          'embedContent',
6884                  'edit-image':     'editImageContent',
6885                  'edit-selection': 'editSelectionContent'
6886              },
6887  
6888              toolbar: {
6889                  'main-insert':      'mainInsertToolbar',
6890                  'main-gallery':     'mainGalleryToolbar',
6891                  'gallery-edit':     'galleryEditToolbar',
6892                  'gallery-add':      'galleryAddToolbar',
6893                  'main-playlist':    'mainPlaylistToolbar',
6894                  'playlist-edit':    'playlistEditToolbar',
6895                  'playlist-add':        'playlistAddToolbar',
6896                  'main-video-playlist': 'mainVideoPlaylistToolbar',
6897                  'video-playlist-edit': 'videoPlaylistEditToolbar',
6898                  'video-playlist-add': 'videoPlaylistAddToolbar'
6899              }
6900          };
6901  
6902          _.each( handlers, function( regionHandlers, region ) {
6903              _.each( regionHandlers, function( callback, handler ) {
6904                  this.on( region + ':render:' + handler, this[ callback ], this );
6905              }, this );
6906          }, this );
6907      },
6908  
6909      /**
6910       * Activates the frame.
6911       */
6912      activate: function() {
6913          // Hide menu items for states tied to particular media types if there are no items.
6914          _.each( this.counts, function( type ) {
6915              if ( type.count < 1 ) {
6916                  this.menuItemVisibility( type.state, 'hide' );
6917              }
6918          }, this );
6919      },
6920  
6921      /**
6922       * Handles the counts of media types.
6923       *
6924       * @param {wp.media.model.Attachments} model The attachment model that changed.
6925       * @param {string}                     attr  The attribute that changed on the model.
6926       */
6927      mediaTypeCounts: function( model, attr ) {
6928          if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
6929              this.counts[ attr ].count++;
6930              this.menuItemVisibility( this.counts[ attr ].state, 'show' );
6931          }
6932      },
6933  
6934      // Menus.
6935      /**
6936       * Handles the main menu for the frame.
6937       *
6938       * @param {wp.Backbone.View} view The menu view for the main menu.
6939       */
6940      mainMenu: function( view ) {
6941          view.set({
6942              'library-separator': new wp.media.View({
6943                  className:  'separator',
6944                  priority:   100,
6945                  attributes: {
6946                      role: 'presentation'
6947                  }
6948              })
6949          });
6950      },
6951  
6952      /**
6953       * Handles the visibility of menu items for the frame.
6954       *
6955       * @param {string} state      The state to show or hide.
6956       * @param {string} visibility The visibility of the menu item, either 'show' or 'hide'.
6957       */
6958      menuItemVisibility: function( state, visibility ) {
6959          var menu = this.menu.get();
6960          if ( visibility === 'hide' ) {
6961              menu.hide( state );
6962          } else if ( visibility === 'show' ) {
6963              menu.show( state );
6964          }
6965      },
6966      /**
6967       * Handles the gallery menu for the frame.
6968       *
6969       * @param {wp.Backbone.View} view The menu view for the gallery menu.
6970       */
6971      galleryMenu: function( view ) {
6972          var lastState = this.lastState(),
6973              previous = lastState && lastState.id,
6974              frame = this;
6975  
6976          view.set({
6977              cancel: {
6978                  text:     l10n.cancelGalleryTitle,
6979                  priority: 20,
6980                  click:    function() {
6981                      if ( previous ) {
6982                          frame.setState( previous );
6983                      } else {
6984                          frame.close();
6985                      }
6986  
6987                      // Move focus to the modal after canceling a Gallery.
6988                      this.controller.modal.focusManager.focus();
6989                  }
6990              },
6991              separateCancel: new wp.media.View({
6992                  className: 'separator',
6993                  priority: 40
6994              })
6995          });
6996      },
6997  
6998      /**
6999       * Handles the playlist menu for the frame.
7000       *
7001       * @param {wp.Backbone.View} view The menu view for the playlist menu.
7002       */
7003      playlistMenu: function( view ) {
7004          var lastState = this.lastState(),
7005              previous = lastState && lastState.id,
7006              frame = this;
7007  
7008          view.set({
7009              cancel: {
7010                  text:     l10n.cancelPlaylistTitle,
7011                  priority: 20,
7012                  click:    function() {
7013                      if ( previous ) {
7014                          frame.setState( previous );
7015                      } else {
7016                          frame.close();
7017                      }
7018  
7019                      // Move focus to the modal after canceling an Audio Playlist.
7020                      this.controller.modal.focusManager.focus();
7021                  }
7022              },
7023              separateCancel: new wp.media.View({
7024                  className: 'separator',
7025                  priority: 40
7026              })
7027          });
7028      },
7029  
7030      /**
7031       * Handles the video playlist menu for the frame.
7032       *
7033       * @param {wp.Backbone.View} view The menu view.
7034       */
7035      videoPlaylistMenu: function( view ) {
7036          var lastState = this.lastState(),
7037              previous = lastState && lastState.id,
7038              frame = this;
7039  
7040          view.set({
7041              cancel: {
7042                  text:     l10n.cancelVideoPlaylistTitle,
7043                  priority: 20,
7044                  click:    function() {
7045                      if ( previous ) {
7046                          frame.setState( previous );
7047                      } else {
7048                          frame.close();
7049                      }
7050  
7051                      // Move focus to the modal after canceling a Video Playlist.
7052                      this.controller.modal.focusManager.focus();
7053                  }
7054              },
7055              separateCancel: new wp.media.View({
7056                  className: 'separator',
7057                  priority: 40
7058              })
7059          });
7060      },
7061  
7062      // Content.
7063      /**
7064       * Handles the embed content for the frame.
7065       */
7066      embedContent: function() {
7067          var view = new wp.media.view.Embed({
7068              controller: this,
7069              model:      this.state()
7070          }).render();
7071  
7072          this.content.set( view );
7073      },
7074  
7075      /**
7076       * Handles the edit selection content for the frame.
7077       */
7078      editSelectionContent: function() {
7079          var state = this.state(),
7080              selection = state.get('selection'),
7081              view;
7082  
7083          view = new wp.media.view.AttachmentsBrowser({
7084              controller: this,
7085              collection: selection,
7086              selection:  selection,
7087              model:      state,
7088              sortable:   true,
7089              search:     false,
7090              date:       false,
7091              dragInfo:   true,
7092  
7093              AttachmentView: wp.media.view.Attachments.EditSelection
7094          }).render();
7095  
7096          view.toolbar.set( 'backToLibrary', {
7097              text:     l10n.returnToLibrary,
7098              priority: -100,
7099  
7100              click: function() {
7101                  this.controller.content.mode('browse');
7102                  // Move focus to the modal when jumping back from Edit Selection to Add Media view.
7103                  this.controller.modal.focusManager.focus();
7104              }
7105          });
7106  
7107          // Browse our library of attachments.
7108          this.content.set( view );
7109  
7110          // Trigger the controller to set focus.
7111          this.trigger( 'edit:selection', this );
7112      },
7113  
7114      /**
7115       * Handles the edit image content for the frame.
7116       */
7117      editImageContent: function() {
7118          var image = this.state().get('image'),
7119              view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
7120  
7121          this.content.set( view );
7122  
7123          // After creating the wrapper view, load the actual editor via an Ajax call.
7124          view.loadEditor();
7125  
7126      },
7127  
7128      // Toolbars.
7129  
7130      /**
7131       * Handles the selection status toolbar for the frame
7132       *
7133       * @param {wp.Backbone.View} view The toolbar view.
7134       */
7135      selectionStatusToolbar: function( view ) {
7136          var editable = this.state().get('editable');
7137  
7138          view.set( 'selection', new wp.media.view.Selection({
7139              controller: this,
7140              collection: this.state().get('selection'),
7141              priority:   -40,
7142  
7143              // If the selection is editable, pass the callback to
7144              // switch the content mode.
7145              editable: editable && function() {
7146                  this.controller.content.mode('edit-selection');
7147              }
7148          }).render() );
7149      },
7150  
7151      /**
7152       * Handles the main insert toolbar for the frame.
7153       *
7154       * @param {wp.Backbone.View} view The toolbar view.
7155       */
7156      mainInsertToolbar: function( view ) {
7157          var controller = this;
7158  
7159          this.selectionStatusToolbar( view );
7160  
7161          view.set( 'insert', {
7162              style:    'primary',
7163              priority: 80,
7164              text:     l10n.insertIntoPost,
7165              requires: { selection: true },
7166  
7167              /**
7168               * @ignore
7169               *
7170               * @fires wp.media.controller.State#insert
7171               */
7172              click: function() {
7173                  var state = controller.state(),
7174                      selection = state.get('selection');
7175  
7176                  controller.close();
7177                  state.trigger( 'insert', selection ).reset();
7178              }
7179          });
7180      },
7181  
7182      /**
7183       * Handles the main gallery toolbar for the frame.
7184       *
7185       * @param {wp.Backbone.View} view The toolbar view.
7186       */
7187      mainGalleryToolbar: function( view ) {
7188          var controller = this;
7189  
7190          this.selectionStatusToolbar( view );
7191  
7192          view.set( 'gallery', {
7193              style:    'primary',
7194              text:     l10n.createNewGallery,
7195              priority: 60,
7196              requires: { selection: true },
7197  
7198              click: function() {
7199                  var selection = controller.state().get('selection'),
7200                      edit = controller.state('gallery-edit'),
7201                      models = selection.where({ type: 'image' });
7202  
7203                  edit.set( 'library', new wp.media.model.Selection( models, {
7204                      props:    selection.props.toJSON(),
7205                      multiple: true
7206                  }) );
7207  
7208                  // Jump to Edit Gallery view.
7209                  this.controller.setState( 'gallery-edit' );
7210  
7211                  // Move focus to the modal after jumping to Edit Gallery view.
7212                  this.controller.modal.focusManager.focus();
7213              }
7214          });
7215      },
7216  
7217      /**
7218       * Handles the main playlist toolbar for the frame.
7219       *
7220       * @param {wp.Backbone.View} view The toolbar view.
7221       */
7222      mainPlaylistToolbar: function( view ) {
7223          var controller = this;
7224  
7225          this.selectionStatusToolbar( view );
7226  
7227          view.set( 'playlist', {
7228              style:    'primary',
7229              text:     l10n.createNewPlaylist,
7230              priority: 100,
7231              requires: { selection: true },
7232  
7233              click: function() {
7234                  var selection = controller.state().get('selection'),
7235                      edit = controller.state('playlist-edit'),
7236                      models = selection.where({ type: 'audio' });
7237  
7238                  edit.set( 'library', new wp.media.model.Selection( models, {
7239                      props:    selection.props.toJSON(),
7240                      multiple: true
7241                  }) );
7242  
7243                  // Jump to Edit Audio Playlist view.
7244                  this.controller.setState( 'playlist-edit' );
7245  
7246                  // Move focus to the modal after jumping to Edit Audio Playlist view.
7247                  this.controller.modal.focusManager.focus();
7248              }
7249          });
7250      },
7251  
7252      /**
7253       * Handles the main video playlist toolbar for the frame.
7254       *
7255       * @param {wp.Backbone.View} view The toolbar view.
7256       */
7257      mainVideoPlaylistToolbar: function( view ) {
7258          var controller = this;
7259  
7260          this.selectionStatusToolbar( view );
7261  
7262          view.set( 'video-playlist', {
7263              style:    'primary',
7264              text:     l10n.createNewVideoPlaylist,
7265              priority: 100,
7266              requires: { selection: true },
7267  
7268              click: function() {
7269                  var selection = controller.state().get('selection'),
7270                      edit = controller.state('video-playlist-edit'),
7271                      models = selection.where({ type: 'video' });
7272  
7273                  edit.set( 'library', new wp.media.model.Selection( models, {
7274                      props:    selection.props.toJSON(),
7275                      multiple: true
7276                  }) );
7277  
7278                  // Jump to Edit Video Playlist view.
7279                  this.controller.setState( 'video-playlist-edit' );
7280  
7281                  // Move focus to the modal after jumping to Edit Video Playlist view.
7282                  this.controller.modal.focusManager.focus();
7283              }
7284          });
7285      },
7286  
7287      /**
7288       * Handles the featured image toolbar for the frame.
7289       *
7290       * @param {wp.media.view.Toolbar} toolbar The toolbar view.
7291       */
7292      featuredImageToolbar: function( toolbar ) {
7293          this.createSelectToolbar( toolbar, {
7294              text:  l10n.setFeaturedImage,
7295              state: this.options.state
7296          });
7297      },
7298  
7299      /**
7300       * Handles the main embed toolbar for the frame.
7301       *
7302       * @param {wp.media.view.Toolbar} toolbar The toolbar view.
7303       */
7304      mainEmbedToolbar: function( toolbar ) {
7305          toolbar.view = new wp.media.view.Toolbar.Embed({
7306              controller: this
7307          });
7308      },
7309  
7310      /**
7311       * Handles the edit image toolbar for the frame.
7312       */
7313      galleryEditToolbar: function() {
7314          var editing = this.state().get('editing');
7315          this.toolbar.set( new wp.media.view.Toolbar({
7316              controller: this,
7317              items: {
7318                  insert: {
7319                      style:    'primary',
7320                      text:     editing ? l10n.updateGallery : l10n.insertGallery,
7321                      priority: 80,
7322                      requires: { library: true, uploadingComplete: true },
7323  
7324                      /**
7325                       * Handles the click event for the insert button.
7326                       *
7327                       * @fires wp.media.controller.State#update
7328                       */
7329                      click: function() {
7330                          var controller = this.controller,
7331                              state = controller.state();
7332  
7333                          controller.close();
7334                          state.trigger( 'update', state.get('library') );
7335  
7336                          // Restore and reset the default state.
7337                          controller.setState( controller.options.state );
7338                          controller.reset();
7339                      }
7340                  }
7341              }
7342          }) );
7343      },
7344  
7345      /**
7346       * Handles the add to gallery toolbar for the frame.
7347       */
7348      galleryAddToolbar: function() {
7349          this.toolbar.set( new wp.media.view.Toolbar({
7350              controller: this,
7351              items: {
7352                  insert: {
7353                      style:    'primary',
7354                      text:     l10n.addToGallery,
7355                      priority: 80,
7356                      requires: { selection: true },
7357  
7358                      /**
7359                       * Handles the click event for the insert button.
7360                       *
7361                       * @fires wp.media.controller.State#reset
7362                       */
7363                      click: function() {
7364                          var controller = this.controller,
7365                              state = controller.state(),
7366                              edit = controller.state('gallery-edit');
7367  
7368                          edit.get('library').add( state.get('selection').models );
7369                          state.trigger('reset');
7370                          controller.setState('gallery-edit');
7371                          // Move focus to the modal when jumping back from Add to Gallery to Edit Gallery view.
7372                          this.controller.modal.focusManager.focus();
7373                      }
7374                  }
7375              }
7376          }) );
7377      },
7378  
7379      /**
7380       * Handles the edit playlist toolbar for the frame.
7381       */
7382      playlistEditToolbar: function() {
7383          var editing = this.state().get('editing');
7384          this.toolbar.set( new wp.media.view.Toolbar({
7385              controller: this,
7386              items: {
7387                  insert: {
7388                      style:    'primary',
7389                      text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
7390                      priority: 80,
7391                      requires: { library: true },
7392  
7393                      /**
7394                       * Handles the click event for the insert button.
7395                       *
7396                       * @fires wp.media.controller.State#update
7397                       */
7398                      click: function() {
7399                          var controller = this.controller,
7400                              state = controller.state();
7401  
7402                          controller.close();
7403                          state.trigger( 'update', state.get('library') );
7404  
7405                          // Restore and reset the default state.
7406                          controller.setState( controller.options.state );
7407                          controller.reset();
7408                      }
7409                  }
7410              }
7411          }) );
7412      },
7413  
7414      /**
7415       * Handles the add to playlist toolbar for the frame.
7416       */
7417      playlistAddToolbar: function() {
7418          this.toolbar.set( new wp.media.view.Toolbar({
7419              controller: this,
7420              items: {
7421                  insert: {
7422                      style:    'primary',
7423                      text:     l10n.addToPlaylist,
7424                      priority: 80,
7425                      requires: { selection: true },
7426  
7427                      /**
7428                       * Handles the click event for the insert button.
7429                       *
7430                       * @fires wp.media.controller.State#reset
7431                       */
7432                      click: function() {
7433                          var controller = this.controller,
7434                              state = controller.state(),
7435                              edit = controller.state('playlist-edit');
7436  
7437                          edit.get('library').add( state.get('selection').models );
7438                          state.trigger('reset');
7439                          controller.setState('playlist-edit');
7440                          // Move focus to the modal when jumping back from Add to Audio Playlist to Edit Audio Playlist view.
7441                          this.controller.modal.focusManager.focus();
7442                      }
7443                  }
7444              }
7445          }) );
7446      },
7447  
7448      /**
7449       * Handles the edit video playlist toolbar for the frame.
7450       */
7451      videoPlaylistEditToolbar: function() {
7452          var editing = this.state().get('editing');
7453          this.toolbar.set( new wp.media.view.Toolbar({
7454              controller: this,
7455              items: {
7456                  insert: {
7457                      style:    'primary',
7458                      text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
7459                      priority: 140,
7460                      requires: { library: true },
7461  
7462                      click: function() {
7463                          var controller = this.controller,
7464                              state = controller.state(),
7465                              library = state.get('library');
7466  
7467                          library.type = 'video';
7468  
7469                          controller.close();
7470                          state.trigger( 'update', library );
7471  
7472                          // Restore and reset the default state.
7473                          controller.setState( controller.options.state );
7474                          controller.reset();
7475                      }
7476                  }
7477              }
7478          }) );
7479      },
7480  
7481      /**
7482       * Handles the add to video playlist toolbar for the frame.
7483       */
7484      videoPlaylistAddToolbar: function() {
7485          this.toolbar.set( new wp.media.view.Toolbar({
7486              controller: this,
7487              items: {
7488                  insert: {
7489                      style:    'primary',
7490                      text:     l10n.addToVideoPlaylist,
7491                      priority: 140,
7492                      requires: { selection: true },
7493  
7494                      click: function() {
7495                          var controller = this.controller,
7496                              state = controller.state(),
7497                              edit = controller.state('video-playlist-edit');
7498  
7499                          edit.get('library').add( state.get('selection').models );
7500                          state.trigger('reset');
7501                          controller.setState('video-playlist-edit');
7502                          // Move focus to the modal when jumping back from Add to Video Playlist to Edit Video Playlist view.
7503                          this.controller.modal.focusManager.focus();
7504                      }
7505                  }
7506              }
7507          }) );
7508      }
7509  });
7510  
7511  module.exports = Post;
7512  
7513  
7514  /***/ },
7515  
7516  /***/ 455
7517  (module) {
7518  
7519  var MediaFrame = wp.media.view.MediaFrame,
7520      l10n = wp.media.view.l10n,
7521      Select;
7522  
7523  /**
7524   * wp.media.view.MediaFrame.Select
7525   *
7526   * A frame for selecting an item or items from the media library.
7527   *
7528   * @memberOf wp.media.view.MediaFrame
7529   *
7530   * @class
7531   * @augments wp.media.view.MediaFrame
7532   * @augments wp.media.view.Frame
7533   * @augments wp.media.View
7534   * @augments wp.Backbone.View
7535   * @augments Backbone.View
7536   * @mixes wp.media.controller.StateMachine
7537   */
7538  Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{
7539      initialize: function() {
7540          // Call 'initialize' directly on the parent class.
7541          MediaFrame.prototype.initialize.apply( this, arguments );
7542  
7543          _.defaults( this.options, {
7544              selection: [],
7545              library:   {},
7546              multiple:  false,
7547              state:    'library'
7548          });
7549  
7550          this.createSelection();
7551          this.createStates();
7552          this.bindHandlers();
7553      },
7554  
7555      /**
7556       * Attach a selection collection to the frame.
7557       *
7558       * A selection is a collection of attachments used for a specific purpose
7559       * by a media frame. e.g. Selecting an attachment (or many) to insert into
7560       * post content.
7561       *
7562       * @see media.model.Selection
7563       */
7564      createSelection: function() {
7565          var selection = this.options.selection;
7566  
7567          if ( ! (selection instanceof wp.media.model.Selection) ) {
7568              this.options.selection = new wp.media.model.Selection( selection, {
7569                  multiple: this.options.multiple
7570              });
7571          }
7572  
7573          this._selection = {
7574              attachments: new wp.media.model.Attachments(),
7575              difference: []
7576          };
7577      },
7578  
7579      editImageContent: function() {
7580          var image = this.state().get('image'),
7581              view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
7582  
7583          this.content.set( view );
7584  
7585          // After creating the wrapper view, load the actual editor via an Ajax call.
7586          view.loadEditor();
7587      },
7588  
7589      /**
7590       * Create the default states on the frame.
7591       */
7592      createStates: function() {
7593          var options = this.options;
7594  
7595          if ( this.options.states ) {
7596              return;
7597          }
7598  
7599          // Add the default states.
7600          this.states.add([
7601              // Main states.
7602              new wp.media.controller.Library({
7603                  library:   wp.media.query( options.library ),
7604                  multiple:  options.multiple,
7605                  title:     options.title,
7606                  priority:  20
7607              }),
7608              new wp.media.controller.EditImage( { model: options.editImage } )
7609          ]);
7610      },
7611  
7612      /**
7613       * Bind region mode event callbacks.
7614       *
7615       * @see media.controller.Region.render
7616       */
7617      bindHandlers: function() {
7618          this.on( 'router:create:browse', this.createRouter, this );
7619          this.on( 'router:render:browse', this.browseRouter, this );
7620          this.on( 'content:create:browse', this.browseContent, this );
7621          this.on( 'content:render:upload', this.uploadContent, this );
7622          this.on( 'toolbar:create:select', this.createSelectToolbar, this );
7623          this.on( 'content:render:edit-image', this.editImageContent, this );
7624      },
7625  
7626      /**
7627       * Render callback for the router region in the `browse` mode.
7628       *
7629       * @param {wp.media.view.Router} routerView The router view for the `browse` mode.
7630       */
7631      browseRouter: function( routerView ) {
7632          routerView.set({
7633              upload: {
7634                  text:     l10n.uploadFilesTitle,
7635                  priority: 20
7636              },
7637              browse: {
7638                  text:     l10n.mediaLibraryTitle,
7639                  priority: 40
7640              }
7641          });
7642      },
7643  
7644      /**
7645       * Render callback for the content region in the `browse` mode.
7646       *
7647       * @param {wp.media.controller.Region} contentRegion The content region for the `browse` mode.
7648       */
7649      browseContent: function( contentRegion ) {
7650          var state = this.state();
7651  
7652          this.$el.removeClass('hide-toolbar');
7653  
7654          // Browse our library of attachments.
7655          contentRegion.view = new wp.media.view.AttachmentsBrowser({
7656              controller: this,
7657              collection: state.get('library'),
7658              selection:  state.get('selection'),
7659              model:      state,
7660              sortable:   state.get('sortable'),
7661              search:     state.get('searchable'),
7662              filters:    state.get('filterable'),
7663              date:       state.get('date'),
7664              display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
7665              dragInfo:   state.get('dragInfo'),
7666  
7667              idealColumnWidth: state.get('idealColumnWidth'),
7668              suggestedWidth:   state.get('suggestedWidth'),
7669              suggestedHeight:  state.get('suggestedHeight'),
7670  
7671              AttachmentView: state.get('AttachmentView')
7672          });
7673      },
7674  
7675      /**
7676       * Render callback for the content region in the `upload` mode.
7677       */
7678      uploadContent: function() {
7679          this.$el.removeClass( 'hide-toolbar' );
7680          this.content.set( new wp.media.view.UploaderInline({
7681              controller: this
7682          }) );
7683      },
7684  
7685      /**
7686       * Toolbars
7687       *
7688       * @param {Object} toolbar      The toolbar object for the `select` mode.
7689       * @param {Object} [options={}] The options for creating the select toolbar view.
7690       * @this {wp.media.controller.Region}
7691       */
7692      createSelectToolbar: function( toolbar, options ) {
7693          options = options || this.options.button || {};
7694          options.controller = this;
7695  
7696          toolbar.view = new wp.media.view.Toolbar.Select( options );
7697      }
7698  });
7699  
7700  module.exports = Select;
7701  
7702  
7703  /***/ },
7704  
7705  /***/ 170
7706  (module) {
7707  
7708  /**
7709   * wp.media.view.Heading
7710   *
7711   * A reusable heading component for the media library
7712   *
7713   * Used to add accessibility friendly headers in the media library/modal.
7714   *
7715   * @class
7716   * @augments wp.media.View
7717   * @augments wp.Backbone.View
7718   * @augments Backbone.View
7719   */
7720  var Heading = wp.media.View.extend( {
7721      tagName: function() {
7722          return this.options.level || 'h1';
7723      },
7724      className: 'media-views-heading',
7725  
7726      initialize: function() {
7727  
7728          if ( this.options.className ) {
7729              this.$el.addClass( this.options.className );
7730          }
7731  
7732          this.text = this.options.text;
7733      },
7734  
7735      render: function() {
7736          this.$el.html( this.text );
7737          return this;
7738      }
7739  } );
7740  
7741  module.exports = Heading;
7742  
7743  
7744  /***/ },
7745  
7746  /***/ 1982
7747  (module) {
7748  
7749  /**
7750   * wp.media.view.Iframe
7751   *
7752   * @memberOf wp.media.view
7753   *
7754   * @class
7755   * @augments wp.media.View
7756   * @augments wp.Backbone.View
7757   * @augments Backbone.View
7758   */
7759  var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{
7760      className: 'media-iframe',
7761      /**
7762       * Renders the iframe view.
7763       *
7764       * @return {wp.media.view.Iframe} Returns itself to allow chaining.
7765       */
7766      render: function() {
7767          this.views.detach();
7768          this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
7769          this.views.render();
7770          return this;
7771      }
7772  });
7773  
7774  module.exports = Iframe;
7775  
7776  
7777  /***/ },
7778  
7779  /***/ 2650
7780  (module) {
7781  
7782  var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
7783      $ = jQuery,
7784      ImageDetails;
7785  
7786  /**
7787   * wp.media.view.ImageDetails
7788   *
7789   * @memberOf wp.media.view
7790   *
7791   * @class
7792   * @augments wp.media.view.Settings.AttachmentDisplay
7793   * @augments wp.media.view.Settings
7794   * @augments wp.media.View
7795   * @augments wp.Backbone.View
7796   * @augments Backbone.View
7797   */
7798  ImageDetails = AttachmentDisplay.extend(/** @lends wp.media.view.ImageDetails.prototype */{
7799      className: 'image-details',
7800      template:  wp.template('image-details'),
7801      events: _.defaults( AttachmentDisplay.prototype.events, {
7802          'click .edit-attachment': 'editAttachment',
7803          'click .replace-attachment': 'replaceAttachment',
7804          'click .advanced-toggle': 'onToggleAdvanced',
7805          'change [data-setting="customWidth"]': 'onCustomSize',
7806          'change [data-setting="customHeight"]': 'onCustomSize',
7807          'keyup [data-setting="customWidth"]': 'onCustomSize',
7808          'keyup [data-setting="customHeight"]': 'onCustomSize'
7809      } ),
7810      initialize: function() {
7811          // Used in AttachmentDisplay.prototype.updateLinkTo.
7812          this.options.attachment = this.model.attachment;
7813          this.listenTo( this.model, 'change:url', this.updateUrl );
7814          this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
7815          this.listenTo( this.model, 'change:size', this.toggleCustomSize );
7816  
7817          AttachmentDisplay.prototype.initialize.apply( this, arguments );
7818      },
7819  
7820      prepare: function() {
7821          var attachment = false;
7822  
7823          if ( this.model.attachment ) {
7824              attachment = this.model.attachment.toJSON();
7825          }
7826          return _.defaults({
7827              model: this.model.toJSON(),
7828              attachment: attachment
7829          }, this.options );
7830      },
7831  
7832      render: function() {
7833          var args = arguments;
7834  
7835          if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
7836              this.model.dfd
7837                  .done( _.bind( function() {
7838                      AttachmentDisplay.prototype.render.apply( this, args );
7839                      this.postRender();
7840                  }, this ) )
7841                  .fail( _.bind( function() {
7842                      this.model.attachment = false;
7843                      AttachmentDisplay.prototype.render.apply( this, args );
7844                      this.postRender();
7845                  }, this ) );
7846          } else {
7847              AttachmentDisplay.prototype.render.apply( this, arguments );
7848              this.postRender();
7849          }
7850  
7851          return this;
7852      },
7853  
7854      postRender: function() {
7855          setTimeout( _.bind( this.scrollToTop, this ), 10 );
7856          this.toggleLinkSettings();
7857          if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
7858              this.toggleAdvanced( true );
7859          }
7860          this.trigger( 'post-render' );
7861      },
7862  
7863      scrollToTop: function() {
7864          this.$( '.embed-media-settings' ).scrollTop( 0 );
7865      },
7866  
7867      updateUrl: function() {
7868          this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
7869          this.$( '.url' ).val( this.model.get( 'url' ) );
7870      },
7871  
7872      toggleLinkSettings: function() {
7873          if ( this.model.get( 'link' ) === 'none' ) {
7874              this.$( '.link-settings' ).addClass('hidden');
7875          } else {
7876              this.$( '.link-settings' ).removeClass('hidden');
7877          }
7878      },
7879  
7880      toggleCustomSize: function() {
7881          if ( this.model.get( 'size' ) !== 'custom' ) {
7882              this.$( '.custom-size' ).addClass('hidden');
7883          } else {
7884              this.$( '.custom-size' ).removeClass('hidden');
7885          }
7886      },
7887  
7888      onCustomSize: function( event ) {
7889          var dimension = $( event.target ).data('setting'),
7890              num = $( event.target ).val(),
7891              value;
7892  
7893          // Ignore bogus input.
7894          if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
7895              event.preventDefault();
7896              return;
7897          }
7898  
7899          if ( dimension === 'customWidth' ) {
7900              value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );
7901              this.model.set( 'customHeight', value, { silent: true } );
7902              this.$( '[data-setting="customHeight"]' ).val( value );
7903          } else {
7904              value = Math.round( this.model.get( 'aspectRatio' ) * num );
7905              this.model.set( 'customWidth', value, { silent: true  } );
7906              this.$( '[data-setting="customWidth"]' ).val( value );
7907          }
7908      },
7909  
7910      onToggleAdvanced: function( event ) {
7911          event.preventDefault();
7912          this.toggleAdvanced();
7913      },
7914  
7915      toggleAdvanced: function( show ) {
7916          var $advanced = this.$el.find( '.advanced-section' ),
7917              mode;
7918  
7919          if ( $advanced.hasClass('advanced-visible') || show === false ) {
7920              $advanced.removeClass('advanced-visible');
7921              $advanced.find('.advanced-settings').addClass('hidden');
7922              mode = 'hide';
7923          } else {
7924              $advanced.addClass('advanced-visible');
7925              $advanced.find('.advanced-settings').removeClass('hidden');
7926              mode = 'show';
7927          }
7928  
7929          window.setUserSetting( 'advImgDetails', mode );
7930      },
7931  
7932      editAttachment: function( event ) {
7933          var editState = this.controller.states.get( 'edit-image' );
7934  
7935          if ( window.imageEdit && editState ) {
7936              event.preventDefault();
7937              editState.set( 'image', this.model.attachment );
7938              this.controller.setState( 'edit-image' );
7939          }
7940      },
7941  
7942      replaceAttachment: function( event ) {
7943          event.preventDefault();
7944          this.controller.setState( 'replace-image' );
7945      }
7946  });
7947  
7948  module.exports = ImageDetails;
7949  
7950  
7951  /***/ },
7952  
7953  /***/ 4338
7954  (module) {
7955  
7956  /**
7957   * wp.media.view.Label
7958   *
7959   * @memberOf wp.media.view
7960   *
7961   * @class
7962   * @augments wp.media.View
7963   * @augments wp.Backbone.View
7964   * @augments Backbone.View
7965   */
7966  var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{
7967      tagName: 'label',
7968  
7969      initialize: function() {
7970          this.value = this.options.value;
7971      },
7972  
7973      render: function() {
7974          this.$el.html( this.value );
7975  
7976          return this;
7977      }
7978  });
7979  
7980  module.exports = Label;
7981  
7982  
7983  /***/ },
7984  
7985  /***/ 2836
7986  (module) {
7987  
7988  var Frame = wp.media.view.Frame,
7989      l10n = wp.media.view.l10n,
7990      $ = jQuery,
7991      MediaFrame;
7992  
7993  /**
7994   * wp.media.view.MediaFrame
7995   *
7996   * The frame used to create the media modal.
7997   *
7998   * @memberOf wp.media.view
7999   *
8000   * @class
8001   * @augments wp.media.view.Frame
8002   * @augments wp.media.View
8003   * @augments wp.Backbone.View
8004   * @augments Backbone.View
8005   * @mixes wp.media.controller.StateMachine
8006   */
8007  MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{
8008      className: 'media-frame',
8009      template:  wp.template('media-frame'),
8010      regions:   ['menu','title','content','toolbar','router'],
8011  
8012      events: {
8013          'click .media-frame-menu-toggle': 'toggleMenu'
8014      },
8015  
8016      /**
8017       * Initializes the media frame.
8018       *
8019       * @constructs
8020       */
8021      initialize: function() {
8022          Frame.prototype.initialize.apply( this, arguments );
8023  
8024          _.defaults( this.options, {
8025              title:    l10n.mediaFrameDefaultTitle,
8026              modal:    true,
8027              uploader: true
8028          });
8029  
8030          // Ensure core UI is enabled.
8031          this.$el.addClass('wp-core-ui');
8032  
8033          // Initialize modal container view.
8034          if ( this.options.modal ) {
8035              this.modal = new wp.media.view.Modal({
8036                  controller: this,
8037                  title:      this.options.title
8038              });
8039  
8040              this.modal.content( this );
8041          }
8042  
8043          // Force the uploader off if the upload limit has been exceeded or
8044          // if the browser isn't supported.
8045          if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
8046              this.options.uploader = false;
8047          }
8048  
8049          // Initialize window-wide uploader.
8050          if ( this.options.uploader ) {
8051              this.uploader = new wp.media.view.UploaderWindow({
8052                  controller: this,
8053                  uploader: {
8054                      dropzone:  this.modal ? this.modal.$el : this.$el,
8055                      container: this.$el
8056                  }
8057              });
8058              this.views.set( '.media-frame-uploader', this.uploader );
8059          }
8060  
8061          this.on( 'attach', _.bind( this.views.ready, this.views ), this );
8062  
8063          // Bind default title creation.
8064          this.on( 'title:create:default', this.createTitle, this );
8065          this.title.mode('default');
8066  
8067          // Bind default menu.
8068          this.on( 'menu:create:default', this.createMenu, this );
8069  
8070          // Set the menu ARIA tab panel attributes when the modal opens.
8071          this.on( 'open', this.setMenuTabPanelAriaAttributes, this );
8072          // Set the router ARIA tab panel attributes when the modal opens.
8073          this.on( 'open', this.setRouterTabPanelAriaAttributes, this );
8074  
8075          // Update the menu ARIA tab panel attributes when the content updates.
8076          this.on( 'content:render', this.setMenuTabPanelAriaAttributes, this );
8077          // Update the router ARIA tab panel attributes when the content updates.
8078          this.on( 'content:render', this.setRouterTabPanelAriaAttributes, this );
8079      },
8080  
8081      /**
8082       * Sets the attributes to be used on the menu ARIA tab panel.
8083       *
8084       * @since 5.3.0
8085       *
8086       * @return {void}
8087       */
8088      setMenuTabPanelAriaAttributes: function() {
8089          var stateId = this.state().get( 'id' ),
8090              tabPanelEl = this.$el.find( '.media-frame-tab-panel' ),
8091              ariaLabelledby;
8092  
8093          tabPanelEl.removeAttr( 'role aria-labelledby' );
8094  
8095          if ( this.state().get( 'menu' ) && this.menuView && this.menuView.isVisible ) {
8096              ariaLabelledby = 'menu-item-' + stateId;
8097  
8098              // Set the tab panel attributes only if the tabs are visible.
8099              tabPanelEl
8100                  .attr( {
8101                      role: 'tabpanel',
8102                      'aria-labelledby': ariaLabelledby,
8103                  } );
8104          }
8105      },
8106  
8107      /**
8108       * Sets the attributes to be used on the router ARIA tab panel.
8109       *
8110       * @since 5.3.0
8111       *
8112       * @return {void}
8113       */
8114      setRouterTabPanelAriaAttributes: function() {
8115          var tabPanelEl = this.$el.find( '.media-frame-content' ),
8116              ariaLabelledby;
8117  
8118          tabPanelEl.removeAttr( 'role aria-labelledby' );
8119  
8120          // Set the tab panel attributes only if the tabs are visible.
8121          if ( this.state().get( 'router' ) && this.routerView && this.routerView.isVisible && this.content._mode ) {
8122              ariaLabelledby = 'menu-item-' + this.content._mode;
8123  
8124              tabPanelEl
8125                  .attr( {
8126                      role: 'tabpanel',
8127                      'aria-labelledby': ariaLabelledby,
8128                  } );
8129          }
8130      },
8131  
8132      /**
8133       * Renders the media frame.
8134       *
8135       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
8136       */
8137      render: function() {
8138          // Activate the default state if no active state exists.
8139          if ( ! this.state() && this.options.state ) {
8140              this.setState( this.options.state );
8141          }
8142          /**
8143           * call 'render' directly on the parent class
8144           */
8145          return Frame.prototype.render.apply( this, arguments );
8146      },
8147      /**
8148       * Creates the title view.
8149       *
8150       * @param {Object} title The title object for creating the title view.
8151       * @this {wp.media.controller.Region}
8152       */
8153      createTitle: function( title ) {
8154          // A modal's dialog element points `aria-labelledby` at its frame heading.
8155          title.view = new wp.media.View({
8156              controller: this,
8157              tagName: 'h1',
8158              attributes: this.modal ? { id: this.modal.titleId } : {}
8159          });
8160      },
8161      /**
8162       * Creates the menu view.
8163       *
8164       * @param {Object} menu The menu object for creating the menu view.
8165       * @this {wp.media.controller.Region}
8166       */
8167      createMenu: function( menu ) {
8168          menu.view = new wp.media.view.Menu({
8169              controller: this,
8170  
8171              attributes: {
8172                  role:               'tablist',
8173                  'aria-orientation': 'vertical'
8174              }
8175          });
8176  
8177          this.menuView = menu.view;
8178      },
8179  
8180      /**
8181       * Toggles the menu visibility.
8182       *
8183       * @param {JQuery.Event} event The click event.
8184       */
8185      toggleMenu: function( event ) {
8186          var menu = this.$el.find( '.media-menu' );
8187  
8188          menu.toggleClass( 'visible' );
8189          $( event.target ).attr( 'aria-expanded', menu.hasClass( 'visible' ) );
8190      },
8191  
8192      /**
8193       * Creates the toolbar view.
8194       *
8195       * @param {Object} toolbar The toolbar object for creating the toolbar view.
8196       * @this {wp.media.controller.Region}
8197       */
8198      createToolbar: function( toolbar ) {
8199          toolbar.view = new wp.media.view.Toolbar({
8200              controller: this
8201          });
8202      },
8203      /**
8204       * Creates the router view.
8205       *
8206       * @param {Object} router The router object for creating the router view.
8207       * @this {wp.media.controller.Region}
8208       */
8209      createRouter: function( router ) {
8210          router.view = new wp.media.view.Router({
8211              controller: this,
8212  
8213              attributes: {
8214                  role:               'tablist',
8215                  'aria-orientation': 'horizontal'
8216              }
8217          });
8218  
8219          this.routerView = router.view;
8220      },
8221      /**
8222       * Creates the iframe states.
8223       *
8224       * @param {Object} options The options for creating the iframe states.
8225       */
8226      createIframeStates: function( options ) {
8227          var settings = wp.media.view.settings,
8228              tabs = settings.tabs,
8229              tabUrl = settings.tabUrl,
8230              $postId;
8231  
8232          if ( ! tabs || ! tabUrl ) {
8233              return;
8234          }
8235  
8236          // Add the post ID to the tab URL if it exists.
8237          $postId = $('#post_ID');
8238          if ( $postId.length ) {
8239              tabUrl += '&post_id=' + $postId.val();
8240          }
8241  
8242          // Generate the tab states.
8243          _.each( tabs, function( title, id ) {
8244              this.state( 'iframe:' + id ).set( _.defaults({
8245                  tab:     id,
8246                  src:     tabUrl + '&tab=' + id,
8247                  title:   title,
8248                  content: 'iframe',
8249                  menu:    'default'
8250              }, options ) );
8251          }, this );
8252  
8253          this.on( 'content:create:iframe', this.iframeContent, this );
8254          this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
8255          this.on( 'menu:render:default', this.iframeMenu, this );
8256          this.on( 'open', this.hijackThickbox, this );
8257          this.on( 'close', this.restoreThickbox, this );
8258      },
8259  
8260      /**
8261       * Creates the iframe content view.
8262       *
8263       * @param {Object} content The content object for creating the iframe content view.
8264       * @this {wp.media.controller.Region}
8265       */
8266      iframeContent: function( content ) {
8267          this.$el.addClass('hide-toolbar');
8268          content.view = new wp.media.view.Iframe({
8269              controller: this
8270          });
8271      },
8272  
8273      /**
8274       * Cleans up the iframe content view.
8275       */
8276      iframeContentCleanup: function() {
8277          this.$el.removeClass('hide-toolbar');
8278      },
8279  
8280      /**
8281       * Creates the iframe menu.
8282       *
8283       * @param {wp.media.view.Menu} view The menu view.
8284       */
8285      iframeMenu: function( view ) {
8286          var views = {};
8287  
8288          if ( ! view ) {
8289              return;
8290          }
8291  
8292          _.each( wp.media.view.settings.tabs, function( title, id ) {
8293              views[ 'iframe:' + id ] = {
8294                  text: this.state( 'iframe:' + id ).get('title'),
8295                  priority: 200
8296              };
8297          }, this );
8298  
8299          view.set( views );
8300      },
8301  
8302      /**
8303       * Hijacks the Thickbox close function to close the media modal.
8304       */
8305      hijackThickbox: function() {
8306          var frame = this;
8307  
8308          if ( ! window.tb_remove || this._tb_remove ) {
8309              return;
8310          }
8311  
8312          this._tb_remove = window.tb_remove;
8313          window.tb_remove = function() {
8314              frame.close();
8315              frame.reset();
8316              frame.setState( frame.options.state );
8317              frame._tb_remove.call( window );
8318          };
8319      },
8320  
8321      /**
8322       * Restores the Thickbox close function.
8323       */
8324      restoreThickbox: function() {
8325          if ( ! this._tb_remove ) {
8326              return;
8327          }
8328  
8329          window.tb_remove = this._tb_remove;
8330          delete this._tb_remove;
8331      }
8332  });
8333  
8334  // Map some of the modal's methods to the frame.
8335  _.each(['open','close','attach','detach','escape'], function( method ) {
8336      /**
8337       * Opens the media frame modal.
8338       *
8339       * @function open
8340       * @memberOf wp.media.view.MediaFrame
8341       * @instance
8342       *
8343       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
8344       */
8345      /**
8346       * Closes the media frame modal.
8347       *
8348       * @function close
8349       * @memberOf wp.media.view.MediaFrame
8350       * @instance
8351       *
8352       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
8353       */
8354      /**
8355       * Attaches the media frame to the DOM.
8356       *
8357       * @function attach
8358       * @memberOf wp.media.view.MediaFrame
8359       * @instance
8360       *
8361       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
8362       */
8363      /**
8364       * Detaches the media frame from the DOM.
8365       *
8366       * @function detach
8367       * @memberOf wp.media.view.MediaFrame
8368       * @instance
8369       *
8370       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
8371       */
8372      /**
8373       * Triggers the escape action on the media frame modal.
8374       *
8375       * @function escape
8376       * @memberOf wp.media.view.MediaFrame
8377       * @instance
8378       *
8379       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
8380       */
8381      MediaFrame.prototype[ method ] = function() {
8382          if ( this.modal ) {
8383              this.modal[ method ].apply( this.modal, arguments );
8384          }
8385          return this;
8386      };
8387  });
8388  
8389  module.exports = MediaFrame;
8390  
8391  
8392  /***/ },
8393  
8394  /***/ 9013
8395  (module) {
8396  
8397  var MenuItem;
8398  
8399  /**
8400   * wp.media.view.MenuItem
8401   *
8402   * @memberOf wp.media.view
8403   *
8404   * @class
8405   * @augments wp.media.View
8406   * @augments wp.Backbone.View
8407   * @augments Backbone.View
8408   */
8409  MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{
8410      tagName:   'button',
8411      className: 'media-menu-item',
8412  
8413      attributes: {
8414          type: 'button',
8415          role: 'tab'
8416      },
8417  
8418      events: {
8419          'click': '_click'
8420      },
8421  
8422      /**
8423       * Allows to override the click event.
8424       */
8425      _click: function() {
8426          var clickOverride = this.options.click;
8427  
8428          if ( clickOverride ) {
8429              clickOverride.call( this );
8430          } else {
8431              this.click();
8432          }
8433      },
8434  
8435      /**
8436       * Handles the click event.
8437       */
8438      click: function() {
8439          var state = this.options.state;
8440  
8441          if ( state ) {
8442              this.controller.setState( state );
8443              // Toggle the menu visibility in the responsive view.
8444              this.views.parent.$el.removeClass( 'visible' ); // @todo Or hide on any click, see below.
8445          }
8446      },
8447  
8448      /**
8449       * Renders the menu item.
8450       *
8451       * @return {wp.media.view.MenuItem} returns itself to allow chaining.
8452       */
8453      render: function() {
8454          var options = this.options,
8455              menuProperty = options.state || options.contentMode;
8456  
8457          if ( options.text ) {
8458              this.$el.text( options.text );
8459          } else if ( options.html ) {
8460              this.$el.html( options.html );
8461          }
8462  
8463          // Set the menu item ID based on the frame state associated to the menu item.
8464          this.$el.attr( 'id', 'menu-item-' + menuProperty );
8465  
8466          return this;
8467      }
8468  });
8469  
8470  module.exports = MenuItem;
8471  
8472  
8473  /***/ },
8474  
8475  /***/ 1
8476  (module) {
8477  
8478  var MenuItem = wp.media.view.MenuItem,
8479      PriorityList = wp.media.view.PriorityList,
8480      Menu;
8481  
8482  /**
8483   * wp.media.view.Menu
8484   *
8485   * @memberOf wp.media.view
8486   *
8487   * @class
8488   * @augments wp.media.view.PriorityList
8489   * @augments wp.media.View
8490   * @augments wp.Backbone.View
8491   * @augments Backbone.View
8492   */
8493  Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{
8494      tagName:   'div',
8495      className: 'media-menu',
8496      property:  'state',
8497      ItemView:  MenuItem,
8498      region:    'menu',
8499  
8500      attributes: {
8501          role:               'tablist',
8502          'aria-orientation': 'horizontal'
8503      },
8504  
8505      /**
8506       * Initializes the menu view.
8507       */
8508      initialize: function() {
8509          this._views = {};
8510  
8511          this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
8512          delete this.options.views;
8513  
8514          if ( ! this.options.silent ) {
8515              this.render();
8516          }
8517  
8518          // Initialize the Focus Manager.
8519          this.focusManager = new wp.media.view.FocusManager( {
8520              el:   this.el,
8521              mode: 'tabsNavigation'
8522          } );
8523  
8524          // The menu is always rendered and can be visible or hidden on some frames.
8525          this.isVisible = true;
8526      },
8527  
8528      /**
8529       * Creates a view for the given options and id.
8530       *
8531       * @param {Object} options The options for creating the view.
8532       * @param {string} id      The id of the view to create.
8533       * @return {wp.media.View} The view instance.
8534       */
8535      toView: function( options, id ) {
8536          options = options || {};
8537          options[ this.property ] = options[ this.property ] || id;
8538          return new this.ItemView( options ).render();
8539      },
8540  
8541      /**
8542       * Updates the menu when the state changes.
8543       */
8544      ready: function() {
8545          /**
8546           * call 'ready' directly on the parent class
8547           */
8548          PriorityList.prototype.ready.apply( this, arguments );
8549          this.visibility();
8550  
8551          // Set up aria tabs initial attributes.
8552          this.focusManager.setupAriaTabs();
8553      },
8554  
8555      /**
8556       * Sets the menu items.
8557       */
8558      set: function() {
8559          /**
8560           * call 'set' directly on the parent class
8561           */
8562          PriorityList.prototype.set.apply( this, arguments );
8563          this.visibility();
8564      },
8565  
8566      /**
8567       * Unsets the menu items.
8568       */
8569      unset: function() {
8570          /**
8571           * call 'unset' directly on the parent class
8572           */
8573          PriorityList.prototype.unset.apply( this, arguments );
8574          this.visibility();
8575      },
8576  
8577      /**
8578       * Updates the menu visibility.
8579       */
8580      visibility: function() {
8581          var region = this.region,
8582              view = this.controller[ region ].get(),
8583              views = this.views.get(),
8584              hide = ! views || views.length < 2;
8585  
8586          if ( this === view ) {
8587              // Flag this menu as hidden or visible.
8588              this.isVisible = ! hide;
8589              // Set or remove a CSS class to hide the menu.
8590              this.controller.$el.toggleClass( 'hide-' + region, hide );
8591          }
8592      },
8593      /**
8594       * Selects the menu item with the given id.
8595       *
8596       * @param {string} id The menu item id.
8597       */
8598      select: function( id ) {
8599          var view = this.get( id );
8600  
8601          if ( ! view ) {
8602              return;
8603          }
8604  
8605          this.deselect();
8606          view.$el.addClass('active');
8607  
8608          // Set up again the aria tabs initial attributes after the menu updates.
8609          this.focusManager.setupAriaTabs();
8610      },
8611  
8612      /**
8613       * Deselects the menu items.
8614       */
8615      deselect: function() {
8616          this.$el.children().removeClass('active');
8617      },
8618  
8619      /**
8620       * Hides the menu item with the given id.
8621       *
8622       * @param {string} id The menu item id.
8623       */
8624      hide: function( id ) {
8625          var view = this.get( id );
8626  
8627          if ( ! view ) {
8628              return;
8629          }
8630  
8631          view.$el.addClass('hidden');
8632      },
8633  
8634      /**
8635       * Shows the menu item with the given id.
8636       *
8637       * @param {string} id The menu item id.
8638       */
8639      show: function( id ) {
8640          var view = this.get( id );
8641  
8642          if ( ! view ) {
8643              return;
8644          }
8645  
8646          view.$el.removeClass('hidden');
8647      }
8648  });
8649  
8650  module.exports = Menu;
8651  
8652  
8653  /***/ },
8654  
8655  /***/ 2621
8656  (module) {
8657  
8658  var $ = jQuery,
8659      Modal;
8660  
8661  /**
8662   * wp.media.view.Modal
8663   *
8664   * A modal view, which the media modal uses as its default container.
8665   *
8666   * @memberOf wp.media.view
8667   *
8668   * @class
8669   * @augments wp.media.View
8670   * @augments wp.Backbone.View
8671   * @augments Backbone.View
8672   */
8673  Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{
8674      tagName:  'div',
8675      template: wp.template('media-modal'),
8676  
8677      events: {
8678          'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
8679          'keydown': 'keydown'
8680      },
8681  
8682      clickedOpenerEl: null,
8683  
8684      initialize: function() {
8685          _.defaults( this.options, {
8686              container:      document.body,
8687              title:          '',
8688              propagate:      true,
8689              hasCloseButton: true
8690          });
8691  
8692          this.titleId = _.uniqueId( 'media-frame-title-' );
8693  
8694          this.focusManager = new wp.media.view.FocusManager({
8695              el: this.el
8696          });
8697      },
8698      /**
8699       * Prepares the data for the modal template.
8700       *
8701       * @since 7.2.0 Added the `titleId` property.
8702       *
8703       * @return {Object} The prepared data.
8704       */
8705      prepare: function() {
8706          return {
8707              title:          this.options.title,
8708              hasCloseButton: this.options.hasCloseButton,
8709              titleId:        this.titleId
8710          };
8711      },
8712  
8713      /**
8714       * Attaches the modal to the DOM and triggers the ready event.
8715       *
8716       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8717       */
8718      attach: function() {
8719          if ( this.views.attached ) {
8720              return this;
8721          }
8722  
8723          if ( ! this.views.rendered ) {
8724              this.render();
8725          }
8726  
8727          this.$el.appendTo( this.options.container );
8728  
8729          // Manually mark the view as attached and trigger ready.
8730          this.views.attached = true;
8731          this.views.ready();
8732  
8733          return this.propagate('attach');
8734      },
8735  
8736      /**
8737       * Detaches the modal from the DOM and triggers the detach event.
8738       *
8739       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8740       */
8741      detach: function() {
8742          if ( this.$el.is(':visible') ) {
8743              this.close();
8744          }
8745  
8746          this.$el.detach();
8747          this.views.attached = false;
8748          return this.propagate('detach');
8749      },
8750  
8751      /**
8752       * Opens the modal and triggers the open event.
8753       *
8754       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8755       */
8756      open: function() {
8757          var $el = this.$el,
8758              mceEditor;
8759  
8760          if ( $el.is(':visible') ) {
8761              return this;
8762          }
8763  
8764          this.clickedOpenerEl = document.activeElement;
8765  
8766          if ( ! this.views.attached ) {
8767              this.attach();
8768          }
8769  
8770          // Disable page scrolling.
8771          $( 'body' ).addClass( 'modal-open' );
8772  
8773          $el.show();
8774  
8775          // Try to close the onscreen keyboard.
8776          if ( 'ontouchend' in document ) {
8777              if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
8778                  mceEditor.iframeElement.focus();
8779                  mceEditor.iframeElement.blur();
8780  
8781                  setTimeout( function() {
8782                      mceEditor.iframeElement.blur();
8783                  }, 100 );
8784              }
8785          }
8786  
8787          // Set initial focus on the content instead of this view element, to avoid page scrolling.
8788          this.$( '.media-modal' ).trigger( 'focus' );
8789  
8790          // Hide the page content from assistive technologies.
8791          this.focusManager.setAriaHiddenOnBodyChildren( $el );
8792  
8793          return this.propagate('open');
8794      },
8795  
8796      /**
8797       * Closes the modal and triggers the close event.
8798       *
8799       * @param {Object}  [options={}]     The options for closing the modal.
8800       * @param {boolean} [options.escape] Whether the modal is being closed due to the escape key.
8801       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8802       */
8803      close: function( options ) {
8804          if ( ! this.views.attached || ! this.$el.is(':visible') ) {
8805              return this;
8806          }
8807  
8808          // Pause current audio/video even after closing the modal.
8809          $( '.mejs-pause button' ).trigger( 'click' );
8810  
8811          // Enable page scrolling.
8812          $( 'body' ).removeClass( 'modal-open' );
8813  
8814          // Hide the modal element by adding display:none.
8815          this.$el.hide();
8816  
8817          /*
8818           * Make visible again to assistive technologies all body children that
8819           * have been made hidden when the modal opened.
8820           */
8821          this.focusManager.removeAriaHiddenFromBodyChildren();
8822  
8823          // Move focus back in useful location once modal is closed.
8824          if ( null !== this.clickedOpenerEl ) {
8825              // Move focus back to the element that opened the modal.
8826              this.clickedOpenerEl.focus();
8827          } else {
8828              // Fallback to the admin page main element.
8829              $( '#wpbody-content' )
8830                  .attr( 'tabindex', '-1' )
8831                  .trigger( 'focus' );
8832          }
8833  
8834          this.propagate('close');
8835  
8836          if ( options && options.escape ) {
8837              this.propagate('escape');
8838          }
8839  
8840          return this;
8841      },
8842      /**
8843       * Closes the modal and triggers the escape event.
8844       *
8845       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8846       */
8847      escape: function() {
8848          return this.close({ escape: true });
8849      },
8850      /**
8851       * Handles the escape key press event to close the modal.
8852       *
8853       * @param {JQuery.Event} event The jQuery event object.
8854       */
8855      escapeHandler: function( event ) {
8856          event.preventDefault();
8857          this.escape();
8858      },
8859  
8860      /**
8861       * Handles the selection of attachments when the command or control key is pressed with the enter key.
8862       *
8863       * @since 6.7
8864       *
8865       * @param {JQuery.Event} event The jQuery event object.
8866       */
8867      selectHandler: function( event ) {
8868          var selection = this.controller.state().get( 'selection' );
8869  
8870          if ( selection.length <= 0 ) {
8871              return;
8872          }
8873  
8874          if ( 'insert' === this.controller.options.state ) {
8875              this.controller.trigger( 'insert', selection );
8876          } else {
8877              this.controller.trigger( 'select', selection );
8878              event.preventDefault();
8879              this.escape();
8880          }
8881      },
8882  
8883      /**
8884       * Sets the content of the modal by registering views to the '.media-modal-content' selector.
8885       *
8886       * @param {Array|Object} content Views to register to '.media-modal-content'
8887       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8888       */
8889      content: function( content ) {
8890          this.views.set( '.media-modal-content', content );
8891          return this;
8892      },
8893  
8894      /**
8895       * Triggers a modal event and if the `propagate` option is set,
8896       * forwards events to the modal's controller.
8897       *
8898       * @param {string} id The ID of the event to propagate.
8899       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8900       */
8901      propagate: function( id ) {
8902          this.trigger( id );
8903  
8904          if ( this.options.propagate ) {
8905              this.controller.trigger( id );
8906          }
8907  
8908          return this;
8909      },
8910      /**
8911       * Handles keydown events within the modal.
8912       *
8913       * @param {JQuery.Event} event The jQuery keydown event object.
8914       */
8915      keydown: function( event ) {
8916          // Close the modal when escape is pressed.
8917          if ( 27 === event.which && this.$el.is(':visible') ) {
8918              this.escape();
8919              event.stopImmediatePropagation();
8920          }
8921  
8922          // Select the attachment when command or control and enter are pressed.
8923          if ( ( 13 === event.which || 10 === event.which ) && ( event.metaKey || event.ctrlKey ) ) {
8924              this.selectHandler( event );
8925              event.stopImmediatePropagation();
8926          }
8927  
8928      }
8929  });
8930  
8931  module.exports = Modal;
8932  
8933  
8934  /***/ },
8935  
8936  /***/ 8815
8937  (module) {
8938  
8939  /**
8940   * wp.media.view.PriorityList
8941   *
8942   * @memberOf wp.media.view
8943   *
8944   * @class
8945   * @augments wp.media.View
8946   * @augments wp.Backbone.View
8947   * @augments Backbone.View
8948   */
8949  var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{
8950      tagName:   'div',
8951  
8952      initialize: function() {
8953          this._views = {};
8954  
8955          this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
8956          delete this.options.views;
8957  
8958          if ( ! this.options.silent ) {
8959              this.render();
8960          }
8961      },
8962      /**
8963       * Adds a view to the list, sorted by its priority.
8964       *
8965       * @param {string}               id      The ID of the view to set.
8966       * @param {wp.media.View|Object} view    The view to set.
8967       * @param {Object}               options The options for setting the view.
8968       * @return {wp.media.view.PriorityList} Returns itself to allow chaining.
8969       */
8970      set: function( id, view, options ) {
8971          var priority, views, index;
8972  
8973          options = options || {};
8974  
8975          // Accept an object with an `id` : `view` mapping.
8976          if ( _.isObject( id ) ) {
8977              _.each( id, function( view, id ) {
8978                  this.set( id, view );
8979              }, this );
8980              return this;
8981          }
8982  
8983          if ( ! (view instanceof Backbone.View) ) {
8984              view = this.toView( view, id, options );
8985          }
8986          view.controller = view.controller || this.controller;
8987  
8988          this.unset( id );
8989  
8990          priority = view.options.priority || 10;
8991          views = this.views.get() || [];
8992  
8993          _.find( views, function( existing, i ) {
8994              if ( existing.options.priority > priority ) {
8995                  index = i;
8996                  return true;
8997              }
8998          });
8999  
9000          this._views[ id ] = view;
9001          this.views.add( view, {
9002              at: _.isNumber( index ) ? index : views.length || 0
9003          });
9004  
9005          return this;
9006      },
9007      /**
9008       * Retrieves a view by its ID.
9009       *
9010       * @param {string} id The ID of the view to retrieve.
9011       * @return {wp.media.View} Returns the view if found, otherwise undefined.
9012       */
9013      get: function( id ) {
9014          return this._views[ id ];
9015      },
9016      /**
9017       * Removes a view by its ID.
9018       *
9019       * @param {string} id The ID of the view to remove.
9020       * @return {wp.media.view.PriorityList} Returns itself to allow chaining.
9021       */
9022      unset: function( id ) {
9023          var view = this.get( id );
9024  
9025          if ( view ) {
9026              view.remove();
9027          }
9028  
9029          delete this._views[ id ];
9030          return this;
9031      },
9032      /**
9033       * Creates a view from an object of options.
9034       *
9035       * @param {Object} options The options for creating the view.
9036       * @return {wp.media.View} Returns the created view.
9037       */
9038      toView: function( options ) {
9039          return new wp.media.View( options );
9040      }
9041  });
9042  
9043  module.exports = PriorityList;
9044  
9045  
9046  /***/ },
9047  
9048  /***/ 6327
9049  (module) {
9050  
9051  /**
9052   * wp.media.view.RouterItem
9053   *
9054   * @memberOf wp.media.view
9055   *
9056   * @class
9057   * @augments wp.media.view.MenuItem
9058   * @augments wp.media.View
9059   * @augments wp.Backbone.View
9060   * @augments Backbone.View
9061   */
9062  var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{
9063      /**
9064       * On click handler to activate the content region's corresponding mode.
9065       */
9066      click: function() {
9067          var contentMode = this.options.contentMode;
9068          if ( contentMode ) {
9069              this.controller.content.mode( contentMode );
9070          }
9071      }
9072  });
9073  
9074  module.exports = RouterItem;
9075  
9076  
9077  /***/ },
9078  
9079  /***/ 4783
9080  (module) {
9081  
9082  var Menu = wp.media.view.Menu,
9083      Router;
9084  
9085  /**
9086   * wp.media.view.Router
9087   *
9088   * @memberOf wp.media.view
9089   *
9090   * @class
9091   * @augments wp.media.view.Menu
9092   * @augments wp.media.view.PriorityList
9093   * @augments wp.media.View
9094   * @augments wp.Backbone.View
9095   * @augments Backbone.View
9096   */
9097  Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{
9098      tagName:   'div',
9099      className: 'media-router',
9100      property:  'contentMode',
9101      ItemView:  wp.media.view.RouterItem,
9102      region:    'router',
9103  
9104      attributes: {
9105          role:               'tablist',
9106          'aria-orientation': 'horizontal'
9107      },
9108  
9109      initialize: function() {
9110          this.controller.on( 'content:render', this.update, this );
9111          // Call 'initialize' directly on the parent class.
9112          Menu.prototype.initialize.apply( this, arguments );
9113      },
9114  
9115      update: function() {
9116          var mode = this.controller.content.mode();
9117          if ( mode ) {
9118              this.select( mode );
9119          }
9120      }
9121  });
9122  
9123  module.exports = Router;
9124  
9125  
9126  /***/ },
9127  
9128  /***/ 2102
9129  (module) {
9130  
9131  var Search;
9132  
9133  /**
9134   * wp.media.view.Search
9135   *
9136   * @memberOf wp.media.view
9137   *
9138   * @class
9139   * @augments wp.media.View
9140   * @augments wp.Backbone.View
9141   * @augments Backbone.View
9142   */
9143  Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{
9144      tagName:   'input',
9145      className: 'search',
9146      id:        'media-search-input',
9147  
9148      attributes: {
9149          type: 'search'
9150      },
9151  
9152      events: {
9153          'input': 'search'
9154      },
9155  
9156      /**
9157       * Renders the search input.
9158       *
9159       * @return {wp.media.view.Search} Returns itself to allow chaining.
9160       */
9161      render: function() {
9162          this.el.value = this.model.escape('search');
9163          return this;
9164      },
9165  
9166      /**
9167       * Searches the media library.
9168       *
9169       * @param {JQuery.Event} event The input event.
9170       */
9171      search: _.debounce( function( event ) {
9172          var searchTerm = event.target.value.trim();
9173  
9174          // Trigger the search only after 2 ASCII characters.
9175          if ( searchTerm && searchTerm.length > 1 ) {
9176              this.model.set( 'search', searchTerm );
9177          } else {
9178              this.model.unset( 'search' );
9179          }
9180      }, 500 )
9181  });
9182  
9183  module.exports = Search;
9184  
9185  
9186  /***/ },
9187  
9188  /***/ 8282
9189  (module) {
9190  
9191  var _n = wp.i18n._n,
9192      sprintf = wp.i18n.sprintf,
9193      Selection;
9194  
9195  /**
9196   * wp.media.view.Selection
9197   *
9198   * @memberOf wp.media.view
9199   *
9200   * @class
9201   * @augments wp.media.View
9202   * @augments wp.Backbone.View
9203   * @augments Backbone.View
9204   */
9205  Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */{
9206      tagName:   'div',
9207      className: 'media-selection',
9208      template:  wp.template('media-selection'),
9209  
9210      events: {
9211          'click .edit-selection':  'edit',
9212          'click .clear-selection': 'clear'
9213      },
9214  
9215      initialize: function() {
9216          _.defaults( this.options, {
9217              editable:  false,
9218              clearable: true
9219          });
9220  
9221          /**
9222           * @member {wp.media.view.Attachments.Selection}
9223           */
9224          this.attachments = new wp.media.view.Attachments.Selection({
9225              controller: this.controller,
9226              collection: this.collection,
9227              selection:  this.collection,
9228              model:      new Backbone.Model()
9229          });
9230  
9231          this.views.set( '.selection-view', this.attachments );
9232          this.collection.on( 'add remove reset', this.refresh, this );
9233          this.controller.on( 'content:activate', this.refresh, this );
9234      },
9235  
9236      ready: function() {
9237          this.refresh();
9238      },
9239  
9240      refresh: function() {
9241          // If the selection hasn't been rendered, bail.
9242          if ( ! this.$el.children().length ) {
9243              return;
9244          }
9245  
9246          var collection = this.collection,
9247              editing = 'edit-selection' === this.controller.content.mode();
9248  
9249          // If nothing is selected, display nothing.
9250          this.$el.toggleClass( 'empty', ! collection.length );
9251          this.$el.toggleClass( 'one', 1 === collection.length );
9252          this.$el.toggleClass( 'editing', editing );
9253  
9254          this.$( '.count' ).text(
9255              /* translators: %s: Number of selected media attachments. */
9256              sprintf( _n( '%s item selected', '%s items selected', collection.length ), collection.length )
9257          );
9258      },
9259  
9260      edit: function( event ) {
9261          event.preventDefault();
9262          if ( this.options.editable ) {
9263              this.options.editable.call( this, this.collection );
9264          }
9265      },
9266  
9267      clear: function( event ) {
9268          event.preventDefault();
9269          this.collection.reset();
9270  
9271          // Move focus to the modal.
9272          this.controller.modal.focusManager.focus();
9273      }
9274  });
9275  
9276  module.exports = Selection;
9277  
9278  
9279  /***/ },
9280  
9281  /***/ 1915
9282  (module) {
9283  
9284  var View = wp.media.View,
9285      $ = Backbone.$,
9286      Settings;
9287  
9288  /**
9289   * wp.media.view.Settings
9290   *
9291   * @memberOf wp.media.view
9292   *
9293   * @class
9294   * @augments wp.media.View
9295   * @augments wp.Backbone.View
9296   * @augments Backbone.View
9297   */
9298  Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{
9299      events: {
9300          'click button':    'updateHandler',
9301          'change input':    'updateHandler',
9302          'change select':   'updateHandler',
9303          'change textarea': 'updateHandler'
9304      },
9305  
9306      /**
9307       * Initializes the settings view.
9308       */
9309      initialize: function() {
9310          this.model = this.model || new Backbone.Model();
9311          this.listenTo( this.model, 'change', this.updateChanges );
9312      },
9313  
9314      /**
9315       * Prepares the data for rendering.
9316       *
9317       * @return {Object} The data to be used in the template.
9318       */
9319      prepare: function() {
9320          return _.defaults({
9321              model: this.model.toJSON()
9322          }, this.options );
9323      },
9324      /**
9325       * Renders the settings view.
9326       *
9327       * @return {wp.media.view.Settings} Returns itself to allow chaining.
9328       */
9329      render: function() {
9330          View.prototype.render.apply( this, arguments );
9331          // Select the correct values.
9332          _( this.model.attributes ).chain().keys().each( this.update, this );
9333          return this;
9334      },
9335      /**
9336       * Updates the selected value for a setting.
9337       *
9338       * @param {string} key The key of the setting to update.
9339       */
9340      update: function( key ) {
9341          var value = this.model.get( key ),
9342              $setting = this.$('[data-setting="' + key + '"]'),
9343              $buttons, $value;
9344  
9345          // Bail if we didn't find a matching setting.
9346          if ( ! $setting.length ) {
9347              return;
9348          }
9349  
9350          // Attempt to determine how the setting is rendered and update
9351          // the selected value.
9352  
9353          // Handle dropdowns.
9354          if ( $setting.is('select') ) {
9355              $value = $setting.find('[value="' + value + '"]');
9356  
9357              if ( $value.length ) {
9358                  $setting.find('option').prop( 'selected', false );
9359                  $value.prop( 'selected', true );
9360              } else {
9361                  // If we can't find the desired value, record what *is* selected.
9362                  this.model.set( key, $setting.find(':selected').val() );
9363              }
9364  
9365          // Handle button groups.
9366          } else if ( $setting.hasClass('button-group') ) {
9367              $buttons = $setting.find( 'button' )
9368                  .removeClass( 'active' )
9369                  .attr( 'aria-pressed', 'false' );
9370              $buttons.filter( '[value="' + value + '"]' )
9371                  .addClass( 'active' )
9372                  .attr( 'aria-pressed', 'true' );
9373  
9374          // Handle text inputs and textareas.
9375          } else if ( $setting.is('input[type="text"], textarea') ) {
9376              if ( ! $setting.is(':focus') ) {
9377                  $setting.val( value );
9378              }
9379          // Handle checkboxes.
9380          } else if ( $setting.is('input[type="checkbox"]') ) {
9381              $setting.prop( 'checked', !! value && 'false' !== value );
9382          }
9383      },
9384      /**
9385       * Updates the model when a setting is changed.
9386       *
9387       * @param {JQuery.Event} event The jQuery event object associated with the setting change.
9388       */
9389      updateHandler: function( event ) {
9390          var $setting = $( event.target ).closest('[data-setting]'),
9391              value = event.target.value,
9392              userSetting;
9393  
9394          event.preventDefault();
9395  
9396          if ( ! $setting.length ) {
9397              return;
9398          }
9399  
9400          // Use the correct value for checkboxes.
9401          if ( $setting.is('input[type="checkbox"]') ) {
9402              value = $setting[0].checked;
9403          }
9404  
9405          // Update the corresponding setting.
9406          this.model.set( $setting.data('setting'), value );
9407  
9408          // If the setting has a corresponding user setting,
9409          // update that as well.
9410          userSetting = $setting.data('userSetting');
9411          if ( userSetting ) {
9412              window.setUserSetting( userSetting, value );
9413          }
9414      },
9415  
9416      /**
9417       * Updates the view when the model changes.
9418       *
9419       * @param {Backbone.Model} model The model that changed.
9420       */
9421      updateChanges: function( model ) {
9422          if ( model.hasChanged() ) {
9423              _( model.changed ).chain().keys().each( this.update, this );
9424          }
9425      }
9426  });
9427  
9428  module.exports = Settings;
9429  
9430  
9431  /***/ },
9432  
9433  /***/ 7656
9434  (module) {
9435  
9436  var Settings = wp.media.view.Settings,
9437      AttachmentDisplay;
9438  
9439  /**
9440   * wp.media.view.Settings.AttachmentDisplay
9441   *
9442   * @memberOf wp.media.view.Settings
9443   *
9444   * @class
9445   * @augments wp.media.view.Settings
9446   * @augments wp.media.View
9447   * @augments wp.Backbone.View
9448   * @augments Backbone.View
9449   */
9450  AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.AttachmentDisplay.prototype */{
9451      className: 'attachment-display-settings',
9452      template:  wp.template('attachment-display-settings'),
9453  
9454      /**
9455       * Initializes the attachment display settings view.
9456       */
9457      initialize: function() {
9458          var attachment = this.options.attachment;
9459  
9460          _.defaults( this.options, {
9461              userSettings: false
9462          });
9463          // Call 'initialize' directly on the parent class.
9464          Settings.prototype.initialize.apply( this, arguments );
9465          this.listenTo( this.model, 'change:link', this.updateLinkTo );
9466  
9467          if ( attachment ) {
9468              attachment.on( 'change:uploading', this.render, this );
9469          }
9470      },
9471  
9472      /**
9473       * Disposes of the attachment display settings view.
9474       */
9475      dispose: function() {
9476          var attachment = this.options.attachment;
9477          if ( attachment ) {
9478              attachment.off( null, null, this );
9479          }
9480          /**
9481           * call 'dispose' directly on the parent class
9482           */
9483          Settings.prototype.dispose.apply( this, arguments );
9484      },
9485      /**
9486       * Renders the attachment display settings view.
9487       *
9488       * @return {wp.media.view.AttachmentDisplay} Returns itself to allow chaining.
9489       */
9490      render: function() {
9491          var attachment = this.options.attachment;
9492          if ( attachment ) {
9493              _.extend( this.options, {
9494                  sizes: attachment.get('sizes'),
9495                  type:  attachment.get('type')
9496              });
9497          }
9498          /**
9499           * call 'render' directly on the parent class
9500           */
9501          Settings.prototype.render.call( this );
9502          this.updateLinkTo();
9503          return this;
9504      },
9505  
9506      /**
9507       * Updates the linkTo setting.
9508       */
9509      updateLinkTo: function() {
9510          var linkTo = this.model.get('link'),
9511              $input = this.$('.link-to-custom'),
9512              attachment = this.options.attachment;
9513  
9514          if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
9515              $input.closest( '.setting' ).addClass( 'hidden' );
9516              return;
9517          }
9518  
9519          if ( attachment ) {
9520              if ( 'post' === linkTo ) {
9521                  $input.val( attachment.get('link') );
9522              } else if ( 'file' === linkTo ) {
9523                  $input.val( attachment.get('url') );
9524              } else if ( ! this.model.get('linkUrl') ) {
9525                  $input.val('http://');
9526              }
9527  
9528              $input.prop( 'readonly', 'custom' !== linkTo );
9529          }
9530  
9531          $input.closest( '.setting' ).removeClass( 'hidden' );
9532          if ( $input.length ) {
9533              $input[0].scrollIntoView();
9534          }
9535      }
9536  });
9537  
9538  module.exports = AttachmentDisplay;
9539  
9540  
9541  /***/ },
9542  
9543  /***/ 7266
9544  (module) {
9545  
9546  /**
9547   * wp.media.view.Settings.Gallery
9548   *
9549   * @memberOf wp.media.view.Settings
9550   *
9551   * @class
9552   * @augments wp.media.view.Settings
9553   * @augments wp.media.View
9554   * @augments wp.Backbone.View
9555   * @augments Backbone.View
9556   */
9557  var Gallery = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Gallery.prototype */{
9558      className: 'collection-settings gallery-settings',
9559      template:  wp.template('gallery-settings')
9560  });
9561  
9562  module.exports = Gallery;
9563  
9564  
9565  /***/ },
9566  
9567  /***/ 2356
9568  (module) {
9569  
9570  /**
9571   * wp.media.view.Settings.Playlist
9572   *
9573   * @memberOf wp.media.view.Settings
9574   *
9575   * @class
9576   * @augments wp.media.view.Settings
9577   * @augments wp.media.View
9578   * @augments wp.Backbone.View
9579   * @augments Backbone.View
9580   */
9581  var Playlist = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Playlist.prototype */{
9582      className: 'collection-settings playlist-settings',
9583      template:  wp.template('playlist-settings')
9584  });
9585  
9586  module.exports = Playlist;
9587  
9588  
9589  /***/ },
9590  
9591  /***/ 1992
9592  (module) {
9593  
9594  /**
9595   * wp.media.view.Sidebar
9596   *
9597   * @memberOf wp.media.view
9598   *
9599   * @class
9600   * @augments wp.media.view.PriorityList
9601   * @augments wp.media.View
9602   * @augments wp.Backbone.View
9603   * @augments Backbone.View
9604   */
9605  var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{
9606      className: 'media-sidebar'
9607  });
9608  
9609  module.exports = Sidebar;
9610  
9611  
9612  /***/ },
9613  
9614  /***/ 443
9615  (module) {
9616  
9617  var View = wp.media.view,
9618      SiteIconCropper;
9619  
9620  /**
9621   * wp.media.view.SiteIconCropper
9622   *
9623   * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon.
9624   *
9625   * Takes imgAreaSelect options from
9626   * wp.customize.SiteIconControl.calculateImageSelectOptions.
9627   *
9628   * @memberOf wp.media.view
9629   *
9630   * @class
9631   * @augments wp.media.view.Cropper
9632   * @augments wp.media.View
9633   * @augments wp.Backbone.View
9634   * @augments Backbone.View
9635   */
9636  SiteIconCropper = View.Cropper.extend(/** @lends wp.media.view.SiteIconCropper.prototype */{
9637      className: 'crop-content site-icon',
9638  
9639      ready: function () {
9640          View.Cropper.prototype.ready.apply( this, arguments );
9641  
9642          this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) );
9643      },
9644  
9645      addSidebar: function() {
9646          this.sidebar = new wp.media.view.Sidebar({
9647              controller: this.controller
9648          });
9649  
9650          this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({
9651              controller: this.controller,
9652              attachment: this.options.attachment
9653          }) );
9654  
9655          this.controller.cropperView.views.add( this.sidebar );
9656      }
9657  });
9658  
9659  module.exports = SiteIconCropper;
9660  
9661  
9662  /***/ },
9663  
9664  /***/ 7810
9665  (module) {
9666  
9667  var View = wp.media.View,
9668      $ = jQuery,
9669      SiteIconPreview;
9670  
9671  /**
9672   * wp.media.view.SiteIconPreview
9673   *
9674   * Shows a preview of the Site Icon as a favicon and app icon while cropping.
9675   *
9676   * @memberOf wp.media.view
9677   *
9678   * @class
9679   * @augments wp.media.View
9680   * @augments wp.Backbone.View
9681   * @augments Backbone.View
9682   */
9683  SiteIconPreview = View.extend(/** @lends wp.media.view.SiteIconPreview.prototype */{
9684      className: 'site-icon-preview-crop-modal',
9685      template: wp.template( 'site-icon-preview-crop' ),
9686  
9687      ready: function() {
9688          this.controller.imgSelect.setOptions({
9689              onInit: this.updatePreview,
9690              onSelectChange: this.updatePreview
9691          });
9692      },
9693  
9694      prepare: function() {
9695          return {
9696              url: this.options.attachment.get( 'url' )
9697          };
9698      },
9699  
9700      updatePreview: function( img, coords ) {
9701          var rx = 64 / coords.width,
9702              ry = 64 / coords.height,
9703              preview_rx = 24 / coords.width,
9704              preview_ry = 24 / coords.height;
9705  
9706          $( '#preview-app-icon' ).css({
9707              width: Math.round(rx * this.imageWidth ) + 'px',
9708              height: Math.round(ry * this.imageHeight ) + 'px',
9709              marginLeft: '-' + Math.round(rx * coords.x1) + 'px',
9710              marginTop: '-' + Math.round(ry * coords.y1) + 'px'
9711          });
9712  
9713          $( '#preview-favicon' ).css({
9714              width: Math.round( preview_rx * this.imageWidth ) + 'px',
9715              height: Math.round( preview_ry * this.imageHeight ) + 'px',
9716              marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px',
9717              marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px'
9718          });
9719      }
9720  });
9721  
9722  module.exports = SiteIconPreview;
9723  
9724  
9725  /***/ },
9726  
9727  /***/ 9141
9728  (module) {
9729  
9730  /**
9731   * wp.media.view.Spinner
9732   *
9733   * Represents a spinner in the Media Library.
9734   *
9735   * @since 3.9.0
9736   *
9737   * @memberOf wp.media.view
9738   *
9739   * @class
9740   * @augments wp.media.View
9741   * @augments wp.Backbone.View
9742   * @augments Backbone.View
9743   */
9744  var Spinner = wp.media.View.extend(/** @lends wp.media.view.Spinner.prototype */{
9745      tagName:   'span',
9746      className: 'spinner',
9747      spinnerTimeout: false,
9748      delay: 400,
9749  
9750      /**
9751       * Shows the spinner. Delays the visibility by the configured amount.
9752       *
9753       * @since 3.9.0
9754       *
9755       * @return {wp.media.view.Spinner} The spinner.
9756       */
9757      show: function() {
9758          if ( ! this.spinnerTimeout ) {
9759              this.spinnerTimeout = _.delay(function( $el ) {
9760                  $el.addClass( 'is-active' );
9761              }, this.delay, this.$el );
9762          }
9763  
9764          return this;
9765      },
9766  
9767      /**
9768       * Hides the spinner.
9769       *
9770       * @since 3.9.0
9771       *
9772       * @return {wp.media.view.Spinner} The spinner.
9773       */
9774      hide: function() {
9775          this.$el.removeClass( 'is-active' );
9776          this.spinnerTimeout = clearTimeout( this.spinnerTimeout );
9777  
9778          return this;
9779      }
9780  });
9781  
9782  module.exports = Spinner;
9783  
9784  
9785  /***/ },
9786  
9787  /***/ 5275
9788  (module) {
9789  
9790  var View = wp.media.View,
9791      Toolbar;
9792  
9793  /**
9794   * wp.media.view.Toolbar
9795   *
9796   * A toolbar which consists of a primary and a secondary section. Each sections
9797   * can be filled with views.
9798   *
9799   * @memberOf wp.media.view
9800   *
9801   * @class
9802   * @augments wp.media.View
9803   * @augments wp.Backbone.View
9804   * @augments Backbone.View
9805   */
9806  Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{
9807      tagName:   'div',
9808      className: 'media-toolbar',
9809  
9810      /**
9811       * Initializes the toolbar view.
9812       */
9813      initialize: function() {
9814          var state = this.controller.state(),
9815              selection = this.selection = state.get('selection'),
9816              library = this.library = state.get('library');
9817  
9818          this._views = {};
9819  
9820          // The toolbar is composed of two `PriorityList` views.
9821          this.primary   = new wp.media.view.PriorityList();
9822          this.secondary = new wp.media.view.PriorityList();
9823          this.tertiary  = new wp.media.view.PriorityList();
9824          this.primary.$el.addClass('media-toolbar-primary search-form');
9825          this.secondary.$el.addClass('media-toolbar-secondary');
9826          this.tertiary.$el.addClass('media-bg-overlay');
9827  
9828          this.views.set([ this.secondary, this.primary, this.tertiary ]);
9829  
9830          if ( this.options.items ) {
9831              this.set( this.options.items, { silent: true });
9832          }
9833  
9834          if ( ! this.options.silent ) {
9835              this.render();
9836          }
9837  
9838          if ( selection ) {
9839              selection.on( 'add remove reset', this.refresh, this );
9840          }
9841  
9842          if ( library ) {
9843              library.on( 'add remove reset', this.refresh, this );
9844          }
9845      },
9846      /**
9847       * Disposes of the toolbar view.
9848       *
9849       * @return {wp.media.view.Toolbar} Returns itself to allow chaining
9850       */
9851      dispose: function() {
9852          if ( this.selection ) {
9853              this.selection.off( null, null, this );
9854          }
9855  
9856          if ( this.library ) {
9857              this.library.off( null, null, this );
9858          }
9859          /**
9860           * call 'dispose' directly on the parent class
9861           */
9862          return View.prototype.dispose.apply( this, arguments );
9863      },
9864  
9865      /**
9866       * Prepares the data for rendering.
9867       */
9868      ready: function() {
9869          this.refresh();
9870      },
9871  
9872      /**
9873       * Sets a view by its ID.
9874       *
9875       * @param {string}               id           The ID of the view to set.
9876       * @param {Backbone.View|Object} view         The view to set.
9877       * @param {Object}               [options={}] The options for setting the view.
9878       * @return {wp.media.view.Toolbar} Returns itself to allow chaining.
9879       */
9880      set: function( id, view, options ) {
9881          var list;
9882          options = options || {};
9883  
9884          // Accept an object with an `id` : `view` mapping.
9885          if ( _.isObject( id ) ) {
9886              _.each( id, function( view, id ) {
9887                  this.set( id, view, { silent: true });
9888              }, this );
9889  
9890          } else {
9891              if ( ! ( view instanceof Backbone.View ) ) {
9892                  view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
9893                  view = new wp.media.view.Button( view ).render();
9894              }
9895  
9896              view.controller = view.controller || this.controller;
9897  
9898              this._views[ id ] = view;
9899  
9900              list = view.options.priority < 0 ? 'secondary' : 'primary';
9901              this[ list ].set( id, view, options );
9902          }
9903  
9904          if ( ! options.silent ) {
9905              this.refresh();
9906          }
9907  
9908          return this;
9909      },
9910      /**
9911       * Retrieves a view by its ID.
9912       *
9913       * @param {string} id The ID of the view to retrieve.
9914       * @return {wp.media.view.Button} The view associated with the given ID, or undefined if no view is found.
9915       */
9916      get: function( id ) {
9917          return this._views[ id ];
9918      },
9919      /**
9920       * Unsets a view by its ID.
9921       *
9922       * @param {string} id           The ID of the view to unset.
9923       * @param {Object} [options={}] The options for unsetting the view.
9924       * @return {wp.media.view.Toolbar} Returns itself to allow chaining.
9925       */
9926      unset: function( id, options ) {
9927          delete this._views[ id ];
9928          this.primary.unset( id, options );
9929          this.secondary.unset( id, options );
9930          this.tertiary.unset( id, options );
9931  
9932          if ( ! options || ! options.silent ) {
9933              this.refresh();
9934          }
9935          return this;
9936      },
9937  
9938      /**
9939       * Refreshes the toolbar view.
9940       */
9941      refresh: function() {
9942          var state = this.controller.state(),
9943              library = state.get('library'),
9944              selection = state.get('selection');
9945  
9946          _.each( this._views, function( button ) {
9947              if ( ! button.model || ! button.options || ! button.options.requires ) {
9948                  return;
9949              }
9950  
9951              var requires = button.options.requires,
9952                  disabled = false,
9953                  modelsUploading = library && ! _.isEmpty( library.findWhere( { 'uploading': true } ) );
9954  
9955              // Prevent insertion of attachments if any of them are still uploading.
9956              if ( selection && selection.models ) {
9957                  disabled = _.some( selection.models, function( attachment ) {
9958                      return attachment.get('uploading') === true;
9959                  });
9960              }
9961              if ( requires.uploadingComplete && modelsUploading ) {
9962                  disabled = true;
9963              }
9964  
9965              if ( requires.selection && selection && ! selection.length ) {
9966                  disabled = true;
9967              } else if ( requires.library && library && ! library.length ) {
9968                  disabled = true;
9969              }
9970              button.model.set( 'disabled', disabled );
9971          });
9972      }
9973  });
9974  
9975  module.exports = Toolbar;
9976  
9977  
9978  /***/ },
9979  
9980  /***/ 397
9981  (module) {
9982  
9983  var Select = wp.media.view.Toolbar.Select,
9984      l10n = wp.media.view.l10n,
9985      Embed;
9986  
9987  /**
9988   * wp.media.view.Toolbar.Embed
9989   *
9990   * @memberOf wp.media.view.Toolbar
9991   *
9992   * @class
9993   * @augments wp.media.view.Toolbar.Select
9994   * @augments wp.media.view.Toolbar
9995   * @augments wp.media.View
9996   * @augments wp.Backbone.View
9997   * @augments Backbone.View
9998   */
9999  Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{
10000      initialize: function() {
10001          _.defaults( this.options, {
10002              text: l10n.insertIntoPost,
10003              requires: false
10004          });
10005          // Call 'initialize' directly on the parent class.
10006          Select.prototype.initialize.apply( this, arguments );
10007      },
10008  
10009      refresh: function() {
10010          var url = this.controller.state().props.get('url');
10011          this.get('select').model.set( 'disabled', ! url || url === 'http://' );
10012          /**
10013           * call 'refresh' directly on the parent class
10014           */
10015          Select.prototype.refresh.apply( this, arguments );
10016      }
10017  });
10018  
10019  module.exports = Embed;
10020  
10021  
10022  /***/ },
10023  
10024  /***/ 9458
10025  (module) {
10026  
10027  var Toolbar = wp.media.view.Toolbar,
10028      l10n = wp.media.view.l10n,
10029      Select;
10030  
10031  /**
10032   * wp.media.view.Toolbar.Select
10033   *
10034   * @memberOf wp.media.view.Toolbar
10035   *
10036   * @class
10037   * @augments wp.media.view.Toolbar
10038   * @augments wp.media.View
10039   * @augments wp.Backbone.View
10040   * @augments Backbone.View
10041   */
10042  Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{
10043      initialize: function() {
10044          var options = this.options;
10045  
10046          _.bindAll( this, 'clickSelect' );
10047  
10048          _.defaults( options, {
10049              event: 'select',
10050              state: false,
10051              reset: true,
10052              close: true,
10053              text:  l10n.select,
10054  
10055              // Does the button rely on the selection?
10056              requires: {
10057                  selection: true
10058              }
10059          });
10060  
10061          options.items = _.defaults( options.items || {}, {
10062              select: {
10063                  style:    'primary',
10064                  text:     options.text,
10065                  priority: 80,
10066                  click:    this.clickSelect,
10067                  requires: options.requires
10068              }
10069          });
10070          // Call 'initialize' directly on the parent class.
10071          Toolbar.prototype.initialize.apply( this, arguments );
10072      },
10073  
10074      clickSelect: function() {
10075          var options = this.options,
10076              controller = this.controller;
10077  
10078          if ( options.close ) {
10079              controller.close();
10080          }
10081  
10082          if ( options.event ) {
10083              controller.state().trigger( options.event );
10084          }
10085  
10086          if ( options.state ) {
10087              controller.setState( options.state );
10088          }
10089  
10090          if ( options.reset ) {
10091              controller.reset();
10092          }
10093      }
10094  });
10095  
10096  module.exports = Select;
10097  
10098  
10099  /***/ },
10100  
10101  /***/ 3674
10102  (module) {
10103  
10104  var View = wp.media.View,
10105      l10n = wp.media.view.l10n,
10106      $ = jQuery,
10107      EditorUploader;
10108  
10109  /**
10110   * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
10111   * and relays drag'n'dropped files to a media workflow.
10112   *
10113   * wp.media.view.EditorUploader
10114   *
10115   * @memberOf wp.media.view
10116   *
10117   * @class
10118   * @augments wp.media.View
10119   * @augments wp.Backbone.View
10120   * @augments Backbone.View
10121   */
10122  EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{
10123      tagName:   'div',
10124      className: 'uploader-editor',
10125      template:  wp.template( 'uploader-editor' ),
10126  
10127      localDrag: false,
10128      overContainer: false,
10129      overDropzone: false,
10130      draggingFile: null,
10131  
10132      /**
10133       * Bind drag'n'drop events to callbacks.
10134       *
10135       * @return {wp.media.view.EditorUploader} Chainable.
10136       */
10137      initialize: function() {
10138          this.initialized = false;
10139  
10140          // Bail if not enabled or UA does not support drag'n'drop or File API.
10141          if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
10142              return this;
10143          }
10144  
10145          this.$document = $(document);
10146          this.dropzones = [];
10147          this.files = [];
10148  
10149          this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
10150          this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
10151          this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
10152          this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
10153  
10154          this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
10155          this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
10156  
10157          this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
10158              this.localDrag = event.type === 'dragstart';
10159  
10160              if ( event.type === 'drop' ) {
10161                  this.containerDragleave();
10162              }
10163          }, this ) );
10164  
10165          this.initialized = true;
10166          return this;
10167      },
10168  
10169      /**
10170       * Check browser support for drag'n'drop.
10171       *
10172       * @return {boolean} True if the browser supports drag'n'drop, false otherwise.
10173       */
10174      browserSupport: function() {
10175          var supports = false, div = document.createElement('div');
10176  
10177          supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
10178          supports = supports && !! ( window.File && window.FileList && window.FileReader );
10179          return supports;
10180      },
10181  
10182      isDraggingFile: function( event ) {
10183          if ( this.draggingFile !== null ) {
10184              return this.draggingFile;
10185          }
10186  
10187          if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
10188              return false;
10189          }
10190  
10191          this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
10192              _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
10193  
10194          return this.draggingFile;
10195      },
10196  
10197      refresh: function( e ) {
10198          var dropzone_id;
10199          for ( dropzone_id in this.dropzones ) {
10200              // Hide the dropzones only if dragging has left the screen.
10201              this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
10202          }
10203  
10204          if ( ! _.isUndefined( e ) ) {
10205              $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
10206          }
10207  
10208          if ( ! this.overContainer && ! this.overDropzone ) {
10209              this.draggingFile = null;
10210          }
10211  
10212          return this;
10213      },
10214  
10215      render: function() {
10216          if ( ! this.initialized ) {
10217              return this;
10218          }
10219  
10220          View.prototype.render.apply( this, arguments );
10221          $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
10222          return this;
10223      },
10224  
10225      attach: function( index, editor ) {
10226          // Attach a dropzone to an editor.
10227          var dropzone = this.$el.clone();
10228          this.dropzones.push( dropzone );
10229          $( editor ).append( dropzone );
10230          return this;
10231      },
10232  
10233      /**
10234       * When a file is dropped on the editor uploader, open up an editor media workflow
10235       * and upload the file immediately.
10236       *
10237       * @param {jQuery.Event} event The 'drop' event.
10238       * @return {void|boolean} False to prevent default behavior.
10239       */
10240      drop: function( event ) {
10241          var $wrap, uploadView;
10242  
10243          this.containerDragleave( event );
10244          this.dropzoneDragleave( event );
10245  
10246          this.files = event.originalEvent.dataTransfer.files;
10247          if ( this.files.length < 1 ) {
10248              return;
10249          }
10250  
10251          // Set the active editor to the drop target.
10252          $wrap = $( event.target ).parents( '.wp-editor-wrap' );
10253          if ( $wrap.length > 0 && $wrap[0].id ) {
10254              window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
10255          }
10256  
10257          if ( ! this.workflow ) {
10258              this.workflow = wp.media.editor.open( window.wpActiveEditor, {
10259                  frame:    'post',
10260                  state:    'insert',
10261                  title:    l10n.addMedia,
10262                  multiple: true
10263              });
10264  
10265              uploadView = this.workflow.uploader;
10266  
10267              if ( uploadView.uploader && uploadView.uploader.ready ) {
10268                  this.addFiles.apply( this );
10269              } else {
10270                  this.workflow.on( 'uploader:ready', this.addFiles, this );
10271              }
10272          } else {
10273              this.workflow.state().reset();
10274              this.addFiles.apply( this );
10275              this.workflow.open();
10276          }
10277  
10278          return false;
10279      },
10280  
10281      /**
10282       * Add the files to the uploader.
10283       *
10284       * @return {wp.media.view.EditorUploader} Chainable.
10285       */
10286      addFiles: function() {
10287          if ( this.files.length ) {
10288              this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
10289              this.files = [];
10290          }
10291          return this;
10292      },
10293  
10294      containerDragover: function( event ) {
10295          if ( this.localDrag || ! this.isDraggingFile( event ) ) {
10296              return;
10297          }
10298  
10299          this.overContainer = true;
10300          this.refresh();
10301      },
10302  
10303      containerDragleave: function() {
10304          this.overContainer = false;
10305  
10306          // Throttle dragleave because it's called when bouncing from some elements to others.
10307          _.delay( _.bind( this.refresh, this ), 50 );
10308      },
10309  
10310      dropzoneDragover: function( event ) {
10311          if ( this.localDrag || ! this.isDraggingFile( event ) ) {
10312              return;
10313          }
10314  
10315          this.overDropzone = true;
10316          this.refresh( event );
10317          return false;
10318      },
10319  
10320      dropzoneDragleave: function( e ) {
10321          this.overDropzone = false;
10322          _.delay( _.bind( this.refresh, this, e ), 50 );
10323      },
10324  
10325      click: function( e ) {
10326          // In the rare case where the dropzone gets stuck, hide it on click.
10327          this.containerDragleave( e );
10328          this.dropzoneDragleave( e );
10329          this.localDrag = false;
10330      }
10331  });
10332  
10333  module.exports = EditorUploader;
10334  
10335  
10336  /***/ },
10337  
10338  /***/ 1753
10339  (module) {
10340  
10341  var View = wp.media.View,
10342      UploaderInline;
10343  
10344  /**
10345   * wp.media.view.UploaderInline
10346   *
10347   * The inline uploader that shows up in the 'Upload Files' tab.
10348   *
10349   * @memberOf wp.media.view
10350   *
10351   * @class
10352   * @augments wp.media.View
10353   * @augments wp.Backbone.View
10354   * @augments Backbone.View
10355   */
10356  UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{
10357      tagName:   'div',
10358      className: 'uploader-inline',
10359      template:  wp.template('uploader-inline'),
10360  
10361      events: {
10362          'click .close': 'hide'
10363      },
10364  
10365      initialize: function() {
10366          _.defaults( this.options, {
10367              message: '',
10368              status:  true,
10369              canClose: false
10370          });
10371  
10372          if ( ! this.options.$browser && this.controller.uploader ) {
10373              this.options.$browser = this.controller.uploader.$browser;
10374          }
10375  
10376          if ( _.isUndefined( this.options.postId ) ) {
10377              this.options.postId = wp.media.view.settings.post.id;
10378          }
10379  
10380          if ( this.options.status ) {
10381              this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
10382                  controller: this.controller
10383              }) );
10384          }
10385      },
10386  
10387      prepare: function() {
10388          var suggestedWidth = this.controller.state().get('suggestedWidth'),
10389              suggestedHeight = this.controller.state().get('suggestedHeight'),
10390              data = {};
10391  
10392          data.message = this.options.message;
10393          data.canClose = this.options.canClose;
10394  
10395          if ( suggestedWidth && suggestedHeight ) {
10396              data.suggestedWidth = suggestedWidth;
10397              data.suggestedHeight = suggestedHeight;
10398          }
10399  
10400          return data;
10401      },
10402      /**
10403       * Disposes of the inline uploader and its associated views.
10404       *
10405       * @return {wp.media.view.UploaderInline} Returns itself to allow chaining.
10406       */
10407      dispose: function() {
10408          if ( this.disposing ) {
10409              /*
10410               * call 'dispose' directly on the parent class
10411               */
10412              return View.prototype.dispose.apply( this, arguments );
10413          }
10414  
10415          /*
10416           * Run remove on `dispose`, so we can be sure to refresh the
10417           * uploader with a view-less DOM. Track whether we're disposing
10418           * so we don't trigger an infinite loop.
10419           */
10420          this.disposing = true;
10421          return this.remove();
10422      },
10423      /**
10424       * Disposes of the inline uploader and its associated views.
10425       *
10426       * @return {wp.media.view.UploaderInline} Returns itself to allow chaining.
10427       */
10428      remove: function() {
10429          /*
10430           * call 'remove' directly on the parent class
10431           */
10432          var result = View.prototype.remove.apply( this, arguments );
10433  
10434          _.defer( _.bind( this.refresh, this ) );
10435          return result;
10436      },
10437  
10438      refresh: function() {
10439          var uploader = this.controller.uploader;
10440  
10441          if ( uploader ) {
10442              uploader.refresh();
10443          }
10444      },
10445      /**
10446       * Replaces the placeholder with the uploader browser and refreshes the uploader.
10447       *
10448       * @return {void|wp.media.view.UploaderInline} Returns itself to allow chaining.
10449       */
10450      ready: function() {
10451          var $browser = this.options.$browser,
10452              $placeholder;
10453  
10454          if ( this.controller.uploader ) {
10455              $placeholder = this.$('.browser');
10456  
10457              // Check if we've already replaced the placeholder.
10458              if ( $placeholder[0] === $browser[0] ) {
10459                  return;
10460              }
10461  
10462              $browser.detach().text( $placeholder.text() );
10463              $browser[0].className = $placeholder[0].className;
10464              $browser[0].setAttribute( 'aria-describedby', $placeholder[0].getAttribute('aria-describedby') );
10465              $placeholder.replaceWith( $browser.show() );
10466          }
10467  
10468          this.refresh();
10469          return this;
10470      },
10471      show: function() {
10472          this.$el.removeClass( 'hidden' );
10473          if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
10474              this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' );
10475          }
10476      },
10477      hide: function() {
10478          this.$el.addClass( 'hidden' );
10479          if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
10480              this.controller.$uploaderToggler
10481                  .attr( 'aria-expanded', 'false' )
10482                  // Move focus back to the toggle button when closing the uploader.
10483                  .trigger( 'focus' );
10484          }
10485      }
10486  
10487  });
10488  
10489  module.exports = UploaderInline;
10490  
10491  
10492  /***/ },
10493  
10494  /***/ 6442
10495  (module) {
10496  
10497  /**
10498   * wp.media.view.UploaderStatusError
10499   *
10500   * @memberOf wp.media.view
10501   *
10502   * @class
10503   * @augments wp.media.View
10504   * @augments wp.Backbone.View
10505   * @augments Backbone.View
10506   */
10507  var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{
10508      className: 'upload-error',
10509      template:  wp.template('uploader-status-error')
10510  });
10511  
10512  module.exports = UploaderStatusError;
10513  
10514  
10515  /***/ },
10516  
10517  /***/ 8197
10518  (module) {
10519  
10520  var View = wp.media.View,
10521      UploaderStatus;
10522  
10523  /**
10524   * wp.media.view.UploaderStatus
10525   *
10526   * An uploader status for on-going uploads.
10527   *
10528   * @memberOf wp.media.view
10529   *
10530   * @class
10531   * @augments wp.media.View
10532   * @augments wp.Backbone.View
10533   * @augments Backbone.View
10534   */
10535  UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{
10536      className: 'media-uploader-status',
10537      template:  wp.template('uploader-status'),
10538  
10539      events: {
10540          'click .upload-dismiss-errors': 'dismiss'
10541      },
10542  
10543      initialize: function() {
10544          this.queue = wp.Uploader.queue;
10545          this.queue.on( 'add remove reset', this.visibility, this );
10546          this.queue.on( 'add remove reset change:percent', this.progress, this );
10547          this.queue.on( 'add remove reset change:uploading', this.info, this );
10548  
10549          this.errors = wp.Uploader.errors;
10550          this.errors.reset();
10551          this.errors.on( 'add remove reset', this.visibility, this );
10552          this.errors.on( 'add', this.error, this );
10553      },
10554      /**
10555       * Disposes of the uploader status and its associated views.
10556       *
10557       * @return {wp.media.view.UploaderStatus} Returns the instance of the UploaderStatus view.
10558       */
10559      dispose: function() {
10560          wp.Uploader.queue.off( null, null, this );
10561          /*
10562           * call 'dispose' directly on the parent class
10563           */
10564          View.prototype.dispose.apply( this, arguments );
10565          return this;
10566      },
10567  
10568      visibility: function() {
10569          this.$el.toggleClass( 'uploading', !! this.queue.length );
10570          this.$el.toggleClass( 'errors', !! this.errors.length );
10571          this.$el.toggle( !! this.queue.length || !! this.errors.length );
10572      },
10573  
10574      ready: function() {
10575          _.each({
10576              '$bar':      '.media-progress-bar div',
10577              '$index':    '.upload-index',
10578              '$total':    '.upload-total',
10579              '$filename': '.upload-filename'
10580          }, function( selector, key ) {
10581              this[ key ] = this.$( selector );
10582          }, this );
10583  
10584          this.visibility();
10585          this.progress();
10586          this.info();
10587      },
10588  
10589      progress: function() {
10590          var queue = this.queue,
10591              $bar = this.$bar;
10592  
10593          if ( ! $bar || ! queue.length ) {
10594              return;
10595          }
10596  
10597          $bar.width( ( queue.reduce( function( memo, attachment ) {
10598              if ( ! attachment.get('uploading') ) {
10599                  return memo + 100;
10600              }
10601  
10602              var percent = attachment.get('percent');
10603              return memo + ( _.isNumber( percent ) ? percent : 100 );
10604          }, 0 ) / queue.length ) + '%' );
10605      },
10606  
10607      info: function() {
10608          var queue = this.queue,
10609              index = 0, active;
10610  
10611          if ( ! queue.length ) {
10612              return;
10613          }
10614  
10615          active = this.queue.find( function( attachment, i ) {
10616              index = i;
10617              return attachment.get('uploading');
10618          });
10619  
10620          if ( this.$index && this.$total && this.$filename ) {
10621              this.$index.text( index + 1 );
10622              this.$total.text( queue.length );
10623              this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
10624          }
10625      },
10626      /**
10627       * Escapes the filename to prevent XSS attacks.
10628       *
10629       * @param {string} filename The name of the file to be escaped.
10630       * @return {string} Escaped filename.
10631       */
10632      filename: function( filename ) {
10633          return _.escape( filename );
10634      },
10635      /**
10636       * Handles an error event from the uploader queue.
10637       *
10638       * @param {Backbone.Model} error The error model from the uploader queue.
10639       * @return {void}
10640       */
10641      error: function( error ) {
10642          var statusError = new wp.media.view.UploaderStatusError( {
10643              filename: this.filename( error.get( 'file' ).name ),
10644              message:  error.get( 'message' )
10645          } );
10646  
10647          var buttonClose = this.$el.find( 'button' );
10648  
10649          // Can show additional info here while retrying to create image sub-sizes.
10650          this.views.add( '.upload-errors', statusError, { at: 0 } );
10651          _.delay( function() {
10652              buttonClose.trigger( 'focus' );
10653          }, 1000 );
10654  
10655          _.delay( function() {
10656              wp.a11y.speak( error.get( 'message' ) );
10657          }, 1500 );
10658      },
10659  
10660      /**
10661       * Dismisses the error messages and resets the uploader errors.
10662       */
10663      dismiss: function() {
10664          var errors = this.views.get('.upload-errors');
10665  
10666          if ( errors ) {
10667              _.invoke( errors, 'remove' );
10668          }
10669          wp.Uploader.errors.reset();
10670          wp.a11y.speak( wp.i18n.__( 'Error dismissed.' ) );
10671          // Move focus to the modal after the dismiss button gets removed from the DOM.
10672          if ( this.controller.modal ) {
10673              this.controller.modal.focusManager.focus();
10674          }
10675      }
10676  });
10677  
10678  module.exports = UploaderStatus;
10679  
10680  
10681  /***/ },
10682  
10683  /***/ 8291
10684  (module) {
10685  
10686  var $ = jQuery,
10687      UploaderWindow;
10688  
10689  /**
10690   * wp.media.view.UploaderWindow
10691   *
10692   * An uploader window that allows for dragging and dropping media.
10693   *
10694   * @memberOf wp.media.view
10695   *
10696   * @class
10697   * @augments wp.media.View
10698   * @augments wp.Backbone.View
10699   * @augments Backbone.View
10700   *
10701   * @param {Object} [options]                   Options hash passed to the view.
10702   * @param {Object} [options.uploader]          Uploader properties.
10703   * @param {jQuery} [options.uploader.browser]
10704   * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
10705   * @param {Object} [options.uploader.params]
10706   */
10707  UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{
10708      tagName:   'div',
10709      className: 'uploader-window',
10710      template:  wp.template('uploader-window'),
10711  
10712      initialize: function() {
10713          var uploader;
10714  
10715          this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' );
10716  
10717          uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
10718              dropzone:  this.$el,
10719              browser:   this.$browser,
10720              params:    {}
10721          });
10722  
10723          // Ensure the dropzone is a jQuery collection.
10724          if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
10725              uploader.dropzone = $( uploader.dropzone );
10726          }
10727  
10728          this.controller.on( 'activate', this.refresh, this );
10729  
10730          this.controller.on( 'detach', function() {
10731              this.$browser.remove();
10732          }, this );
10733      },
10734  
10735      refresh: function() {
10736          if ( this.uploader ) {
10737              this.uploader.refresh();
10738          }
10739      },
10740  
10741      ready: function() {
10742          var postId = wp.media.view.settings.post.id,
10743              dropzone;
10744  
10745          // If the uploader already exists, bail.
10746          if ( this.uploader ) {
10747              return;
10748          }
10749  
10750          if ( postId ) {
10751              this.options.uploader.params.post_id = postId;
10752          }
10753          this.uploader = new wp.Uploader( this.options.uploader );
10754  
10755          dropzone = this.uploader.dropzone;
10756          dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
10757          dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
10758  
10759          $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
10760      },
10761  
10762      _ready: function() {
10763          this.controller.trigger( 'uploader:ready' );
10764      },
10765  
10766      show: function() {
10767          var $el = this.$el.show();
10768  
10769          // Ensure that the animation is triggered by waiting until
10770          // the transparent element is painted into the DOM.
10771          _.defer( function() {
10772              $el.css({ opacity: 1 });
10773          });
10774      },
10775  
10776      hide: function() {
10777          var $el = this.$el.css({ opacity: 0 });
10778  
10779          wp.media.transition( $el ).done( function() {
10780              // Transition end events are subject to race conditions.
10781              // Make sure that the value is set as intended.
10782              if ( '0' === $el.css('opacity') ) {
10783                  $el.hide();
10784              }
10785          });
10786  
10787          // https://core.trac.wordpress.org/ticket/27341
10788          _.delay( function() {
10789              if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
10790                  $el.hide();
10791              }
10792          }, 500 );
10793      }
10794  });
10795  
10796  module.exports = UploaderWindow;
10797  
10798  
10799  /***/ },
10800  
10801  /***/ 4747
10802  (module) {
10803  
10804  /**
10805   * wp.media.View
10806   *
10807   * The base view class for media.
10808   *
10809   * Undelegating events, removing events from the model, and
10810   * removing events from the controller mirror the code for
10811   * `Backbone.View.dispose` in Backbone 0.9.8 development.
10812   *
10813   * This behavior has since been removed, and should not be used
10814   * outside of the media manager.
10815   *
10816   * @memberOf wp.media
10817   *
10818   * @class
10819   * @augments wp.Backbone.View
10820   * @augments Backbone.View
10821   */
10822  var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{
10823      /**
10824       * Constructs the media view.
10825       *
10826       * @param {Object} [options] Options for the view.
10827       */
10828      constructor: function( options ) {
10829          if ( options && options.controller ) {
10830              this.controller = options.controller;
10831          }
10832          wp.Backbone.View.apply( this, arguments );
10833      },
10834      /**
10835       * Disposes of the media view.
10836       *
10837       * @todo The internal comment mentions this might have been a stop-gap
10838       *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
10839       *       care of this in Backbone.View now.
10840       *
10841       * @return {wp.media.View} Returns itself to allow chaining.
10842       */
10843      dispose: function() {
10844          /*
10845           * Undelegating events, removing events from the model, and
10846           * removing events from the controller mirror the code for
10847           * `Backbone.View.dispose` in Backbone 0.9.8 development.
10848           */
10849          this.undelegateEvents();
10850  
10851          if ( this.model && this.model.off ) {
10852              this.model.off( null, null, this );
10853          }
10854  
10855          if ( this.collection && this.collection.off ) {
10856              this.collection.off( null, null, this );
10857          }
10858  
10859          // Unbind controller events.
10860          if ( this.controller && this.controller.off ) {
10861              this.controller.off( null, null, this );
10862          }
10863  
10864          return this;
10865      },
10866      /**
10867       * Removes the media view.
10868       *
10869       * @return {wp.media.View} Returns itself to allow chaining.
10870       */
10871      remove: function() {
10872          this.dispose();
10873          /**
10874           * call 'remove' directly on the parent class
10875           */
10876          return wp.Backbone.View.prototype.remove.apply( this, arguments );
10877      }
10878  });
10879  
10880  module.exports = View;
10881  
10882  
10883  /***/ }
10884  
10885  /******/     });
10886  /************************************************************************/
10887  /******/     // The module cache
10888  /******/     const __webpack_module_cache__ = {};
10889  /******/     
10890  /******/     // The require function
10891  /******/ 	function __webpack_require__(moduleId) {
10892  /******/         // Check if module is in cache
10893  /******/         const cachedModule = __webpack_module_cache__[moduleId];
10894  /******/         if (cachedModule !== undefined) {
10895  /******/             return cachedModule.exports;
10896  /******/         }
10897  /******/         // Create a new module (and put it into the cache)
10898  /******/         const module = __webpack_module_cache__[moduleId] = {
10899  /******/             // no module.id needed
10900  /******/             // no module.loaded needed
10901  /******/             exports: {}
10902  /******/         };
10903  /******/     
10904  /******/         // Execute the module function
10905  /******/         __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
10906  /******/     
10907  /******/         // Return the exports of the module
10908  /******/         return module.exports;
10909  /******/     }
10910  /******/     
10911  /************************************************************************/
10912  /**
10913   * @output wp-includes/js/media-views.js
10914   */
10915  
10916  var media = wp.media,
10917      $ = jQuery,
10918      l10n;
10919  
10920  media.isTouchDevice = ( 'ontouchend' in document );
10921  
10922  // Link any localized strings.
10923  l10n = media.view.l10n = window._wpMediaViewsL10n || {};
10924  
10925  // Link any settings.
10926  media.view.settings = l10n.settings || {};
10927  delete l10n.settings;
10928  
10929  // Copy the `post` setting over to the model settings.
10930  media.model.settings.post = media.view.settings.post;
10931  
10932  // Check if the browser supports CSS 3.0 transitions.
10933  $.support.transition = (function(){
10934      var style = document.documentElement.style,
10935          transitions = {
10936              WebkitTransition: 'webkitTransitionEnd',
10937              MozTransition:    'transitionend',
10938              OTransition:      'oTransitionEnd otransitionend',
10939              transition:       'transitionend'
10940          }, transition;
10941  
10942      transition = _.find( _.keys( transitions ), function( transition ) {
10943          return ! _.isUndefined( style[ transition ] );
10944      });
10945  
10946      return transition && {
10947          end: transitions[ transition ]
10948      };
10949  }());
10950  
10951  /**
10952   * A shared event bus used to provide events into
10953   * the media workflows that 3rd-party devs can use to hook
10954   * in.
10955   */
10956  media.events = _.extend( {}, Backbone.Events );
10957  
10958  /**
10959   * Makes it easier to bind events using transitions.
10960   *
10961   * @param {string} selector    The CSS selector for the element to bind the transition event to.
10962   * @param {number} sensitivity The maximum time to wait for the transition event before resolving the promise.
10963   * @return {Promise} A promise that resolves when the transition has completed.
10964   */
10965  media.transition = function( selector, sensitivity ) {
10966      var deferred = $.Deferred();
10967  
10968      sensitivity = sensitivity || 2000;
10969  
10970      if ( $.support.transition ) {
10971          if ( ! (selector instanceof $) ) {
10972              selector = $( selector );
10973          }
10974  
10975          // Resolve the deferred when the first element finishes animating.
10976          selector.first().one( $.support.transition.end, deferred.resolve );
10977  
10978          // Just in case the event doesn't trigger, fire a callback.
10979          _.delay( deferred.resolve, sensitivity );
10980  
10981      // Otherwise, execute on the spot.
10982      } else {
10983          deferred.resolve();
10984      }
10985  
10986      return deferred.promise();
10987  };
10988  
10989  media.controller.Region = __webpack_require__( 9875 );
10990  media.controller.StateMachine = __webpack_require__( 6150 );
10991  media.controller.State = __webpack_require__( 5694 );
10992  
10993  media.selectionSync = __webpack_require__( 4181 );
10994  media.controller.Library = __webpack_require__( 472 );
10995  media.controller.ImageDetails = __webpack_require__( 705 );
10996  media.controller.GalleryEdit = __webpack_require__( 2038 );
10997  media.controller.GalleryAdd = __webpack_require__( 7127 );
10998  media.controller.CollectionEdit = __webpack_require__( 8612 );
10999  media.controller.CollectionAdd = __webpack_require__( 7145 );
11000  media.controller.FeaturedImage = __webpack_require__( 1169 );
11001  media.controller.ReplaceImage = __webpack_require__( 2275 );
11002  media.controller.EditImage = __webpack_require__( 5663 );
11003  media.controller.MediaLibrary = __webpack_require__( 8065 );
11004  media.controller.Embed = __webpack_require__( 4910 );
11005  media.controller.Cropper = __webpack_require__( 5422 );
11006  media.controller.CustomizeImageCropper = __webpack_require__( 9660 );
11007  media.controller.SiteIconCropper = __webpack_require__( 6172 );
11008  
11009  media.View = __webpack_require__( 4747 );
11010  media.view.Frame = __webpack_require__( 1061 );
11011  media.view.MediaFrame = __webpack_require__( 2836 );
11012  media.view.MediaFrame.Select = __webpack_require__( 455 );
11013  media.view.MediaFrame.Post = __webpack_require__( 4274 );
11014  media.view.MediaFrame.ImageDetails = __webpack_require__( 5424 );
11015  media.view.Modal = __webpack_require__( 2621 );
11016  media.view.FocusManager = __webpack_require__( 718 );
11017  media.view.UploaderWindow = __webpack_require__( 8291 );
11018  media.view.EditorUploader = __webpack_require__( 3674 );
11019  media.view.UploaderInline = __webpack_require__( 1753 );
11020  media.view.UploaderStatus = __webpack_require__( 8197 );
11021  media.view.UploaderStatusError = __webpack_require__( 6442 );
11022  media.view.Toolbar = __webpack_require__( 5275 );
11023  media.view.Toolbar.Select = __webpack_require__( 9458 );
11024  media.view.Toolbar.Embed = __webpack_require__( 397 );
11025  media.view.Button = __webpack_require__( 846 );
11026  media.view.ButtonGroup = __webpack_require__( 168 );
11027  media.view.PriorityList = __webpack_require__( 8815 );
11028  media.view.MenuItem = __webpack_require__( 9013 );
11029  media.view.Menu = __webpack_require__( 1 );
11030  media.view.RouterItem = __webpack_require__( 6327 );
11031  media.view.Router = __webpack_require__( 4783 );
11032  media.view.Sidebar = __webpack_require__( 1992 );
11033  media.view.Attachment = __webpack_require__( 4075 );
11034  media.view.Attachment.Library = __webpack_require__( 3443 );
11035  media.view.Attachment.EditLibrary = __webpack_require__( 5232 );
11036  media.view.Attachments = __webpack_require__( 8142 );
11037  media.view.Search = __webpack_require__( 2102 );
11038  media.view.AttachmentFilters = __webpack_require__( 7709 );
11039  media.view.DateFilter = __webpack_require__( 6472 );
11040  media.view.AttachmentFilters.Uploaded = __webpack_require__( 1368 );
11041  media.view.AttachmentFilters.All = __webpack_require__( 7349 );
11042  media.view.AttachmentsBrowser = __webpack_require__( 6829 );
11043  media.view.Selection = __webpack_require__( 8282 );
11044  media.view.Attachment.Selection = __webpack_require__( 3962 );
11045  media.view.Attachments.Selection = __webpack_require__( 3479 );
11046  media.view.Attachment.EditSelection = __webpack_require__( 4593 );
11047  media.view.Settings = __webpack_require__( 1915 );
11048  media.view.Settings.AttachmentDisplay = __webpack_require__( 7656 );
11049  media.view.Settings.Gallery = __webpack_require__( 7266 );
11050  media.view.Settings.Playlist = __webpack_require__( 2356 );
11051  media.view.Attachment.Details = __webpack_require__( 6090 );
11052  media.view.AttachmentCompat = __webpack_require__( 2982 );
11053  media.view.Iframe = __webpack_require__( 1982 );
11054  media.view.Embed = __webpack_require__( 5741 );
11055  media.view.Label = __webpack_require__( 4338 );
11056  media.view.EmbedUrl = __webpack_require__( 7327 );
11057  media.view.EmbedLink = __webpack_require__( 8232 );
11058  media.view.EmbedImage = __webpack_require__( 2395 );
11059  media.view.ImageDetails = __webpack_require__( 2650 );
11060  media.view.Cropper = __webpack_require__( 7637 );
11061  media.view.SiteIconCropper = __webpack_require__( 443 );
11062  media.view.SiteIconPreview = __webpack_require__( 7810 );
11063  media.view.EditImage = __webpack_require__( 6126 );
11064  media.view.Spinner = __webpack_require__( 9141 );
11065  media.view.Heading = __webpack_require__( 170 );
11066  
11067  /******/ })()
11068  ;


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