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