[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

title

Body

[close]

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

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