[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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