[ 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.prop( '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          var ariaLabel = this.model.get( 'title' );
2917  
2918          if ( ! ariaLabel ) {
2919              if ( this.model.get( 'uploading' ) ) {
2920                  ariaLabel = wp.i18n.__( 'uploading…' );
2921              } else {
2922                  ariaLabel = wp.i18n.__( '(no title)' );
2923              }
2924          }
2925  
2926          return {
2927              'tabIndex':     0,
2928              'role':         'checkbox',
2929              'aria-label':   ariaLabel,
2930              'aria-checked': false,
2931              'data-id':      this.model.get( 'id' )
2932          };
2933      },
2934  
2935      events: {
2936          'click':                          'toggleSelectionHandler',
2937          'change [data-setting]':          'updateSetting',
2938          'change [data-setting] input':    'updateSetting',
2939          'change [data-setting] select':   'updateSetting',
2940          'change [data-setting] textarea': 'updateSetting',
2941          'click .attachment-close':        'removeFromLibrary',
2942          'click .check':                   'checkClickHandler',
2943          'keydown':                        'toggleSelectionHandler'
2944      },
2945  
2946      buttons: {},
2947  
2948      initialize: function() {
2949          var selection = this.options.selection,
2950              options = _.defaults( this.options, {
2951                  rerenderOnModelChange: true
2952              } );
2953  
2954          if ( options.rerenderOnModelChange ) {
2955              this.listenTo( this.model, 'change', this.render );
2956          } else {
2957              this.listenTo( this.model, 'change:percent', this.progress );
2958          }
2959          this.listenTo( this.model, 'change:title', this._syncTitle );
2960          this.listenTo( this.model, 'change:caption', this._syncCaption );
2961          this.listenTo( this.model, 'change:artist', this._syncArtist );
2962          this.listenTo( this.model, 'change:album', this._syncAlbum );
2963  
2964          // Update the selection.
2965          this.listenTo( this.model, 'add', this.select );
2966          this.listenTo( this.model, 'remove', this.deselect );
2967          if ( selection ) {
2968              selection.on( 'reset', this.updateSelect, this );
2969              // Update the model's details view.
2970              this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
2971              this.details( this.model, this.controller.state().get('selection') );
2972          }
2973  
2974          this.listenTo( this.controller.states, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
2975      },
2976      /**
2977       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
2978       */
2979      dispose: function() {
2980          var selection = this.options.selection;
2981  
2982          // Make sure all settings are saved before removing the view.
2983          this.updateAll();
2984  
2985          if ( selection ) {
2986              selection.off( null, null, this );
2987          }
2988          /**
2989           * call 'dispose' directly on the parent class
2990           */
2991          View.prototype.dispose.apply( this, arguments );
2992          return this;
2993      },
2994      /**
2995       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
2996       */
2997      render: function() {
2998          var options = _.defaults( this.model.toJSON(), {
2999                  orientation:   'landscape',
3000                  uploading:     false,
3001                  type:          '',
3002                  subtype:       '',
3003                  icon:          '',
3004                  filename:      '',
3005                  caption:       '',
3006                  title:         '',
3007                  dateFormatted: '',
3008                  width:         '',
3009                  height:        '',
3010                  compat:        false,
3011                  alt:           '',
3012                  description:   ''
3013              }, this.options );
3014  
3015          options.buttons  = this.buttons;
3016          options.describe = this.controller.state().get('describe');
3017  
3018          if ( 'image' === options.type ) {
3019              options.size = this.imageSize();
3020          }
3021  
3022          options.can = {};
3023          if ( options.nonces ) {
3024              options.can.remove = !! options.nonces['delete'];
3025              options.can.save = !! options.nonces.update;
3026          }
3027  
3028          if ( this.controller.state().get('allowLocalEdits') && ! options.uploading ) {
3029              options.allowLocalEdits = true;
3030          }
3031  
3032          if ( options.uploading && ! options.percent ) {
3033              options.percent = 0;
3034          }
3035  
3036          this.views.detach();
3037          this.$el.html( this.template( options ) );
3038  
3039          this.$el.toggleClass( 'uploading', options.uploading );
3040  
3041          if ( options.uploading ) {
3042              this.$bar = this.$('.media-progress-bar div');
3043          } else {
3044              delete this.$bar;
3045          }
3046  
3047          // Check if the model is selected.
3048          this.updateSelect();
3049  
3050          // Update the save status.
3051          this.updateSave();
3052  
3053          this.views.render();
3054  
3055          return this;
3056      },
3057  
3058      progress: function() {
3059          if ( this.$bar && this.$bar.length ) {
3060              this.$bar.width( this.model.get('percent') + '%' );
3061          }
3062      },
3063  
3064      /**
3065       * @param {Object} event
3066       */
3067      toggleSelectionHandler: function( event ) {
3068          var method;
3069  
3070          // Don't do anything inside inputs and on the attachment check and remove buttons.
3071          if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
3072              return;
3073          }
3074  
3075          // Catch arrow events.
3076          if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
3077              this.controller.trigger( 'attachment:keydown:arrow', event );
3078              return;
3079          }
3080  
3081          // Catch enter and space events.
3082          if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
3083              return;
3084          }
3085  
3086          event.preventDefault();
3087  
3088          // In the grid view, bubble up an edit:attachment event to the controller.
3089          if ( this.controller.isModeActive( 'grid' ) ) {
3090              if ( this.controller.isModeActive( 'edit' ) ) {
3091                  // Pass the current target to restore focus when closing.
3092                  this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
3093                  return;
3094              }
3095  
3096              if ( this.controller.isModeActive( 'select' ) ) {
3097                  method = 'toggle';
3098              }
3099          }
3100  
3101          if ( event.shiftKey ) {
3102              method = 'between';
3103          } else if ( event.ctrlKey || event.metaKey ) {
3104              method = 'toggle';
3105          }
3106  
3107          // Avoid toggles when the command or control key is pressed with the enter key to prevent deselecting the last selected attachment.
3108          if ( ( event.metaKey || event.ctrlKey ) && ( 13 === event.keyCode || 10 === event.keyCode ) ) {
3109              return;
3110          }
3111  
3112          this.toggleSelection({
3113              method: method
3114          });
3115  
3116          this.controller.trigger( 'selection:toggle' );
3117      },
3118      /**
3119       * @param {Object} options
3120       */
3121      toggleSelection: function( options ) {
3122          var collection = this.collection,
3123              selection = this.options.selection,
3124              model = this.model,
3125              method = options && options.method,
3126              single, models, singleIndex, modelIndex;
3127  
3128          if ( ! selection ) {
3129              return;
3130          }
3131  
3132          single = selection.single();
3133          method = _.isUndefined( method ) ? selection.multiple : method;
3134  
3135          // If the `method` is set to `between`, select all models that
3136          // exist between the current and the selected model.
3137          if ( 'between' === method && single && selection.multiple ) {
3138              // If the models are the same, short-circuit.
3139              if ( single === model ) {
3140                  return;
3141              }
3142  
3143              singleIndex = collection.indexOf( single );
3144              modelIndex  = collection.indexOf( this.model );
3145  
3146              if ( singleIndex < modelIndex ) {
3147                  models = collection.models.slice( singleIndex, modelIndex + 1 );
3148              } else {
3149                  models = collection.models.slice( modelIndex, singleIndex + 1 );
3150              }
3151  
3152              selection.add( models );
3153              selection.single( model );
3154              return;
3155  
3156          // If the `method` is set to `toggle`, just flip the selection
3157          // status, regardless of whether the model is the single model.
3158          } else if ( 'toggle' === method ) {
3159              selection[ this.selected() ? 'remove' : 'add' ]( model );
3160              selection.single( model );
3161              return;
3162          } else if ( 'add' === method ) {
3163              selection.add( model );
3164              selection.single( model );
3165              return;
3166          }
3167  
3168          // Fixes bug that loses focus when selecting a featured image.
3169          if ( ! method ) {
3170              method = 'add';
3171          }
3172  
3173          if ( method !== 'add' ) {
3174              method = 'reset';
3175          }
3176  
3177          if ( this.selected() ) {
3178              /*
3179               * If the model is the single model, remove it.
3180               * If it is not the same as the single model,
3181               * it now becomes the single model.
3182               */
3183              selection[ single === model ? 'remove' : 'single' ]( model );
3184          } else {
3185              /*
3186               * If the model is not selected, run the `method` on the
3187               * selection. By default, we `reset` the selection, but the
3188               * `method` can be set to `add` the model to the selection.
3189               */
3190              selection[ method ]( model );
3191              selection.single( model );
3192          }
3193      },
3194  
3195      updateSelect: function() {
3196          this[ this.selected() ? 'select' : 'deselect' ]();
3197      },
3198      /**
3199       * @return {unresolved|boolean}
3200       */
3201      selected: function() {
3202          var selection = this.options.selection;
3203          if ( selection ) {
3204              return !! selection.get( this.model.cid );
3205          }
3206      },
3207      /**
3208       * @param {Backbone.Model} model
3209       * @param {Backbone.Collection} collection
3210       */
3211      select: function( model, collection ) {
3212          var selection = this.options.selection,
3213              controller = this.controller;
3214  
3215          /*
3216           * Check if a selection exists and if it's the collection provided.
3217           * If they're not the same collection, bail; we're in another
3218           * selection's event loop.
3219           */
3220          if ( ! selection || ( collection && collection !== selection ) ) {
3221              return;
3222          }
3223  
3224          // Bail if the model is already selected.
3225          if ( this.$el.hasClass( 'selected' ) ) {
3226              return;
3227          }
3228  
3229          // Add 'selected' class to model, set aria-checked to true.
3230          this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
3231          //  Make the checkbox tabable, except in media grid (bulk select mode).
3232          if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
3233              this.$( '.check' ).attr( 'tabindex', '0' );
3234          }
3235      },
3236      /**
3237       * @param {Backbone.Model} model
3238       * @param {Backbone.Collection} collection
3239       */
3240      deselect: function( model, collection ) {
3241          var selection = this.options.selection;
3242  
3243          /*
3244           * Check if a selection exists and if it's the collection provided.
3245           * If they're not the same collection, bail; we're in another
3246           * selection's event loop.
3247           */
3248          if ( ! selection || ( collection && collection !== selection ) ) {
3249              return;
3250          }
3251          this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
3252              .find( '.check' ).attr( 'tabindex', '-1' );
3253      },
3254      /**
3255       * @param {Backbone.Model} model
3256       * @param {Backbone.Collection} collection
3257       */
3258      details: function( model, collection ) {
3259          var selection = this.options.selection,
3260              details;
3261  
3262          if ( selection !== collection ) {
3263              return;
3264          }
3265  
3266          details = selection.single();
3267          this.$el.toggleClass( 'details', details === this.model );
3268      },
3269      /**
3270       * @param {string} size
3271       * @return {Object}
3272       */
3273      imageSize: function( size ) {
3274          var sizes = this.model.get('sizes'), matched = false;
3275  
3276          size = size || 'medium';
3277  
3278          // Use the provided image size if possible.
3279          if ( sizes ) {
3280              if ( sizes[ size ] ) {
3281                  matched = sizes[ size ];
3282              } else if ( sizes.large ) {
3283                  matched = sizes.large;
3284              } else if ( sizes.thumbnail ) {
3285                  matched = sizes.thumbnail;
3286              } else if ( sizes.full ) {
3287                  matched = sizes.full;
3288              }
3289  
3290              if ( matched ) {
3291                  return _.clone( matched );
3292              }
3293          }
3294  
3295          return {
3296              url:         this.model.get('url'),
3297              width:       this.model.get('width'),
3298              height:      this.model.get('height'),
3299              orientation: this.model.get('orientation')
3300          };
3301      },
3302      /**
3303       * @param {Object} event
3304       */
3305      updateSetting: function( event ) {
3306          var $setting = $( event.target ).closest('[data-setting]'),
3307              setting, value;
3308  
3309          if ( ! $setting.length ) {
3310              return;
3311          }
3312  
3313          setting = $setting.data('setting');
3314          value   = event.target.value;
3315  
3316          if ( this.model.get( setting ) !== value ) {
3317              this.save( setting, value );
3318          }
3319      },
3320  
3321      /**
3322       * Pass all the arguments to the model's save method.
3323       *
3324       * Records the aggregate status of all save requests and updates the
3325       * view's classes accordingly.
3326       */
3327      save: function() {
3328          var view = this,
3329              save = this._save = this._save || { status: 'ready' },
3330              request = this.model.save.apply( this.model, arguments ),
3331              requests = save.requests ? $.when( request, save.requests ) : request;
3332  
3333          // If we're waiting to remove 'Saved.', stop.
3334          if ( save.savedTimer ) {
3335              clearTimeout( save.savedTimer );
3336          }
3337  
3338          this.updateSave('waiting');
3339          save.requests = requests;
3340          requests.always( function() {
3341              // If we've performed another request since this one, bail.
3342              if ( save.requests !== requests ) {
3343                  return;
3344              }
3345  
3346              view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
3347              save.savedTimer = setTimeout( function() {
3348                  view.updateSave('ready');
3349                  delete save.savedTimer;
3350              }, 2000 );
3351          });
3352      },
3353      /**
3354       * @param {string} status
3355       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3356       */
3357      updateSave: function( status ) {
3358          var save = this._save = this._save || { status: 'ready' };
3359  
3360          if ( status && status !== save.status ) {
3361              this.$el.removeClass( 'save-' + save.status );
3362              save.status = status;
3363          }
3364  
3365          this.$el.addClass( 'save-' + save.status );
3366          return this;
3367      },
3368  
3369      updateAll: function() {
3370          var $settings = this.$('[data-setting]'),
3371              model = this.model,
3372              changed;
3373  
3374          changed = _.chain( $settings ).map( function( el ) {
3375              var $input = $('input, textarea, select, [value]', el ),
3376                  setting, value;
3377  
3378              if ( ! $input.length ) {
3379                  return;
3380              }
3381  
3382              setting = $(el).data('setting');
3383              value = $input.val();
3384  
3385              // Record the value if it changed.
3386              if ( model.get( setting ) !== value ) {
3387                  return [ setting, value ];
3388              }
3389          }).compact().object().value();
3390  
3391          if ( ! _.isEmpty( changed ) ) {
3392              model.save( changed );
3393          }
3394      },
3395      /**
3396       * @param {Object} event
3397       */
3398      removeFromLibrary: function( event ) {
3399          // Catch enter and space events.
3400          if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
3401              return;
3402          }
3403  
3404          // Stop propagation so the model isn't selected.
3405          event.stopPropagation();
3406  
3407          this.collection.remove( this.model );
3408      },
3409  
3410      /**
3411       * Add the model if it isn't in the selection, if it is in the selection,
3412       * remove it.
3413       *
3414       * @param {[type]} event [description]
3415       * @return {[type]} [description]
3416       */
3417      checkClickHandler: function ( event ) {
3418          var selection = this.options.selection;
3419          if ( ! selection ) {
3420              return;
3421          }
3422          event.stopPropagation();
3423          if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
3424              selection.remove( this.model );
3425              // Move focus back to the attachment tile (from the check).
3426              this.$el.focus();
3427          } else {
3428              selection.add( this.model );
3429          }
3430  
3431          // Trigger an action button update.
3432          this.controller.trigger( 'selection:toggle' );
3433      }
3434  });
3435  
3436  // Ensure settings remain in sync between attachment views.
3437  _.each({
3438      caption: '_syncCaption',
3439      title:   '_syncTitle',
3440      artist:  '_syncArtist',
3441      album:   '_syncAlbum'
3442  }, function( method, setting ) {
3443      /**
3444       * @function _syncCaption
3445       * @memberOf wp.media.view.Attachment
3446       * @instance
3447       *
3448       * @param {Backbone.Model} model
3449       * @param {string} value
3450       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3451       */
3452      /**
3453       * @function _syncTitle
3454       * @memberOf wp.media.view.Attachment
3455       * @instance
3456       *
3457       * @param {Backbone.Model} model
3458       * @param {string} value
3459       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3460       */
3461      /**
3462       * @function _syncArtist
3463       * @memberOf wp.media.view.Attachment
3464       * @instance
3465       *
3466       * @param {Backbone.Model} model
3467       * @param {string} value
3468       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3469       */
3470      /**
3471       * @function _syncAlbum
3472       * @memberOf wp.media.view.Attachment
3473       * @instance
3474       *
3475       * @param {Backbone.Model} model
3476       * @param {string} value
3477       * @return {wp.media.view.Attachment} Returns itself to allow chaining.
3478       */
3479      Attachment.prototype[ method ] = function( model, value ) {
3480          var $setting = this.$('[data-setting="' + setting + '"]');
3481  
3482          if ( ! $setting.length ) {
3483              return this;
3484          }
3485  
3486          /*
3487           * If the updated value is in sync with the value in the DOM, there
3488           * is no need to re-render. If we're currently editing the value,
3489           * it will automatically be in sync, suppressing the re-render for
3490           * the view we're editing, while updating any others.
3491           */
3492          if ( value === $setting.find('input, textarea, select, [value]').val() ) {
3493              return this;
3494          }
3495  
3496          return this.render();
3497      };
3498  });
3499  
3500  module.exports = Attachment;
3501  
3502  
3503  /***/ },
3504  
3505  /***/ 6090
3506  (module) {
3507  
3508  /* global ClipboardJS */
3509  var Attachment = wp.media.view.Attachment,
3510      l10n = wp.media.view.l10n,
3511      $ = jQuery,
3512      Details,
3513      __ = wp.i18n.__;
3514  
3515  Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{
3516      tagName:   'div',
3517      className: 'attachment-details',
3518      template:  wp.template('attachment-details'),
3519  
3520      /*
3521       * Reset all the attributes inherited from Attachment including role=checkbox,
3522       * tabindex, etc., as they are inappropriate for this view. See #47458 and [30483] / #30390.
3523       */
3524      attributes: {},
3525  
3526      events: {
3527          'change [data-setting]':          'updateSetting',
3528          'change [data-setting] input':    'updateSetting',
3529          'change [data-setting] select':   'updateSetting',
3530          'change [data-setting] textarea': 'updateSetting',
3531          'click .delete-attachment':       'deleteAttachment',
3532          'click .trash-attachment':        'trashAttachment',
3533          'click .untrash-attachment':      'untrashAttachment',
3534          'click .edit-attachment':         'editAttachment',
3535          'keydown':                        'toggleSelectionHandler'
3536      },
3537  
3538      /**
3539       * Copies the attachment URL to the clipboard.
3540       *
3541       * @since 5.5.0
3542       *
3543       * @param {MouseEvent} event A click event.
3544       *
3545       * @return {void}
3546       */
3547       copyAttachmentDetailsURLClipboard: function() {
3548          var clipboard = new ClipboardJS( '.copy-attachment-url' ),
3549              successTimeout;
3550  
3551          clipboard.on( 'success', function( event ) {
3552              var triggerElement = $( event.trigger ),
3553                  successElement = $( '.success', triggerElement.closest( '.copy-to-clipboard-container' ) );
3554  
3555              // Clear the selection and move focus back to the trigger.
3556              event.clearSelection();
3557  
3558              // Show success visual feedback.
3559              clearTimeout( successTimeout );
3560              successElement.removeClass( 'hidden' );
3561  
3562              // Hide success visual feedback after 3 seconds since last success.
3563              successTimeout = setTimeout( function() {
3564                  successElement.addClass( 'hidden' );
3565              }, 3000 );
3566  
3567              // Handle success audible feedback.
3568              wp.a11y.speak( __( 'The file URL has been copied to your clipboard' ) );
3569          } );
3570       },
3571  
3572      /**
3573       * Shows the details of an attachment.
3574       *
3575       * @since 3.5.0
3576       *
3577       * @constructs wp.media.view.Attachment.Details
3578       * @augments wp.media.view.Attachment
3579       *
3580       * @return {void}
3581       */
3582      initialize: function() {
3583          this.options = _.defaults( this.options, {
3584              rerenderOnModelChange: false
3585          });
3586  
3587          // Call 'initialize' directly on the parent class.
3588          Attachment.prototype.initialize.apply( this, arguments );
3589  
3590          this.copyAttachmentDetailsURLClipboard();
3591      },
3592  
3593      /**
3594       * Gets the focusable elements to move focus to.
3595       *
3596       * @since 5.3.0
3597       */
3598      getFocusableElements: function() {
3599          var editedAttachment = $( 'li[data-id="' + this.model.id + '"]' );
3600  
3601          this.previousAttachment = editedAttachment.prev();
3602          this.nextAttachment = editedAttachment.next();
3603      },
3604  
3605      /**
3606       * Moves focus to the previous or next attachment in the grid.
3607       * Fallbacks to the upload button or media frame when there are no attachments.
3608       *
3609       * @since 5.3.0
3610       */
3611      moveFocus: function() {
3612          if ( this.previousAttachment.length ) {
3613              this.previousAttachment.trigger( 'focus' );
3614              return;
3615          }
3616  
3617          if ( this.nextAttachment.length ) {
3618              this.nextAttachment.trigger( 'focus' );
3619              return;
3620          }
3621  
3622          // Fallback: move focus to the "Select Files" button in the media modal.
3623          if ( this.controller.uploader && this.controller.uploader.$browser ) {
3624              this.controller.uploader.$browser.trigger( 'focus' );
3625              return;
3626          }
3627  
3628          // Last fallback.
3629          this.moveFocusToLastFallback();
3630      },
3631  
3632      /**
3633       * Moves focus to the media frame as last fallback.
3634       *
3635       * @since 5.3.0
3636       */
3637      moveFocusToLastFallback: function() {
3638          // Last fallback: make the frame focusable and move focus to it.
3639          $( '.media-frame' )
3640              .attr( 'tabindex', '-1' )
3641              .trigger( 'focus' );
3642      },
3643  
3644      /**
3645       * Deletes an attachment.
3646       *
3647       * Deletes an attachment after asking for confirmation. After deletion,
3648       * keeps focus in the modal.
3649       *
3650       * @since 3.5.0
3651       *
3652       * @param {MouseEvent} event A click event.
3653       *
3654       * @return {void}
3655       */
3656      deleteAttachment: function( event ) {
3657          event.preventDefault();
3658  
3659          this.getFocusableElements();
3660  
3661          if ( window.confirm( l10n.warnDelete ) ) {
3662              this.model.destroy( {
3663                  wait: true,
3664                  error: function() {
3665                      window.alert( l10n.errorDeleting );
3666                  }
3667              } );
3668  
3669              this.moveFocus();
3670          }
3671      },
3672  
3673      /**
3674       * Sets the Trash state on an attachment, or destroys the model itself.
3675       *
3676       * If the mediaTrash setting is set to true, trashes the attachment.
3677       * Otherwise, the model itself is destroyed.
3678       *
3679       * @since 3.9.0
3680       *
3681       * @param {MouseEvent} event A click event.
3682       *
3683       * @return {void}
3684       */
3685      trashAttachment: function( event ) {
3686          var library = this.controller.library,
3687              self = this;
3688          event.preventDefault();
3689  
3690          this.getFocusableElements();
3691  
3692          // When in the Media Library and the Media Trash is enabled.
3693          if ( wp.media.view.settings.mediaTrash &&
3694              'edit-metadata' === this.controller.content.mode() ) {
3695  
3696              this.model.set( 'status', 'trash' );
3697              this.model.save().done( function() {
3698                  library._requery( true );
3699                  /*
3700                   * @todo We need to move focus back to the previous, next, or first
3701                   * attachment but the library gets re-queried and refreshed.
3702                   * Thus, the references to the previous attachments are lost.
3703                   * We need an alternate method.
3704                   */
3705                  self.moveFocusToLastFallback();
3706              } );
3707          } else {
3708              this.model.destroy();
3709              this.moveFocus();
3710          }
3711      },
3712  
3713      /**
3714       * Untrashes an attachment.
3715       *
3716       * @since 4.0.0
3717       *
3718       * @param {MouseEvent} event A click event.
3719       *
3720       * @return {void}
3721       */
3722      untrashAttachment: function( event ) {
3723          var library = this.controller.library;
3724          event.preventDefault();
3725  
3726          this.model.set( 'status', 'inherit' );
3727          this.model.save().done( function() {
3728              library._requery( true );
3729          } );
3730      },
3731  
3732      /**
3733       * Opens the edit page for a specific attachment.
3734       *
3735       * @since 3.5.0
3736       *
3737       * @param {MouseEvent} event A click event.
3738       *
3739       * @return {void}
3740       */
3741      editAttachment: function( event ) {
3742          var editState = this.controller.states.get( 'edit-image' );
3743          if ( window.imageEdit && editState ) {
3744              event.preventDefault();
3745  
3746              editState.set( 'image', this.model );
3747              this.controller.setState( 'edit-image' );
3748          } else {
3749              this.$el.addClass('needs-refresh');
3750          }
3751      },
3752  
3753      /**
3754       * Triggers an event on the controller when reverse tabbing (shift+tab).
3755       *
3756       * This event can be used to make sure to move the focus correctly.
3757       *
3758       * @since 4.0.0
3759       *
3760       * @fires wp.media.controller.MediaLibrary#attachment:details:shift-tab
3761       * @fires wp.media.controller.MediaLibrary#attachment:keydown:arrow
3762       *
3763       * @param {KeyboardEvent} event A keyboard event.
3764       *
3765       * @return {boolean|void} Returns false or undefined.
3766       */
3767      toggleSelectionHandler: function( event ) {
3768          if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
3769              this.controller.trigger( 'attachment:details:shift-tab', event );
3770              return false;
3771          }
3772      },
3773  
3774      render: function() {
3775          Attachment.prototype.render.apply( this, arguments );
3776  
3777          wp.media.mixin.removeAllPlayers();
3778          this.$( 'audio, video' ).each( function (i, elem) {
3779              var el = wp.media.view.MediaDetails.prepareSrc( elem );
3780              new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
3781          } );
3782      }
3783  });
3784  
3785  module.exports = Details;
3786  
3787  
3788  /***/ },
3789  
3790  /***/ 5232
3791  (module) {
3792  
3793  /**
3794   * wp.media.view.Attachment.EditLibrary
3795   *
3796   * @memberOf wp.media.view.Attachment
3797   *
3798   * @class
3799   * @augments wp.media.view.Attachment
3800   * @augments wp.media.View
3801   * @augments wp.Backbone.View
3802   * @augments Backbone.View
3803   */
3804  var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{
3805      buttons: {
3806          close: true
3807      }
3808  });
3809  
3810  module.exports = EditLibrary;
3811  
3812  
3813  /***/ },
3814  
3815  /***/ 4593
3816  (module) {
3817  
3818  /**
3819   * wp.media.view.Attachment.EditSelection
3820   *
3821   * @memberOf wp.media.view.Attachment
3822   *
3823   * @class
3824   * @augments wp.media.view.Attachment.Selection
3825   * @augments wp.media.view.Attachment
3826   * @augments wp.media.View
3827   * @augments wp.Backbone.View
3828   * @augments Backbone.View
3829   */
3830  var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{
3831      buttons: {
3832          close: true
3833      }
3834  });
3835  
3836  module.exports = EditSelection;
3837  
3838  
3839  /***/ },
3840  
3841  /***/ 3443
3842  (module) {
3843  
3844  /**
3845   * wp.media.view.Attachment.Library
3846   *
3847   * @memberOf wp.media.view.Attachment
3848   *
3849   * @class
3850   * @augments wp.media.view.Attachment
3851   * @augments wp.media.View
3852   * @augments wp.Backbone.View
3853   * @augments Backbone.View
3854   */
3855  var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{
3856      buttons: {
3857          check: true
3858      }
3859  });
3860  
3861  module.exports = Library;
3862  
3863  
3864  /***/ },
3865  
3866  /***/ 3962
3867  (module) {
3868  
3869  /**
3870   * wp.media.view.Attachment.Selection
3871   *
3872   * @memberOf wp.media.view.Attachment
3873   *
3874   * @class
3875   * @augments wp.media.view.Attachment
3876   * @augments wp.media.View
3877   * @augments wp.Backbone.View
3878   * @augments Backbone.View
3879   */
3880  var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{
3881      className: 'attachment selection',
3882  
3883      // On click, just select the model, instead of removing the model from
3884      // the selection.
3885      toggleSelection: function() {
3886          this.options.selection.single( this.model );
3887      }
3888  });
3889  
3890  module.exports = Selection;
3891  
3892  
3893  /***/ },
3894  
3895  /***/ 8142
3896  (module) {
3897  
3898  var View = wp.media.View,
3899      $ = jQuery,
3900      Attachments,
3901      infiniteScrolling = wp.media.view.settings.infiniteScrolling;
3902  
3903  Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
3904      tagName:   'ul',
3905      className: 'attachments',
3906  
3907      attributes: {
3908          tabIndex: -1
3909      },
3910  
3911      /**
3912       * Represents the overview of attachments in the Media Library.
3913       *
3914       * The constructor binds events to the collection this view represents when
3915       * adding or removing attachments or resetting the entire collection.
3916       *
3917       * @since 3.5.0
3918       *
3919       * @constructs
3920       * @memberof wp.media.view
3921       *
3922       * @augments wp.media.View
3923       *
3924       * @listens collection:add
3925       * @listens collection:remove
3926       * @listens collection:reset
3927       * @listens controller:library:selection:add
3928       * @listens scrollElement:scroll
3929       * @listens this:ready
3930       * @listens controller:open
3931       */
3932      initialize: function() {
3933          this.el.id = _.uniqueId('__attachments-view-');
3934  
3935          /**
3936           * @since 5.8.0 Added the `infiniteScrolling` parameter.
3937           *
3938           * @param infiniteScrolling  Whether to enable infinite scrolling or use
3939           *                           the default "load more" button.
3940           * @param refreshSensitivity The time in milliseconds to throttle the scroll
3941           *                           handler.
3942           * @param refreshThreshold   The amount of pixels that should be scrolled before
3943           *                           loading more attachments from the server.
3944           * @param AttachmentView     The view class to be used for models in the
3945           *                           collection.
3946           * @param sortable           A jQuery sortable options object
3947           *                           ( http://api.jqueryui.com/sortable/ ).
3948           * @param resize             A boolean indicating whether or not to listen to
3949           *                           resize events.
3950           * @param idealColumnWidth   The width in pixels which a column should have when
3951           *                           calculating the total number of columns.
3952           */
3953          _.defaults( this.options, {
3954              infiniteScrolling:  infiniteScrolling || false,
3955              refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
3956              refreshThreshold:   3,
3957              AttachmentView:     wp.media.view.Attachment,
3958              sortable:           false,
3959              resize:             true,
3960              idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
3961          });
3962  
3963          this._viewsByCid = {};
3964          this.$window = $( window );
3965          this.resizeEvent = 'resize.media-modal-columns';
3966  
3967          this.collection.on( 'add', function( attachment ) {
3968              this.views.add( this.createAttachmentView( attachment ), {
3969                  at: this.collection.indexOf( attachment )
3970              });
3971          }, this );
3972  
3973          /*
3974           * Find the view to be removed, delete it and call the remove function to clear
3975           * any set event handlers.
3976           */
3977          this.collection.on( 'remove', function( attachment ) {
3978              var view = this._viewsByCid[ attachment.cid ];
3979              delete this._viewsByCid[ attachment.cid ];
3980  
3981              if ( view ) {
3982                  view.remove();
3983              }
3984          }, this );
3985  
3986          this.collection.on( 'reset', this.render, this );
3987  
3988          this.controller.on( 'library:selection:add', this.attachmentFocus, this );
3989  
3990          if ( this.options.infiniteScrolling ) {
3991              // Throttle the scroll handler and bind this.
3992              this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
3993  
3994              this.options.scrollElement = this.options.scrollElement || this.el;
3995              $( this.options.scrollElement ).on( 'scroll', this.scroll );
3996          }
3997  
3998          this.initSortable();
3999  
4000          _.bindAll( this, 'setColumns' );
4001  
4002          if ( this.options.resize ) {
4003              this.on( 'ready', this.bindEvents );
4004              this.controller.on( 'open', this.setColumns );
4005  
4006              /*
4007               * Call this.setColumns() after this view has been rendered in the
4008               * DOM so attachments get proper width applied.
4009               */
4010              _.defer( this.setColumns, this );
4011          }
4012      },
4013  
4014      /**
4015       * Listens to the resizeEvent on the window.
4016       *
4017       * Adjusts the amount of columns accordingly. First removes any existing event
4018       * handlers to prevent duplicate listeners.
4019       *
4020       * @since 4.0.0
4021       *
4022       * @listens window:resize
4023       *
4024       * @return {void}
4025       */
4026      bindEvents: function() {
4027          this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
4028      },
4029  
4030      /**
4031       * Focuses the first item in the collection.
4032       *
4033       * @since 4.0.0
4034       *
4035       * @return {void}
4036       */
4037      attachmentFocus: function() {
4038          /*
4039           * @todo When uploading new attachments, this tries to move focus to
4040           * the attachments grid. Actually, a progress bar gets initially displayed
4041           * and then updated when uploading completes, so focus is lost.
4042           * Additionally: this view is used for both the attachments list and
4043           * the list of selected attachments in the bottom media toolbar. Thus, when
4044           * uploading attachments, it is called twice and returns two different `this`.
4045           * `this.columns` is truthy within the modal.
4046           */
4047          if ( this.columns ) {
4048              // Move focus to the grid list within the modal.
4049              this.$el.focus();
4050          }
4051      },
4052  
4053      /**
4054       * Restores focus to the selected item in the collection.
4055       *
4056       * Moves focus back to the first selected attachment in the grid. Used when
4057       * tabbing backwards from the attachment details sidebar.
4058       * See media.view.AttachmentsBrowser.
4059       *
4060       * @since 4.0.0
4061       *
4062       * @return {void}
4063       */
4064      restoreFocus: function() {
4065          this.$( 'li.selected:first' ).focus();
4066      },
4067  
4068      /**
4069       * Handles events for arrow key presses.
4070       *
4071       * Focuses the attachment in the direction of the used arrow key if it exists.
4072       *
4073       * @since 4.0.0
4074       *
4075       * @param {KeyboardEvent} event The keyboard event that triggered this function.
4076       *
4077       * @return {void}
4078       */
4079      arrowEvent: function( event ) {
4080          var attachments = this.$el.children( 'li' ),
4081              perRow = this.columns,
4082              index = attachments.filter( ':focus' ).index(),
4083              row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
4084  
4085          if ( index === -1 ) {
4086              return;
4087          }
4088  
4089          // Left arrow = 37.
4090          if ( 37 === event.keyCode ) {
4091              if ( 0 === index ) {
4092                  return;
4093              }
4094              attachments.eq( index - 1 ).focus();
4095          }
4096  
4097          // Up arrow = 38.
4098          if ( 38 === event.keyCode ) {
4099              if ( 1 === row ) {
4100                  return;
4101              }
4102              attachments.eq( index - perRow ).focus();
4103          }
4104  
4105          // Right arrow = 39.
4106          if ( 39 === event.keyCode ) {
4107              if ( attachments.length === index ) {
4108                  return;
4109              }
4110              attachments.eq( index + 1 ).focus();
4111          }
4112  
4113          // Down arrow = 40.
4114          if ( 40 === event.keyCode ) {
4115              if ( Math.ceil( attachments.length / perRow ) === row ) {
4116                  return;
4117              }
4118              attachments.eq( index + perRow ).focus();
4119          }
4120      },
4121  
4122      /**
4123       * Clears any set event handlers.
4124       *
4125       * @since 3.5.0
4126       *
4127       * @return {void}
4128       */
4129      dispose: function() {
4130          this.collection.props.off( null, null, this );
4131          if ( this.options.resize ) {
4132              this.$window.off( this.resizeEvent );
4133          }
4134  
4135          // Call 'dispose' directly on the parent class.
4136          View.prototype.dispose.apply( this, arguments );
4137      },
4138  
4139      /**
4140       * Calculates the amount of columns.
4141       *
4142       * Calculates the amount of columns and sets it on the data-columns attribute
4143       * of .media-frame-content.
4144       *
4145       * @since 4.0.0
4146       *
4147       * @return {void}
4148       */
4149      setColumns: function() {
4150          var prev = this.columns,
4151              width = this.$el.width();
4152  
4153          if ( width ) {
4154              this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
4155  
4156              if ( ! prev || prev !== this.columns ) {
4157                  this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
4158              }
4159          }
4160      },
4161  
4162      /**
4163       * Initializes jQuery sortable on the attachment list.
4164       *
4165       * Fails gracefully if jQuery sortable doesn't exist or isn't passed
4166       * in the options.
4167       *
4168       * @since 3.5.0
4169       *
4170       * @fires collection:reset
4171       *
4172       * @return {void}
4173       */
4174      initSortable: function() {
4175          var collection = this.collection;
4176  
4177          if ( ! this.options.sortable || ! $.fn.sortable ) {
4178              return;
4179          }
4180  
4181          this.$el.sortable( _.extend({
4182              // If the `collection` has a `comparator`, disable sorting.
4183              disabled: !! collection.comparator,
4184  
4185              /*
4186               * Change the position of the attachment as soon as the mouse pointer
4187               * overlaps a thumbnail.
4188               */
4189              tolerance: 'pointer',
4190  
4191              // Record the initial `index` of the dragged model.
4192              start: function( event, ui ) {
4193                  ui.item.data('sortableIndexStart', ui.item.index());
4194              },
4195  
4196              /*
4197               * Update the model's index in the collection. Do so silently, as the view
4198               * is already accurate.
4199               */
4200              update: function( event, ui ) {
4201                  var model = collection.at( ui.item.data('sortableIndexStart') ),
4202                      comparator = collection.comparator;
4203  
4204                  // Temporarily disable the comparator to prevent `add`
4205                  // from re-sorting.
4206                  delete collection.comparator;
4207  
4208                  // Silently shift the model to its new index.
4209                  collection.remove( model, {
4210                      silent: true
4211                  });
4212                  collection.add( model, {
4213                      silent: true,
4214                      at:     ui.item.index()
4215                  });
4216  
4217                  // Restore the comparator.
4218                  collection.comparator = comparator;
4219  
4220                  // Fire the `reset` event to ensure other collections sync.
4221                  collection.trigger( 'reset', collection );
4222  
4223                  // If the collection is sorted by menu order, update the menu order.
4224                  collection.saveMenuOrder();
4225              }
4226          }, this.options.sortable ) );
4227  
4228          /*
4229           * If the `orderby` property is changed on the `collection`,
4230           * check to see if we have a `comparator`. If so, disable sorting.
4231           */
4232          collection.props.on( 'change:orderby', function() {
4233              this.$el.sortable( 'option', 'disabled', !! collection.comparator );
4234          }, this );
4235  
4236          this.collection.props.on( 'change:orderby', this.refreshSortable, this );
4237          this.refreshSortable();
4238      },
4239  
4240      /**
4241       * Disables jQuery sortable if collection has a comparator or collection.orderby
4242       * equals menuOrder.
4243       *
4244       * @since 3.5.0
4245       *
4246       * @return {void}
4247       */
4248      refreshSortable: function() {
4249          if ( ! this.options.sortable || ! $.fn.sortable ) {
4250              return;
4251          }
4252  
4253          var collection = this.collection,
4254              orderby = collection.props.get('orderby'),
4255              enabled = 'menuOrder' === orderby || ! collection.comparator;
4256  
4257          this.$el.sortable( 'option', 'disabled', ! enabled );
4258      },
4259  
4260      /**
4261       * Creates a new view for an attachment and adds it to _viewsByCid.
4262       *
4263       * @since 3.5.0
4264       *
4265       * @param {wp.media.model.Attachment} attachment
4266       *
4267       * @return {wp.media.View} The created view.
4268       */
4269      createAttachmentView: function( attachment ) {
4270          var view = new this.options.AttachmentView({
4271              controller:           this.controller,
4272              model:                attachment,
4273              collection:           this.collection,
4274              selection:            this.options.selection
4275          });
4276  
4277          return this._viewsByCid[ attachment.cid ] = view;
4278      },
4279  
4280      /**
4281       * Prepares view for display.
4282       *
4283       * Creates views for every attachment in collection if the collection is not
4284       * empty, otherwise clears all views and loads more attachments.
4285       *
4286       * @since 3.5.0
4287       *
4288       * @return {void}
4289       */
4290      prepare: function() {
4291          if ( this.collection.length ) {
4292              this.views.set( this.collection.map( this.createAttachmentView, this ) );
4293          } else {
4294              this.views.unset();
4295              if ( this.options.infiniteScrolling ) {
4296                  this.collection.more().done( this.scroll );
4297              }
4298          }
4299      },
4300  
4301      /**
4302       * Triggers the scroll function to check if we should query for additional
4303       * attachments right away.
4304       *
4305       * @since 3.5.0
4306       *
4307       * @return {void}
4308       */
4309      ready: function() {
4310          if ( this.options.infiniteScrolling ) {
4311              this.scroll();
4312          }
4313      },
4314  
4315      /**
4316       * Handles scroll events.
4317       *
4318       * Shows the spinner if we're close to the bottom. Loads more attachments from
4319       * server if we're {refreshThreshold} times away from the bottom.
4320       *
4321       * @since 3.5.0
4322       *
4323       * @return {void}
4324       */
4325      scroll: function() {
4326          var view = this,
4327              el = this.options.scrollElement,
4328              scrollTop = el.scrollTop,
4329              toolbar;
4330  
4331          /*
4332           * The scroll event occurs on the document, but the element that should be
4333           * checked is the document body.
4334           */
4335          if ( el === document ) {
4336              el = document.body;
4337              scrollTop = $(document).scrollTop();
4338          }
4339  
4340          if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
4341              return;
4342          }
4343  
4344          toolbar = this.views.parent.toolbar;
4345  
4346          // Show the spinner only if we are close to the bottom.
4347          if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
4348              toolbar.get('spinner').show();
4349          }
4350  
4351          if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
4352              this.collection.more().done(function() {
4353                  view.scroll();
4354                  toolbar.get('spinner').hide();
4355              });
4356          }
4357      }
4358  });
4359  
4360  module.exports = Attachments;
4361  
4362  
4363  /***/ },
4364  
4365  /***/ 6829
4366  (module) {
4367  
4368  var View = wp.media.View,
4369      mediaTrash = wp.media.view.settings.mediaTrash,
4370      l10n = wp.media.view.l10n,
4371      $ = jQuery,
4372      AttachmentsBrowser,
4373      infiniteScrolling = wp.media.view.settings.infiniteScrolling,
4374      __ = wp.i18n.__,
4375      sprintf = wp.i18n.sprintf;
4376  
4377  /**
4378   * wp.media.view.AttachmentsBrowser
4379   *
4380   * @memberOf wp.media.view
4381   *
4382   * @class
4383   * @augments wp.media.View
4384   * @augments wp.Backbone.View
4385   * @augments Backbone.View
4386   *
4387   * @param {object}         [options]               The options hash passed to the view.
4388   * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar.
4389   *                                                 Accepts 'uploaded' and 'all'.
4390   * @param {boolean}        [options.search=true]   Whether to show the search interface in the
4391   *                                                 browser's toolbar.
4392   * @param {boolean}        [options.date=true]     Whether to show the date filter in the
4393   *                                                 browser's toolbar.
4394   * @param {boolean}        [options.display=false] Whether to show the attachments display settings
4395   *                                                 view in the sidebar.
4396   * @param {boolean|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
4397   *                                                 Accepts true, false, and 'errors'.
4398   */
4399  AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{
4400      tagName:   'div',
4401      className: 'attachments-browser',
4402  
4403      initialize: function() {
4404          _.defaults( this.options, {
4405              filters: false,
4406              search:  true,
4407              date:    true,
4408              display: false,
4409              sidebar: true,
4410              AttachmentView: wp.media.view.Attachment.Library
4411          });
4412  
4413          this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this );
4414          this.controller.on( 'edit:selection', this.editSelection );
4415  
4416          // In the Media Library, the sidebar is used to display errors before the attachments grid.
4417          if ( this.options.sidebar && 'errors' === this.options.sidebar ) {
4418              this.createSidebar();
4419          }
4420  
4421          /*
4422           * In the grid mode (the Media Library), place the Inline Uploader before
4423           * other sections so that the visual order and the DOM order match. This way,
4424           * the Inline Uploader in the Media Library is right after the "Add New"
4425           * button, see ticket #37188.
4426           */
4427          if ( this.controller.isModeActive( 'grid' ) ) {
4428              this.createUploader();
4429  
4430              /*
4431               * Create a multi-purpose toolbar. Used as main toolbar in the Media Library
4432               * and also for other things, for example the "Drag and drop to reorder" and
4433               * "Suggested dimensions" info in the media modal.
4434               */
4435              this.createToolbar();
4436          } else {
4437              this.createToolbar();
4438              this.createUploader();
4439          }
4440  
4441          // Add a heading before the attachments list.
4442          this.createAttachmentsHeading();
4443  
4444          // Create the attachments wrapper view.
4445          this.createAttachmentsWrapperView();
4446  
4447          if ( ! infiniteScrolling ) {
4448              this.$el.addClass( 'has-load-more' );
4449              this.createLoadMoreView();
4450          }
4451  
4452          // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909.
4453          if ( this.options.sidebar && 'errors' !== this.options.sidebar ) {
4454              this.createSidebar();
4455          }
4456  
4457          this.updateContent();
4458  
4459          if ( ! infiniteScrolling ) {
4460              this.updateLoadMoreView();
4461          }
4462  
4463          if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
4464              this.$el.addClass( 'hide-sidebar' );
4465  
4466              if ( 'errors' === this.options.sidebar ) {
4467                  this.$el.addClass( 'sidebar-for-errors' );
4468              }
4469          }
4470  
4471          this.collection.on( 'add remove reset', this.updateContent, this );
4472  
4473          if ( ! infiniteScrolling ) {
4474              this.collection.on( 'add remove reset', this.updateLoadMoreView, this );
4475          }
4476  
4477          // The non-cached or cached attachments query has completed.
4478          this.collection.on( 'attachments:received', this.announceSearchResults, this );
4479      },
4480  
4481      /**
4482       * Updates the `wp.a11y.speak()` ARIA live region with a message to communicate
4483       * the number of search results to screen reader users. This function is
4484       * debounced because the collection updates multiple times.
4485       *
4486       * @since 5.3.0
4487       *
4488       * @return {void}
4489       */
4490      announceSearchResults: _.debounce( function() {
4491          var count,
4492              /* translators: Accessibility text. %d: Number of attachments found in a search. */
4493              mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Click load more for more results.' );
4494  
4495          if ( infiniteScrolling ) {
4496              /* translators: Accessibility text. %d: Number of attachments found in a search. */
4497              mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Scroll the page for more results.' );
4498          }
4499  
4500          if ( this.collection.mirroring && this.collection.mirroring.args.s ) {
4501              count = this.collection.length;
4502  
4503              if ( 0 === count ) {
4504                  wp.a11y.speak( l10n.noMediaTryNewSearch );
4505                  return;
4506              }
4507  
4508              if ( this.collection.hasMore() ) {
4509                  wp.a11y.speak( mediaFoundHasMoreResultsMessage.replace( '%d', count ) );
4510                  return;
4511              }
4512  
4513              wp.a11y.speak( l10n.mediaFound.replace( '%d', count ) );
4514          }
4515      }, 200 ),
4516  
4517      editSelection: function( modal ) {
4518          // When editing a selection, move focus to the "Go to library" button.
4519          modal.$( '.media-button-backToLibrary' ).focus();
4520      },
4521  
4522      /**
4523       * @return {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining.
4524       */
4525      dispose: function() {
4526          this.options.selection.off( null, null, this );
4527          View.prototype.dispose.apply( this, arguments );
4528          return this;
4529      },
4530  
4531      createToolbar: function() {
4532          var LibraryViewSwitcher, Filters, toolbarOptions,
4533              showFilterByType = -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] );
4534  
4535          toolbarOptions = {
4536              controller: this.controller
4537          };
4538  
4539          if ( this.controller.isModeActive( 'grid' ) ) {
4540              toolbarOptions.className = 'media-toolbar wp-filter';
4541          }
4542  
4543          /**
4544          * @member {wp.media.view.Toolbar}
4545          */
4546          this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
4547  
4548          this.views.add( this.toolbar );
4549  
4550          this.toolbar.set( 'spinner', new wp.media.view.Spinner({
4551              priority: -20
4552          }) );
4553  
4554          if ( showFilterByType || this.options.date ) {
4555              /*
4556               * Create a h2 heading before the select elements that filter attachments.
4557               * This heading is visible in the modal and visually hidden in the grid.
4558               */
4559              this.toolbar.set( 'filters-heading', new wp.media.view.Heading( {
4560                  priority:   -100,
4561                  text:       l10n.filterAttachments,
4562                  level:      'h2',
4563                  className:  'media-attachments-filter-heading screen-reader-text'
4564              }).render() );
4565          }
4566  
4567          if ( showFilterByType ) {
4568              // "Filters" is a <select>, a label element needs to be rendered before.
4569              this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
4570                  value: l10n.filterByType,
4571                  attributes: {
4572                      'for':  'media-attachment-filters'
4573                  },
4574                  priority:   -80
4575              }).render() );
4576  
4577              if ( 'uploaded' === this.options.filters ) {
4578                  this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({
4579                      controller: this.controller,
4580                      model:      this.collection.props,
4581                      priority:   -80
4582                  }).render() );
4583              } else {
4584                  Filters = new wp.media.view.AttachmentFilters.All({
4585                      controller: this.controller,
4586                      model:      this.collection.props,
4587                      priority:   -80
4588                  });
4589  
4590                  this.toolbar.set( 'filters', Filters.render() );
4591              }
4592          }
4593  
4594          /*
4595           * Feels odd to bring the global media library switcher into the Attachment browser view.
4596           * Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
4597           * which the controller can tap into and add this view?
4598           */
4599          if ( this.controller.isModeActive( 'grid' ) ) {
4600              LibraryViewSwitcher = View.extend({
4601                  className: 'view-switch media-grid-view-switch',
4602                  template: wp.template( 'media-library-view-switcher')
4603              });
4604  
4605              this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
4606                  controller: this.controller,
4607                  priority: -90
4608              }).render() );
4609  
4610              // DateFilter is a <select>, a label element needs to be rendered before.
4611              this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
4612                  value: l10n.filterByDate,
4613                  attributes: {
4614                      'for': 'media-attachment-date-filters'
4615                  },
4616                  priority: -75
4617              }).render() );
4618              this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
4619                  controller: this.controller,
4620                  model:      this.collection.props,
4621                  priority:   -75,
4622              }).render() );
4623  
4624              // BulkSelection is a <div> with subviews, including screen reader text.
4625              this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
4626                  text: l10n.bulkSelect,
4627                  controller: this.controller,
4628                  priority: -70
4629              }).render() );
4630  
4631              this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
4632                  filters: Filters,
4633                  style: 'primary',
4634                  disabled: true,
4635                  text: mediaTrash ? l10n.trashSelected : l10n.deletePermanently,
4636                  controller: this.controller,
4637                  priority: -80,
4638                  click: function() {
4639                      var changed = [], removed = [],
4640                          selection = this.controller.state().get( 'selection' ),
4641                          library = this.controller.state().get( 'library' );
4642  
4643                      if ( ! selection.length ) {
4644                          return;
4645                      }
4646  
4647                      if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
4648                          return;
4649                      }
4650  
4651                      if ( mediaTrash &&
4652                          'trash' !== selection.at( 0 ).get( 'status' ) &&
4653                          ! window.confirm( l10n.warnBulkTrash ) ) {
4654  
4655                          return;
4656                      }
4657  
4658                      selection.each( function( model ) {
4659                          if ( ! model.get( 'nonces' )['delete'] ) {
4660                              removed.push( model );
4661                              return;
4662                          }
4663  
4664                          if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
4665                              model.set( 'status', 'inherit' );
4666                              changed.push( model.save() );
4667                              removed.push( model );
4668                          } else if ( mediaTrash ) {
4669                              model.set( 'status', 'trash' );
4670                              changed.push( model.save() );
4671                              removed.push( model );
4672                          } else {
4673                              model.destroy({wait: true});
4674                          }
4675                      } );
4676  
4677                      if ( changed.length ) {
4678                          selection.remove( removed );
4679  
4680                          $.when.apply( null, changed ).then( _.bind( function() {
4681                              library._requery( true );
4682                              this.controller.trigger( 'selection:action:done' );
4683                          }, this ) );
4684                      } else {
4685                          this.controller.trigger( 'selection:action:done' );
4686                      }
4687                  }
4688              }).render() );
4689  
4690              if ( mediaTrash ) {
4691                  this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
4692                      filters: Filters,
4693                      style: 'link button-link-delete',
4694                      disabled: true,
4695                      text: l10n.deletePermanently,
4696                      controller: this.controller,
4697                      priority: -55,
4698                      size: '',
4699                      click: function() {
4700                          var removed = [],
4701                              destroy = [],
4702                              selection = this.controller.state().get( 'selection' );
4703  
4704                          if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
4705                              return;
4706                          }
4707  
4708                          selection.each( function( model ) {
4709                              if ( ! model.get( 'nonces' )['delete'] ) {
4710                                  removed.push( model );
4711                                  return;
4712                              }
4713  
4714                              destroy.push( model );
4715                          } );
4716  
4717                          if ( removed.length ) {
4718                              selection.remove( removed );
4719                          }
4720  
4721                          if ( destroy.length ) {
4722                              $.when.apply( null, destroy.map( function (item) {
4723                                  return item.destroy();
4724                              } ) ).then( _.bind( function() {
4725                                  this.controller.trigger( 'selection:action:done' );
4726                              }, this ) );
4727                          }
4728                      }
4729                  }).render() );
4730              }
4731  
4732          } else if ( this.options.date ) {
4733              // DateFilter is a <select>, a label element needs to be rendered before.
4734              this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
4735                  value: l10n.filterByDate,
4736                  attributes: {
4737                      'for': 'media-attachment-date-filters'
4738                  },
4739                  priority: -75
4740              }).render() );
4741              this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
4742                  controller: this.controller,
4743                  model:      this.collection.props,
4744                  priority:   -75
4745              }).render() );
4746          }
4747  
4748          if ( this.options.search ) {
4749              // Search is an input, a label element needs to be rendered before.
4750              this.toolbar.set( 'searchLabel', new wp.media.view.Label({
4751                  value: l10n.searchLabel,
4752                  className: 'media-search-input-label',
4753                  attributes: {
4754                      'for': 'media-search-input'
4755                  },
4756                  priority:   60
4757              }).render() );
4758              this.toolbar.set( 'search', new wp.media.view.Search({
4759                  controller: this.controller,
4760                  model:      this.collection.props,
4761                  priority:   60
4762              }).render() );
4763          }
4764  
4765          if ( this.options.dragInfo ) {
4766              this.toolbar.set( 'dragInfo', new View({
4767                  el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
4768                  priority: -40
4769              }) );
4770          }
4771  
4772          if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
4773              this.toolbar.set( 'suggestedDimensions', new View({
4774                  el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0],
4775                  priority: -40
4776              }) );
4777          }
4778      },
4779  
4780      updateContent: function() {
4781          var view = this,
4782              noItemsView;
4783  
4784          if ( this.controller.isModeActive( 'grid' ) ) {
4785              // Usually the media library.
4786              noItemsView = view.attachmentsNoResults;
4787          } else {
4788              // Usually the media modal.
4789              noItemsView = view.uploader;
4790          }
4791  
4792          if ( ! this.collection.length ) {
4793              this.toolbar.get( 'spinner' ).show();
4794              this.toolbar.$( '.media-bg-overlay' ).show();
4795              this.dfd = this.collection.more().done( function() {
4796                  if ( ! view.collection.length ) {
4797                      noItemsView.$el.removeClass( 'hidden' );
4798                  } else {
4799                      noItemsView.$el.addClass( 'hidden' );
4800                  }
4801                  view.toolbar.get( 'spinner' ).hide();
4802                  view.toolbar.$( '.media-bg-overlay' ).hide();
4803              } );
4804          } else {
4805              noItemsView.$el.addClass( 'hidden' );
4806              view.toolbar.get( 'spinner' ).hide();
4807              this.toolbar.$( '.media-bg-overlay' ).hide();
4808          }
4809      },
4810  
4811      createUploader: function() {
4812          this.uploader = new wp.media.view.UploaderInline({
4813              controller: this.controller,
4814              status:     false,
4815              message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
4816              canClose:   this.controller.isModeActive( 'grid' )
4817          });
4818  
4819          this.uploader.$el.addClass( 'hidden' );
4820          this.views.add( this.uploader );
4821      },
4822  
4823      toggleUploader: function() {
4824          if ( this.uploader.$el.hasClass( 'hidden' ) ) {
4825              this.uploader.show();
4826          } else {
4827              this.uploader.hide();
4828          }
4829      },
4830  
4831      /**
4832       * Creates the Attachments wrapper view.
4833       *
4834       * @since 5.8.0
4835       *
4836       * @return {void}
4837       */
4838      createAttachmentsWrapperView: function() {
4839          this.attachmentsWrapper = new wp.media.View( {
4840              className: 'attachments-wrapper'
4841          } );
4842  
4843          // Create the list of attachments.
4844          this.views.add( this.attachmentsWrapper );
4845          this.createAttachments();
4846      },
4847  
4848      createAttachments: function() {
4849          this.attachments = new wp.media.view.Attachments({
4850              controller:           this.controller,
4851              collection:           this.collection,
4852              selection:            this.options.selection,
4853              model:                this.model,
4854              sortable:             this.options.sortable,
4855              scrollElement:        this.options.scrollElement,
4856              idealColumnWidth:     this.options.idealColumnWidth,
4857  
4858              // The single `Attachment` view to be used in the `Attachments` view.
4859              AttachmentView: this.options.AttachmentView
4860          });
4861  
4862          // Add keydown listener to the instance of the Attachments view.
4863          this.controller.on( 'attachment:keydown:arrow',     _.bind( this.attachments.arrowEvent, this.attachments ) );
4864          this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) );
4865  
4866          this.views.add( '.attachments-wrapper', this.attachments );
4867  
4868          if ( this.controller.isModeActive( 'grid' ) ) {
4869              this.attachmentsNoResults = new View({
4870                  controller: this.controller,
4871                  tagName: 'p'
4872              });
4873  
4874              this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
4875              this.attachmentsNoResults.$el.html( l10n.noMedia );
4876  
4877              this.views.add( this.attachmentsNoResults );
4878          }
4879      },
4880  
4881      /**
4882       * Creates the load more button and attachments counter view.
4883       *
4884       * @since 5.8.0
4885       *
4886       * @return {void}
4887       */
4888      createLoadMoreView: function() {
4889          var view = this;
4890  
4891          this.loadMoreWrapper = new View( {
4892              controller: this.controller,
4893              className: 'load-more-wrapper'
4894          } );
4895  
4896          this.loadMoreCount = new View( {
4897              controller: this.controller,
4898              tagName: 'p',
4899              className: 'load-more-count hidden'
4900          } );
4901  
4902          this.loadMoreButton = new wp.media.view.Button( {
4903              text: __( 'Load more' ),
4904              className: 'load-more hidden',
4905              style: 'primary',
4906              size: '',
4907              click: function() {
4908                  view.loadMoreAttachments();
4909              }
4910          } );
4911  
4912          this.loadMoreSpinner = new wp.media.view.Spinner();
4913  
4914          this.loadMoreJumpToFirst = new wp.media.view.Button( {
4915              text: __( 'Jump to first loaded item' ),
4916              className: 'load-more-jump hidden',
4917              size: '',
4918              click: function() {
4919                  view.jumpToFirstAddedItem();
4920              }
4921          } );
4922  
4923          this.views.add( '.attachments-wrapper', this.loadMoreWrapper );
4924          this.views.add( '.load-more-wrapper', this.loadMoreSpinner );
4925          this.views.add( '.load-more-wrapper', this.loadMoreCount );
4926          this.views.add( '.load-more-wrapper', this.loadMoreButton );
4927          this.views.add( '.load-more-wrapper', this.loadMoreJumpToFirst );
4928      },
4929  
4930      /**
4931       * Updates the Load More view. This function is debounced because the
4932       * collection updates multiple times at the add, remove, and reset events.
4933       * We need it to run only once, after all attachments are added or removed.
4934       *
4935       * @since 5.8.0
4936       *
4937       * @return {void}
4938       */
4939      updateLoadMoreView: _.debounce( function() {
4940          // Ensure the load more view elements are initially hidden at each update.
4941          this.loadMoreButton.$el.addClass( 'hidden' );
4942          this.loadMoreCount.$el.addClass( 'hidden' );
4943          this.loadMoreJumpToFirst.$el.addClass( 'hidden' ).prop( 'disabled', true );
4944  
4945          if ( ! this.collection.getTotalAttachments() ) {
4946              return;
4947          }
4948  
4949          if ( this.collection.length ) {
4950              this.loadMoreCount.$el.text(
4951                  /* translators: 1: Number of displayed attachments, 2: Number of total attachments. */
4952                  sprintf(
4953                      __( 'Showing %1$s of %2$s media items' ),
4954                      this.collection.length,
4955                      this.collection.getTotalAttachments()
4956                  )
4957              );
4958  
4959              this.loadMoreCount.$el.removeClass( 'hidden' );
4960          }
4961  
4962          /*
4963           * Notice that while the collection updates multiple times hasMore() may
4964           * return true when it's actually not true.
4965           */
4966          if ( this.collection.hasMore() ) {
4967              this.loadMoreButton.$el.removeClass( 'hidden' );
4968          }
4969  
4970          // Find the media item to move focus to. The jQuery `eq()` index is zero-based.
4971          this.firstAddedMediaItem = this.$el.find( '.attachment' ).eq( this.firstAddedMediaItemIndex );
4972  
4973          // If there's a media item to move focus to, make the "Jump to" button available.
4974          if ( this.firstAddedMediaItem.length ) {
4975              this.firstAddedMediaItem.addClass( 'new-media' );
4976              this.loadMoreJumpToFirst.$el.removeClass( 'hidden' ).prop( 'disabled', false );
4977          }
4978  
4979          // If there are new items added, but no more to be added, move focus to Jump button.
4980          if ( this.firstAddedMediaItem.length && ! this.collection.hasMore() ) {
4981              this.loadMoreJumpToFirst.$el.trigger( 'focus' );
4982          }
4983      }, 10 ),
4984  
4985      /**
4986       * Loads more attachments.
4987       *
4988       * @since 5.8.0
4989       *
4990       * @return {void}
4991       */
4992      loadMoreAttachments: function() {
4993          var view = this;
4994  
4995          if ( ! this.collection.hasMore() ) {
4996              return;
4997          }
4998  
4999          /*
5000           * The collection index is zero-based while the length counts the actual
5001           * amount of items. Thus the length is equivalent to the position of the
5002           * first added item.
5003           */
5004          this.firstAddedMediaItemIndex = this.collection.length;
5005  
5006          this.$el.addClass( 'more-loaded' );
5007          this.collection.each( function( attachment ) {
5008              var attach_id = attachment.attributes.id;
5009              $( '[data-id="' + attach_id + '"]' ).addClass( 'found-media' );
5010          });
5011  
5012          view.loadMoreSpinner.show();
5013          this.collection.once( 'attachments:received', function() {
5014              view.loadMoreSpinner.hide();
5015          } );
5016          this.collection.more();
5017      },
5018  
5019      /**
5020       * Moves focus to the first new added item.    .
5021       *
5022       * @since 5.8.0
5023       *
5024       * @return {void}
5025       */
5026      jumpToFirstAddedItem: function() {
5027          // Set focus on first added item.
5028          this.firstAddedMediaItem.focus();
5029      },
5030  
5031      createAttachmentsHeading: function() {
5032          this.attachmentsHeading = new wp.media.view.Heading( {
5033              text: l10n.attachmentsList,
5034              level: 'h2',
5035              className: 'media-views-heading screen-reader-text'
5036          } );
5037          this.views.add( this.attachmentsHeading );
5038      },
5039  
5040      createSidebar: function() {
5041          var options = this.options,
5042              selection = options.selection,
5043              sidebar = this.sidebar = new wp.media.view.Sidebar({
5044                  controller: this.controller
5045              });
5046  
5047          this.views.add( sidebar );
5048  
5049          if ( this.controller.uploader ) {
5050              sidebar.set( 'uploads', new wp.media.view.UploaderStatus({
5051                  controller: this.controller,
5052                  priority:   40
5053              }) );
5054          }
5055  
5056          selection.on( 'selection:single', this.createSingle, this );
5057          selection.on( 'selection:unsingle', this.disposeSingle, this );
5058  
5059          if ( selection.single() ) {
5060              this.createSingle();
5061          }
5062      },
5063  
5064      createSingle: function() {
5065          var sidebar = this.sidebar,
5066              single = this.options.selection.single();
5067  
5068          sidebar.set( 'details', new wp.media.view.Attachment.Details({
5069              controller: this.controller,
5070              model:      single,
5071              priority:   80
5072          }) );
5073  
5074          sidebar.set( 'compat', new wp.media.view.AttachmentCompat({
5075              controller: this.controller,
5076              model:      single,
5077              priority:   120
5078          }) );
5079  
5080          if ( this.options.display ) {
5081              sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({
5082                  controller:   this.controller,
5083                  model:        this.model.display( single ),
5084                  attachment:   single,
5085                  priority:     160,
5086                  userSettings: this.model.get('displayUserSettings')
5087              }) );
5088          }
5089  
5090          // Show the sidebar on mobile.
5091          if ( this.model.id === 'insert' ) {
5092              sidebar.$el.addClass( 'visible' );
5093          }
5094      },
5095  
5096      disposeSingle: function() {
5097          var sidebar = this.sidebar;
5098          sidebar.unset('details');
5099          sidebar.unset('compat');
5100          sidebar.unset('display');
5101          // Hide the sidebar on mobile.
5102          sidebar.$el.removeClass( 'visible' );
5103      }
5104  });
5105  
5106  module.exports = AttachmentsBrowser;
5107  
5108  
5109  /***/ },
5110  
5111  /***/ 3479
5112  (module) {
5113  
5114  var Attachments = wp.media.view.Attachments,
5115      Selection;
5116  
5117  /**
5118   * wp.media.view.Attachments.Selection
5119   *
5120   * @memberOf wp.media.view.Attachments
5121   *
5122   * @class
5123   * @augments wp.media.view.Attachments
5124   * @augments wp.media.View
5125   * @augments wp.Backbone.View
5126   * @augments Backbone.View
5127   */
5128  Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{
5129      events: {},
5130      initialize: function() {
5131          _.defaults( this.options, {
5132              sortable:   false,
5133              resize:     false,
5134  
5135              // The single `Attachment` view to be used in the `Attachments` view.
5136              AttachmentView: wp.media.view.Attachment.Selection
5137          });
5138          // Call 'initialize' directly on the parent class.
5139          return Attachments.prototype.initialize.apply( this, arguments );
5140      }
5141  });
5142  
5143  module.exports = Selection;
5144  
5145  
5146  /***/ },
5147  
5148  /***/ 168
5149  (module) {
5150  
5151  var $ = Backbone.$,
5152      ButtonGroup;
5153  
5154  /**
5155   * wp.media.view.ButtonGroup
5156   *
5157   * @memberOf wp.media.view
5158   *
5159   * @class
5160   * @augments wp.media.View
5161   * @augments wp.Backbone.View
5162   * @augments Backbone.View
5163   */
5164  ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{
5165      tagName:   'div',
5166      className: 'button-group button-large media-button-group',
5167  
5168      initialize: function() {
5169          /**
5170           * @member {wp.media.view.Button[]}
5171           */
5172          this.buttons = _.map( this.options.buttons || [], function( button ) {
5173              if ( button instanceof Backbone.View ) {
5174                  return button;
5175              } else {
5176                  return new wp.media.view.Button( button ).render();
5177              }
5178          });
5179  
5180          delete this.options.buttons;
5181  
5182          if ( this.options.classes ) {
5183              this.$el.addClass( this.options.classes );
5184          }
5185      },
5186  
5187      /**
5188       * @return {wp.media.view.ButtonGroup}
5189       */
5190      render: function() {
5191          this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
5192          return this;
5193      }
5194  });
5195  
5196  module.exports = ButtonGroup;
5197  
5198  
5199  /***/ },
5200  
5201  /***/ 846
5202  (module) {
5203  
5204  /**
5205   * wp.media.view.Button
5206   *
5207   * @memberOf wp.media.view
5208   *
5209   * @class
5210   * @augments wp.media.View
5211   * @augments wp.Backbone.View
5212   * @augments Backbone.View
5213   */
5214  var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{
5215      tagName:    'button',
5216      className:  'media-button',
5217      attributes: { type: 'button' },
5218  
5219      events: {
5220          'click': 'click'
5221      },
5222  
5223      defaults: {
5224          text:     '',
5225          style:    '',
5226          size:     'large',
5227          disabled: false
5228      },
5229  
5230      initialize: function() {
5231          /**
5232           * Create a model with the provided `defaults`.
5233           *
5234           * @member {Backbone.Model}
5235           */
5236          this.model = new Backbone.Model( this.defaults );
5237  
5238          // If any of the `options` have a key from `defaults`, apply its
5239          // value to the `model` and remove it from the `options` object.
5240          _.each( this.defaults, function( def, key ) {
5241              var value = this.options[ key ];
5242              if ( _.isUndefined( value ) ) {
5243                  return;
5244              }
5245  
5246              this.model.set( key, value );
5247              delete this.options[ key ];
5248          }, this );
5249  
5250          this.listenTo( this.model, 'change', this.render );
5251      },
5252      /**
5253       * @return {wp.media.view.Button} Returns itself to allow chaining.
5254       */
5255      render: function() {
5256          var classes = [ 'button', this.className ],
5257              model = this.model.toJSON();
5258  
5259          if ( model.style ) {
5260              classes.push( 'button-' + model.style );
5261          }
5262  
5263          if ( model.size ) {
5264              classes.push( 'button-' + model.size );
5265          }
5266  
5267          classes = _.uniq( classes.concat( this.options.classes ) );
5268          this.el.className = classes.join(' ');
5269  
5270          this.$el.prop( 'disabled', model.disabled );
5271          this.$el.text( this.model.get('text') );
5272  
5273          return this;
5274      },
5275      /**
5276       * @param {Object} event
5277       */
5278      click: function( event ) {
5279          if ( '#' === this.attributes.href ) {
5280              event.preventDefault();
5281          }
5282  
5283          if ( this.options.click && ! this.model.get('disabled') ) {
5284              this.options.click.apply( this, arguments );
5285          }
5286      }
5287  });
5288  
5289  module.exports = Button;
5290  
5291  
5292  /***/ },
5293  
5294  /***/ 7637
5295  (module) {
5296  
5297  var View = wp.media.View,
5298      UploaderStatus = wp.media.view.UploaderStatus,
5299      l10n = wp.media.view.l10n,
5300      $ = jQuery,
5301      Cropper;
5302  
5303  /**
5304   * wp.media.view.Cropper
5305   *
5306   * Uses the imgAreaSelect plugin to allow a user to crop an image.
5307   *
5308   * Takes imgAreaSelect options from
5309   * wp.customize.HeaderControl.calculateImageSelectOptions via
5310   * wp.customize.HeaderControl.openMM.
5311   *
5312   * @memberOf wp.media.view
5313   *
5314   * @class
5315   * @augments wp.media.View
5316   * @augments wp.Backbone.View
5317   * @augments Backbone.View
5318   */
5319  Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{
5320      className: 'crop-content',
5321      template: wp.template('crop-content'),
5322      initialize: function() {
5323          _.bindAll(this, 'onImageLoad');
5324      },
5325      ready: function() {
5326          this.controller.frame.on('content:error:crop', this.onError, this);
5327          this.$image = this.$el.find('.crop-image');
5328          this.$image.on('load', this.onImageLoad);
5329          $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
5330      },
5331      remove: function() {
5332          $(window).off('resize.cropper');
5333          this.$el.remove();
5334          this.$el.off();
5335          View.prototype.remove.apply(this, arguments);
5336      },
5337      prepare: function() {
5338          return {
5339              title: l10n.cropYourImage,
5340              url: this.options.attachment.get('url')
5341          };
5342      },
5343      onImageLoad: function() {
5344          var imgOptions = this.controller.get('imgSelectOptions'),
5345              imgSelect;
5346  
5347          if (typeof imgOptions === 'function') {
5348              imgOptions = imgOptions(this.options.attachment, this.controller);
5349          }
5350  
5351          imgOptions = _.extend(imgOptions, {
5352              parent: this.$el,
5353              onInit: function() {
5354  
5355                  // Store the set ratio.
5356                  var setRatio = imgSelect.getOptions().aspectRatio;
5357  
5358                  // On mousedown, if no ratio is set and the Shift key is down, use a 1:1 ratio.
5359                  this.parent.children().on( 'mousedown touchstart', function( e ) {
5360  
5361                      // If no ratio is set and the shift key is down, use a 1:1 ratio.
5362                      if ( ! setRatio && e.shiftKey ) {
5363                          imgSelect.setOptions( {
5364                              aspectRatio: '1:1'
5365                          } );
5366                      }
5367                  } );
5368  
5369                  this.parent.children().on( 'mouseup touchend', function() {
5370  
5371                      // Restore the set ratio.
5372                      imgSelect.setOptions( {
5373                          aspectRatio: setRatio ? setRatio : false
5374                      } );
5375                  } );
5376              }
5377          } );
5378          this.trigger('image-loaded');
5379          imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
5380      },
5381      onError: function() {
5382          var filename = this.options.attachment.get('filename');
5383  
5384          this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
5385              filename: UploaderStatus.prototype.filename(filename),
5386              message: window._wpMediaViewsL10n.cropError
5387          }), { at: 0 });
5388      }
5389  });
5390  
5391  module.exports = Cropper;
5392  
5393  
5394  /***/ },
5395  
5396  /***/ 6126
5397  (module) {
5398  
5399  var View = wp.media.View,
5400      EditImage;
5401  
5402  /**
5403   * wp.media.view.EditImage
5404   *
5405   * @memberOf wp.media.view
5406   *
5407   * @class
5408   * @augments wp.media.View
5409   * @augments wp.Backbone.View
5410   * @augments Backbone.View
5411   */
5412  EditImage = View.extend(/** @lends wp.media.view.EditImage.prototype */{
5413      className: 'image-editor',
5414      template: wp.template('image-editor'),
5415  
5416      initialize: function( options ) {
5417          this.editor = window.imageEdit;
5418          this.controller = options.controller;
5419          View.prototype.initialize.apply( this, arguments );
5420      },
5421  
5422      prepare: function() {
5423          return this.model.toJSON();
5424      },
5425  
5426      loadEditor: function() {
5427          this.editor.open( this.model.get( 'id' ), this.model.get( 'nonces' ).edit, this );
5428      },
5429  
5430      back: function() {
5431          var lastState = this.controller.lastState();
5432          this.controller.setState( lastState );
5433      },
5434  
5435      refresh: function() {
5436          this.model.fetch();
5437      },
5438  
5439      save: function() {
5440          var lastState = this.controller.lastState();
5441  
5442          this.model.fetch().done( _.bind( function() {
5443              this.controller.setState( lastState );
5444          }, this ) );
5445      }
5446  
5447  });
5448  
5449  module.exports = EditImage;
5450  
5451  
5452  /***/ },
5453  
5454  /***/ 5741
5455  (module) {
5456  
5457  /**
5458   * wp.media.view.Embed
5459   *
5460   * @memberOf wp.media.view
5461   *
5462   * @class
5463   * @augments wp.media.View
5464   * @augments wp.Backbone.View
5465   * @augments Backbone.View
5466   */
5467  var Embed = wp.media.View.extend(/** @lends wp.media.view.Ember.prototype */{
5468      className: 'media-embed',
5469  
5470      initialize: function() {
5471          /**
5472           * @member {wp.media.view.EmbedUrl}
5473           */
5474          this.url = new wp.media.view.EmbedUrl({
5475              controller: this.controller,
5476              model:      this.model.props
5477          }).render();
5478  
5479          this.views.set([ this.url ]);
5480          this.refresh();
5481          this.listenTo( this.model, 'change:type', this.refresh );
5482          this.listenTo( this.model, 'change:loading', this.loading );
5483      },
5484  
5485      /**
5486       * @param {Object} view
5487       */
5488      settings: function( view ) {
5489          if ( this._settings ) {
5490              this._settings.remove();
5491          }
5492          this._settings = view;
5493          this.views.add( view );
5494      },
5495  
5496      refresh: function() {
5497          var type = this.model.get('type'),
5498              constructor;
5499  
5500          if ( 'image' === type ) {
5501              constructor = wp.media.view.EmbedImage;
5502          } else if ( 'link' === type ) {
5503              constructor = wp.media.view.EmbedLink;
5504          } else {
5505              return;
5506          }
5507  
5508          this.settings( new constructor({
5509              controller: this.controller,
5510              model:      this.model.props,
5511              priority:   40
5512          }) );
5513      },
5514  
5515      loading: function() {
5516          this.$el.toggleClass( 'embed-loading', this.model.get('loading') );
5517      }
5518  });
5519  
5520  module.exports = Embed;
5521  
5522  
5523  /***/ },
5524  
5525  /***/ 2395
5526  (module) {
5527  
5528  var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
5529      EmbedImage;
5530  
5531  /**
5532   * wp.media.view.EmbedImage
5533   *
5534   * @memberOf wp.media.view
5535   *
5536   * @class
5537   * @augments wp.media.view.Settings.AttachmentDisplay
5538   * @augments wp.media.view.Settings
5539   * @augments wp.media.View
5540   * @augments wp.Backbone.View
5541   * @augments Backbone.View
5542   */
5543  EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{
5544      className: 'embed-media-settings',
5545      template:  wp.template('embed-image-settings'),
5546  
5547      initialize: function() {
5548          /**
5549           * Call `initialize` directly on parent class with passed arguments
5550           */
5551          AttachmentDisplay.prototype.initialize.apply( this, arguments );
5552          this.listenTo( this.model, 'change:url', this.updateImage );
5553      },
5554  
5555      updateImage: function() {
5556          this.$('img').attr( 'src', this.model.get('url') );
5557      }
5558  });
5559  
5560  module.exports = EmbedImage;
5561  
5562  
5563  /***/ },
5564  
5565  /***/ 8232
5566  (module) {
5567  
5568  var $ = jQuery,
5569      EmbedLink;
5570  
5571  /**
5572   * wp.media.view.EmbedLink
5573   *
5574   * @memberOf wp.media.view
5575   *
5576   * @class
5577   * @augments wp.media.view.Settings
5578   * @augments wp.media.View
5579   * @augments wp.Backbone.View
5580   * @augments Backbone.View
5581   */
5582  EmbedLink = wp.media.view.Settings.extend(/** @lends wp.media.view.EmbedLink.prototype */{
5583      className: 'embed-link-settings',
5584      template:  wp.template('embed-link-settings'),
5585  
5586      initialize: function() {
5587          this.listenTo( this.model, 'change:url', this.updateoEmbed );
5588      },
5589  
5590      updateoEmbed: _.debounce( function() {
5591          var url = this.model.get( 'url' );
5592  
5593          // Clear out previous results.
5594          this.$('.embed-container').hide().find('.embed-preview').empty();
5595          this.$( '.setting' ).hide();
5596  
5597          // Only proceed with embed if the field contains more than 11 characters.
5598          // Example: http://a.io is 11 chars
5599          if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {
5600              return;
5601          }
5602  
5603          this.fetch();
5604      }, wp.media.controller.Embed.sensitivity ),
5605  
5606      fetch: function() {
5607          var url = this.model.get( 'url' ), re, youTubeEmbedMatch;
5608  
5609          // Check if they haven't typed in 500 ms.
5610          if ( $('#embed-url-field').val() !== url ) {
5611              return;
5612          }
5613  
5614          if ( this.dfd && 'pending' === this.dfd.state() ) {
5615              this.dfd.abort();
5616          }
5617  
5618          // Support YouTube embed urls, since they work once in the editor.
5619          re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/;
5620          youTubeEmbedMatch = re.exec( url );
5621          if ( youTubeEmbedMatch ) {
5622              url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ];
5623          }
5624  
5625          this.dfd = wp.apiRequest({
5626              url: wp.media.view.settings.oEmbedProxyUrl,
5627              data: {
5628                  url: url,
5629                  maxwidth: this.model.get( 'width' ),
5630                  maxheight: this.model.get( 'height' )
5631              },
5632              type: 'GET',
5633              dataType: 'json',
5634              context: this
5635          })
5636              .done( function( response ) {
5637                  this.renderoEmbed( {
5638                      data: {
5639                          body: response.html || ''
5640                      }
5641                  } );
5642              } )
5643              .fail( this.renderFail );
5644      },
5645  
5646      renderFail: function ( response, status ) {
5647          if ( 'abort' === status ) {
5648              return;
5649          }
5650          this.$( '.link-text' ).show();
5651      },
5652  
5653      renderoEmbed: function( response ) {
5654          var html = ( response && response.data && response.data.body ) || '';
5655  
5656          if ( html ) {
5657              this.$('.embed-container').show().find('.embed-preview').html( html );
5658          } else {
5659              this.renderFail();
5660          }
5661      }
5662  });
5663  
5664  module.exports = EmbedLink;
5665  
5666  
5667  /***/ },
5668  
5669  /***/ 7327
5670  (module) {
5671  
5672  var View = wp.media.View,
5673      $ = jQuery,
5674      l10n = wp.media.view.l10n,
5675      EmbedUrl;
5676  
5677  /**
5678   * wp.media.view.EmbedUrl
5679   *
5680   * @memberOf wp.media.view
5681   *
5682   * @class
5683   * @augments wp.media.View
5684   * @augments wp.Backbone.View
5685   * @augments Backbone.View
5686   */
5687  EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{
5688      tagName:   'span',
5689      className: 'embed-url',
5690  
5691      events: {
5692          'input': 'url'
5693      },
5694  
5695      initialize: function() {
5696          this.$input = $( '<input id="embed-url-field" type="url" />' )
5697              .attr( 'aria-label', l10n.insertFromUrlTitle )
5698              .val( this.model.get('url') );
5699          this.input = this.$input[0];
5700  
5701          this.spinner = $('<span class="spinner" />')[0];
5702          this.$el.append([ this.input, this.spinner ]);
5703  
5704          this.listenTo( this.model, 'change:url', this.render );
5705  
5706          if ( this.model.get( 'url' ) ) {
5707              _.delay( _.bind( function () {
5708                  this.model.trigger( 'change:url' );
5709              }, this ), 500 );
5710          }
5711      },
5712      /**
5713       * @return {wp.media.view.EmbedUrl} Returns itself to allow chaining.
5714       */
5715      render: function() {
5716          var $input = this.$input;
5717  
5718          if ( $input.is(':focus') ) {
5719              return;
5720          }
5721  
5722          if ( this.model.get( 'url' ) ) {
5723              this.input.value = this.model.get('url');
5724          } else {
5725              this.input.setAttribute( 'placeholder', 'https://' );
5726          }
5727  
5728          /**
5729           * Call `render` directly on parent class with passed arguments
5730           */
5731          View.prototype.render.apply( this, arguments );
5732          return this;
5733      },
5734  
5735      url: function( event ) {
5736          var url = event.target.value || '';
5737          this.model.set( 'url', url.trim() );
5738      }
5739  });
5740  
5741  module.exports = EmbedUrl;
5742  
5743  
5744  /***/ },
5745  
5746  /***/ 718
5747  (module) {
5748  
5749  var $ = jQuery;
5750  
5751  /**
5752   * wp.media.view.FocusManager
5753   *
5754   * @memberOf wp.media.view
5755   *
5756   * @class
5757   * @augments wp.media.View
5758   * @augments wp.Backbone.View
5759   * @augments Backbone.View
5760   */
5761  var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{
5762  
5763      events: {
5764          'keydown': 'focusManagementMode'
5765      },
5766  
5767      /**
5768       * Initializes the Focus Manager.
5769       *
5770       * @param {Object} options The Focus Manager options.
5771       *
5772       * @since 5.3.0
5773       *
5774       * @return {void}
5775       */
5776      initialize: function( options ) {
5777          this.mode                    = options.mode || 'constrainTabbing';
5778          this.tabsAutomaticActivation = options.tabsAutomaticActivation || false;
5779      },
5780  
5781       /**
5782       * Determines which focus management mode to use.
5783       *
5784       * @since 5.3.0
5785       *
5786       * @param {Object} event jQuery event object.
5787       *
5788       * @return {void}
5789       */
5790      focusManagementMode: function( event ) {
5791          if ( this.mode === 'constrainTabbing' ) {
5792              this.constrainTabbing( event );
5793          }
5794  
5795          if ( this.mode === 'tabsNavigation' ) {
5796              this.tabsNavigation( event );
5797          }
5798      },
5799  
5800      /**
5801       * Gets all the tabbable elements.
5802       *
5803       * @since 5.3.0
5804       *
5805       * @return {Object} A jQuery collection of tabbable elements.
5806       */
5807      getTabbables: function() {
5808          // Skip the file input added by Plupload.
5809          return this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
5810      },
5811  
5812      /**
5813       * Moves focus to the modal dialog.
5814       *
5815       * @since 3.5.0
5816       *
5817       * @return {void}
5818       */
5819      focus: function() {
5820          this.$( '.media-modal' ).trigger( 'focus' );
5821      },
5822  
5823      /**
5824       * Constrains navigation with the Tab key within the media view element.
5825       *
5826       * @since 4.0.0
5827       *
5828       * @param {Object} event A keydown jQuery event.
5829       *
5830       * @return {void}
5831       */
5832      constrainTabbing: function( event ) {
5833          var tabbables;
5834  
5835          // Look for the tab key.
5836          if ( 9 !== event.keyCode ) {
5837              return;
5838          }
5839  
5840          tabbables = this.getTabbables();
5841  
5842          // Keep tab focus within media modal while it's open.
5843          if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
5844              tabbables.first().focus();
5845              return false;
5846          } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
5847              tabbables.last().focus();
5848              return false;
5849          }
5850      },
5851  
5852      /**
5853       * Hides from assistive technologies all the body children.
5854       *
5855       * Sets an `aria-hidden="true"` attribute on all the body children except
5856       * the provided element and other elements that should not be hidden.
5857       *
5858       * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy
5859       * in Safari 11.1 and support is spotty in other browsers. Also, `aria-modal="true"`
5860       * prevents the `wp.a11y.speak()` ARIA live regions to work as they're outside
5861       * of the modal dialog and get hidden from assistive technologies.
5862       *
5863       * @since 5.2.3
5864       *
5865       * @param {Object} visibleElement The jQuery object representing the element that should not be hidden.
5866       *
5867       * @return {void}
5868       */
5869      setAriaHiddenOnBodyChildren: function( visibleElement ) {
5870          var bodyChildren,
5871              self = this;
5872  
5873          if ( this.isBodyAriaHidden ) {
5874              return;
5875          }
5876  
5877          // Get all the body children.
5878          bodyChildren = document.body.children;
5879  
5880          // Loop through the body children and hide the ones that should be hidden.
5881          _.each( bodyChildren, function( element ) {
5882              // Don't hide the modal element.
5883              if ( element === visibleElement[0] ) {
5884                  return;
5885              }
5886  
5887              // Determine the body children to hide.
5888              if ( self.elementShouldBeHidden( element ) ) {
5889                  element.setAttribute( 'aria-hidden', 'true' );
5890                  // Store the hidden elements.
5891                  self.ariaHiddenElements.push( element );
5892              }
5893          } );
5894  
5895          this.isBodyAriaHidden = true;
5896      },
5897  
5898      /**
5899       * Unhides from assistive technologies all the body children.
5900       *
5901       * Makes visible again to assistive technologies all the body children
5902       * previously hidden and stored in this.ariaHiddenElements.
5903       *
5904       * @since 5.2.3
5905       *
5906       * @return {void}
5907       */
5908      removeAriaHiddenFromBodyChildren: function() {
5909          _.each( this.ariaHiddenElements, function( element ) {
5910              element.removeAttribute( 'aria-hidden' );
5911          } );
5912  
5913          this.ariaHiddenElements = [];
5914          this.isBodyAriaHidden   = false;
5915      },
5916  
5917      /**
5918       * Determines if the passed element should not be hidden from assistive technologies.
5919       *
5920       * @since 5.2.3
5921       *
5922       * @param {Object} element The DOM element that should be checked.
5923       *
5924       * @return {boolean} Whether the element should not be hidden from assistive technologies.
5925       */
5926      elementShouldBeHidden: function( element ) {
5927          var role = element.getAttribute( 'role' ),
5928              liveRegionsRoles = [ 'alert', 'status', 'log', 'marquee', 'timer' ];
5929  
5930          /*
5931           * Don't hide scripts, elements that already have `aria-hidden`, and
5932           * ARIA live regions.
5933           */
5934          return ! (
5935              element.tagName === 'SCRIPT' ||
5936              element.hasAttribute( 'aria-hidden' ) ||
5937              element.hasAttribute( 'aria-live' ) ||
5938              liveRegionsRoles.indexOf( role ) !== -1
5939          );
5940      },
5941  
5942      /**
5943       * Whether the body children are hidden from assistive technologies.
5944       *
5945       * @since 5.2.3
5946       */
5947      isBodyAriaHidden: false,
5948  
5949      /**
5950       * Stores an array of DOM elements that should be hidden from assistive
5951       * technologies, for example when the media modal dialog opens.
5952       *
5953       * @since 5.2.3
5954       */
5955      ariaHiddenElements: [],
5956  
5957      /**
5958       * Holds the jQuery collection of ARIA tabs.
5959       *
5960       * @since 5.3.0
5961       */
5962      tabs: $(),
5963  
5964      /**
5965       * Sets up tabs in an ARIA tabbed interface.
5966       *
5967       * @since 5.3.0
5968       *
5969       * @param {Object} event jQuery event object.
5970       *
5971       * @return {void}
5972       */
5973      setupAriaTabs: function() {
5974          this.tabs = this.$( '[role="tab"]' );
5975  
5976          // Set up initial attributes.
5977          this.tabs.attr( {
5978              'aria-selected': 'false',
5979              tabIndex: '-1'
5980          } );
5981  
5982          // Set up attributes on the initially active tab.
5983          this.tabs.filter( '.active' )
5984              .removeAttr( 'tabindex' )
5985              .attr( 'aria-selected', 'true' );
5986      },
5987  
5988      /**
5989       * Enables arrows navigation within the ARIA tabbed interface.
5990       *
5991       * @since 5.3.0
5992       *
5993       * @param {Object} event jQuery event object.
5994       *
5995       * @return {void}
5996       */
5997      tabsNavigation: function( event ) {
5998          var orientation = 'horizontal',
5999              keys = [ 32, 35, 36, 37, 38, 39, 40 ];
6000  
6001          // Return if not Spacebar, End, Home, or Arrow keys.
6002          if ( keys.indexOf( event.which ) === -1 ) {
6003              return;
6004          }
6005  
6006          // Determine navigation direction.
6007          if ( this.$el.attr( 'aria-orientation' ) === 'vertical' ) {
6008              orientation = 'vertical';
6009          }
6010  
6011          // Make Up and Down arrow keys do nothing with horizontal tabs.
6012          if ( orientation === 'horizontal' && [ 38, 40 ].indexOf( event.which ) !== -1 ) {
6013              return;
6014          }
6015  
6016          // Make Left and Right arrow keys do nothing with vertical tabs.
6017          if ( orientation === 'vertical' && [ 37, 39 ].indexOf( event.which ) !== -1 ) {
6018              return;
6019          }
6020  
6021          this.switchTabs( event, this.tabs );
6022      },
6023  
6024      /**
6025       * Switches tabs in the ARIA tabbed interface.
6026       *
6027       * @since 5.3.0
6028       *
6029       * @param {Object} event jQuery event object.
6030       *
6031       * @return {void}
6032       */
6033      switchTabs: function( event ) {
6034          var key   = event.which,
6035              index = this.tabs.index( $( event.target ) ),
6036              newIndex;
6037  
6038          switch ( key ) {
6039              // Space bar: Activate current targeted tab.
6040              case 32: {
6041                  this.activateTab( this.tabs[ index ] );
6042                  break;
6043              }
6044              // End key: Activate last tab.
6045              case 35: {
6046                  event.preventDefault();
6047                  this.activateTab( this.tabs[ this.tabs.length - 1 ] );
6048                  break;
6049              }
6050              // Home key: Activate first tab.
6051              case 36: {
6052                  event.preventDefault();
6053                  this.activateTab( this.tabs[ 0 ] );
6054                  break;
6055              }
6056              // Left and up keys: Activate previous tab.
6057              case 37:
6058              case 38: {
6059                  event.preventDefault();
6060                  newIndex = ( index - 1 ) < 0 ? this.tabs.length - 1 : index - 1;
6061                  this.activateTab( this.tabs[ newIndex ] );
6062                  break;
6063              }
6064              // Right and down keys: Activate next tab.
6065              case 39:
6066              case 40: {
6067                  event.preventDefault();
6068                  newIndex = ( index + 1 ) === this.tabs.length ? 0 : index + 1;
6069                  this.activateTab( this.tabs[ newIndex ] );
6070                  break;
6071              }
6072          }
6073      },
6074  
6075      /**
6076       * Sets a single tab to be focusable and semantically selected.
6077       *
6078       * @since 5.3.0
6079       *
6080       * @param {Object} tab The tab DOM element.
6081       *
6082       * @return {void}
6083       */
6084      activateTab: function( tab ) {
6085          if ( ! tab ) {
6086              return;
6087          }
6088  
6089          // The tab is a DOM element: no need for jQuery methods.
6090          tab.focus();
6091  
6092          // Handle automatic activation.
6093          if ( this.tabsAutomaticActivation ) {
6094              tab.removeAttribute( 'tabindex' );
6095              tab.setAttribute( 'aria-selected', 'true' );
6096              tab.click();
6097  
6098              return;
6099          }
6100  
6101          // Handle manual activation.
6102          $( tab ).on( 'click', function() {
6103              tab.removeAttribute( 'tabindex' );
6104              tab.setAttribute( 'aria-selected', 'true' );
6105          } );
6106       }
6107  });
6108  
6109  module.exports = FocusManager;
6110  
6111  
6112  /***/ },
6113  
6114  /***/ 1061
6115  (module) {
6116  
6117  /**
6118   * wp.media.view.Frame
6119   *
6120   * A frame is a composite view consisting of one or more regions and one or more
6121   * states.
6122   *
6123   * @memberOf wp.media.view
6124   *
6125   * @see wp.media.controller.State
6126   * @see wp.media.controller.Region
6127   *
6128   * @class
6129   * @augments wp.media.View
6130   * @augments wp.Backbone.View
6131   * @augments Backbone.View
6132   * @mixes wp.media.controller.StateMachine
6133   */
6134  var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{
6135      initialize: function() {
6136          _.defaults( this.options, {
6137              mode: [ 'select' ]
6138          });
6139          this._createRegions();
6140          this._createStates();
6141          this._createModes();
6142      },
6143  
6144      _createRegions: function() {
6145          // Clone the regions array.
6146          this.regions = this.regions ? this.regions.slice() : [];
6147  
6148          // Initialize regions.
6149          _.each( this.regions, function( region ) {
6150              this[ region ] = new wp.media.controller.Region({
6151                  view:     this,
6152                  id:       region,
6153                  selector: '.media-frame-' + region
6154              });
6155          }, this );
6156      },
6157      /**
6158       * Create the frame's states.
6159       *
6160       * @see wp.media.controller.State
6161       * @see wp.media.controller.StateMachine
6162       *
6163       * @fires wp.media.controller.State#ready
6164       */
6165      _createStates: function() {
6166          // Create the default `states` collection.
6167          this.states = new Backbone.Collection( null, {
6168              model: wp.media.controller.State
6169          });
6170  
6171          // Ensure states have a reference to the frame.
6172          this.states.on( 'add', function( model ) {
6173              model.frame = this;
6174              model.trigger('ready');
6175          }, this );
6176  
6177          if ( this.options.states ) {
6178              this.states.add( this.options.states );
6179          }
6180      },
6181  
6182      /**
6183       * A frame can be in a mode or multiple modes at one time.
6184       *
6185       * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
6186       */
6187      _createModes: function() {
6188          // Store active "modes" that the frame is in. Unrelated to region modes.
6189          this.activeModes = new Backbone.Collection();
6190          this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
6191  
6192          _.each( this.options.mode, function( mode ) {
6193              this.activateMode( mode );
6194          }, this );
6195      },
6196      /**
6197       * Reset all states on the frame to their defaults.
6198       *
6199       * @return {wp.media.view.Frame} Returns itself to allow chaining.
6200       */
6201      reset: function() {
6202          this.states.invoke( 'trigger', 'reset' );
6203          return this;
6204      },
6205      /**
6206       * Map activeMode collection events to the frame.
6207       */
6208      triggerModeEvents: function( model, collection, options ) {
6209          var collectionEvent,
6210              modeEventMap = {
6211                  add: 'activate',
6212                  remove: 'deactivate'
6213              },
6214              eventToTrigger;
6215          // Probably a better way to do this.
6216          _.each( options, function( value, key ) {
6217              if ( value ) {
6218                  collectionEvent = key;
6219              }
6220          } );
6221  
6222          if ( ! _.has( modeEventMap, collectionEvent ) ) {
6223              return;
6224          }
6225  
6226          eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
6227          this.trigger( eventToTrigger );
6228      },
6229      /**
6230       * Activate a mode on the frame.
6231       *
6232       * @param string mode Mode ID.
6233       * @return {this} Returns itself to allow chaining.
6234       */
6235      activateMode: function( mode ) {
6236          // Bail if the mode is already active.
6237          if ( this.isModeActive( mode ) ) {
6238              return;
6239          }
6240          this.activeModes.add( [ { id: mode } ] );
6241          // Add a CSS class to the frame so elements can be styled for the mode.
6242          this.$el.addClass( 'mode-' + mode );
6243  
6244          return this;
6245      },
6246      /**
6247       * Deactivate a mode on the frame.
6248       *
6249       * @param string mode Mode ID.
6250       * @return {this} Returns itself to allow chaining.
6251       */
6252      deactivateMode: function( mode ) {
6253          // Bail if the mode isn't active.
6254          if ( ! this.isModeActive( mode ) ) {
6255              return this;
6256          }
6257          this.activeModes.remove( this.activeModes.where( { id: mode } ) );
6258          this.$el.removeClass( 'mode-' + mode );
6259          /**
6260           * Frame mode deactivation event.
6261           *
6262           * @event wp.media.view.Frame#{mode}:deactivate
6263           */
6264          this.trigger( mode + ':deactivate' );
6265  
6266          return this;
6267      },
6268      /**
6269       * Check if a mode is enabled on the frame.
6270       *
6271       * @param string mode Mode ID.
6272       * @return bool
6273       */
6274      isModeActive: function( mode ) {
6275          return Boolean( this.activeModes.where( { id: mode } ).length );
6276      }
6277  });
6278  
6279  // Make the `Frame` a `StateMachine`.
6280  _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
6281  
6282  module.exports = Frame;
6283  
6284  
6285  /***/ },
6286  
6287  /***/ 5424
6288  (module) {
6289  
6290  var Select = wp.media.view.MediaFrame.Select,
6291      l10n = wp.media.view.l10n,
6292      ImageDetails;
6293  
6294  /**
6295   * wp.media.view.MediaFrame.ImageDetails
6296   *
6297   * A media frame for manipulating an image that's already been inserted
6298   * into a post.
6299   *
6300   * @memberOf wp.media.view.MediaFrame
6301   *
6302   * @class
6303   * @augments wp.media.view.MediaFrame.Select
6304   * @augments wp.media.view.MediaFrame
6305   * @augments wp.media.view.Frame
6306   * @augments wp.media.View
6307   * @augments wp.Backbone.View
6308   * @augments Backbone.View
6309   * @mixes wp.media.controller.StateMachine
6310   */
6311  ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{
6312      defaults: {
6313          id:      'image',
6314          url:     '',
6315          menu:    'image-details',
6316          content: 'image-details',
6317          toolbar: 'image-details',
6318          type:    'link',
6319          title:    l10n.imageDetailsTitle,
6320          priority: 120
6321      },
6322  
6323      initialize: function( options ) {
6324          this.image = new wp.media.model.PostImage( options.metadata );
6325          this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
6326          Select.prototype.initialize.apply( this, arguments );
6327      },
6328  
6329      bindHandlers: function() {
6330          Select.prototype.bindHandlers.apply( this, arguments );
6331          this.on( 'menu:create:image-details', this.createMenu, this );
6332          this.on( 'content:create:image-details', this.imageDetailsContent, this );
6333          this.on( 'content:render:edit-image', this.editImageContent, this );
6334          this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
6335          // Override the select toolbar.
6336          this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
6337      },
6338  
6339      createStates: function() {
6340          this.states.add([
6341              new wp.media.controller.ImageDetails({
6342                  image: this.image,
6343                  editable: false
6344              }),
6345              new wp.media.controller.ReplaceImage({
6346                  id: 'replace-image',
6347                  library: wp.media.query( { type: 'image' } ),
6348                  image: this.image,
6349                  multiple:  false,
6350                  title:     l10n.imageReplaceTitle,
6351                  toolbar: 'replace',
6352                  priority:  80,
6353                  displaySettings: true
6354              }),
6355              new wp.media.controller.EditImage( {
6356                  image: this.image,
6357                  selection: this.options.selection
6358              } )
6359          ]);
6360      },
6361  
6362      imageDetailsContent: function( options ) {
6363          options.view = new wp.media.view.ImageDetails({
6364              controller: this,
6365              model: this.state().image,
6366              attachment: this.state().image.attachment
6367          });
6368      },
6369  
6370      editImageContent: function() {
6371          var state = this.state(),
6372              model = state.get('image'),
6373              view;
6374  
6375          if ( ! model ) {
6376              return;
6377          }
6378  
6379          view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
6380  
6381          this.content.set( view );
6382  
6383          // After bringing in the frame, load the actual editor via an Ajax call.
6384          view.loadEditor();
6385  
6386      },
6387  
6388      renderImageDetailsToolbar: function() {
6389          this.toolbar.set( new wp.media.view.Toolbar({
6390              controller: this,
6391              items: {
6392                  select: {
6393                      style:    'primary',
6394                      text:     l10n.update,
6395                      priority: 80,
6396  
6397                      click: function() {
6398                          var controller = this.controller,
6399                              state = controller.state();
6400  
6401                          controller.close();
6402  
6403                          // Not sure if we want to use wp.media.string.image which will create a shortcode or
6404                          // perhaps wp.html.string to at least to build the <img />.
6405                          state.trigger( 'update', controller.image.toJSON() );
6406  
6407                          // Restore and reset the default state.
6408                          controller.setState( controller.options.state );
6409                          controller.reset();
6410                      }
6411                  }
6412              }
6413          }) );
6414      },
6415  
6416      renderReplaceImageToolbar: function() {
6417          var frame = this,
6418              lastState = frame.lastState(),
6419              previous = lastState && lastState.id;
6420  
6421          this.toolbar.set( new wp.media.view.Toolbar({
6422              controller: this,
6423              items: {
6424                  back: {
6425                      text:     l10n.back,
6426                      priority: 80,
6427                      click:    function() {
6428                          if ( previous ) {
6429                              frame.setState( previous );
6430                          } else {
6431                              frame.close();
6432                          }
6433                      }
6434                  },
6435  
6436                  replace: {
6437                      style:    'primary',
6438                      text:     l10n.replace,
6439                      priority: 20,
6440                      requires: { selection: true },
6441  
6442                      click: function() {
6443                          var controller = this.controller,
6444                              state = controller.state(),
6445                              selection = state.get( 'selection' ),
6446                              attachment = selection.single();
6447  
6448                          controller.close();
6449  
6450                          controller.image.changeAttachment( attachment, state.display( attachment ) );
6451  
6452                          // Not sure if we want to use wp.media.string.image which will create a shortcode or
6453                          // perhaps wp.html.string to at least to build the <img />.
6454                          state.trigger( 'replace', controller.image.toJSON() );
6455  
6456                          // Restore and reset the default state.
6457                          controller.setState( controller.options.state );
6458                          controller.reset();
6459                      }
6460                  }
6461              }
6462          }) );
6463      }
6464  
6465  });
6466  
6467  module.exports = ImageDetails;
6468  
6469  
6470  /***/ },
6471  
6472  /***/ 4274
6473  (module) {
6474  
6475  var Select = wp.media.view.MediaFrame.Select,
6476      Library = wp.media.controller.Library,
6477      l10n = wp.media.view.l10n,
6478      Post;
6479  
6480  /**
6481   * wp.media.view.MediaFrame.Post
6482   *
6483   * The frame for manipulating media on the Edit Post page.
6484   *
6485   * @memberOf wp.media.view.MediaFrame
6486   *
6487   * @class
6488   * @augments wp.media.view.MediaFrame.Select
6489   * @augments wp.media.view.MediaFrame
6490   * @augments wp.media.view.Frame
6491   * @augments wp.media.View
6492   * @augments wp.Backbone.View
6493   * @augments Backbone.View
6494   * @mixes wp.media.controller.StateMachine
6495   */
6496  Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{
6497      initialize: function() {
6498          this.counts = {
6499              audio: {
6500                  count: wp.media.view.settings.attachmentCounts.audio,
6501                  state: 'playlist'
6502              },
6503              video: {
6504                  count: wp.media.view.settings.attachmentCounts.video,
6505                  state: 'video-playlist'
6506              }
6507          };
6508  
6509          _.defaults( this.options, {
6510              multiple:  true,
6511              editing:   false,
6512              state:    'insert',
6513              metadata:  {}
6514          });
6515  
6516          // Call 'initialize' directly on the parent class.
6517          Select.prototype.initialize.apply( this, arguments );
6518          this.createIframeStates();
6519  
6520      },
6521  
6522      /**
6523       * Create the default states.
6524       */
6525      createStates: function() {
6526          var options = this.options;
6527  
6528          this.states.add([
6529              // Main states.
6530              new Library({
6531                  id:         'insert',
6532                  title:      l10n.insertMediaTitle,
6533                  priority:   20,
6534                  toolbar:    'main-insert',
6535                  filterable: 'all',
6536                  library:    wp.media.query( options.library ),
6537                  multiple:   options.multiple ? 'reset' : false,
6538                  editable:   true,
6539  
6540                  // If the user isn't allowed to edit fields,
6541                  // can they still edit it locally?
6542                  allowLocalEdits: true,
6543  
6544                  // Show the attachment display settings.
6545                  displaySettings: true,
6546                  // Update user settings when users adjust the
6547                  // attachment display settings.
6548                  displayUserSettings: true
6549              }),
6550  
6551              new Library({
6552                  id:         'gallery',
6553                  title:      l10n.createGalleryTitle,
6554                  priority:   40,
6555                  toolbar:    'main-gallery',
6556                  filterable: 'uploaded',
6557                  multiple:   'add',
6558                  editable:   false,
6559  
6560                  library:  wp.media.query( _.defaults({
6561                      type: 'image'
6562                  }, options.library ) )
6563              }),
6564  
6565              // Embed states.
6566              new wp.media.controller.Embed( { metadata: options.metadata } ),
6567  
6568              new wp.media.controller.EditImage( { model: options.editImage } ),
6569  
6570              // Gallery states.
6571              new wp.media.controller.GalleryEdit({
6572                  library: options.selection,
6573                  editing: options.editing,
6574                  menu:    'gallery'
6575              }),
6576  
6577              new wp.media.controller.GalleryAdd(),
6578  
6579              new Library({
6580                  id:         'playlist',
6581                  title:      l10n.createPlaylistTitle,
6582                  priority:   60,
6583                  toolbar:    'main-playlist',
6584                  filterable: 'uploaded',
6585                  multiple:   'add',
6586                  editable:   false,
6587  
6588                  library:  wp.media.query( _.defaults({
6589                      type: 'audio'
6590                  }, options.library ) )
6591              }),
6592  
6593              // Playlist states.
6594              new wp.media.controller.CollectionEdit({
6595                  type: 'audio',
6596                  collectionType: 'playlist',
6597                  title:          l10n.editPlaylistTitle,
6598                  SettingsView:   wp.media.view.Settings.Playlist,
6599                  library:        options.selection,
6600                  editing:        options.editing,
6601                  menu:           'playlist',
6602                  dragInfoText:   l10n.playlistDragInfo,
6603                  dragInfo:       false
6604              }),
6605  
6606              new wp.media.controller.CollectionAdd({
6607                  type: 'audio',
6608                  collectionType: 'playlist',
6609                  title: l10n.addToPlaylistTitle
6610              }),
6611  
6612              new Library({
6613                  id:         'video-playlist',
6614                  title:      l10n.createVideoPlaylistTitle,
6615                  priority:   60,
6616                  toolbar:    'main-video-playlist',
6617                  filterable: 'uploaded',
6618                  multiple:   'add',
6619                  editable:   false,
6620  
6621                  library:  wp.media.query( _.defaults({
6622                      type: 'video'
6623                  }, options.library ) )
6624              }),
6625  
6626              new wp.media.controller.CollectionEdit({
6627                  type: 'video',
6628                  collectionType: 'playlist',
6629                  title:          l10n.editVideoPlaylistTitle,
6630                  SettingsView:   wp.media.view.Settings.Playlist,
6631                  library:        options.selection,
6632                  editing:        options.editing,
6633                  menu:           'video-playlist',
6634                  dragInfoText:   l10n.videoPlaylistDragInfo,
6635                  dragInfo:       false
6636              }),
6637  
6638              new wp.media.controller.CollectionAdd({
6639                  type: 'video',
6640                  collectionType: 'playlist',
6641                  title: l10n.addToVideoPlaylistTitle
6642              })
6643          ]);
6644  
6645          if ( wp.media.view.settings.post.featuredImageId ) {
6646              this.states.add( new wp.media.controller.FeaturedImage() );
6647          }
6648      },
6649  
6650      bindHandlers: function() {
6651          var handlers, checkCounts;
6652  
6653          Select.prototype.bindHandlers.apply( this, arguments );
6654  
6655          this.on( 'activate', this.activate, this );
6656  
6657          // Only bother checking media type counts if one of the counts is zero.
6658          checkCounts = _.find( this.counts, function( type ) {
6659              return type.count === 0;
6660          } );
6661  
6662          if ( typeof checkCounts !== 'undefined' ) {
6663              this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
6664          }
6665  
6666          this.on( 'menu:create:gallery', this.createMenu, this );
6667          this.on( 'menu:create:playlist', this.createMenu, this );
6668          this.on( 'menu:create:video-playlist', this.createMenu, this );
6669          this.on( 'toolbar:create:main-insert', this.createToolbar, this );
6670          this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
6671          this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
6672          this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
6673          this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
6674          this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
6675  
6676          handlers = {
6677              menu: {
6678                  'default': 'mainMenu',
6679                  'gallery': 'galleryMenu',
6680                  'playlist': 'playlistMenu',
6681                  'video-playlist': 'videoPlaylistMenu'
6682              },
6683  
6684              content: {
6685                  'embed':          'embedContent',
6686                  'edit-image':     'editImageContent',
6687                  'edit-selection': 'editSelectionContent'
6688              },
6689  
6690              toolbar: {
6691                  'main-insert':      'mainInsertToolbar',
6692                  'main-gallery':     'mainGalleryToolbar',
6693                  'gallery-edit':     'galleryEditToolbar',
6694                  'gallery-add':      'galleryAddToolbar',
6695                  'main-playlist':    'mainPlaylistToolbar',
6696                  'playlist-edit':    'playlistEditToolbar',
6697                  'playlist-add':        'playlistAddToolbar',
6698                  'main-video-playlist': 'mainVideoPlaylistToolbar',
6699                  'video-playlist-edit': 'videoPlaylistEditToolbar',
6700                  'video-playlist-add': 'videoPlaylistAddToolbar'
6701              }
6702          };
6703  
6704          _.each( handlers, function( regionHandlers, region ) {
6705              _.each( regionHandlers, function( callback, handler ) {
6706                  this.on( region + ':render:' + handler, this[ callback ], this );
6707              }, this );
6708          }, this );
6709      },
6710  
6711      activate: function() {
6712          // Hide menu items for states tied to particular media types if there are no items.
6713          _.each( this.counts, function( type ) {
6714              if ( type.count < 1 ) {
6715                  this.menuItemVisibility( type.state, 'hide' );
6716              }
6717          }, this );
6718      },
6719  
6720      mediaTypeCounts: function( model, attr ) {
6721          if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
6722              this.counts[ attr ].count++;
6723              this.menuItemVisibility( this.counts[ attr ].state, 'show' );
6724          }
6725      },
6726  
6727      // Menus.
6728      /**
6729       * @param {wp.Backbone.View} view
6730       */
6731      mainMenu: function( view ) {
6732          view.set({
6733              'library-separator': new wp.media.View({
6734                  className:  'separator',
6735                  priority:   100,
6736                  attributes: {
6737                      role: 'presentation'
6738                  }
6739              })
6740          });
6741      },
6742  
6743      menuItemVisibility: function( state, visibility ) {
6744          var menu = this.menu.get();
6745          if ( visibility === 'hide' ) {
6746              menu.hide( state );
6747          } else if ( visibility === 'show' ) {
6748              menu.show( state );
6749          }
6750      },
6751      /**
6752       * @param {wp.Backbone.View} view
6753       */
6754      galleryMenu: function( view ) {
6755          var lastState = this.lastState(),
6756              previous = lastState && lastState.id,
6757              frame = this;
6758  
6759          view.set({
6760              cancel: {
6761                  text:     l10n.cancelGalleryTitle,
6762                  priority: 20,
6763                  click:    function() {
6764                      if ( previous ) {
6765                          frame.setState( previous );
6766                      } else {
6767                          frame.close();
6768                      }
6769  
6770                      // Move focus to the modal after canceling a Gallery.
6771                      this.controller.modal.focusManager.focus();
6772                  }
6773              },
6774              separateCancel: new wp.media.View({
6775                  className: 'separator',
6776                  priority: 40
6777              })
6778          });
6779      },
6780  
6781      playlistMenu: function( view ) {
6782          var lastState = this.lastState(),
6783              previous = lastState && lastState.id,
6784              frame = this;
6785  
6786          view.set({
6787              cancel: {
6788                  text:     l10n.cancelPlaylistTitle,
6789                  priority: 20,
6790                  click:    function() {
6791                      if ( previous ) {
6792                          frame.setState( previous );
6793                      } else {
6794                          frame.close();
6795                      }
6796  
6797                      // Move focus to the modal after canceling an Audio Playlist.
6798                      this.controller.modal.focusManager.focus();
6799                  }
6800              },
6801              separateCancel: new wp.media.View({
6802                  className: 'separator',
6803                  priority: 40
6804              })
6805          });
6806      },
6807  
6808      videoPlaylistMenu: function( view ) {
6809          var lastState = this.lastState(),
6810              previous = lastState && lastState.id,
6811              frame = this;
6812  
6813          view.set({
6814              cancel: {
6815                  text:     l10n.cancelVideoPlaylistTitle,
6816                  priority: 20,
6817                  click:    function() {
6818                      if ( previous ) {
6819                          frame.setState( previous );
6820                      } else {
6821                          frame.close();
6822                      }
6823  
6824                      // Move focus to the modal after canceling a Video Playlist.
6825                      this.controller.modal.focusManager.focus();
6826                  }
6827              },
6828              separateCancel: new wp.media.View({
6829                  className: 'separator',
6830                  priority: 40
6831              })
6832          });
6833      },
6834  
6835      // Content.
6836      embedContent: function() {
6837          var view = new wp.media.view.Embed({
6838              controller: this,
6839              model:      this.state()
6840          }).render();
6841  
6842          this.content.set( view );
6843      },
6844  
6845      editSelectionContent: function() {
6846          var state = this.state(),
6847              selection = state.get('selection'),
6848              view;
6849  
6850          view = new wp.media.view.AttachmentsBrowser({
6851              controller: this,
6852              collection: selection,
6853              selection:  selection,
6854              model:      state,
6855              sortable:   true,
6856              search:     false,
6857              date:       false,
6858              dragInfo:   true,
6859  
6860              AttachmentView: wp.media.view.Attachments.EditSelection
6861          }).render();
6862  
6863          view.toolbar.set( 'backToLibrary', {
6864              text:     l10n.returnToLibrary,
6865              priority: -100,
6866  
6867              click: function() {
6868                  this.controller.content.mode('browse');
6869                  // Move focus to the modal when jumping back from Edit Selection to Add Media view.
6870                  this.controller.modal.focusManager.focus();
6871              }
6872          });
6873  
6874          // Browse our library of attachments.
6875          this.content.set( view );
6876  
6877          // Trigger the controller to set focus.
6878          this.trigger( 'edit:selection', this );
6879      },
6880  
6881      editImageContent: function() {
6882          var image = this.state().get('image'),
6883              view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
6884  
6885          this.content.set( view );
6886  
6887          // After creating the wrapper view, load the actual editor via an Ajax call.
6888          view.loadEditor();
6889  
6890      },
6891  
6892      // Toolbars.
6893  
6894      /**
6895       * @param {wp.Backbone.View} view
6896       */
6897      selectionStatusToolbar: function( view ) {
6898          var editable = this.state().get('editable');
6899  
6900          view.set( 'selection', new wp.media.view.Selection({
6901              controller: this,
6902              collection: this.state().get('selection'),
6903              priority:   -40,
6904  
6905              // If the selection is editable, pass the callback to
6906              // switch the content mode.
6907              editable: editable && function() {
6908                  this.controller.content.mode('edit-selection');
6909              }
6910          }).render() );
6911      },
6912  
6913      /**
6914       * @param {wp.Backbone.View} view
6915       */
6916      mainInsertToolbar: function( view ) {
6917          var controller = this;
6918  
6919          this.selectionStatusToolbar( view );
6920  
6921          view.set( 'insert', {
6922              style:    'primary',
6923              priority: 80,
6924              text:     l10n.insertIntoPost,
6925              requires: { selection: true },
6926  
6927              /**
6928               * @ignore
6929               *
6930               * @fires wp.media.controller.State#insert
6931               */
6932              click: function() {
6933                  var state = controller.state(),
6934                      selection = state.get('selection');
6935  
6936                  controller.close();
6937                  state.trigger( 'insert', selection ).reset();
6938              }
6939          });
6940      },
6941  
6942      /**
6943       * @param {wp.Backbone.View} view
6944       */
6945      mainGalleryToolbar: function( view ) {
6946          var controller = this;
6947  
6948          this.selectionStatusToolbar( view );
6949  
6950          view.set( 'gallery', {
6951              style:    'primary',
6952              text:     l10n.createNewGallery,
6953              priority: 60,
6954              requires: { selection: true },
6955  
6956              click: function() {
6957                  var selection = controller.state().get('selection'),
6958                      edit = controller.state('gallery-edit'),
6959                      models = selection.where({ type: 'image' });
6960  
6961                  edit.set( 'library', new wp.media.model.Selection( models, {
6962                      props:    selection.props.toJSON(),
6963                      multiple: true
6964                  }) );
6965  
6966                  // Jump to Edit Gallery view.
6967                  this.controller.setState( 'gallery-edit' );
6968  
6969                  // Move focus to the modal after jumping to Edit Gallery view.
6970                  this.controller.modal.focusManager.focus();
6971              }
6972          });
6973      },
6974  
6975      mainPlaylistToolbar: function( view ) {
6976          var controller = this;
6977  
6978          this.selectionStatusToolbar( view );
6979  
6980          view.set( 'playlist', {
6981              style:    'primary',
6982              text:     l10n.createNewPlaylist,
6983              priority: 100,
6984              requires: { selection: true },
6985  
6986              click: function() {
6987                  var selection = controller.state().get('selection'),
6988                      edit = controller.state('playlist-edit'),
6989                      models = selection.where({ type: 'audio' });
6990  
6991                  edit.set( 'library', new wp.media.model.Selection( models, {
6992                      props:    selection.props.toJSON(),
6993                      multiple: true
6994                  }) );
6995  
6996                  // Jump to Edit Audio Playlist view.
6997                  this.controller.setState( 'playlist-edit' );
6998  
6999                  // Move focus to the modal after jumping to Edit Audio Playlist view.
7000                  this.controller.modal.focusManager.focus();
7001              }
7002          });
7003      },
7004  
7005      mainVideoPlaylistToolbar: function( view ) {
7006          var controller = this;
7007  
7008          this.selectionStatusToolbar( view );
7009  
7010          view.set( 'video-playlist', {
7011              style:    'primary',
7012              text:     l10n.createNewVideoPlaylist,
7013              priority: 100,
7014              requires: { selection: true },
7015  
7016              click: function() {
7017                  var selection = controller.state().get('selection'),
7018                      edit = controller.state('video-playlist-edit'),
7019                      models = selection.where({ type: 'video' });
7020  
7021                  edit.set( 'library', new wp.media.model.Selection( models, {
7022                      props:    selection.props.toJSON(),
7023                      multiple: true
7024                  }) );
7025  
7026                  // Jump to Edit Video Playlist view.
7027                  this.controller.setState( 'video-playlist-edit' );
7028  
7029                  // Move focus to the modal after jumping to Edit Video Playlist view.
7030                  this.controller.modal.focusManager.focus();
7031              }
7032          });
7033      },
7034  
7035      featuredImageToolbar: function( toolbar ) {
7036          this.createSelectToolbar( toolbar, {
7037              text:  l10n.setFeaturedImage,
7038              state: this.options.state
7039          });
7040      },
7041  
7042      mainEmbedToolbar: function( toolbar ) {
7043          toolbar.view = new wp.media.view.Toolbar.Embed({
7044              controller: this
7045          });
7046      },
7047  
7048      galleryEditToolbar: function() {
7049          var editing = this.state().get('editing');
7050          this.toolbar.set( new wp.media.view.Toolbar({
7051              controller: this,
7052              items: {
7053                  insert: {
7054                      style:    'primary',
7055                      text:     editing ? l10n.updateGallery : l10n.insertGallery,
7056                      priority: 80,
7057                      requires: { library: true, uploadingComplete: true },
7058  
7059                      /**
7060                       * @fires wp.media.controller.State#update
7061                       */
7062                      click: function() {
7063                          var controller = this.controller,
7064                              state = controller.state();
7065  
7066                          controller.close();
7067                          state.trigger( 'update', state.get('library') );
7068  
7069                          // Restore and reset the default state.
7070                          controller.setState( controller.options.state );
7071                          controller.reset();
7072                      }
7073                  }
7074              }
7075          }) );
7076      },
7077  
7078      galleryAddToolbar: function() {
7079          this.toolbar.set( new wp.media.view.Toolbar({
7080              controller: this,
7081              items: {
7082                  insert: {
7083                      style:    'primary',
7084                      text:     l10n.addToGallery,
7085                      priority: 80,
7086                      requires: { selection: true },
7087  
7088                      /**
7089                       * @fires wp.media.controller.State#reset
7090                       */
7091                      click: function() {
7092                          var controller = this.controller,
7093                              state = controller.state(),
7094                              edit = controller.state('gallery-edit');
7095  
7096                          edit.get('library').add( state.get('selection').models );
7097                          state.trigger('reset');
7098                          controller.setState('gallery-edit');
7099                          // Move focus to the modal when jumping back from Add to Gallery to Edit Gallery view.
7100                          this.controller.modal.focusManager.focus();
7101                      }
7102                  }
7103              }
7104          }) );
7105      },
7106  
7107      playlistEditToolbar: function() {
7108          var editing = this.state().get('editing');
7109          this.toolbar.set( new wp.media.view.Toolbar({
7110              controller: this,
7111              items: {
7112                  insert: {
7113                      style:    'primary',
7114                      text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
7115                      priority: 80,
7116                      requires: { library: true },
7117  
7118                      /**
7119                       * @fires wp.media.controller.State#update
7120                       */
7121                      click: function() {
7122                          var controller = this.controller,
7123                              state = controller.state();
7124  
7125                          controller.close();
7126                          state.trigger( 'update', state.get('library') );
7127  
7128                          // Restore and reset the default state.
7129                          controller.setState( controller.options.state );
7130                          controller.reset();
7131                      }
7132                  }
7133              }
7134          }) );
7135      },
7136  
7137      playlistAddToolbar: function() {
7138          this.toolbar.set( new wp.media.view.Toolbar({
7139              controller: this,
7140              items: {
7141                  insert: {
7142                      style:    'primary',
7143                      text:     l10n.addToPlaylist,
7144                      priority: 80,
7145                      requires: { selection: true },
7146  
7147                      /**
7148                       * @fires wp.media.controller.State#reset
7149                       */
7150                      click: function() {
7151                          var controller = this.controller,
7152                              state = controller.state(),
7153                              edit = controller.state('playlist-edit');
7154  
7155                          edit.get('library').add( state.get('selection').models );
7156                          state.trigger('reset');
7157                          controller.setState('playlist-edit');
7158                          // Move focus to the modal when jumping back from Add to Audio Playlist to Edit Audio Playlist view.
7159                          this.controller.modal.focusManager.focus();
7160                      }
7161                  }
7162              }
7163          }) );
7164      },
7165  
7166      videoPlaylistEditToolbar: function() {
7167          var editing = this.state().get('editing');
7168          this.toolbar.set( new wp.media.view.Toolbar({
7169              controller: this,
7170              items: {
7171                  insert: {
7172                      style:    'primary',
7173                      text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
7174                      priority: 140,
7175                      requires: { library: true },
7176  
7177                      click: function() {
7178                          var controller = this.controller,
7179                              state = controller.state(),
7180                              library = state.get('library');
7181  
7182                          library.type = 'video';
7183  
7184                          controller.close();
7185                          state.trigger( 'update', library );
7186  
7187                          // Restore and reset the default state.
7188                          controller.setState( controller.options.state );
7189                          controller.reset();
7190                      }
7191                  }
7192              }
7193          }) );
7194      },
7195  
7196      videoPlaylistAddToolbar: function() {
7197          this.toolbar.set( new wp.media.view.Toolbar({
7198              controller: this,
7199              items: {
7200                  insert: {
7201                      style:    'primary',
7202                      text:     l10n.addToVideoPlaylist,
7203                      priority: 140,
7204                      requires: { selection: true },
7205  
7206                      click: function() {
7207                          var controller = this.controller,
7208                              state = controller.state(),
7209                              edit = controller.state('video-playlist-edit');
7210  
7211                          edit.get('library').add( state.get('selection').models );
7212                          state.trigger('reset');
7213                          controller.setState('video-playlist-edit');
7214                          // Move focus to the modal when jumping back from Add to Video Playlist to Edit Video Playlist view.
7215                          this.controller.modal.focusManager.focus();
7216                      }
7217                  }
7218              }
7219          }) );
7220      }
7221  });
7222  
7223  module.exports = Post;
7224  
7225  
7226  /***/ },
7227  
7228  /***/ 455
7229  (module) {
7230  
7231  var MediaFrame = wp.media.view.MediaFrame,
7232      l10n = wp.media.view.l10n,
7233      Select;
7234  
7235  /**
7236   * wp.media.view.MediaFrame.Select
7237   *
7238   * A frame for selecting an item or items from the media library.
7239   *
7240   * @memberOf wp.media.view.MediaFrame
7241   *
7242   * @class
7243   * @augments wp.media.view.MediaFrame
7244   * @augments wp.media.view.Frame
7245   * @augments wp.media.View
7246   * @augments wp.Backbone.View
7247   * @augments Backbone.View
7248   * @mixes wp.media.controller.StateMachine
7249   */
7250  Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{
7251      initialize: function() {
7252          // Call 'initialize' directly on the parent class.
7253          MediaFrame.prototype.initialize.apply( this, arguments );
7254  
7255          _.defaults( this.options, {
7256              selection: [],
7257              library:   {},
7258              multiple:  false,
7259              state:    'library'
7260          });
7261  
7262          this.createSelection();
7263          this.createStates();
7264          this.bindHandlers();
7265      },
7266  
7267      /**
7268       * Attach a selection collection to the frame.
7269       *
7270       * A selection is a collection of attachments used for a specific purpose
7271       * by a media frame. e.g. Selecting an attachment (or many) to insert into
7272       * post content.
7273       *
7274       * @see media.model.Selection
7275       */
7276      createSelection: function() {
7277          var selection = this.options.selection;
7278  
7279          if ( ! (selection instanceof wp.media.model.Selection) ) {
7280              this.options.selection = new wp.media.model.Selection( selection, {
7281                  multiple: this.options.multiple
7282              });
7283          }
7284  
7285          this._selection = {
7286              attachments: new wp.media.model.Attachments(),
7287              difference: []
7288          };
7289      },
7290  
7291      editImageContent: function() {
7292          var image = this.state().get('image'),
7293              view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
7294  
7295          this.content.set( view );
7296  
7297          // After creating the wrapper view, load the actual editor via an Ajax call.
7298          view.loadEditor();
7299      },
7300  
7301      /**
7302       * Create the default states on the frame.
7303       */
7304      createStates: function() {
7305          var options = this.options;
7306  
7307          if ( this.options.states ) {
7308              return;
7309          }
7310  
7311          // Add the default states.
7312          this.states.add([
7313              // Main states.
7314              new wp.media.controller.Library({
7315                  library:   wp.media.query( options.library ),
7316                  multiple:  options.multiple,
7317                  title:     options.title,
7318                  priority:  20
7319              }),
7320              new wp.media.controller.EditImage( { model: options.editImage } )
7321          ]);
7322      },
7323  
7324      /**
7325       * Bind region mode event callbacks.
7326       *
7327       * @see media.controller.Region.render
7328       */
7329      bindHandlers: function() {
7330          this.on( 'router:create:browse', this.createRouter, this );
7331          this.on( 'router:render:browse', this.browseRouter, this );
7332          this.on( 'content:create:browse', this.browseContent, this );
7333          this.on( 'content:render:upload', this.uploadContent, this );
7334          this.on( 'toolbar:create:select', this.createSelectToolbar, this );
7335          this.on( 'content:render:edit-image', this.editImageContent, this );
7336      },
7337  
7338      /**
7339       * Render callback for the router region in the `browse` mode.
7340       *
7341       * @param {wp.media.view.Router} routerView
7342       */
7343      browseRouter: function( routerView ) {
7344          routerView.set({
7345              upload: {
7346                  text:     l10n.uploadFilesTitle,
7347                  priority: 20
7348              },
7349              browse: {
7350                  text:     l10n.mediaLibraryTitle,
7351                  priority: 40
7352              }
7353          });
7354      },
7355  
7356      /**
7357       * Render callback for the content region in the `browse` mode.
7358       *
7359       * @param {wp.media.controller.Region} contentRegion
7360       */
7361      browseContent: function( contentRegion ) {
7362          var state = this.state();
7363  
7364          this.$el.removeClass('hide-toolbar');
7365  
7366          // Browse our library of attachments.
7367          contentRegion.view = new wp.media.view.AttachmentsBrowser({
7368              controller: this,
7369              collection: state.get('library'),
7370              selection:  state.get('selection'),
7371              model:      state,
7372              sortable:   state.get('sortable'),
7373              search:     state.get('searchable'),
7374              filters:    state.get('filterable'),
7375              date:       state.get('date'),
7376              display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
7377              dragInfo:   state.get('dragInfo'),
7378  
7379              idealColumnWidth: state.get('idealColumnWidth'),
7380              suggestedWidth:   state.get('suggestedWidth'),
7381              suggestedHeight:  state.get('suggestedHeight'),
7382  
7383              AttachmentView: state.get('AttachmentView')
7384          });
7385      },
7386  
7387      /**
7388       * Render callback for the content region in the `upload` mode.
7389       */
7390      uploadContent: function() {
7391          this.$el.removeClass( 'hide-toolbar' );
7392          this.content.set( new wp.media.view.UploaderInline({
7393              controller: this
7394          }) );
7395      },
7396  
7397      /**
7398       * Toolbars
7399       *
7400       * @param {Object} toolbar
7401       * @param {Object} [options={}]
7402       * @this wp.media.controller.Region
7403       */
7404      createSelectToolbar: function( toolbar, options ) {
7405          options = options || this.options.button || {};
7406          options.controller = this;
7407  
7408          toolbar.view = new wp.media.view.Toolbar.Select( options );
7409      }
7410  });
7411  
7412  module.exports = Select;
7413  
7414  
7415  /***/ },
7416  
7417  /***/ 170
7418  (module) {
7419  
7420  /**
7421   * wp.media.view.Heading
7422   *
7423   * A reusable heading component for the media library
7424   *
7425   * Used to add accessibility friendly headers in the media library/modal.
7426   *
7427   * @class
7428   * @augments wp.media.View
7429   * @augments wp.Backbone.View
7430   * @augments Backbone.View
7431   */
7432  var Heading = wp.media.View.extend( {
7433      tagName: function() {
7434          return this.options.level || 'h1';
7435      },
7436      className: 'media-views-heading',
7437  
7438      initialize: function() {
7439  
7440          if ( this.options.className ) {
7441              this.$el.addClass( this.options.className );
7442          }
7443  
7444          this.text = this.options.text;
7445      },
7446  
7447      render: function() {
7448          this.$el.html( this.text );
7449          return this;
7450      }
7451  } );
7452  
7453  module.exports = Heading;
7454  
7455  
7456  /***/ },
7457  
7458  /***/ 1982
7459  (module) {
7460  
7461  /**
7462   * wp.media.view.Iframe
7463   *
7464   * @memberOf wp.media.view
7465   *
7466   * @class
7467   * @augments wp.media.View
7468   * @augments wp.Backbone.View
7469   * @augments Backbone.View
7470   */
7471  var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{
7472      className: 'media-iframe',
7473      /**
7474       * @return {wp.media.view.Iframe} Returns itself to allow chaining.
7475       */
7476      render: function() {
7477          this.views.detach();
7478          this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
7479          this.views.render();
7480          return this;
7481      }
7482  });
7483  
7484  module.exports = Iframe;
7485  
7486  
7487  /***/ },
7488  
7489  /***/ 2650
7490  (module) {
7491  
7492  var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
7493      $ = jQuery,
7494      ImageDetails;
7495  
7496  /**
7497   * wp.media.view.ImageDetails
7498   *
7499   * @memberOf wp.media.view
7500   *
7501   * @class
7502   * @augments wp.media.view.Settings.AttachmentDisplay
7503   * @augments wp.media.view.Settings
7504   * @augments wp.media.View
7505   * @augments wp.Backbone.View
7506   * @augments Backbone.View
7507   */
7508  ImageDetails = AttachmentDisplay.extend(/** @lends wp.media.view.ImageDetails.prototype */{
7509      className: 'image-details',
7510      template:  wp.template('image-details'),
7511      events: _.defaults( AttachmentDisplay.prototype.events, {
7512          'click .edit-attachment': 'editAttachment',
7513          'click .replace-attachment': 'replaceAttachment',
7514          'click .advanced-toggle': 'onToggleAdvanced',
7515          'change [data-setting="customWidth"]': 'onCustomSize',
7516          'change [data-setting="customHeight"]': 'onCustomSize',
7517          'keyup [data-setting="customWidth"]': 'onCustomSize',
7518          'keyup [data-setting="customHeight"]': 'onCustomSize'
7519      } ),
7520      initialize: function() {
7521          // Used in AttachmentDisplay.prototype.updateLinkTo.
7522          this.options.attachment = this.model.attachment;
7523          this.listenTo( this.model, 'change:url', this.updateUrl );
7524          this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
7525          this.listenTo( this.model, 'change:size', this.toggleCustomSize );
7526  
7527          AttachmentDisplay.prototype.initialize.apply( this, arguments );
7528      },
7529  
7530      prepare: function() {
7531          var attachment = false;
7532  
7533          if ( this.model.attachment ) {
7534              attachment = this.model.attachment.toJSON();
7535          }
7536          return _.defaults({
7537              model: this.model.toJSON(),
7538              attachment: attachment
7539          }, this.options );
7540      },
7541  
7542      render: function() {
7543          var args = arguments;
7544  
7545          if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
7546              this.model.dfd
7547                  .done( _.bind( function() {
7548                      AttachmentDisplay.prototype.render.apply( this, args );
7549                      this.postRender();
7550                  }, this ) )
7551                  .fail( _.bind( function() {
7552                      this.model.attachment = false;
7553                      AttachmentDisplay.prototype.render.apply( this, args );
7554                      this.postRender();
7555                  }, this ) );
7556          } else {
7557              AttachmentDisplay.prototype.render.apply( this, arguments );
7558              this.postRender();
7559          }
7560  
7561          return this;
7562      },
7563  
7564      postRender: function() {
7565          setTimeout( _.bind( this.scrollToTop, this ), 10 );
7566          this.toggleLinkSettings();
7567          if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
7568              this.toggleAdvanced( true );
7569          }
7570          this.trigger( 'post-render' );
7571      },
7572  
7573      scrollToTop: function() {
7574          this.$( '.embed-media-settings' ).scrollTop( 0 );
7575      },
7576  
7577      updateUrl: function() {
7578          this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
7579          this.$( '.url' ).val( this.model.get( 'url' ) );
7580      },
7581  
7582      toggleLinkSettings: function() {
7583          if ( this.model.get( 'link' ) === 'none' ) {
7584              this.$( '.link-settings' ).addClass('hidden');
7585          } else {
7586              this.$( '.link-settings' ).removeClass('hidden');
7587          }
7588      },
7589  
7590      toggleCustomSize: function() {
7591          if ( this.model.get( 'size' ) !== 'custom' ) {
7592              this.$( '.custom-size' ).addClass('hidden');
7593          } else {
7594              this.$( '.custom-size' ).removeClass('hidden');
7595          }
7596      },
7597  
7598      onCustomSize: function( event ) {
7599          var dimension = $( event.target ).data('setting'),
7600              num = $( event.target ).val(),
7601              value;
7602  
7603          // Ignore bogus input.
7604          if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
7605              event.preventDefault();
7606              return;
7607          }
7608  
7609          if ( dimension === 'customWidth' ) {
7610              value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );
7611              this.model.set( 'customHeight', value, { silent: true } );
7612              this.$( '[data-setting="customHeight"]' ).val( value );
7613          } else {
7614              value = Math.round( this.model.get( 'aspectRatio' ) * num );
7615              this.model.set( 'customWidth', value, { silent: true  } );
7616              this.$( '[data-setting="customWidth"]' ).val( value );
7617          }
7618      },
7619  
7620      onToggleAdvanced: function( event ) {
7621          event.preventDefault();
7622          this.toggleAdvanced();
7623      },
7624  
7625      toggleAdvanced: function( show ) {
7626          var $advanced = this.$el.find( '.advanced-section' ),
7627              mode;
7628  
7629          if ( $advanced.hasClass('advanced-visible') || show === false ) {
7630              $advanced.removeClass('advanced-visible');
7631              $advanced.find('.advanced-settings').addClass('hidden');
7632              mode = 'hide';
7633          } else {
7634              $advanced.addClass('advanced-visible');
7635              $advanced.find('.advanced-settings').removeClass('hidden');
7636              mode = 'show';
7637          }
7638  
7639          window.setUserSetting( 'advImgDetails', mode );
7640      },
7641  
7642      editAttachment: function( event ) {
7643          var editState = this.controller.states.get( 'edit-image' );
7644  
7645          if ( window.imageEdit && editState ) {
7646              event.preventDefault();
7647              editState.set( 'image', this.model.attachment );
7648              this.controller.setState( 'edit-image' );
7649          }
7650      },
7651  
7652      replaceAttachment: function( event ) {
7653          event.preventDefault();
7654          this.controller.setState( 'replace-image' );
7655      }
7656  });
7657  
7658  module.exports = ImageDetails;
7659  
7660  
7661  /***/ },
7662  
7663  /***/ 4338
7664  (module) {
7665  
7666  /**
7667   * wp.media.view.Label
7668   *
7669   * @memberOf wp.media.view
7670   *
7671   * @class
7672   * @augments wp.media.View
7673   * @augments wp.Backbone.View
7674   * @augments Backbone.View
7675   */
7676  var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{
7677      tagName: 'label',
7678  
7679      initialize: function() {
7680          this.value = this.options.value;
7681      },
7682  
7683      render: function() {
7684          this.$el.html( this.value );
7685  
7686          return this;
7687      }
7688  });
7689  
7690  module.exports = Label;
7691  
7692  
7693  /***/ },
7694  
7695  /***/ 2836
7696  (module) {
7697  
7698  var Frame = wp.media.view.Frame,
7699      l10n = wp.media.view.l10n,
7700      $ = jQuery,
7701      MediaFrame;
7702  
7703  /**
7704   * wp.media.view.MediaFrame
7705   *
7706   * The frame used to create the media modal.
7707   *
7708   * @memberOf wp.media.view
7709   *
7710   * @class
7711   * @augments wp.media.view.Frame
7712   * @augments wp.media.View
7713   * @augments wp.Backbone.View
7714   * @augments Backbone.View
7715   * @mixes wp.media.controller.StateMachine
7716   */
7717  MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{
7718      className: 'media-frame',
7719      template:  wp.template('media-frame'),
7720      regions:   ['menu','title','content','toolbar','router'],
7721  
7722      events: {
7723          'click .media-frame-menu-toggle': 'toggleMenu'
7724      },
7725  
7726      /**
7727       * @constructs
7728       */
7729      initialize: function() {
7730          Frame.prototype.initialize.apply( this, arguments );
7731  
7732          _.defaults( this.options, {
7733              title:    l10n.mediaFrameDefaultTitle,
7734              modal:    true,
7735              uploader: true
7736          });
7737  
7738          // Ensure core UI is enabled.
7739          this.$el.addClass('wp-core-ui');
7740  
7741          // Initialize modal container view.
7742          if ( this.options.modal ) {
7743              this.modal = new wp.media.view.Modal({
7744                  controller: this,
7745                  title:      this.options.title
7746              });
7747  
7748              this.modal.content( this );
7749          }
7750  
7751          // Force the uploader off if the upload limit has been exceeded or
7752          // if the browser isn't supported.
7753          if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
7754              this.options.uploader = false;
7755          }
7756  
7757          // Initialize window-wide uploader.
7758          if ( this.options.uploader ) {
7759              this.uploader = new wp.media.view.UploaderWindow({
7760                  controller: this,
7761                  uploader: {
7762                      dropzone:  this.modal ? this.modal.$el : this.$el,
7763                      container: this.$el
7764                  }
7765              });
7766              this.views.set( '.media-frame-uploader', this.uploader );
7767          }
7768  
7769          this.on( 'attach', _.bind( this.views.ready, this.views ), this );
7770  
7771          // Bind default title creation.
7772          this.on( 'title:create:default', this.createTitle, this );
7773          this.title.mode('default');
7774  
7775          // Bind default menu.
7776          this.on( 'menu:create:default', this.createMenu, this );
7777  
7778          // Set the menu ARIA tab panel attributes when the modal opens.
7779          this.on( 'open', this.setMenuTabPanelAriaAttributes, this );
7780          // Set the router ARIA tab panel attributes when the modal opens.
7781          this.on( 'open', this.setRouterTabPanelAriaAttributes, this );
7782  
7783          // Update the menu ARIA tab panel attributes when the content updates.
7784          this.on( 'content:render', this.setMenuTabPanelAriaAttributes, this );
7785          // Update the router ARIA tab panel attributes when the content updates.
7786          this.on( 'content:render', this.setRouterTabPanelAriaAttributes, this );
7787      },
7788  
7789      /**
7790       * Sets the attributes to be used on the menu ARIA tab panel.
7791       *
7792       * @since 5.3.0
7793       *
7794       * @return {void}
7795       */
7796      setMenuTabPanelAriaAttributes: function() {
7797          var stateId = this.state().get( 'id' ),
7798              tabPanelEl = this.$el.find( '.media-frame-tab-panel' ),
7799              ariaLabelledby;
7800  
7801          tabPanelEl.removeAttr( 'role aria-labelledby' );
7802  
7803          if ( this.state().get( 'menu' ) && this.menuView && this.menuView.isVisible ) {
7804              ariaLabelledby = 'menu-item-' + stateId;
7805  
7806              // Set the tab panel attributes only if the tabs are visible.
7807              tabPanelEl
7808                  .attr( {
7809                      role: 'tabpanel',
7810                      'aria-labelledby': ariaLabelledby,
7811                  } );
7812          }
7813      },
7814  
7815      /**
7816       * Sets the attributes to be used on the router ARIA tab panel.
7817       *
7818       * @since 5.3.0
7819       *
7820       * @return {void}
7821       */
7822      setRouterTabPanelAriaAttributes: function() {
7823          var tabPanelEl = this.$el.find( '.media-frame-content' ),
7824              ariaLabelledby;
7825  
7826          tabPanelEl.removeAttr( 'role aria-labelledby' );
7827  
7828          // Set the tab panel attributes only if the tabs are visible.
7829          if ( this.state().get( 'router' ) && this.routerView && this.routerView.isVisible && this.content._mode ) {
7830              ariaLabelledby = 'menu-item-' + this.content._mode;
7831  
7832              tabPanelEl
7833                  .attr( {
7834                      role: 'tabpanel',
7835                      'aria-labelledby': ariaLabelledby,
7836                  } );
7837          }
7838      },
7839  
7840      /**
7841       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
7842       */
7843      render: function() {
7844          // Activate the default state if no active state exists.
7845          if ( ! this.state() && this.options.state ) {
7846              this.setState( this.options.state );
7847          }
7848          /**
7849           * call 'render' directly on the parent class
7850           */
7851          return Frame.prototype.render.apply( this, arguments );
7852      },
7853      /**
7854       * @param {Object} title
7855       * @this wp.media.controller.Region
7856       */
7857      createTitle: function( title ) {
7858          title.view = new wp.media.View({
7859              controller: this,
7860              tagName: 'h1'
7861          });
7862      },
7863      /**
7864       * @param {Object} menu
7865       * @this wp.media.controller.Region
7866       */
7867      createMenu: function( menu ) {
7868          menu.view = new wp.media.view.Menu({
7869              controller: this,
7870  
7871              attributes: {
7872                  role:               'tablist',
7873                  'aria-orientation': 'vertical'
7874              }
7875          });
7876  
7877          this.menuView = menu.view;
7878      },
7879  
7880      toggleMenu: function( event ) {
7881          var menu = this.$el.find( '.media-menu' );
7882  
7883          menu.toggleClass( 'visible' );
7884          $( event.target ).attr( 'aria-expanded', menu.hasClass( 'visible' ) );
7885      },
7886  
7887      /**
7888       * @param {Object} toolbar
7889       * @this wp.media.controller.Region
7890       */
7891      createToolbar: function( toolbar ) {
7892          toolbar.view = new wp.media.view.Toolbar({
7893              controller: this
7894          });
7895      },
7896      /**
7897       * @param {Object} router
7898       * @this wp.media.controller.Region
7899       */
7900      createRouter: function( router ) {
7901          router.view = new wp.media.view.Router({
7902              controller: this,
7903  
7904              attributes: {
7905                  role:               'tablist',
7906                  'aria-orientation': 'horizontal'
7907              }
7908          });
7909  
7910          this.routerView = router.view;
7911      },
7912      /**
7913       * @param {Object} options
7914       */
7915      createIframeStates: function( options ) {
7916          var settings = wp.media.view.settings,
7917              tabs = settings.tabs,
7918              tabUrl = settings.tabUrl,
7919              $postId;
7920  
7921          if ( ! tabs || ! tabUrl ) {
7922              return;
7923          }
7924  
7925          // Add the post ID to the tab URL if it exists.
7926          $postId = $('#post_ID');
7927          if ( $postId.length ) {
7928              tabUrl += '&post_id=' + $postId.val();
7929          }
7930  
7931          // Generate the tab states.
7932          _.each( tabs, function( title, id ) {
7933              this.state( 'iframe:' + id ).set( _.defaults({
7934                  tab:     id,
7935                  src:     tabUrl + '&tab=' + id,
7936                  title:   title,
7937                  content: 'iframe',
7938                  menu:    'default'
7939              }, options ) );
7940          }, this );
7941  
7942          this.on( 'content:create:iframe', this.iframeContent, this );
7943          this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
7944          this.on( 'menu:render:default', this.iframeMenu, this );
7945          this.on( 'open', this.hijackThickbox, this );
7946          this.on( 'close', this.restoreThickbox, this );
7947      },
7948  
7949      /**
7950       * @param {Object} content
7951       * @this wp.media.controller.Region
7952       */
7953      iframeContent: function( content ) {
7954          this.$el.addClass('hide-toolbar');
7955          content.view = new wp.media.view.Iframe({
7956              controller: this
7957          });
7958      },
7959  
7960      iframeContentCleanup: function() {
7961          this.$el.removeClass('hide-toolbar');
7962      },
7963  
7964      iframeMenu: function( view ) {
7965          var views = {};
7966  
7967          if ( ! view ) {
7968              return;
7969          }
7970  
7971          _.each( wp.media.view.settings.tabs, function( title, id ) {
7972              views[ 'iframe:' + id ] = {
7973                  text: this.state( 'iframe:' + id ).get('title'),
7974                  priority: 200
7975              };
7976          }, this );
7977  
7978          view.set( views );
7979      },
7980  
7981      hijackThickbox: function() {
7982          var frame = this;
7983  
7984          if ( ! window.tb_remove || this._tb_remove ) {
7985              return;
7986          }
7987  
7988          this._tb_remove = window.tb_remove;
7989          window.tb_remove = function() {
7990              frame.close();
7991              frame.reset();
7992              frame.setState( frame.options.state );
7993              frame._tb_remove.call( window );
7994          };
7995      },
7996  
7997      restoreThickbox: function() {
7998          if ( ! this._tb_remove ) {
7999              return;
8000          }
8001  
8002          window.tb_remove = this._tb_remove;
8003          delete this._tb_remove;
8004      }
8005  });
8006  
8007  // Map some of the modal's methods to the frame.
8008  _.each(['open','close','attach','detach','escape'], function( method ) {
8009      /**
8010       * @function open
8011       * @memberOf wp.media.view.MediaFrame
8012       * @instance
8013       *
8014       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
8015       */
8016      /**
8017       * @function close
8018       * @memberOf wp.media.view.MediaFrame
8019       * @instance
8020       *
8021       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
8022       */
8023      /**
8024       * @function attach
8025       * @memberOf wp.media.view.MediaFrame
8026       * @instance
8027       *
8028       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
8029       */
8030      /**
8031       * @function detach
8032       * @memberOf wp.media.view.MediaFrame
8033       * @instance
8034       *
8035       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
8036       */
8037      /**
8038       * @function escape
8039       * @memberOf wp.media.view.MediaFrame
8040       * @instance
8041       *
8042       * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
8043       */
8044      MediaFrame.prototype[ method ] = function() {
8045          if ( this.modal ) {
8046              this.modal[ method ].apply( this.modal, arguments );
8047          }
8048          return this;
8049      };
8050  });
8051  
8052  module.exports = MediaFrame;
8053  
8054  
8055  /***/ },
8056  
8057  /***/ 9013
8058  (module) {
8059  
8060  var MenuItem;
8061  
8062  /**
8063   * wp.media.view.MenuItem
8064   *
8065   * @memberOf wp.media.view
8066   *
8067   * @class
8068   * @augments wp.media.View
8069   * @augments wp.Backbone.View
8070   * @augments Backbone.View
8071   */
8072  MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{
8073      tagName:   'button',
8074      className: 'media-menu-item',
8075  
8076      attributes: {
8077          type: 'button',
8078          role: 'tab'
8079      },
8080  
8081      events: {
8082          'click': '_click'
8083      },
8084  
8085      /**
8086       * Allows to override the click event.
8087       */
8088      _click: function() {
8089          var clickOverride = this.options.click;
8090  
8091          if ( clickOverride ) {
8092              clickOverride.call( this );
8093          } else {
8094              this.click();
8095          }
8096      },
8097  
8098      click: function() {
8099          var state = this.options.state;
8100  
8101          if ( state ) {
8102              this.controller.setState( state );
8103              // Toggle the menu visibility in the responsive view.
8104              this.views.parent.$el.removeClass( 'visible' ); // @todo Or hide on any click, see below.
8105          }
8106      },
8107  
8108      /**
8109       * @return {wp.media.view.MenuItem} returns itself to allow chaining.
8110       */
8111      render: function() {
8112          var options = this.options,
8113              menuProperty = options.state || options.contentMode;
8114  
8115          if ( options.text ) {
8116              this.$el.text( options.text );
8117          } else if ( options.html ) {
8118              this.$el.html( options.html );
8119          }
8120  
8121          // Set the menu item ID based on the frame state associated to the menu item.
8122          this.$el.attr( 'id', 'menu-item-' + menuProperty );
8123  
8124          return this;
8125      }
8126  });
8127  
8128  module.exports = MenuItem;
8129  
8130  
8131  /***/ },
8132  
8133  /***/ 1
8134  (module) {
8135  
8136  var MenuItem = wp.media.view.MenuItem,
8137      PriorityList = wp.media.view.PriorityList,
8138      Menu;
8139  
8140  /**
8141   * wp.media.view.Menu
8142   *
8143   * @memberOf wp.media.view
8144   *
8145   * @class
8146   * @augments wp.media.view.PriorityList
8147   * @augments wp.media.View
8148   * @augments wp.Backbone.View
8149   * @augments Backbone.View
8150   */
8151  Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{
8152      tagName:   'div',
8153      className: 'media-menu',
8154      property:  'state',
8155      ItemView:  MenuItem,
8156      region:    'menu',
8157  
8158      attributes: {
8159          role:               'tablist',
8160          'aria-orientation': 'horizontal'
8161      },
8162  
8163      initialize: function() {
8164          this._views = {};
8165  
8166          this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
8167          delete this.options.views;
8168  
8169          if ( ! this.options.silent ) {
8170              this.render();
8171          }
8172  
8173          // Initialize the Focus Manager.
8174          this.focusManager = new wp.media.view.FocusManager( {
8175              el:   this.el,
8176              mode: 'tabsNavigation'
8177          } );
8178  
8179          // The menu is always rendered and can be visible or hidden on some frames.
8180          this.isVisible = true;
8181      },
8182  
8183      /**
8184       * @param {Object} options
8185       * @param {string} id
8186       * @return {wp.media.View}
8187       */
8188      toView: function( options, id ) {
8189          options = options || {};
8190          options[ this.property ] = options[ this.property ] || id;
8191          return new this.ItemView( options ).render();
8192      },
8193  
8194      ready: function() {
8195          /**
8196           * call 'ready' directly on the parent class
8197           */
8198          PriorityList.prototype.ready.apply( this, arguments );
8199          this.visibility();
8200  
8201          // Set up aria tabs initial attributes.
8202          this.focusManager.setupAriaTabs();
8203      },
8204  
8205      set: function() {
8206          /**
8207           * call 'set' directly on the parent class
8208           */
8209          PriorityList.prototype.set.apply( this, arguments );
8210          this.visibility();
8211      },
8212  
8213      unset: function() {
8214          /**
8215           * call 'unset' directly on the parent class
8216           */
8217          PriorityList.prototype.unset.apply( this, arguments );
8218          this.visibility();
8219      },
8220  
8221      visibility: function() {
8222          var region = this.region,
8223              view = this.controller[ region ].get(),
8224              views = this.views.get(),
8225              hide = ! views || views.length < 2;
8226  
8227          if ( this === view ) {
8228              // Flag this menu as hidden or visible.
8229              this.isVisible = ! hide;
8230              // Set or remove a CSS class to hide the menu.
8231              this.controller.$el.toggleClass( 'hide-' + region, hide );
8232          }
8233      },
8234      /**
8235       * @param {string} id
8236       */
8237      select: function( id ) {
8238          var view = this.get( id );
8239  
8240          if ( ! view ) {
8241              return;
8242          }
8243  
8244          this.deselect();
8245          view.$el.addClass('active');
8246  
8247          // Set up again the aria tabs initial attributes after the menu updates.
8248          this.focusManager.setupAriaTabs();
8249      },
8250  
8251      deselect: function() {
8252          this.$el.children().removeClass('active');
8253      },
8254  
8255      hide: function( id ) {
8256          var view = this.get( id );
8257  
8258          if ( ! view ) {
8259              return;
8260          }
8261  
8262          view.$el.addClass('hidden');
8263      },
8264  
8265      show: function( id ) {
8266          var view = this.get( id );
8267  
8268          if ( ! view ) {
8269              return;
8270          }
8271  
8272          view.$el.removeClass('hidden');
8273      }
8274  });
8275  
8276  module.exports = Menu;
8277  
8278  
8279  /***/ },
8280  
8281  /***/ 2621
8282  (module) {
8283  
8284  var $ = jQuery,
8285      Modal;
8286  
8287  /**
8288   * wp.media.view.Modal
8289   *
8290   * A modal view, which the media modal uses as its default container.
8291   *
8292   * @memberOf wp.media.view
8293   *
8294   * @class
8295   * @augments wp.media.View
8296   * @augments wp.Backbone.View
8297   * @augments Backbone.View
8298   */
8299  Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{
8300      tagName:  'div',
8301      template: wp.template('media-modal'),
8302  
8303      events: {
8304          'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
8305          'keydown': 'keydown'
8306      },
8307  
8308      clickedOpenerEl: null,
8309  
8310      initialize: function() {
8311          _.defaults( this.options, {
8312              container:      document.body,
8313              title:          '',
8314              propagate:      true,
8315              hasCloseButton: true
8316          });
8317  
8318          this.focusManager = new wp.media.view.FocusManager({
8319              el: this.el
8320          });
8321      },
8322      /**
8323       * @return {Object}
8324       */
8325      prepare: function() {
8326          return {
8327              title:          this.options.title,
8328              hasCloseButton: this.options.hasCloseButton
8329          };
8330      },
8331  
8332      /**
8333       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8334       */
8335      attach: function() {
8336          if ( this.views.attached ) {
8337              return this;
8338          }
8339  
8340          if ( ! this.views.rendered ) {
8341              this.render();
8342          }
8343  
8344          this.$el.appendTo( this.options.container );
8345  
8346          // Manually mark the view as attached and trigger ready.
8347          this.views.attached = true;
8348          this.views.ready();
8349  
8350          return this.propagate('attach');
8351      },
8352  
8353      /**
8354       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8355       */
8356      detach: function() {
8357          if ( this.$el.is(':visible') ) {
8358              this.close();
8359          }
8360  
8361          this.$el.detach();
8362          this.views.attached = false;
8363          return this.propagate('detach');
8364      },
8365  
8366      /**
8367       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8368       */
8369      open: function() {
8370          var $el = this.$el,
8371              mceEditor;
8372  
8373          if ( $el.is(':visible') ) {
8374              return this;
8375          }
8376  
8377          this.clickedOpenerEl = document.activeElement;
8378  
8379          if ( ! this.views.attached ) {
8380              this.attach();
8381          }
8382  
8383          // Disable page scrolling.
8384          $( 'body' ).addClass( 'modal-open' );
8385  
8386          $el.show();
8387  
8388          // Try to close the onscreen keyboard.
8389          if ( 'ontouchend' in document ) {
8390              if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
8391                  mceEditor.iframeElement.focus();
8392                  mceEditor.iframeElement.blur();
8393  
8394                  setTimeout( function() {
8395                      mceEditor.iframeElement.blur();
8396                  }, 100 );
8397              }
8398          }
8399  
8400          // Set initial focus on the content instead of this view element, to avoid page scrolling.
8401          this.$( '.media-modal' ).trigger( 'focus' );
8402  
8403          // Hide the page content from assistive technologies.
8404          this.focusManager.setAriaHiddenOnBodyChildren( $el );
8405  
8406          return this.propagate('open');
8407      },
8408  
8409      /**
8410       * @param {Object} options
8411       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8412       */
8413      close: function( options ) {
8414          if ( ! this.views.attached || ! this.$el.is(':visible') ) {
8415              return this;
8416          }
8417  
8418          // Pause current audio/video even after closing the modal.
8419          $( '.mejs-pause button' ).trigger( 'click' );
8420  
8421          // Enable page scrolling.
8422          $( 'body' ).removeClass( 'modal-open' );
8423  
8424          // Hide the modal element by adding display:none.
8425          this.$el.hide();
8426  
8427          /*
8428           * Make visible again to assistive technologies all body children that
8429           * have been made hidden when the modal opened.
8430           */
8431          this.focusManager.removeAriaHiddenFromBodyChildren();
8432  
8433          // Move focus back in useful location once modal is closed.
8434          if ( null !== this.clickedOpenerEl ) {
8435              // Move focus back to the element that opened the modal.
8436              this.clickedOpenerEl.focus();
8437          } else {
8438              // Fallback to the admin page main element.
8439              $( '#wpbody-content' )
8440                  .attr( 'tabindex', '-1' )
8441                  .trigger( 'focus' );
8442          }
8443  
8444          this.propagate('close');
8445  
8446          if ( options && options.escape ) {
8447              this.propagate('escape');
8448          }
8449  
8450          return this;
8451      },
8452      /**
8453       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8454       */
8455      escape: function() {
8456          return this.close({ escape: true });
8457      },
8458      /**
8459       * @param {Object} event
8460       */
8461      escapeHandler: function( event ) {
8462          event.preventDefault();
8463          this.escape();
8464      },
8465  
8466      /**
8467       * Handles the selection of attachments when the command or control key is pressed with the enter key.
8468       *
8469       * @since 6.7
8470       *
8471       * @param {Object} event The keydown event object.
8472       */
8473      selectHandler: function( event ) {
8474          var selection = this.controller.state().get( 'selection' );
8475  
8476          if ( selection.length <= 0 ) {
8477              return;
8478          }
8479  
8480          if ( 'insert' === this.controller.options.state ) {
8481              this.controller.trigger( 'insert', selection );
8482          } else {
8483              this.controller.trigger( 'select', selection );
8484              event.preventDefault();
8485              this.escape();
8486          }
8487      },
8488  
8489      /**
8490       * @param {Array|Object} content Views to register to '.media-modal-content'
8491       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8492       */
8493      content: function( content ) {
8494          this.views.set( '.media-modal-content', content );
8495          return this;
8496      },
8497  
8498      /**
8499       * Triggers a modal event and if the `propagate` option is set,
8500       * forwards events to the modal's controller.
8501       *
8502       * @param {string} id
8503       * @return {wp.media.view.Modal} Returns itself to allow chaining.
8504       */
8505      propagate: function( id ) {
8506          this.trigger( id );
8507  
8508          if ( this.options.propagate ) {
8509              this.controller.trigger( id );
8510          }
8511  
8512          return this;
8513      },
8514      /**
8515       * @param {Object} event
8516       */
8517      keydown: function( event ) {
8518          // Close the modal when escape is pressed.
8519          if ( 27 === event.which && this.$el.is(':visible') ) {
8520              this.escape();
8521              event.stopImmediatePropagation();
8522          }
8523  
8524          // Select the attachment when command or control and enter are pressed.
8525          if ( ( 13 === event.which || 10 === event.which ) && ( event.metaKey || event.ctrlKey ) ) {
8526              this.selectHandler( event );
8527              event.stopImmediatePropagation();
8528          }
8529  
8530      }
8531  });
8532  
8533  module.exports = Modal;
8534  
8535  
8536  /***/ },
8537  
8538  /***/ 8815
8539  (module) {
8540  
8541  /**
8542   * wp.media.view.PriorityList
8543   *
8544   * @memberOf wp.media.view
8545   *
8546   * @class
8547   * @augments wp.media.View
8548   * @augments wp.Backbone.View
8549   * @augments Backbone.View
8550   */
8551  var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{
8552      tagName:   'div',
8553  
8554      initialize: function() {
8555          this._views = {};
8556  
8557          this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
8558          delete this.options.views;
8559  
8560          if ( ! this.options.silent ) {
8561              this.render();
8562          }
8563      },
8564      /**
8565       * @param {string} id
8566       * @param {wp.media.View|Object} view
8567       * @param {Object} options
8568       * @return {wp.media.view.PriorityList} Returns itself to allow chaining.
8569       */
8570      set: function( id, view, options ) {
8571          var priority, views, index;
8572  
8573          options = options || {};
8574  
8575          // Accept an object with an `id` : `view` mapping.
8576          if ( _.isObject( id ) ) {
8577              _.each( id, function( view, id ) {
8578                  this.set( id, view );
8579              }, this );
8580              return this;
8581          }
8582  
8583          if ( ! (view instanceof Backbone.View) ) {
8584              view = this.toView( view, id, options );
8585          }
8586          view.controller = view.controller || this.controller;
8587  
8588          this.unset( id );
8589  
8590          priority = view.options.priority || 10;
8591          views = this.views.get() || [];
8592  
8593          _.find( views, function( existing, i ) {
8594              if ( existing.options.priority > priority ) {
8595                  index = i;
8596                  return true;
8597              }
8598          });
8599  
8600          this._views[ id ] = view;
8601          this.views.add( view, {
8602              at: _.isNumber( index ) ? index : views.length || 0
8603          });
8604  
8605          return this;
8606      },
8607      /**
8608       * @param {string} id
8609       * @return {wp.media.View}
8610       */
8611      get: function( id ) {
8612          return this._views[ id ];
8613      },
8614      /**
8615       * @param {string} id
8616       * @return {wp.media.view.PriorityList}
8617       */
8618      unset: function( id ) {
8619          var view = this.get( id );
8620  
8621          if ( view ) {
8622              view.remove();
8623          }
8624  
8625          delete this._views[ id ];
8626          return this;
8627      },
8628      /**
8629       * @param {Object} options
8630       * @return {wp.media.View}
8631       */
8632      toView: function( options ) {
8633          return new wp.media.View( options );
8634      }
8635  });
8636  
8637  module.exports = PriorityList;
8638  
8639  
8640  /***/ },
8641  
8642  /***/ 6327
8643  (module) {
8644  
8645  /**
8646   * wp.media.view.RouterItem
8647   *
8648   * @memberOf wp.media.view
8649   *
8650   * @class
8651   * @augments wp.media.view.MenuItem
8652   * @augments wp.media.View
8653   * @augments wp.Backbone.View
8654   * @augments Backbone.View
8655   */
8656  var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{
8657      /**
8658       * On click handler to activate the content region's corresponding mode.
8659       */
8660      click: function() {
8661          var contentMode = this.options.contentMode;
8662          if ( contentMode ) {
8663              this.controller.content.mode( contentMode );
8664          }
8665      }
8666  });
8667  
8668  module.exports = RouterItem;
8669  
8670  
8671  /***/ },
8672  
8673  /***/ 4783
8674  (module) {
8675  
8676  var Menu = wp.media.view.Menu,
8677      Router;
8678  
8679  /**
8680   * wp.media.view.Router
8681   *
8682   * @memberOf wp.media.view
8683   *
8684   * @class
8685   * @augments wp.media.view.Menu
8686   * @augments wp.media.view.PriorityList
8687   * @augments wp.media.View
8688   * @augments wp.Backbone.View
8689   * @augments Backbone.View
8690   */
8691  Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{
8692      tagName:   'div',
8693      className: 'media-router',
8694      property:  'contentMode',
8695      ItemView:  wp.media.view.RouterItem,
8696      region:    'router',
8697  
8698      attributes: {
8699          role:               'tablist',
8700          'aria-orientation': 'horizontal'
8701      },
8702  
8703      initialize: function() {
8704          this.controller.on( 'content:render', this.update, this );
8705          // Call 'initialize' directly on the parent class.
8706          Menu.prototype.initialize.apply( this, arguments );
8707      },
8708  
8709      update: function() {
8710          var mode = this.controller.content.mode();
8711          if ( mode ) {
8712              this.select( mode );
8713          }
8714      }
8715  });
8716  
8717  module.exports = Router;
8718  
8719  
8720  /***/ },
8721  
8722  /***/ 2102
8723  (module) {
8724  
8725  var Search;
8726  
8727  /**
8728   * wp.media.view.Search
8729   *
8730   * @memberOf wp.media.view
8731   *
8732   * @class
8733   * @augments wp.media.View
8734   * @augments wp.Backbone.View
8735   * @augments Backbone.View
8736   */
8737  Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{
8738      tagName:   'input',
8739      className: 'search',
8740      id:        'media-search-input',
8741  
8742      attributes: {
8743          type: 'search'
8744      },
8745  
8746      events: {
8747          'input': 'search'
8748      },
8749  
8750      /**
8751       * @return {wp.media.view.Search} Returns itself to allow chaining.
8752       */
8753      render: function() {
8754          this.el.value = this.model.escape('search');
8755          return this;
8756      },
8757  
8758      search: _.debounce( function( event ) {
8759          var searchTerm = event.target.value.trim();
8760  
8761          // Trigger the search only after 2 ASCII characters.
8762          if ( searchTerm && searchTerm.length > 1 ) {
8763              this.model.set( 'search', searchTerm );
8764          } else {
8765              this.model.unset( 'search' );
8766          }
8767      }, 500 )
8768  });
8769  
8770  module.exports = Search;
8771  
8772  
8773  /***/ },
8774  
8775  /***/ 8282
8776  (module) {
8777  
8778  var _n = wp.i18n._n,
8779      sprintf = wp.i18n.sprintf,
8780      Selection;
8781  
8782  /**
8783   * wp.media.view.Selection
8784   *
8785   * @memberOf wp.media.view
8786   *
8787   * @class
8788   * @augments wp.media.View
8789   * @augments wp.Backbone.View
8790   * @augments Backbone.View
8791   */
8792  Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */{
8793      tagName:   'div',
8794      className: 'media-selection',
8795      template:  wp.template('media-selection'),
8796  
8797      events: {
8798          'click .edit-selection':  'edit',
8799          'click .clear-selection': 'clear'
8800      },
8801  
8802      initialize: function() {
8803          _.defaults( this.options, {
8804              editable:  false,
8805              clearable: true
8806          });
8807  
8808          /**
8809           * @member {wp.media.view.Attachments.Selection}
8810           */
8811          this.attachments = new wp.media.view.Attachments.Selection({
8812              controller: this.controller,
8813              collection: this.collection,
8814              selection:  this.collection,
8815              model:      new Backbone.Model()
8816          });
8817  
8818          this.views.set( '.selection-view', this.attachments );
8819          this.collection.on( 'add remove reset', this.refresh, this );
8820          this.controller.on( 'content:activate', this.refresh, this );
8821      },
8822  
8823      ready: function() {
8824          this.refresh();
8825      },
8826  
8827      refresh: function() {
8828          // If the selection hasn't been rendered, bail.
8829          if ( ! this.$el.children().length ) {
8830              return;
8831          }
8832  
8833          var collection = this.collection,
8834              editing = 'edit-selection' === this.controller.content.mode();
8835  
8836          // If nothing is selected, display nothing.
8837          this.$el.toggleClass( 'empty', ! collection.length );
8838          this.$el.toggleClass( 'one', 1 === collection.length );
8839          this.$el.toggleClass( 'editing', editing );
8840  
8841          this.$( '.count' ).text(
8842              /* translators: %s: Number of selected media attachments. */
8843              sprintf( _n( '%s item selected', '%s items selected', collection.length ), collection.length )
8844          );
8845      },
8846  
8847      edit: function( event ) {
8848          event.preventDefault();
8849          if ( this.options.editable ) {
8850              this.options.editable.call( this, this.collection );
8851          }
8852      },
8853  
8854      clear: function( event ) {
8855          event.preventDefault();
8856          this.collection.reset();
8857  
8858          // Move focus to the modal.
8859          this.controller.modal.focusManager.focus();
8860      }
8861  });
8862  
8863  module.exports = Selection;
8864  
8865  
8866  /***/ },
8867  
8868  /***/ 1915
8869  (module) {
8870  
8871  var View = wp.media.View,
8872      $ = Backbone.$,
8873      Settings;
8874  
8875  /**
8876   * wp.media.view.Settings
8877   *
8878   * @memberOf wp.media.view
8879   *
8880   * @class
8881   * @augments wp.media.View
8882   * @augments wp.Backbone.View
8883   * @augments Backbone.View
8884   */
8885  Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{
8886      events: {
8887          'click button':    'updateHandler',
8888          'change input':    'updateHandler',
8889          'change select':   'updateHandler',
8890          'change textarea': 'updateHandler'
8891      },
8892  
8893      initialize: function() {
8894          this.model = this.model || new Backbone.Model();
8895          this.listenTo( this.model, 'change', this.updateChanges );
8896      },
8897  
8898      prepare: function() {
8899          return _.defaults({
8900              model: this.model.toJSON()
8901          }, this.options );
8902      },
8903      /**
8904       * @return {wp.media.view.Settings} Returns itself to allow chaining.
8905       */
8906      render: function() {
8907          View.prototype.render.apply( this, arguments );
8908          // Select the correct values.
8909          _( this.model.attributes ).chain().keys().each( this.update, this );
8910          return this;
8911      },
8912      /**
8913       * @param {string} key
8914       */
8915      update: function( key ) {
8916          var value = this.model.get( key ),
8917              $setting = this.$('[data-setting="' + key + '"]'),
8918              $buttons, $value;
8919  
8920          // Bail if we didn't find a matching setting.
8921          if ( ! $setting.length ) {
8922              return;
8923          }
8924  
8925          // Attempt to determine how the setting is rendered and update
8926          // the selected value.
8927  
8928          // Handle dropdowns.
8929          if ( $setting.is('select') ) {
8930              $value = $setting.find('[value="' + value + '"]');
8931  
8932              if ( $value.length ) {
8933                  $setting.find('option').prop( 'selected', false );
8934                  $value.prop( 'selected', true );
8935              } else {
8936                  // If we can't find the desired value, record what *is* selected.
8937                  this.model.set( key, $setting.find(':selected').val() );
8938              }
8939  
8940          // Handle button groups.
8941          } else if ( $setting.hasClass('button-group') ) {
8942              $buttons = $setting.find( 'button' )
8943                  .removeClass( 'active' )
8944                  .attr( 'aria-pressed', 'false' );
8945              $buttons.filter( '[value="' + value + '"]' )
8946                  .addClass( 'active' )
8947                  .attr( 'aria-pressed', 'true' );
8948  
8949          // Handle text inputs and textareas.
8950          } else if ( $setting.is('input[type="text"], textarea') ) {
8951              if ( ! $setting.is(':focus') ) {
8952                  $setting.val( value );
8953              }
8954          // Handle checkboxes.
8955          } else if ( $setting.is('input[type="checkbox"]') ) {
8956              $setting.prop( 'checked', !! value && 'false' !== value );
8957          }
8958      },
8959      /**
8960       * @param {Object} event
8961       */
8962      updateHandler: function( event ) {
8963          var $setting = $( event.target ).closest('[data-setting]'),
8964              value = event.target.value,
8965              userSetting;
8966  
8967          event.preventDefault();
8968  
8969          if ( ! $setting.length ) {
8970              return;
8971          }
8972  
8973          // Use the correct value for checkboxes.
8974          if ( $setting.is('input[type="checkbox"]') ) {
8975              value = $setting[0].checked;
8976          }
8977  
8978          // Update the corresponding setting.
8979          this.model.set( $setting.data('setting'), value );
8980  
8981          // If the setting has a corresponding user setting,
8982          // update that as well.
8983          userSetting = $setting.data('userSetting');
8984          if ( userSetting ) {
8985              window.setUserSetting( userSetting, value );
8986          }
8987      },
8988  
8989      updateChanges: function( model ) {
8990          if ( model.hasChanged() ) {
8991              _( model.changed ).chain().keys().each( this.update, this );
8992          }
8993      }
8994  });
8995  
8996  module.exports = Settings;
8997  
8998  
8999  /***/ },
9000  
9001  /***/ 7656
9002  (module) {
9003  
9004  var Settings = wp.media.view.Settings,
9005      AttachmentDisplay;
9006  
9007  /**
9008   * wp.media.view.Settings.AttachmentDisplay
9009   *
9010   * @memberOf wp.media.view.Settings
9011   *
9012   * @class
9013   * @augments wp.media.view.Settings
9014   * @augments wp.media.View
9015   * @augments wp.Backbone.View
9016   * @augments Backbone.View
9017   */
9018  AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.AttachmentDisplay.prototype */{
9019      className: 'attachment-display-settings',
9020      template:  wp.template('attachment-display-settings'),
9021  
9022      initialize: function() {
9023          var attachment = this.options.attachment;
9024  
9025          _.defaults( this.options, {
9026              userSettings: false
9027          });
9028          // Call 'initialize' directly on the parent class.
9029          Settings.prototype.initialize.apply( this, arguments );
9030          this.listenTo( this.model, 'change:link', this.updateLinkTo );
9031  
9032          if ( attachment ) {
9033              attachment.on( 'change:uploading', this.render, this );
9034          }
9035      },
9036  
9037      dispose: function() {
9038          var attachment = this.options.attachment;
9039          if ( attachment ) {
9040              attachment.off( null, null, this );
9041          }
9042          /**
9043           * call 'dispose' directly on the parent class
9044           */
9045          Settings.prototype.dispose.apply( this, arguments );
9046      },
9047      /**
9048       * @return {wp.media.view.AttachmentDisplay} Returns itself to allow chaining.
9049       */
9050      render: function() {
9051          var attachment = this.options.attachment;
9052          if ( attachment ) {
9053              _.extend( this.options, {
9054                  sizes: attachment.get('sizes'),
9055                  type:  attachment.get('type')
9056              });
9057          }
9058          /**
9059           * call 'render' directly on the parent class
9060           */
9061          Settings.prototype.render.call( this );
9062          this.updateLinkTo();
9063          return this;
9064      },
9065  
9066      updateLinkTo: function() {
9067          var linkTo = this.model.get('link'),
9068              $input = this.$('.link-to-custom'),
9069              attachment = this.options.attachment;
9070  
9071          if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
9072              $input.closest( '.setting' ).addClass( 'hidden' );
9073              return;
9074          }
9075  
9076          if ( attachment ) {
9077              if ( 'post' === linkTo ) {
9078                  $input.val( attachment.get('link') );
9079              } else if ( 'file' === linkTo ) {
9080                  $input.val( attachment.get('url') );
9081              } else if ( ! this.model.get('linkUrl') ) {
9082                  $input.val('http://');
9083              }
9084  
9085              $input.prop( 'readonly', 'custom' !== linkTo );
9086          }
9087  
9088          $input.closest( '.setting' ).removeClass( 'hidden' );
9089          if ( $input.length ) {
9090              $input[0].scrollIntoView();
9091          }
9092      }
9093  });
9094  
9095  module.exports = AttachmentDisplay;
9096  
9097  
9098  /***/ },
9099  
9100  /***/ 7266
9101  (module) {
9102  
9103  /**
9104   * wp.media.view.Settings.Gallery
9105   *
9106   * @memberOf wp.media.view.Settings
9107   *
9108   * @class
9109   * @augments wp.media.view.Settings
9110   * @augments wp.media.View
9111   * @augments wp.Backbone.View
9112   * @augments Backbone.View
9113   */
9114  var Gallery = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Gallery.prototype */{
9115      className: 'collection-settings gallery-settings',
9116      template:  wp.template('gallery-settings')
9117  });
9118  
9119  module.exports = Gallery;
9120  
9121  
9122  /***/ },
9123  
9124  /***/ 2356
9125  (module) {
9126  
9127  /**
9128   * wp.media.view.Settings.Playlist
9129   *
9130   * @memberOf wp.media.view.Settings
9131   *
9132   * @class
9133   * @augments wp.media.view.Settings
9134   * @augments wp.media.View
9135   * @augments wp.Backbone.View
9136   * @augments Backbone.View
9137   */
9138  var Playlist = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Playlist.prototype */{
9139      className: 'collection-settings playlist-settings',
9140      template:  wp.template('playlist-settings')
9141  });
9142  
9143  module.exports = Playlist;
9144  
9145  
9146  /***/ },
9147  
9148  /***/ 1992
9149  (module) {
9150  
9151  /**
9152   * wp.media.view.Sidebar
9153   *
9154   * @memberOf wp.media.view
9155   *
9156   * @class
9157   * @augments wp.media.view.PriorityList
9158   * @augments wp.media.View
9159   * @augments wp.Backbone.View
9160   * @augments Backbone.View
9161   */
9162  var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{
9163      className: 'media-sidebar'
9164  });
9165  
9166  module.exports = Sidebar;
9167  
9168  
9169  /***/ },
9170  
9171  /***/ 443
9172  (module) {
9173  
9174  var View = wp.media.view,
9175      SiteIconCropper;
9176  
9177  /**
9178   * wp.media.view.SiteIconCropper
9179   *
9180   * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon.
9181   *
9182   * Takes imgAreaSelect options from
9183   * wp.customize.SiteIconControl.calculateImageSelectOptions.
9184   *
9185   * @memberOf wp.media.view
9186   *
9187   * @class
9188   * @augments wp.media.view.Cropper
9189   * @augments wp.media.View
9190   * @augments wp.Backbone.View
9191   * @augments Backbone.View
9192   */
9193  SiteIconCropper = View.Cropper.extend(/** @lends wp.media.view.SiteIconCropper.prototype */{
9194      className: 'crop-content site-icon',
9195  
9196      ready: function () {
9197          View.Cropper.prototype.ready.apply( this, arguments );
9198  
9199          this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) );
9200      },
9201  
9202      addSidebar: function() {
9203          this.sidebar = new wp.media.view.Sidebar({
9204              controller: this.controller
9205          });
9206  
9207          this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({
9208              controller: this.controller,
9209              attachment: this.options.attachment
9210          }) );
9211  
9212          this.controller.cropperView.views.add( this.sidebar );
9213      }
9214  });
9215  
9216  module.exports = SiteIconCropper;
9217  
9218  
9219  /***/ },
9220  
9221  /***/ 7810
9222  (module) {
9223  
9224  var View = wp.media.View,
9225      $ = jQuery,
9226      SiteIconPreview;
9227  
9228  /**
9229   * wp.media.view.SiteIconPreview
9230   *
9231   * Shows a preview of the Site Icon as a favicon and app icon while cropping.
9232   *
9233   * @memberOf wp.media.view
9234   *
9235   * @class
9236   * @augments wp.media.View
9237   * @augments wp.Backbone.View
9238   * @augments Backbone.View
9239   */
9240  SiteIconPreview = View.extend(/** @lends wp.media.view.SiteIconPreview.prototype */{
9241      className: 'site-icon-preview-crop-modal',
9242      template: wp.template( 'site-icon-preview-crop' ),
9243  
9244      ready: function() {
9245          this.controller.imgSelect.setOptions({
9246              onInit: this.updatePreview,
9247              onSelectChange: this.updatePreview
9248          });
9249      },
9250  
9251      prepare: function() {
9252          return {
9253              url: this.options.attachment.get( 'url' )
9254          };
9255      },
9256  
9257      updatePreview: function( img, coords ) {
9258          var rx = 64 / coords.width,
9259              ry = 64 / coords.height,
9260              preview_rx = 24 / coords.width,
9261              preview_ry = 24 / coords.height;
9262  
9263          $( '#preview-app-icon' ).css({
9264              width: Math.round(rx * this.imageWidth ) + 'px',
9265              height: Math.round(ry * this.imageHeight ) + 'px',
9266              marginLeft: '-' + Math.round(rx * coords.x1) + 'px',
9267              marginTop: '-' + Math.round(ry * coords.y1) + 'px'
9268          });
9269  
9270          $( '#preview-favicon' ).css({
9271              width: Math.round( preview_rx * this.imageWidth ) + 'px',
9272              height: Math.round( preview_ry * this.imageHeight ) + 'px',
9273              marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px',
9274              marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px'
9275          });
9276      }
9277  });
9278  
9279  module.exports = SiteIconPreview;
9280  
9281  
9282  /***/ },
9283  
9284  /***/ 9141
9285  (module) {
9286  
9287  /**
9288   * wp.media.view.Spinner
9289   *
9290   * Represents a spinner in the Media Library.
9291   *
9292   * @since 3.9.0
9293   *
9294   * @memberOf wp.media.view
9295   *
9296   * @class
9297   * @augments wp.media.View
9298   * @augments wp.Backbone.View
9299   * @augments Backbone.View
9300   */
9301  var Spinner = wp.media.View.extend(/** @lends wp.media.view.Spinner.prototype */{
9302      tagName:   'span',
9303      className: 'spinner',
9304      spinnerTimeout: false,
9305      delay: 400,
9306  
9307      /**
9308       * Shows the spinner. Delays the visibility by the configured amount.
9309       *
9310       * @since 3.9.0
9311       *
9312       * @return {wp.media.view.Spinner} The spinner.
9313       */
9314      show: function() {
9315          if ( ! this.spinnerTimeout ) {
9316              this.spinnerTimeout = _.delay(function( $el ) {
9317                  $el.addClass( 'is-active' );
9318              }, this.delay, this.$el );
9319          }
9320  
9321          return this;
9322      },
9323  
9324      /**
9325       * Hides the spinner.
9326       *
9327       * @since 3.9.0
9328       *
9329       * @return {wp.media.view.Spinner} The spinner.
9330       */
9331      hide: function() {
9332          this.$el.removeClass( 'is-active' );
9333          this.spinnerTimeout = clearTimeout( this.spinnerTimeout );
9334  
9335          return this;
9336      }
9337  });
9338  
9339  module.exports = Spinner;
9340  
9341  
9342  /***/ },
9343  
9344  /***/ 5275
9345  (module) {
9346  
9347  var View = wp.media.View,
9348      Toolbar;
9349  
9350  /**
9351   * wp.media.view.Toolbar
9352   *
9353   * A toolbar which consists of a primary and a secondary section. Each sections
9354   * can be filled with views.
9355   *
9356   * @memberOf wp.media.view
9357   *
9358   * @class
9359   * @augments wp.media.View
9360   * @augments wp.Backbone.View
9361   * @augments Backbone.View
9362   */
9363  Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{
9364      tagName:   'div',
9365      className: 'media-toolbar',
9366  
9367      initialize: function() {
9368          var state = this.controller.state(),
9369              selection = this.selection = state.get('selection'),
9370              library = this.library = state.get('library');
9371  
9372          this._views = {};
9373  
9374          // The toolbar is composed of two `PriorityList` views.
9375          this.primary   = new wp.media.view.PriorityList();
9376          this.secondary = new wp.media.view.PriorityList();
9377          this.tertiary  = new wp.media.view.PriorityList();
9378          this.primary.$el.addClass('media-toolbar-primary search-form');
9379          this.secondary.$el.addClass('media-toolbar-secondary');
9380          this.tertiary.$el.addClass('media-bg-overlay');
9381  
9382          this.views.set([ this.secondary, this.primary, this.tertiary ]);
9383  
9384          if ( this.options.items ) {
9385              this.set( this.options.items, { silent: true });
9386          }
9387  
9388          if ( ! this.options.silent ) {
9389              this.render();
9390          }
9391  
9392          if ( selection ) {
9393              selection.on( 'add remove reset', this.refresh, this );
9394          }
9395  
9396          if ( library ) {
9397              library.on( 'add remove reset', this.refresh, this );
9398          }
9399      },
9400      /**
9401       * @return {wp.media.view.Toolbar} Returns itself to allow chaining
9402       */
9403      dispose: function() {
9404          if ( this.selection ) {
9405              this.selection.off( null, null, this );
9406          }
9407  
9408          if ( this.library ) {
9409              this.library.off( null, null, this );
9410          }
9411          /**
9412           * call 'dispose' directly on the parent class
9413           */
9414          return View.prototype.dispose.apply( this, arguments );
9415      },
9416  
9417      ready: function() {
9418          this.refresh();
9419      },
9420  
9421      /**
9422       * @param {string} id
9423       * @param {Backbone.View|Object} view
9424       * @param {Object} [options={}]
9425       * @return {wp.media.view.Toolbar} Returns itself to allow chaining.
9426       */
9427      set: function( id, view, options ) {
9428          var list;
9429          options = options || {};
9430  
9431          // Accept an object with an `id` : `view` mapping.
9432          if ( _.isObject( id ) ) {
9433              _.each( id, function( view, id ) {
9434                  this.set( id, view, { silent: true });
9435              }, this );
9436  
9437          } else {
9438              if ( ! ( view instanceof Backbone.View ) ) {
9439                  view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
9440                  view = new wp.media.view.Button( view ).render();
9441              }
9442  
9443              view.controller = view.controller || this.controller;
9444  
9445              this._views[ id ] = view;
9446  
9447              list = view.options.priority < 0 ? 'secondary' : 'primary';
9448              this[ list ].set( id, view, options );
9449          }
9450  
9451          if ( ! options.silent ) {
9452              this.refresh();
9453          }
9454  
9455          return this;
9456      },
9457      /**
9458       * @param {string} id
9459       * @return {wp.media.view.Button}
9460       */
9461      get: function( id ) {
9462          return this._views[ id ];
9463      },
9464      /**
9465       * @param {string} id
9466       * @param {Object} options
9467       * @return {wp.media.view.Toolbar} Returns itself to allow chaining.
9468       */
9469      unset: function( id, options ) {
9470          delete this._views[ id ];
9471          this.primary.unset( id, options );
9472          this.secondary.unset( id, options );
9473          this.tertiary.unset( id, options );
9474  
9475          if ( ! options || ! options.silent ) {
9476              this.refresh();
9477          }
9478          return this;
9479      },
9480  
9481      refresh: function() {
9482          var state = this.controller.state(),
9483              library = state.get('library'),
9484              selection = state.get('selection');
9485  
9486          _.each( this._views, function( button ) {
9487              if ( ! button.model || ! button.options || ! button.options.requires ) {
9488                  return;
9489              }
9490  
9491              var requires = button.options.requires,
9492                  disabled = false,
9493                  modelsUploading = library && ! _.isEmpty( library.findWhere( { 'uploading': true } ) );
9494  
9495              // Prevent insertion of attachments if any of them are still uploading.
9496              if ( selection && selection.models ) {
9497                  disabled = _.some( selection.models, function( attachment ) {
9498                      return attachment.get('uploading') === true;
9499                  });
9500              }
9501              if ( requires.uploadingComplete && modelsUploading ) {
9502                  disabled = true;
9503              }
9504  
9505              if ( requires.selection && selection && ! selection.length ) {
9506                  disabled = true;
9507              } else if ( requires.library && library && ! library.length ) {
9508                  disabled = true;
9509              }
9510              button.model.set( 'disabled', disabled );
9511          });
9512      }
9513  });
9514  
9515  module.exports = Toolbar;
9516  
9517  
9518  /***/ },
9519  
9520  /***/ 397
9521  (module) {
9522  
9523  var Select = wp.media.view.Toolbar.Select,
9524      l10n = wp.media.view.l10n,
9525      Embed;
9526  
9527  /**
9528   * wp.media.view.Toolbar.Embed
9529   *
9530   * @memberOf wp.media.view.Toolbar
9531   *
9532   * @class
9533   * @augments wp.media.view.Toolbar.Select
9534   * @augments wp.media.view.Toolbar
9535   * @augments wp.media.View
9536   * @augments wp.Backbone.View
9537   * @augments Backbone.View
9538   */
9539  Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{
9540      initialize: function() {
9541          _.defaults( this.options, {
9542              text: l10n.insertIntoPost,
9543              requires: false
9544          });
9545          // Call 'initialize' directly on the parent class.
9546          Select.prototype.initialize.apply( this, arguments );
9547      },
9548  
9549      refresh: function() {
9550          var url = this.controller.state().props.get('url');
9551          this.get('select').model.set( 'disabled', ! url || url === 'http://' );
9552          /**
9553           * call 'refresh' directly on the parent class
9554           */
9555          Select.prototype.refresh.apply( this, arguments );
9556      }
9557  });
9558  
9559  module.exports = Embed;
9560  
9561  
9562  /***/ },
9563  
9564  /***/ 9458
9565  (module) {
9566  
9567  var Toolbar = wp.media.view.Toolbar,
9568      l10n = wp.media.view.l10n,
9569      Select;
9570  
9571  /**
9572   * wp.media.view.Toolbar.Select
9573   *
9574   * @memberOf wp.media.view.Toolbar
9575   *
9576   * @class
9577   * @augments wp.media.view.Toolbar
9578   * @augments wp.media.View
9579   * @augments wp.Backbone.View
9580   * @augments Backbone.View
9581   */
9582  Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{
9583      initialize: function() {
9584          var options = this.options;
9585  
9586          _.bindAll( this, 'clickSelect' );
9587  
9588          _.defaults( options, {
9589              event: 'select',
9590              state: false,
9591              reset: true,
9592              close: true,
9593              text:  l10n.select,
9594  
9595              // Does the button rely on the selection?
9596              requires: {
9597                  selection: true
9598              }
9599          });
9600  
9601          options.items = _.defaults( options.items || {}, {
9602              select: {
9603                  style:    'primary',
9604                  text:     options.text,
9605                  priority: 80,
9606                  click:    this.clickSelect,
9607                  requires: options.requires
9608              }
9609          });
9610          // Call 'initialize' directly on the parent class.
9611          Toolbar.prototype.initialize.apply( this, arguments );
9612      },
9613  
9614      clickSelect: function() {
9615          var options = this.options,
9616              controller = this.controller;
9617  
9618          if ( options.close ) {
9619              controller.close();
9620          }
9621  
9622          if ( options.event ) {
9623              controller.state().trigger( options.event );
9624          }
9625  
9626          if ( options.state ) {
9627              controller.setState( options.state );
9628          }
9629  
9630          if ( options.reset ) {
9631              controller.reset();
9632          }
9633      }
9634  });
9635  
9636  module.exports = Select;
9637  
9638  
9639  /***/ },
9640  
9641  /***/ 3674
9642  (module) {
9643  
9644  var View = wp.media.View,
9645      l10n = wp.media.view.l10n,
9646      $ = jQuery,
9647      EditorUploader;
9648  
9649  /**
9650   * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
9651   * and relays drag'n'dropped files to a media workflow.
9652   *
9653   * wp.media.view.EditorUploader
9654   *
9655   * @memberOf wp.media.view
9656   *
9657   * @class
9658   * @augments wp.media.View
9659   * @augments wp.Backbone.View
9660   * @augments Backbone.View
9661   */
9662  EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{
9663      tagName:   'div',
9664      className: 'uploader-editor',
9665      template:  wp.template( 'uploader-editor' ),
9666  
9667      localDrag: false,
9668      overContainer: false,
9669      overDropzone: false,
9670      draggingFile: null,
9671  
9672      /**
9673       * Bind drag'n'drop events to callbacks.
9674       */
9675      initialize: function() {
9676          this.initialized = false;
9677  
9678          // Bail if not enabled or UA does not support drag'n'drop or File API.
9679          if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
9680              return this;
9681          }
9682  
9683          this.$document = $(document);
9684          this.dropzones = [];
9685          this.files = [];
9686  
9687          this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
9688          this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
9689          this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
9690          this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
9691  
9692          this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
9693          this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
9694  
9695          this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
9696              this.localDrag = event.type === 'dragstart';
9697  
9698              if ( event.type === 'drop' ) {
9699                  this.containerDragleave();
9700              }
9701          }, this ) );
9702  
9703          this.initialized = true;
9704          return this;
9705      },
9706  
9707      /**
9708       * Check browser support for drag'n'drop.
9709       *
9710       * @return {boolean}
9711       */
9712      browserSupport: function() {
9713          var supports = false, div = document.createElement('div');
9714  
9715          supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
9716          supports = supports && !! ( window.File && window.FileList && window.FileReader );
9717          return supports;
9718      },
9719  
9720      isDraggingFile: function( event ) {
9721          if ( this.draggingFile !== null ) {
9722              return this.draggingFile;
9723          }
9724  
9725          if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
9726              return false;
9727          }
9728  
9729          this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
9730              _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
9731  
9732          return this.draggingFile;
9733      },
9734  
9735      refresh: function( e ) {
9736          var dropzone_id;
9737          for ( dropzone_id in this.dropzones ) {
9738              // Hide the dropzones only if dragging has left the screen.
9739              this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
9740          }
9741  
9742          if ( ! _.isUndefined( e ) ) {
9743              $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
9744          }
9745  
9746          if ( ! this.overContainer && ! this.overDropzone ) {
9747              this.draggingFile = null;
9748          }
9749  
9750          return this;
9751      },
9752  
9753      render: function() {
9754          if ( ! this.initialized ) {
9755              return this;
9756          }
9757  
9758          View.prototype.render.apply( this, arguments );
9759          $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
9760          return this;
9761      },
9762  
9763      attach: function( index, editor ) {
9764          // Attach a dropzone to an editor.
9765          var dropzone = this.$el.clone();
9766          this.dropzones.push( dropzone );
9767          $( editor ).append( dropzone );
9768          return this;
9769      },
9770  
9771      /**
9772       * When a file is dropped on the editor uploader, open up an editor media workflow
9773       * and upload the file immediately.
9774       *
9775       * @param {jQuery.Event} event The 'drop' event.
9776       */
9777      drop: function( event ) {
9778          var $wrap, uploadView;
9779  
9780          this.containerDragleave( event );
9781          this.dropzoneDragleave( event );
9782  
9783          this.files = event.originalEvent.dataTransfer.files;
9784          if ( this.files.length < 1 ) {
9785              return;
9786          }
9787  
9788          // Set the active editor to the drop target.
9789          $wrap = $( event.target ).parents( '.wp-editor-wrap' );
9790          if ( $wrap.length > 0 && $wrap[0].id ) {
9791              window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
9792          }
9793  
9794          if ( ! this.workflow ) {
9795              this.workflow = wp.media.editor.open( window.wpActiveEditor, {
9796                  frame:    'post',
9797                  state:    'insert',
9798                  title:    l10n.addMedia,
9799                  multiple: true
9800              });
9801  
9802              uploadView = this.workflow.uploader;
9803  
9804              if ( uploadView.uploader && uploadView.uploader.ready ) {
9805                  this.addFiles.apply( this );
9806              } else {
9807                  this.workflow.on( 'uploader:ready', this.addFiles, this );
9808              }
9809          } else {
9810              this.workflow.state().reset();
9811              this.addFiles.apply( this );
9812              this.workflow.open();
9813          }
9814  
9815          return false;
9816      },
9817  
9818      /**
9819       * Add the files to the uploader.
9820       */
9821      addFiles: function() {
9822          if ( this.files.length ) {
9823              this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
9824              this.files = [];
9825          }
9826          return this;
9827      },
9828  
9829      containerDragover: function( event ) {
9830          if ( this.localDrag || ! this.isDraggingFile( event ) ) {
9831              return;
9832          }
9833  
9834          this.overContainer = true;
9835          this.refresh();
9836      },
9837  
9838      containerDragleave: function() {
9839          this.overContainer = false;
9840  
9841          // Throttle dragleave because it's called when bouncing from some elements to others.
9842          _.delay( _.bind( this.refresh, this ), 50 );
9843      },
9844  
9845      dropzoneDragover: function( event ) {
9846          if ( this.localDrag || ! this.isDraggingFile( event ) ) {
9847              return;
9848          }
9849  
9850          this.overDropzone = true;
9851          this.refresh( event );
9852          return false;
9853      },
9854  
9855      dropzoneDragleave: function( e ) {
9856          this.overDropzone = false;
9857          _.delay( _.bind( this.refresh, this, e ), 50 );
9858      },
9859  
9860      click: function( e ) {
9861          // In the rare case where the dropzone gets stuck, hide it on click.
9862          this.containerDragleave( e );
9863          this.dropzoneDragleave( e );
9864          this.localDrag = false;
9865      }
9866  });
9867  
9868  module.exports = EditorUploader;
9869  
9870  
9871  /***/ },
9872  
9873  /***/ 1753
9874  (module) {
9875  
9876  var View = wp.media.View,
9877      UploaderInline;
9878  
9879  /**
9880   * wp.media.view.UploaderInline
9881   *
9882   * The inline uploader that shows up in the 'Upload Files' tab.
9883   *
9884   * @memberOf wp.media.view
9885   *
9886   * @class
9887   * @augments wp.media.View
9888   * @augments wp.Backbone.View
9889   * @augments Backbone.View
9890   */
9891  UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{
9892      tagName:   'div',
9893      className: 'uploader-inline',
9894      template:  wp.template('uploader-inline'),
9895  
9896      events: {
9897          'click .close': 'hide'
9898      },
9899  
9900      initialize: function() {
9901          _.defaults( this.options, {
9902              message: '',
9903              status:  true,
9904              canClose: false
9905          });
9906  
9907          if ( ! this.options.$browser && this.controller.uploader ) {
9908              this.options.$browser = this.controller.uploader.$browser;
9909          }
9910  
9911          if ( _.isUndefined( this.options.postId ) ) {
9912              this.options.postId = wp.media.view.settings.post.id;
9913          }
9914  
9915          if ( this.options.status ) {
9916              this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
9917                  controller: this.controller
9918              }) );
9919          }
9920      },
9921  
9922      prepare: function() {
9923          var suggestedWidth = this.controller.state().get('suggestedWidth'),
9924              suggestedHeight = this.controller.state().get('suggestedHeight'),
9925              data = {};
9926  
9927          data.message = this.options.message;
9928          data.canClose = this.options.canClose;
9929  
9930          if ( suggestedWidth && suggestedHeight ) {
9931              data.suggestedWidth = suggestedWidth;
9932              data.suggestedHeight = suggestedHeight;
9933          }
9934  
9935          return data;
9936      },
9937      /**
9938       * @return {wp.media.view.UploaderInline} Returns itself to allow chaining.
9939       */
9940      dispose: function() {
9941          if ( this.disposing ) {
9942              /**
9943               * call 'dispose' directly on the parent class
9944               */
9945              return View.prototype.dispose.apply( this, arguments );
9946          }
9947  
9948          /*
9949           * Run remove on `dispose`, so we can be sure to refresh the
9950           * uploader with a view-less DOM. Track whether we're disposing
9951           * so we don't trigger an infinite loop.
9952           */
9953          this.disposing = true;
9954          return this.remove();
9955      },
9956      /**
9957       * @return {wp.media.view.UploaderInline} Returns itself to allow chaining.
9958       */
9959      remove: function() {
9960          /**
9961           * call 'remove' directly on the parent class
9962           */
9963          var result = View.prototype.remove.apply( this, arguments );
9964  
9965          _.defer( _.bind( this.refresh, this ) );
9966          return result;
9967      },
9968  
9969      refresh: function() {
9970          var uploader = this.controller.uploader;
9971  
9972          if ( uploader ) {
9973              uploader.refresh();
9974          }
9975      },
9976      /**
9977       * @return {wp.media.view.UploaderInline}
9978       */
9979      ready: function() {
9980          var $browser = this.options.$browser,
9981              $placeholder;
9982  
9983          if ( this.controller.uploader ) {
9984              $placeholder = this.$('.browser');
9985  
9986              // Check if we've already replaced the placeholder.
9987              if ( $placeholder[0] === $browser[0] ) {
9988                  return;
9989              }
9990  
9991              $browser.detach().text( $placeholder.text() );
9992              $browser[0].className = $placeholder[0].className;
9993              $browser[0].setAttribute( 'aria-describedby', $placeholder[0].getAttribute('aria-describedby') );
9994              $placeholder.replaceWith( $browser.show() );
9995          }
9996  
9997          this.refresh();
9998          return this;
9999      },
10000      show: function() {
10001          this.$el.removeClass( 'hidden' );
10002          if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
10003              this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' );
10004          }
10005      },
10006      hide: function() {
10007          this.$el.addClass( 'hidden' );
10008          if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
10009              this.controller.$uploaderToggler
10010                  .attr( 'aria-expanded', 'false' )
10011                  // Move focus back to the toggle button when closing the uploader.
10012                  .trigger( 'focus' );
10013          }
10014      }
10015  
10016  });
10017  
10018  module.exports = UploaderInline;
10019  
10020  
10021  /***/ },
10022  
10023  /***/ 6442
10024  (module) {
10025  
10026  /**
10027   * wp.media.view.UploaderStatusError
10028   *
10029   * @memberOf wp.media.view
10030   *
10031   * @class
10032   * @augments wp.media.View
10033   * @augments wp.Backbone.View
10034   * @augments Backbone.View
10035   */
10036  var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{
10037      className: 'upload-error',
10038      template:  wp.template('uploader-status-error')
10039  });
10040  
10041  module.exports = UploaderStatusError;
10042  
10043  
10044  /***/ },
10045  
10046  /***/ 8197
10047  (module) {
10048  
10049  var View = wp.media.View,
10050      UploaderStatus;
10051  
10052  /**
10053   * wp.media.view.UploaderStatus
10054   *
10055   * An uploader status for on-going uploads.
10056   *
10057   * @memberOf wp.media.view
10058   *
10059   * @class
10060   * @augments wp.media.View
10061   * @augments wp.Backbone.View
10062   * @augments Backbone.View
10063   */
10064  UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{
10065      className: 'media-uploader-status',
10066      template:  wp.template('uploader-status'),
10067  
10068      events: {
10069          'click .upload-dismiss-errors': 'dismiss'
10070      },
10071  
10072      initialize: function() {
10073          this.queue = wp.Uploader.queue;
10074          this.queue.on( 'add remove reset', this.visibility, this );
10075          this.queue.on( 'add remove reset change:percent', this.progress, this );
10076          this.queue.on( 'add remove reset change:uploading', this.info, this );
10077  
10078          this.errors = wp.Uploader.errors;
10079          this.errors.reset();
10080          this.errors.on( 'add remove reset', this.visibility, this );
10081          this.errors.on( 'add', this.error, this );
10082      },
10083      /**
10084       * @return {wp.media.view.UploaderStatus}
10085       */
10086      dispose: function() {
10087          wp.Uploader.queue.off( null, null, this );
10088          /**
10089           * call 'dispose' directly on the parent class
10090           */
10091          View.prototype.dispose.apply( this, arguments );
10092          return this;
10093      },
10094  
10095      visibility: function() {
10096          this.$el.toggleClass( 'uploading', !! this.queue.length );
10097          this.$el.toggleClass( 'errors', !! this.errors.length );
10098          this.$el.toggle( !! this.queue.length || !! this.errors.length );
10099      },
10100  
10101      ready: function() {
10102          _.each({
10103              '$bar':      '.media-progress-bar div',
10104              '$index':    '.upload-index',
10105              '$total':    '.upload-total',
10106              '$filename': '.upload-filename'
10107          }, function( selector, key ) {
10108              this[ key ] = this.$( selector );
10109          }, this );
10110  
10111          this.visibility();
10112          this.progress();
10113          this.info();
10114      },
10115  
10116      progress: function() {
10117          var queue = this.queue,
10118              $bar = this.$bar;
10119  
10120          if ( ! $bar || ! queue.length ) {
10121              return;
10122          }
10123  
10124          $bar.width( ( queue.reduce( function( memo, attachment ) {
10125              if ( ! attachment.get('uploading') ) {
10126                  return memo + 100;
10127              }
10128  
10129              var percent = attachment.get('percent');
10130              return memo + ( _.isNumber( percent ) ? percent : 100 );
10131          }, 0 ) / queue.length ) + '%' );
10132      },
10133  
10134      info: function() {
10135          var queue = this.queue,
10136              index = 0, active;
10137  
10138          if ( ! queue.length ) {
10139              return;
10140          }
10141  
10142          active = this.queue.find( function( attachment, i ) {
10143              index = i;
10144              return attachment.get('uploading');
10145          });
10146  
10147          if ( this.$index && this.$total && this.$filename ) {
10148              this.$index.text( index + 1 );
10149              this.$total.text( queue.length );
10150              this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
10151          }
10152      },
10153      /**
10154       * @param {string} filename
10155       * @return {string}
10156       */
10157      filename: function( filename ) {
10158          return _.escape( filename );
10159      },
10160      /**
10161       * @param {Backbone.Model} error
10162       */
10163      error: function( error ) {
10164          var statusError = new wp.media.view.UploaderStatusError( {
10165              filename: this.filename( error.get( 'file' ).name ),
10166              message:  error.get( 'message' )
10167          } );
10168  
10169          var buttonClose = this.$el.find( 'button' );
10170  
10171          // Can show additional info here while retrying to create image sub-sizes.
10172          this.views.add( '.upload-errors', statusError, { at: 0 } );
10173          _.delay( function() {
10174              buttonClose.trigger( 'focus' );
10175          }, 1000 );
10176  
10177          _.delay( function() {
10178              wp.a11y.speak( error.get( 'message' ) );
10179          }, 1500 );
10180      },
10181  
10182      dismiss: function() {
10183          var errors = this.views.get('.upload-errors');
10184  
10185          if ( errors ) {
10186              _.invoke( errors, 'remove' );
10187          }
10188          wp.Uploader.errors.reset();
10189          wp.a11y.speak( wp.i18n.__( 'Error dismissed.' ) );
10190          // Move focus to the modal after the dismiss button gets removed from the DOM.
10191          if ( this.controller.modal ) {
10192              this.controller.modal.focusManager.focus();
10193          }
10194      }
10195  });
10196  
10197  module.exports = UploaderStatus;
10198  
10199  
10200  /***/ },
10201  
10202  /***/ 8291
10203  (module) {
10204  
10205  var $ = jQuery,
10206      UploaderWindow;
10207  
10208  /**
10209   * wp.media.view.UploaderWindow
10210   *
10211   * An uploader window that allows for dragging and dropping media.
10212   *
10213   * @memberOf wp.media.view
10214   *
10215   * @class
10216   * @augments wp.media.View
10217   * @augments wp.Backbone.View
10218   * @augments Backbone.View
10219   *
10220   * @param {object} [options]                   Options hash passed to the view.
10221   * @param {object} [options.uploader]          Uploader properties.
10222   * @param {jQuery} [options.uploader.browser]
10223   * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
10224   * @param {object} [options.uploader.params]
10225   */
10226  UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{
10227      tagName:   'div',
10228      className: 'uploader-window',
10229      template:  wp.template('uploader-window'),
10230  
10231      initialize: function() {
10232          var uploader;
10233  
10234          this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' );
10235  
10236          uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
10237              dropzone:  this.$el,
10238              browser:   this.$browser,
10239              params:    {}
10240          });
10241  
10242          // Ensure the dropzone is a jQuery collection.
10243          if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
10244              uploader.dropzone = $( uploader.dropzone );
10245          }
10246  
10247          this.controller.on( 'activate', this.refresh, this );
10248  
10249          this.controller.on( 'detach', function() {
10250              this.$browser.remove();
10251          }, this );
10252      },
10253  
10254      refresh: function() {
10255          if ( this.uploader ) {
10256              this.uploader.refresh();
10257          }
10258      },
10259  
10260      ready: function() {
10261          var postId = wp.media.view.settings.post.id,
10262              dropzone;
10263  
10264          // If the uploader already exists, bail.
10265          if ( this.uploader ) {
10266              return;
10267          }
10268  
10269          if ( postId ) {
10270              this.options.uploader.params.post_id = postId;
10271          }
10272          this.uploader = new wp.Uploader( this.options.uploader );
10273  
10274          dropzone = this.uploader.dropzone;
10275          dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
10276          dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
10277  
10278          $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
10279      },
10280  
10281      _ready: function() {
10282          this.controller.trigger( 'uploader:ready' );
10283      },
10284  
10285      show: function() {
10286          var $el = this.$el.show();
10287  
10288          // Ensure that the animation is triggered by waiting until
10289          // the transparent element is painted into the DOM.
10290          _.defer( function() {
10291              $el.css({ opacity: 1 });
10292          });
10293      },
10294  
10295      hide: function() {
10296          var $el = this.$el.css({ opacity: 0 });
10297  
10298          wp.media.transition( $el ).done( function() {
10299              // Transition end events are subject to race conditions.
10300              // Make sure that the value is set as intended.
10301              if ( '0' === $el.css('opacity') ) {
10302                  $el.hide();
10303              }
10304          });
10305  
10306          // https://core.trac.wordpress.org/ticket/27341
10307          _.delay( function() {
10308              if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
10309                  $el.hide();
10310              }
10311          }, 500 );
10312      }
10313  });
10314  
10315  module.exports = UploaderWindow;
10316  
10317  
10318  /***/ },
10319  
10320  /***/ 4747
10321  (module) {
10322  
10323  /**
10324   * wp.media.View
10325   *
10326   * The base view class for media.
10327   *
10328   * Undelegating events, removing events from the model, and
10329   * removing events from the controller mirror the code for
10330   * `Backbone.View.dispose` in Backbone 0.9.8 development.
10331   *
10332   * This behavior has since been removed, and should not be used
10333   * outside of the media manager.
10334   *
10335   * @memberOf wp.media
10336   *
10337   * @class
10338   * @augments wp.Backbone.View
10339   * @augments Backbone.View
10340   */
10341  var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{
10342      constructor: function( options ) {
10343          if ( options && options.controller ) {
10344              this.controller = options.controller;
10345          }
10346          wp.Backbone.View.apply( this, arguments );
10347      },
10348      /**
10349       * @todo The internal comment mentions this might have been a stop-gap
10350       *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
10351       *       care of this in Backbone.View now.
10352       *
10353       * @return {wp.media.View} Returns itself to allow chaining.
10354       */
10355      dispose: function() {
10356          /*
10357           * Undelegating events, removing events from the model, and
10358           * removing events from the controller mirror the code for
10359           * `Backbone.View.dispose` in Backbone 0.9.8 development.
10360           */
10361          this.undelegateEvents();
10362  
10363          if ( this.model && this.model.off ) {
10364              this.model.off( null, null, this );
10365          }
10366  
10367          if ( this.collection && this.collection.off ) {
10368              this.collection.off( null, null, this );
10369          }
10370  
10371          // Unbind controller events.
10372          if ( this.controller && this.controller.off ) {
10373              this.controller.off( null, null, this );
10374          }
10375  
10376          return this;
10377      },
10378      /**
10379       * @return {wp.media.View} Returns itself to allow chaining.
10380       */
10381      remove: function() {
10382          this.dispose();
10383          /**
10384           * call 'remove' directly on the parent class
10385           */
10386          return wp.Backbone.View.prototype.remove.apply( this, arguments );
10387      }
10388  });
10389  
10390  module.exports = View;
10391  
10392  
10393  /***/ }
10394  
10395  /******/     });
10396  /************************************************************************/
10397  /******/     // The module cache
10398  /******/     const __webpack_module_cache__ = {};
10399  /******/     
10400  /******/     // The require function
10401  /******/ 	function __webpack_require__(moduleId) {
10402  /******/         // Check if module is in cache
10403  /******/         const cachedModule = __webpack_module_cache__[moduleId];
10404  /******/         if (cachedModule !== undefined) {
10405  /******/             return cachedModule.exports;
10406  /******/         }
10407  /******/         // Create a new module (and put it into the cache)
10408  /******/         const module = __webpack_module_cache__[moduleId] = {
10409  /******/             // no module.id needed
10410  /******/             // no module.loaded needed
10411  /******/             exports: {}
10412  /******/         };
10413  /******/     
10414  /******/         // Execute the module function
10415  /******/         __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
10416  /******/     
10417  /******/         // Return the exports of the module
10418  /******/         return module.exports;
10419  /******/     }
10420  /******/     
10421  /************************************************************************/
10422  /**
10423   * @output wp-includes/js/media-views.js
10424   */
10425  
10426  var media = wp.media,
10427      $ = jQuery,
10428      l10n;
10429  
10430  media.isTouchDevice = ( 'ontouchend' in document );
10431  
10432  // Link any localized strings.
10433  l10n = media.view.l10n = window._wpMediaViewsL10n || {};
10434  
10435  // Link any settings.
10436  media.view.settings = l10n.settings || {};
10437  delete l10n.settings;
10438  
10439  // Copy the `post` setting over to the model settings.
10440  media.model.settings.post = media.view.settings.post;
10441  
10442  // Check if the browser supports CSS 3.0 transitions.
10443  $.support.transition = (function(){
10444      var style = document.documentElement.style,
10445          transitions = {
10446              WebkitTransition: 'webkitTransitionEnd',
10447              MozTransition:    'transitionend',
10448              OTransition:      'oTransitionEnd otransitionend',
10449              transition:       'transitionend'
10450          }, transition;
10451  
10452      transition = _.find( _.keys( transitions ), function( transition ) {
10453          return ! _.isUndefined( style[ transition ] );
10454      });
10455  
10456      return transition && {
10457          end: transitions[ transition ]
10458      };
10459  }());
10460  
10461  /**
10462   * A shared event bus used to provide events into
10463   * the media workflows that 3rd-party devs can use to hook
10464   * in.
10465   */
10466  media.events = _.extend( {}, Backbone.Events );
10467  
10468  /**
10469   * Makes it easier to bind events using transitions.
10470   *
10471   * @param {string} selector
10472   * @param {number} sensitivity
10473   * @return {Promise}
10474   */
10475  media.transition = function( selector, sensitivity ) {
10476      var deferred = $.Deferred();
10477  
10478      sensitivity = sensitivity || 2000;
10479  
10480      if ( $.support.transition ) {
10481          if ( ! (selector instanceof $) ) {
10482              selector = $( selector );
10483          }
10484  
10485          // Resolve the deferred when the first element finishes animating.
10486          selector.first().one( $.support.transition.end, deferred.resolve );
10487  
10488          // Just in case the event doesn't trigger, fire a callback.
10489          _.delay( deferred.resolve, sensitivity );
10490  
10491      // Otherwise, execute on the spot.
10492      } else {
10493          deferred.resolve();
10494      }
10495  
10496      return deferred.promise();
10497  };
10498  
10499  media.controller.Region = __webpack_require__( 9875 );
10500  media.controller.StateMachine = __webpack_require__( 6150 );
10501  media.controller.State = __webpack_require__( 5694 );
10502  
10503  media.selectionSync = __webpack_require__( 4181 );
10504  media.controller.Library = __webpack_require__( 472 );
10505  media.controller.ImageDetails = __webpack_require__( 705 );
10506  media.controller.GalleryEdit = __webpack_require__( 2038 );
10507  media.controller.GalleryAdd = __webpack_require__( 7127 );
10508  media.controller.CollectionEdit = __webpack_require__( 8612 );
10509  media.controller.CollectionAdd = __webpack_require__( 7145 );
10510  media.controller.FeaturedImage = __webpack_require__( 1169 );
10511  media.controller.ReplaceImage = __webpack_require__( 2275 );
10512  media.controller.EditImage = __webpack_require__( 5663 );
10513  media.controller.MediaLibrary = __webpack_require__( 8065 );
10514  media.controller.Embed = __webpack_require__( 4910 );
10515  media.controller.Cropper = __webpack_require__( 5422 );
10516  media.controller.CustomizeImageCropper = __webpack_require__( 9660 );
10517  media.controller.SiteIconCropper = __webpack_require__( 6172 );
10518  
10519  media.View = __webpack_require__( 4747 );
10520  media.view.Frame = __webpack_require__( 1061 );
10521  media.view.MediaFrame = __webpack_require__( 2836 );
10522  media.view.MediaFrame.Select = __webpack_require__( 455 );
10523  media.view.MediaFrame.Post = __webpack_require__( 4274 );
10524  media.view.MediaFrame.ImageDetails = __webpack_require__( 5424 );
10525  media.view.Modal = __webpack_require__( 2621 );
10526  media.view.FocusManager = __webpack_require__( 718 );
10527  media.view.UploaderWindow = __webpack_require__( 8291 );
10528  media.view.EditorUploader = __webpack_require__( 3674 );
10529  media.view.UploaderInline = __webpack_require__( 1753 );
10530  media.view.UploaderStatus = __webpack_require__( 8197 );
10531  media.view.UploaderStatusError = __webpack_require__( 6442 );
10532  media.view.Toolbar = __webpack_require__( 5275 );
10533  media.view.Toolbar.Select = __webpack_require__( 9458 );
10534  media.view.Toolbar.Embed = __webpack_require__( 397 );
10535  media.view.Button = __webpack_require__( 846 );
10536  media.view.ButtonGroup = __webpack_require__( 168 );
10537  media.view.PriorityList = __webpack_require__( 8815 );
10538  media.view.MenuItem = __webpack_require__( 9013 );
10539  media.view.Menu = __webpack_require__( 1 );
10540  media.view.RouterItem = __webpack_require__( 6327 );
10541  media.view.Router = __webpack_require__( 4783 );
10542  media.view.Sidebar = __webpack_require__( 1992 );
10543  media.view.Attachment = __webpack_require__( 4075 );
10544  media.view.Attachment.Library = __webpack_require__( 3443 );
10545  media.view.Attachment.EditLibrary = __webpack_require__( 5232 );
10546  media.view.Attachments = __webpack_require__( 8142 );
10547  media.view.Search = __webpack_require__( 2102 );
10548  media.view.AttachmentFilters = __webpack_require__( 7709 );
10549  media.view.DateFilter = __webpack_require__( 6472 );
10550  media.view.AttachmentFilters.Uploaded = __webpack_require__( 1368 );
10551  media.view.AttachmentFilters.All = __webpack_require__( 7349 );
10552  media.view.AttachmentsBrowser = __webpack_require__( 6829 );
10553  media.view.Selection = __webpack_require__( 8282 );
10554  media.view.Attachment.Selection = __webpack_require__( 3962 );
10555  media.view.Attachments.Selection = __webpack_require__( 3479 );
10556  media.view.Attachment.EditSelection = __webpack_require__( 4593 );
10557  media.view.Settings = __webpack_require__( 1915 );
10558  media.view.Settings.AttachmentDisplay = __webpack_require__( 7656 );
10559  media.view.Settings.Gallery = __webpack_require__( 7266 );
10560  media.view.Settings.Playlist = __webpack_require__( 2356 );
10561  media.view.Attachment.Details = __webpack_require__( 6090 );
10562  media.view.AttachmentCompat = __webpack_require__( 2982 );
10563  media.view.Iframe = __webpack_require__( 1982 );
10564  media.view.Embed = __webpack_require__( 5741 );
10565  media.view.Label = __webpack_require__( 4338 );
10566  media.view.EmbedUrl = __webpack_require__( 7327 );
10567  media.view.EmbedLink = __webpack_require__( 8232 );
10568  media.view.EmbedImage = __webpack_require__( 2395 );
10569  media.view.ImageDetails = __webpack_require__( 2650 );
10570  media.view.Cropper = __webpack_require__( 7637 );
10571  media.view.SiteIconCropper = __webpack_require__( 443 );
10572  media.view.SiteIconPreview = __webpack_require__( 7810 );
10573  media.view.EditImage = __webpack_require__( 6126 );
10574  media.view.Spinner = __webpack_require__( 9141 );
10575  media.view.Heading = __webpack_require__( 170 );
10576  
10577  /******/ })()
10578  ;


Generated : Sat Aug 15 08:20:25 2026 Cross-referenced by PHPXref