[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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