[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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