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