[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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


Generated : Fri Jun 26 08:20:11 2026 Cross-referenced by PHPXref