Make WordPress Core

Changeset 46499


Ignore:
Timestamp:
10/14/2019 07:11:50 PM (6 years ago)
Author:
whyisjake
Message:

Backporting several bug fixes.

  • Query: Remove the static query property.
  • HTTP API: Protect against hex interpretation.
  • Filesystem API: Prevent directory travelersals when creating new folders.
  • Administration: Ensure that admin referer nonce is valid.
  • REST API: Send a Vary: Origin header on GET requests.
  • Customizer: Properly sanitize background images.

Backports [46474], [46475], [46476], [46477], [46478], [46483], [46485] to the 4.3 branch.

Location:
branches/4.3
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • branches/4.3

  • branches/4.3/src/wp-includes/class-wp.php

    r44063 r46499  
    1616     * @var array
    1717     */
    18     public $public_query_vars = array('m', 'p', 'posts', 'w', 'cat', 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', 'name', 'category_name', 'tag', 'feed', 'author_name', 'static', 'pagename', 'page_id', 'error', 'comments_popup', 'attachment', 'attachment_id', 'subpost', 'subpost_id', 'preview', 'robots', 'taxonomy', 'term', 'cpage', 'post_type');
     18    public $public_query_vars = array( 'm', 'p', 'posts', 'w', 'cat', 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', 'name', 'category_name', 'tag', 'feed', 'author_name', 'pagename', 'page_id', 'error', 'comments_popup', 'attachment', 'attachment_id', 'subpost', 'subpost_id', 'preview', 'robots', 'taxonomy', 'term', 'cpage', 'post_type', 'embed' );
    1919
    2020    /**
  • branches/4.3/src/wp-includes/functions.php

    r43996 r46499  
    15071507    if ( file_exists( $target ) )
    15081508        return @is_dir( $target );
     1509
     1510    // Do not allow path traversals.
     1511    if ( false !== strpos( $target, '../' ) || false !== strpos( $target, '..' . DIRECTORY_SEPARATOR ) ) {
     1512        return false;
     1513    }
    15091514
    15101515    // We need to find the permissions of the parent folder that exists and inherit that.
  • branches/4.3/src/wp-includes/http.php

    r37117 r46499  
    474474        } else {
    475475            $ip = gethostbyname( $host );
    476             if ( $ip === $host ) // Error condition for gethostbyname()
    477                 $ip = false;
     476            if ( $ip === $host ) { // Error condition for gethostbyname()
     477                return false;
     478            }
    478479        }
    479480        if ( $ip ) {
  • branches/4.3/src/wp-includes/js/media-views.js

    r33337 r46499  
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     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, {
     40/******/                configurable: false,
     41/******/                enumerable: true,
     42/******/                get: getter
     43/******/            });
     44/******/        }
     45/******/    };
     46/******/
     47/******/    // getDefaultExport function for compatibility with non-harmony modules
     48/******/    __webpack_require__.n = function(module) {
     49/******/        var getter = module && module.__esModule ?
     50/******/            function getDefault() { return module['default']; } :
     51/******/            function getModuleExports() { return module; };
     52/******/        __webpack_require__.d(getter, 'a', getter);
     53/******/        return getter;
     54/******/    };
     55/******/
     56/******/    // Object.prototype.hasOwnProperty.call
     57/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/    // __webpack_public_path__
     60/******/    __webpack_require__.p = "";
     61/******/
     62/******/    // Load entry module and return exports
     63/******/    return __webpack_require__(__webpack_require__.s = 26);
     64/******/ })
     65/************************************************************************/
     66/******/ (Array(26).concat([
     67/* 26 */
     68/***/ (function(module, exports, __webpack_require__) {
     69
     70var media = wp.media,
     71    $ = jQuery,
     72    l10n;
     73
     74media.isTouchDevice = ( 'ontouchend' in document );
     75
     76// Link any localized strings.
     77l10n = media.view.l10n = window._wpMediaViewsL10n || {};
     78
     79// Link any settings.
     80media.view.settings = l10n.settings || {};
     81delete l10n.settings;
     82
     83// Copy the `post` setting over to the model settings.
     84media.model.settings.post = media.view.settings.post;
     85
     86// Check if the browser supports CSS 3.0 transitions
     87$.support.transition = (function(){
     88    var style = document.documentElement.style,
     89        transitions = {
     90            WebkitTransition: 'webkitTransitionEnd',
     91            MozTransition:    'transitionend',
     92            OTransition:      'oTransitionEnd otransitionend',
     93            transition:       'transitionend'
     94        }, transition;
     95
     96    transition = _.find( _.keys( transitions ), function( transition ) {
     97        return ! _.isUndefined( style[ transition ] );
     98    });
     99
     100    return transition && {
     101        end: transitions[ transition ]
     102    };
     103}());
     104
    2105/**
    3  * wp.media.controller.CollectionAdd
    4  *
    5  * A state for adding attachments to a collection (e.g. video playlist).
     106 * A shared event bus used to provide events into
     107 * the media workflows that 3rd-party devs can use to hook
     108 * in.
     109 */
     110media.events = _.extend( {}, Backbone.Events );
     111
     112/**
     113 * Makes it easier to bind events using transitions.
     114 *
     115 * @param {string} selector
     116 * @param {Number} sensitivity
     117 * @returns {Promise}
     118 */
     119media.transition = function( selector, sensitivity ) {
     120    var deferred = $.Deferred();
     121
     122    sensitivity = sensitivity || 2000;
     123
     124    if ( $.support.transition ) {
     125        if ( ! (selector instanceof $) ) {
     126            selector = $( selector );
     127        }
     128
     129        // Resolve the deferred when the first element finishes animating.
     130        selector.first().one( $.support.transition.end, deferred.resolve );
     131
     132        // Just in case the event doesn't trigger, fire a callback.
     133        _.delay( deferred.resolve, sensitivity );
     134
     135    // Otherwise, execute on the spot.
     136    } else {
     137        deferred.resolve();
     138    }
     139
     140    return deferred.promise();
     141};
     142
     143media.controller.Region = __webpack_require__( 27 );
     144media.controller.StateMachine = __webpack_require__( 28 );
     145media.controller.State = __webpack_require__( 29 );
     146
     147media.selectionSync = __webpack_require__( 30 );
     148media.controller.Library = __webpack_require__( 31 );
     149media.controller.ImageDetails = __webpack_require__( 32 );
     150media.controller.GalleryEdit = __webpack_require__( 33 );
     151media.controller.GalleryAdd = __webpack_require__( 34 );
     152media.controller.CollectionEdit = __webpack_require__( 35 );
     153media.controller.CollectionAdd = __webpack_require__( 36 );
     154media.controller.FeaturedImage = __webpack_require__( 37 );
     155media.controller.ReplaceImage = __webpack_require__( 38 );
     156media.controller.EditImage = __webpack_require__( 39 );
     157media.controller.MediaLibrary = __webpack_require__( 40 );
     158media.controller.Embed = __webpack_require__( 41 );
     159media.controller.Cropper = __webpack_require__( 42 );
     160media.controller.CustomizeImageCropper = __webpack_require__( 43 );
     161media.controller.SiteIconCropper = __webpack_require__( 44 );
     162
     163media.View = __webpack_require__( 45 );
     164media.view.Frame = __webpack_require__( 46 );
     165media.view.MediaFrame = __webpack_require__( 47 );
     166media.view.MediaFrame.Select = __webpack_require__( 48 );
     167media.view.MediaFrame.Post = __webpack_require__( 49 );
     168media.view.MediaFrame.ImageDetails = __webpack_require__( 50 );
     169media.view.Modal = __webpack_require__( 51 );
     170media.view.FocusManager = __webpack_require__( 52 );
     171media.view.UploaderWindow = __webpack_require__( 53 );
     172media.view.EditorUploader = __webpack_require__( 54 );
     173media.view.UploaderInline = __webpack_require__( 55 );
     174media.view.UploaderStatus = __webpack_require__( 56 );
     175media.view.UploaderStatusError = __webpack_require__( 57 );
     176media.view.Toolbar = __webpack_require__( 58 );
     177media.view.Toolbar.Select = __webpack_require__( 59 );
     178media.view.Toolbar.Embed = __webpack_require__( 60 );
     179media.view.Button = __webpack_require__( 61 );
     180media.view.ButtonGroup = __webpack_require__( 62 );
     181media.view.PriorityList = __webpack_require__( 63 );
     182media.view.MenuItem = __webpack_require__( 64 );
     183media.view.Menu = __webpack_require__( 65 );
     184media.view.RouterItem = __webpack_require__( 66 );
     185media.view.Router = __webpack_require__( 67 );
     186media.view.Sidebar = __webpack_require__( 68 );
     187media.view.Attachment = __webpack_require__( 69 );
     188media.view.Attachment.Library = __webpack_require__( 70 );
     189media.view.Attachment.EditLibrary = __webpack_require__( 71 );
     190media.view.Attachments = __webpack_require__( 72 );
     191media.view.Search = __webpack_require__( 73 );
     192media.view.AttachmentFilters = __webpack_require__( 74 );
     193media.view.DateFilter = __webpack_require__( 75 );
     194media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 );
     195media.view.AttachmentFilters.All = __webpack_require__( 77 );
     196media.view.AttachmentsBrowser = __webpack_require__( 78 );
     197media.view.Selection = __webpack_require__( 79 );
     198media.view.Attachment.Selection = __webpack_require__( 80 );
     199media.view.Attachments.Selection = __webpack_require__( 81 );
     200media.view.Attachment.EditSelection = __webpack_require__( 82 );
     201media.view.Settings = __webpack_require__( 83 );
     202media.view.Settings.AttachmentDisplay = __webpack_require__( 84 );
     203media.view.Settings.Gallery = __webpack_require__( 85 );
     204media.view.Settings.Playlist = __webpack_require__( 86 );
     205media.view.Attachment.Details = __webpack_require__( 87 );
     206media.view.AttachmentCompat = __webpack_require__( 88 );
     207media.view.Iframe = __webpack_require__( 89 );
     208media.view.Embed = __webpack_require__( 90 );
     209media.view.Label = __webpack_require__( 91 );
     210media.view.EmbedUrl = __webpack_require__( 92 );
     211media.view.EmbedLink = __webpack_require__( 93 );
     212media.view.EmbedImage = __webpack_require__( 94 );
     213media.view.ImageDetails = __webpack_require__( 95 );
     214media.view.Cropper = __webpack_require__( 96 );
     215media.view.SiteIconCropper = __webpack_require__( 97 );
     216media.view.SiteIconPreview = __webpack_require__( 98 );
     217media.view.EditImage = __webpack_require__( 99 );
     218media.view.Spinner = __webpack_require__( 100 );
     219
     220
     221/***/ }),
     222/* 27 */
     223/***/ (function(module, exports) {
     224
     225/**
     226 * wp.media.controller.Region
     227 *
     228 * A region is a persistent application layout area.
     229 *
     230 * A region assumes one mode at any time, and can be switched to another.
     231 *
     232 * When mode changes, events are triggered on the region's parent view.
     233 * The parent view will listen to specific events and fill the region with an
     234 * appropriate view depending on mode. For example, a frame listens for the
     235 * 'browse' mode t be activated on the 'content' view and then fills the region
     236 * with an AttachmentsBrowser view.
     237 *
     238 * @class
     239 *
     240 * @param {object}        options          Options hash for the region.
     241 * @param {string}        options.id       Unique identifier for the region.
     242 * @param {Backbone.View} options.view     A parent view the region exists within.
     243 * @param {string}        options.selector jQuery selector for the region within the parent view.
     244 */
     245var Region = function( options ) {
     246    _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
     247};
     248
     249// Use Backbone's self-propagating `extend` inheritance method.
     250Region.extend = Backbone.Model.extend;
     251
     252_.extend( Region.prototype, {
     253    /**
     254     * Activate a mode.
     255     *
     256     * @since 3.5.0
     257     *
     258     * @param {string} mode
     259     *
     260     * @fires this.view#{this.id}:activate:{this._mode}
     261     * @fires this.view#{this.id}:activate
     262     * @fires this.view#{this.id}:deactivate:{this._mode}
     263     * @fires this.view#{this.id}:deactivate
     264     *
     265     * @returns {wp.media.controller.Region} Returns itself to allow chaining.
     266     */
     267    mode: function( mode ) {
     268        if ( ! mode ) {
     269            return this._mode;
     270        }
     271        // Bail if we're trying to change to the current mode.
     272        if ( mode === this._mode ) {
     273            return this;
     274        }
     275
     276        /**
     277         * Region mode deactivation event.
     278         *
     279         * @event this.view#{this.id}:deactivate:{this._mode}
     280         * @event this.view#{this.id}:deactivate
     281         */
     282        this.trigger('deactivate');
     283
     284        this._mode = mode;
     285        this.render( mode );
     286
     287        /**
     288         * Region mode activation event.
     289         *
     290         * @event this.view#{this.id}:activate:{this._mode}
     291         * @event this.view#{this.id}:activate
     292         */
     293        this.trigger('activate');
     294        return this;
     295    },
     296    /**
     297     * Render a mode.
     298     *
     299     * @since 3.5.0
     300     *
     301     * @param {string} mode
     302     *
     303     * @fires this.view#{this.id}:create:{this._mode}
     304     * @fires this.view#{this.id}:create
     305     * @fires this.view#{this.id}:render:{this._mode}
     306     * @fires this.view#{this.id}:render
     307     *
     308     * @returns {wp.media.controller.Region} Returns itself to allow chaining
     309     */
     310    render: function( mode ) {
     311        // If the mode isn't active, activate it.
     312        if ( mode && mode !== this._mode ) {
     313            return this.mode( mode );
     314        }
     315
     316        var set = { view: null },
     317            view;
     318
     319        /**
     320         * Create region view event.
     321         *
     322         * Region view creation takes place in an event callback on the frame.
     323         *
     324         * @event this.view#{this.id}:create:{this._mode}
     325         * @event this.view#{this.id}:create
     326         */
     327        this.trigger( 'create', set );
     328        view = set.view;
     329
     330        /**
     331         * Render region view event.
     332         *
     333         * Region view creation takes place in an event callback on the frame.
     334         *
     335         * @event this.view#{this.id}:create:{this._mode}
     336         * @event this.view#{this.id}:create
     337         */
     338        this.trigger( 'render', view );
     339        if ( view ) {
     340            this.set( view );
     341        }
     342        return this;
     343    },
     344
     345    /**
     346     * Get the region's view.
     347     *
     348     * @since 3.5.0
     349     *
     350     * @returns {wp.media.View}
     351     */
     352    get: function() {
     353        return this.view.views.first( this.selector );
     354    },
     355
     356    /**
     357     * Set the region's view as a subview of the frame.
     358     *
     359     * @since 3.5.0
     360     *
     361     * @param {Array|Object} views
     362     * @param {Object} [options={}]
     363     * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
     364     */
     365    set: function( views, options ) {
     366        if ( options ) {
     367            options.add = false;
     368        }
     369        return this.view.views.set( this.selector, views, options );
     370    },
     371
     372    /**
     373     * Trigger regional view events on the frame.
     374     *
     375     * @since 3.5.0
     376     *
     377     * @param {string} event
     378     * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
     379     */
     380    trigger: function( event ) {
     381        var base, args;
     382
     383        if ( ! this._mode ) {
     384            return;
     385        }
     386
     387        args = _.toArray( arguments );
     388        base = this.id + ':' + event;
     389
     390        // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
     391        args[0] = base + ':' + this._mode;
     392        this.view.trigger.apply( this.view, args );
     393
     394        // Trigger `{this.id}:{event}` event on the frame.
     395        args[0] = base;
     396        this.view.trigger.apply( this.view, args );
     397        return this;
     398    }
     399});
     400
     401module.exports = Region;
     402
     403
     404/***/ }),
     405/* 28 */
     406/***/ (function(module, exports) {
     407
     408/**
     409 * wp.media.controller.StateMachine
     410 *
     411 * A state machine keeps track of state. It is in one state at a time,
     412 * and can change from one state to another.
     413 *
     414 * States are stored as models in a Backbone collection.
     415 *
     416 * @since 3.5.0
     417 *
     418 * @class
     419 * @augments Backbone.Model
     420 * @mixin
     421 * @mixes Backbone.Events
     422 *
     423 * @param {Array} states
     424 */
     425var StateMachine = function( states ) {
     426    // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
     427    this.states = new Backbone.Collection( states );
     428};
     429
     430// Use Backbone's self-propagating `extend` inheritance method.
     431StateMachine.extend = Backbone.Model.extend;
     432
     433_.extend( StateMachine.prototype, Backbone.Events, {
     434    /**
     435     * Fetch a state.
     436     *
     437     * If no `id` is provided, returns the active state.
     438     *
     439     * Implicitly creates states.
     440     *
     441     * Ensure that the `states` collection exists so the `StateMachine`
     442     *   can be used as a mixin.
     443     *
     444     * @since 3.5.0
     445     *
     446     * @param {string} id
     447     * @returns {wp.media.controller.State} Returns a State model
     448     *   from the StateMachine collection
     449     */
     450    state: function( id ) {
     451        this.states = this.states || new Backbone.Collection();
     452
     453        // Default to the active state.
     454        id = id || this._state;
     455
     456        if ( id && ! this.states.get( id ) ) {
     457            this.states.add({ id: id });
     458        }
     459        return this.states.get( id );
     460    },
     461
     462    /**
     463     * Sets the active state.
     464     *
     465     * Bail if we're trying to select the current state, if we haven't
     466     * created the `states` collection, or are trying to select a state
     467     * that does not exist.
     468     *
     469     * @since 3.5.0
     470     *
     471     * @param {string} id
     472     *
     473     * @fires wp.media.controller.State#deactivate
     474     * @fires wp.media.controller.State#activate
     475     *
     476     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
     477     */
     478    setState: function( id ) {
     479        var previous = this.state();
     480
     481        if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
     482            return this;
     483        }
     484
     485        if ( previous ) {
     486            previous.trigger('deactivate');
     487            this._lastState = previous.id;
     488        }
     489
     490        this._state = id;
     491        this.state().trigger('activate');
     492
     493        return this;
     494    },
     495
     496    /**
     497     * Returns the previous active state.
     498     *
     499     * Call the `state()` method with no parameters to retrieve the current
     500     * active state.
     501     *
     502     * @since 3.5.0
     503     *
     504     * @returns {wp.media.controller.State} Returns a State model
     505     *    from the StateMachine collection
     506     */
     507    lastState: function() {
     508        if ( this._lastState ) {
     509            return this.state( this._lastState );
     510        }
     511    }
     512});
     513
     514// Map all event binding and triggering on a StateMachine to its `states` collection.
     515_.each([ 'on', 'off', 'trigger' ], function( method ) {
     516    /**
     517     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     518     */
     519    StateMachine.prototype[ method ] = function() {
     520        // Ensure that the `states` collection exists so the `StateMachine`
     521        // can be used as a mixin.
     522        this.states = this.states || new Backbone.Collection();
     523        // Forward the method to the `states` collection.
     524        this.states[ method ].apply( this.states, arguments );
     525        return this;
     526    };
     527});
     528
     529module.exports = StateMachine;
     530
     531
     532/***/ }),
     533/* 29 */
     534/***/ (function(module, exports) {
     535
     536/**
     537 * wp.media.controller.State
     538 *
     539 * A state is a step in a workflow that when set will trigger the controllers
     540 * for the regions to be updated as specified in the frame.
     541 *
     542 * A state has an event-driven lifecycle:
     543 *
     544 *     'ready'      triggers when a state is added to a state machine's collection.
     545 *     'activate'   triggers when a state is activated by a state machine.
     546 *     'deactivate' triggers when a state is deactivated by a state machine.
     547 *     'reset'      is not triggered automatically. It should be invoked by the
     548 *                  proper controller to reset the state to its default.
     549 *
     550 * @class
     551 * @augments Backbone.Model
     552 */
     553var State = Backbone.Model.extend({
     554    /**
     555     * Constructor.
     556     *
     557     * @since 3.5.0
     558     */
     559    constructor: function() {
     560        this.on( 'activate', this._preActivate, this );
     561        this.on( 'activate', this.activate, this );
     562        this.on( 'activate', this._postActivate, this );
     563        this.on( 'deactivate', this._deactivate, this );
     564        this.on( 'deactivate', this.deactivate, this );
     565        this.on( 'reset', this.reset, this );
     566        this.on( 'ready', this._ready, this );
     567        this.on( 'ready', this.ready, this );
     568        /**
     569         * Call parent constructor with passed arguments
     570         */
     571        Backbone.Model.apply( this, arguments );
     572        this.on( 'change:menu', this._updateMenu, this );
     573    },
     574    /**
     575     * Ready event callback.
     576     *
     577     * @abstract
     578     * @since 3.5.0
     579     */
     580    ready: function() {},
     581
     582    /**
     583     * Activate event callback.
     584     *
     585     * @abstract
     586     * @since 3.5.0
     587     */
     588    activate: function() {},
     589
     590    /**
     591     * Deactivate event callback.
     592     *
     593     * @abstract
     594     * @since 3.5.0
     595     */
     596    deactivate: function() {},
     597
     598    /**
     599     * Reset event callback.
     600     *
     601     * @abstract
     602     * @since 3.5.0
     603     */
     604    reset: function() {},
     605
     606    /**
     607     * @access private
     608     * @since 3.5.0
     609     */
     610    _ready: function() {
     611        this._updateMenu();
     612    },
     613
     614    /**
     615     * @access private
     616     * @since 3.5.0
     617    */
     618    _preActivate: function() {
     619        this.active = true;
     620    },
     621
     622    /**
     623     * @access private
     624     * @since 3.5.0
     625     */
     626    _postActivate: function() {
     627        this.on( 'change:menu', this._menu, this );
     628        this.on( 'change:titleMode', this._title, this );
     629        this.on( 'change:content', this._content, this );
     630        this.on( 'change:toolbar', this._toolbar, this );
     631
     632        this.frame.on( 'title:render:default', this._renderTitle, this );
     633
     634        this._title();
     635        this._menu();
     636        this._toolbar();
     637        this._content();
     638        this._router();
     639    },
     640
     641    /**
     642     * @access private
     643     * @since 3.5.0
     644     */
     645    _deactivate: function() {
     646        this.active = false;
     647
     648        this.frame.off( 'title:render:default', this._renderTitle, this );
     649
     650        this.off( 'change:menu', this._menu, this );
     651        this.off( 'change:titleMode', this._title, this );
     652        this.off( 'change:content', this._content, this );
     653        this.off( 'change:toolbar', this._toolbar, this );
     654    },
     655
     656    /**
     657     * @access private
     658     * @since 3.5.0
     659     */
     660    _title: function() {
     661        this.frame.title.render( this.get('titleMode') || 'default' );
     662    },
     663
     664    /**
     665     * @access private
     666     * @since 3.5.0
     667     */
     668    _renderTitle: function( view ) {
     669        view.$el.text( this.get('title') || '' );
     670    },
     671
     672    /**
     673     * @access private
     674     * @since 3.5.0
     675     */
     676    _router: function() {
     677        var router = this.frame.router,
     678            mode = this.get('router'),
     679            view;
     680
     681        this.frame.$el.toggleClass( 'hide-router', ! mode );
     682        if ( ! mode ) {
     683            return;
     684        }
     685
     686        this.frame.router.render( mode );
     687
     688        view = router.get();
     689        if ( view && view.select ) {
     690            view.select( this.frame.content.mode() );
     691        }
     692    },
     693
     694    /**
     695     * @access private
     696     * @since 3.5.0
     697     */
     698    _menu: function() {
     699        var menu = this.frame.menu,
     700            mode = this.get('menu'),
     701            view;
     702
     703        this.frame.$el.toggleClass( 'hide-menu', ! mode );
     704        if ( ! mode ) {
     705            return;
     706        }
     707
     708        menu.mode( mode );
     709
     710        view = menu.get();
     711        if ( view && view.select ) {
     712            view.select( this.id );
     713        }
     714    },
     715
     716    /**
     717     * @access private
     718     * @since 3.5.0
     719     */
     720    _updateMenu: function() {
     721        var previous = this.previous('menu'),
     722            menu = this.get('menu');
     723
     724        if ( previous ) {
     725            this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
     726        }
     727
     728        if ( menu ) {
     729            this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
     730        }
     731    },
     732
     733    /**
     734     * Create a view in the media menu for the state.
     735     *
     736     * @access private
     737     * @since 3.5.0
     738     *
     739     * @param {media.view.Menu} view The menu view.
     740     */
     741    _renderMenu: function( view ) {
     742        var menuItem = this.get('menuItem'),
     743            title = this.get('title'),
     744            priority = this.get('priority');
     745
     746        if ( ! menuItem && title ) {
     747            menuItem = { text: title };
     748
     749            if ( priority ) {
     750                menuItem.priority = priority;
     751            }
     752        }
     753
     754        if ( ! menuItem ) {
     755            return;
     756        }
     757
     758        view.set( this.id, menuItem );
     759    }
     760});
     761
     762_.each(['toolbar','content'], function( region ) {
     763    /**
     764     * @access private
     765     */
     766    State.prototype[ '_' + region ] = function() {
     767        var mode = this.get( region );
     768        if ( mode ) {
     769            this.frame[ region ].render( mode );
     770        }
     771    };
     772});
     773
     774module.exports = State;
     775
     776
     777/***/ }),
     778/* 30 */
     779/***/ (function(module, exports) {
     780
     781/**
     782 * wp.media.selectionSync
     783 *
     784 * Sync an attachments selection in a state with another state.
     785 *
     786 * Allows for selecting multiple images in the Insert Media workflow, and then
     787 * switching to the Insert Gallery workflow while preserving the attachments selection.
     788 *
     789 * @mixin
     790 */
     791var selectionSync = {
     792    /**
     793     * @since 3.5.0
     794     */
     795    syncSelection: function() {
     796        var selection = this.get('selection'),
     797            manager = this.frame._selection;
     798
     799        if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     800            return;
     801        }
     802
     803        // If the selection supports multiple items, validate the stored
     804        // attachments based on the new selection's conditions. Record
     805        // the attachments that are not included; we'll maintain a
     806        // reference to those. Other attachments are considered in flux.
     807        if ( selection.multiple ) {
     808            selection.reset( [], { silent: true });
     809            selection.validateAll( manager.attachments );
     810            manager.difference = _.difference( manager.attachments.models, selection.models );
     811        }
     812
     813        // Sync the selection's single item with the master.
     814        selection.single( manager.single );
     815    },
     816
     817    /**
     818     * Record the currently active attachments, which is a combination
     819     * of the selection's attachments and the set of selected
     820     * attachments that this specific selection considered invalid.
     821     * Reset the difference and record the single attachment.
     822     *
     823     * @since 3.5.0
     824     */
     825    recordSelection: function() {
     826        var selection = this.get('selection'),
     827            manager = this.frame._selection;
     828
     829        if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     830            return;
     831        }
     832
     833        if ( selection.multiple ) {
     834            manager.attachments.reset( selection.toArray().concat( manager.difference ) );
     835            manager.difference = [];
     836        } else {
     837            manager.attachments.add( selection.toArray() );
     838        }
     839
     840        manager.single = selection._single;
     841    }
     842};
     843
     844module.exports = selectionSync;
     845
     846
     847/***/ }),
     848/* 31 */
     849/***/ (function(module, exports) {
     850
     851/**
     852 * wp.media.controller.Library
     853 *
     854 * A state for choosing an attachment or group of attachments from the media library.
     855 *
     856 * @class
     857 * @augments wp.media.controller.State
     858 * @augments Backbone.Model
     859 * @mixes media.selectionSync
     860 *
     861 * @param {object}                          [attributes]                         The attributes hash passed to the state.
     862 * @param {string}                          [attributes.id=library]              Unique identifier.
     863 * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
     864 * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
     865 *                                                                               If one is not supplied, a collection of all attachments will be created.
     866 * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
     867 *                                                                               If the 'selection' attribute is a plain JS object,
     868 *                                                                               a Selection will be created using its values as the selection instance's `props` model.
     869 *                                                                               Otherwise, it will copy the library's `props` model.
     870 * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
     871 * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
     872 *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
     873 * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
     874 * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
     875 * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
     876 * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
     877 * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
     878 *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
     879 * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     880 * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     881 * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     882 * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     883 * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
     884 */
     885var l10n = wp.media.view.l10n,
     886    getUserSetting = window.getUserSetting,
     887    setUserSetting = window.setUserSetting,
     888    Library;
     889
     890Library = wp.media.controller.State.extend({
     891    defaults: {
     892        id:                 'library',
     893        title:              l10n.mediaLibraryTitle,
     894        multiple:           false,
     895        content:            'upload',
     896        menu:               'default',
     897        router:             'browse',
     898        toolbar:            'select',
     899        searchable:         true,
     900        filterable:         false,
     901        sortable:           true,
     902        autoSelect:         true,
     903        describe:           false,
     904        contentUserSetting: true,
     905        syncSelection:      true
     906    },
     907
     908    /**
     909     * If a library isn't provided, query all media items.
     910     * If a selection instance isn't provided, create one.
     911     *
     912     * @since 3.5.0
     913     */
     914    initialize: function() {
     915        var selection = this.get('selection'),
     916            props;
     917
     918        if ( ! this.get('library') ) {
     919            this.set( 'library', wp.media.query() );
     920        }
     921
     922        if ( ! ( selection instanceof wp.media.model.Selection ) ) {
     923            props = selection;
     924
     925            if ( ! props ) {
     926                props = this.get('library').props.toJSON();
     927                props = _.omit( props, 'orderby', 'query' );
     928            }
     929
     930            this.set( 'selection', new wp.media.model.Selection( null, {
     931                multiple: this.get('multiple'),
     932                props: props
     933            }) );
     934        }
     935
     936        this.resetDisplays();
     937    },
     938
     939    /**
     940     * @since 3.5.0
     941     */
     942    activate: function() {
     943        this.syncSelection();
     944
     945        wp.Uploader.queue.on( 'add', this.uploading, this );
     946
     947        this.get('selection').on( 'add remove reset', this.refreshContent, this );
     948
     949        if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
     950            this.frame.on( 'content:activate', this.saveContentMode, this );
     951            this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
     952        }
     953    },
     954
     955    /**
     956     * @since 3.5.0
     957     */
     958    deactivate: function() {
     959        this.recordSelection();
     960
     961        this.frame.off( 'content:activate', this.saveContentMode, this );
     962
     963        // Unbind all event handlers that use this state as the context
     964        // from the selection.
     965        this.get('selection').off( null, null, this );
     966
     967        wp.Uploader.queue.off( null, null, this );
     968    },
     969
     970    /**
     971     * Reset the library to its initial state.
     972     *
     973     * @since 3.5.0
     974     */
     975    reset: function() {
     976        this.get('selection').reset();
     977        this.resetDisplays();
     978        this.refreshContent();
     979    },
     980
     981    /**
     982     * Reset the attachment display settings defaults to the site options.
     983     *
     984     * If site options don't define them, fall back to a persistent user setting.
     985     *
     986     * @since 3.5.0
     987     */
     988    resetDisplays: function() {
     989        var defaultProps = wp.media.view.settings.defaultProps;
     990        this._displays = [];
     991        this._defaultDisplaySettings = {
     992            align: defaultProps.align || getUserSetting( 'align', 'none' ),
     993            size:  defaultProps.size  || getUserSetting( 'imgsize', 'medium' ),
     994            link:  defaultProps.link  || getUserSetting( 'urlbutton', 'file' )
     995        };
     996    },
     997
     998    /**
     999     * Create a model to represent display settings (alignment, etc.) for an attachment.
     1000     *
     1001     * @since 3.5.0
     1002     *
     1003     * @param {wp.media.model.Attachment} attachment
     1004     * @returns {Backbone.Model}
     1005     */
     1006    display: function( attachment ) {
     1007        var displays = this._displays;
     1008
     1009        if ( ! displays[ attachment.cid ] ) {
     1010            displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
     1011        }
     1012        return displays[ attachment.cid ];
     1013    },
     1014
     1015    /**
     1016     * Given an attachment, create attachment display settings properties.
     1017     *
     1018     * @since 3.6.0
     1019     *
     1020     * @param {wp.media.model.Attachment} attachment
     1021     * @returns {Object}
     1022     */
     1023    defaultDisplaySettings: function( attachment ) {
     1024        var settings = this._defaultDisplaySettings;
     1025        if ( settings.canEmbed = this.canEmbed( attachment ) ) {
     1026            settings.link = 'embed';
     1027        }
     1028        return settings;
     1029    },
     1030
     1031    /**
     1032     * Whether an attachment can be embedded (audio or video).
     1033     *
     1034     * @since 3.6.0
     1035     *
     1036     * @param {wp.media.model.Attachment} attachment
     1037     * @returns {Boolean}
     1038     */
     1039    canEmbed: function( attachment ) {
     1040        // If uploading, we know the filename but not the mime type.
     1041        if ( ! attachment.get('uploading') ) {
     1042            var type = attachment.get('type');
     1043            if ( type !== 'audio' && type !== 'video' ) {
     1044                return false;
     1045            }
     1046        }
     1047
     1048        return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
     1049    },
     1050
     1051
     1052    /**
     1053     * If the state is active, no items are selected, and the current
     1054     * content mode is not an option in the state's router (provided
     1055     * the state has a router), reset the content mode to the default.
     1056     *
     1057     * @since 3.5.0
     1058     */
     1059    refreshContent: function() {
     1060        var selection = this.get('selection'),
     1061            frame = this.frame,
     1062            router = frame.router.get(),
     1063            mode = frame.content.mode();
     1064
     1065        if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
     1066            this.frame.content.render( this.get('content') );
     1067        }
     1068    },
     1069
     1070    /**
     1071     * Callback handler when an attachment is uploaded.
     1072     *
     1073     * Switch to the Media Library if uploaded from the 'Upload Files' tab.
     1074     *
     1075     * Adds any uploading attachments to the selection.
     1076     *
     1077     * If the state only supports one attachment to be selected and multiple
     1078     * attachments are uploaded, the last attachment in the upload queue will
     1079     * be selected.
     1080     *
     1081     * @since 3.5.0
     1082     *
     1083     * @param {wp.media.model.Attachment} attachment
     1084     */
     1085    uploading: function( attachment ) {
     1086        var content = this.frame.content;
     1087
     1088        if ( 'upload' === content.mode() ) {
     1089            this.frame.content.mode('browse');
     1090        }
     1091
     1092        if ( this.get( 'autoSelect' ) ) {
     1093            this.get('selection').add( attachment );
     1094            this.frame.trigger( 'library:selection:add' );
     1095        }
     1096    },
     1097
     1098    /**
     1099     * Persist the mode of the content region as a user setting.
     1100     *
     1101     * @since 3.5.0
     1102     */
     1103    saveContentMode: function() {
     1104        if ( 'browse' !== this.get('router') ) {
     1105            return;
     1106        }
     1107
     1108        var mode = this.frame.content.mode(),
     1109            view = this.frame.router.get();
     1110
     1111        if ( view && view.get( mode ) ) {
     1112            setUserSetting( 'libraryContent', mode );
     1113        }
     1114    }
     1115});
     1116
     1117// Make selectionSync available on any Media Library state.
     1118_.extend( Library.prototype, wp.media.selectionSync );
     1119
     1120module.exports = Library;
     1121
     1122
     1123/***/ }),
     1124/* 32 */
     1125/***/ (function(module, exports) {
     1126
     1127/**
     1128 * wp.media.controller.ImageDetails
     1129 *
     1130 * A state for editing the attachment display settings of an image that's been
     1131 * inserted into the editor.
     1132 *
     1133 * @class
     1134 * @augments wp.media.controller.State
     1135 * @augments Backbone.Model
     1136 *
     1137 * @param {object}                    [attributes]                       The attributes hash passed to the state.
     1138 * @param {string}                    [attributes.id=image-details]      Unique identifier.
     1139 * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
     1140 * @param {wp.media.model.Attachment} attributes.image                   The image's model.
     1141 * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
     1142 * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
     1143 * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
     1144 * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
     1145 * @param {boolean}                   [attributes.editing=false]         Unused.
     1146 * @param {int}                       [attributes.priority=60]           Unused.
     1147 *
     1148 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
     1149 *       however this may not do anything.
     1150 */
     1151var State = wp.media.controller.State,
     1152    Library = wp.media.controller.Library,
     1153    l10n = wp.media.view.l10n,
     1154    ImageDetails;
     1155
     1156ImageDetails = State.extend({
     1157    defaults: _.defaults({
     1158        id:       'image-details',
     1159        title:    l10n.imageDetailsTitle,
     1160        content:  'image-details',
     1161        menu:     false,
     1162        router:   false,
     1163        toolbar:  'image-details',
     1164        editing:  false,
     1165        priority: 60
     1166    }, Library.prototype.defaults ),
     1167
     1168    /**
     1169     * @since 3.9.0
     1170     *
     1171     * @param options Attributes
     1172     */
     1173    initialize: function( options ) {
     1174        this.image = options.image;
     1175        State.prototype.initialize.apply( this, arguments );
     1176    },
     1177
     1178    /**
     1179     * @since 3.9.0
     1180     */
     1181    activate: function() {
     1182        this.frame.modal.$el.addClass('image-details');
     1183    }
     1184});
     1185
     1186module.exports = ImageDetails;
     1187
     1188
     1189/***/ }),
     1190/* 33 */
     1191/***/ (function(module, exports) {
     1192
     1193/**
     1194 * wp.media.controller.GalleryEdit
     1195 *
     1196 * A state for editing a gallery's images and settings.
    61197 *
    71198 * @class
     
    101201 * @augments Backbone.Model
    111202 *
     1203 * @param {object}                     [attributes]                       The attributes hash passed to the state.
     1204 * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
     1205 * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
     1206 * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
     1207 *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
     1208 * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
     1209 * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
     1210 * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1211 * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
     1212 * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
     1213 * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
     1214 * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     1215 * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
     1216 * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
     1217 * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
     1218 * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
     1219 * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
     1220 * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
     1221 *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
     1222 * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
     1223 *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     1224 */
     1225var Library = wp.media.controller.Library,
     1226    l10n = wp.media.view.l10n,
     1227    GalleryEdit;
     1228
     1229GalleryEdit = Library.extend({
     1230    defaults: {
     1231        id:               'gallery-edit',
     1232        title:            l10n.editGalleryTitle,
     1233        multiple:         false,
     1234        searchable:       false,
     1235        sortable:         true,
     1236        date:             false,
     1237        display:          false,
     1238        content:          'browse',
     1239        toolbar:          'gallery-edit',
     1240        describe:         true,
     1241        displaySettings:  true,
     1242        dragInfo:         true,
     1243        idealColumnWidth: 170,
     1244        editing:          false,
     1245        priority:         60,
     1246        syncSelection:    false
     1247    },
     1248
     1249    /**
     1250     * @since 3.5.0
     1251     */
     1252    initialize: function() {
     1253        // If we haven't been provided a `library`, create a `Selection`.
     1254        if ( ! this.get('library') ) {
     1255            this.set( 'library', new wp.media.model.Selection() );
     1256        }
     1257
     1258        // The single `Attachment` view to be used in the `Attachments` view.
     1259        if ( ! this.get('AttachmentView') ) {
     1260            this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     1261        }
     1262
     1263        Library.prototype.initialize.apply( this, arguments );
     1264    },
     1265
     1266    /**
     1267     * @since 3.5.0
     1268     */
     1269    activate: function() {
     1270        var library = this.get('library');
     1271
     1272        // Limit the library to images only.
     1273        library.props.set( 'type', 'image' );
     1274
     1275        // Watch for uploaded attachments.
     1276        this.get('library').observe( wp.Uploader.queue );
     1277
     1278        this.frame.on( 'content:render:browse', this.gallerySettings, this );
     1279
     1280        Library.prototype.activate.apply( this, arguments );
     1281    },
     1282
     1283    /**
     1284     * @since 3.5.0
     1285     */
     1286    deactivate: function() {
     1287        // Stop watching for uploaded attachments.
     1288        this.get('library').unobserve( wp.Uploader.queue );
     1289
     1290        this.frame.off( 'content:render:browse', this.gallerySettings, this );
     1291
     1292        Library.prototype.deactivate.apply( this, arguments );
     1293    },
     1294
     1295    /**
     1296     * @since 3.5.0
     1297     *
     1298     * @param browser
     1299     */
     1300    gallerySettings: function( browser ) {
     1301        if ( ! this.get('displaySettings') ) {
     1302            return;
     1303        }
     1304
     1305        var library = this.get('library');
     1306
     1307        if ( ! library || ! browser ) {
     1308            return;
     1309        }
     1310
     1311        library.gallery = library.gallery || new Backbone.Model();
     1312
     1313        browser.sidebar.set({
     1314            gallery: new wp.media.view.Settings.Gallery({
     1315                controller: this,
     1316                model:      library.gallery,
     1317                priority:   40
     1318            })
     1319        });
     1320
     1321        browser.toolbar.set( 'reverse', {
     1322            text:     l10n.reverseOrder,
     1323            priority: 80,
     1324
     1325            click: function() {
     1326                library.reset( library.toArray().reverse() );
     1327            }
     1328        });
     1329    }
     1330});
     1331
     1332module.exports = GalleryEdit;
     1333
     1334
     1335/***/ }),
     1336/* 34 */
     1337/***/ (function(module, exports) {
     1338
     1339/**
     1340 * wp.media.controller.GalleryAdd
     1341 *
     1342 * A state for selecting more images to add to a gallery.
     1343 *
     1344 * @class
     1345 * @augments wp.media.controller.Library
     1346 * @augments wp.media.controller.State
     1347 * @augments Backbone.Model
     1348 *
    121349 * @param {object}                     [attributes]                         The attributes hash passed to the state.
    13  * @param {string}                     [attributes.id=library]      Unique identifier.
    14  * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
     1350 * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
     1351 * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
    151352 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    161353 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    17  *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
     1354 *                                                                          If one is not supplied, a collection of all images will be created.
    181355 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    191356 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     
    301367 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    311368 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    32  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    33  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
    341369 */
    351370var Selection = wp.media.model.Selection,
    361371    Library = wp.media.controller.Library,
    37     CollectionAdd;
    38 
    39 CollectionAdd = Library.extend({
    40     defaults: _.defaults( {
    41         // Selection defaults. @see media.model.Selection
     1372    l10n = wp.media.view.l10n,
     1373    GalleryAdd;
     1374
     1375GalleryAdd = Library.extend({
     1376    defaults: _.defaults({
     1377        id:            'gallery-library',
     1378        title:         l10n.addToGalleryTitle,
    421379        multiple:      'add',
    43         // Attachments browser defaults. @see media.view.AttachmentsBrowser
    441380        filterable:    'uploaded',
    45 
     1381        menu:          'gallery',
     1382        toolbar:       'gallery-add',
    461383        priority:      100,
    471384        syncSelection: false
     
    491386
    501387    /**
    51      * @since 3.9.0
     1388     * @since 3.5.0
    521389     */
    531390    initialize: function() {
    54         var collectionType = this.get('collectionType');
    55 
    56         if ( 'video' === this.get( 'type' ) ) {
    57             collectionType = 'video-' + collectionType;
    58         }
    59 
    60         this.set( 'id', collectionType + '-library' );
    61         this.set( 'toolbar', collectionType + '-add' );
    62         this.set( 'menu', collectionType );
    63 
    64         // If we haven't been provided a `library`, create a `Selection`.
     1391        // If a library wasn't supplied, create a library of images.
    651392        if ( ! this.get('library') ) {
    66             this.set( 'library', wp.media.query({ type: this.get('type') }) );
    67         }
     1393            this.set( 'library', wp.media.query({ type: 'image' }) );
     1394        }
     1395
    681396        Library.prototype.initialize.apply( this, arguments );
    691397    },
    701398
    711399    /**
    72      * @since 3.9.0
     1400     * @since 3.5.0
    731401     */
    741402    activate: function() {
    751403        var library = this.get('library'),
    76             editLibrary = this.get('editLibrary'),
    77             edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
    78 
    79         if ( editLibrary && editLibrary !== edit ) {
    80             library.unobserve( editLibrary );
     1404            edit    = this.frame.state('gallery-edit').get('library');
     1405
     1406        if ( this.editLibrary && this.editLibrary !== edit ) {
     1407            library.unobserve( this.editLibrary );
    811408        }
    821409
     
    921419        library.reset( library.mirroring.models, { silent: true });
    931420        library.observe( edit );
    94         this.set('editLibrary', edit);
     1421        this.editLibrary = edit;
    951422
    961423        Library.prototype.activate.apply( this, arguments );
     
    981425});
    991426
    100 module.exports = CollectionAdd;
    101 
    102 },{}],2:[function(require,module,exports){
     1427module.exports = GalleryAdd;
     1428
     1429
     1430/***/ }),
     1431/* 35 */
     1432/***/ (function(module, exports) {
     1433
    1031434/**
    1041435 * wp.media.controller.CollectionEdit
     
    2621593module.exports = CollectionEdit;
    2631594
    264 },{}],3:[function(require,module,exports){
     1595
     1596/***/ }),
     1597/* 36 */
     1598/***/ (function(module, exports) {
     1599
    2651600/**
    266  * wp.media.controller.Cropper
    267  *
    268  * A state for cropping an image.
     1601 * wp.media.controller.CollectionAdd
     1602 *
     1603 * A state for adding attachments to a collection (e.g. video playlist).
    2691604 *
    2701605 * @class
     1606 * @augments wp.media.controller.Library
    2711607 * @augments wp.media.controller.State
    2721608 * @augments Backbone.Model
     1609 *
     1610 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     1611 * @param {string}                     [attributes.id=library]      Unique identifier.
     1612 * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
     1613 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     1614 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     1615 *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
     1616 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     1617 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     1618 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     1619 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     1620 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     1621 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     1622 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     1623 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     1624 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1625 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     1626 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     1627 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     1628 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     1629 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     1630 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     1631 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
    2731632 */
    274 var l10n = wp.media.view.l10n,
    275     Cropper;
    276 
    277 Cropper = wp.media.controller.State.extend({
    278     defaults: {
    279         id:          'cropper',
    280         title:       l10n.cropImage,
    281         // Region mode defaults.
    282         toolbar:     'crop',
    283         content:     'crop',
    284         router:      false,
    285 
    286         canSkipCrop: false
    287     },
    288 
     1633var Selection = wp.media.model.Selection,
     1634    Library = wp.media.controller.Library,
     1635    CollectionAdd;
     1636
     1637CollectionAdd = Library.extend({
     1638    defaults: _.defaults( {
     1639        // Selection defaults. @see media.model.Selection
     1640        multiple:      'add',
     1641        // Attachments browser defaults. @see media.view.AttachmentsBrowser
     1642        filterable:    'uploaded',
     1643
     1644        priority:      100,
     1645        syncSelection: false
     1646    }, Library.prototype.defaults ),
     1647
     1648    /**
     1649     * @since 3.9.0
     1650     */
     1651    initialize: function() {
     1652        var collectionType = this.get('collectionType');
     1653
     1654        if ( 'video' === this.get( 'type' ) ) {
     1655            collectionType = 'video-' + collectionType;
     1656        }
     1657
     1658        this.set( 'id', collectionType + '-library' );
     1659        this.set( 'toolbar', collectionType + '-add' );
     1660        this.set( 'menu', collectionType );
     1661
     1662        // If we haven't been provided a `library`, create a `Selection`.
     1663        if ( ! this.get('library') ) {
     1664            this.set( 'library', wp.media.query({ type: this.get('type') }) );
     1665        }
     1666        Library.prototype.initialize.apply( this, arguments );
     1667    },
     1668
     1669    /**
     1670     * @since 3.9.0
     1671     */
    2891672    activate: function() {
    290         this.frame.on( 'content:create:crop', this.createCropContent, this );
    291         this.frame.on( 'close', this.removeCropper, this );
    292         this.set('selection', new Backbone.Collection(this.frame._selection.single));
    293     },
    294 
    295     deactivate: function() {
    296         this.frame.toolbar.mode('browse');
    297     },
    298 
    299     createCropContent: function() {
    300         this.cropperView = new wp.media.view.Cropper({
    301             controller: this,
    302             attachment: this.get('selection').first()
    303         });
    304         this.cropperView.on('image-loaded', this.createCropToolbar, this);
    305         this.frame.content.set(this.cropperView);
    306 
    307     },
    308     removeCropper: function() {
    309         this.imgSelect.cancelSelection();
    310         this.imgSelect.setOptions({remove: true});
    311         this.imgSelect.update();
    312         this.cropperView.remove();
    313     },
    314     createCropToolbar: function() {
    315         var canSkipCrop, toolbarOptions;
    316 
    317         canSkipCrop = this.get('canSkipCrop') || false;
    318 
    319         toolbarOptions = {
    320             controller: this.frame,
    321             items: {
    322                 insert: {
    323                     style:    'primary',
    324                     text:     l10n.cropImage,
    325                     priority: 80,
    326                     requires: { library: false, selection: false },
    327 
    328                     click: function() {
    329                         var controller = this.controller,
    330                             selection;
    331 
    332                         selection = controller.state().get('selection').first();
    333                         selection.set({cropDetails: controller.state().imgSelect.getSelection()});
    334 
    335                         this.$el.text(l10n.cropping);
    336                         this.$el.attr('disabled', true);
    337 
    338                         controller.state().doCrop( selection ).done( function( croppedImage ) {
    339                             controller.trigger('cropped', croppedImage );
    340                             controller.close();
    341                         }).fail( function() {
    342                             controller.trigger('content:error:crop');
    343                         });
    344                     }
    345                 }
    346             }
     1673        var library = this.get('library'),
     1674            editLibrary = this.get('editLibrary'),
     1675            edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
     1676
     1677        if ( editLibrary && editLibrary !== edit ) {
     1678            library.unobserve( editLibrary );
     1679        }
     1680
     1681        // Accepts attachments that exist in the original library and
     1682        // that do not exist in gallery's library.
     1683        library.validator = function( attachment ) {
     1684            return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    3471685        };
    3481686
    349         if ( canSkipCrop ) {
    350             _.extend( toolbarOptions.items, {
    351                 skip: {
    352                     style:      'secondary',
    353                     text:       l10n.skipCropping,
    354                     priority:   70,
    355                     requires:   { library: false, selection: false },
    356                     click:      function() {
    357                         var selection = this.controller.state().get('selection').first();
    358                         this.controller.state().cropperView.remove();
    359                         this.controller.trigger('skippedcrop', selection);
    360                         this.controller.close();
    361                     }
    362                 }
    363             });
    364         }
    365 
    366         this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
    367     },
    368 
    369     doCrop: function( attachment ) {
    370         return wp.ajax.post( 'custom-header-crop', {
    371             nonce: attachment.get('nonces').edit,
    372             id: attachment.get('id'),
    373             cropDetails: attachment.get('cropDetails')
    374         } );
     1687        // Reset the library to ensure that all attachments are re-added
     1688        // to the collection. Do so silently, as calling `observe` will
     1689        // trigger the `reset` event.
     1690        library.reset( library.mirroring.models, { silent: true });
     1691        library.observe( edit );
     1692        this.set('editLibrary', edit);
     1693
     1694        Library.prototype.activate.apply( this, arguments );
    3751695    }
    3761696});
    3771697
    378 module.exports = Cropper;
    379 
    380 },{}],4:[function(require,module,exports){
    381 /**
    382  * wp.media.controller.CustomizeImageCropper
    383  *
    384  * A state for cropping an image.
    385  *
    386  * @class
    387  * @augments wp.media.controller.Cropper
    388  * @augments wp.media.controller.State
    389  * @augments Backbone.Model
    390  */
    391 var Controller = wp.media.controller,
    392     CustomizeImageCropper;
    393 
    394 CustomizeImageCropper = Controller.Cropper.extend({
    395     doCrop: function( attachment ) {
    396         var cropDetails = attachment.get( 'cropDetails' ),
    397             control = this.get( 'control' );
    398 
    399         cropDetails.dst_width  = control.params.width;
    400         cropDetails.dst_height = control.params.height;
    401 
    402         return wp.ajax.post( 'crop-image', {
    403             wp_customize: 'on',
    404             nonce: attachment.get( 'nonces' ).edit,
    405             id: attachment.get( 'id' ),
    406             context: control.id,
    407             cropDetails: cropDetails
    408         } );
    409     }
    410 });
    411 
    412 module.exports = CustomizeImageCropper;
    413 
    414 },{}],5:[function(require,module,exports){
    415 /**
    416  * wp.media.controller.EditImage
    417  *
    418  * A state for editing (cropping, etc.) an image.
    419  *
    420  * @class
    421  * @augments wp.media.controller.State
    422  * @augments Backbone.Model
    423  *
    424  * @param {object}                    attributes                      The attributes hash passed to the state.
    425  * @param {wp.media.model.Attachment} attributes.model                The attachment.
    426  * @param {string}                    [attributes.id=edit-image]      Unique identifier.
    427  * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
    428  * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
    429  * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
    430  * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
    431  * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
    432  */
    433 var l10n = wp.media.view.l10n,
    434     EditImage;
    435 
    436 EditImage = wp.media.controller.State.extend({
    437     defaults: {
    438         id:      'edit-image',
    439         title:   l10n.editImage,
    440         menu:    false,
    441         toolbar: 'edit-image',
    442         content: 'edit-image',
    443         url:     ''
    444     },
    445 
    446     /**
    447      * @since 3.9.0
    448      */
    449     activate: function() {
    450         this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
    451     },
    452 
    453     /**
    454      * @since 3.9.0
    455      */
    456     deactivate: function() {
    457         this.stopListening( this.frame );
    458     },
    459 
    460     /**
    461      * @since 3.9.0
    462      */
    463     toolbar: function() {
    464         var frame = this.frame,
    465             lastState = frame.lastState(),
    466             previous = lastState && lastState.id;
    467 
    468         frame.toolbar.set( new wp.media.view.Toolbar({
    469             controller: frame,
    470             items: {
    471                 back: {
    472                     style: 'primary',
    473                     text:     l10n.back,
    474                     priority: 20,
    475                     click:    function() {
    476                         if ( previous ) {
    477                             frame.setState( previous );
    478                         } else {
    479                             frame.close();
    480                         }
    481                     }
    482                 }
    483             }
    484         }) );
    485     }
    486 });
    487 
    488 module.exports = EditImage;
    489 
    490 },{}],6:[function(require,module,exports){
    491 /**
    492  * wp.media.controller.Embed
    493  *
    494  * A state for embedding media from a URL.
    495  *
    496  * @class
    497  * @augments wp.media.controller.State
    498  * @augments Backbone.Model
    499  *
    500  * @param {object} attributes                         The attributes hash passed to the state.
    501  * @param {string} [attributes.id=embed]              Unique identifier.
    502  * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
    503  * @param {string} [attributes.content=embed]         Initial mode for the content region.
    504  * @param {string} [attributes.menu=default]          Initial mode for the menu region.
    505  * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
    506  * @param {string} [attributes.menu=false]            Initial mode for the menu region.
    507  * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
    508  * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
    509  * @param {string} [attributes.url]                   The embed URL.
    510  * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
    511  */
    512 var l10n = wp.media.view.l10n,
    513     $ = Backbone.$,
    514     Embed;
    515 
    516 Embed = wp.media.controller.State.extend({
    517     defaults: {
    518         id:       'embed',
    519         title:    l10n.insertFromUrlTitle,
    520         content:  'embed',
    521         menu:     'default',
    522         toolbar:  'main-embed',
    523         priority: 120,
    524         type:     'link',
    525         url:      '',
    526         metadata: {}
    527     },
    528 
    529     // The amount of time used when debouncing the scan.
    530     sensitivity: 400,
    531 
    532     initialize: function(options) {
    533         this.metadata = options.metadata;
    534         this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
    535         this.props = new Backbone.Model( this.metadata || { url: '' });
    536         this.props.on( 'change:url', this.debouncedScan, this );
    537         this.props.on( 'change:url', this.refresh, this );
    538         this.on( 'scan', this.scanImage, this );
    539     },
    540 
    541     /**
    542      * Trigger a scan of the embedded URL's content for metadata required to embed.
    543      *
    544      * @fires wp.media.controller.Embed#scan
    545      */
    546     scan: function() {
    547         var scanners,
    548             embed = this,
    549             attributes = {
    550                 type: 'link',
    551                 scanners: []
    552             };
    553 
    554         // Scan is triggered with the list of `attributes` to set on the
    555         // state, useful for the 'type' attribute and 'scanners' attribute,
    556         // an array of promise objects for asynchronous scan operations.
    557         if ( this.props.get('url') ) {
    558             this.trigger( 'scan', attributes );
    559         }
    560 
    561         if ( attributes.scanners.length ) {
    562             scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
    563             scanners.always( function() {
    564                 if ( embed.get('scanners') === scanners ) {
    565                     embed.set( 'loading', false );
    566                 }
    567             });
    568         } else {
    569             attributes.scanners = null;
    570         }
    571 
    572         attributes.loading = !! attributes.scanners;
    573         this.set( attributes );
    574     },
    575     /**
    576      * Try scanning the embed as an image to discover its dimensions.
    577      *
    578      * @param {Object} attributes
    579      */
    580     scanImage: function( attributes ) {
    581         var frame = this.frame,
    582             state = this,
    583             url = this.props.get('url'),
    584             image = new Image(),
    585             deferred = $.Deferred();
    586 
    587         attributes.scanners.push( deferred.promise() );
    588 
    589         // Try to load the image and find its width/height.
    590         image.onload = function() {
    591             deferred.resolve();
    592 
    593             if ( state !== frame.state() || url !== state.props.get('url') ) {
    594                 return;
    595             }
    596 
    597             state.set({
    598                 type: 'image'
    599             });
    600 
    601             state.props.set({
    602                 width:  image.width,
    603                 height: image.height
    604             });
    605         };
    606 
    607         image.onerror = deferred.reject;
    608         image.src = url;
    609     },
    610 
    611     refresh: function() {
    612         this.frame.toolbar.get().refresh();
    613     },
    614 
    615     reset: function() {
    616         this.props.clear().set({ url: '' });
    617 
    618         if ( this.active ) {
    619             this.refresh();
    620         }
    621     }
    622 });
    623 
    624 module.exports = Embed;
    625 
    626 },{}],7:[function(require,module,exports){
     1698module.exports = CollectionAdd;
     1699
     1700
     1701/***/ }),
     1702/* 37 */
     1703/***/ (function(module, exports) {
     1704
    6271705/**
    6281706 * wp.media.controller.FeaturedImage
     
    7461824module.exports = FeaturedImage;
    7471825
    748 },{}],8:[function(require,module,exports){
    749 /**
    750  * wp.media.controller.GalleryAdd
    751  *
    752  * A state for selecting more images to add to a gallery.
    753  *
    754  * @class
    755  * @augments wp.media.controller.Library
    756  * @augments wp.media.controller.State
    757  * @augments Backbone.Model
    758  *
    759  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    760  * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
    761  * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
    762  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    763  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    764  *                                                                          If one is not supplied, a collection of all images will be created.
    765  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    766  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    767  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    768  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    769  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    770  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    771  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    772  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    773  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    774  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    775  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    776  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    777  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    778  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    779  */
    780 var Selection = wp.media.model.Selection,
    781     Library = wp.media.controller.Library,
    782     l10n = wp.media.view.l10n,
    783     GalleryAdd;
    784 
    785 GalleryAdd = Library.extend({
    786     defaults: _.defaults({
    787         id:            'gallery-library',
    788         title:         l10n.addToGalleryTitle,
    789         multiple:      'add',
    790         filterable:    'uploaded',
    791         menu:          'gallery',
    792         toolbar:       'gallery-add',
    793         priority:      100,
    794         syncSelection: false
    795     }, Library.prototype.defaults ),
    796 
    797     /**
    798      * @since 3.5.0
    799      */
    800     initialize: function() {
    801         // If a library wasn't supplied, create a library of images.
    802         if ( ! this.get('library') ) {
    803             this.set( 'library', wp.media.query({ type: 'image' }) );
    804         }
    805 
    806         Library.prototype.initialize.apply( this, arguments );
    807     },
    808 
    809     /**
    810      * @since 3.5.0
    811      */
    812     activate: function() {
    813         var library = this.get('library'),
    814             edit    = this.frame.state('gallery-edit').get('library');
    815 
    816         if ( this.editLibrary && this.editLibrary !== edit ) {
    817             library.unobserve( this.editLibrary );
    818         }
    819 
    820         // Accepts attachments that exist in the original library and
    821         // that do not exist in gallery's library.
    822         library.validator = function( attachment ) {
    823             return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    824         };
    825 
    826         // Reset the library to ensure that all attachments are re-added
    827         // to the collection. Do so silently, as calling `observe` will
    828         // trigger the `reset` event.
    829         library.reset( library.mirroring.models, { silent: true });
    830         library.observe( edit );
    831         this.editLibrary = edit;
    832 
    833         Library.prototype.activate.apply( this, arguments );
    834     }
    835 });
    836 
    837 module.exports = GalleryAdd;
    838 
    839 },{}],9:[function(require,module,exports){
    840 /**
    841  * wp.media.controller.GalleryEdit
    842  *
    843  * A state for editing a gallery's images and settings.
    844  *
    845  * @class
    846  * @augments wp.media.controller.Library
    847  * @augments wp.media.controller.State
    848  * @augments Backbone.Model
    849  *
    850  * @param {object}                     [attributes]                       The attributes hash passed to the state.
    851  * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
    852  * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
    853  * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
    854  *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
    855  * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
    856  * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
    857  * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    858  * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
    859  * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
    860  * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
    861  * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    862  * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
    863  * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
    864  * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
    865  * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
    866  * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
    867  * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
    868  *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
    869  * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
    870  *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    871  */
    872 var Library = wp.media.controller.Library,
    873     l10n = wp.media.view.l10n,
    874     GalleryEdit;
    875 
    876 GalleryEdit = Library.extend({
    877     defaults: {
    878         id:               'gallery-edit',
    879         title:            l10n.editGalleryTitle,
    880         multiple:         false,
    881         searchable:       false,
    882         sortable:         true,
    883         date:             false,
    884         display:          false,
    885         content:          'browse',
    886         toolbar:          'gallery-edit',
    887         describe:         true,
    888         displaySettings:  true,
    889         dragInfo:         true,
    890         idealColumnWidth: 170,
    891         editing:          false,
    892         priority:         60,
    893         syncSelection:    false
    894     },
    895 
    896     /**
    897      * @since 3.5.0
    898      */
    899     initialize: function() {
    900         // If we haven't been provided a `library`, create a `Selection`.
    901         if ( ! this.get('library') ) {
    902             this.set( 'library', new wp.media.model.Selection() );
    903         }
    904 
    905         // The single `Attachment` view to be used in the `Attachments` view.
    906         if ( ! this.get('AttachmentView') ) {
    907             this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    908         }
    909 
    910         Library.prototype.initialize.apply( this, arguments );
    911     },
    912 
    913     /**
    914      * @since 3.5.0
    915      */
    916     activate: function() {
    917         var library = this.get('library');
    918 
    919         // Limit the library to images only.
    920         library.props.set( 'type', 'image' );
    921 
    922         // Watch for uploaded attachments.
    923         this.get('library').observe( wp.Uploader.queue );
    924 
    925         this.frame.on( 'content:render:browse', this.gallerySettings, this );
    926 
    927         Library.prototype.activate.apply( this, arguments );
    928     },
    929 
    930     /**
    931      * @since 3.5.0
    932      */
    933     deactivate: function() {
    934         // Stop watching for uploaded attachments.
    935         this.get('library').unobserve( wp.Uploader.queue );
    936 
    937         this.frame.off( 'content:render:browse', this.gallerySettings, this );
    938 
    939         Library.prototype.deactivate.apply( this, arguments );
    940     },
    941 
    942     /**
    943      * @since 3.5.0
    944      *
    945      * @param browser
    946      */
    947     gallerySettings: function( browser ) {
    948         if ( ! this.get('displaySettings') ) {
    949             return;
    950         }
    951 
    952         var library = this.get('library');
    953 
    954         if ( ! library || ! browser ) {
    955             return;
    956         }
    957 
    958         library.gallery = library.gallery || new Backbone.Model();
    959 
    960         browser.sidebar.set({
    961             gallery: new wp.media.view.Settings.Gallery({
    962                 controller: this,
    963                 model:      library.gallery,
    964                 priority:   40
    965             })
    966         });
    967 
    968         browser.toolbar.set( 'reverse', {
    969             text:     l10n.reverseOrder,
    970             priority: 80,
    971 
    972             click: function() {
    973                 library.reset( library.toArray().reverse() );
    974             }
    975         });
    976     }
    977 });
    978 
    979 module.exports = GalleryEdit;
    980 
    981 },{}],10:[function(require,module,exports){
    982 /**
    983  * wp.media.controller.ImageDetails
    984  *
    985  * A state for editing the attachment display settings of an image that's been
    986  * inserted into the editor.
    987  *
    988  * @class
    989  * @augments wp.media.controller.State
    990  * @augments Backbone.Model
    991  *
    992  * @param {object}                    [attributes]                       The attributes hash passed to the state.
    993  * @param {string}                    [attributes.id=image-details]      Unique identifier.
    994  * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
    995  * @param {wp.media.model.Attachment} attributes.image                   The image's model.
    996  * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
    997  * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
    998  * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
    999  * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
    1000  * @param {boolean}                   [attributes.editing=false]         Unused.
    1001  * @param {int}                       [attributes.priority=60]           Unused.
    1002  *
    1003  * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
    1004  *       however this may not do anything.
    1005  */
    1006 var State = wp.media.controller.State,
    1007     Library = wp.media.controller.Library,
    1008     l10n = wp.media.view.l10n,
    1009     ImageDetails;
    1010 
    1011 ImageDetails = State.extend({
    1012     defaults: _.defaults({
    1013         id:       'image-details',
    1014         title:    l10n.imageDetailsTitle,
    1015         content:  'image-details',
    1016         menu:     false,
    1017         router:   false,
    1018         toolbar:  'image-details',
    1019         editing:  false,
    1020         priority: 60
    1021     }, Library.prototype.defaults ),
    1022 
    1023     /**
    1024      * @since 3.9.0
    1025      *
    1026      * @param options Attributes
    1027      */
    1028     initialize: function( options ) {
    1029         this.image = options.image;
    1030         State.prototype.initialize.apply( this, arguments );
    1031     },
    1032 
    1033     /**
    1034      * @since 3.9.0
    1035      */
    1036     activate: function() {
    1037         this.frame.modal.$el.addClass('image-details');
    1038     }
    1039 });
    1040 
    1041 module.exports = ImageDetails;
    1042 
    1043 },{}],11:[function(require,module,exports){
    1044 /**
    1045  * wp.media.controller.Library
    1046  *
    1047  * A state for choosing an attachment or group of attachments from the media library.
    1048  *
    1049  * @class
    1050  * @augments wp.media.controller.State
    1051  * @augments Backbone.Model
    1052  * @mixes media.selectionSync
    1053  *
    1054  * @param {object}                          [attributes]                         The attributes hash passed to the state.
    1055  * @param {string}                          [attributes.id=library]              Unique identifier.
    1056  * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
    1057  * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
    1058  *                                                                               If one is not supplied, a collection of all attachments will be created.
    1059  * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
    1060  *                                                                               If the 'selection' attribute is a plain JS object,
    1061  *                                                                               a Selection will be created using its values as the selection instance's `props` model.
    1062  *                                                                               Otherwise, it will copy the library's `props` model.
    1063  * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
    1064  * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
    1065  *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
    1066  * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
    1067  * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
    1068  * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
    1069  * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
    1070  * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
    1071  *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
    1072  * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1073  * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1074  * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1075  * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1076  * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    1077  */
    1078 var l10n = wp.media.view.l10n,
    1079     getUserSetting = window.getUserSetting,
    1080     setUserSetting = window.setUserSetting,
    1081     Library;
    1082 
    1083 Library = wp.media.controller.State.extend({
    1084     defaults: {
    1085         id:                 'library',
    1086         title:              l10n.mediaLibraryTitle,
    1087         multiple:           false,
    1088         content:            'upload',
    1089         menu:               'default',
    1090         router:             'browse',
    1091         toolbar:            'select',
    1092         searchable:         true,
    1093         filterable:         false,
    1094         sortable:           true,
    1095         autoSelect:         true,
    1096         describe:           false,
    1097         contentUserSetting: true,
    1098         syncSelection:      true
    1099     },
    1100 
    1101     /**
    1102      * If a library isn't provided, query all media items.
    1103      * If a selection instance isn't provided, create one.
    1104      *
    1105      * @since 3.5.0
    1106      */
    1107     initialize: function() {
    1108         var selection = this.get('selection'),
    1109             props;
    1110 
    1111         if ( ! this.get('library') ) {
    1112             this.set( 'library', wp.media.query() );
    1113         }
    1114 
    1115         if ( ! ( selection instanceof wp.media.model.Selection ) ) {
    1116             props = selection;
    1117 
    1118             if ( ! props ) {
    1119                 props = this.get('library').props.toJSON();
    1120                 props = _.omit( props, 'orderby', 'query' );
    1121             }
    1122 
    1123             this.set( 'selection', new wp.media.model.Selection( null, {
    1124                 multiple: this.get('multiple'),
    1125                 props: props
    1126             }) );
    1127         }
    1128 
    1129         this.resetDisplays();
    1130     },
    1131 
    1132     /**
    1133      * @since 3.5.0
    1134      */
    1135     activate: function() {
    1136         this.syncSelection();
    1137 
    1138         wp.Uploader.queue.on( 'add', this.uploading, this );
    1139 
    1140         this.get('selection').on( 'add remove reset', this.refreshContent, this );
    1141 
    1142         if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
    1143             this.frame.on( 'content:activate', this.saveContentMode, this );
    1144             this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
    1145         }
    1146     },
    1147 
    1148     /**
    1149      * @since 3.5.0
    1150      */
    1151     deactivate: function() {
    1152         this.recordSelection();
    1153 
    1154         this.frame.off( 'content:activate', this.saveContentMode, this );
    1155 
    1156         // Unbind all event handlers that use this state as the context
    1157         // from the selection.
    1158         this.get('selection').off( null, null, this );
    1159 
    1160         wp.Uploader.queue.off( null, null, this );
    1161     },
    1162 
    1163     /**
    1164      * Reset the library to its initial state.
    1165      *
    1166      * @since 3.5.0
    1167      */
    1168     reset: function() {
    1169         this.get('selection').reset();
    1170         this.resetDisplays();
    1171         this.refreshContent();
    1172     },
    1173 
    1174     /**
    1175      * Reset the attachment display settings defaults to the site options.
    1176      *
    1177      * If site options don't define them, fall back to a persistent user setting.
    1178      *
    1179      * @since 3.5.0
    1180      */
    1181     resetDisplays: function() {
    1182         var defaultProps = wp.media.view.settings.defaultProps;
    1183         this._displays = [];
    1184         this._defaultDisplaySettings = {
    1185             align: defaultProps.align || getUserSetting( 'align', 'none' ),
    1186             size:  defaultProps.size  || getUserSetting( 'imgsize', 'medium' ),
    1187             link:  defaultProps.link  || getUserSetting( 'urlbutton', 'file' )
    1188         };
    1189     },
    1190 
    1191     /**
    1192      * Create a model to represent display settings (alignment, etc.) for an attachment.
    1193      *
    1194      * @since 3.5.0
    1195      *
    1196      * @param {wp.media.model.Attachment} attachment
    1197      * @returns {Backbone.Model}
    1198      */
    1199     display: function( attachment ) {
    1200         var displays = this._displays;
    1201 
    1202         if ( ! displays[ attachment.cid ] ) {
    1203             displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
    1204         }
    1205         return displays[ attachment.cid ];
    1206     },
    1207 
    1208     /**
    1209      * Given an attachment, create attachment display settings properties.
    1210      *
    1211      * @since 3.6.0
    1212      *
    1213      * @param {wp.media.model.Attachment} attachment
    1214      * @returns {Object}
    1215      */
    1216     defaultDisplaySettings: function( attachment ) {
    1217         var settings = this._defaultDisplaySettings;
    1218         if ( settings.canEmbed = this.canEmbed( attachment ) ) {
    1219             settings.link = 'embed';
    1220         }
    1221         return settings;
    1222     },
    1223 
    1224     /**
    1225      * Whether an attachment can be embedded (audio or video).
    1226      *
    1227      * @since 3.6.0
    1228      *
    1229      * @param {wp.media.model.Attachment} attachment
    1230      * @returns {Boolean}
    1231      */
    1232     canEmbed: function( attachment ) {
    1233         // If uploading, we know the filename but not the mime type.
    1234         if ( ! attachment.get('uploading') ) {
    1235             var type = attachment.get('type');
    1236             if ( type !== 'audio' && type !== 'video' ) {
    1237                 return false;
    1238             }
    1239         }
    1240 
    1241         return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
    1242     },
    1243 
    1244 
    1245     /**
    1246      * If the state is active, no items are selected, and the current
    1247      * content mode is not an option in the state's router (provided
    1248      * the state has a router), reset the content mode to the default.
    1249      *
    1250      * @since 3.5.0
    1251      */
    1252     refreshContent: function() {
    1253         var selection = this.get('selection'),
    1254             frame = this.frame,
    1255             router = frame.router.get(),
    1256             mode = frame.content.mode();
    1257 
    1258         if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
    1259             this.frame.content.render( this.get('content') );
    1260         }
    1261     },
    1262 
    1263     /**
    1264      * Callback handler when an attachment is uploaded.
    1265      *
    1266      * Switch to the Media Library if uploaded from the 'Upload Files' tab.
    1267      *
    1268      * Adds any uploading attachments to the selection.
    1269      *
    1270      * If the state only supports one attachment to be selected and multiple
    1271      * attachments are uploaded, the last attachment in the upload queue will
    1272      * be selected.
    1273      *
    1274      * @since 3.5.0
    1275      *
    1276      * @param {wp.media.model.Attachment} attachment
    1277      */
    1278     uploading: function( attachment ) {
    1279         var content = this.frame.content;
    1280 
    1281         if ( 'upload' === content.mode() ) {
    1282             this.frame.content.mode('browse');
    1283         }
    1284 
    1285         if ( this.get( 'autoSelect' ) ) {
    1286             this.get('selection').add( attachment );
    1287             this.frame.trigger( 'library:selection:add' );
    1288         }
    1289     },
    1290 
    1291     /**
    1292      * Persist the mode of the content region as a user setting.
    1293      *
    1294      * @since 3.5.0
    1295      */
    1296     saveContentMode: function() {
    1297         if ( 'browse' !== this.get('router') ) {
    1298             return;
    1299         }
    1300 
    1301         var mode = this.frame.content.mode(),
    1302             view = this.frame.router.get();
    1303 
    1304         if ( view && view.get( mode ) ) {
    1305             setUserSetting( 'libraryContent', mode );
    1306         }
    1307     }
    1308 });
    1309 
    1310 // Make selectionSync available on any Media Library state.
    1311 _.extend( Library.prototype, wp.media.selectionSync );
    1312 
    1313 module.exports = Library;
    1314 
    1315 },{}],12:[function(require,module,exports){
    1316 /**
    1317  * wp.media.controller.MediaLibrary
    1318  *
    1319  * @class
    1320  * @augments wp.media.controller.Library
    1321  * @augments wp.media.controller.State
    1322  * @augments Backbone.Model
    1323  */
    1324 var Library = wp.media.controller.Library,
    1325     MediaLibrary;
    1326 
    1327 MediaLibrary = Library.extend({
    1328     defaults: _.defaults({
    1329         // Attachments browser defaults. @see media.view.AttachmentsBrowser
    1330         filterable:      'uploaded',
    1331 
    1332         displaySettings: false,
    1333         priority:        80,
    1334         syncSelection:   false
    1335     }, Library.prototype.defaults ),
    1336 
    1337     /**
    1338      * @since 3.9.0
    1339      *
    1340      * @param options
    1341      */
    1342     initialize: function( options ) {
    1343         this.media = options.media;
    1344         this.type = options.type;
    1345         this.set( 'library', wp.media.query({ type: this.type }) );
    1346 
    1347         Library.prototype.initialize.apply( this, arguments );
    1348     },
    1349 
    1350     /**
    1351      * @since 3.9.0
    1352      */
    1353     activate: function() {
    1354         // @todo this should use this.frame.
    1355         if ( wp.media.frame.lastMime ) {
    1356             this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
    1357             delete wp.media.frame.lastMime;
    1358         }
    1359         Library.prototype.activate.apply( this, arguments );
    1360     }
    1361 });
    1362 
    1363 module.exports = MediaLibrary;
    1364 
    1365 },{}],13:[function(require,module,exports){
    1366 /**
    1367  * wp.media.controller.Region
    1368  *
    1369  * A region is a persistent application layout area.
    1370  *
    1371  * A region assumes one mode at any time, and can be switched to another.
    1372  *
    1373  * When mode changes, events are triggered on the region's parent view.
    1374  * The parent view will listen to specific events and fill the region with an
    1375  * appropriate view depending on mode. For example, a frame listens for the
    1376  * 'browse' mode t be activated on the 'content' view and then fills the region
    1377  * with an AttachmentsBrowser view.
    1378  *
    1379  * @class
    1380  *
    1381  * @param {object}        options          Options hash for the region.
    1382  * @param {string}        options.id       Unique identifier for the region.
    1383  * @param {Backbone.View} options.view     A parent view the region exists within.
    1384  * @param {string}        options.selector jQuery selector for the region within the parent view.
    1385  */
    1386 var Region = function( options ) {
    1387     _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
    1388 };
    1389 
    1390 // Use Backbone's self-propagating `extend` inheritance method.
    1391 Region.extend = Backbone.Model.extend;
    1392 
    1393 _.extend( Region.prototype, {
    1394     /**
    1395      * Activate a mode.
    1396      *
    1397      * @since 3.5.0
    1398      *
    1399      * @param {string} mode
    1400      *
    1401      * @fires this.view#{this.id}:activate:{this._mode}
    1402      * @fires this.view#{this.id}:activate
    1403      * @fires this.view#{this.id}:deactivate:{this._mode}
    1404      * @fires this.view#{this.id}:deactivate
    1405      *
    1406      * @returns {wp.media.controller.Region} Returns itself to allow chaining.
    1407      */
    1408     mode: function( mode ) {
    1409         if ( ! mode ) {
    1410             return this._mode;
    1411         }
    1412         // Bail if we're trying to change to the current mode.
    1413         if ( mode === this._mode ) {
    1414             return this;
    1415         }
    1416 
    1417         /**
    1418          * Region mode deactivation event.
    1419          *
    1420          * @event this.view#{this.id}:deactivate:{this._mode}
    1421          * @event this.view#{this.id}:deactivate
    1422          */
    1423         this.trigger('deactivate');
    1424 
    1425         this._mode = mode;
    1426         this.render( mode );
    1427 
    1428         /**
    1429          * Region mode activation event.
    1430          *
    1431          * @event this.view#{this.id}:activate:{this._mode}
    1432          * @event this.view#{this.id}:activate
    1433          */
    1434         this.trigger('activate');
    1435         return this;
    1436     },
    1437     /**
    1438      * Render a mode.
    1439      *
    1440      * @since 3.5.0
    1441      *
    1442      * @param {string} mode
    1443      *
    1444      * @fires this.view#{this.id}:create:{this._mode}
    1445      * @fires this.view#{this.id}:create
    1446      * @fires this.view#{this.id}:render:{this._mode}
    1447      * @fires this.view#{this.id}:render
    1448      *
    1449      * @returns {wp.media.controller.Region} Returns itself to allow chaining
    1450      */
    1451     render: function( mode ) {
    1452         // If the mode isn't active, activate it.
    1453         if ( mode && mode !== this._mode ) {
    1454             return this.mode( mode );
    1455         }
    1456 
    1457         var set = { view: null },
    1458             view;
    1459 
    1460         /**
    1461          * Create region view event.
    1462          *
    1463          * Region view creation takes place in an event callback on the frame.
    1464          *
    1465          * @event this.view#{this.id}:create:{this._mode}
    1466          * @event this.view#{this.id}:create
    1467          */
    1468         this.trigger( 'create', set );
    1469         view = set.view;
    1470 
    1471         /**
    1472          * Render region view event.
    1473          *
    1474          * Region view creation takes place in an event callback on the frame.
    1475          *
    1476          * @event this.view#{this.id}:create:{this._mode}
    1477          * @event this.view#{this.id}:create
    1478          */
    1479         this.trigger( 'render', view );
    1480         if ( view ) {
    1481             this.set( view );
    1482         }
    1483         return this;
    1484     },
    1485 
    1486     /**
    1487      * Get the region's view.
    1488      *
    1489      * @since 3.5.0
    1490      *
    1491      * @returns {wp.media.View}
    1492      */
    1493     get: function() {
    1494         return this.view.views.first( this.selector );
    1495     },
    1496 
    1497     /**
    1498      * Set the region's view as a subview of the frame.
    1499      *
    1500      * @since 3.5.0
    1501      *
    1502      * @param {Array|Object} views
    1503      * @param {Object} [options={}]
    1504      * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
    1505      */
    1506     set: function( views, options ) {
    1507         if ( options ) {
    1508             options.add = false;
    1509         }
    1510         return this.view.views.set( this.selector, views, options );
    1511     },
    1512 
    1513     /**
    1514      * Trigger regional view events on the frame.
    1515      *
    1516      * @since 3.5.0
    1517      *
    1518      * @param {string} event
    1519      * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
    1520      */
    1521     trigger: function( event ) {
    1522         var base, args;
    1523 
    1524         if ( ! this._mode ) {
    1525             return;
    1526         }
    1527 
    1528         args = _.toArray( arguments );
    1529         base = this.id + ':' + event;
    1530 
    1531         // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
    1532         args[0] = base + ':' + this._mode;
    1533         this.view.trigger.apply( this.view, args );
    1534 
    1535         // Trigger `{this.id}:{event}` event on the frame.
    1536         args[0] = base;
    1537         this.view.trigger.apply( this.view, args );
    1538         return this;
    1539     }
    1540 });
    1541 
    1542 module.exports = Region;
    1543 
    1544 },{}],14:[function(require,module,exports){
     1826
     1827/***/ }),
     1828/* 38 */
     1829/***/ (function(module, exports) {
     1830
    15451831/**
    15461832 * wp.media.controller.ReplaceImage
     
    16501936module.exports = ReplaceImage;
    16511937
    1652 },{}],15:[function(require,module,exports){
     1938
     1939/***/ }),
     1940/* 39 */
     1941/***/ (function(module, exports) {
     1942
     1943/**
     1944 * wp.media.controller.EditImage
     1945 *
     1946 * A state for editing (cropping, etc.) an image.
     1947 *
     1948 * @class
     1949 * @augments wp.media.controller.State
     1950 * @augments Backbone.Model
     1951 *
     1952 * @param {object}                    attributes                      The attributes hash passed to the state.
     1953 * @param {wp.media.model.Attachment} attributes.model                The attachment.
     1954 * @param {string}                    [attributes.id=edit-image]      Unique identifier.
     1955 * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
     1956 * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
     1957 * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
     1958 * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
     1959 * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
     1960 */
     1961var l10n = wp.media.view.l10n,
     1962    EditImage;
     1963
     1964EditImage = wp.media.controller.State.extend({
     1965    defaults: {
     1966        id:      'edit-image',
     1967        title:   l10n.editImage,
     1968        menu:    false,
     1969        toolbar: 'edit-image',
     1970        content: 'edit-image',
     1971        url:     ''
     1972    },
     1973
     1974    /**
     1975     * @since 3.9.0
     1976     */
     1977    activate: function() {
     1978        this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
     1979    },
     1980
     1981    /**
     1982     * @since 3.9.0
     1983     */
     1984    deactivate: function() {
     1985        this.stopListening( this.frame );
     1986    },
     1987
     1988    /**
     1989     * @since 3.9.0
     1990     */
     1991    toolbar: function() {
     1992        var frame = this.frame,
     1993            lastState = frame.lastState(),
     1994            previous = lastState && lastState.id;
     1995
     1996        frame.toolbar.set( new wp.media.view.Toolbar({
     1997            controller: frame,
     1998            items: {
     1999                back: {
     2000                    style: 'primary',
     2001                    text:     l10n.back,
     2002                    priority: 20,
     2003                    click:    function() {
     2004                        if ( previous ) {
     2005                            frame.setState( previous );
     2006                        } else {
     2007                            frame.close();
     2008                        }
     2009                    }
     2010                }
     2011            }
     2012        }) );
     2013    }
     2014});
     2015
     2016module.exports = EditImage;
     2017
     2018
     2019/***/ }),
     2020/* 40 */
     2021/***/ (function(module, exports) {
     2022
     2023/**
     2024 * wp.media.controller.MediaLibrary
     2025 *
     2026 * @class
     2027 * @augments wp.media.controller.Library
     2028 * @augments wp.media.controller.State
     2029 * @augments Backbone.Model
     2030 */
     2031var Library = wp.media.controller.Library,
     2032    MediaLibrary;
     2033
     2034MediaLibrary = Library.extend({
     2035    defaults: _.defaults({
     2036        // Attachments browser defaults. @see media.view.AttachmentsBrowser
     2037        filterable:      'uploaded',
     2038
     2039        displaySettings: false,
     2040        priority:        80,
     2041        syncSelection:   false
     2042    }, Library.prototype.defaults ),
     2043
     2044    /**
     2045     * @since 3.9.0
     2046     *
     2047     * @param options
     2048     */
     2049    initialize: function( options ) {
     2050        this.media = options.media;
     2051        this.type = options.type;
     2052        this.set( 'library', wp.media.query({ type: this.type }) );
     2053
     2054        Library.prototype.initialize.apply( this, arguments );
     2055    },
     2056
     2057    /**
     2058     * @since 3.9.0
     2059     */
     2060    activate: function() {
     2061        // @todo this should use this.frame.
     2062        if ( wp.media.frame.lastMime ) {
     2063            this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
     2064            delete wp.media.frame.lastMime;
     2065        }
     2066        Library.prototype.activate.apply( this, arguments );
     2067    }
     2068});
     2069
     2070module.exports = MediaLibrary;
     2071
     2072
     2073/***/ }),
     2074/* 41 */
     2075/***/ (function(module, exports) {
     2076
     2077/**
     2078 * wp.media.controller.Embed
     2079 *
     2080 * A state for embedding media from a URL.
     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 {string} [attributes.id=embed]              Unique identifier.
     2088 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
     2089 * @param {string} [attributes.content=embed]         Initial mode for the content region.
     2090 * @param {string} [attributes.menu=default]          Initial mode for the menu region.
     2091 * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
     2092 * @param {string} [attributes.menu=false]            Initial mode for the menu region.
     2093 * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
     2094 * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
     2095 * @param {string} [attributes.url]                   The embed URL.
     2096 * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
     2097 */
     2098var l10n = wp.media.view.l10n,
     2099    $ = Backbone.$,
     2100    Embed;
     2101
     2102Embed = wp.media.controller.State.extend({
     2103    defaults: {
     2104        id:       'embed',
     2105        title:    l10n.insertFromUrlTitle,
     2106        content:  'embed',
     2107        menu:     'default',
     2108        toolbar:  'main-embed',
     2109        priority: 120,
     2110        type:     'link',
     2111        url:      '',
     2112        metadata: {}
     2113    },
     2114
     2115    // The amount of time used when debouncing the scan.
     2116    sensitivity: 400,
     2117
     2118    initialize: function(options) {
     2119        this.metadata = options.metadata;
     2120        this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
     2121        this.props = new Backbone.Model( this.metadata || { url: '' });
     2122        this.props.on( 'change:url', this.debouncedScan, this );
     2123        this.props.on( 'change:url', this.refresh, this );
     2124        this.on( 'scan', this.scanImage, this );
     2125    },
     2126
     2127    /**
     2128     * Trigger a scan of the embedded URL's content for metadata required to embed.
     2129     *
     2130     * @fires wp.media.controller.Embed#scan
     2131     */
     2132    scan: function() {
     2133        var scanners,
     2134            embed = this,
     2135            attributes = {
     2136                type: 'link',
     2137                scanners: []
     2138            };
     2139
     2140        // Scan is triggered with the list of `attributes` to set on the
     2141        // state, useful for the 'type' attribute and 'scanners' attribute,
     2142        // an array of promise objects for asynchronous scan operations.
     2143        if ( this.props.get('url') ) {
     2144            this.trigger( 'scan', attributes );
     2145        }
     2146
     2147        if ( attributes.scanners.length ) {
     2148            scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
     2149            scanners.always( function() {
     2150                if ( embed.get('scanners') === scanners ) {
     2151                    embed.set( 'loading', false );
     2152                }
     2153            });
     2154        } else {
     2155            attributes.scanners = null;
     2156        }
     2157
     2158        attributes.loading = !! attributes.scanners;
     2159        this.set( attributes );
     2160    },
     2161    /**
     2162     * Try scanning the embed as an image to discover its dimensions.
     2163     *
     2164     * @param {Object} attributes
     2165     */
     2166    scanImage: function( attributes ) {
     2167        var frame = this.frame,
     2168            state = this,
     2169            url = this.props.get('url'),
     2170            image = new Image(),
     2171            deferred = $.Deferred();
     2172
     2173        attributes.scanners.push( deferred.promise() );
     2174
     2175        // Try to load the image and find its width/height.
     2176        image.onload = function() {
     2177            deferred.resolve();
     2178
     2179            if ( state !== frame.state() || url !== state.props.get('url') ) {
     2180                return;
     2181            }
     2182
     2183            state.set({
     2184                type: 'image'
     2185            });
     2186
     2187            state.props.set({
     2188                width:  image.width,
     2189                height: image.height
     2190            });
     2191        };
     2192
     2193        image.onerror = deferred.reject;
     2194        image.src = url;
     2195    },
     2196
     2197    refresh: function() {
     2198        this.frame.toolbar.get().refresh();
     2199    },
     2200
     2201    reset: function() {
     2202        this.props.clear().set({ url: '' });
     2203
     2204        if ( this.active ) {
     2205            this.refresh();
     2206        }
     2207    }
     2208});
     2209
     2210module.exports = Embed;
     2211
     2212
     2213/***/ }),
     2214/* 42 */
     2215/***/ (function(module, exports) {
     2216
     2217/**
     2218 * wp.media.controller.Cropper
     2219 *
     2220 * A state for cropping an image.
     2221 *
     2222 * @class
     2223 * @augments wp.media.controller.State
     2224 * @augments Backbone.Model
     2225 */
     2226var l10n = wp.media.view.l10n,
     2227    Cropper;
     2228
     2229Cropper = wp.media.controller.State.extend({
     2230    defaults: {
     2231        id:          'cropper',
     2232        title:       l10n.cropImage,
     2233        // Region mode defaults.
     2234        toolbar:     'crop',
     2235        content:     'crop',
     2236        router:      false,
     2237
     2238        canSkipCrop: false
     2239    },
     2240
     2241    activate: function() {
     2242        this.frame.on( 'content:create:crop', this.createCropContent, this );
     2243        this.frame.on( 'close', this.removeCropper, this );
     2244        this.set('selection', new Backbone.Collection(this.frame._selection.single));
     2245    },
     2246
     2247    deactivate: function() {
     2248        this.frame.toolbar.mode('browse');
     2249    },
     2250
     2251    createCropContent: function() {
     2252        this.cropperView = new wp.media.view.Cropper({
     2253            controller: this,
     2254            attachment: this.get('selection').first()
     2255        });
     2256        this.cropperView.on('image-loaded', this.createCropToolbar, this);
     2257        this.frame.content.set(this.cropperView);
     2258
     2259    },
     2260    removeCropper: function() {
     2261        this.imgSelect.cancelSelection();
     2262        this.imgSelect.setOptions({remove: true});
     2263        this.imgSelect.update();
     2264        this.cropperView.remove();
     2265    },
     2266    createCropToolbar: function() {
     2267        var canSkipCrop, toolbarOptions;
     2268
     2269        canSkipCrop = this.get('canSkipCrop') || false;
     2270
     2271        toolbarOptions = {
     2272            controller: this.frame,
     2273            items: {
     2274                insert: {
     2275                    style:    'primary',
     2276                    text:     l10n.cropImage,
     2277                    priority: 80,
     2278                    requires: { library: false, selection: false },
     2279
     2280                    click: function() {
     2281                        var controller = this.controller,
     2282                            selection;
     2283
     2284                        selection = controller.state().get('selection').first();
     2285                        selection.set({cropDetails: controller.state().imgSelect.getSelection()});
     2286
     2287                        this.$el.text(l10n.cropping);
     2288                        this.$el.attr('disabled', true);
     2289
     2290                        controller.state().doCrop( selection ).done( function( croppedImage ) {
     2291                            controller.trigger('cropped', croppedImage );
     2292                            controller.close();
     2293                        }).fail( function() {
     2294                            controller.trigger('content:error:crop');
     2295                        });
     2296                    }
     2297                }
     2298            }
     2299        };
     2300
     2301        if ( canSkipCrop ) {
     2302            _.extend( toolbarOptions.items, {
     2303                skip: {
     2304                    style:      'secondary',
     2305                    text:       l10n.skipCropping,
     2306                    priority:   70,
     2307                    requires:   { library: false, selection: false },
     2308                    click:      function() {
     2309                        var selection = this.controller.state().get('selection').first();
     2310                        this.controller.state().cropperView.remove();
     2311                        this.controller.trigger('skippedcrop', selection);
     2312                        this.controller.close();
     2313                    }
     2314                }
     2315            });
     2316        }
     2317
     2318        this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
     2319    },
     2320
     2321    doCrop: function( attachment ) {
     2322        return wp.ajax.post( 'custom-header-crop', {
     2323            nonce: attachment.get('nonces').edit,
     2324            id: attachment.get('id'),
     2325            cropDetails: attachment.get('cropDetails')
     2326        } );
     2327    }
     2328});
     2329
     2330module.exports = Cropper;
     2331
     2332
     2333/***/ }),
     2334/* 43 */
     2335/***/ (function(module, exports) {
     2336
     2337/**
     2338 * wp.media.controller.CustomizeImageCropper
     2339 *
     2340 * A state for cropping an image.
     2341 *
     2342 * @class
     2343 * @augments wp.media.controller.Cropper
     2344 * @augments wp.media.controller.State
     2345 * @augments Backbone.Model
     2346 */
     2347var Controller = wp.media.controller,
     2348    CustomizeImageCropper;
     2349
     2350CustomizeImageCropper = Controller.Cropper.extend({
     2351    doCrop: function( attachment ) {
     2352        var cropDetails = attachment.get( 'cropDetails' ),
     2353            control = this.get( 'control' );
     2354
     2355        cropDetails.dst_width  = control.params.width;
     2356        cropDetails.dst_height = control.params.height;
     2357
     2358        return wp.ajax.post( 'crop-image', {
     2359            wp_customize: 'on',
     2360            nonce: attachment.get( 'nonces' ).edit,
     2361            id: attachment.get( 'id' ),
     2362            context: control.id,
     2363            cropDetails: cropDetails
     2364        } );
     2365    }
     2366});
     2367
     2368module.exports = CustomizeImageCropper;
     2369
     2370
     2371/***/ }),
     2372/* 44 */
     2373/***/ (function(module, exports) {
     2374
    16532375/**
    16542376 * wp.media.controller.SiteIconCropper
     
    16992421module.exports = SiteIconCropper;
    17002422
    1701 },{}],16:[function(require,module,exports){
     2423
     2424/***/ }),
     2425/* 45 */
     2426/***/ (function(module, exports) {
     2427
    17022428/**
    1703  * wp.media.controller.StateMachine
    1704  *
    1705  * A state machine keeps track of state. It is in one state at a time,
    1706  * and can change from one state to another.
    1707  *
    1708  * States are stored as models in a Backbone collection.
    1709  *
    1710  * @since 3.5.0
     2429 * wp.media.View
     2430 *
     2431 * The base view class for media.
     2432 *
     2433 * Undelegating events, removing events from the model, and
     2434 * removing events from the controller mirror the code for
     2435 * `Backbone.View.dispose` in Backbone 0.9.8 development.
     2436 *
     2437 * This behavior has since been removed, and should not be used
     2438 * outside of the media manager.
    17112439 *
    17122440 * @class
    1713  * @augments Backbone.Model
    1714  * @mixin
    1715  * @mixes Backbone.Events
    1716  *
    1717  * @param {Array} states
     2441 * @augments wp.Backbone.View
     2442 * @augments Backbone.View
    17182443 */
    1719 var StateMachine = function( states ) {
    1720     // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
    1721     this.states = new Backbone.Collection( states );
    1722 };
    1723 
    1724 // Use Backbone's self-propagating `extend` inheritance method.
    1725 StateMachine.extend = Backbone.Model.extend;
    1726 
    1727 _.extend( StateMachine.prototype, Backbone.Events, {
    1728     /**
    1729      * Fetch a state.
     2444var View = wp.Backbone.View.extend({
     2445    constructor: function( options ) {
     2446        if ( options && options.controller ) {
     2447            this.controller = options.controller;
     2448        }
     2449        wp.Backbone.View.apply( this, arguments );
     2450    },
     2451    /**
     2452     * @todo The internal comment mentions this might have been a stop-gap
     2453     *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
     2454     *       care of this in Backbone.View now.
    17302455     *
    1731      * If no `id` is provided, returns the active state.
    1732      *
    1733      * Implicitly creates states.
    1734      *
    1735      * Ensure that the `states` collection exists so the `StateMachine`
    1736      *   can be used as a mixin.
    1737      *
    1738      * @since 3.5.0
    1739      *
    1740      * @param {string} id
    1741      * @returns {wp.media.controller.State} Returns a State model
    1742      *   from the StateMachine collection
    1743      */
    1744     state: function( id ) {
    1745         this.states = this.states || new Backbone.Collection();
    1746 
    1747         // Default to the active state.
    1748         id = id || this._state;
    1749 
    1750         if ( id && ! this.states.get( id ) ) {
    1751             this.states.add({ id: id });
    1752         }
    1753         return this.states.get( id );
    1754     },
    1755 
    1756     /**
    1757      * Sets the active state.
    1758      *
    1759      * Bail if we're trying to select the current state, if we haven't
    1760      * created the `states` collection, or are trying to select a state
    1761      * that does not exist.
    1762      *
    1763      * @since 3.5.0
    1764      *
    1765      * @param {string} id
    1766      *
    1767      * @fires wp.media.controller.State#deactivate
    1768      * @fires wp.media.controller.State#activate
    1769      *
    1770      * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
    1771      */
    1772     setState: function( id ) {
    1773         var previous = this.state();
    1774 
    1775         if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
    1776             return this;
    1777         }
    1778 
    1779         if ( previous ) {
    1780             previous.trigger('deactivate');
    1781             this._lastState = previous.id;
    1782         }
    1783 
    1784         this._state = id;
    1785         this.state().trigger('activate');
     2456     * @returns {wp.media.View} Returns itself to allow chaining
     2457     */
     2458    dispose: function() {
     2459        // Undelegating events, removing events from the model, and
     2460        // removing events from the controller mirror the code for
     2461        // `Backbone.View.dispose` in Backbone 0.9.8 development.
     2462        this.undelegateEvents();
     2463
     2464        if ( this.model && this.model.off ) {
     2465            this.model.off( null, null, this );
     2466        }
     2467
     2468        if ( this.collection && this.collection.off ) {
     2469            this.collection.off( null, null, this );
     2470        }
     2471
     2472        // Unbind controller events.
     2473        if ( this.controller && this.controller.off ) {
     2474            this.controller.off( null, null, this );
     2475        }
    17862476
    17872477        return this;
    17882478    },
    1789 
    1790     /**
    1791      * Returns the previous active state.
    1792      *
    1793      * Call the `state()` method with no parameters to retrieve the current
    1794      * active state.
    1795      *
    1796      * @since 3.5.0
    1797      *
    1798      * @returns {wp.media.controller.State} Returns a State model
    1799      *    from the StateMachine collection
    1800      */
    1801     lastState: function() {
    1802         if ( this._lastState ) {
    1803             return this.state( this._lastState );
    1804         }
     2479    /**
     2480     * @returns {wp.media.View} Returns itself to allow chaining
     2481     */
     2482    remove: function() {
     2483        this.dispose();
     2484        /**
     2485         * call 'remove' directly on the parent class
     2486         */
     2487        return wp.Backbone.View.prototype.remove.apply( this, arguments );
    18052488    }
    18062489});
    18072490
    1808 // Map all event binding and triggering on a StateMachine to its `states` collection.
    1809 _.each([ 'on', 'off', 'trigger' ], function( method ) {
    1810     /**
    1811      * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    1812      */
    1813     StateMachine.prototype[ method ] = function() {
    1814         // Ensure that the `states` collection exists so the `StateMachine`
    1815         // can be used as a mixin.
    1816         this.states = this.states || new Backbone.Collection();
    1817         // Forward the method to the `states` collection.
    1818         this.states[ method ].apply( this.states, arguments );
     2491module.exports = View;
     2492
     2493
     2494/***/ }),
     2495/* 46 */
     2496/***/ (function(module, exports) {
     2497
     2498/**
     2499 * wp.media.view.Frame
     2500 *
     2501 * A frame is a composite view consisting of one or more regions and one or more
     2502 * states.
     2503 *
     2504 * @see wp.media.controller.State
     2505 * @see wp.media.controller.Region
     2506 *
     2507 * @class
     2508 * @augments wp.media.View
     2509 * @augments wp.Backbone.View
     2510 * @augments Backbone.View
     2511 * @mixes wp.media.controller.StateMachine
     2512 */
     2513var Frame = wp.media.View.extend({
     2514    initialize: function() {
     2515        _.defaults( this.options, {
     2516            mode: [ 'select' ]
     2517        });
     2518        this._createRegions();
     2519        this._createStates();
     2520        this._createModes();
     2521    },
     2522
     2523    _createRegions: function() {
     2524        // Clone the regions array.
     2525        this.regions = this.regions ? this.regions.slice() : [];
     2526
     2527        // Initialize regions.
     2528        _.each( this.regions, function( region ) {
     2529            this[ region ] = new wp.media.controller.Region({
     2530                view:     this,
     2531                id:       region,
     2532                selector: '.media-frame-' + region
     2533            });
     2534        }, this );
     2535    },
     2536    /**
     2537     * Create the frame's states.
     2538     *
     2539     * @see wp.media.controller.State
     2540     * @see wp.media.controller.StateMachine
     2541     *
     2542     * @fires wp.media.controller.State#ready
     2543     */
     2544    _createStates: function() {
     2545        // Create the default `states` collection.
     2546        this.states = new Backbone.Collection( null, {
     2547            model: wp.media.controller.State
     2548        });
     2549
     2550        // Ensure states have a reference to the frame.
     2551        this.states.on( 'add', function( model ) {
     2552            model.frame = this;
     2553            model.trigger('ready');
     2554        }, this );
     2555
     2556        if ( this.options.states ) {
     2557            this.states.add( this.options.states );
     2558        }
     2559    },
     2560
     2561    /**
     2562     * A frame can be in a mode or multiple modes at one time.
     2563     *
     2564     * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
     2565     */
     2566    _createModes: function() {
     2567        // Store active "modes" that the frame is in. Unrelated to region modes.
     2568        this.activeModes = new Backbone.Collection();
     2569        this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
     2570
     2571        _.each( this.options.mode, function( mode ) {
     2572            this.activateMode( mode );
     2573        }, this );
     2574    },
     2575    /**
     2576     * Reset all states on the frame to their defaults.
     2577     *
     2578     * @returns {wp.media.view.Frame} Returns itself to allow chaining
     2579     */
     2580    reset: function() {
     2581        this.states.invoke( 'trigger', 'reset' );
     2582        return this;
     2583    },
     2584    /**
     2585     * Map activeMode collection events to the frame.
     2586     */
     2587    triggerModeEvents: function( model, collection, options ) {
     2588        var collectionEvent,
     2589            modeEventMap = {
     2590                add: 'activate',
     2591                remove: 'deactivate'
     2592            },
     2593            eventToTrigger;
     2594        // Probably a better way to do this.
     2595        _.each( options, function( value, key ) {
     2596            if ( value ) {
     2597                collectionEvent = key;
     2598            }
     2599        } );
     2600
     2601        if ( ! _.has( modeEventMap, collectionEvent ) ) {
     2602            return;
     2603        }
     2604
     2605        eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
     2606        this.trigger( eventToTrigger );
     2607    },
     2608    /**
     2609     * Activate a mode on the frame.
     2610     *
     2611     * @param string mode Mode ID.
     2612     * @returns {this} Returns itself to allow chaining.
     2613     */
     2614    activateMode: function( mode ) {
     2615        // Bail if the mode is already active.
     2616        if ( this.isModeActive( mode ) ) {
     2617            return;
     2618        }
     2619        this.activeModes.add( [ { id: mode } ] );
     2620        // Add a CSS class to the frame so elements can be styled for the mode.
     2621        this.$el.addClass( 'mode-' + mode );
     2622
     2623        return this;
     2624    },
     2625    /**
     2626     * Deactivate a mode on the frame.
     2627     *
     2628     * @param string mode Mode ID.
     2629     * @returns {this} Returns itself to allow chaining.
     2630     */
     2631    deactivateMode: function( mode ) {
     2632        // Bail if the mode isn't active.
     2633        if ( ! this.isModeActive( mode ) ) {
     2634            return this;
     2635        }
     2636        this.activeModes.remove( this.activeModes.where( { id: mode } ) );
     2637        this.$el.removeClass( 'mode-' + mode );
     2638        /**
     2639         * Frame mode deactivation event.
     2640         *
     2641         * @event this#{mode}:deactivate
     2642         */
     2643        this.trigger( mode + ':deactivate' );
     2644
     2645        return this;
     2646    },
     2647    /**
     2648     * Check if a mode is enabled on the frame.
     2649     *
     2650     * @param  string mode Mode ID.
     2651     * @return bool
     2652     */
     2653    isModeActive: function( mode ) {
     2654        return Boolean( this.activeModes.where( { id: mode } ).length );
     2655    }
     2656});
     2657
     2658// Make the `Frame` a `StateMachine`.
     2659_.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
     2660
     2661module.exports = Frame;
     2662
     2663
     2664/***/ }),
     2665/* 47 */
     2666/***/ (function(module, exports) {
     2667
     2668/**
     2669 * wp.media.view.MediaFrame
     2670 *
     2671 * The frame used to create the media modal.
     2672 *
     2673 * @class
     2674 * @augments wp.media.view.Frame
     2675 * @augments wp.media.View
     2676 * @augments wp.Backbone.View
     2677 * @augments Backbone.View
     2678 * @mixes wp.media.controller.StateMachine
     2679 */
     2680var Frame = wp.media.view.Frame,
     2681    $ = jQuery,
     2682    MediaFrame;
     2683
     2684MediaFrame = Frame.extend({
     2685    className: 'media-frame',
     2686    template:  wp.template('media-frame'),
     2687    regions:   ['menu','title','content','toolbar','router'],
     2688
     2689    events: {
     2690        'click div.media-frame-title h1': 'toggleMenu'
     2691    },
     2692
     2693    /**
     2694     * @global wp.Uploader
     2695     */
     2696    initialize: function() {
     2697        Frame.prototype.initialize.apply( this, arguments );
     2698
     2699        _.defaults( this.options, {
     2700            title:    '',
     2701            modal:    true,
     2702            uploader: true
     2703        });
     2704
     2705        // Ensure core UI is enabled.
     2706        this.$el.addClass('wp-core-ui');
     2707
     2708        // Initialize modal container view.
     2709        if ( this.options.modal ) {
     2710            this.modal = new wp.media.view.Modal({
     2711                controller: this,
     2712                title:      this.options.title
     2713            });
     2714
     2715            this.modal.content( this );
     2716        }
     2717
     2718        // Force the uploader off if the upload limit has been exceeded or
     2719        // if the browser isn't supported.
     2720        if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     2721            this.options.uploader = false;
     2722        }
     2723
     2724        // Initialize window-wide uploader.
     2725        if ( this.options.uploader ) {
     2726            this.uploader = new wp.media.view.UploaderWindow({
     2727                controller: this,
     2728                uploader: {
     2729                    dropzone:  this.modal ? this.modal.$el : this.$el,
     2730                    container: this.$el
     2731                }
     2732            });
     2733            this.views.set( '.media-frame-uploader', this.uploader );
     2734        }
     2735
     2736        this.on( 'attach', _.bind( this.views.ready, this.views ), this );
     2737
     2738        // Bind default title creation.
     2739        this.on( 'title:create:default', this.createTitle, this );
     2740        this.title.mode('default');
     2741
     2742        this.on( 'title:render', function( view ) {
     2743            view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
     2744        });
     2745
     2746        // Bind default menu.
     2747        this.on( 'menu:create:default', this.createMenu, this );
     2748    },
     2749    /**
     2750     * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     2751     */
     2752    render: function() {
     2753        // Activate the default state if no active state exists.
     2754        if ( ! this.state() && this.options.state ) {
     2755            this.setState( this.options.state );
     2756        }
     2757        /**
     2758         * call 'render' directly on the parent class
     2759         */
     2760        return Frame.prototype.render.apply( this, arguments );
     2761    },
     2762    /**
     2763     * @param {Object} title
     2764     * @this wp.media.controller.Region
     2765     */
     2766    createTitle: function( title ) {
     2767        title.view = new wp.media.View({
     2768            controller: this,
     2769            tagName: 'h1'
     2770        });
     2771    },
     2772    /**
     2773     * @param {Object} menu
     2774     * @this wp.media.controller.Region
     2775     */
     2776    createMenu: function( menu ) {
     2777        menu.view = new wp.media.view.Menu({
     2778            controller: this
     2779        });
     2780    },
     2781
     2782    toggleMenu: function() {
     2783        this.$el.find( '.media-menu' ).toggleClass( 'visible' );
     2784    },
     2785
     2786    /**
     2787     * @param {Object} toolbar
     2788     * @this wp.media.controller.Region
     2789     */
     2790    createToolbar: function( toolbar ) {
     2791        toolbar.view = new wp.media.view.Toolbar({
     2792            controller: this
     2793        });
     2794    },
     2795    /**
     2796     * @param {Object} router
     2797     * @this wp.media.controller.Region
     2798     */
     2799    createRouter: function( router ) {
     2800        router.view = new wp.media.view.Router({
     2801            controller: this
     2802        });
     2803    },
     2804    /**
     2805     * @param {Object} options
     2806     */
     2807    createIframeStates: function( options ) {
     2808        var settings = wp.media.view.settings,
     2809            tabs = settings.tabs,
     2810            tabUrl = settings.tabUrl,
     2811            $postId;
     2812
     2813        if ( ! tabs || ! tabUrl ) {
     2814            return;
     2815        }
     2816
     2817        // Add the post ID to the tab URL if it exists.
     2818        $postId = $('#post_ID');
     2819        if ( $postId.length ) {
     2820            tabUrl += '&post_id=' + $postId.val();
     2821        }
     2822
     2823        // Generate the tab states.
     2824        _.each( tabs, function( title, id ) {
     2825            this.state( 'iframe:' + id ).set( _.defaults({
     2826                tab:     id,
     2827                src:     tabUrl + '&tab=' + id,
     2828                title:   title,
     2829                content: 'iframe',
     2830                menu:    'default'
     2831            }, options ) );
     2832        }, this );
     2833
     2834        this.on( 'content:create:iframe', this.iframeContent, this );
     2835        this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
     2836        this.on( 'menu:render:default', this.iframeMenu, this );
     2837        this.on( 'open', this.hijackThickbox, this );
     2838        this.on( 'close', this.restoreThickbox, this );
     2839    },
     2840
     2841    /**
     2842     * @param {Object} content
     2843     * @this wp.media.controller.Region
     2844     */
     2845    iframeContent: function( content ) {
     2846        this.$el.addClass('hide-toolbar');
     2847        content.view = new wp.media.view.Iframe({
     2848            controller: this
     2849        });
     2850    },
     2851
     2852    iframeContentCleanup: function() {
     2853        this.$el.removeClass('hide-toolbar');
     2854    },
     2855
     2856    iframeMenu: function( view ) {
     2857        var views = {};
     2858
     2859        if ( ! view ) {
     2860            return;
     2861        }
     2862
     2863        _.each( wp.media.view.settings.tabs, function( title, id ) {
     2864            views[ 'iframe:' + id ] = {
     2865                text: this.state( 'iframe:' + id ).get('title'),
     2866                priority: 200
     2867            };
     2868        }, this );
     2869
     2870        view.set( views );
     2871    },
     2872
     2873    hijackThickbox: function() {
     2874        var frame = this;
     2875
     2876        if ( ! window.tb_remove || this._tb_remove ) {
     2877            return;
     2878        }
     2879
     2880        this._tb_remove = window.tb_remove;
     2881        window.tb_remove = function() {
     2882            frame.close();
     2883            frame.reset();
     2884            frame.setState( frame.options.state );
     2885            frame._tb_remove.call( window );
     2886        };
     2887    },
     2888
     2889    restoreThickbox: function() {
     2890        if ( ! this._tb_remove ) {
     2891            return;
     2892        }
     2893
     2894        window.tb_remove = this._tb_remove;
     2895        delete this._tb_remove;
     2896    }
     2897});
     2898
     2899// Map some of the modal's methods to the frame.
     2900_.each(['open','close','attach','detach','escape'], function( method ) {
     2901    /**
     2902     * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     2903     */
     2904    MediaFrame.prototype[ method ] = function() {
     2905        if ( this.modal ) {
     2906            this.modal[ method ].apply( this.modal, arguments );
     2907        }
    18192908        return this;
    18202909    };
    18212910});
    18222911
    1823 module.exports = StateMachine;
    1824 
    1825 },{}],17:[function(require,module,exports){
     2912module.exports = MediaFrame;
     2913
     2914
     2915/***/ }),
     2916/* 48 */
     2917/***/ (function(module, exports) {
     2918
    18262919/**
    1827  * wp.media.controller.State
    1828  *
    1829  * A state is a step in a workflow that when set will trigger the controllers
    1830  * for the regions to be updated as specified in the frame.
    1831  *
    1832  * A state has an event-driven lifecycle:
    1833  *
    1834  *     'ready'      triggers when a state is added to a state machine's collection.
    1835  *     'activate'   triggers when a state is activated by a state machine.
    1836  *     'deactivate' triggers when a state is deactivated by a state machine.
    1837  *     'reset'      is not triggered automatically. It should be invoked by the
    1838  *                  proper controller to reset the state to its default.
     2920 * wp.media.view.MediaFrame.Select
     2921 *
     2922 * A frame for selecting an item or items from the media library.
    18392923 *
    18402924 * @class
    1841  * @augments Backbone.Model
     2925 * @augments wp.media.view.MediaFrame
     2926 * @augments wp.media.view.Frame
     2927 * @augments wp.media.View
     2928 * @augments wp.Backbone.View
     2929 * @augments Backbone.View
     2930 * @mixes wp.media.controller.StateMachine
    18422931 */
    1843 var State = Backbone.Model.extend({
    1844     /**
    1845      * Constructor.
     2932
     2933var MediaFrame = wp.media.view.MediaFrame,
     2934    l10n = wp.media.view.l10n,
     2935    Select;
     2936
     2937Select = MediaFrame.extend({
     2938    initialize: function() {
     2939        // Call 'initialize' directly on the parent class.
     2940        MediaFrame.prototype.initialize.apply( this, arguments );
     2941
     2942        _.defaults( this.options, {
     2943            selection: [],
     2944            library:   {},
     2945            multiple:  false,
     2946            state:    'library'
     2947        });
     2948
     2949        this.createSelection();
     2950        this.createStates();
     2951        this.bindHandlers();
     2952    },
     2953
     2954    /**
     2955     * Attach a selection collection to the frame.
    18462956     *
    1847      * @since 3.5.0
    1848      */
    1849     constructor: function() {
    1850         this.on( 'activate', this._preActivate, this );
    1851         this.on( 'activate', this.activate, this );
    1852         this.on( 'activate', this._postActivate, this );
    1853         this.on( 'deactivate', this._deactivate, this );
    1854         this.on( 'deactivate', this.deactivate, this );
    1855         this.on( 'reset', this.reset, this );
    1856         this.on( 'ready', this._ready, this );
    1857         this.on( 'ready', this.ready, this );
    1858         /**
    1859          * Call parent constructor with passed arguments
    1860          */
    1861         Backbone.Model.apply( this, arguments );
    1862         this.on( 'change:menu', this._updateMenu, this );
    1863     },
    1864     /**
    1865      * Ready event callback.
     2957     * A selection is a collection of attachments used for a specific purpose
     2958     * by a media frame. e.g. Selecting an attachment (or many) to insert into
     2959     * post content.
    18662960     *
    1867      * @abstract
    1868      * @since 3.5.0
    1869      */
    1870     ready: function() {},
    1871 
    1872     /**
    1873      * Activate event callback.
     2961     * @see media.model.Selection
     2962     */
     2963    createSelection: function() {
     2964        var selection = this.options.selection;
     2965
     2966        if ( ! (selection instanceof wp.media.model.Selection) ) {
     2967            this.options.selection = new wp.media.model.Selection( selection, {
     2968                multiple: this.options.multiple
     2969            });
     2970        }
     2971
     2972        this._selection = {
     2973            attachments: new wp.media.model.Attachments(),
     2974            difference: []
     2975        };
     2976    },
     2977
     2978    /**
     2979     * Create the default states on the frame.
     2980     */
     2981    createStates: function() {
     2982        var options = this.options;
     2983
     2984        if ( this.options.states ) {
     2985            return;
     2986        }
     2987
     2988        // Add the default states.
     2989        this.states.add([
     2990            // Main states.
     2991            new wp.media.controller.Library({
     2992                library:   wp.media.query( options.library ),
     2993                multiple:  options.multiple,
     2994                title:     options.title,
     2995                priority:  20
     2996            })
     2997        ]);
     2998    },
     2999
     3000    /**
     3001     * Bind region mode event callbacks.
    18743002     *
    1875      * @abstract
    1876      * @since 3.5.0
    1877      */
    1878     activate: function() {},
    1879 
    1880     /**
    1881      * Deactivate event callback.
     3003     * @see media.controller.Region.render
     3004     */
     3005    bindHandlers: function() {
     3006        this.on( 'router:create:browse', this.createRouter, this );
     3007        this.on( 'router:render:browse', this.browseRouter, this );
     3008        this.on( 'content:create:browse', this.browseContent, this );
     3009        this.on( 'content:render:upload', this.uploadContent, this );
     3010        this.on( 'toolbar:create:select', this.createSelectToolbar, this );
     3011    },
     3012
     3013    /**
     3014     * Render callback for the router region in the `browse` mode.
    18823015     *
    1883      * @abstract
    1884      * @since 3.5.0
    1885      */
    1886     deactivate: function() {},
    1887 
    1888     /**
    1889      * Reset event callback.
     3016     * @param {wp.media.view.Router} routerView
     3017     */
     3018    browseRouter: function( routerView ) {
     3019        routerView.set({
     3020            upload: {
     3021                text:     l10n.uploadFilesTitle,
     3022                priority: 20
     3023            },
     3024            browse: {
     3025                text:     l10n.mediaLibraryTitle,
     3026                priority: 40
     3027            }
     3028        });
     3029    },
     3030
     3031    /**
     3032     * Render callback for the content region in the `browse` mode.
    18903033     *
    1891      * @abstract
    1892      * @since 3.5.0
    1893      */
    1894     reset: function() {},
    1895 
    1896     /**
    1897      * @access private
    1898      * @since 3.5.0
    1899      */
    1900     _ready: function() {
    1901         this._updateMenu();
    1902     },
    1903 
    1904     /**
    1905      * @access private
    1906      * @since 3.5.0
    1907     */
    1908     _preActivate: function() {
    1909         this.active = true;
    1910     },
    1911 
    1912     /**
    1913      * @access private
    1914      * @since 3.5.0
    1915      */
    1916     _postActivate: function() {
    1917         this.on( 'change:menu', this._menu, this );
    1918         this.on( 'change:titleMode', this._title, this );
    1919         this.on( 'change:content', this._content, this );
    1920         this.on( 'change:toolbar', this._toolbar, this );
    1921 
    1922         this.frame.on( 'title:render:default', this._renderTitle, this );
    1923 
    1924         this._title();
    1925         this._menu();
    1926         this._toolbar();
    1927         this._content();
    1928         this._router();
    1929     },
    1930 
    1931     /**
    1932      * @access private
    1933      * @since 3.5.0
    1934      */
    1935     _deactivate: function() {
    1936         this.active = false;
    1937 
    1938         this.frame.off( 'title:render:default', this._renderTitle, this );
    1939 
    1940         this.off( 'change:menu', this._menu, this );
    1941         this.off( 'change:titleMode', this._title, this );
    1942         this.off( 'change:content', this._content, this );
    1943         this.off( 'change:toolbar', this._toolbar, this );
    1944     },
    1945 
    1946     /**
    1947      * @access private
    1948      * @since 3.5.0
    1949      */
    1950     _title: function() {
    1951         this.frame.title.render( this.get('titleMode') || 'default' );
    1952     },
    1953 
    1954     /**
    1955      * @access private
    1956      * @since 3.5.0
    1957      */
    1958     _renderTitle: function( view ) {
    1959         view.$el.text( this.get('title') || '' );
    1960     },
    1961 
    1962     /**
    1963      * @access private
    1964      * @since 3.5.0
    1965      */
    1966     _router: function() {
    1967         var router = this.frame.router,
    1968             mode = this.get('router'),
    1969             view;
    1970 
    1971         this.frame.$el.toggleClass( 'hide-router', ! mode );
    1972         if ( ! mode ) {
    1973             return;
    1974         }
    1975 
    1976         this.frame.router.render( mode );
    1977 
    1978         view = router.get();
    1979         if ( view && view.select ) {
    1980             view.select( this.frame.content.mode() );
    1981         }
    1982     },
    1983 
    1984     /**
    1985      * @access private
    1986      * @since 3.5.0
    1987      */
    1988     _menu: function() {
    1989         var menu = this.frame.menu,
    1990             mode = this.get('menu'),
    1991             view;
    1992 
    1993         this.frame.$el.toggleClass( 'hide-menu', ! mode );
    1994         if ( ! mode ) {
    1995             return;
    1996         }
    1997 
    1998         menu.mode( mode );
    1999 
    2000         view = menu.get();
    2001         if ( view && view.select ) {
    2002             view.select( this.id );
    2003         }
    2004     },
    2005 
    2006     /**
    2007      * @access private
    2008      * @since 3.5.0
    2009      */
    2010     _updateMenu: function() {
    2011         var previous = this.previous('menu'),
    2012             menu = this.get('menu');
    2013 
    2014         if ( previous ) {
    2015             this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
    2016         }
    2017 
    2018         if ( menu ) {
    2019             this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
    2020         }
    2021     },
    2022 
    2023     /**
    2024      * Create a view in the media menu for the state.
     3034     * @param {wp.media.controller.Region} contentRegion
     3035     */
     3036    browseContent: function( contentRegion ) {
     3037        var state = this.state();
     3038
     3039        this.$el.removeClass('hide-toolbar');
     3040
     3041        // Browse our library of attachments.
     3042        contentRegion.view = new wp.media.view.AttachmentsBrowser({
     3043            controller: this,
     3044            collection: state.get('library'),
     3045            selection:  state.get('selection'),
     3046            model:      state,
     3047            sortable:   state.get('sortable'),
     3048            search:     state.get('searchable'),
     3049            filters:    state.get('filterable'),
     3050            date:       state.get('date'),
     3051            display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
     3052            dragInfo:   state.get('dragInfo'),
     3053
     3054            idealColumnWidth: state.get('idealColumnWidth'),
     3055            suggestedWidth:   state.get('suggestedWidth'),
     3056            suggestedHeight:  state.get('suggestedHeight'),
     3057
     3058            AttachmentView: state.get('AttachmentView')
     3059        });
     3060    },
     3061
     3062    /**
     3063     * Render callback for the content region in the `upload` mode.
     3064     */
     3065    uploadContent: function() {
     3066        this.$el.removeClass( 'hide-toolbar' );
     3067        this.content.set( new wp.media.view.UploaderInline({
     3068            controller: this
     3069        }) );
     3070    },
     3071
     3072    /**
     3073     * Toolbars
    20253074     *
    2026      * @access private
    2027      * @since 3.5.0
    2028      *
    2029      * @param {media.view.Menu} view The menu view.
    2030      */
    2031     _renderMenu: function( view ) {
    2032         var menuItem = this.get('menuItem'),
    2033             title = this.get('title'),
    2034             priority = this.get('priority');
    2035 
    2036         if ( ! menuItem && title ) {
    2037             menuItem = { text: title };
    2038 
    2039             if ( priority ) {
    2040                 menuItem.priority = priority;
    2041             }
    2042         }
    2043 
    2044         if ( ! menuItem ) {
    2045             return;
    2046         }
    2047 
    2048         view.set( this.id, menuItem );
     3075     * @param {Object} toolbar
     3076     * @param {Object} [options={}]
     3077     * @this wp.media.controller.Region
     3078     */
     3079    createSelectToolbar: function( toolbar, options ) {
     3080        options = options || this.options.button || {};
     3081        options.controller = this;
     3082
     3083        toolbar.view = new wp.media.view.Toolbar.Select( options );
    20493084    }
    20503085});
    20513086
    2052 _.each(['toolbar','content'], function( region ) {
    2053     /**
    2054      * @access private
    2055      */
    2056     State.prototype[ '_' + region ] = function() {
    2057         var mode = this.get( region );
    2058         if ( mode ) {
    2059             this.frame[ region ].render( mode );
    2060         }
    2061     };
     3087module.exports = Select;
     3088
     3089
     3090/***/ }),
     3091/* 49 */
     3092/***/ (function(module, exports) {
     3093
     3094/**
     3095 * wp.media.view.MediaFrame.Post
     3096 *
     3097 * The frame for manipulating media on the Edit Post page.
     3098 *
     3099 * @class
     3100 * @augments wp.media.view.MediaFrame.Select
     3101 * @augments wp.media.view.MediaFrame
     3102 * @augments wp.media.view.Frame
     3103 * @augments wp.media.View
     3104 * @augments wp.Backbone.View
     3105 * @augments Backbone.View
     3106 * @mixes wp.media.controller.StateMachine
     3107 */
     3108var Select = wp.media.view.MediaFrame.Select,
     3109    Library = wp.media.controller.Library,
     3110    l10n = wp.media.view.l10n,
     3111    Post;
     3112
     3113Post = Select.extend({
     3114    initialize: function() {
     3115        this.counts = {
     3116            audio: {
     3117                count: wp.media.view.settings.attachmentCounts.audio,
     3118                state: 'playlist'
     3119            },
     3120            video: {
     3121                count: wp.media.view.settings.attachmentCounts.video,
     3122                state: 'video-playlist'
     3123            }
     3124        };
     3125
     3126        _.defaults( this.options, {
     3127            multiple:  true,
     3128            editing:   false,
     3129            state:    'insert',
     3130            metadata:  {}
     3131        });
     3132
     3133        // Call 'initialize' directly on the parent class.
     3134        Select.prototype.initialize.apply( this, arguments );
     3135        this.createIframeStates();
     3136
     3137    },
     3138
     3139    /**
     3140     * Create the default states.
     3141     */
     3142    createStates: function() {
     3143        var options = this.options;
     3144
     3145        this.states.add([
     3146            // Main states.
     3147            new Library({
     3148                id:         'insert',
     3149                title:      l10n.insertMediaTitle,
     3150                priority:   20,
     3151                toolbar:    'main-insert',
     3152                filterable: 'all',
     3153                library:    wp.media.query( options.library ),
     3154                multiple:   options.multiple ? 'reset' : false,
     3155                editable:   true,
     3156
     3157                // If the user isn't allowed to edit fields,
     3158                // can they still edit it locally?
     3159                allowLocalEdits: true,
     3160
     3161                // Show the attachment display settings.
     3162                displaySettings: true,
     3163                // Update user settings when users adjust the
     3164                // attachment display settings.
     3165                displayUserSettings: true
     3166            }),
     3167
     3168            new Library({
     3169                id:         'gallery',
     3170                title:      l10n.createGalleryTitle,
     3171                priority:   40,
     3172                toolbar:    'main-gallery',
     3173                filterable: 'uploaded',
     3174                multiple:   'add',
     3175                editable:   false,
     3176
     3177                library:  wp.media.query( _.defaults({
     3178                    type: 'image'
     3179                }, options.library ) )
     3180            }),
     3181
     3182            // Embed states.
     3183            new wp.media.controller.Embed( { metadata: options.metadata } ),
     3184
     3185            new wp.media.controller.EditImage( { model: options.editImage } ),
     3186
     3187            // Gallery states.
     3188            new wp.media.controller.GalleryEdit({
     3189                library: options.selection,
     3190                editing: options.editing,
     3191                menu:    'gallery'
     3192            }),
     3193
     3194            new wp.media.controller.GalleryAdd(),
     3195
     3196            new Library({
     3197                id:         'playlist',
     3198                title:      l10n.createPlaylistTitle,
     3199                priority:   60,
     3200                toolbar:    'main-playlist',
     3201                filterable: 'uploaded',
     3202                multiple:   'add',
     3203                editable:   false,
     3204
     3205                library:  wp.media.query( _.defaults({
     3206                    type: 'audio'
     3207                }, options.library ) )
     3208            }),
     3209
     3210            // Playlist states.
     3211            new wp.media.controller.CollectionEdit({
     3212                type: 'audio',
     3213                collectionType: 'playlist',
     3214                title:          l10n.editPlaylistTitle,
     3215                SettingsView:   wp.media.view.Settings.Playlist,
     3216                library:        options.selection,
     3217                editing:        options.editing,
     3218                menu:           'playlist',
     3219                dragInfoText:   l10n.playlistDragInfo,
     3220                dragInfo:       false
     3221            }),
     3222
     3223            new wp.media.controller.CollectionAdd({
     3224                type: 'audio',
     3225                collectionType: 'playlist',
     3226                title: l10n.addToPlaylistTitle
     3227            }),
     3228
     3229            new Library({
     3230                id:         'video-playlist',
     3231                title:      l10n.createVideoPlaylistTitle,
     3232                priority:   60,
     3233                toolbar:    'main-video-playlist',
     3234                filterable: 'uploaded',
     3235                multiple:   'add',
     3236                editable:   false,
     3237
     3238                library:  wp.media.query( _.defaults({
     3239                    type: 'video'
     3240                }, options.library ) )
     3241            }),
     3242
     3243            new wp.media.controller.CollectionEdit({
     3244                type: 'video',
     3245                collectionType: 'playlist',
     3246                title:          l10n.editVideoPlaylistTitle,
     3247                SettingsView:   wp.media.view.Settings.Playlist,
     3248                library:        options.selection,
     3249                editing:        options.editing,
     3250                menu:           'video-playlist',
     3251                dragInfoText:   l10n.videoPlaylistDragInfo,
     3252                dragInfo:       false
     3253            }),
     3254
     3255            new wp.media.controller.CollectionAdd({
     3256                type: 'video',
     3257                collectionType: 'playlist',
     3258                title: l10n.addToVideoPlaylistTitle
     3259            })
     3260        ]);
     3261
     3262        if ( wp.media.view.settings.post.featuredImageId ) {
     3263            this.states.add( new wp.media.controller.FeaturedImage() );
     3264        }
     3265    },
     3266
     3267    bindHandlers: function() {
     3268        var handlers, checkCounts;
     3269
     3270        Select.prototype.bindHandlers.apply( this, arguments );
     3271
     3272        this.on( 'activate', this.activate, this );
     3273
     3274        // Only bother checking media type counts if one of the counts is zero
     3275        checkCounts = _.find( this.counts, function( type ) {
     3276            return type.count === 0;
     3277        } );
     3278
     3279        if ( typeof checkCounts !== 'undefined' ) {
     3280            this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
     3281        }
     3282
     3283        this.on( 'menu:create:gallery', this.createMenu, this );
     3284        this.on( 'menu:create:playlist', this.createMenu, this );
     3285        this.on( 'menu:create:video-playlist', this.createMenu, this );
     3286        this.on( 'toolbar:create:main-insert', this.createToolbar, this );
     3287        this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
     3288        this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
     3289        this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
     3290        this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
     3291        this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
     3292
     3293        handlers = {
     3294            menu: {
     3295                'default': 'mainMenu',
     3296                'gallery': 'galleryMenu',
     3297                'playlist': 'playlistMenu',
     3298                'video-playlist': 'videoPlaylistMenu'
     3299            },
     3300
     3301            content: {
     3302                'embed':          'embedContent',
     3303                'edit-image':     'editImageContent',
     3304                'edit-selection': 'editSelectionContent'
     3305            },
     3306
     3307            toolbar: {
     3308                'main-insert':      'mainInsertToolbar',
     3309                'main-gallery':     'mainGalleryToolbar',
     3310                'gallery-edit':     'galleryEditToolbar',
     3311                'gallery-add':      'galleryAddToolbar',
     3312                'main-playlist':    'mainPlaylistToolbar',
     3313                'playlist-edit':    'playlistEditToolbar',
     3314                'playlist-add':     'playlistAddToolbar',
     3315                'main-video-playlist': 'mainVideoPlaylistToolbar',
     3316                'video-playlist-edit': 'videoPlaylistEditToolbar',
     3317                'video-playlist-add': 'videoPlaylistAddToolbar'
     3318            }
     3319        };
     3320
     3321        _.each( handlers, function( regionHandlers, region ) {
     3322            _.each( regionHandlers, function( callback, handler ) {
     3323                this.on( region + ':render:' + handler, this[ callback ], this );
     3324            }, this );
     3325        }, this );
     3326    },
     3327
     3328    activate: function() {
     3329        // Hide menu items for states tied to particular media types if there are no items
     3330        _.each( this.counts, function( type ) {
     3331            if ( type.count < 1 ) {
     3332                this.menuItemVisibility( type.state, 'hide' );
     3333            }
     3334        }, this );
     3335    },
     3336
     3337    mediaTypeCounts: function( model, attr ) {
     3338        if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
     3339            this.counts[ attr ].count++;
     3340            this.menuItemVisibility( this.counts[ attr ].state, 'show' );
     3341        }
     3342    },
     3343
     3344    // Menus
     3345    /**
     3346     * @param {wp.Backbone.View} view
     3347     */
     3348    mainMenu: function( view ) {
     3349        view.set({
     3350            'library-separator': new wp.media.View({
     3351                className: 'separator',
     3352                priority: 100
     3353            })
     3354        });
     3355    },
     3356
     3357    menuItemVisibility: function( state, visibility ) {
     3358        var menu = this.menu.get();
     3359        if ( visibility === 'hide' ) {
     3360            menu.hide( state );
     3361        } else if ( visibility === 'show' ) {
     3362            menu.show( state );
     3363        }
     3364    },
     3365    /**
     3366     * @param {wp.Backbone.View} view
     3367     */
     3368    galleryMenu: function( view ) {
     3369        var lastState = this.lastState(),
     3370            previous = lastState && lastState.id,
     3371            frame = this;
     3372
     3373        view.set({
     3374            cancel: {
     3375                text:     l10n.cancelGalleryTitle,
     3376                priority: 20,
     3377                click:    function() {
     3378                    if ( previous ) {
     3379                        frame.setState( previous );
     3380                    } else {
     3381                        frame.close();
     3382                    }
     3383
     3384                    // Keep focus inside media modal
     3385                    // after canceling a gallery
     3386                    this.controller.modal.focusManager.focus();
     3387                }
     3388            },
     3389            separateCancel: new wp.media.View({
     3390                className: 'separator',
     3391                priority: 40
     3392            })
     3393        });
     3394    },
     3395
     3396    playlistMenu: function( view ) {
     3397        var lastState = this.lastState(),
     3398            previous = lastState && lastState.id,
     3399            frame = this;
     3400
     3401        view.set({
     3402            cancel: {
     3403                text:     l10n.cancelPlaylistTitle,
     3404                priority: 20,
     3405                click:    function() {
     3406                    if ( previous ) {
     3407                        frame.setState( previous );
     3408                    } else {
     3409                        frame.close();
     3410                    }
     3411                }
     3412            },
     3413            separateCancel: new wp.media.View({
     3414                className: 'separator',
     3415                priority: 40
     3416            })
     3417        });
     3418    },
     3419
     3420    videoPlaylistMenu: function( view ) {
     3421        var lastState = this.lastState(),
     3422            previous = lastState && lastState.id,
     3423            frame = this;
     3424
     3425        view.set({
     3426            cancel: {
     3427                text:     l10n.cancelVideoPlaylistTitle,
     3428                priority: 20,
     3429                click:    function() {
     3430                    if ( previous ) {
     3431                        frame.setState( previous );
     3432                    } else {
     3433                        frame.close();
     3434                    }
     3435                }
     3436            },
     3437            separateCancel: new wp.media.View({
     3438                className: 'separator',
     3439                priority: 40
     3440            })
     3441        });
     3442    },
     3443
     3444    // Content
     3445    embedContent: function() {
     3446        var view = new wp.media.view.Embed({
     3447            controller: this,
     3448            model:      this.state()
     3449        }).render();
     3450
     3451        this.content.set( view );
     3452
     3453        if ( ! wp.media.isTouchDevice ) {
     3454            view.url.focus();
     3455        }
     3456    },
     3457
     3458    editSelectionContent: function() {
     3459        var state = this.state(),
     3460            selection = state.get('selection'),
     3461            view;
     3462
     3463        view = new wp.media.view.AttachmentsBrowser({
     3464            controller: this,
     3465            collection: selection,
     3466            selection:  selection,
     3467            model:      state,
     3468            sortable:   true,
     3469            search:     false,
     3470            date:       false,
     3471            dragInfo:   true,
     3472
     3473            AttachmentView: wp.media.view.Attachments.EditSelection
     3474        }).render();
     3475
     3476        view.toolbar.set( 'backToLibrary', {
     3477            text:     l10n.returnToLibrary,
     3478            priority: -100,
     3479
     3480            click: function() {
     3481                this.controller.content.mode('browse');
     3482            }
     3483        });
     3484
     3485        // Browse our library of attachments.
     3486        this.content.set( view );
     3487
     3488        // Trigger the controller to set focus
     3489        this.trigger( 'edit:selection', this );
     3490    },
     3491
     3492    editImageContent: function() {
     3493        var image = this.state().get('image'),
     3494            view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
     3495
     3496        this.content.set( view );
     3497
     3498        // after creating the wrapper view, load the actual editor via an ajax call
     3499        view.loadEditor();
     3500
     3501    },
     3502
     3503    // Toolbars
     3504
     3505    /**
     3506     * @param {wp.Backbone.View} view
     3507     */
     3508    selectionStatusToolbar: function( view ) {
     3509        var editable = this.state().get('editable');
     3510
     3511        view.set( 'selection', new wp.media.view.Selection({
     3512            controller: this,
     3513            collection: this.state().get('selection'),
     3514            priority:   -40,
     3515
     3516            // If the selection is editable, pass the callback to
     3517            // switch the content mode.
     3518            editable: editable && function() {
     3519                this.controller.content.mode('edit-selection');
     3520            }
     3521        }).render() );
     3522    },
     3523
     3524    /**
     3525     * @param {wp.Backbone.View} view
     3526     */
     3527    mainInsertToolbar: function( view ) {
     3528        var controller = this;
     3529
     3530        this.selectionStatusToolbar( view );
     3531
     3532        view.set( 'insert', {
     3533            style:    'primary',
     3534            priority: 80,
     3535            text:     l10n.insertIntoPost,
     3536            requires: { selection: true },
     3537
     3538            /**
     3539             * @fires wp.media.controller.State#insert
     3540             */
     3541            click: function() {
     3542                var state = controller.state(),
     3543                    selection = state.get('selection');
     3544
     3545                controller.close();
     3546                state.trigger( 'insert', selection ).reset();
     3547            }
     3548        });
     3549    },
     3550
     3551    /**
     3552     * @param {wp.Backbone.View} view
     3553     */
     3554    mainGalleryToolbar: function( view ) {
     3555        var controller = this;
     3556
     3557        this.selectionStatusToolbar( view );
     3558
     3559        view.set( 'gallery', {
     3560            style:    'primary',
     3561            text:     l10n.createNewGallery,
     3562            priority: 60,
     3563            requires: { selection: true },
     3564
     3565            click: function() {
     3566                var selection = controller.state().get('selection'),
     3567                    edit = controller.state('gallery-edit'),
     3568                    models = selection.where({ type: 'image' });
     3569
     3570                edit.set( 'library', new wp.media.model.Selection( models, {
     3571                    props:    selection.props.toJSON(),
     3572                    multiple: true
     3573                }) );
     3574
     3575                this.controller.setState('gallery-edit');
     3576
     3577                // Keep focus inside media modal
     3578                // after jumping to gallery view
     3579                this.controller.modal.focusManager.focus();
     3580            }
     3581        });
     3582    },
     3583
     3584    mainPlaylistToolbar: function( view ) {
     3585        var controller = this;
     3586
     3587        this.selectionStatusToolbar( view );
     3588
     3589        view.set( 'playlist', {
     3590            style:    'primary',
     3591            text:     l10n.createNewPlaylist,
     3592            priority: 100,
     3593            requires: { selection: true },
     3594
     3595            click: function() {
     3596                var selection = controller.state().get('selection'),
     3597                    edit = controller.state('playlist-edit'),
     3598                    models = selection.where({ type: 'audio' });
     3599
     3600                edit.set( 'library', new wp.media.model.Selection( models, {
     3601                    props:    selection.props.toJSON(),
     3602                    multiple: true
     3603                }) );
     3604
     3605                this.controller.setState('playlist-edit');
     3606
     3607                // Keep focus inside media modal
     3608                // after jumping to playlist view
     3609                this.controller.modal.focusManager.focus();
     3610            }
     3611        });
     3612    },
     3613
     3614    mainVideoPlaylistToolbar: function( view ) {
     3615        var controller = this;
     3616
     3617        this.selectionStatusToolbar( view );
     3618
     3619        view.set( 'video-playlist', {
     3620            style:    'primary',
     3621            text:     l10n.createNewVideoPlaylist,
     3622            priority: 100,
     3623            requires: { selection: true },
     3624
     3625            click: function() {
     3626                var selection = controller.state().get('selection'),
     3627                    edit = controller.state('video-playlist-edit'),
     3628                    models = selection.where({ type: 'video' });
     3629
     3630                edit.set( 'library', new wp.media.model.Selection( models, {
     3631                    props:    selection.props.toJSON(),
     3632                    multiple: true
     3633                }) );
     3634
     3635                this.controller.setState('video-playlist-edit');
     3636
     3637                // Keep focus inside media modal
     3638                // after jumping to video playlist view
     3639                this.controller.modal.focusManager.focus();
     3640            }
     3641        });
     3642    },
     3643
     3644    featuredImageToolbar: function( toolbar ) {
     3645        this.createSelectToolbar( toolbar, {
     3646            text:  l10n.setFeaturedImage,
     3647            state: this.options.state
     3648        });
     3649    },
     3650
     3651    mainEmbedToolbar: function( toolbar ) {
     3652        toolbar.view = new wp.media.view.Toolbar.Embed({
     3653            controller: this
     3654        });
     3655    },
     3656
     3657    galleryEditToolbar: function() {
     3658        var editing = this.state().get('editing');
     3659        this.toolbar.set( new wp.media.view.Toolbar({
     3660            controller: this,
     3661            items: {
     3662                insert: {
     3663                    style:    'primary',
     3664                    text:     editing ? l10n.updateGallery : l10n.insertGallery,
     3665                    priority: 80,
     3666                    requires: { library: true },
     3667
     3668                    /**
     3669                     * @fires wp.media.controller.State#update
     3670                     */
     3671                    click: function() {
     3672                        var controller = this.controller,
     3673                            state = controller.state();
     3674
     3675                        controller.close();
     3676                        state.trigger( 'update', state.get('library') );
     3677
     3678                        // Restore and reset the default state.
     3679                        controller.setState( controller.options.state );
     3680                        controller.reset();
     3681                    }
     3682                }
     3683            }
     3684        }) );
     3685    },
     3686
     3687    galleryAddToolbar: function() {
     3688        this.toolbar.set( new wp.media.view.Toolbar({
     3689            controller: this,
     3690            items: {
     3691                insert: {
     3692                    style:    'primary',
     3693                    text:     l10n.addToGallery,
     3694                    priority: 80,
     3695                    requires: { selection: true },
     3696
     3697                    /**
     3698                     * @fires wp.media.controller.State#reset
     3699                     */
     3700                    click: function() {
     3701                        var controller = this.controller,
     3702                            state = controller.state(),
     3703                            edit = controller.state('gallery-edit');
     3704
     3705                        edit.get('library').add( state.get('selection').models );
     3706                        state.trigger('reset');
     3707                        controller.setState('gallery-edit');
     3708                    }
     3709                }
     3710            }
     3711        }) );
     3712    },
     3713
     3714    playlistEditToolbar: function() {
     3715        var editing = this.state().get('editing');
     3716        this.toolbar.set( new wp.media.view.Toolbar({
     3717            controller: this,
     3718            items: {
     3719                insert: {
     3720                    style:    'primary',
     3721                    text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
     3722                    priority: 80,
     3723                    requires: { library: true },
     3724
     3725                    /**
     3726                     * @fires wp.media.controller.State#update
     3727                     */
     3728                    click: function() {
     3729                        var controller = this.controller,
     3730                            state = controller.state();
     3731
     3732                        controller.close();
     3733                        state.trigger( 'update', state.get('library') );
     3734
     3735                        // Restore and reset the default state.
     3736                        controller.setState( controller.options.state );
     3737                        controller.reset();
     3738                    }
     3739                }
     3740            }
     3741        }) );
     3742    },
     3743
     3744    playlistAddToolbar: function() {
     3745        this.toolbar.set( new wp.media.view.Toolbar({
     3746            controller: this,
     3747            items: {
     3748                insert: {
     3749                    style:    'primary',
     3750                    text:     l10n.addToPlaylist,
     3751                    priority: 80,
     3752                    requires: { selection: true },
     3753
     3754                    /**
     3755                     * @fires wp.media.controller.State#reset
     3756                     */
     3757                    click: function() {
     3758                        var controller = this.controller,
     3759                            state = controller.state(),
     3760                            edit = controller.state('playlist-edit');
     3761
     3762                        edit.get('library').add( state.get('selection').models );
     3763                        state.trigger('reset');
     3764                        controller.setState('playlist-edit');
     3765                    }
     3766                }
     3767            }
     3768        }) );
     3769    },
     3770
     3771    videoPlaylistEditToolbar: function() {
     3772        var editing = this.state().get('editing');
     3773        this.toolbar.set( new wp.media.view.Toolbar({
     3774            controller: this,
     3775            items: {
     3776                insert: {
     3777                    style:    'primary',
     3778                    text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
     3779                    priority: 140,
     3780                    requires: { library: true },
     3781
     3782                    click: function() {
     3783                        var controller = this.controller,
     3784                            state = controller.state(),
     3785                            library = state.get('library');
     3786
     3787                        library.type = 'video';
     3788
     3789                        controller.close();
     3790                        state.trigger( 'update', library );
     3791
     3792                        // Restore and reset the default state.
     3793                        controller.setState( controller.options.state );
     3794                        controller.reset();
     3795                    }
     3796                }
     3797            }
     3798        }) );
     3799    },
     3800
     3801    videoPlaylistAddToolbar: function() {
     3802        this.toolbar.set( new wp.media.view.Toolbar({
     3803            controller: this,
     3804            items: {
     3805                insert: {
     3806                    style:    'primary',
     3807                    text:     l10n.addToVideoPlaylist,
     3808                    priority: 140,
     3809                    requires: { selection: true },
     3810
     3811                    click: function() {
     3812                        var controller = this.controller,
     3813                            state = controller.state(),
     3814                            edit = controller.state('video-playlist-edit');
     3815
     3816                        edit.get('library').add( state.get('selection').models );
     3817                        state.trigger('reset');
     3818                        controller.setState('video-playlist-edit');
     3819                    }
     3820                }
     3821            }
     3822        }) );
     3823    }
    20623824});
    20633825
    2064 module.exports = State;
    2065 
    2066 },{}],18:[function(require,module,exports){
     3826module.exports = Post;
     3827
     3828
     3829/***/ }),
     3830/* 50 */
     3831/***/ (function(module, exports) {
     3832
    20673833/**
    2068  * wp.media.selectionSync
    2069  *
    2070  * Sync an attachments selection in a state with another state.
    2071  *
    2072  * Allows for selecting multiple images in the Insert Media workflow, and then
    2073  * switching to the Insert Gallery workflow while preserving the attachments selection.
    2074  *
    2075  * @mixin
     3834 * wp.media.view.MediaFrame.ImageDetails
     3835 *
     3836 * A media frame for manipulating an image that's already been inserted
     3837 * into a post.
     3838 *
     3839 * @class
     3840 * @augments wp.media.view.MediaFrame.Select
     3841 * @augments wp.media.view.MediaFrame
     3842 * @augments wp.media.view.Frame
     3843 * @augments wp.media.View
     3844 * @augments wp.Backbone.View
     3845 * @augments Backbone.View
     3846 * @mixes wp.media.controller.StateMachine
    20763847 */
    2077 var selectionSync = {
    2078     /**
    2079      * @since 3.5.0
    2080      */
    2081     syncSelection: function() {
    2082         var selection = this.get('selection'),
    2083             manager = this.frame._selection;
    2084 
    2085         if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     3848var Select = wp.media.view.MediaFrame.Select,
     3849    l10n = wp.media.view.l10n,
     3850    ImageDetails;
     3851
     3852ImageDetails = Select.extend({
     3853    defaults: {
     3854        id:      'image',
     3855        url:     '',
     3856        menu:    'image-details',
     3857        content: 'image-details',
     3858        toolbar: 'image-details',
     3859        type:    'link',
     3860        title:    l10n.imageDetailsTitle,
     3861        priority: 120
     3862    },
     3863
     3864    initialize: function( options ) {
     3865        this.image = new wp.media.model.PostImage( options.metadata );
     3866        this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
     3867        Select.prototype.initialize.apply( this, arguments );
     3868    },
     3869
     3870    bindHandlers: function() {
     3871        Select.prototype.bindHandlers.apply( this, arguments );
     3872        this.on( 'menu:create:image-details', this.createMenu, this );
     3873        this.on( 'content:create:image-details', this.imageDetailsContent, this );
     3874        this.on( 'content:render:edit-image', this.editImageContent, this );
     3875        this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
     3876        // override the select toolbar
     3877        this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
     3878    },
     3879
     3880    createStates: function() {
     3881        this.states.add([
     3882            new wp.media.controller.ImageDetails({
     3883                image: this.image,
     3884                editable: false
     3885            }),
     3886            new wp.media.controller.ReplaceImage({
     3887                id: 'replace-image',
     3888                library: wp.media.query( { type: 'image' } ),
     3889                image: this.image,
     3890                multiple:  false,
     3891                title:     l10n.imageReplaceTitle,
     3892                toolbar: 'replace',
     3893                priority:  80,
     3894                displaySettings: true
     3895            }),
     3896            new wp.media.controller.EditImage( {
     3897                image: this.image,
     3898                selection: this.options.selection
     3899            } )
     3900        ]);
     3901    },
     3902
     3903    imageDetailsContent: function( options ) {
     3904        options.view = new wp.media.view.ImageDetails({
     3905            controller: this,
     3906            model: this.state().image,
     3907            attachment: this.state().image.attachment
     3908        });
     3909    },
     3910
     3911    editImageContent: function() {
     3912        var state = this.state(),
     3913            model = state.get('image'),
     3914            view;
     3915
     3916        if ( ! model ) {
    20863917            return;
    20873918        }
    20883919
    2089         // If the selection supports multiple items, validate the stored
    2090         // attachments based on the new selection's conditions. Record
    2091         // the attachments that are not included; we'll maintain a
    2092         // reference to those. Other attachments are considered in flux.
    2093         if ( selection.multiple ) {
    2094             selection.reset( [], { silent: true });
    2095             selection.validateAll( manager.attachments );
    2096             manager.difference = _.difference( manager.attachments.models, selection.models );
    2097         }
    2098 
    2099         // Sync the selection's single item with the master.
    2100         selection.single( manager.single );
    2101     },
    2102 
    2103     /**
    2104      * Record the currently active attachments, which is a combination
    2105      * of the selection's attachments and the set of selected
    2106      * attachments that this specific selection considered invalid.
    2107      * Reset the difference and record the single attachment.
     3920        view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
     3921
     3922        this.content.set( view );
     3923
     3924        // after bringing in the frame, load the actual editor via an ajax call
     3925        view.loadEditor();
     3926
     3927    },
     3928
     3929    renderImageDetailsToolbar: function() {
     3930        this.toolbar.set( new wp.media.view.Toolbar({
     3931            controller: this,
     3932            items: {
     3933                select: {
     3934                    style:    'primary',
     3935                    text:     l10n.update,
     3936                    priority: 80,
     3937
     3938                    click: function() {
     3939                        var controller = this.controller,
     3940                            state = controller.state();
     3941
     3942                        controller.close();
     3943
     3944                        // not sure if we want to use wp.media.string.image which will create a shortcode or
     3945                        // perhaps wp.html.string to at least to build the <img />
     3946                        state.trigger( 'update', controller.image.toJSON() );
     3947
     3948                        // Restore and reset the default state.
     3949                        controller.setState( controller.options.state );
     3950                        controller.reset();
     3951                    }
     3952                }
     3953            }
     3954        }) );
     3955    },
     3956
     3957    renderReplaceImageToolbar: function() {
     3958        var frame = this,
     3959            lastState = frame.lastState(),
     3960            previous = lastState && lastState.id;
     3961
     3962        this.toolbar.set( new wp.media.view.Toolbar({
     3963            controller: this,
     3964            items: {
     3965                back: {
     3966                    text:     l10n.back,
     3967                    priority: 20,
     3968                    click:    function() {
     3969                        if ( previous ) {
     3970                            frame.setState( previous );
     3971                        } else {
     3972                            frame.close();
     3973                        }
     3974                    }
     3975                },
     3976
     3977                replace: {
     3978                    style:    'primary',
     3979                    text:     l10n.replace,
     3980                    priority: 80,
     3981
     3982                    click: function() {
     3983                        var controller = this.controller,
     3984                            state = controller.state(),
     3985                            selection = state.get( 'selection' ),
     3986                            attachment = selection.single();
     3987
     3988                        controller.close();
     3989
     3990                        controller.image.changeAttachment( attachment, state.display( attachment ) );
     3991
     3992                        // not sure if we want to use wp.media.string.image which will create a shortcode or
     3993                        // perhaps wp.html.string to at least to build the <img />
     3994                        state.trigger( 'replace', controller.image.toJSON() );
     3995
     3996                        // Restore and reset the default state.
     3997                        controller.setState( controller.options.state );
     3998                        controller.reset();
     3999                    }
     4000                }
     4001            }
     4002        }) );
     4003    }
     4004
     4005});
     4006
     4007module.exports = ImageDetails;
     4008
     4009
     4010/***/ }),
     4011/* 51 */
     4012/***/ (function(module, exports) {
     4013
     4014/**
     4015 * wp.media.view.Modal
     4016 *
     4017 * A modal view, which the media modal uses as its default container.
     4018 *
     4019 * @class
     4020 * @augments wp.media.View
     4021 * @augments wp.Backbone.View
     4022 * @augments Backbone.View
     4023 */
     4024var $ = jQuery,
     4025    Modal;
     4026
     4027Modal = wp.media.View.extend({
     4028    tagName:  'div',
     4029    template: wp.template('media-modal'),
     4030
     4031    attributes: {
     4032        tabindex: 0
     4033    },
     4034
     4035    events: {
     4036        'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
     4037        'keydown': 'keydown'
     4038    },
     4039
     4040    initialize: function() {
     4041        _.defaults( this.options, {
     4042            container: document.body,
     4043            title:     '',
     4044            propagate: true,
     4045            freeze:    true
     4046        });
     4047
     4048        this.focusManager = new wp.media.view.FocusManager({
     4049            el: this.el
     4050        });
     4051    },
     4052    /**
     4053     * @returns {Object}
     4054     */
     4055    prepare: function() {
     4056        return {
     4057            title: this.options.title
     4058        };
     4059    },
     4060
     4061    /**
     4062     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4063     */
     4064    attach: function() {
     4065        if ( this.views.attached ) {
     4066            return this;
     4067        }
     4068
     4069        if ( ! this.views.rendered ) {
     4070            this.render();
     4071        }
     4072
     4073        this.$el.appendTo( this.options.container );
     4074
     4075        // Manually mark the view as attached and trigger ready.
     4076        this.views.attached = true;
     4077        this.views.ready();
     4078
     4079        return this.propagate('attach');
     4080    },
     4081
     4082    /**
     4083     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4084     */
     4085    detach: function() {
     4086        if ( this.$el.is(':visible') ) {
     4087            this.close();
     4088        }
     4089
     4090        this.$el.detach();
     4091        this.views.attached = false;
     4092        return this.propagate('detach');
     4093    },
     4094
     4095    /**
     4096     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4097     */
     4098    open: function() {
     4099        var $el = this.$el,
     4100            options = this.options,
     4101            mceEditor;
     4102
     4103        if ( $el.is(':visible') ) {
     4104            return this;
     4105        }
     4106
     4107        if ( ! this.views.attached ) {
     4108            this.attach();
     4109        }
     4110
     4111        // If the `freeze` option is set, record the window's scroll position.
     4112        if ( options.freeze ) {
     4113            this._freeze = {
     4114                scrollTop: $( window ).scrollTop()
     4115            };
     4116        }
     4117
     4118        // Disable page scrolling.
     4119        $( 'body' ).addClass( 'modal-open' );
     4120
     4121        $el.show();
     4122
     4123        // Try to close the onscreen keyboard
     4124        if ( 'ontouchend' in document ) {
     4125            if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
     4126                mceEditor.iframeElement.focus();
     4127                mceEditor.iframeElement.blur();
     4128
     4129                setTimeout( function() {
     4130                    mceEditor.iframeElement.blur();
     4131                }, 100 );
     4132            }
     4133        }
     4134
     4135        this.$el.focus();
     4136
     4137        return this.propagate('open');
     4138    },
     4139
     4140    /**
     4141     * @param {Object} options
     4142     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4143     */
     4144    close: function( options ) {
     4145        var freeze = this._freeze;
     4146
     4147        if ( ! this.views.attached || ! this.$el.is(':visible') ) {
     4148            return this;
     4149        }
     4150
     4151        // Enable page scrolling.
     4152        $( 'body' ).removeClass( 'modal-open' );
     4153
     4154        // Hide modal and remove restricted media modal tab focus once it's closed
     4155        this.$el.hide().undelegate( 'keydown' );
     4156
     4157        // Put focus back in useful location once modal is closed
     4158        $('#wpbody-content').focus();
     4159
     4160        this.propagate('close');
     4161
     4162        // If the `freeze` option is set, restore the container's scroll position.
     4163        if ( freeze ) {
     4164            $( window ).scrollTop( freeze.scrollTop );
     4165        }
     4166
     4167        if ( options && options.escape ) {
     4168            this.propagate('escape');
     4169        }
     4170
     4171        return this;
     4172    },
     4173    /**
     4174     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4175     */
     4176    escape: function() {
     4177        return this.close({ escape: true });
     4178    },
     4179    /**
     4180     * @param {Object} event
     4181     */
     4182    escapeHandler: function( event ) {
     4183        event.preventDefault();
     4184        this.escape();
     4185    },
     4186
     4187    /**
     4188     * @param {Array|Object} content Views to register to '.media-modal-content'
     4189     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4190     */
     4191    content: function( content ) {
     4192        this.views.set( '.media-modal-content', content );
     4193        return this;
     4194    },
     4195
     4196    /**
     4197     * Triggers a modal event and if the `propagate` option is set,
     4198     * forwards events to the modal's controller.
    21084199     *
    2109      * @since 3.5.0
    2110      */
    2111     recordSelection: function() {
    2112         var selection = this.get('selection'),
    2113             manager = this.frame._selection;
    2114 
    2115         if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     4200     * @param {string} id
     4201     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4202     */
     4203    propagate: function( id ) {
     4204        this.trigger( id );
     4205
     4206        if ( this.options.propagate ) {
     4207            this.controller.trigger( id );
     4208        }
     4209
     4210        return this;
     4211    },
     4212    /**
     4213     * @param {Object} event
     4214     */
     4215    keydown: function( event ) {
     4216        // Close the modal when escape is pressed.
     4217        if ( 27 === event.which && this.$el.is(':visible') ) {
     4218            this.escape();
     4219            event.stopImmediatePropagation();
     4220        }
     4221    }
     4222});
     4223
     4224module.exports = Modal;
     4225
     4226
     4227/***/ }),
     4228/* 52 */
     4229/***/ (function(module, exports) {
     4230
     4231/**
     4232 * wp.media.view.FocusManager
     4233 *
     4234 * @class
     4235 * @augments wp.media.View
     4236 * @augments wp.Backbone.View
     4237 * @augments Backbone.View
     4238 */
     4239var FocusManager = wp.media.View.extend({
     4240
     4241    events: {
     4242        'keydown': 'constrainTabbing'
     4243    },
     4244
     4245    focus: function() { // Reset focus on first left menu item
     4246        this.$('.media-menu-item').first().focus();
     4247    },
     4248    /**
     4249     * @param {Object} event
     4250     */
     4251    constrainTabbing: function( event ) {
     4252        var tabbables;
     4253
     4254        // Look for the tab key.
     4255        if ( 9 !== event.keyCode ) {
    21164256            return;
    21174257        }
    21184258
    2119         if ( selection.multiple ) {
    2120             manager.attachments.reset( selection.toArray().concat( manager.difference ) );
    2121             manager.difference = [];
    2122         } else {
    2123             manager.attachments.add( selection.toArray() );
    2124         }
    2125 
    2126         manager.single = selection._single;
     4259        // Skip the file input added by Plupload.
     4260        tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
     4261
     4262        // Keep tab focus within media modal while it's open
     4263        if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
     4264            tabbables.first().focus();
     4265            return false;
     4266        } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
     4267            tabbables.last().focus();
     4268            return false;
     4269        }
    21274270    }
    2128 };
    2129 
    2130 module.exports = selectionSync;
    2131 
    2132 },{}],19:[function(require,module,exports){
    2133 var media = wp.media,
    2134     $ = jQuery,
    2135     l10n;
    2136 
    2137 media.isTouchDevice = ( 'ontouchend' in document );
    2138 
    2139 // Link any localized strings.
    2140 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
    2141 
    2142 // Link any settings.
    2143 media.view.settings = l10n.settings || {};
    2144 delete l10n.settings;
    2145 
    2146 // Copy the `post` setting over to the model settings.
    2147 media.model.settings.post = media.view.settings.post;
    2148 
    2149 // Check if the browser supports CSS 3.0 transitions
    2150 $.support.transition = (function(){
    2151     var style = document.documentElement.style,
    2152         transitions = {
    2153             WebkitTransition: 'webkitTransitionEnd',
    2154             MozTransition:    'transitionend',
    2155             OTransition:      'oTransitionEnd otransitionend',
    2156             transition:       'transitionend'
    2157         }, transition;
    2158 
    2159     transition = _.find( _.keys( transitions ), function( transition ) {
    2160         return ! _.isUndefined( style[ transition ] );
    2161     });
    2162 
    2163     return transition && {
    2164         end: transitions[ transition ]
    2165     };
    2166 }());
     4271
     4272});
     4273
     4274module.exports = FocusManager;
     4275
     4276
     4277/***/ }),
     4278/* 53 */
     4279/***/ (function(module, exports) {
    21674280
    21684281/**
    2169  * A shared event bus used to provide events into
    2170  * the media workflows that 3rd-party devs can use to hook
    2171  * in.
     4282 * wp.media.view.UploaderWindow
     4283 *
     4284 * An uploader window that allows for dragging and dropping media.
     4285 *
     4286 * @class
     4287 * @augments wp.media.View
     4288 * @augments wp.Backbone.View
     4289 * @augments Backbone.View
     4290 *
     4291 * @param {object} [options]                   Options hash passed to the view.
     4292 * @param {object} [options.uploader]          Uploader properties.
     4293 * @param {jQuery} [options.uploader.browser]
     4294 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
     4295 * @param {object} [options.uploader.params]
    21724296 */
    2173 media.events = _.extend( {}, Backbone.Events );
     4297var $ = jQuery,
     4298    UploaderWindow;
     4299
     4300UploaderWindow = wp.media.View.extend({
     4301    tagName:   'div',
     4302    className: 'uploader-window',
     4303    template:  wp.template('uploader-window'),
     4304
     4305    initialize: function() {
     4306        var uploader;
     4307
     4308        this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');
     4309
     4310        uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
     4311            dropzone:  this.$el,
     4312            browser:   this.$browser,
     4313            params:    {}
     4314        });
     4315
     4316        // Ensure the dropzone is a jQuery collection.
     4317        if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
     4318            uploader.dropzone = $( uploader.dropzone );
     4319        }
     4320
     4321        this.controller.on( 'activate', this.refresh, this );
     4322
     4323        this.controller.on( 'detach', function() {
     4324            this.$browser.remove();
     4325        }, this );
     4326    },
     4327
     4328    refresh: function() {
     4329        if ( this.uploader ) {
     4330            this.uploader.refresh();
     4331        }
     4332    },
     4333
     4334    ready: function() {
     4335        var postId = wp.media.view.settings.post.id,
     4336            dropzone;
     4337
     4338        // If the uploader already exists, bail.
     4339        if ( this.uploader ) {
     4340            return;
     4341        }
     4342
     4343        if ( postId ) {
     4344            this.options.uploader.params.post_id = postId;
     4345        }
     4346        this.uploader = new wp.Uploader( this.options.uploader );
     4347
     4348        dropzone = this.uploader.dropzone;
     4349        dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
     4350        dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
     4351
     4352        $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
     4353    },
     4354
     4355    _ready: function() {
     4356        this.controller.trigger( 'uploader:ready' );
     4357    },
     4358
     4359    show: function() {
     4360        var $el = this.$el.show();
     4361
     4362        // Ensure that the animation is triggered by waiting until
     4363        // the transparent element is painted into the DOM.
     4364        _.defer( function() {
     4365            $el.css({ opacity: 1 });
     4366        });
     4367    },
     4368
     4369    hide: function() {
     4370        var $el = this.$el.css({ opacity: 0 });
     4371
     4372        wp.media.transition( $el ).done( function() {
     4373            // Transition end events are subject to race conditions.
     4374            // Make sure that the value is set as intended.
     4375            if ( '0' === $el.css('opacity') ) {
     4376                $el.hide();
     4377            }
     4378        });
     4379
     4380        // https://core.trac.wordpress.org/ticket/27341
     4381        _.delay( function() {
     4382            if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
     4383                $el.hide();
     4384            }
     4385        }, 500 );
     4386    }
     4387});
     4388
     4389module.exports = UploaderWindow;
     4390
     4391
     4392/***/ }),
     4393/* 54 */
     4394/***/ (function(module, exports) {
    21744395
    21754396/**
    2176  * Makes it easier to bind events using transitions.
    2177  *
    2178  * @param {string} selector
    2179  * @param {Number} sensitivity
    2180  * @returns {Promise}
    2181  */
    2182 media.transition = function( selector, sensitivity ) {
    2183     var deferred = $.Deferred();
    2184 
    2185     sensitivity = sensitivity || 2000;
    2186 
    2187     if ( $.support.transition ) {
    2188         if ( ! (selector instanceof $) ) {
    2189             selector = $( selector );
    2190         }
    2191 
    2192         // Resolve the deferred when the first element finishes animating.
    2193         selector.first().one( $.support.transition.end, deferred.resolve );
    2194 
    2195         // Just in case the event doesn't trigger, fire a callback.
    2196         _.delay( deferred.resolve, sensitivity );
    2197 
    2198     // Otherwise, execute on the spot.
    2199     } else {
    2200         deferred.resolve();
    2201     }
    2202 
    2203     return deferred.promise();
    2204 };
    2205 
    2206 media.controller.Region = require( './controllers/region.js' );
    2207 media.controller.StateMachine = require( './controllers/state-machine.js' );
    2208 media.controller.State = require( './controllers/state.js' );
    2209 
    2210 media.selectionSync = require( './utils/selection-sync.js' );
    2211 media.controller.Library = require( './controllers/library.js' );
    2212 media.controller.ImageDetails = require( './controllers/image-details.js' );
    2213 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
    2214 media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
    2215 media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
    2216 media.controller.CollectionAdd = require( './controllers/collection-add.js' );
    2217 media.controller.FeaturedImage = require( './controllers/featured-image.js' );
    2218 media.controller.ReplaceImage = require( './controllers/replace-image.js' );
    2219 media.controller.EditImage = require( './controllers/edit-image.js' );
    2220 media.controller.MediaLibrary = require( './controllers/media-library.js' );
    2221 media.controller.Embed = require( './controllers/embed.js' );
    2222 media.controller.Cropper = require( './controllers/cropper.js' );
    2223 media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' );
    2224 media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' );
    2225 
    2226 media.View = require( './views/view.js' );
    2227 media.view.Frame = require( './views/frame.js' );
    2228 media.view.MediaFrame = require( './views/media-frame.js' );
    2229 media.view.MediaFrame.Select = require( './views/frame/select.js' );
    2230 media.view.MediaFrame.Post = require( './views/frame/post.js' );
    2231 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
    2232 media.view.Modal = require( './views/modal.js' );
    2233 media.view.FocusManager = require( './views/focus-manager.js' );
    2234 media.view.UploaderWindow = require( './views/uploader/window.js' );
    2235 media.view.EditorUploader = require( './views/uploader/editor.js' );
    2236 media.view.UploaderInline = require( './views/uploader/inline.js' );
    2237 media.view.UploaderStatus = require( './views/uploader/status.js' );
    2238 media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
    2239 media.view.Toolbar = require( './views/toolbar.js' );
    2240 media.view.Toolbar.Select = require( './views/toolbar/select.js' );
    2241 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
    2242 media.view.Button = require( './views/button.js' );
    2243 media.view.ButtonGroup = require( './views/button-group.js' );
    2244 media.view.PriorityList = require( './views/priority-list.js' );
    2245 media.view.MenuItem = require( './views/menu-item.js' );
    2246 media.view.Menu = require( './views/menu.js' );
    2247 media.view.RouterItem = require( './views/router-item.js' );
    2248 media.view.Router = require( './views/router.js' );
    2249 media.view.Sidebar = require( './views/sidebar.js' );
    2250 media.view.Attachment = require( './views/attachment.js' );
    2251 media.view.Attachment.Library = require( './views/attachment/library.js' );
    2252 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
    2253 media.view.Attachments = require( './views/attachments.js' );
    2254 media.view.Search = require( './views/search.js' );
    2255 media.view.AttachmentFilters = require( './views/attachment-filters.js' );
    2256 media.view.DateFilter = require( './views/attachment-filters/date.js' );
    2257 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
    2258 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
    2259 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
    2260 media.view.Selection = require( './views/selection.js' );
    2261 media.view.Attachment.Selection = require( './views/attachment/selection.js' );
    2262 media.view.Attachments.Selection = require( './views/attachments/selection.js' );
    2263 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
    2264 media.view.Settings = require( './views/settings.js' );
    2265 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
    2266 media.view.Settings.Gallery = require( './views/settings/gallery.js' );
    2267 media.view.Settings.Playlist = require( './views/settings/playlist.js' );
    2268 media.view.Attachment.Details = require( './views/attachment/details.js' );
    2269 media.view.AttachmentCompat = require( './views/attachment-compat.js' );
    2270 media.view.Iframe = require( './views/iframe.js' );
    2271 media.view.Embed = require( './views/embed.js' );
    2272 media.view.Label = require( './views/label.js' );
    2273 media.view.EmbedUrl = require( './views/embed/url.js' );
    2274 media.view.EmbedLink = require( './views/embed/link.js' );
    2275 media.view.EmbedImage = require( './views/embed/image.js' );
    2276 media.view.ImageDetails = require( './views/image-details.js' );
    2277 media.view.Cropper = require( './views/cropper.js' );
    2278 media.view.SiteIconCropper = require( './views/site-icon-cropper.js' );
    2279 media.view.SiteIconPreview = require( './views/site-icon-preview.js' );
    2280 media.view.EditImage = require( './views/edit-image.js' );
    2281 media.view.Spinner = require( './views/spinner.js' );
    2282 
    2283 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/customize-image-cropper.js":4,"./controllers/edit-image.js":5,"./controllers/embed.js":6,"./controllers/featured-image.js":7,"./controllers/gallery-add.js":8,"./controllers/gallery-edit.js":9,"./controllers/image-details.js":10,"./controllers/library.js":11,"./controllers/media-library.js":12,"./controllers/region.js":13,"./controllers/replace-image.js":14,"./controllers/site-icon-cropper.js":15,"./controllers/state-machine.js":16,"./controllers/state.js":17,"./utils/selection-sync.js":18,"./views/attachment-compat.js":20,"./views/attachment-filters.js":21,"./views/attachment-filters/all.js":22,"./views/attachment-filters/date.js":23,"./views/attachment-filters/uploaded.js":24,"./views/attachment.js":25,"./views/attachment/details.js":26,"./views/attachment/edit-library.js":27,"./views/attachment/edit-selection.js":28,"./views/attachment/library.js":29,"./views/attachment/selection.js":30,"./views/attachments.js":31,"./views/attachments/browser.js":32,"./views/attachments/selection.js":33,"./views/button-group.js":34,"./views/button.js":35,"./views/cropper.js":36,"./views/edit-image.js":37,"./views/embed.js":38,"./views/embed/image.js":39,"./views/embed/link.js":40,"./views/embed/url.js":41,"./views/focus-manager.js":42,"./views/frame.js":43,"./views/frame/image-details.js":44,"./views/frame/post.js":45,"./views/frame/select.js":46,"./views/iframe.js":47,"./views/image-details.js":48,"./views/label.js":49,"./views/media-frame.js":50,"./views/menu-item.js":51,"./views/menu.js":52,"./views/modal.js":53,"./views/priority-list.js":54,"./views/router-item.js":55,"./views/router.js":56,"./views/search.js":57,"./views/selection.js":58,"./views/settings.js":59,"./views/settings/attachment-display.js":60,"./views/settings/gallery.js":61,"./views/settings/playlist.js":62,"./views/sidebar.js":63,"./views/site-icon-cropper.js":64,"./views/site-icon-preview.js":65,"./views/spinner.js":66,"./views/toolbar.js":67,"./views/toolbar/embed.js":68,"./views/toolbar/select.js":69,"./views/uploader/editor.js":70,"./views/uploader/inline.js":71,"./views/uploader/status-error.js":72,"./views/uploader/status.js":73,"./views/uploader/window.js":74,"./views/view.js":75}],20:[function(require,module,exports){
    2284 /**
    2285  * wp.media.view.AttachmentCompat
    2286  *
    2287  * A view to display fields added via the `attachment_fields_to_edit` filter.
     4397 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
     4398 * and relays drag'n'dropped files to a media workflow.
     4399 *
     4400 * wp.media.view.EditorUploader
    22884401 *
    22894402 * @class
     
    22934406 */
    22944407var View = wp.media.View,
    2295     AttachmentCompat;
    2296 
    2297 AttachmentCompat = View.extend({
    2298     tagName:   'form',
    2299     className: 'compat-item',
     4408    l10n = wp.media.view.l10n,
     4409    $ = jQuery,
     4410    EditorUploader;
     4411
     4412EditorUploader = View.extend({
     4413    tagName:   'div',
     4414    className: 'uploader-editor',
     4415    template:  wp.template( 'uploader-editor' ),
     4416
     4417    localDrag: false,
     4418    overContainer: false,
     4419    overDropzone: false,
     4420    draggingFile: null,
     4421
     4422    /**
     4423     * Bind drag'n'drop events to callbacks.
     4424     */
     4425    initialize: function() {
     4426        this.initialized = false;
     4427
     4428        // Bail if not enabled or UA does not support drag'n'drop or File API.
     4429        if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
     4430            return this;
     4431        }
     4432
     4433        this.$document = $(document);
     4434        this.dropzones = [];
     4435        this.files = [];
     4436
     4437        this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
     4438        this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
     4439        this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
     4440        this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
     4441
     4442        this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
     4443        this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
     4444
     4445        this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
     4446            this.localDrag = event.type === 'dragstart';
     4447        }, this ) );
     4448
     4449        this.initialized = true;
     4450        return this;
     4451    },
     4452
     4453    /**
     4454     * Check browser support for drag'n'drop.
     4455     *
     4456     * @return Boolean
     4457     */
     4458    browserSupport: function() {
     4459        var supports = false, div = document.createElement('div');
     4460
     4461        supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
     4462        supports = supports && !! ( window.File && window.FileList && window.FileReader );
     4463        return supports;
     4464    },
     4465
     4466    isDraggingFile: function( event ) {
     4467        if ( this.draggingFile !== null ) {
     4468            return this.draggingFile;
     4469        }
     4470
     4471        if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
     4472            return false;
     4473        }
     4474
     4475        this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
     4476            _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
     4477
     4478        return this.draggingFile;
     4479    },
     4480
     4481    refresh: function( e ) {
     4482        var dropzone_id;
     4483        for ( dropzone_id in this.dropzones ) {
     4484            // Hide the dropzones only if dragging has left the screen.
     4485            this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
     4486        }
     4487
     4488        if ( ! _.isUndefined( e ) ) {
     4489            $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
     4490        }
     4491
     4492        if ( ! this.overContainer && ! this.overDropzone ) {
     4493            this.draggingFile = null;
     4494        }
     4495
     4496        return this;
     4497    },
     4498
     4499    render: function() {
     4500        if ( ! this.initialized ) {
     4501            return this;
     4502        }
     4503
     4504        View.prototype.render.apply( this, arguments );
     4505        $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
     4506        return this;
     4507    },
     4508
     4509    attach: function( index, editor ) {
     4510        // Attach a dropzone to an editor.
     4511        var dropzone = this.$el.clone();
     4512        this.dropzones.push( dropzone );
     4513        $( editor ).append( dropzone );
     4514        return this;
     4515    },
     4516
     4517    /**
     4518     * When a file is dropped on the editor uploader, open up an editor media workflow
     4519     * and upload the file immediately.
     4520     *
     4521     * @param  {jQuery.Event} event The 'drop' event.
     4522     */
     4523    drop: function( event ) {
     4524        var $wrap, uploadView;
     4525
     4526        this.containerDragleave( event );
     4527        this.dropzoneDragleave( event );
     4528
     4529        this.files = event.originalEvent.dataTransfer.files;
     4530        if ( this.files.length < 1 ) {
     4531            return;
     4532        }
     4533
     4534        // Set the active editor to the drop target.
     4535        $wrap = $( event.target ).parents( '.wp-editor-wrap' );
     4536        if ( $wrap.length > 0 && $wrap[0].id ) {
     4537            window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
     4538        }
     4539
     4540        if ( ! this.workflow ) {
     4541            this.workflow = wp.media.editor.open( window.wpActiveEditor, {
     4542                frame:    'post',
     4543                state:    'insert',
     4544                title:    l10n.addMedia,
     4545                multiple: true
     4546            });
     4547
     4548            uploadView = this.workflow.uploader;
     4549
     4550            if ( uploadView.uploader && uploadView.uploader.ready ) {
     4551                this.addFiles.apply( this );
     4552            } else {
     4553                this.workflow.on( 'uploader:ready', this.addFiles, this );
     4554            }
     4555        } else {
     4556            this.workflow.state().reset();
     4557            this.addFiles.apply( this );
     4558            this.workflow.open();
     4559        }
     4560
     4561        return false;
     4562    },
     4563
     4564    /**
     4565     * Add the files to the uploader.
     4566     */
     4567    addFiles: function() {
     4568        if ( this.files.length ) {
     4569            this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
     4570            this.files = [];
     4571        }
     4572        return this;
     4573    },
     4574
     4575    containerDragover: function( event ) {
     4576        if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     4577            return;
     4578        }
     4579
     4580        this.overContainer = true;
     4581        this.refresh();
     4582    },
     4583
     4584    containerDragleave: function() {
     4585        this.overContainer = false;
     4586
     4587        // Throttle dragleave because it's called when bouncing from some elements to others.
     4588        _.delay( _.bind( this.refresh, this ), 50 );
     4589    },
     4590
     4591    dropzoneDragover: function( event ) {
     4592        if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     4593            return;
     4594        }
     4595
     4596        this.overDropzone = true;
     4597        this.refresh( event );
     4598        return false;
     4599    },
     4600
     4601    dropzoneDragleave: function( e ) {
     4602        this.overDropzone = false;
     4603        _.delay( _.bind( this.refresh, this, e ), 50 );
     4604    },
     4605
     4606    click: function( e ) {
     4607        // In the rare case where the dropzone gets stuck, hide it on click.
     4608        this.containerDragleave( e );
     4609        this.dropzoneDragleave( e );
     4610        this.localDrag = false;
     4611    }
     4612});
     4613
     4614module.exports = EditorUploader;
     4615
     4616
     4617/***/ }),
     4618/* 55 */
     4619/***/ (function(module, exports) {
     4620
     4621/**
     4622 * wp.media.view.UploaderInline
     4623 *
     4624 * The inline uploader that shows up in the 'Upload Files' tab.
     4625 *
     4626 * @class
     4627 * @augments wp.media.View
     4628 * @augments wp.Backbone.View
     4629 * @augments Backbone.View
     4630 */
     4631var View = wp.media.View,
     4632    UploaderInline;
     4633
     4634UploaderInline = View.extend({
     4635    tagName:   'div',
     4636    className: 'uploader-inline',
     4637    template:  wp.template('uploader-inline'),
    23004638
    23014639    events: {
    2302         'submit':          'preventDefault',
    2303         'change input':    'save',
    2304         'change select':   'save',
    2305         'change textarea': 'save'
     4640        'click .close': 'hide'
    23064641    },
    23074642
    23084643    initialize: function() {
    2309         this.listenTo( this.model, 'change:compat', this.render );
    2310     },
    2311     /**
    2312      * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     4644        _.defaults( this.options, {
     4645            message: '',
     4646            status:  true,
     4647            canClose: false
     4648        });
     4649
     4650        if ( ! this.options.$browser && this.controller.uploader ) {
     4651            this.options.$browser = this.controller.uploader.$browser;
     4652        }
     4653
     4654        if ( _.isUndefined( this.options.postId ) ) {
     4655            this.options.postId = wp.media.view.settings.post.id;
     4656        }
     4657
     4658        if ( this.options.status ) {
     4659            this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
     4660                controller: this.controller
     4661            }) );
     4662        }
     4663    },
     4664
     4665    prepare: function() {
     4666        var suggestedWidth = this.controller.state().get('suggestedWidth'),
     4667            suggestedHeight = this.controller.state().get('suggestedHeight'),
     4668            data = {};
     4669
     4670        data.message = this.options.message;
     4671        data.canClose = this.options.canClose;
     4672
     4673        if ( suggestedWidth && suggestedHeight ) {
     4674            data.suggestedWidth = suggestedWidth;
     4675            data.suggestedHeight = suggestedHeight;
     4676        }
     4677
     4678        return data;
     4679    },
     4680    /**
     4681     * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    23134682     */
    23144683    dispose: function() {
    2315         if ( this.$(':focus').length ) {
    2316             this.save();
     4684        if ( this.disposing ) {
     4685            /**
     4686             * call 'dispose' directly on the parent class
     4687             */
     4688            return View.prototype.dispose.apply( this, arguments );
     4689        }
     4690
     4691        // Run remove on `dispose`, so we can be sure to refresh the
     4692        // uploader with a view-less DOM. Track whether we're disposing
     4693        // so we don't trigger an infinite loop.
     4694        this.disposing = true;
     4695        return this.remove();
     4696    },
     4697    /**
     4698     * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
     4699     */
     4700    remove: function() {
     4701        /**
     4702         * call 'remove' directly on the parent class
     4703         */
     4704        var result = View.prototype.remove.apply( this, arguments );
     4705
     4706        _.defer( _.bind( this.refresh, this ) );
     4707        return result;
     4708    },
     4709
     4710    refresh: function() {
     4711        var uploader = this.controller.uploader;
     4712
     4713        if ( uploader ) {
     4714            uploader.refresh();
     4715        }
     4716    },
     4717    /**
     4718     * @returns {wp.media.view.UploaderInline}
     4719     */
     4720    ready: function() {
     4721        var $browser = this.options.$browser,
     4722            $placeholder;
     4723
     4724        if ( this.controller.uploader ) {
     4725            $placeholder = this.$('.browser');
     4726
     4727            // Check if we've already replaced the placeholder.
     4728            if ( $placeholder[0] === $browser[0] ) {
     4729                return;
     4730            }
     4731
     4732            $browser.detach().text( $placeholder.text() );
     4733            $browser[0].className = $placeholder[0].className;
     4734            $placeholder.replaceWith( $browser.show() );
     4735        }
     4736
     4737        this.refresh();
     4738        return this;
     4739    },
     4740    show: function() {
     4741        this.$el.removeClass( 'hidden' );
     4742    },
     4743    hide: function() {
     4744        this.$el.addClass( 'hidden' );
     4745    }
     4746
     4747});
     4748
     4749module.exports = UploaderInline;
     4750
     4751
     4752/***/ }),
     4753/* 56 */
     4754/***/ (function(module, exports) {
     4755
     4756/**
     4757 * wp.media.view.UploaderStatus
     4758 *
     4759 * An uploader status for on-going uploads.
     4760 *
     4761 * @class
     4762 * @augments wp.media.View
     4763 * @augments wp.Backbone.View
     4764 * @augments Backbone.View
     4765 */
     4766var View = wp.media.View,
     4767    UploaderStatus;
     4768
     4769UploaderStatus = View.extend({
     4770    className: 'media-uploader-status',
     4771    template:  wp.template('uploader-status'),
     4772
     4773    events: {
     4774        'click .upload-dismiss-errors': 'dismiss'
     4775    },
     4776
     4777    initialize: function() {
     4778        this.queue = wp.Uploader.queue;
     4779        this.queue.on( 'add remove reset', this.visibility, this );
     4780        this.queue.on( 'add remove reset change:percent', this.progress, this );
     4781        this.queue.on( 'add remove reset change:uploading', this.info, this );
     4782
     4783        this.errors = wp.Uploader.errors;
     4784        this.errors.reset();
     4785        this.errors.on( 'add remove reset', this.visibility, this );
     4786        this.errors.on( 'add', this.error, this );
     4787    },
     4788    /**
     4789     * @global wp.Uploader
     4790     * @returns {wp.media.view.UploaderStatus}
     4791     */
     4792    dispose: function() {
     4793        wp.Uploader.queue.off( null, null, this );
     4794        /**
     4795         * call 'dispose' directly on the parent class
     4796         */
     4797        View.prototype.dispose.apply( this, arguments );
     4798        return this;
     4799    },
     4800
     4801    visibility: function() {
     4802        this.$el.toggleClass( 'uploading', !! this.queue.length );
     4803        this.$el.toggleClass( 'errors', !! this.errors.length );
     4804        this.$el.toggle( !! this.queue.length || !! this.errors.length );
     4805    },
     4806
     4807    ready: function() {
     4808        _.each({
     4809            '$bar':      '.media-progress-bar div',
     4810            '$index':    '.upload-index',
     4811            '$total':    '.upload-total',
     4812            '$filename': '.upload-filename'
     4813        }, function( selector, key ) {
     4814            this[ key ] = this.$( selector );
     4815        }, this );
     4816
     4817        this.visibility();
     4818        this.progress();
     4819        this.info();
     4820    },
     4821
     4822    progress: function() {
     4823        var queue = this.queue,
     4824            $bar = this.$bar;
     4825
     4826        if ( ! $bar || ! queue.length ) {
     4827            return;
     4828        }
     4829
     4830        $bar.width( ( queue.reduce( function( memo, attachment ) {
     4831            if ( ! attachment.get('uploading') ) {
     4832                return memo + 100;
     4833            }
     4834
     4835            var percent = attachment.get('percent');
     4836            return memo + ( _.isNumber( percent ) ? percent : 100 );
     4837        }, 0 ) / queue.length ) + '%' );
     4838    },
     4839
     4840    info: function() {
     4841        var queue = this.queue,
     4842            index = 0, active;
     4843
     4844        if ( ! queue.length ) {
     4845            return;
     4846        }
     4847
     4848        active = this.queue.find( function( attachment, i ) {
     4849            index = i;
     4850            return attachment.get('uploading');
     4851        });
     4852
     4853        this.$index.text( index + 1 );
     4854        this.$total.text( queue.length );
     4855        this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
     4856    },
     4857    /**
     4858     * @param {string} filename
     4859     * @returns {string}
     4860     */
     4861    filename: function( filename ) {
     4862        return _.escape( filename );
     4863    },
     4864    /**
     4865     * @param {Backbone.Model} error
     4866     */
     4867    error: function( error ) {
     4868        this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     4869            filename: this.filename( error.get('file').name ),
     4870            message:  error.get('message')
     4871        }), { at: 0 });
     4872    },
     4873
     4874    /**
     4875     * @global wp.Uploader
     4876     *
     4877     * @param {Object} event
     4878     */
     4879    dismiss: function( event ) {
     4880        var errors = this.views.get('.upload-errors');
     4881
     4882        event.preventDefault();
     4883
     4884        if ( errors ) {
     4885            _.invoke( errors, 'remove' );
     4886        }
     4887        wp.Uploader.errors.reset();
     4888    }
     4889});
     4890
     4891module.exports = UploaderStatus;
     4892
     4893
     4894/***/ }),
     4895/* 57 */
     4896/***/ (function(module, exports) {
     4897
     4898/**
     4899 * wp.media.view.UploaderStatusError
     4900 *
     4901 * @class
     4902 * @augments wp.media.View
     4903 * @augments wp.Backbone.View
     4904 * @augments Backbone.View
     4905 */
     4906var UploaderStatusError = wp.media.View.extend({
     4907    className: 'upload-error',
     4908    template:  wp.template('uploader-status-error')
     4909});
     4910
     4911module.exports = UploaderStatusError;
     4912
     4913
     4914/***/ }),
     4915/* 58 */
     4916/***/ (function(module, exports) {
     4917
     4918/**
     4919 * wp.media.view.Toolbar
     4920 *
     4921 * A toolbar which consists of a primary and a secondary section. Each sections
     4922 * can be filled with views.
     4923 *
     4924 * @class
     4925 * @augments wp.media.View
     4926 * @augments wp.Backbone.View
     4927 * @augments Backbone.View
     4928 */
     4929var View = wp.media.View,
     4930    Toolbar;
     4931
     4932Toolbar = View.extend({
     4933    tagName:   'div',
     4934    className: 'media-toolbar',
     4935
     4936    initialize: function() {
     4937        var state = this.controller.state(),
     4938            selection = this.selection = state.get('selection'),
     4939            library = this.library = state.get('library');
     4940
     4941        this._views = {};
     4942
     4943        // The toolbar is composed of two `PriorityList` views.
     4944        this.primary   = new wp.media.view.PriorityList();
     4945        this.secondary = new wp.media.view.PriorityList();
     4946        this.primary.$el.addClass('media-toolbar-primary search-form');
     4947        this.secondary.$el.addClass('media-toolbar-secondary');
     4948
     4949        this.views.set([ this.secondary, this.primary ]);
     4950
     4951        if ( this.options.items ) {
     4952            this.set( this.options.items, { silent: true });
     4953        }
     4954
     4955        if ( ! this.options.silent ) {
     4956            this.render();
     4957        }
     4958
     4959        if ( selection ) {
     4960            selection.on( 'add remove reset', this.refresh, this );
     4961        }
     4962
     4963        if ( library ) {
     4964            library.on( 'add remove reset', this.refresh, this );
     4965        }
     4966    },
     4967    /**
     4968     * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
     4969     */
     4970    dispose: function() {
     4971        if ( this.selection ) {
     4972            this.selection.off( null, null, this );
     4973        }
     4974
     4975        if ( this.library ) {
     4976            this.library.off( null, null, this );
    23174977        }
    23184978        /**
     
    23214981        return View.prototype.dispose.apply( this, arguments );
    23224982    },
    2323     /**
    2324      * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     4983
     4984    ready: function() {
     4985        this.refresh();
     4986    },
     4987
     4988    /**
     4989     * @param {string} id
     4990     * @param {Backbone.View|Object} view
     4991     * @param {Object} [options={}]
     4992     * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     4993     */
     4994    set: function( id, view, options ) {
     4995        var list;
     4996        options = options || {};
     4997
     4998        // Accept an object with an `id` : `view` mapping.
     4999        if ( _.isObject( id ) ) {
     5000            _.each( id, function( view, id ) {
     5001                this.set( id, view, { silent: true });
     5002            }, this );
     5003
     5004        } else {
     5005            if ( ! ( view instanceof Backbone.View ) ) {
     5006                view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
     5007                view = new wp.media.view.Button( view ).render();
     5008            }
     5009
     5010            view.controller = view.controller || this.controller;
     5011
     5012            this._views[ id ] = view;
     5013
     5014            list = view.options.priority < 0 ? 'secondary' : 'primary';
     5015            this[ list ].set( id, view, options );
     5016        }
     5017
     5018        if ( ! options.silent ) {
     5019            this.refresh();
     5020        }
     5021
     5022        return this;
     5023    },
     5024    /**
     5025     * @param {string} id
     5026     * @returns {wp.media.view.Button}
     5027     */
     5028    get: function( id ) {
     5029        return this._views[ id ];
     5030    },
     5031    /**
     5032     * @param {string} id
     5033     * @param {Object} options
     5034     * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     5035     */
     5036    unset: function( id, options ) {
     5037        delete this._views[ id ];
     5038        this.primary.unset( id, options );
     5039        this.secondary.unset( id, options );
     5040
     5041        if ( ! options || ! options.silent ) {
     5042            this.refresh();
     5043        }
     5044        return this;
     5045    },
     5046
     5047    refresh: function() {
     5048        var state = this.controller.state(),
     5049            library = state.get('library'),
     5050            selection = state.get('selection');
     5051
     5052        _.each( this._views, function( button ) {
     5053            if ( ! button.model || ! button.options || ! button.options.requires ) {
     5054                return;
     5055            }
     5056
     5057            var requires = button.options.requires,
     5058                disabled = false;
     5059
     5060            // Prevent insertion of attachments if any of them are still uploading
     5061            disabled = _.some( selection.models, function( attachment ) {
     5062                return attachment.get('uploading') === true;
     5063            });
     5064
     5065            if ( requires.selection && selection && ! selection.length ) {
     5066                disabled = true;
     5067            } else if ( requires.library && library && ! library.length ) {
     5068                disabled = true;
     5069            }
     5070            button.model.set( 'disabled', disabled );
     5071        });
     5072    }
     5073});
     5074
     5075module.exports = Toolbar;
     5076
     5077
     5078/***/ }),
     5079/* 59 */
     5080/***/ (function(module, exports) {
     5081
     5082/**
     5083 * wp.media.view.Toolbar.Select
     5084 *
     5085 * @class
     5086 * @augments wp.media.view.Toolbar
     5087 * @augments wp.media.View
     5088 * @augments wp.Backbone.View
     5089 * @augments Backbone.View
     5090 */
     5091var Toolbar = wp.media.view.Toolbar,
     5092    l10n = wp.media.view.l10n,
     5093    Select;
     5094
     5095Select = Toolbar.extend({
     5096    initialize: function() {
     5097        var options = this.options;
     5098
     5099        _.bindAll( this, 'clickSelect' );
     5100
     5101        _.defaults( options, {
     5102            event: 'select',
     5103            state: false,
     5104            reset: true,
     5105            close: true,
     5106            text:  l10n.select,
     5107
     5108            // Does the button rely on the selection?
     5109            requires: {
     5110                selection: true
     5111            }
     5112        });
     5113
     5114        options.items = _.defaults( options.items || {}, {
     5115            select: {
     5116                style:    'primary',
     5117                text:     options.text,
     5118                priority: 80,
     5119                click:    this.clickSelect,
     5120                requires: options.requires
     5121            }
     5122        });
     5123        // Call 'initialize' directly on the parent class.
     5124        Toolbar.prototype.initialize.apply( this, arguments );
     5125    },
     5126
     5127    clickSelect: function() {
     5128        var options = this.options,
     5129            controller = this.controller;
     5130
     5131        if ( options.close ) {
     5132            controller.close();
     5133        }
     5134
     5135        if ( options.event ) {
     5136            controller.state().trigger( options.event );
     5137        }
     5138
     5139        if ( options.state ) {
     5140            controller.setState( options.state );
     5141        }
     5142
     5143        if ( options.reset ) {
     5144            controller.reset();
     5145        }
     5146    }
     5147});
     5148
     5149module.exports = Select;
     5150
     5151
     5152/***/ }),
     5153/* 60 */
     5154/***/ (function(module, exports) {
     5155
     5156/**
     5157 * wp.media.view.Toolbar.Embed
     5158 *
     5159 * @class
     5160 * @augments wp.media.view.Toolbar.Select
     5161 * @augments wp.media.view.Toolbar
     5162 * @augments wp.media.View
     5163 * @augments wp.Backbone.View
     5164 * @augments Backbone.View
     5165 */
     5166var Select = wp.media.view.Toolbar.Select,
     5167    l10n = wp.media.view.l10n,
     5168    Embed;
     5169
     5170Embed = Select.extend({
     5171    initialize: function() {
     5172        _.defaults( this.options, {
     5173            text: l10n.insertIntoPost,
     5174            requires: false
     5175        });
     5176        // Call 'initialize' directly on the parent class.
     5177        Select.prototype.initialize.apply( this, arguments );
     5178    },
     5179
     5180    refresh: function() {
     5181        var url = this.controller.state().props.get('url');
     5182        this.get('select').model.set( 'disabled', ! url || url === 'http://' );
     5183        /**
     5184         * call 'refresh' directly on the parent class
     5185         */
     5186        Select.prototype.refresh.apply( this, arguments );
     5187    }
     5188});
     5189
     5190module.exports = Embed;
     5191
     5192
     5193/***/ }),
     5194/* 61 */
     5195/***/ (function(module, exports) {
     5196
     5197/**
     5198 * wp.media.view.Button
     5199 *
     5200 * @class
     5201 * @augments wp.media.View
     5202 * @augments wp.Backbone.View
     5203 * @augments Backbone.View
     5204 */
     5205var Button = wp.media.View.extend({
     5206    tagName:    'button',
     5207    className:  'media-button',
     5208    attributes: { type: 'button' },
     5209
     5210    events: {
     5211        'click': 'click'
     5212    },
     5213
     5214    defaults: {
     5215        text:     '',
     5216        style:    '',
     5217        size:     'large',
     5218        disabled: false
     5219    },
     5220
     5221    initialize: function() {
     5222        /**
     5223         * Create a model with the provided `defaults`.
     5224         *
     5225         * @member {Backbone.Model}
     5226         */
     5227        this.model = new Backbone.Model( this.defaults );
     5228
     5229        // If any of the `options` have a key from `defaults`, apply its
     5230        // value to the `model` and remove it from the `options object.
     5231        _.each( this.defaults, function( def, key ) {
     5232            var value = this.options[ key ];
     5233            if ( _.isUndefined( value ) ) {
     5234                return;
     5235            }
     5236
     5237            this.model.set( key, value );
     5238            delete this.options[ key ];
     5239        }, this );
     5240
     5241        this.listenTo( this.model, 'change', this.render );
     5242    },
     5243    /**
     5244     * @returns {wp.media.view.Button} Returns itself to allow chaining
    23255245     */
    23265246    render: function() {
    2327         var compat = this.model.get('compat');
    2328         if ( ! compat || ! compat.item ) {
    2329             return;
    2330         }
    2331 
    2332         this.views.detach();
    2333         this.$el.html( compat.item );
    2334         this.views.render();
     5247        var classes = [ 'button', this.className ],
     5248            model = this.model.toJSON();
     5249
     5250        if ( model.style ) {
     5251            classes.push( 'button-' + model.style );
     5252        }
     5253
     5254        if ( model.size ) {
     5255            classes.push( 'button-' + model.size );
     5256        }
     5257
     5258        classes = _.uniq( classes.concat( this.options.classes ) );
     5259        this.el.className = classes.join(' ');
     5260
     5261        this.$el.attr( 'disabled', model.disabled );
     5262        this.$el.text( this.model.get('text') );
     5263
    23355264        return this;
    23365265    },
     
    23385267     * @param {Object} event
    23395268     */
    2340     preventDefault: function( event ) {
    2341         event.preventDefault();
     5269    click: function( event ) {
     5270        if ( '#' === this.attributes.href ) {
     5271            event.preventDefault();
     5272        }
     5273
     5274        if ( this.options.click && ! this.model.get('disabled') ) {
     5275            this.options.click.apply( this, arguments );
     5276        }
     5277    }
     5278});
     5279
     5280module.exports = Button;
     5281
     5282
     5283/***/ }),
     5284/* 62 */
     5285/***/ (function(module, exports) {
     5286
     5287/**
     5288 * wp.media.view.ButtonGroup
     5289 *
     5290 * @class
     5291 * @augments wp.media.View
     5292 * @augments wp.Backbone.View
     5293 * @augments Backbone.View
     5294 */
     5295var $ = Backbone.$,
     5296    ButtonGroup;
     5297
     5298ButtonGroup = wp.media.View.extend({
     5299    tagName:   'div',
     5300    className: 'button-group button-large media-button-group',
     5301
     5302    initialize: function() {
     5303        /**
     5304         * @member {wp.media.view.Button[]}
     5305         */
     5306        this.buttons = _.map( this.options.buttons || [], function( button ) {
     5307            if ( button instanceof Backbone.View ) {
     5308                return button;
     5309            } else {
     5310                return new wp.media.view.Button( button ).render();
     5311            }
     5312        });
     5313
     5314        delete this.options.buttons;
     5315
     5316        if ( this.options.classes ) {
     5317            this.$el.addClass( this.options.classes );
     5318        }
     5319    },
     5320
     5321    /**
     5322     * @returns {wp.media.view.ButtonGroup}
     5323     */
     5324    render: function() {
     5325        this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
     5326        return this;
     5327    }
     5328});
     5329
     5330module.exports = ButtonGroup;
     5331
     5332
     5333/***/ }),
     5334/* 63 */
     5335/***/ (function(module, exports) {
     5336
     5337/**
     5338 * wp.media.view.PriorityList
     5339 *
     5340 * @class
     5341 * @augments wp.media.View
     5342 * @augments wp.Backbone.View
     5343 * @augments Backbone.View
     5344 */
     5345var PriorityList = wp.media.View.extend({
     5346    tagName:   'div',
     5347
     5348    initialize: function() {
     5349        this._views = {};
     5350
     5351        this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
     5352        delete this.options.views;
     5353
     5354        if ( ! this.options.silent ) {
     5355            this.render();
     5356        }
     5357    },
     5358    /**
     5359     * @param {string} id
     5360     * @param {wp.media.View|Object} view
     5361     * @param {Object} options
     5362     * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
     5363     */
     5364    set: function( id, view, options ) {
     5365        var priority, views, index;
     5366
     5367        options = options || {};
     5368
     5369        // Accept an object with an `id` : `view` mapping.
     5370        if ( _.isObject( id ) ) {
     5371            _.each( id, function( view, id ) {
     5372                this.set( id, view );
     5373            }, this );
     5374            return this;
     5375        }
     5376
     5377        if ( ! (view instanceof Backbone.View) ) {
     5378            view = this.toView( view, id, options );
     5379        }
     5380        view.controller = view.controller || this.controller;
     5381
     5382        this.unset( id );
     5383
     5384        priority = view.options.priority || 10;
     5385        views = this.views.get() || [];
     5386
     5387        _.find( views, function( existing, i ) {
     5388            if ( existing.options.priority > priority ) {
     5389                index = i;
     5390                return true;
     5391            }
     5392        });
     5393
     5394        this._views[ id ] = view;
     5395        this.views.add( view, {
     5396            at: _.isNumber( index ) ? index : views.length || 0
     5397        });
     5398
     5399        return this;
     5400    },
     5401    /**
     5402     * @param {string} id
     5403     * @returns {wp.media.View}
     5404     */
     5405    get: function( id ) {
     5406        return this._views[ id ];
     5407    },
     5408    /**
     5409     * @param {string} id
     5410     * @returns {wp.media.view.PriorityList}
     5411     */
     5412    unset: function( id ) {
     5413        var view = this.get( id );
     5414
     5415        if ( view ) {
     5416            view.remove();
     5417        }
     5418
     5419        delete this._views[ id ];
     5420        return this;
     5421    },
     5422    /**
     5423     * @param {Object} options
     5424     * @returns {wp.media.View}
     5425     */
     5426    toView: function( options ) {
     5427        return new wp.media.View( options );
     5428    }
     5429});
     5430
     5431module.exports = PriorityList;
     5432
     5433
     5434/***/ }),
     5435/* 64 */
     5436/***/ (function(module, exports) {
     5437
     5438/**
     5439 * wp.media.view.MenuItem
     5440 *
     5441 * @class
     5442 * @augments wp.media.View
     5443 * @augments wp.Backbone.View
     5444 * @augments Backbone.View
     5445 */
     5446var $ = jQuery,
     5447    MenuItem;
     5448
     5449MenuItem = wp.media.View.extend({
     5450    tagName:   'a',
     5451    className: 'media-menu-item',
     5452
     5453    attributes: {
     5454        href: '#'
     5455    },
     5456
     5457    events: {
     5458        'click': '_click'
    23425459    },
    23435460    /**
    23445461     * @param {Object} event
    23455462     */
    2346     save: function( event ) {
    2347         var data = {};
     5463    _click: function( event ) {
     5464        var clickOverride = this.options.click;
    23485465
    23495466        if ( event ) {
     
    23515468        }
    23525469
    2353         _.each( this.$el.serializeArray(), function( pair ) {
    2354             data[ pair.name ] = pair.value;
    2355         });
    2356 
    2357         this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
    2358         this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
    2359     },
    2360 
    2361     postSave: function() {
    2362         this.controller.trigger( 'attachment:compat:ready', ['ready'] );
     5470        if ( clickOverride ) {
     5471            clickOverride.call( this );
     5472        } else {
     5473            this.click();
     5474        }
     5475
     5476        // When selecting a tab along the left side,
     5477        // focus should be transferred into the main panel
     5478        if ( ! wp.media.isTouchDevice ) {
     5479            $('.media-frame-content input').first().focus();
     5480        }
     5481    },
     5482
     5483    click: function() {
     5484        var state = this.options.state;
     5485
     5486        if ( state ) {
     5487            this.controller.setState( state );
     5488            this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
     5489        }
     5490    },
     5491    /**
     5492     * @returns {wp.media.view.MenuItem} returns itself to allow chaining
     5493     */
     5494    render: function() {
     5495        var options = this.options;
     5496
     5497        if ( options.text ) {
     5498            this.$el.text( options.text );
     5499        } else if ( options.html ) {
     5500            this.$el.html( options.html );
     5501        }
     5502
     5503        return this;
    23635504    }
    23645505});
    23655506
    2366 module.exports = AttachmentCompat;
    2367 
    2368 },{}],21:[function(require,module,exports){
     5507module.exports = MenuItem;
     5508
     5509
     5510/***/ }),
     5511/* 65 */
     5512/***/ (function(module, exports) {
     5513
     5514/**
     5515 * wp.media.view.Menu
     5516 *
     5517 * @class
     5518 * @augments wp.media.view.PriorityList
     5519 * @augments wp.media.View
     5520 * @augments wp.Backbone.View
     5521 * @augments Backbone.View
     5522 */
     5523var MenuItem = wp.media.view.MenuItem,
     5524    PriorityList = wp.media.view.PriorityList,
     5525    Menu;
     5526
     5527Menu = PriorityList.extend({
     5528    tagName:   'div',
     5529    className: 'media-menu',
     5530    property:  'state',
     5531    ItemView:  MenuItem,
     5532    region:    'menu',
     5533
     5534    /* TODO: alternatively hide on any click anywhere
     5535    events: {
     5536        'click': 'click'
     5537    },
     5538
     5539    click: function() {
     5540        this.$el.removeClass( 'visible' );
     5541    },
     5542    */
     5543
     5544    /**
     5545     * @param {Object} options
     5546     * @param {string} id
     5547     * @returns {wp.media.View}
     5548     */
     5549    toView: function( options, id ) {
     5550        options = options || {};
     5551        options[ this.property ] = options[ this.property ] || id;
     5552        return new this.ItemView( options ).render();
     5553    },
     5554
     5555    ready: function() {
     5556        /**
     5557         * call 'ready' directly on the parent class
     5558         */
     5559        PriorityList.prototype.ready.apply( this, arguments );
     5560        this.visibility();
     5561    },
     5562
     5563    set: function() {
     5564        /**
     5565         * call 'set' directly on the parent class
     5566         */
     5567        PriorityList.prototype.set.apply( this, arguments );
     5568        this.visibility();
     5569    },
     5570
     5571    unset: function() {
     5572        /**
     5573         * call 'unset' directly on the parent class
     5574         */
     5575        PriorityList.prototype.unset.apply( this, arguments );
     5576        this.visibility();
     5577    },
     5578
     5579    visibility: function() {
     5580        var region = this.region,
     5581            view = this.controller[ region ].get(),
     5582            views = this.views.get(),
     5583            hide = ! views || views.length < 2;
     5584
     5585        if ( this === view ) {
     5586            this.controller.$el.toggleClass( 'hide-' + region, hide );
     5587        }
     5588    },
     5589    /**
     5590     * @param {string} id
     5591     */
     5592    select: function( id ) {
     5593        var view = this.get( id );
     5594
     5595        if ( ! view ) {
     5596            return;
     5597        }
     5598
     5599        this.deselect();
     5600        view.$el.addClass('active');
     5601    },
     5602
     5603    deselect: function() {
     5604        this.$el.children().removeClass('active');
     5605    },
     5606
     5607    hide: function( id ) {
     5608        var view = this.get( id );
     5609
     5610        if ( ! view ) {
     5611            return;
     5612        }
     5613
     5614        view.$el.addClass('hidden');
     5615    },
     5616
     5617    show: function( id ) {
     5618        var view = this.get( id );
     5619
     5620        if ( ! view ) {
     5621            return;
     5622        }
     5623
     5624        view.$el.removeClass('hidden');
     5625    }
     5626});
     5627
     5628module.exports = Menu;
     5629
     5630
     5631/***/ }),
     5632/* 66 */
     5633/***/ (function(module, exports) {
     5634
     5635/**
     5636 * wp.media.view.RouterItem
     5637 *
     5638 * @class
     5639 * @augments wp.media.view.MenuItem
     5640 * @augments wp.media.View
     5641 * @augments wp.Backbone.View
     5642 * @augments Backbone.View
     5643 */
     5644var RouterItem = wp.media.view.MenuItem.extend({
     5645    /**
     5646     * On click handler to activate the content region's corresponding mode.
     5647     */
     5648    click: function() {
     5649        var contentMode = this.options.contentMode;
     5650        if ( contentMode ) {
     5651            this.controller.content.mode( contentMode );
     5652        }
     5653    }
     5654});
     5655
     5656module.exports = RouterItem;
     5657
     5658
     5659/***/ }),
     5660/* 67 */
     5661/***/ (function(module, exports) {
     5662
     5663/**
     5664 * wp.media.view.Router
     5665 *
     5666 * @class
     5667 * @augments wp.media.view.Menu
     5668 * @augments wp.media.view.PriorityList
     5669 * @augments wp.media.View
     5670 * @augments wp.Backbone.View
     5671 * @augments Backbone.View
     5672 */
     5673var Menu = wp.media.view.Menu,
     5674    Router;
     5675
     5676Router = Menu.extend({
     5677    tagName:   'div',
     5678    className: 'media-router',
     5679    property:  'contentMode',
     5680    ItemView:  wp.media.view.RouterItem,
     5681    region:    'router',
     5682
     5683    initialize: function() {
     5684        this.controller.on( 'content:render', this.update, this );
     5685        // Call 'initialize' directly on the parent class.
     5686        Menu.prototype.initialize.apply( this, arguments );
     5687    },
     5688
     5689    update: function() {
     5690        var mode = this.controller.content.mode();
     5691        if ( mode ) {
     5692            this.select( mode );
     5693        }
     5694    }
     5695});
     5696
     5697module.exports = Router;
     5698
     5699
     5700/***/ }),
     5701/* 68 */
     5702/***/ (function(module, exports) {
     5703
     5704/**
     5705 * wp.media.view.Sidebar
     5706 *
     5707 * @class
     5708 * @augments wp.media.view.PriorityList
     5709 * @augments wp.media.View
     5710 * @augments wp.Backbone.View
     5711 * @augments Backbone.View
     5712 */
     5713var Sidebar = wp.media.view.PriorityList.extend({
     5714    className: 'media-sidebar'
     5715});
     5716
     5717module.exports = Sidebar;
     5718
     5719
     5720/***/ }),
     5721/* 69 */
     5722/***/ (function(module, exports) {
     5723
     5724/**
     5725 * wp.media.view.Attachment
     5726 *
     5727 * @class
     5728 * @augments wp.media.View
     5729 * @augments wp.Backbone.View
     5730 * @augments Backbone.View
     5731 */
     5732var View = wp.media.View,
     5733    $ = jQuery,
     5734    Attachment;
     5735
     5736Attachment = View.extend({
     5737    tagName:   'li',
     5738    className: 'attachment',
     5739    template:  wp.template('attachment'),
     5740
     5741    attributes: function() {
     5742        return {
     5743            'tabIndex':     0,
     5744            'role':         'checkbox',
     5745            'aria-label':   this.model.get( 'title' ),
     5746            'aria-checked': false,
     5747            'data-id':      this.model.get( 'id' )
     5748        };
     5749    },
     5750
     5751    events: {
     5752        'click .js--select-attachment':   'toggleSelectionHandler',
     5753        'change [data-setting]':          'updateSetting',
     5754        'change [data-setting] input':    'updateSetting',
     5755        'change [data-setting] select':   'updateSetting',
     5756        'change [data-setting] textarea': 'updateSetting',
     5757        'click .attachment-close':        'removeFromLibrary',
     5758        'click .check':                   'checkClickHandler',
     5759        'keydown':                        'toggleSelectionHandler'
     5760    },
     5761
     5762    buttons: {},
     5763
     5764    initialize: function() {
     5765        var selection = this.options.selection,
     5766            options = _.defaults( this.options, {
     5767                rerenderOnModelChange: true
     5768            } );
     5769
     5770        if ( options.rerenderOnModelChange ) {
     5771            this.listenTo( this.model, 'change', this.render );
     5772        } else {
     5773            this.listenTo( this.model, 'change:percent', this.progress );
     5774        }
     5775        this.listenTo( this.model, 'change:title', this._syncTitle );
     5776        this.listenTo( this.model, 'change:caption', this._syncCaption );
     5777        this.listenTo( this.model, 'change:artist', this._syncArtist );
     5778        this.listenTo( this.model, 'change:album', this._syncAlbum );
     5779
     5780        // Update the selection.
     5781        this.listenTo( this.model, 'add', this.select );
     5782        this.listenTo( this.model, 'remove', this.deselect );
     5783        if ( selection ) {
     5784            selection.on( 'reset', this.updateSelect, this );
     5785            // Update the model's details view.
     5786            this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
     5787            this.details( this.model, this.controller.state().get('selection') );
     5788        }
     5789
     5790        this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
     5791    },
     5792    /**
     5793     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     5794     */
     5795    dispose: function() {
     5796        var selection = this.options.selection;
     5797
     5798        // Make sure all settings are saved before removing the view.
     5799        this.updateAll();
     5800
     5801        if ( selection ) {
     5802            selection.off( null, null, this );
     5803        }
     5804        /**
     5805         * call 'dispose' directly on the parent class
     5806         */
     5807        View.prototype.dispose.apply( this, arguments );
     5808        return this;
     5809    },
     5810    /**
     5811     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     5812     */
     5813    render: function() {
     5814        var options = _.defaults( this.model.toJSON(), {
     5815                orientation:   'landscape',
     5816                uploading:     false,
     5817                type:          '',
     5818                subtype:       '',
     5819                icon:          '',
     5820                filename:      '',
     5821                caption:       '',
     5822                title:         '',
     5823                dateFormatted: '',
     5824                width:         '',
     5825                height:        '',
     5826                compat:        false,
     5827                alt:           '',
     5828                description:   ''
     5829            }, this.options );
     5830
     5831        options.buttons  = this.buttons;
     5832        options.describe = this.controller.state().get('describe');
     5833
     5834        if ( 'image' === options.type ) {
     5835            options.size = this.imageSize();
     5836        }
     5837
     5838        options.can = {};
     5839        if ( options.nonces ) {
     5840            options.can.remove = !! options.nonces['delete'];
     5841            options.can.save = !! options.nonces.update;
     5842        }
     5843
     5844        if ( this.controller.state().get('allowLocalEdits') ) {
     5845            options.allowLocalEdits = true;
     5846        }
     5847
     5848        if ( options.uploading && ! options.percent ) {
     5849            options.percent = 0;
     5850        }
     5851
     5852        this.views.detach();
     5853        this.$el.html( this.template( options ) );
     5854
     5855        this.$el.toggleClass( 'uploading', options.uploading );
     5856
     5857        if ( options.uploading ) {
     5858            this.$bar = this.$('.media-progress-bar div');
     5859        } else {
     5860            delete this.$bar;
     5861        }
     5862
     5863        // Check if the model is selected.
     5864        this.updateSelect();
     5865
     5866        // Update the save status.
     5867        this.updateSave();
     5868
     5869        this.views.render();
     5870
     5871        return this;
     5872    },
     5873
     5874    progress: function() {
     5875        if ( this.$bar && this.$bar.length ) {
     5876            this.$bar.width( this.model.get('percent') + '%' );
     5877        }
     5878    },
     5879
     5880    /**
     5881     * @param {Object} event
     5882     */
     5883    toggleSelectionHandler: function( event ) {
     5884        var method;
     5885
     5886        // Don't do anything inside inputs and on the attachment check and remove buttons.
     5887        if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
     5888            return;
     5889        }
     5890
     5891        // Catch arrow events
     5892        if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     5893            this.controller.trigger( 'attachment:keydown:arrow', event );
     5894            return;
     5895        }
     5896
     5897        // Catch enter and space events
     5898        if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     5899            return;
     5900        }
     5901
     5902        event.preventDefault();
     5903
     5904        // In the grid view, bubble up an edit:attachment event to the controller.
     5905        if ( this.controller.isModeActive( 'grid' ) ) {
     5906            if ( this.controller.isModeActive( 'edit' ) ) {
     5907                // Pass the current target to restore focus when closing
     5908                this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
     5909                return;
     5910            }
     5911
     5912            if ( this.controller.isModeActive( 'select' ) ) {
     5913                method = 'toggle';
     5914            }
     5915        }
     5916
     5917        if ( event.shiftKey ) {
     5918            method = 'between';
     5919        } else if ( event.ctrlKey || event.metaKey ) {
     5920            method = 'toggle';
     5921        }
     5922
     5923        this.toggleSelection({
     5924            method: method
     5925        });
     5926
     5927        this.controller.trigger( 'selection:toggle' );
     5928    },
     5929    /**
     5930     * @param {Object} options
     5931     */
     5932    toggleSelection: function( options ) {
     5933        var collection = this.collection,
     5934            selection = this.options.selection,
     5935            model = this.model,
     5936            method = options && options.method,
     5937            single, models, singleIndex, modelIndex;
     5938
     5939        if ( ! selection ) {
     5940            return;
     5941        }
     5942
     5943        single = selection.single();
     5944        method = _.isUndefined( method ) ? selection.multiple : method;
     5945
     5946        // If the `method` is set to `between`, select all models that
     5947        // exist between the current and the selected model.
     5948        if ( 'between' === method && single && selection.multiple ) {
     5949            // If the models are the same, short-circuit.
     5950            if ( single === model ) {
     5951                return;
     5952            }
     5953
     5954            singleIndex = collection.indexOf( single );
     5955            modelIndex  = collection.indexOf( this.model );
     5956
     5957            if ( singleIndex < modelIndex ) {
     5958                models = collection.models.slice( singleIndex, modelIndex + 1 );
     5959            } else {
     5960                models = collection.models.slice( modelIndex, singleIndex + 1 );
     5961            }
     5962
     5963            selection.add( models );
     5964            selection.single( model );
     5965            return;
     5966
     5967        // If the `method` is set to `toggle`, just flip the selection
     5968        // status, regardless of whether the model is the single model.
     5969        } else if ( 'toggle' === method ) {
     5970            selection[ this.selected() ? 'remove' : 'add' ]( model );
     5971            selection.single( model );
     5972            return;
     5973        } else if ( 'add' === method ) {
     5974            selection.add( model );
     5975            selection.single( model );
     5976            return;
     5977        }
     5978
     5979        // Fixes bug that loses focus when selecting a featured image
     5980        if ( ! method ) {
     5981            method = 'add';
     5982        }
     5983
     5984        if ( method !== 'add' ) {
     5985            method = 'reset';
     5986        }
     5987
     5988        if ( this.selected() ) {
     5989            // If the model is the single model, remove it.
     5990            // If it is not the same as the single model,
     5991            // it now becomes the single model.
     5992            selection[ single === model ? 'remove' : 'single' ]( model );
     5993        } else {
     5994            // If the model is not selected, run the `method` on the
     5995            // selection. By default, we `reset` the selection, but the
     5996            // `method` can be set to `add` the model to the selection.
     5997            selection[ method ]( model );
     5998            selection.single( model );
     5999        }
     6000    },
     6001
     6002    updateSelect: function() {
     6003        this[ this.selected() ? 'select' : 'deselect' ]();
     6004    },
     6005    /**
     6006     * @returns {unresolved|Boolean}
     6007     */
     6008    selected: function() {
     6009        var selection = this.options.selection;
     6010        if ( selection ) {
     6011            return !! selection.get( this.model.cid );
     6012        }
     6013    },
     6014    /**
     6015     * @param {Backbone.Model} model
     6016     * @param {Backbone.Collection} collection
     6017     */
     6018    select: function( model, collection ) {
     6019        var selection = this.options.selection,
     6020            controller = this.controller;
     6021
     6022        // Check if a selection exists and if it's the collection provided.
     6023        // If they're not the same collection, bail; we're in another
     6024        // selection's event loop.
     6025        if ( ! selection || ( collection && collection !== selection ) ) {
     6026            return;
     6027        }
     6028
     6029        // Bail if the model is already selected.
     6030        if ( this.$el.hasClass( 'selected' ) ) {
     6031            return;
     6032        }
     6033
     6034        // Add 'selected' class to model, set aria-checked to true.
     6035        this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
     6036        //  Make the checkbox tabable, except in media grid (bulk select mode).
     6037        if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
     6038            this.$( '.check' ).attr( 'tabindex', '0' );
     6039        }
     6040    },
     6041    /**
     6042     * @param {Backbone.Model} model
     6043     * @param {Backbone.Collection} collection
     6044     */
     6045    deselect: function( model, collection ) {
     6046        var selection = this.options.selection;
     6047
     6048        // Check if a selection exists and if it's the collection provided.
     6049        // If they're not the same collection, bail; we're in another
     6050        // selection's event loop.
     6051        if ( ! selection || ( collection && collection !== selection ) ) {
     6052            return;
     6053        }
     6054        this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
     6055            .find( '.check' ).attr( 'tabindex', '-1' );
     6056    },
     6057    /**
     6058     * @param {Backbone.Model} model
     6059     * @param {Backbone.Collection} collection
     6060     */
     6061    details: function( model, collection ) {
     6062        var selection = this.options.selection,
     6063            details;
     6064
     6065        if ( selection !== collection ) {
     6066            return;
     6067        }
     6068
     6069        details = selection.single();
     6070        this.$el.toggleClass( 'details', details === this.model );
     6071    },
     6072    /**
     6073     * @param {string} size
     6074     * @returns {Object}
     6075     */
     6076    imageSize: function( size ) {
     6077        var sizes = this.model.get('sizes'), matched = false;
     6078
     6079        size = size || 'medium';
     6080
     6081        // Use the provided image size if possible.
     6082        if ( sizes ) {
     6083            if ( sizes[ size ] ) {
     6084                matched = sizes[ size ];
     6085            } else if ( sizes.large ) {
     6086                matched = sizes.large;
     6087            } else if ( sizes.thumbnail ) {
     6088                matched = sizes.thumbnail;
     6089            } else if ( sizes.full ) {
     6090                matched = sizes.full;
     6091            }
     6092
     6093            if ( matched ) {
     6094                return _.clone( matched );
     6095            }
     6096        }
     6097
     6098        return {
     6099            url:         this.model.get('url'),
     6100            width:       this.model.get('width'),
     6101            height:      this.model.get('height'),
     6102            orientation: this.model.get('orientation')
     6103        };
     6104    },
     6105    /**
     6106     * @param {Object} event
     6107     */
     6108    updateSetting: function( event ) {
     6109        var $setting = $( event.target ).closest('[data-setting]'),
     6110            setting, value;
     6111
     6112        if ( ! $setting.length ) {
     6113            return;
     6114        }
     6115
     6116        setting = $setting.data('setting');
     6117        value   = event.target.value;
     6118
     6119        if ( this.model.get( setting ) !== value ) {
     6120            this.save( setting, value );
     6121        }
     6122    },
     6123
     6124    /**
     6125     * Pass all the arguments to the model's save method.
     6126     *
     6127     * Records the aggregate status of all save requests and updates the
     6128     * view's classes accordingly.
     6129     */
     6130    save: function() {
     6131        var view = this,
     6132            save = this._save = this._save || { status: 'ready' },
     6133            request = this.model.save.apply( this.model, arguments ),
     6134            requests = save.requests ? $.when( request, save.requests ) : request;
     6135
     6136        // If we're waiting to remove 'Saved.', stop.
     6137        if ( save.savedTimer ) {
     6138            clearTimeout( save.savedTimer );
     6139        }
     6140
     6141        this.updateSave('waiting');
     6142        save.requests = requests;
     6143        requests.always( function() {
     6144            // If we've performed another request since this one, bail.
     6145            if ( save.requests !== requests ) {
     6146                return;
     6147            }
     6148
     6149            view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
     6150            save.savedTimer = setTimeout( function() {
     6151                view.updateSave('ready');
     6152                delete save.savedTimer;
     6153            }, 2000 );
     6154        });
     6155    },
     6156    /**
     6157     * @param {string} status
     6158     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6159     */
     6160    updateSave: function( status ) {
     6161        var save = this._save = this._save || { status: 'ready' };
     6162
     6163        if ( status && status !== save.status ) {
     6164            this.$el.removeClass( 'save-' + save.status );
     6165            save.status = status;
     6166        }
     6167
     6168        this.$el.addClass( 'save-' + save.status );
     6169        return this;
     6170    },
     6171
     6172    updateAll: function() {
     6173        var $settings = this.$('[data-setting]'),
     6174            model = this.model,
     6175            changed;
     6176
     6177        changed = _.chain( $settings ).map( function( el ) {
     6178            var $input = $('input, textarea, select, [value]', el ),
     6179                setting, value;
     6180
     6181            if ( ! $input.length ) {
     6182                return;
     6183            }
     6184
     6185            setting = $(el).data('setting');
     6186            value = $input.val();
     6187
     6188            // Record the value if it changed.
     6189            if ( model.get( setting ) !== value ) {
     6190                return [ setting, value ];
     6191            }
     6192        }).compact().object().value();
     6193
     6194        if ( ! _.isEmpty( changed ) ) {
     6195            model.save( changed );
     6196        }
     6197    },
     6198    /**
     6199     * @param {Object} event
     6200     */
     6201    removeFromLibrary: function( event ) {
     6202        // Catch enter and space events
     6203        if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     6204            return;
     6205        }
     6206
     6207        // Stop propagation so the model isn't selected.
     6208        event.stopPropagation();
     6209
     6210        this.collection.remove( this.model );
     6211    },
     6212
     6213    /**
     6214     * Add the model if it isn't in the selection, if it is in the selection,
     6215     * remove it.
     6216     *
     6217     * @param  {[type]} event [description]
     6218     * @return {[type]}       [description]
     6219     */
     6220    checkClickHandler: function ( event ) {
     6221        var selection = this.options.selection;
     6222        if ( ! selection ) {
     6223            return;
     6224        }
     6225        event.stopPropagation();
     6226        if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
     6227            selection.remove( this.model );
     6228            // Move focus back to the attachment tile (from the check).
     6229            this.$el.focus();
     6230        } else {
     6231            selection.add( this.model );
     6232        }
     6233    }
     6234});
     6235
     6236// Ensure settings remain in sync between attachment views.
     6237_.each({
     6238    caption: '_syncCaption',
     6239    title:   '_syncTitle',
     6240    artist:  '_syncArtist',
     6241    album:   '_syncAlbum'
     6242}, function( method, setting ) {
     6243    /**
     6244     * @param {Backbone.Model} model
     6245     * @param {string} value
     6246     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6247     */
     6248    Attachment.prototype[ method ] = function( model, value ) {
     6249        var $setting = this.$('[data-setting="' + setting + '"]');
     6250
     6251        if ( ! $setting.length ) {
     6252            return this;
     6253        }
     6254
     6255        // If the updated value is in sync with the value in the DOM, there
     6256        // is no need to re-render. If we're currently editing the value,
     6257        // it will automatically be in sync, suppressing the re-render for
     6258        // the view we're editing, while updating any others.
     6259        if ( value === $setting.find('input, textarea, select, [value]').val() ) {
     6260            return this;
     6261        }
     6262
     6263        return this.render();
     6264    };
     6265});
     6266
     6267module.exports = Attachment;
     6268
     6269
     6270/***/ }),
     6271/* 70 */
     6272/***/ (function(module, exports) {
     6273
     6274/**
     6275 * wp.media.view.Attachment.Library
     6276 *
     6277 * @class
     6278 * @augments wp.media.view.Attachment
     6279 * @augments wp.media.View
     6280 * @augments wp.Backbone.View
     6281 * @augments Backbone.View
     6282 */
     6283var Library = wp.media.view.Attachment.extend({
     6284    buttons: {
     6285        check: true
     6286    }
     6287});
     6288
     6289module.exports = Library;
     6290
     6291
     6292/***/ }),
     6293/* 71 */
     6294/***/ (function(module, exports) {
     6295
     6296/**
     6297 * wp.media.view.Attachment.EditLibrary
     6298 *
     6299 * @class
     6300 * @augments wp.media.view.Attachment
     6301 * @augments wp.media.View
     6302 * @augments wp.Backbone.View
     6303 * @augments Backbone.View
     6304 */
     6305var EditLibrary = wp.media.view.Attachment.extend({
     6306    buttons: {
     6307        close: true
     6308    }
     6309});
     6310
     6311module.exports = EditLibrary;
     6312
     6313
     6314/***/ }),
     6315/* 72 */
     6316/***/ (function(module, exports) {
     6317
     6318/**
     6319 * wp.media.view.Attachments
     6320 *
     6321 * @class
     6322 * @augments wp.media.View
     6323 * @augments wp.Backbone.View
     6324 * @augments Backbone.View
     6325 */
     6326var View = wp.media.View,
     6327    $ = jQuery,
     6328    Attachments;
     6329
     6330Attachments = View.extend({
     6331    tagName:   'ul',
     6332    className: 'attachments',
     6333
     6334    attributes: {
     6335        tabIndex: -1
     6336    },
     6337
     6338    initialize: function() {
     6339        this.el.id = _.uniqueId('__attachments-view-');
     6340
     6341        _.defaults( this.options, {
     6342            refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
     6343            refreshThreshold:   3,
     6344            AttachmentView:     wp.media.view.Attachment,
     6345            sortable:           false,
     6346            resize:             true,
     6347            idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
     6348        });
     6349
     6350        this._viewsByCid = {};
     6351        this.$window = $( window );
     6352        this.resizeEvent = 'resize.media-modal-columns';
     6353
     6354        this.collection.on( 'add', function( attachment ) {
     6355            this.views.add( this.createAttachmentView( attachment ), {
     6356                at: this.collection.indexOf( attachment )
     6357            });
     6358        }, this );
     6359
     6360        this.collection.on( 'remove', function( attachment ) {
     6361            var view = this._viewsByCid[ attachment.cid ];
     6362            delete this._viewsByCid[ attachment.cid ];
     6363
     6364            if ( view ) {
     6365                view.remove();
     6366            }
     6367        }, this );
     6368
     6369        this.collection.on( 'reset', this.render, this );
     6370
     6371        this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
     6372
     6373        // Throttle the scroll handler and bind this.
     6374        this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
     6375
     6376        this.options.scrollElement = this.options.scrollElement || this.el;
     6377        $( this.options.scrollElement ).on( 'scroll', this.scroll );
     6378
     6379        this.initSortable();
     6380
     6381        _.bindAll( this, 'setColumns' );
     6382
     6383        if ( this.options.resize ) {
     6384            this.on( 'ready', this.bindEvents );
     6385            this.controller.on( 'open', this.setColumns );
     6386
     6387            // Call this.setColumns() after this view has been rendered in the DOM so
     6388            // attachments get proper width applied.
     6389            _.defer( this.setColumns, this );
     6390        }
     6391    },
     6392
     6393    bindEvents: function() {
     6394        this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
     6395    },
     6396
     6397    attachmentFocus: function() {
     6398        this.$( 'li:first' ).focus();
     6399    },
     6400
     6401    restoreFocus: function() {
     6402        this.$( 'li.selected:first' ).focus();
     6403    },
     6404
     6405    arrowEvent: function( event ) {
     6406        var attachments = this.$el.children( 'li' ),
     6407            perRow = this.columns,
     6408            index = attachments.filter( ':focus' ).index(),
     6409            row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
     6410
     6411        if ( index === -1 ) {
     6412            return;
     6413        }
     6414
     6415        // Left arrow
     6416        if ( 37 === event.keyCode ) {
     6417            if ( 0 === index ) {
     6418                return;
     6419            }
     6420            attachments.eq( index - 1 ).focus();
     6421        }
     6422
     6423        // Up arrow
     6424        if ( 38 === event.keyCode ) {
     6425            if ( 1 === row ) {
     6426                return;
     6427            }
     6428            attachments.eq( index - perRow ).focus();
     6429        }
     6430
     6431        // Right arrow
     6432        if ( 39 === event.keyCode ) {
     6433            if ( attachments.length === index ) {
     6434                return;
     6435            }
     6436            attachments.eq( index + 1 ).focus();
     6437        }
     6438
     6439        // Down arrow
     6440        if ( 40 === event.keyCode ) {
     6441            if ( Math.ceil( attachments.length / perRow ) === row ) {
     6442                return;
     6443            }
     6444            attachments.eq( index + perRow ).focus();
     6445        }
     6446    },
     6447
     6448    dispose: function() {
     6449        this.collection.props.off( null, null, this );
     6450        if ( this.options.resize ) {
     6451            this.$window.off( this.resizeEvent );
     6452        }
     6453
     6454        /**
     6455         * call 'dispose' directly on the parent class
     6456         */
     6457        View.prototype.dispose.apply( this, arguments );
     6458    },
     6459
     6460    setColumns: function() {
     6461        var prev = this.columns,
     6462            width = this.$el.width();
     6463
     6464        if ( width ) {
     6465            this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
     6466
     6467            if ( ! prev || prev !== this.columns ) {
     6468                this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
     6469            }
     6470        }
     6471    },
     6472
     6473    initSortable: function() {
     6474        var collection = this.collection;
     6475
     6476        if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
     6477            return;
     6478        }
     6479
     6480        this.$el.sortable( _.extend({
     6481            // If the `collection` has a `comparator`, disable sorting.
     6482            disabled: !! collection.comparator,
     6483
     6484            // Change the position of the attachment as soon as the
     6485            // mouse pointer overlaps a thumbnail.
     6486            tolerance: 'pointer',
     6487
     6488            // Record the initial `index` of the dragged model.
     6489            start: function( event, ui ) {
     6490                ui.item.data('sortableIndexStart', ui.item.index());
     6491            },
     6492
     6493            // Update the model's index in the collection.
     6494            // Do so silently, as the view is already accurate.
     6495            update: function( event, ui ) {
     6496                var model = collection.at( ui.item.data('sortableIndexStart') ),
     6497                    comparator = collection.comparator;
     6498
     6499                // Temporarily disable the comparator to prevent `add`
     6500                // from re-sorting.
     6501                delete collection.comparator;
     6502
     6503                // Silently shift the model to its new index.
     6504                collection.remove( model, {
     6505                    silent: true
     6506                });
     6507                collection.add( model, {
     6508                    silent: true,
     6509                    at:     ui.item.index()
     6510                });
     6511
     6512                // Restore the comparator.
     6513                collection.comparator = comparator;
     6514
     6515                // Fire the `reset` event to ensure other collections sync.
     6516                collection.trigger( 'reset', collection );
     6517
     6518                // If the collection is sorted by menu order,
     6519                // update the menu order.
     6520                collection.saveMenuOrder();
     6521            }
     6522        }, this.options.sortable ) );
     6523
     6524        // If the `orderby` property is changed on the `collection`,
     6525        // check to see if we have a `comparator`. If so, disable sorting.
     6526        collection.props.on( 'change:orderby', function() {
     6527            this.$el.sortable( 'option', 'disabled', !! collection.comparator );
     6528        }, this );
     6529
     6530        this.collection.props.on( 'change:orderby', this.refreshSortable, this );
     6531        this.refreshSortable();
     6532    },
     6533
     6534    refreshSortable: function() {
     6535        if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
     6536            return;
     6537        }
     6538
     6539        // If the `collection` has a `comparator`, disable sorting.
     6540        var collection = this.collection,
     6541            orderby = collection.props.get('orderby'),
     6542            enabled = 'menuOrder' === orderby || ! collection.comparator;
     6543
     6544        this.$el.sortable( 'option', 'disabled', ! enabled );
     6545    },
     6546
     6547    /**
     6548     * @param {wp.media.model.Attachment} attachment
     6549     * @returns {wp.media.View}
     6550     */
     6551    createAttachmentView: function( attachment ) {
     6552        var view = new this.options.AttachmentView({
     6553            controller:           this.controller,
     6554            model:                attachment,
     6555            collection:           this.collection,
     6556            selection:            this.options.selection
     6557        });
     6558
     6559        return this._viewsByCid[ attachment.cid ] = view;
     6560    },
     6561
     6562    prepare: function() {
     6563        // Create all of the Attachment views, and replace
     6564        // the list in a single DOM operation.
     6565        if ( this.collection.length ) {
     6566            this.views.set( this.collection.map( this.createAttachmentView, this ) );
     6567
     6568        // If there are no elements, clear the views and load some.
     6569        } else {
     6570            this.views.unset();
     6571            this.collection.more().done( this.scroll );
     6572        }
     6573    },
     6574
     6575    ready: function() {
     6576        // Trigger the scroll event to check if we're within the
     6577        // threshold to query for additional attachments.
     6578        this.scroll();
     6579    },
     6580
     6581    scroll: function() {
     6582        var view = this,
     6583            el = this.options.scrollElement,
     6584            scrollTop = el.scrollTop,
     6585            toolbar;
     6586
     6587        // The scroll event occurs on the document, but the element
     6588        // that should be checked is the document body.
     6589        if ( el === document ) {
     6590            el = document.body;
     6591            scrollTop = $(document).scrollTop();
     6592        }
     6593
     6594        if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
     6595            return;
     6596        }
     6597
     6598        toolbar = this.views.parent.toolbar;
     6599
     6600        // Show the spinner only if we are close to the bottom.
     6601        if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
     6602            toolbar.get('spinner').show();
     6603        }
     6604
     6605        if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
     6606            this.collection.more().done(function() {
     6607                view.scroll();
     6608                toolbar.get('spinner').hide();
     6609            });
     6610        }
     6611    }
     6612});
     6613
     6614module.exports = Attachments;
     6615
     6616
     6617/***/ }),
     6618/* 73 */
     6619/***/ (function(module, exports) {
     6620
     6621/**
     6622 * wp.media.view.Search
     6623 *
     6624 * @class
     6625 * @augments wp.media.View
     6626 * @augments wp.Backbone.View
     6627 * @augments Backbone.View
     6628 */
     6629var l10n = wp.media.view.l10n,
     6630    Search;
     6631
     6632Search = wp.media.View.extend({
     6633    tagName:   'input',
     6634    className: 'search',
     6635    id:        'media-search-input',
     6636
     6637    attributes: {
     6638        type:        'search',
     6639        placeholder: l10n.search
     6640    },
     6641
     6642    events: {
     6643        'input':  'search',
     6644        'keyup':  'search',
     6645        'change': 'search',
     6646        'search': 'search'
     6647    },
     6648
     6649    /**
     6650     * @returns {wp.media.view.Search} Returns itself to allow chaining
     6651     */
     6652    render: function() {
     6653        this.el.value = this.model.escape('search');
     6654        return this;
     6655    },
     6656
     6657    search: function( event ) {
     6658        if ( event.target.value ) {
     6659            this.model.set( 'search', event.target.value );
     6660        } else {
     6661            this.model.unset('search');
     6662        }
     6663    }
     6664});
     6665
     6666module.exports = Search;
     6667
     6668
     6669/***/ }),
     6670/* 74 */
     6671/***/ (function(module, exports) {
     6672
    23696673/**
    23706674 * wp.media.view.AttachmentFilters
     
    24436747module.exports = AttachmentFilters;
    24446748
    2445 },{}],22:[function(require,module,exports){
    2446 /**
    2447  * wp.media.view.AttachmentFilters.All
    2448  *
    2449  * @class
    2450  * @augments wp.media.view.AttachmentFilters
    2451  * @augments wp.media.View
    2452  * @augments wp.Backbone.View
    2453  * @augments Backbone.View
    2454  */
    2455 var l10n = wp.media.view.l10n,
    2456     All;
    2457 
    2458 All = wp.media.view.AttachmentFilters.extend({
    2459     createFilters: function() {
    2460         var filters = {};
    2461 
    2462         _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
    2463             filters[ key ] = {
    2464                 text: text,
    2465                 props: {
    2466                     status:  null,
    2467                     type:    key,
    2468                     uploadedTo: null,
    2469                     orderby: 'date',
    2470                     order:   'DESC'
    2471                 }
    2472             };
    2473         });
    2474 
    2475         filters.all = {
    2476             text:  l10n.allMediaItems,
    2477             props: {
    2478                 status:  null,
    2479                 type:    null,
    2480                 uploadedTo: null,
    2481                 orderby: 'date',
    2482                 order:   'DESC'
    2483             },
    2484             priority: 10
    2485         };
    2486 
    2487         if ( wp.media.view.settings.post.id ) {
    2488             filters.uploaded = {
    2489                 text:  l10n.uploadedToThisPost,
    2490                 props: {
    2491                     status:  null,
    2492                     type:    null,
    2493                     uploadedTo: wp.media.view.settings.post.id,
    2494                     orderby: 'menuOrder',
    2495                     order:   'ASC'
    2496                 },
    2497                 priority: 20
    2498             };
    2499         }
    2500 
    2501         filters.unattached = {
    2502             text:  l10n.unattached,
    2503             props: {
    2504                 status:     null,
    2505                 uploadedTo: 0,
    2506                 type:       null,
    2507                 orderby:    'menuOrder',
    2508                 order:      'ASC'
    2509             },
    2510             priority: 50
    2511         };
    2512 
    2513         if ( wp.media.view.settings.mediaTrash &&
    2514             this.controller.isModeActive( 'grid' ) ) {
    2515 
    2516             filters.trash = {
    2517                 text:  l10n.trash,
    2518                 props: {
    2519                     uploadedTo: null,
    2520                     status:     'trash',
    2521                     type:       null,
    2522                     orderby:    'date',
    2523                     order:      'DESC'
    2524                 },
    2525                 priority: 50
    2526             };
    2527         }
    2528 
    2529         this.filters = filters;
    2530     }
    2531 });
    2532 
    2533 module.exports = All;
    2534 
    2535 },{}],23:[function(require,module,exports){
     6749
     6750/***/ }),
     6751/* 75 */
     6752/***/ (function(module, exports) {
     6753
    25366754/**
    25376755 * A filter dropdown for month/dates.
     
    25746792module.exports = DateFilter;
    25756793
    2576 },{}],24:[function(require,module,exports){
     6794
     6795/***/ }),
     6796/* 76 */
     6797/***/ (function(module, exports) {
     6798
    25776799/**
    25786800 * wp.media.view.AttachmentFilters.Uploaded
     
    26336855module.exports = Uploaded;
    26346856
    2635 },{}],25:[function(require,module,exports){
     6857
     6858/***/ }),
     6859/* 77 */
     6860/***/ (function(module, exports) {
     6861
    26366862/**
    2637  * wp.media.view.Attachment
     6863 * wp.media.view.AttachmentFilters.All
    26386864 *
    26396865 * @class
     6866 * @augments wp.media.view.AttachmentFilters
    26406867 * @augments wp.media.View
    26416868 * @augments wp.Backbone.View
    26426869 * @augments Backbone.View
    26436870 */
    2644 var View = wp.media.View,
    2645     $ = jQuery,
    2646     Attachment;
    2647 
    2648 Attachment = View.extend({
    2649     tagName:   'li',
    2650     className: 'attachment',
    2651     template:  wp.template('attachment'),
    2652 
    2653     attributes: function() {
    2654         return {
    2655             'tabIndex':     0,
    2656             'role':         'checkbox',
    2657             'aria-label':   this.model.get( 'title' ),
    2658             'aria-checked': false,
    2659             'data-id':      this.model.get( 'id' )
     6871var l10n = wp.media.view.l10n,
     6872    All;
     6873
     6874All = wp.media.view.AttachmentFilters.extend({
     6875    createFilters: function() {
     6876        var filters = {};
     6877
     6878        _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
     6879            filters[ key ] = {
     6880                text: text,
     6881                props: {
     6882                    status:  null,
     6883                    type:    key,
     6884                    uploadedTo: null,
     6885                    orderby: 'date',
     6886                    order:   'DESC'
     6887                }
     6888            };
     6889        });
     6890
     6891        filters.all = {
     6892            text:  l10n.allMediaItems,
     6893            props: {
     6894                status:  null,
     6895                type:    null,
     6896                uploadedTo: null,
     6897                orderby: 'date',
     6898                order:   'DESC'
     6899            },
     6900            priority: 10
    26606901        };
    2661     },
    2662 
    2663     events: {
    2664         'click .js--select-attachment':   'toggleSelectionHandler',
    2665         'change [data-setting]':          'updateSetting',
    2666         'change [data-setting] input':    'updateSetting',
    2667         'change [data-setting] select':   'updateSetting',
    2668         'change [data-setting] textarea': 'updateSetting',
    2669         'click .attachment-close':        'removeFromLibrary',
    2670         'click .check':                   'checkClickHandler',
    2671         'keydown':                        'toggleSelectionHandler'
    2672     },
    2673 
    2674     buttons: {},
    2675 
    2676     initialize: function() {
    2677         var selection = this.options.selection,
    2678             options = _.defaults( this.options, {
    2679                 rerenderOnModelChange: true
    2680             } );
    2681 
    2682         if ( options.rerenderOnModelChange ) {
    2683             this.listenTo( this.model, 'change', this.render );
    2684         } else {
    2685             this.listenTo( this.model, 'change:percent', this.progress );
    2686         }
    2687         this.listenTo( this.model, 'change:title', this._syncTitle );
    2688         this.listenTo( this.model, 'change:caption', this._syncCaption );
    2689         this.listenTo( this.model, 'change:artist', this._syncArtist );
    2690         this.listenTo( this.model, 'change:album', this._syncAlbum );
    2691 
    2692         // Update the selection.
    2693         this.listenTo( this.model, 'add', this.select );
    2694         this.listenTo( this.model, 'remove', this.deselect );
    2695         if ( selection ) {
    2696             selection.on( 'reset', this.updateSelect, this );
    2697             // Update the model's details view.
    2698             this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
    2699             this.details( this.model, this.controller.state().get('selection') );
    2700         }
    2701 
    2702         this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
    2703     },
    2704     /**
    2705      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    2706      */
    2707     dispose: function() {
    2708         var selection = this.options.selection;
    2709 
    2710         // Make sure all settings are saved before removing the view.
    2711         this.updateAll();
    2712 
    2713         if ( selection ) {
    2714             selection.off( null, null, this );
    2715         }
    2716         /**
    2717          * call 'dispose' directly on the parent class
    2718          */
    2719         View.prototype.dispose.apply( this, arguments );
    2720         return this;
    2721     },
    2722     /**
    2723      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    2724      */
    2725     render: function() {
    2726         var options = _.defaults( this.model.toJSON(), {
    2727                 orientation:   'landscape',
    2728                 uploading:     false,
    2729                 type:          '',
    2730                 subtype:       '',
    2731                 icon:          '',
    2732                 filename:      '',
    2733                 caption:       '',
    2734                 title:         '',
    2735                 dateFormatted: '',
    2736                 width:         '',
    2737                 height:        '',
    2738                 compat:        false,
    2739                 alt:           '',
    2740                 description:   ''
    2741             }, this.options );
    2742 
    2743         options.buttons  = this.buttons;
    2744         options.describe = this.controller.state().get('describe');
    2745 
    2746         if ( 'image' === options.type ) {
    2747             options.size = this.imageSize();
    2748         }
    2749 
    2750         options.can = {};
    2751         if ( options.nonces ) {
    2752             options.can.remove = !! options.nonces['delete'];
    2753             options.can.save = !! options.nonces.update;
    2754         }
    2755 
    2756         if ( this.controller.state().get('allowLocalEdits') ) {
    2757             options.allowLocalEdits = true;
    2758         }
    2759 
    2760         if ( options.uploading && ! options.percent ) {
    2761             options.percent = 0;
    2762         }
    2763 
    2764         this.views.detach();
    2765         this.$el.html( this.template( options ) );
    2766 
    2767         this.$el.toggleClass( 'uploading', options.uploading );
    2768 
    2769         if ( options.uploading ) {
    2770             this.$bar = this.$('.media-progress-bar div');
    2771         } else {
    2772             delete this.$bar;
    2773         }
    2774 
    2775         // Check if the model is selected.
    2776         this.updateSelect();
    2777 
    2778         // Update the save status.
    2779         this.updateSave();
    2780 
    2781         this.views.render();
    2782 
    2783         return this;
    2784     },
    2785 
    2786     progress: function() {
    2787         if ( this.$bar && this.$bar.length ) {
    2788             this.$bar.width( this.model.get('percent') + '%' );
    2789         }
    2790     },
    2791 
    2792     /**
    2793      * @param {Object} event
    2794      */
    2795     toggleSelectionHandler: function( event ) {
    2796         var method;
    2797 
    2798         // Don't do anything inside inputs and on the attachment check and remove buttons.
    2799         if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
    2800             return;
    2801         }
    2802 
    2803         // Catch arrow events
    2804         if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    2805             this.controller.trigger( 'attachment:keydown:arrow', event );
    2806             return;
    2807         }
    2808 
    2809         // Catch enter and space events
    2810         if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    2811             return;
    2812         }
    2813 
    2814         event.preventDefault();
    2815 
    2816         // In the grid view, bubble up an edit:attachment event to the controller.
    2817         if ( this.controller.isModeActive( 'grid' ) ) {
    2818             if ( this.controller.isModeActive( 'edit' ) ) {
    2819                 // Pass the current target to restore focus when closing
    2820                 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
    2821                 return;
    2822             }
    2823 
    2824             if ( this.controller.isModeActive( 'select' ) ) {
    2825                 method = 'toggle';
    2826             }
    2827         }
    2828 
    2829         if ( event.shiftKey ) {
    2830             method = 'between';
    2831         } else if ( event.ctrlKey || event.metaKey ) {
    2832             method = 'toggle';
    2833         }
    2834 
    2835         this.toggleSelection({
    2836             method: method
    2837         });
    2838 
    2839         this.controller.trigger( 'selection:toggle' );
    2840     },
    2841     /**
    2842      * @param {Object} options
    2843      */
    2844     toggleSelection: function( options ) {
    2845         var collection = this.collection,
    2846             selection = this.options.selection,
    2847             model = this.model,
    2848             method = options && options.method,
    2849             single, models, singleIndex, modelIndex;
    2850 
    2851         if ( ! selection ) {
    2852             return;
    2853         }
    2854 
    2855         single = selection.single();
    2856         method = _.isUndefined( method ) ? selection.multiple : method;
    2857 
    2858         // If the `method` is set to `between`, select all models that
    2859         // exist between the current and the selected model.
    2860         if ( 'between' === method && single && selection.multiple ) {
    2861             // If the models are the same, short-circuit.
    2862             if ( single === model ) {
    2863                 return;
    2864             }
    2865 
    2866             singleIndex = collection.indexOf( single );
    2867             modelIndex  = collection.indexOf( this.model );
    2868 
    2869             if ( singleIndex < modelIndex ) {
    2870                 models = collection.models.slice( singleIndex, modelIndex + 1 );
    2871             } else {
    2872                 models = collection.models.slice( modelIndex, singleIndex + 1 );
    2873             }
    2874 
    2875             selection.add( models );
    2876             selection.single( model );
    2877             return;
    2878 
    2879         // If the `method` is set to `toggle`, just flip the selection
    2880         // status, regardless of whether the model is the single model.
    2881         } else if ( 'toggle' === method ) {
    2882             selection[ this.selected() ? 'remove' : 'add' ]( model );
    2883             selection.single( model );
    2884             return;
    2885         } else if ( 'add' === method ) {
    2886             selection.add( model );
    2887             selection.single( model );
    2888             return;
    2889         }
    2890 
    2891         // Fixes bug that loses focus when selecting a featured image
    2892         if ( ! method ) {
    2893             method = 'add';
    2894         }
    2895 
    2896         if ( method !== 'add' ) {
    2897             method = 'reset';
    2898         }
    2899 
    2900         if ( this.selected() ) {
    2901             // If the model is the single model, remove it.
    2902             // If it is not the same as the single model,
    2903             // it now becomes the single model.
    2904             selection[ single === model ? 'remove' : 'single' ]( model );
    2905         } else {
    2906             // If the model is not selected, run the `method` on the
    2907             // selection. By default, we `reset` the selection, but the
    2908             // `method` can be set to `add` the model to the selection.
    2909             selection[ method ]( model );
    2910             selection.single( model );
    2911         }
    2912     },
    2913 
    2914     updateSelect: function() {
    2915         this[ this.selected() ? 'select' : 'deselect' ]();
    2916     },
    2917     /**
    2918      * @returns {unresolved|Boolean}
    2919      */
    2920     selected: function() {
    2921         var selection = this.options.selection;
    2922         if ( selection ) {
    2923             return !! selection.get( this.model.cid );
    2924         }
    2925     },
    2926     /**
    2927      * @param {Backbone.Model} model
    2928      * @param {Backbone.Collection} collection
    2929      */
    2930     select: function( model, collection ) {
    2931         var selection = this.options.selection,
    2932             controller = this.controller;
    2933 
    2934         // Check if a selection exists and if it's the collection provided.
    2935         // If they're not the same collection, bail; we're in another
    2936         // selection's event loop.
    2937         if ( ! selection || ( collection && collection !== selection ) ) {
    2938             return;
    2939         }
    2940 
    2941         // Bail if the model is already selected.
    2942         if ( this.$el.hasClass( 'selected' ) ) {
    2943             return;
    2944         }
    2945 
    2946         // Add 'selected' class to model, set aria-checked to true.
    2947         this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
    2948         //  Make the checkbox tabable, except in media grid (bulk select mode).
    2949         if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
    2950             this.$( '.check' ).attr( 'tabindex', '0' );
    2951         }
    2952     },
    2953     /**
    2954      * @param {Backbone.Model} model
    2955      * @param {Backbone.Collection} collection
    2956      */
    2957     deselect: function( model, collection ) {
    2958         var selection = this.options.selection;
    2959 
    2960         // Check if a selection exists and if it's the collection provided.
    2961         // If they're not the same collection, bail; we're in another
    2962         // selection's event loop.
    2963         if ( ! selection || ( collection && collection !== selection ) ) {
    2964             return;
    2965         }
    2966         this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
    2967             .find( '.check' ).attr( 'tabindex', '-1' );
    2968     },
    2969     /**
    2970      * @param {Backbone.Model} model
    2971      * @param {Backbone.Collection} collection
    2972      */
    2973     details: function( model, collection ) {
    2974         var selection = this.options.selection,
    2975             details;
    2976 
    2977         if ( selection !== collection ) {
    2978             return;
    2979         }
    2980 
    2981         details = selection.single();
    2982         this.$el.toggleClass( 'details', details === this.model );
    2983     },
    2984     /**
    2985      * @param {string} size
    2986      * @returns {Object}
    2987      */
    2988     imageSize: function( size ) {
    2989         var sizes = this.model.get('sizes'), matched = false;
    2990 
    2991         size = size || 'medium';
    2992 
    2993         // Use the provided image size if possible.
    2994         if ( sizes ) {
    2995             if ( sizes[ size ] ) {
    2996                 matched = sizes[ size ];
    2997             } else if ( sizes.large ) {
    2998                 matched = sizes.large;
    2999             } else if ( sizes.thumbnail ) {
    3000                 matched = sizes.thumbnail;
    3001             } else if ( sizes.full ) {
    3002                 matched = sizes.full;
    3003             }
    3004 
    3005             if ( matched ) {
    3006                 return _.clone( matched );
    3007             }
    3008         }
    3009 
    3010         return {
    3011             url:         this.model.get('url'),
    3012             width:       this.model.get('width'),
    3013             height:      this.model.get('height'),
    3014             orientation: this.model.get('orientation')
     6902
     6903        if ( wp.media.view.settings.post.id ) {
     6904            filters.uploaded = {
     6905                text:  l10n.uploadedToThisPost,
     6906                props: {
     6907                    status:  null,
     6908                    type:    null,
     6909                    uploadedTo: wp.media.view.settings.post.id,
     6910                    orderby: 'menuOrder',
     6911                    order:   'ASC'
     6912                },
     6913                priority: 20
     6914            };
     6915        }
     6916
     6917        filters.unattached = {
     6918            text:  l10n.unattached,
     6919            props: {
     6920                status:     null,
     6921                uploadedTo: 0,
     6922                type:       null,
     6923                orderby:    'menuOrder',
     6924                order:      'ASC'
     6925            },
     6926            priority: 50
    30156927        };
    3016     },
    3017     /**
    3018      * @param {Object} event
    3019      */
    3020     updateSetting: function( event ) {
    3021         var $setting = $( event.target ).closest('[data-setting]'),
    3022             setting, value;
    3023 
    3024         if ( ! $setting.length ) {
    3025             return;
    3026         }
    3027 
    3028         setting = $setting.data('setting');
    3029         value   = event.target.value;
    3030 
    3031         if ( this.model.get( setting ) !== value ) {
    3032             this.save( setting, value );
    3033         }
    3034     },
    3035 
    3036     /**
    3037      * Pass all the arguments to the model's save method.
    3038      *
    3039      * Records the aggregate status of all save requests and updates the
    3040      * view's classes accordingly.
    3041      */
    3042     save: function() {
    3043         var view = this,
    3044             save = this._save = this._save || { status: 'ready' },
    3045             request = this.model.save.apply( this.model, arguments ),
    3046             requests = save.requests ? $.when( request, save.requests ) : request;
    3047 
    3048         // If we're waiting to remove 'Saved.', stop.
    3049         if ( save.savedTimer ) {
    3050             clearTimeout( save.savedTimer );
    3051         }
    3052 
    3053         this.updateSave('waiting');
    3054         save.requests = requests;
    3055         requests.always( function() {
    3056             // If we've performed another request since this one, bail.
    3057             if ( save.requests !== requests ) {
    3058                 return;
    3059             }
    3060 
    3061             view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
    3062             save.savedTimer = setTimeout( function() {
    3063                 view.updateSave('ready');
    3064                 delete save.savedTimer;
    3065             }, 2000 );
    3066         });
    3067     },
    3068     /**
    3069      * @param {string} status
    3070      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3071      */
    3072     updateSave: function( status ) {
    3073         var save = this._save = this._save || { status: 'ready' };
    3074 
    3075         if ( status && status !== save.status ) {
    3076             this.$el.removeClass( 'save-' + save.status );
    3077             save.status = status;
    3078         }
    3079 
    3080         this.$el.addClass( 'save-' + save.status );
    3081         return this;
    3082     },
    3083 
    3084     updateAll: function() {
    3085         var $settings = this.$('[data-setting]'),
    3086             model = this.model,
    3087             changed;
    3088 
    3089         changed = _.chain( $settings ).map( function( el ) {
    3090             var $input = $('input, textarea, select, [value]', el ),
    3091                 setting, value;
    3092 
    3093             if ( ! $input.length ) {
    3094                 return;
    3095             }
    3096 
    3097             setting = $(el).data('setting');
    3098             value = $input.val();
    3099 
    3100             // Record the value if it changed.
    3101             if ( model.get( setting ) !== value ) {
    3102                 return [ setting, value ];
    3103             }
    3104         }).compact().object().value();
    3105 
    3106         if ( ! _.isEmpty( changed ) ) {
    3107             model.save( changed );
    3108         }
    3109     },
    3110     /**
    3111      * @param {Object} event
    3112      */
    3113     removeFromLibrary: function( event ) {
    3114         // Catch enter and space events
    3115         if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    3116             return;
    3117         }
    3118 
    3119         // Stop propagation so the model isn't selected.
    3120         event.stopPropagation();
    3121 
    3122         this.collection.remove( this.model );
    3123     },
    3124 
    3125     /**
    3126      * Add the model if it isn't in the selection, if it is in the selection,
    3127      * remove it.
    3128      *
    3129      * @param  {[type]} event [description]
    3130      * @return {[type]}       [description]
    3131      */
    3132     checkClickHandler: function ( event ) {
    3133         var selection = this.options.selection;
    3134         if ( ! selection ) {
    3135             return;
    3136         }
    3137         event.stopPropagation();
    3138         if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
    3139             selection.remove( this.model );
    3140             // Move focus back to the attachment tile (from the check).
    3141             this.$el.focus();
    3142         } else {
    3143             selection.add( this.model );
    3144         }
     6928
     6929        if ( wp.media.view.settings.mediaTrash &&
     6930            this.controller.isModeActive( 'grid' ) ) {
     6931
     6932            filters.trash = {
     6933                text:  l10n.trash,
     6934                props: {
     6935                    uploadedTo: null,
     6936                    status:     'trash',
     6937                    type:       null,
     6938                    orderby:    'date',
     6939                    order:      'DESC'
     6940                },
     6941                priority: 50
     6942            };
     6943        }
     6944
     6945        this.filters = filters;
    31456946    }
    31466947});
    31476948
    3148 // Ensure settings remain in sync between attachment views.
    3149 _.each({
    3150     caption: '_syncCaption',
    3151     title:   '_syncTitle',
    3152     artist:  '_syncArtist',
    3153     album:   '_syncAlbum'
    3154 }, function( method, setting ) {
    3155     /**
    3156      * @param {Backbone.Model} model
    3157      * @param {string} value
    3158      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3159      */
    3160     Attachment.prototype[ method ] = function( model, value ) {
    3161         var $setting = this.$('[data-setting="' + setting + '"]');
    3162 
    3163         if ( ! $setting.length ) {
    3164             return this;
    3165         }
    3166 
    3167         // If the updated value is in sync with the value in the DOM, there
    3168         // is no need to re-render. If we're currently editing the value,
    3169         // it will automatically be in sync, suppressing the re-render for
    3170         // the view we're editing, while updating any others.
    3171         if ( value === $setting.find('input, textarea, select, [value]').val() ) {
    3172             return this;
    3173         }
    3174 
    3175         return this.render();
    3176     };
    3177 });
    3178 
    3179 module.exports = Attachment;
    3180 
    3181 },{}],26:[function(require,module,exports){
    3182 /**
    3183  * wp.media.view.Attachment.Details
    3184  *
    3185  * @class
    3186  * @augments wp.media.view.Attachment
    3187  * @augments wp.media.View
    3188  * @augments wp.Backbone.View
    3189  * @augments Backbone.View
    3190  */
    3191 var Attachment = wp.media.view.Attachment,
    3192     l10n = wp.media.view.l10n,
    3193     Details;
    3194 
    3195 Details = Attachment.extend({
    3196     tagName:   'div',
    3197     className: 'attachment-details',
    3198     template:  wp.template('attachment-details'),
    3199 
    3200     attributes: function() {
    3201         return {
    3202             'tabIndex':     0,
    3203             'data-id':      this.model.get( 'id' )
    3204         };
    3205     },
    3206 
    3207     events: {
    3208         'change [data-setting]':          'updateSetting',
    3209         'change [data-setting] input':    'updateSetting',
    3210         'change [data-setting] select':   'updateSetting',
    3211         'change [data-setting] textarea': 'updateSetting',
    3212         'click .delete-attachment':       'deleteAttachment',
    3213         'click .trash-attachment':        'trashAttachment',
    3214         'click .untrash-attachment':      'untrashAttachment',
    3215         'click .edit-attachment':         'editAttachment',
    3216         'keydown':                        'toggleSelectionHandler'
    3217     },
    3218 
    3219     initialize: function() {
    3220         this.options = _.defaults( this.options, {
    3221             rerenderOnModelChange: false
    3222         });
    3223 
    3224         this.on( 'ready', this.initialFocus );
    3225         // Call 'initialize' directly on the parent class.
    3226         Attachment.prototype.initialize.apply( this, arguments );
    3227     },
    3228 
    3229     initialFocus: function() {
    3230         if ( ! wp.media.isTouchDevice ) {
    3231             /*
    3232             Previously focused the first ':input' (the readonly URL text field).
    3233             Since the first ':input' is now a button (delete/trash): when pressing
    3234             spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
    3235             as soon as focus is moved. Explicitly target the first text field for now.
    3236             @todo change initial focus logic, also for accessibility.
    3237             */
    3238             this.$( 'input[type="text"]' ).eq( 0 ).focus();
    3239         }
    3240     },
    3241     /**
    3242      * @param {Object} event
    3243      */
    3244     deleteAttachment: function( event ) {
    3245         event.preventDefault();
    3246 
    3247         if ( window.confirm( l10n.warnDelete ) ) {
    3248             this.model.destroy();
    3249             // Keep focus inside media modal
    3250             // after image is deleted
    3251             this.controller.modal.focusManager.focus();
    3252         }
    3253     },
    3254     /**
    3255      * @param {Object} event
    3256      */
    3257     trashAttachment: function( event ) {
    3258         var library = this.controller.library;
    3259         event.preventDefault();
    3260 
    3261         if ( wp.media.view.settings.mediaTrash &&
    3262             'edit-metadata' === this.controller.content.mode() ) {
    3263 
    3264             this.model.set( 'status', 'trash' );
    3265             this.model.save().done( function() {
    3266                 library._requery( true );
    3267             } );
    3268         }  else {
    3269             this.model.destroy();
    3270         }
    3271     },
    3272     /**
    3273      * @param {Object} event
    3274      */
    3275     untrashAttachment: function( event ) {
    3276         var library = this.controller.library;
    3277         event.preventDefault();
    3278 
    3279         this.model.set( 'status', 'inherit' );
    3280         this.model.save().done( function() {
    3281             library._requery( true );
    3282         } );
    3283     },
    3284     /**
    3285      * @param {Object} event
    3286      */
    3287     editAttachment: function( event ) {
    3288         var editState = this.controller.states.get( 'edit-image' );
    3289         if ( window.imageEdit && editState ) {
    3290             event.preventDefault();
    3291 
    3292             editState.set( 'image', this.model );
    3293             this.controller.setState( 'edit-image' );
    3294         } else {
    3295             this.$el.addClass('needs-refresh');
    3296         }
    3297     },
    3298     /**
    3299      * When reverse tabbing(shift+tab) out of the right details panel, deliver
    3300      * the focus to the item in the list that was being edited.
    3301      *
    3302      * @param {Object} event
    3303      */
    3304     toggleSelectionHandler: function( event ) {
    3305         if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
    3306             this.controller.trigger( 'attachment:details:shift-tab', event );
    3307             return false;
    3308         }
    3309 
    3310         if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    3311             this.controller.trigger( 'attachment:keydown:arrow', event );
    3312             return;
    3313         }
    3314     }
    3315 });
    3316 
    3317 module.exports = Details;
    3318 
    3319 },{}],27:[function(require,module,exports){
    3320 /**
    3321  * wp.media.view.Attachment.EditLibrary
    3322  *
    3323  * @class
    3324  * @augments wp.media.view.Attachment
    3325  * @augments wp.media.View
    3326  * @augments wp.Backbone.View
    3327  * @augments Backbone.View
    3328  */
    3329 var EditLibrary = wp.media.view.Attachment.extend({
    3330     buttons: {
    3331         close: true
    3332     }
    3333 });
    3334 
    3335 module.exports = EditLibrary;
    3336 
    3337 },{}],28:[function(require,module,exports){
    3338 /**
    3339  * wp.media.view.Attachments.EditSelection
    3340  *
    3341  * @class
    3342  * @augments wp.media.view.Attachment.Selection
    3343  * @augments wp.media.view.Attachment
    3344  * @augments wp.media.View
    3345  * @augments wp.Backbone.View
    3346  * @augments Backbone.View
    3347  */
    3348 var EditSelection = wp.media.view.Attachment.Selection.extend({
    3349     buttons: {
    3350         close: true
    3351     }
    3352 });
    3353 
    3354 module.exports = EditSelection;
    3355 
    3356 },{}],29:[function(require,module,exports){
    3357 /**
    3358  * wp.media.view.Attachment.Library
    3359  *
    3360  * @class
    3361  * @augments wp.media.view.Attachment
    3362  * @augments wp.media.View
    3363  * @augments wp.Backbone.View
    3364  * @augments Backbone.View
    3365  */
    3366 var Library = wp.media.view.Attachment.extend({
    3367     buttons: {
    3368         check: true
    3369     }
    3370 });
    3371 
    3372 module.exports = Library;
    3373 
    3374 },{}],30:[function(require,module,exports){
    3375 /**
    3376  * wp.media.view.Attachment.Selection
    3377  *
    3378  * @class
    3379  * @augments wp.media.view.Attachment
    3380  * @augments wp.media.View
    3381  * @augments wp.Backbone.View
    3382  * @augments Backbone.View
    3383  */
    3384 var Selection = wp.media.view.Attachment.extend({
    3385     className: 'attachment selection',
    3386 
    3387     // On click, just select the model, instead of removing the model from
    3388     // the selection.
    3389     toggleSelection: function() {
    3390         this.options.selection.single( this.model );
    3391     }
    3392 });
    3393 
    3394 module.exports = Selection;
    3395 
    3396 },{}],31:[function(require,module,exports){
    3397 /**
    3398  * wp.media.view.Attachments
    3399  *
    3400  * @class
    3401  * @augments wp.media.View
    3402  * @augments wp.Backbone.View
    3403  * @augments Backbone.View
    3404  */
    3405 var View = wp.media.View,
    3406     $ = jQuery,
    3407     Attachments;
    3408 
    3409 Attachments = View.extend({
    3410     tagName:   'ul',
    3411     className: 'attachments',
    3412 
    3413     attributes: {
    3414         tabIndex: -1
    3415     },
    3416 
    3417     initialize: function() {
    3418         this.el.id = _.uniqueId('__attachments-view-');
    3419 
    3420         _.defaults( this.options, {
    3421             refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
    3422             refreshThreshold:   3,
    3423             AttachmentView:     wp.media.view.Attachment,
    3424             sortable:           false,
    3425             resize:             true,
    3426             idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
    3427         });
    3428 
    3429         this._viewsByCid = {};
    3430         this.$window = $( window );
    3431         this.resizeEvent = 'resize.media-modal-columns';
    3432 
    3433         this.collection.on( 'add', function( attachment ) {
    3434             this.views.add( this.createAttachmentView( attachment ), {
    3435                 at: this.collection.indexOf( attachment )
    3436             });
    3437         }, this );
    3438 
    3439         this.collection.on( 'remove', function( attachment ) {
    3440             var view = this._viewsByCid[ attachment.cid ];
    3441             delete this._viewsByCid[ attachment.cid ];
    3442 
    3443             if ( view ) {
    3444                 view.remove();
    3445             }
    3446         }, this );
    3447 
    3448         this.collection.on( 'reset', this.render, this );
    3449 
    3450         this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
    3451 
    3452         // Throttle the scroll handler and bind this.
    3453         this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
    3454 
    3455         this.options.scrollElement = this.options.scrollElement || this.el;
    3456         $( this.options.scrollElement ).on( 'scroll', this.scroll );
    3457 
    3458         this.initSortable();
    3459 
    3460         _.bindAll( this, 'setColumns' );
    3461 
    3462         if ( this.options.resize ) {
    3463             this.on( 'ready', this.bindEvents );
    3464             this.controller.on( 'open', this.setColumns );
    3465 
    3466             // Call this.setColumns() after this view has been rendered in the DOM so
    3467             // attachments get proper width applied.
    3468             _.defer( this.setColumns, this );
    3469         }
    3470     },
    3471 
    3472     bindEvents: function() {
    3473         this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
    3474     },
    3475 
    3476     attachmentFocus: function() {
    3477         this.$( 'li:first' ).focus();
    3478     },
    3479 
    3480     restoreFocus: function() {
    3481         this.$( 'li.selected:first' ).focus();
    3482     },
    3483 
    3484     arrowEvent: function( event ) {
    3485         var attachments = this.$el.children( 'li' ),
    3486             perRow = this.columns,
    3487             index = attachments.filter( ':focus' ).index(),
    3488             row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
    3489 
    3490         if ( index === -1 ) {
    3491             return;
    3492         }
    3493 
    3494         // Left arrow
    3495         if ( 37 === event.keyCode ) {
    3496             if ( 0 === index ) {
    3497                 return;
    3498             }
    3499             attachments.eq( index - 1 ).focus();
    3500         }
    3501 
    3502         // Up arrow
    3503         if ( 38 === event.keyCode ) {
    3504             if ( 1 === row ) {
    3505                 return;
    3506             }
    3507             attachments.eq( index - perRow ).focus();
    3508         }
    3509 
    3510         // Right arrow
    3511         if ( 39 === event.keyCode ) {
    3512             if ( attachments.length === index ) {
    3513                 return;
    3514             }
    3515             attachments.eq( index + 1 ).focus();
    3516         }
    3517 
    3518         // Down arrow
    3519         if ( 40 === event.keyCode ) {
    3520             if ( Math.ceil( attachments.length / perRow ) === row ) {
    3521                 return;
    3522             }
    3523             attachments.eq( index + perRow ).focus();
    3524         }
    3525     },
    3526 
    3527     dispose: function() {
    3528         this.collection.props.off( null, null, this );
    3529         if ( this.options.resize ) {
    3530             this.$window.off( this.resizeEvent );
    3531         }
    3532 
    3533         /**
    3534          * call 'dispose' directly on the parent class
    3535          */
    3536         View.prototype.dispose.apply( this, arguments );
    3537     },
    3538 
    3539     setColumns: function() {
    3540         var prev = this.columns,
    3541             width = this.$el.width();
    3542 
    3543         if ( width ) {
    3544             this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
    3545 
    3546             if ( ! prev || prev !== this.columns ) {
    3547                 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
    3548             }
    3549         }
    3550     },
    3551 
    3552     initSortable: function() {
    3553         var collection = this.collection;
    3554 
    3555         if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
    3556             return;
    3557         }
    3558 
    3559         this.$el.sortable( _.extend({
    3560             // If the `collection` has a `comparator`, disable sorting.
    3561             disabled: !! collection.comparator,
    3562 
    3563             // Change the position of the attachment as soon as the
    3564             // mouse pointer overlaps a thumbnail.
    3565             tolerance: 'pointer',
    3566 
    3567             // Record the initial `index` of the dragged model.
    3568             start: function( event, ui ) {
    3569                 ui.item.data('sortableIndexStart', ui.item.index());
    3570             },
    3571 
    3572             // Update the model's index in the collection.
    3573             // Do so silently, as the view is already accurate.
    3574             update: function( event, ui ) {
    3575                 var model = collection.at( ui.item.data('sortableIndexStart') ),
    3576                     comparator = collection.comparator;
    3577 
    3578                 // Temporarily disable the comparator to prevent `add`
    3579                 // from re-sorting.
    3580                 delete collection.comparator;
    3581 
    3582                 // Silently shift the model to its new index.
    3583                 collection.remove( model, {
    3584                     silent: true
    3585                 });
    3586                 collection.add( model, {
    3587                     silent: true,
    3588                     at:     ui.item.index()
    3589                 });
    3590 
    3591                 // Restore the comparator.
    3592                 collection.comparator = comparator;
    3593 
    3594                 // Fire the `reset` event to ensure other collections sync.
    3595                 collection.trigger( 'reset', collection );
    3596 
    3597                 // If the collection is sorted by menu order,
    3598                 // update the menu order.
    3599                 collection.saveMenuOrder();
    3600             }
    3601         }, this.options.sortable ) );
    3602 
    3603         // If the `orderby` property is changed on the `collection`,
    3604         // check to see if we have a `comparator`. If so, disable sorting.
    3605         collection.props.on( 'change:orderby', function() {
    3606             this.$el.sortable( 'option', 'disabled', !! collection.comparator );
    3607         }, this );
    3608 
    3609         this.collection.props.on( 'change:orderby', this.refreshSortable, this );
    3610         this.refreshSortable();
    3611     },
    3612 
    3613     refreshSortable: function() {
    3614         if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
    3615             return;
    3616         }
    3617 
    3618         // If the `collection` has a `comparator`, disable sorting.
    3619         var collection = this.collection,
    3620             orderby = collection.props.get('orderby'),
    3621             enabled = 'menuOrder' === orderby || ! collection.comparator;
    3622 
    3623         this.$el.sortable( 'option', 'disabled', ! enabled );
    3624     },
    3625 
    3626     /**
    3627      * @param {wp.media.model.Attachment} attachment
    3628      * @returns {wp.media.View}
    3629      */
    3630     createAttachmentView: function( attachment ) {
    3631         var view = new this.options.AttachmentView({
    3632             controller:           this.controller,
    3633             model:                attachment,
    3634             collection:           this.collection,
    3635             selection:            this.options.selection
    3636         });
    3637 
    3638         return this._viewsByCid[ attachment.cid ] = view;
    3639     },
    3640 
    3641     prepare: function() {
    3642         // Create all of the Attachment views, and replace
    3643         // the list in a single DOM operation.
    3644         if ( this.collection.length ) {
    3645             this.views.set( this.collection.map( this.createAttachmentView, this ) );
    3646 
    3647         // If there are no elements, clear the views and load some.
    3648         } else {
    3649             this.views.unset();
    3650             this.collection.more().done( this.scroll );
    3651         }
    3652     },
    3653 
    3654     ready: function() {
    3655         // Trigger the scroll event to check if we're within the
    3656         // threshold to query for additional attachments.
    3657         this.scroll();
    3658     },
    3659 
    3660     scroll: function() {
    3661         var view = this,
    3662             el = this.options.scrollElement,
    3663             scrollTop = el.scrollTop,
    3664             toolbar;
    3665 
    3666         // The scroll event occurs on the document, but the element
    3667         // that should be checked is the document body.
    3668         if ( el === document ) {
    3669             el = document.body;
    3670             scrollTop = $(document).scrollTop();
    3671         }
    3672 
    3673         if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
    3674             return;
    3675         }
    3676 
    3677         toolbar = this.views.parent.toolbar;
    3678 
    3679         // Show the spinner only if we are close to the bottom.
    3680         if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
    3681             toolbar.get('spinner').show();
    3682         }
    3683 
    3684         if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
    3685             this.collection.more().done(function() {
    3686                 view.scroll();
    3687                 toolbar.get('spinner').hide();
    3688             });
    3689         }
    3690     }
    3691 });
    3692 
    3693 module.exports = Attachments;
    3694 
    3695 },{}],32:[function(require,module,exports){
     6949module.exports = All;
     6950
     6951
     6952/***/ }),
     6953/* 78 */
     6954/***/ (function(module, exports) {
     6955
    36966956/**
    36976957 * wp.media.view.AttachmentsBrowser
     
    41377397module.exports = AttachmentsBrowser;
    41387398
    4139 },{}],33:[function(require,module,exports){
     7399
     7400/***/ }),
     7401/* 79 */
     7402/***/ (function(module, exports) {
     7403
     7404/**
     7405 * wp.media.view.Selection
     7406 *
     7407 * @class
     7408 * @augments wp.media.View
     7409 * @augments wp.Backbone.View
     7410 * @augments Backbone.View
     7411 */
     7412var l10n = wp.media.view.l10n,
     7413    Selection;
     7414
     7415Selection = wp.media.View.extend({
     7416    tagName:   'div',
     7417    className: 'media-selection',
     7418    template:  wp.template('media-selection'),
     7419
     7420    events: {
     7421        'click .edit-selection':  'edit',
     7422        'click .clear-selection': 'clear'
     7423    },
     7424
     7425    initialize: function() {
     7426        _.defaults( this.options, {
     7427            editable:  false,
     7428            clearable: true
     7429        });
     7430
     7431        /**
     7432         * @member {wp.media.view.Attachments.Selection}
     7433         */
     7434        this.attachments = new wp.media.view.Attachments.Selection({
     7435            controller: this.controller,
     7436            collection: this.collection,
     7437            selection:  this.collection,
     7438            model:      new Backbone.Model()
     7439        });
     7440
     7441        this.views.set( '.selection-view', this.attachments );
     7442        this.collection.on( 'add remove reset', this.refresh, this );
     7443        this.controller.on( 'content:activate', this.refresh, this );
     7444    },
     7445
     7446    ready: function() {
     7447        this.refresh();
     7448    },
     7449
     7450    refresh: function() {
     7451        // If the selection hasn't been rendered, bail.
     7452        if ( ! this.$el.children().length ) {
     7453            return;
     7454        }
     7455
     7456        var collection = this.collection,
     7457            editing = 'edit-selection' === this.controller.content.mode();
     7458
     7459        // If nothing is selected, display nothing.
     7460        this.$el.toggleClass( 'empty', ! collection.length );
     7461        this.$el.toggleClass( 'one', 1 === collection.length );
     7462        this.$el.toggleClass( 'editing', editing );
     7463
     7464        this.$('.count').text( l10n.selected.replace('%d', collection.length) );
     7465    },
     7466
     7467    edit: function( event ) {
     7468        event.preventDefault();
     7469        if ( this.options.editable ) {
     7470            this.options.editable.call( this, this.collection );
     7471        }
     7472    },
     7473
     7474    clear: function( event ) {
     7475        event.preventDefault();
     7476        this.collection.reset();
     7477
     7478        // Keep focus inside media modal
     7479        // after clear link is selected
     7480        this.controller.modal.focusManager.focus();
     7481    }
     7482});
     7483
     7484module.exports = Selection;
     7485
     7486
     7487/***/ }),
     7488/* 80 */
     7489/***/ (function(module, exports) {
     7490
     7491/**
     7492 * wp.media.view.Attachment.Selection
     7493 *
     7494 * @class
     7495 * @augments wp.media.view.Attachment
     7496 * @augments wp.media.View
     7497 * @augments wp.Backbone.View
     7498 * @augments Backbone.View
     7499 */
     7500var Selection = wp.media.view.Attachment.extend({
     7501    className: 'attachment selection',
     7502
     7503    // On click, just select the model, instead of removing the model from
     7504    // the selection.
     7505    toggleSelection: function() {
     7506        this.options.selection.single( this.model );
     7507    }
     7508});
     7509
     7510module.exports = Selection;
     7511
     7512
     7513/***/ }),
     7514/* 81 */
     7515/***/ (function(module, exports) {
     7516
    41407517/**
    41417518 * wp.media.view.Attachments.Selection
     
    41677544module.exports = Selection;
    41687545
    4169 },{}],34:[function(require,module,exports){
     7546
     7547/***/ }),
     7548/* 82 */
     7549/***/ (function(module, exports) {
     7550
    41707551/**
    4171  * wp.media.view.ButtonGroup
     7552 * wp.media.view.Attachments.EditSelection
    41727553 *
    41737554 * @class
     7555 * @augments wp.media.view.Attachment.Selection
     7556 * @augments wp.media.view.Attachment
    41747557 * @augments wp.media.View
    41757558 * @augments wp.Backbone.View
    41767559 * @augments Backbone.View
    41777560 */
    4178 var $ = Backbone.$,
    4179     ButtonGroup;
    4180 
    4181 ButtonGroup = wp.media.View.extend({
    4182     tagName:   'div',
    4183     className: 'button-group button-large media-button-group',
    4184 
    4185     initialize: function() {
    4186         /**
    4187          * @member {wp.media.view.Button[]}
    4188          */
    4189         this.buttons = _.map( this.options.buttons || [], function( button ) {
    4190             if ( button instanceof Backbone.View ) {
    4191                 return button;
    4192             } else {
    4193                 return new wp.media.view.Button( button ).render();
    4194             }
    4195         });
    4196 
    4197         delete this.options.buttons;
    4198 
    4199         if ( this.options.classes ) {
    4200             this.$el.addClass( this.options.classes );
    4201         }
    4202     },
    4203 
    4204     /**
    4205      * @returns {wp.media.view.ButtonGroup}
    4206      */
    4207     render: function() {
    4208         this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
    4209         return this;
     7561var EditSelection = wp.media.view.Attachment.Selection.extend({
     7562    buttons: {
     7563        close: true
    42107564    }
    42117565});
    42127566
    4213 module.exports = ButtonGroup;
    4214 
    4215 },{}],35:[function(require,module,exports){
     7567module.exports = EditSelection;
     7568
     7569
     7570/***/ }),
     7571/* 83 */
     7572/***/ (function(module, exports) {
     7573
    42167574/**
    4217  * wp.media.view.Button
    4218  *
    4219  * @class
    4220  * @augments wp.media.View
    4221  * @augments wp.Backbone.View
    4222  * @augments Backbone.View
    4223  */
    4224 var Button = wp.media.View.extend({
    4225     tagName:    'button',
    4226     className:  'media-button',
    4227     attributes: { type: 'button' },
    4228 
    4229     events: {
    4230         'click': 'click'
    4231     },
    4232 
    4233     defaults: {
    4234         text:     '',
    4235         style:    '',
    4236         size:     'large',
    4237         disabled: false
    4238     },
    4239 
    4240     initialize: function() {
    4241         /**
    4242          * Create a model with the provided `defaults`.
    4243          *
    4244          * @member {Backbone.Model}
    4245          */
    4246         this.model = new Backbone.Model( this.defaults );
    4247 
    4248         // If any of the `options` have a key from `defaults`, apply its
    4249         // value to the `model` and remove it from the `options object.
    4250         _.each( this.defaults, function( def, key ) {
    4251             var value = this.options[ key ];
    4252             if ( _.isUndefined( value ) ) {
    4253                 return;
    4254             }
    4255 
    4256             this.model.set( key, value );
    4257             delete this.options[ key ];
    4258         }, this );
    4259 
    4260         this.listenTo( this.model, 'change', this.render );
    4261     },
    4262     /**
    4263      * @returns {wp.media.view.Button} Returns itself to allow chaining
    4264      */
    4265     render: function() {
    4266         var classes = [ 'button', this.className ],
    4267             model = this.model.toJSON();
    4268 
    4269         if ( model.style ) {
    4270             classes.push( 'button-' + model.style );
    4271         }
    4272 
    4273         if ( model.size ) {
    4274             classes.push( 'button-' + model.size );
    4275         }
    4276 
    4277         classes = _.uniq( classes.concat( this.options.classes ) );
    4278         this.el.className = classes.join(' ');
    4279 
    4280         this.$el.attr( 'disabled', model.disabled );
    4281         this.$el.text( this.model.get('text') );
    4282 
    4283         return this;
    4284     },
    4285     /**
    4286      * @param {Object} event
    4287      */
    4288     click: function( event ) {
    4289         if ( '#' === this.attributes.href ) {
    4290             event.preventDefault();
    4291         }
    4292 
    4293         if ( this.options.click && ! this.model.get('disabled') ) {
    4294             this.options.click.apply( this, arguments );
    4295         }
    4296     }
    4297 });
    4298 
    4299 module.exports = Button;
    4300 
    4301 },{}],36:[function(require,module,exports){
    4302 /**
    4303  * wp.media.view.Cropper
    4304  *
    4305  * Uses the imgAreaSelect plugin to allow a user to crop an image.
    4306  *
    4307  * Takes imgAreaSelect options from
    4308  * wp.customize.HeaderControl.calculateImageSelectOptions via
    4309  * wp.customize.HeaderControl.openMM.
     7575 * wp.media.view.Settings
    43107576 *
    43117577 * @class
     
    43157581 */
    43167582var View = wp.media.View,
    4317     UploaderStatus = wp.media.view.UploaderStatus,
    4318     l10n = wp.media.view.l10n,
    4319     $ = jQuery,
    4320     Cropper;
    4321 
    4322 Cropper = View.extend({
    4323     className: 'crop-content',
    4324     template: wp.template('crop-content'),
     7583    $ = Backbone.$,
     7584    Settings;
     7585
     7586Settings = View.extend({
     7587    events: {
     7588        'click button':    'updateHandler',
     7589        'change input':    'updateHandler',
     7590        'change select':   'updateHandler',
     7591        'change textarea': 'updateHandler'
     7592    },
     7593
    43257594    initialize: function() {
    4326         _.bindAll(this, 'onImageLoad');
    4327     },
    4328     ready: function() {
    4329         this.controller.frame.on('content:error:crop', this.onError, this);
    4330         this.$image = this.$el.find('.crop-image');
    4331         this.$image.on('load', this.onImageLoad);
    4332         $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
    4333     },
    4334     remove: function() {
    4335         $(window).off('resize.cropper');
    4336         this.$el.remove();
    4337         this.$el.off();
    4338         View.prototype.remove.apply(this, arguments);
    4339     },
     7595        this.model = this.model || new Backbone.Model();
     7596        this.listenTo( this.model, 'change', this.updateChanges );
     7597    },
     7598
    43407599    prepare: function() {
    4341         return {
    4342             title: l10n.cropYourImage,
    4343             url: this.options.attachment.get('url')
    4344         };
    4345     },
    4346     onImageLoad: function() {
    4347         var imgOptions = this.controller.get('imgSelectOptions');
    4348         if (typeof imgOptions === 'function') {
    4349             imgOptions = imgOptions(this.options.attachment, this.controller);
    4350         }
    4351 
    4352         imgOptions = _.extend(imgOptions, {parent: this.$el});
    4353         this.trigger('image-loaded');
    4354         this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
    4355     },
    4356     onError: function() {
    4357         var filename = this.options.attachment.get('filename');
    4358 
    4359         this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    4360             filename: UploaderStatus.prototype.filename(filename),
    4361             message: window._wpMediaViewsL10n.cropError
    4362         }), { at: 0 });
     7600        return _.defaults({
     7601            model: this.model.toJSON()
     7602        }, this.options );
     7603    },
     7604    /**
     7605     * @returns {wp.media.view.Settings} Returns itself to allow chaining
     7606     */
     7607    render: function() {
     7608        View.prototype.render.apply( this, arguments );
     7609        // Select the correct values.
     7610        _( this.model.attributes ).chain().keys().each( this.update, this );
     7611        return this;
     7612    },
     7613    /**
     7614     * @param {string} key
     7615     */
     7616    update: function( key ) {
     7617        var value = this.model.get( key ),
     7618            $setting = this.$('[data-setting="' + key + '"]'),
     7619            $buttons, $value;
     7620
     7621        // Bail if we didn't find a matching setting.
     7622        if ( ! $setting.length ) {
     7623            return;
     7624        }
     7625
     7626        // Attempt to determine how the setting is rendered and update
     7627        // the selected value.
     7628
     7629        // Handle dropdowns.
     7630        if ( $setting.is('select') ) {
     7631            $value = $setting.find('[value="' + value + '"]');
     7632
     7633            if ( $value.length ) {
     7634                $setting.find('option').prop( 'selected', false );
     7635                $value.prop( 'selected', true );
     7636            } else {
     7637                // If we can't find the desired value, record what *is* selected.
     7638                this.model.set( key, $setting.find(':selected').val() );
     7639            }
     7640
     7641        // Handle button groups.
     7642        } else if ( $setting.hasClass('button-group') ) {
     7643            $buttons = $setting.find('button').removeClass('active');
     7644            $buttons.filter( '[value="' + value + '"]' ).addClass('active');
     7645
     7646        // Handle text inputs and textareas.
     7647        } else if ( $setting.is('input[type="text"], textarea') ) {
     7648            if ( ! $setting.is(':focus') ) {
     7649                $setting.val( value );
     7650            }
     7651        // Handle checkboxes.
     7652        } else if ( $setting.is('input[type="checkbox"]') ) {
     7653            $setting.prop( 'checked', !! value && 'false' !== value );
     7654        }
     7655    },
     7656    /**
     7657     * @param {Object} event
     7658     */
     7659    updateHandler: function( event ) {
     7660        var $setting = $( event.target ).closest('[data-setting]'),
     7661            value = event.target.value,
     7662            userSetting;
     7663
     7664        event.preventDefault();
     7665
     7666        if ( ! $setting.length ) {
     7667            return;
     7668        }
     7669
     7670        // Use the correct value for checkboxes.
     7671        if ( $setting.is('input[type="checkbox"]') ) {
     7672            value = $setting[0].checked;
     7673        }
     7674
     7675        // Update the corresponding setting.
     7676        this.model.set( $setting.data('setting'), value );
     7677
     7678        // If the setting has a corresponding user setting,
     7679        // update that as well.
     7680        if ( userSetting = $setting.data('userSetting') ) {
     7681            window.setUserSetting( userSetting, value );
     7682        }
     7683    },
     7684
     7685    updateChanges: function( model ) {
     7686        if ( model.hasChanged() ) {
     7687            _( model.changed ).chain().keys().each( this.update, this );
     7688        }
    43637689    }
    43647690});
    43657691
    4366 module.exports = Cropper;
    4367 
    4368 },{}],37:[function(require,module,exports){
     7692module.exports = Settings;
     7693
     7694
     7695/***/ }),
     7696/* 84 */
     7697/***/ (function(module, exports) {
     7698
    43697699/**
    4370  * wp.media.view.EditImage
     7700 * wp.media.view.Settings.AttachmentDisplay
     7701 *
     7702 * @class
     7703 * @augments wp.media.view.Settings
     7704 * @augments wp.media.View
     7705 * @augments wp.Backbone.View
     7706 * @augments Backbone.View
     7707 */
     7708var Settings = wp.media.view.Settings,
     7709    AttachmentDisplay;
     7710
     7711AttachmentDisplay = Settings.extend({
     7712    className: 'attachment-display-settings',
     7713    template:  wp.template('attachment-display-settings'),
     7714
     7715    initialize: function() {
     7716        var attachment = this.options.attachment;
     7717
     7718        _.defaults( this.options, {
     7719            userSettings: false
     7720        });
     7721        // Call 'initialize' directly on the parent class.
     7722        Settings.prototype.initialize.apply( this, arguments );
     7723        this.listenTo( this.model, 'change:link', this.updateLinkTo );
     7724
     7725        if ( attachment ) {
     7726            attachment.on( 'change:uploading', this.render, this );
     7727        }
     7728    },
     7729
     7730    dispose: function() {
     7731        var attachment = this.options.attachment;
     7732        if ( attachment ) {
     7733            attachment.off( null, null, this );
     7734        }
     7735        /**
     7736         * call 'dispose' directly on the parent class
     7737         */
     7738        Settings.prototype.dispose.apply( this, arguments );
     7739    },
     7740    /**
     7741     * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
     7742     */
     7743    render: function() {
     7744        var attachment = this.options.attachment;
     7745        if ( attachment ) {
     7746            _.extend( this.options, {
     7747                sizes: attachment.get('sizes'),
     7748                type:  attachment.get('type')
     7749            });
     7750        }
     7751        /**
     7752         * call 'render' directly on the parent class
     7753         */
     7754        Settings.prototype.render.call( this );
     7755        this.updateLinkTo();
     7756        return this;
     7757    },
     7758
     7759    updateLinkTo: function() {
     7760        var linkTo = this.model.get('link'),
     7761            $input = this.$('.link-to-custom'),
     7762            attachment = this.options.attachment;
     7763
     7764        if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
     7765            $input.addClass( 'hidden' );
     7766            return;
     7767        }
     7768
     7769        if ( attachment ) {
     7770            if ( 'post' === linkTo ) {
     7771                $input.val( attachment.get('link') );
     7772            } else if ( 'file' === linkTo ) {
     7773                $input.val( attachment.get('url') );
     7774            } else if ( ! this.model.get('linkUrl') ) {
     7775                $input.val('http://');
     7776            }
     7777
     7778            $input.prop( 'readonly', 'custom' !== linkTo );
     7779        }
     7780
     7781        $input.removeClass( 'hidden' );
     7782
     7783        // If the input is visible, focus and select its contents.
     7784        if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
     7785            $input.focus()[0].select();
     7786        }
     7787    }
     7788});
     7789
     7790module.exports = AttachmentDisplay;
     7791
     7792
     7793/***/ }),
     7794/* 85 */
     7795/***/ (function(module, exports) {
     7796
     7797/**
     7798 * wp.media.view.Settings.Gallery
     7799 *
     7800 * @class
     7801 * @augments wp.media.view.Settings
     7802 * @augments wp.media.View
     7803 * @augments wp.Backbone.View
     7804 * @augments Backbone.View
     7805 */
     7806var Gallery = wp.media.view.Settings.extend({
     7807    className: 'collection-settings gallery-settings',
     7808    template:  wp.template('gallery-settings')
     7809});
     7810
     7811module.exports = Gallery;
     7812
     7813
     7814/***/ }),
     7815/* 86 */
     7816/***/ (function(module, exports) {
     7817
     7818/**
     7819 * wp.media.view.Settings.Playlist
     7820 *
     7821 * @class
     7822 * @augments wp.media.view.Settings
     7823 * @augments wp.media.View
     7824 * @augments wp.Backbone.View
     7825 * @augments Backbone.View
     7826 */
     7827var Playlist = wp.media.view.Settings.extend({
     7828    className: 'collection-settings playlist-settings',
     7829    template:  wp.template('playlist-settings')
     7830});
     7831
     7832module.exports = Playlist;
     7833
     7834
     7835/***/ }),
     7836/* 87 */
     7837/***/ (function(module, exports) {
     7838
     7839/**
     7840 * wp.media.view.Attachment.Details
     7841 *
     7842 * @class
     7843 * @augments wp.media.view.Attachment
     7844 * @augments wp.media.View
     7845 * @augments wp.Backbone.View
     7846 * @augments Backbone.View
     7847 */
     7848var Attachment = wp.media.view.Attachment,
     7849    l10n = wp.media.view.l10n,
     7850    Details;
     7851
     7852Details = Attachment.extend({
     7853    tagName:   'div',
     7854    className: 'attachment-details',
     7855    template:  wp.template('attachment-details'),
     7856
     7857    attributes: function() {
     7858        return {
     7859            'tabIndex':     0,
     7860            'data-id':      this.model.get( 'id' )
     7861        };
     7862    },
     7863
     7864    events: {
     7865        'change [data-setting]':          'updateSetting',
     7866        'change [data-setting] input':    'updateSetting',
     7867        'change [data-setting] select':   'updateSetting',
     7868        'change [data-setting] textarea': 'updateSetting',
     7869        'click .delete-attachment':       'deleteAttachment',
     7870        'click .trash-attachment':        'trashAttachment',
     7871        'click .untrash-attachment':      'untrashAttachment',
     7872        'click .edit-attachment':         'editAttachment',
     7873        'keydown':                        'toggleSelectionHandler'
     7874    },
     7875
     7876    initialize: function() {
     7877        this.options = _.defaults( this.options, {
     7878            rerenderOnModelChange: false
     7879        });
     7880
     7881        this.on( 'ready', this.initialFocus );
     7882        // Call 'initialize' directly on the parent class.
     7883        Attachment.prototype.initialize.apply( this, arguments );
     7884    },
     7885
     7886    initialFocus: function() {
     7887        if ( ! wp.media.isTouchDevice ) {
     7888            /*
     7889            Previously focused the first ':input' (the readonly URL text field).
     7890            Since the first ':input' is now a button (delete/trash): when pressing
     7891            spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
     7892            as soon as focus is moved. Explicitly target the first text field for now.
     7893            @todo change initial focus logic, also for accessibility.
     7894            */
     7895            this.$( 'input[type="text"]' ).eq( 0 ).focus();
     7896        }
     7897    },
     7898    /**
     7899     * @param {Object} event
     7900     */
     7901    deleteAttachment: function( event ) {
     7902        event.preventDefault();
     7903
     7904        if ( window.confirm( l10n.warnDelete ) ) {
     7905            this.model.destroy();
     7906            // Keep focus inside media modal
     7907            // after image is deleted
     7908            this.controller.modal.focusManager.focus();
     7909        }
     7910    },
     7911    /**
     7912     * @param {Object} event
     7913     */
     7914    trashAttachment: function( event ) {
     7915        var library = this.controller.library;
     7916        event.preventDefault();
     7917
     7918        if ( wp.media.view.settings.mediaTrash &&
     7919            'edit-metadata' === this.controller.content.mode() ) {
     7920
     7921            this.model.set( 'status', 'trash' );
     7922            this.model.save().done( function() {
     7923                library._requery( true );
     7924            } );
     7925        }  else {
     7926            this.model.destroy();
     7927        }
     7928    },
     7929    /**
     7930     * @param {Object} event
     7931     */
     7932    untrashAttachment: function( event ) {
     7933        var library = this.controller.library;
     7934        event.preventDefault();
     7935
     7936        this.model.set( 'status', 'inherit' );
     7937        this.model.save().done( function() {
     7938            library._requery( true );
     7939        } );
     7940    },
     7941    /**
     7942     * @param {Object} event
     7943     */
     7944    editAttachment: function( event ) {
     7945        var editState = this.controller.states.get( 'edit-image' );
     7946        if ( window.imageEdit && editState ) {
     7947            event.preventDefault();
     7948
     7949            editState.set( 'image', this.model );
     7950            this.controller.setState( 'edit-image' );
     7951        } else {
     7952            this.$el.addClass('needs-refresh');
     7953        }
     7954    },
     7955    /**
     7956     * When reverse tabbing(shift+tab) out of the right details panel, deliver
     7957     * the focus to the item in the list that was being edited.
     7958     *
     7959     * @param {Object} event
     7960     */
     7961    toggleSelectionHandler: function( event ) {
     7962        if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
     7963            this.controller.trigger( 'attachment:details:shift-tab', event );
     7964            return false;
     7965        }
     7966
     7967        if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     7968            this.controller.trigger( 'attachment:keydown:arrow', event );
     7969            return;
     7970        }
     7971    }
     7972});
     7973
     7974module.exports = Details;
     7975
     7976
     7977/***/ }),
     7978/* 88 */
     7979/***/ (function(module, exports) {
     7980
     7981/**
     7982 * wp.media.view.AttachmentCompat
     7983 *
     7984 * A view to display fields added via the `attachment_fields_to_edit` filter.
    43717985 *
    43727986 * @class
     
    43767990 */
    43777991var View = wp.media.View,
    4378     EditImage;
    4379 
    4380 EditImage = View.extend({
    4381     className: 'image-editor',
    4382     template: wp.template('image-editor'),
    4383 
    4384     initialize: function( options ) {
    4385         this.editor = window.imageEdit;
    4386         this.controller = options.controller;
    4387         View.prototype.initialize.apply( this, arguments );
    4388     },
    4389 
    4390     prepare: function() {
    4391         return this.model.toJSON();
    4392     },
    4393 
    4394     loadEditor: function() {
    4395         var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
    4396         dfd.done( _.bind( this.focus, this ) );
    4397     },
    4398 
    4399     focus: function() {
    4400         this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
    4401     },
    4402 
    4403     back: function() {
    4404         var lastState = this.controller.lastState();
    4405         this.controller.setState( lastState );
    4406     },
    4407 
    4408     refresh: function() {
    4409         this.model.fetch();
    4410     },
    4411 
    4412     save: function() {
    4413         var lastState = this.controller.lastState();
    4414 
    4415         this.model.fetch().done( _.bind( function() {
    4416             this.controller.setState( lastState );
    4417         }, this ) );
     7992    AttachmentCompat;
     7993
     7994AttachmentCompat = View.extend({
     7995    tagName:   'form',
     7996    className: 'compat-item',
     7997
     7998    events: {
     7999        'submit':          'preventDefault',
     8000        'change input':    'save',
     8001        'change select':   'save',
     8002        'change textarea': 'save'
     8003    },
     8004
     8005    initialize: function() {
     8006        this.listenTo( this.model, 'change:compat', this.render );
     8007    },
     8008    /**
     8009     * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     8010     */
     8011    dispose: function() {
     8012        if ( this.$(':focus').length ) {
     8013            this.save();
     8014        }
     8015        /**
     8016         * call 'dispose' directly on the parent class
     8017         */
     8018        return View.prototype.dispose.apply( this, arguments );
     8019    },
     8020    /**
     8021     * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     8022     */
     8023    render: function() {
     8024        var compat = this.model.get('compat');
     8025        if ( ! compat || ! compat.item ) {
     8026            return;
     8027        }
     8028
     8029        this.views.detach();
     8030        this.$el.html( compat.item );
     8031        this.views.render();
     8032        return this;
     8033    },
     8034    /**
     8035     * @param {Object} event
     8036     */
     8037    preventDefault: function( event ) {
     8038        event.preventDefault();
     8039    },
     8040    /**
     8041     * @param {Object} event
     8042     */
     8043    save: function( event ) {
     8044        var data = {};
     8045
     8046        if ( event ) {
     8047            event.preventDefault();
     8048        }
     8049
     8050        _.each( this.$el.serializeArray(), function( pair ) {
     8051            data[ pair.name ] = pair.value;
     8052        });
     8053
     8054        this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
     8055        this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
     8056    },
     8057
     8058    postSave: function() {
     8059        this.controller.trigger( 'attachment:compat:ready', ['ready'] );
    44188060    }
    4419 
    44208061});
    44218062
    4422 module.exports = EditImage;
    4423 
    4424 },{}],38:[function(require,module,exports){
     8063module.exports = AttachmentCompat;
     8064
     8065
     8066/***/ }),
     8067/* 89 */
     8068/***/ (function(module, exports) {
     8069
     8070/**
     8071 * wp.media.view.Iframe
     8072 *
     8073 * @class
     8074 * @augments wp.media.View
     8075 * @augments wp.Backbone.View
     8076 * @augments Backbone.View
     8077 */
     8078var Iframe = wp.media.View.extend({
     8079    className: 'media-iframe',
     8080    /**
     8081     * @returns {wp.media.view.Iframe} Returns itself to allow chaining
     8082     */
     8083    render: function() {
     8084        this.views.detach();
     8085        this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
     8086        this.views.render();
     8087        return this;
     8088    }
     8089});
     8090
     8091module.exports = Iframe;
     8092
     8093
     8094/***/ }),
     8095/* 90 */
     8096/***/ (function(module, exports) {
     8097
    44258098/**
    44268099 * wp.media.view.Embed
     
    44868159module.exports = Embed;
    44878160
    4488 },{}],39:[function(require,module,exports){
     8161
     8162/***/ }),
     8163/* 91 */
     8164/***/ (function(module, exports) {
     8165
    44898166/**
    4490  * wp.media.view.EmbedImage
     8167 * wp.media.view.Label
    44918168 *
    44928169 * @class
    4493  * @augments wp.media.view.Settings.AttachmentDisplay
    4494  * @augments wp.media.view.Settings
    44958170 * @augments wp.media.View
    44968171 * @augments wp.Backbone.View
    44978172 * @augments Backbone.View
    44988173 */
    4499 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    4500     EmbedImage;
    4501 
    4502 EmbedImage = AttachmentDisplay.extend({
    4503     className: 'embed-media-settings',
    4504     template:  wp.template('embed-image-settings'),
     8174var Label = wp.media.View.extend({
     8175    tagName: 'label',
     8176    className: 'screen-reader-text',
    45058177
    45068178    initialize: function() {
    4507         /**
    4508          * Call `initialize` directly on parent class with passed arguments
    4509          */
    4510         AttachmentDisplay.prototype.initialize.apply( this, arguments );
    4511         this.listenTo( this.model, 'change:url', this.updateImage );
    4512     },
    4513 
    4514     updateImage: function() {
    4515         this.$('img').attr( 'src', this.model.get('url') );
     8179        this.value = this.options.value;
     8180    },
     8181
     8182    render: function() {
     8183        this.$el.html( this.value );
     8184
     8185        return this;
    45168186    }
    45178187});
    45188188
    4519 module.exports = EmbedImage;
    4520 
    4521 },{}],40:[function(require,module,exports){
     8189module.exports = Label;
     8190
     8191
     8192/***/ }),
     8193/* 92 */
     8194/***/ (function(module, exports) {
     8195
     8196/**
     8197 * wp.media.view.EmbedUrl
     8198 *
     8199 * @class
     8200 * @augments wp.media.View
     8201 * @augments wp.Backbone.View
     8202 * @augments Backbone.View
     8203 */
     8204var View = wp.media.View,
     8205    $ = jQuery,
     8206    EmbedUrl;
     8207
     8208EmbedUrl = View.extend({
     8209    tagName:   'label',
     8210    className: 'embed-url',
     8211
     8212    events: {
     8213        'input':  'url',
     8214        'keyup':  'url',
     8215        'change': 'url'
     8216    },
     8217
     8218    initialize: function() {
     8219        this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
     8220        this.input = this.$input[0];
     8221
     8222        this.spinner = $('<span class="spinner" />')[0];
     8223        this.$el.append([ this.input, this.spinner ]);
     8224
     8225        this.listenTo( this.model, 'change:url', this.render );
     8226
     8227        if ( this.model.get( 'url' ) ) {
     8228            _.delay( _.bind( function () {
     8229                this.model.trigger( 'change:url' );
     8230            }, this ), 500 );
     8231        }
     8232    },
     8233    /**
     8234     * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
     8235     */
     8236    render: function() {
     8237        var $input = this.$input;
     8238
     8239        if ( $input.is(':focus') ) {
     8240            return;
     8241        }
     8242
     8243        this.input.value = this.model.get('url') || 'http://';
     8244        /**
     8245         * Call `render` directly on parent class with passed arguments
     8246         */
     8247        View.prototype.render.apply( this, arguments );
     8248        return this;
     8249    },
     8250
     8251    ready: function() {
     8252        if ( ! wp.media.isTouchDevice ) {
     8253            this.focus();
     8254        }
     8255    },
     8256
     8257    url: function( event ) {
     8258        this.model.set( 'url', event.target.value );
     8259    },
     8260
     8261    /**
     8262     * If the input is visible, focus and select its contents.
     8263     */
     8264    focus: function() {
     8265        var $input = this.$input;
     8266        if ( $input.is(':visible') ) {
     8267            $input.focus()[0].select();
     8268        }
     8269    }
     8270});
     8271
     8272module.exports = EmbedUrl;
     8273
     8274
     8275/***/ }),
     8276/* 93 */
     8277/***/ (function(module, exports) {
     8278
    45228279/**
    45238280 * wp.media.view.EmbedLink
     
    46088365module.exports = EmbedLink;
    46098366
    4610 },{}],41:[function(require,module,exports){
     8367
     8368/***/ }),
     8369/* 94 */
     8370/***/ (function(module, exports) {
     8371
    46118372/**
    4612  * wp.media.view.EmbedUrl
     8373 * wp.media.view.EmbedImage
    46138374 *
    46148375 * @class
     8376 * @augments wp.media.view.Settings.AttachmentDisplay
     8377 * @augments wp.media.view.Settings
    46158378 * @augments wp.media.View
    46168379 * @augments wp.Backbone.View
    46178380 * @augments Backbone.View
    46188381 */
    4619 var View = wp.media.View,
    4620     $ = jQuery,
    4621     EmbedUrl;
    4622 
    4623 EmbedUrl = View.extend({
    4624     tagName:   'label',
    4625     className: 'embed-url',
    4626 
    4627     events: {
    4628         'input':  'url',
    4629         'keyup':  'url',
    4630         'change': 'url'
    4631     },
     8382var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     8383    EmbedImage;
     8384
     8385EmbedImage = AttachmentDisplay.extend({
     8386    className: 'embed-media-settings',
     8387    template:  wp.template('embed-image-settings'),
    46328388
    46338389    initialize: function() {
    4634         this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
    4635         this.input = this.$input[0];
    4636 
    4637         this.spinner = $('<span class="spinner" />')[0];
    4638         this.$el.append([ this.input, this.spinner ]);
    4639 
    4640         this.listenTo( this.model, 'change:url', this.render );
    4641 
    4642         if ( this.model.get( 'url' ) ) {
    4643             _.delay( _.bind( function () {
    4644                 this.model.trigger( 'change:url' );
    4645             }, this ), 500 );
    4646         }
    4647     },
    4648     /**
    4649      * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
    4650      */
    4651     render: function() {
    4652         var $input = this.$input;
    4653 
    4654         if ( $input.is(':focus') ) {
    4655             return;
    4656         }
    4657 
    4658         this.input.value = this.model.get('url') || 'http://';
    46598390        /**
    4660          * Call `render` directly on parent class with passed arguments
     8391         * Call `initialize` directly on parent class with passed arguments
    46618392         */
    4662         View.prototype.render.apply( this, arguments );
    4663         return this;
    4664     },
    4665 
    4666     ready: function() {
    4667         if ( ! wp.media.isTouchDevice ) {
    4668             this.focus();
    4669         }
    4670     },
    4671 
    4672     url: function( event ) {
    4673         this.model.set( 'url', event.target.value );
    4674     },
    4675 
    4676     /**
    4677      * If the input is visible, focus and select its contents.
    4678      */
    4679     focus: function() {
    4680         var $input = this.$input;
    4681         if ( $input.is(':visible') ) {
    4682             $input.focus()[0].select();
    4683         }
     8393        AttachmentDisplay.prototype.initialize.apply( this, arguments );
     8394        this.listenTo( this.model, 'change:url', this.updateImage );
     8395    },
     8396
     8397    updateImage: function() {
     8398        this.$('img').attr( 'src', this.model.get('url') );
    46848399    }
    46858400});
    46868401
    4687 module.exports = EmbedUrl;
    4688 
    4689 },{}],42:[function(require,module,exports){
    4690 /**
    4691  * wp.media.view.FocusManager
    4692  *
    4693  * @class
    4694  * @augments wp.media.View
    4695  * @augments wp.Backbone.View
    4696  * @augments Backbone.View
    4697  */
    4698 var FocusManager = wp.media.View.extend({
    4699 
    4700     events: {
    4701         'keydown': 'constrainTabbing'
    4702     },
    4703 
    4704     focus: function() { // Reset focus on first left menu item
    4705         this.$('.media-menu-item').first().focus();
    4706     },
    4707     /**
    4708      * @param {Object} event
    4709      */
    4710     constrainTabbing: function( event ) {
    4711         var tabbables;
    4712 
    4713         // Look for the tab key.
    4714         if ( 9 !== event.keyCode ) {
    4715             return;
    4716         }
    4717 
    4718         // Skip the file input added by Plupload.
    4719         tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
    4720 
    4721         // Keep tab focus within media modal while it's open
    4722         if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
    4723             tabbables.first().focus();
    4724             return false;
    4725         } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
    4726             tabbables.last().focus();
    4727             return false;
    4728         }
    4729     }
    4730 
    4731 });
    4732 
    4733 module.exports = FocusManager;
    4734 
    4735 },{}],43:[function(require,module,exports){
    4736 /**
    4737  * wp.media.view.Frame
    4738  *
    4739  * A frame is a composite view consisting of one or more regions and one or more
    4740  * states.
    4741  *
    4742  * @see wp.media.controller.State
    4743  * @see wp.media.controller.Region
    4744  *
    4745  * @class
    4746  * @augments wp.media.View
    4747  * @augments wp.Backbone.View
    4748  * @augments Backbone.View
    4749  * @mixes wp.media.controller.StateMachine
    4750  */
    4751 var Frame = wp.media.View.extend({
    4752     initialize: function() {
    4753         _.defaults( this.options, {
    4754             mode: [ 'select' ]
    4755         });
    4756         this._createRegions();
    4757         this._createStates();
    4758         this._createModes();
    4759     },
    4760 
    4761     _createRegions: function() {
    4762         // Clone the regions array.
    4763         this.regions = this.regions ? this.regions.slice() : [];
    4764 
    4765         // Initialize regions.
    4766         _.each( this.regions, function( region ) {
    4767             this[ region ] = new wp.media.controller.Region({
    4768                 view:     this,
    4769                 id:       region,
    4770                 selector: '.media-frame-' + region
    4771             });
    4772         }, this );
    4773     },
    4774     /**
    4775      * Create the frame's states.
    4776      *
    4777      * @see wp.media.controller.State
    4778      * @see wp.media.controller.StateMachine
    4779      *
    4780      * @fires wp.media.controller.State#ready
    4781      */
    4782     _createStates: function() {
    4783         // Create the default `states` collection.
    4784         this.states = new Backbone.Collection( null, {
    4785             model: wp.media.controller.State
    4786         });
    4787 
    4788         // Ensure states have a reference to the frame.
    4789         this.states.on( 'add', function( model ) {
    4790             model.frame = this;
    4791             model.trigger('ready');
    4792         }, this );
    4793 
    4794         if ( this.options.states ) {
    4795             this.states.add( this.options.states );
    4796         }
    4797     },
    4798 
    4799     /**
    4800      * A frame can be in a mode or multiple modes at one time.
    4801      *
    4802      * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
    4803      */
    4804     _createModes: function() {
    4805         // Store active "modes" that the frame is in. Unrelated to region modes.
    4806         this.activeModes = new Backbone.Collection();
    4807         this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
    4808 
    4809         _.each( this.options.mode, function( mode ) {
    4810             this.activateMode( mode );
    4811         }, this );
    4812     },
    4813     /**
    4814      * Reset all states on the frame to their defaults.
    4815      *
    4816      * @returns {wp.media.view.Frame} Returns itself to allow chaining
    4817      */
    4818     reset: function() {
    4819         this.states.invoke( 'trigger', 'reset' );
    4820         return this;
    4821     },
    4822     /**
    4823      * Map activeMode collection events to the frame.
    4824      */
    4825     triggerModeEvents: function( model, collection, options ) {
    4826         var collectionEvent,
    4827             modeEventMap = {
    4828                 add: 'activate',
    4829                 remove: 'deactivate'
    4830             },
    4831             eventToTrigger;
    4832         // Probably a better way to do this.
    4833         _.each( options, function( value, key ) {
    4834             if ( value ) {
    4835                 collectionEvent = key;
    4836             }
    4837         } );
    4838 
    4839         if ( ! _.has( modeEventMap, collectionEvent ) ) {
    4840             return;
    4841         }
    4842 
    4843         eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
    4844         this.trigger( eventToTrigger );
    4845     },
    4846     /**
    4847      * Activate a mode on the frame.
    4848      *
    4849      * @param string mode Mode ID.
    4850      * @returns {this} Returns itself to allow chaining.
    4851      */
    4852     activateMode: function( mode ) {
    4853         // Bail if the mode is already active.
    4854         if ( this.isModeActive( mode ) ) {
    4855             return;
    4856         }
    4857         this.activeModes.add( [ { id: mode } ] );
    4858         // Add a CSS class to the frame so elements can be styled for the mode.
    4859         this.$el.addClass( 'mode-' + mode );
    4860 
    4861         return this;
    4862     },
    4863     /**
    4864      * Deactivate a mode on the frame.
    4865      *
    4866      * @param string mode Mode ID.
    4867      * @returns {this} Returns itself to allow chaining.
    4868      */
    4869     deactivateMode: function( mode ) {
    4870         // Bail if the mode isn't active.
    4871         if ( ! this.isModeActive( mode ) ) {
    4872             return this;
    4873         }
    4874         this.activeModes.remove( this.activeModes.where( { id: mode } ) );
    4875         this.$el.removeClass( 'mode-' + mode );
    4876         /**
    4877          * Frame mode deactivation event.
    4878          *
    4879          * @event this#{mode}:deactivate
    4880          */
    4881         this.trigger( mode + ':deactivate' );
    4882 
    4883         return this;
    4884     },
    4885     /**
    4886      * Check if a mode is enabled on the frame.
    4887      *
    4888      * @param  string mode Mode ID.
    4889      * @return bool
    4890      */
    4891     isModeActive: function( mode ) {
    4892         return Boolean( this.activeModes.where( { id: mode } ).length );
    4893     }
    4894 });
    4895 
    4896 // Make the `Frame` a `StateMachine`.
    4897 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
    4898 
    4899 module.exports = Frame;
    4900 
    4901 },{}],44:[function(require,module,exports){
    4902 /**
    4903  * wp.media.view.MediaFrame.ImageDetails
    4904  *
    4905  * A media frame for manipulating an image that's already been inserted
    4906  * into a post.
    4907  *
    4908  * @class
    4909  * @augments wp.media.view.MediaFrame.Select
    4910  * @augments wp.media.view.MediaFrame
    4911  * @augments wp.media.view.Frame
    4912  * @augments wp.media.View
    4913  * @augments wp.Backbone.View
    4914  * @augments Backbone.View
    4915  * @mixes wp.media.controller.StateMachine
    4916  */
    4917 var Select = wp.media.view.MediaFrame.Select,
    4918     l10n = wp.media.view.l10n,
    4919     ImageDetails;
    4920 
    4921 ImageDetails = Select.extend({
    4922     defaults: {
    4923         id:      'image',
    4924         url:     '',
    4925         menu:    'image-details',
    4926         content: 'image-details',
    4927         toolbar: 'image-details',
    4928         type:    'link',
    4929         title:    l10n.imageDetailsTitle,
    4930         priority: 120
    4931     },
    4932 
    4933     initialize: function( options ) {
    4934         this.image = new wp.media.model.PostImage( options.metadata );
    4935         this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
    4936         Select.prototype.initialize.apply( this, arguments );
    4937     },
    4938 
    4939     bindHandlers: function() {
    4940         Select.prototype.bindHandlers.apply( this, arguments );
    4941         this.on( 'menu:create:image-details', this.createMenu, this );
    4942         this.on( 'content:create:image-details', this.imageDetailsContent, this );
    4943         this.on( 'content:render:edit-image', this.editImageContent, this );
    4944         this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
    4945         // override the select toolbar
    4946         this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
    4947     },
    4948 
    4949     createStates: function() {
    4950         this.states.add([
    4951             new wp.media.controller.ImageDetails({
    4952                 image: this.image,
    4953                 editable: false
    4954             }),
    4955             new wp.media.controller.ReplaceImage({
    4956                 id: 'replace-image',
    4957                 library: wp.media.query( { type: 'image' } ),
    4958                 image: this.image,
    4959                 multiple:  false,
    4960                 title:     l10n.imageReplaceTitle,
    4961                 toolbar: 'replace',
    4962                 priority:  80,
    4963                 displaySettings: true
    4964             }),
    4965             new wp.media.controller.EditImage( {
    4966                 image: this.image,
    4967                 selection: this.options.selection
    4968             } )
    4969         ]);
    4970     },
    4971 
    4972     imageDetailsContent: function( options ) {
    4973         options.view = new wp.media.view.ImageDetails({
    4974             controller: this,
    4975             model: this.state().image,
    4976             attachment: this.state().image.attachment
    4977         });
    4978     },
    4979 
    4980     editImageContent: function() {
    4981         var state = this.state(),
    4982             model = state.get('image'),
    4983             view;
    4984 
    4985         if ( ! model ) {
    4986             return;
    4987         }
    4988 
    4989         view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
    4990 
    4991         this.content.set( view );
    4992 
    4993         // after bringing in the frame, load the actual editor via an ajax call
    4994         view.loadEditor();
    4995 
    4996     },
    4997 
    4998     renderImageDetailsToolbar: function() {
    4999         this.toolbar.set( new wp.media.view.Toolbar({
    5000             controller: this,
    5001             items: {
    5002                 select: {
    5003                     style:    'primary',
    5004                     text:     l10n.update,
    5005                     priority: 80,
    5006 
    5007                     click: function() {
    5008                         var controller = this.controller,
    5009                             state = controller.state();
    5010 
    5011                         controller.close();
    5012 
    5013                         // not sure if we want to use wp.media.string.image which will create a shortcode or
    5014                         // perhaps wp.html.string to at least to build the <img />
    5015                         state.trigger( 'update', controller.image.toJSON() );
    5016 
    5017                         // Restore and reset the default state.
    5018                         controller.setState( controller.options.state );
    5019                         controller.reset();
    5020                     }
    5021                 }
    5022             }
    5023         }) );
    5024     },
    5025 
    5026     renderReplaceImageToolbar: function() {
    5027         var frame = this,
    5028             lastState = frame.lastState(),
    5029             previous = lastState && lastState.id;
    5030 
    5031         this.toolbar.set( new wp.media.view.Toolbar({
    5032             controller: this,
    5033             items: {
    5034                 back: {
    5035                     text:     l10n.back,
    5036                     priority: 20,
    5037                     click:    function() {
    5038                         if ( previous ) {
    5039                             frame.setState( previous );
    5040                         } else {
    5041                             frame.close();
    5042                         }
    5043                     }
    5044                 },
    5045 
    5046                 replace: {
    5047                     style:    'primary',
    5048                     text:     l10n.replace,
    5049                     priority: 80,
    5050 
    5051                     click: function() {
    5052                         var controller = this.controller,
    5053                             state = controller.state(),
    5054                             selection = state.get( 'selection' ),
    5055                             attachment = selection.single();
    5056 
    5057                         controller.close();
    5058 
    5059                         controller.image.changeAttachment( attachment, state.display( attachment ) );
    5060 
    5061                         // not sure if we want to use wp.media.string.image which will create a shortcode or
    5062                         // perhaps wp.html.string to at least to build the <img />
    5063                         state.trigger( 'replace', controller.image.toJSON() );
    5064 
    5065                         // Restore and reset the default state.
    5066                         controller.setState( controller.options.state );
    5067                         controller.reset();
    5068                     }
    5069                 }
    5070             }
    5071         }) );
    5072     }
    5073 
    5074 });
    5075 
    5076 module.exports = ImageDetails;
    5077 
    5078 },{}],45:[function(require,module,exports){
    5079 /**
    5080  * wp.media.view.MediaFrame.Post
    5081  *
    5082  * The frame for manipulating media on the Edit Post page.
    5083  *
    5084  * @class
    5085  * @augments wp.media.view.MediaFrame.Select
    5086  * @augments wp.media.view.MediaFrame
    5087  * @augments wp.media.view.Frame
    5088  * @augments wp.media.View
    5089  * @augments wp.Backbone.View
    5090  * @augments Backbone.View
    5091  * @mixes wp.media.controller.StateMachine
    5092  */
    5093 var Select = wp.media.view.MediaFrame.Select,
    5094     Library = wp.media.controller.Library,
    5095     l10n = wp.media.view.l10n,
    5096     Post;
    5097 
    5098 Post = Select.extend({
    5099     initialize: function() {
    5100         this.counts = {
    5101             audio: {
    5102                 count: wp.media.view.settings.attachmentCounts.audio,
    5103                 state: 'playlist'
    5104             },
    5105             video: {
    5106                 count: wp.media.view.settings.attachmentCounts.video,
    5107                 state: 'video-playlist'
    5108             }
    5109         };
    5110 
    5111         _.defaults( this.options, {
    5112             multiple:  true,
    5113             editing:   false,
    5114             state:    'insert',
    5115             metadata:  {}
    5116         });
    5117 
    5118         // Call 'initialize' directly on the parent class.
    5119         Select.prototype.initialize.apply( this, arguments );
    5120         this.createIframeStates();
    5121 
    5122     },
    5123 
    5124     /**
    5125      * Create the default states.
    5126      */
    5127     createStates: function() {
    5128         var options = this.options;
    5129 
    5130         this.states.add([
    5131             // Main states.
    5132             new Library({
    5133                 id:         'insert',
    5134                 title:      l10n.insertMediaTitle,
    5135                 priority:   20,
    5136                 toolbar:    'main-insert',
    5137                 filterable: 'all',
    5138                 library:    wp.media.query( options.library ),
    5139                 multiple:   options.multiple ? 'reset' : false,
    5140                 editable:   true,
    5141 
    5142                 // If the user isn't allowed to edit fields,
    5143                 // can they still edit it locally?
    5144                 allowLocalEdits: true,
    5145 
    5146                 // Show the attachment display settings.
    5147                 displaySettings: true,
    5148                 // Update user settings when users adjust the
    5149                 // attachment display settings.
    5150                 displayUserSettings: true
    5151             }),
    5152 
    5153             new Library({
    5154                 id:         'gallery',
    5155                 title:      l10n.createGalleryTitle,
    5156                 priority:   40,
    5157                 toolbar:    'main-gallery',
    5158                 filterable: 'uploaded',
    5159                 multiple:   'add',
    5160                 editable:   false,
    5161 
    5162                 library:  wp.media.query( _.defaults({
    5163                     type: 'image'
    5164                 }, options.library ) )
    5165             }),
    5166 
    5167             // Embed states.
    5168             new wp.media.controller.Embed( { metadata: options.metadata } ),
    5169 
    5170             new wp.media.controller.EditImage( { model: options.editImage } ),
    5171 
    5172             // Gallery states.
    5173             new wp.media.controller.GalleryEdit({
    5174                 library: options.selection,
    5175                 editing: options.editing,
    5176                 menu:    'gallery'
    5177             }),
    5178 
    5179             new wp.media.controller.GalleryAdd(),
    5180 
    5181             new Library({
    5182                 id:         'playlist',
    5183                 title:      l10n.createPlaylistTitle,
    5184                 priority:   60,
    5185                 toolbar:    'main-playlist',
    5186                 filterable: 'uploaded',
    5187                 multiple:   'add',
    5188                 editable:   false,
    5189 
    5190                 library:  wp.media.query( _.defaults({
    5191                     type: 'audio'
    5192                 }, options.library ) )
    5193             }),
    5194 
    5195             // Playlist states.
    5196             new wp.media.controller.CollectionEdit({
    5197                 type: 'audio',
    5198                 collectionType: 'playlist',
    5199                 title:          l10n.editPlaylistTitle,
    5200                 SettingsView:   wp.media.view.Settings.Playlist,
    5201                 library:        options.selection,
    5202                 editing:        options.editing,
    5203                 menu:           'playlist',
    5204                 dragInfoText:   l10n.playlistDragInfo,
    5205                 dragInfo:       false
    5206             }),
    5207 
    5208             new wp.media.controller.CollectionAdd({
    5209                 type: 'audio',
    5210                 collectionType: 'playlist',
    5211                 title: l10n.addToPlaylistTitle
    5212             }),
    5213 
    5214             new Library({
    5215                 id:         'video-playlist',
    5216                 title:      l10n.createVideoPlaylistTitle,
    5217                 priority:   60,
    5218                 toolbar:    'main-video-playlist',
    5219                 filterable: 'uploaded',
    5220                 multiple:   'add',
    5221                 editable:   false,
    5222 
    5223                 library:  wp.media.query( _.defaults({
    5224                     type: 'video'
    5225                 }, options.library ) )
    5226             }),
    5227 
    5228             new wp.media.controller.CollectionEdit({
    5229                 type: 'video',
    5230                 collectionType: 'playlist',
    5231                 title:          l10n.editVideoPlaylistTitle,
    5232                 SettingsView:   wp.media.view.Settings.Playlist,
    5233                 library:        options.selection,
    5234                 editing:        options.editing,
    5235                 menu:           'video-playlist',
    5236                 dragInfoText:   l10n.videoPlaylistDragInfo,
    5237                 dragInfo:       false
    5238             }),
    5239 
    5240             new wp.media.controller.CollectionAdd({
    5241                 type: 'video',
    5242                 collectionType: 'playlist',
    5243                 title: l10n.addToVideoPlaylistTitle
    5244             })
    5245         ]);
    5246 
    5247         if ( wp.media.view.settings.post.featuredImageId ) {
    5248             this.states.add( new wp.media.controller.FeaturedImage() );
    5249         }
    5250     },
    5251 
    5252     bindHandlers: function() {
    5253         var handlers, checkCounts;
    5254 
    5255         Select.prototype.bindHandlers.apply( this, arguments );
    5256 
    5257         this.on( 'activate', this.activate, this );
    5258 
    5259         // Only bother checking media type counts if one of the counts is zero
    5260         checkCounts = _.find( this.counts, function( type ) {
    5261             return type.count === 0;
    5262         } );
    5263 
    5264         if ( typeof checkCounts !== 'undefined' ) {
    5265             this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
    5266         }
    5267 
    5268         this.on( 'menu:create:gallery', this.createMenu, this );
    5269         this.on( 'menu:create:playlist', this.createMenu, this );
    5270         this.on( 'menu:create:video-playlist', this.createMenu, this );
    5271         this.on( 'toolbar:create:main-insert', this.createToolbar, this );
    5272         this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
    5273         this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
    5274         this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
    5275         this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
    5276         this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
    5277 
    5278         handlers = {
    5279             menu: {
    5280                 'default': 'mainMenu',
    5281                 'gallery': 'galleryMenu',
    5282                 'playlist': 'playlistMenu',
    5283                 'video-playlist': 'videoPlaylistMenu'
    5284             },
    5285 
    5286             content: {
    5287                 'embed':          'embedContent',
    5288                 'edit-image':     'editImageContent',
    5289                 'edit-selection': 'editSelectionContent'
    5290             },
    5291 
    5292             toolbar: {
    5293                 'main-insert':      'mainInsertToolbar',
    5294                 'main-gallery':     'mainGalleryToolbar',
    5295                 'gallery-edit':     'galleryEditToolbar',
    5296                 'gallery-add':      'galleryAddToolbar',
    5297                 'main-playlist':    'mainPlaylistToolbar',
    5298                 'playlist-edit':    'playlistEditToolbar',
    5299                 'playlist-add':     'playlistAddToolbar',
    5300                 'main-video-playlist': 'mainVideoPlaylistToolbar',
    5301                 'video-playlist-edit': 'videoPlaylistEditToolbar',
    5302                 'video-playlist-add': 'videoPlaylistAddToolbar'
    5303             }
    5304         };
    5305 
    5306         _.each( handlers, function( regionHandlers, region ) {
    5307             _.each( regionHandlers, function( callback, handler ) {
    5308                 this.on( region + ':render:' + handler, this[ callback ], this );
    5309             }, this );
    5310         }, this );
    5311     },
    5312 
    5313     activate: function() {
    5314         // Hide menu items for states tied to particular media types if there are no items
    5315         _.each( this.counts, function( type ) {
    5316             if ( type.count < 1 ) {
    5317                 this.menuItemVisibility( type.state, 'hide' );
    5318             }
    5319         }, this );
    5320     },
    5321 
    5322     mediaTypeCounts: function( model, attr ) {
    5323         if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
    5324             this.counts[ attr ].count++;
    5325             this.menuItemVisibility( this.counts[ attr ].state, 'show' );
    5326         }
    5327     },
    5328 
    5329     // Menus
    5330     /**
    5331      * @param {wp.Backbone.View} view
    5332      */
    5333     mainMenu: function( view ) {
    5334         view.set({
    5335             'library-separator': new wp.media.View({
    5336                 className: 'separator',
    5337                 priority: 100
    5338             })
    5339         });
    5340     },
    5341 
    5342     menuItemVisibility: function( state, visibility ) {
    5343         var menu = this.menu.get();
    5344         if ( visibility === 'hide' ) {
    5345             menu.hide( state );
    5346         } else if ( visibility === 'show' ) {
    5347             menu.show( state );
    5348         }
    5349     },
    5350     /**
    5351      * @param {wp.Backbone.View} view
    5352      */
    5353     galleryMenu: function( view ) {
    5354         var lastState = this.lastState(),
    5355             previous = lastState && lastState.id,
    5356             frame = this;
    5357 
    5358         view.set({
    5359             cancel: {
    5360                 text:     l10n.cancelGalleryTitle,
    5361                 priority: 20,
    5362                 click:    function() {
    5363                     if ( previous ) {
    5364                         frame.setState( previous );
    5365                     } else {
    5366                         frame.close();
    5367                     }
    5368 
    5369                     // Keep focus inside media modal
    5370                     // after canceling a gallery
    5371                     this.controller.modal.focusManager.focus();
    5372                 }
    5373             },
    5374             separateCancel: new wp.media.View({
    5375                 className: 'separator',
    5376                 priority: 40
    5377             })
    5378         });
    5379     },
    5380 
    5381     playlistMenu: function( view ) {
    5382         var lastState = this.lastState(),
    5383             previous = lastState && lastState.id,
    5384             frame = this;
    5385 
    5386         view.set({
    5387             cancel: {
    5388                 text:     l10n.cancelPlaylistTitle,
    5389                 priority: 20,
    5390                 click:    function() {
    5391                     if ( previous ) {
    5392                         frame.setState( previous );
    5393                     } else {
    5394                         frame.close();
    5395                     }
    5396                 }
    5397             },
    5398             separateCancel: new wp.media.View({
    5399                 className: 'separator',
    5400                 priority: 40
    5401             })
    5402         });
    5403     },
    5404 
    5405     videoPlaylistMenu: function( view ) {
    5406         var lastState = this.lastState(),
    5407             previous = lastState && lastState.id,
    5408             frame = this;
    5409 
    5410         view.set({
    5411             cancel: {
    5412                 text:     l10n.cancelVideoPlaylistTitle,
    5413                 priority: 20,
    5414                 click:    function() {
    5415                     if ( previous ) {
    5416                         frame.setState( previous );
    5417                     } else {
    5418                         frame.close();
    5419                     }
    5420                 }
    5421             },
    5422             separateCancel: new wp.media.View({
    5423                 className: 'separator',
    5424                 priority: 40
    5425             })
    5426         });
    5427     },
    5428 
    5429     // Content
    5430     embedContent: function() {
    5431         var view = new wp.media.view.Embed({
    5432             controller: this,
    5433             model:      this.state()
    5434         }).render();
    5435 
    5436         this.content.set( view );
    5437 
    5438         if ( ! wp.media.isTouchDevice ) {
    5439             view.url.focus();
    5440         }
    5441     },
    5442 
    5443     editSelectionContent: function() {
    5444         var state = this.state(),
    5445             selection = state.get('selection'),
    5446             view;
    5447 
    5448         view = new wp.media.view.AttachmentsBrowser({
    5449             controller: this,
    5450             collection: selection,
    5451             selection:  selection,
    5452             model:      state,
    5453             sortable:   true,
    5454             search:     false,
    5455             date:       false,
    5456             dragInfo:   true,
    5457 
    5458             AttachmentView: wp.media.view.Attachments.EditSelection
    5459         }).render();
    5460 
    5461         view.toolbar.set( 'backToLibrary', {
    5462             text:     l10n.returnToLibrary,
    5463             priority: -100,
    5464 
    5465             click: function() {
    5466                 this.controller.content.mode('browse');
    5467             }
    5468         });
    5469 
    5470         // Browse our library of attachments.
    5471         this.content.set( view );
    5472 
    5473         // Trigger the controller to set focus
    5474         this.trigger( 'edit:selection', this );
    5475     },
    5476 
    5477     editImageContent: function() {
    5478         var image = this.state().get('image'),
    5479             view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
    5480 
    5481         this.content.set( view );
    5482 
    5483         // after creating the wrapper view, load the actual editor via an ajax call
    5484         view.loadEditor();
    5485 
    5486     },
    5487 
    5488     // Toolbars
    5489 
    5490     /**
    5491      * @param {wp.Backbone.View} view
    5492      */
    5493     selectionStatusToolbar: function( view ) {
    5494         var editable = this.state().get('editable');
    5495 
    5496         view.set( 'selection', new wp.media.view.Selection({
    5497             controller: this,
    5498             collection: this.state().get('selection'),
    5499             priority:   -40,
    5500 
    5501             // If the selection is editable, pass the callback to
    5502             // switch the content mode.
    5503             editable: editable && function() {
    5504                 this.controller.content.mode('edit-selection');
    5505             }
    5506         }).render() );
    5507     },
    5508 
    5509     /**
    5510      * @param {wp.Backbone.View} view
    5511      */
    5512     mainInsertToolbar: function( view ) {
    5513         var controller = this;
    5514 
    5515         this.selectionStatusToolbar( view );
    5516 
    5517         view.set( 'insert', {
    5518             style:    'primary',
    5519             priority: 80,
    5520             text:     l10n.insertIntoPost,
    5521             requires: { selection: true },
    5522 
    5523             /**
    5524              * @fires wp.media.controller.State#insert
    5525              */
    5526             click: function() {
    5527                 var state = controller.state(),
    5528                     selection = state.get('selection');
    5529 
    5530                 controller.close();
    5531                 state.trigger( 'insert', selection ).reset();
    5532             }
    5533         });
    5534     },
    5535 
    5536     /**
    5537      * @param {wp.Backbone.View} view
    5538      */
    5539     mainGalleryToolbar: function( view ) {
    5540         var controller = this;
    5541 
    5542         this.selectionStatusToolbar( view );
    5543 
    5544         view.set( 'gallery', {
    5545             style:    'primary',
    5546             text:     l10n.createNewGallery,
    5547             priority: 60,
    5548             requires: { selection: true },
    5549 
    5550             click: function() {
    5551                 var selection = controller.state().get('selection'),
    5552                     edit = controller.state('gallery-edit'),
    5553                     models = selection.where({ type: 'image' });
    5554 
    5555                 edit.set( 'library', new wp.media.model.Selection( models, {
    5556                     props:    selection.props.toJSON(),
    5557                     multiple: true
    5558                 }) );
    5559 
    5560                 this.controller.setState('gallery-edit');
    5561 
    5562                 // Keep focus inside media modal
    5563                 // after jumping to gallery view
    5564                 this.controller.modal.focusManager.focus();
    5565             }
    5566         });
    5567     },
    5568 
    5569     mainPlaylistToolbar: function( view ) {
    5570         var controller = this;
    5571 
    5572         this.selectionStatusToolbar( view );
    5573 
    5574         view.set( 'playlist', {
    5575             style:    'primary',
    5576             text:     l10n.createNewPlaylist,
    5577             priority: 100,
    5578             requires: { selection: true },
    5579 
    5580             click: function() {
    5581                 var selection = controller.state().get('selection'),
    5582                     edit = controller.state('playlist-edit'),
    5583                     models = selection.where({ type: 'audio' });
    5584 
    5585                 edit.set( 'library', new wp.media.model.Selection( models, {
    5586                     props:    selection.props.toJSON(),
    5587                     multiple: true
    5588                 }) );
    5589 
    5590                 this.controller.setState('playlist-edit');
    5591 
    5592                 // Keep focus inside media modal
    5593                 // after jumping to playlist view
    5594                 this.controller.modal.focusManager.focus();
    5595             }
    5596         });
    5597     },
    5598 
    5599     mainVideoPlaylistToolbar: function( view ) {
    5600         var controller = this;
    5601 
    5602         this.selectionStatusToolbar( view );
    5603 
    5604         view.set( 'video-playlist', {
    5605             style:    'primary',
    5606             text:     l10n.createNewVideoPlaylist,
    5607             priority: 100,
    5608             requires: { selection: true },
    5609 
    5610             click: function() {
    5611                 var selection = controller.state().get('selection'),
    5612                     edit = controller.state('video-playlist-edit'),
    5613                     models = selection.where({ type: 'video' });
    5614 
    5615                 edit.set( 'library', new wp.media.model.Selection( models, {
    5616                     props:    selection.props.toJSON(),
    5617                     multiple: true
    5618                 }) );
    5619 
    5620                 this.controller.setState('video-playlist-edit');
    5621 
    5622                 // Keep focus inside media modal
    5623                 // after jumping to video playlist view
    5624                 this.controller.modal.focusManager.focus();
    5625             }
    5626         });
    5627     },
    5628 
    5629     featuredImageToolbar: function( toolbar ) {
    5630         this.createSelectToolbar( toolbar, {
    5631             text:  l10n.setFeaturedImage,
    5632             state: this.options.state
    5633         });
    5634     },
    5635 
    5636     mainEmbedToolbar: function( toolbar ) {
    5637         toolbar.view = new wp.media.view.Toolbar.Embed({
    5638             controller: this
    5639         });
    5640     },
    5641 
    5642     galleryEditToolbar: function() {
    5643         var editing = this.state().get('editing');
    5644         this.toolbar.set( new wp.media.view.Toolbar({
    5645             controller: this,
    5646             items: {
    5647                 insert: {
    5648                     style:    'primary',
    5649                     text:     editing ? l10n.updateGallery : l10n.insertGallery,
    5650                     priority: 80,
    5651                     requires: { library: true },
    5652 
    5653                     /**
    5654                      * @fires wp.media.controller.State#update
    5655                      */
    5656                     click: function() {
    5657                         var controller = this.controller,
    5658                             state = controller.state();
    5659 
    5660                         controller.close();
    5661                         state.trigger( 'update', state.get('library') );
    5662 
    5663                         // Restore and reset the default state.
    5664                         controller.setState( controller.options.state );
    5665                         controller.reset();
    5666                     }
    5667                 }
    5668             }
    5669         }) );
    5670     },
    5671 
    5672     galleryAddToolbar: function() {
    5673         this.toolbar.set( new wp.media.view.Toolbar({
    5674             controller: this,
    5675             items: {
    5676                 insert: {
    5677                     style:    'primary',
    5678                     text:     l10n.addToGallery,
    5679                     priority: 80,
    5680                     requires: { selection: true },
    5681 
    5682                     /**
    5683                      * @fires wp.media.controller.State#reset
    5684                      */
    5685                     click: function() {
    5686                         var controller = this.controller,
    5687                             state = controller.state(),
    5688                             edit = controller.state('gallery-edit');
    5689 
    5690                         edit.get('library').add( state.get('selection').models );
    5691                         state.trigger('reset');
    5692                         controller.setState('gallery-edit');
    5693                     }
    5694                 }
    5695             }
    5696         }) );
    5697     },
    5698 
    5699     playlistEditToolbar: function() {
    5700         var editing = this.state().get('editing');
    5701         this.toolbar.set( new wp.media.view.Toolbar({
    5702             controller: this,
    5703             items: {
    5704                 insert: {
    5705                     style:    'primary',
    5706                     text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
    5707                     priority: 80,
    5708                     requires: { library: true },
    5709 
    5710                     /**
    5711                      * @fires wp.media.controller.State#update
    5712                      */
    5713                     click: function() {
    5714                         var controller = this.controller,
    5715                             state = controller.state();
    5716 
    5717                         controller.close();
    5718                         state.trigger( 'update', state.get('library') );
    5719 
    5720                         // Restore and reset the default state.
    5721                         controller.setState( controller.options.state );
    5722                         controller.reset();
    5723                     }
    5724                 }
    5725             }
    5726         }) );
    5727     },
    5728 
    5729     playlistAddToolbar: function() {
    5730         this.toolbar.set( new wp.media.view.Toolbar({
    5731             controller: this,
    5732             items: {
    5733                 insert: {
    5734                     style:    'primary',
    5735                     text:     l10n.addToPlaylist,
    5736                     priority: 80,
    5737                     requires: { selection: true },
    5738 
    5739                     /**
    5740                      * @fires wp.media.controller.State#reset
    5741                      */
    5742                     click: function() {
    5743                         var controller = this.controller,
    5744                             state = controller.state(),
    5745                             edit = controller.state('playlist-edit');
    5746 
    5747                         edit.get('library').add( state.get('selection').models );
    5748                         state.trigger('reset');
    5749                         controller.setState('playlist-edit');
    5750                     }
    5751                 }
    5752             }
    5753         }) );
    5754     },
    5755 
    5756     videoPlaylistEditToolbar: function() {
    5757         var editing = this.state().get('editing');
    5758         this.toolbar.set( new wp.media.view.Toolbar({
    5759             controller: this,
    5760             items: {
    5761                 insert: {
    5762                     style:    'primary',
    5763                     text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
    5764                     priority: 140,
    5765                     requires: { library: true },
    5766 
    5767                     click: function() {
    5768                         var controller = this.controller,
    5769                             state = controller.state(),
    5770                             library = state.get('library');
    5771 
    5772                         library.type = 'video';
    5773 
    5774                         controller.close();
    5775                         state.trigger( 'update', library );
    5776 
    5777                         // Restore and reset the default state.
    5778                         controller.setState( controller.options.state );
    5779                         controller.reset();
    5780                     }
    5781                 }
    5782             }
    5783         }) );
    5784     },
    5785 
    5786     videoPlaylistAddToolbar: function() {
    5787         this.toolbar.set( new wp.media.view.Toolbar({
    5788             controller: this,
    5789             items: {
    5790                 insert: {
    5791                     style:    'primary',
    5792                     text:     l10n.addToVideoPlaylist,
    5793                     priority: 140,
    5794                     requires: { selection: true },
    5795 
    5796                     click: function() {
    5797                         var controller = this.controller,
    5798                             state = controller.state(),
    5799                             edit = controller.state('video-playlist-edit');
    5800 
    5801                         edit.get('library').add( state.get('selection').models );
    5802                         state.trigger('reset');
    5803                         controller.setState('video-playlist-edit');
    5804                     }
    5805                 }
    5806             }
    5807         }) );
    5808     }
    5809 });
    5810 
    5811 module.exports = Post;
    5812 
    5813 },{}],46:[function(require,module,exports){
    5814 /**
    5815  * wp.media.view.MediaFrame.Select
    5816  *
    5817  * A frame for selecting an item or items from the media library.
    5818  *
    5819  * @class
    5820  * @augments wp.media.view.MediaFrame
    5821  * @augments wp.media.view.Frame
    5822  * @augments wp.media.View
    5823  * @augments wp.Backbone.View
    5824  * @augments Backbone.View
    5825  * @mixes wp.media.controller.StateMachine
    5826  */
    5827 
    5828 var MediaFrame = wp.media.view.MediaFrame,
    5829     l10n = wp.media.view.l10n,
    5830     Select;
    5831 
    5832 Select = MediaFrame.extend({
    5833     initialize: function() {
    5834         // Call 'initialize' directly on the parent class.
    5835         MediaFrame.prototype.initialize.apply( this, arguments );
    5836 
    5837         _.defaults( this.options, {
    5838             selection: [],
    5839             library:   {},
    5840             multiple:  false,
    5841             state:    'library'
    5842         });
    5843 
    5844         this.createSelection();
    5845         this.createStates();
    5846         this.bindHandlers();
    5847     },
    5848 
    5849     /**
    5850      * Attach a selection collection to the frame.
    5851      *
    5852      * A selection is a collection of attachments used for a specific purpose
    5853      * by a media frame. e.g. Selecting an attachment (or many) to insert into
    5854      * post content.
    5855      *
    5856      * @see media.model.Selection
    5857      */
    5858     createSelection: function() {
    5859         var selection = this.options.selection;
    5860 
    5861         if ( ! (selection instanceof wp.media.model.Selection) ) {
    5862             this.options.selection = new wp.media.model.Selection( selection, {
    5863                 multiple: this.options.multiple
    5864             });
    5865         }
    5866 
    5867         this._selection = {
    5868             attachments: new wp.media.model.Attachments(),
    5869             difference: []
    5870         };
    5871     },
    5872 
    5873     /**
    5874      * Create the default states on the frame.
    5875      */
    5876     createStates: function() {
    5877         var options = this.options;
    5878 
    5879         if ( this.options.states ) {
    5880             return;
    5881         }
    5882 
    5883         // Add the default states.
    5884         this.states.add([
    5885             // Main states.
    5886             new wp.media.controller.Library({
    5887                 library:   wp.media.query( options.library ),
    5888                 multiple:  options.multiple,
    5889                 title:     options.title,
    5890                 priority:  20
    5891             })
    5892         ]);
    5893     },
    5894 
    5895     /**
    5896      * Bind region mode event callbacks.
    5897      *
    5898      * @see media.controller.Region.render
    5899      */
    5900     bindHandlers: function() {
    5901         this.on( 'router:create:browse', this.createRouter, this );
    5902         this.on( 'router:render:browse', this.browseRouter, this );
    5903         this.on( 'content:create:browse', this.browseContent, this );
    5904         this.on( 'content:render:upload', this.uploadContent, this );
    5905         this.on( 'toolbar:create:select', this.createSelectToolbar, this );
    5906     },
    5907 
    5908     /**
    5909      * Render callback for the router region in the `browse` mode.
    5910      *
    5911      * @param {wp.media.view.Router} routerView
    5912      */
    5913     browseRouter: function( routerView ) {
    5914         routerView.set({
    5915             upload: {
    5916                 text:     l10n.uploadFilesTitle,
    5917                 priority: 20
    5918             },
    5919             browse: {
    5920                 text:     l10n.mediaLibraryTitle,
    5921                 priority: 40
    5922             }
    5923         });
    5924     },
    5925 
    5926     /**
    5927      * Render callback for the content region in the `browse` mode.
    5928      *
    5929      * @param {wp.media.controller.Region} contentRegion
    5930      */
    5931     browseContent: function( contentRegion ) {
    5932         var state = this.state();
    5933 
    5934         this.$el.removeClass('hide-toolbar');
    5935 
    5936         // Browse our library of attachments.
    5937         contentRegion.view = new wp.media.view.AttachmentsBrowser({
    5938             controller: this,
    5939             collection: state.get('library'),
    5940             selection:  state.get('selection'),
    5941             model:      state,
    5942             sortable:   state.get('sortable'),
    5943             search:     state.get('searchable'),
    5944             filters:    state.get('filterable'),
    5945             date:       state.get('date'),
    5946             display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
    5947             dragInfo:   state.get('dragInfo'),
    5948 
    5949             idealColumnWidth: state.get('idealColumnWidth'),
    5950             suggestedWidth:   state.get('suggestedWidth'),
    5951             suggestedHeight:  state.get('suggestedHeight'),
    5952 
    5953             AttachmentView: state.get('AttachmentView')
    5954         });
    5955     },
    5956 
    5957     /**
    5958      * Render callback for the content region in the `upload` mode.
    5959      */
    5960     uploadContent: function() {
    5961         this.$el.removeClass( 'hide-toolbar' );
    5962         this.content.set( new wp.media.view.UploaderInline({
    5963             controller: this
    5964         }) );
    5965     },
    5966 
    5967     /**
    5968      * Toolbars
    5969      *
    5970      * @param {Object} toolbar
    5971      * @param {Object} [options={}]
    5972      * @this wp.media.controller.Region
    5973      */
    5974     createSelectToolbar: function( toolbar, options ) {
    5975         options = options || this.options.button || {};
    5976         options.controller = this;
    5977 
    5978         toolbar.view = new wp.media.view.Toolbar.Select( options );
    5979     }
    5980 });
    5981 
    5982 module.exports = Select;
    5983 
    5984 },{}],47:[function(require,module,exports){
    5985 /**
    5986  * wp.media.view.Iframe
    5987  *
    5988  * @class
    5989  * @augments wp.media.View
    5990  * @augments wp.Backbone.View
    5991  * @augments Backbone.View
    5992  */
    5993 var Iframe = wp.media.View.extend({
    5994     className: 'media-iframe',
    5995     /**
    5996      * @returns {wp.media.view.Iframe} Returns itself to allow chaining
    5997      */
    5998     render: function() {
    5999         this.views.detach();
    6000         this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
    6001         this.views.render();
    6002         return this;
    6003     }
    6004 });
    6005 
    6006 module.exports = Iframe;
    6007 
    6008 },{}],48:[function(require,module,exports){
     8402module.exports = EmbedImage;
     8403
     8404
     8405/***/ }),
     8406/* 95 */
     8407/***/ (function(module, exports) {
     8408
    60098409/**
    60108410 * wp.media.view.ImageDetails
     
    61748574module.exports = ImageDetails;
    61758575
    6176 },{}],49:[function(require,module,exports){
     8576
     8577/***/ }),
     8578/* 96 */
     8579/***/ (function(module, exports) {
     8580
    61778581/**
    6178  * wp.media.view.Label
    6179  *
    6180  * @class
    6181  * @augments wp.media.View
    6182  * @augments wp.Backbone.View
    6183  * @augments Backbone.View
    6184  */
    6185 var Label = wp.media.View.extend({
    6186     tagName: 'label',
    6187     className: 'screen-reader-text',
    6188 
    6189     initialize: function() {
    6190         this.value = this.options.value;
    6191     },
    6192 
    6193     render: function() {
    6194         this.$el.html( this.value );
    6195 
    6196         return this;
    6197     }
    6198 });
    6199 
    6200 module.exports = Label;
    6201 
    6202 },{}],50:[function(require,module,exports){
    6203 /**
    6204  * wp.media.view.MediaFrame
    6205  *
    6206  * The frame used to create the media modal.
    6207  *
    6208  * @class
    6209  * @augments wp.media.view.Frame
    6210  * @augments wp.media.View
    6211  * @augments wp.Backbone.View
    6212  * @augments Backbone.View
    6213  * @mixes wp.media.controller.StateMachine
    6214  */
    6215 var Frame = wp.media.view.Frame,
    6216     $ = jQuery,
    6217     MediaFrame;
    6218 
    6219 MediaFrame = Frame.extend({
    6220     className: 'media-frame',
    6221     template:  wp.template('media-frame'),
    6222     regions:   ['menu','title','content','toolbar','router'],
    6223 
    6224     events: {
    6225         'click div.media-frame-title h1': 'toggleMenu'
    6226     },
    6227 
    6228     /**
    6229      * @global wp.Uploader
    6230      */
    6231     initialize: function() {
    6232         Frame.prototype.initialize.apply( this, arguments );
    6233 
    6234         _.defaults( this.options, {
    6235             title:    '',
    6236             modal:    true,
    6237             uploader: true
    6238         });
    6239 
    6240         // Ensure core UI is enabled.
    6241         this.$el.addClass('wp-core-ui');
    6242 
    6243         // Initialize modal container view.
    6244         if ( this.options.modal ) {
    6245             this.modal = new wp.media.view.Modal({
    6246                 controller: this,
    6247                 title:      this.options.title
    6248             });
    6249 
    6250             this.modal.content( this );
    6251         }
    6252 
    6253         // Force the uploader off if the upload limit has been exceeded or
    6254         // if the browser isn't supported.
    6255         if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
    6256             this.options.uploader = false;
    6257         }
    6258 
    6259         // Initialize window-wide uploader.
    6260         if ( this.options.uploader ) {
    6261             this.uploader = new wp.media.view.UploaderWindow({
    6262                 controller: this,
    6263                 uploader: {
    6264                     dropzone:  this.modal ? this.modal.$el : this.$el,
    6265                     container: this.$el
    6266                 }
    6267             });
    6268             this.views.set( '.media-frame-uploader', this.uploader );
    6269         }
    6270 
    6271         this.on( 'attach', _.bind( this.views.ready, this.views ), this );
    6272 
    6273         // Bind default title creation.
    6274         this.on( 'title:create:default', this.createTitle, this );
    6275         this.title.mode('default');
    6276 
    6277         this.on( 'title:render', function( view ) {
    6278             view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
    6279         });
    6280 
    6281         // Bind default menu.
    6282         this.on( 'menu:create:default', this.createMenu, this );
    6283     },
    6284     /**
    6285      * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6286      */
    6287     render: function() {
    6288         // Activate the default state if no active state exists.
    6289         if ( ! this.state() && this.options.state ) {
    6290             this.setState( this.options.state );
    6291         }
    6292         /**
    6293          * call 'render' directly on the parent class
    6294          */
    6295         return Frame.prototype.render.apply( this, arguments );
    6296     },
    6297     /**
    6298      * @param {Object} title
    6299      * @this wp.media.controller.Region
    6300      */
    6301     createTitle: function( title ) {
    6302         title.view = new wp.media.View({
    6303             controller: this,
    6304             tagName: 'h1'
    6305         });
    6306     },
    6307     /**
    6308      * @param {Object} menu
    6309      * @this wp.media.controller.Region
    6310      */
    6311     createMenu: function( menu ) {
    6312         menu.view = new wp.media.view.Menu({
    6313             controller: this
    6314         });
    6315     },
    6316 
    6317     toggleMenu: function() {
    6318         this.$el.find( '.media-menu' ).toggleClass( 'visible' );
    6319     },
    6320 
    6321     /**
    6322      * @param {Object} toolbar
    6323      * @this wp.media.controller.Region
    6324      */
    6325     createToolbar: function( toolbar ) {
    6326         toolbar.view = new wp.media.view.Toolbar({
    6327             controller: this
    6328         });
    6329     },
    6330     /**
    6331      * @param {Object} router
    6332      * @this wp.media.controller.Region
    6333      */
    6334     createRouter: function( router ) {
    6335         router.view = new wp.media.view.Router({
    6336             controller: this
    6337         });
    6338     },
    6339     /**
    6340      * @param {Object} options
    6341      */
    6342     createIframeStates: function( options ) {
    6343         var settings = wp.media.view.settings,
    6344             tabs = settings.tabs,
    6345             tabUrl = settings.tabUrl,
    6346             $postId;
    6347 
    6348         if ( ! tabs || ! tabUrl ) {
    6349             return;
    6350         }
    6351 
    6352         // Add the post ID to the tab URL if it exists.
    6353         $postId = $('#post_ID');
    6354         if ( $postId.length ) {
    6355             tabUrl += '&post_id=' + $postId.val();
    6356         }
    6357 
    6358         // Generate the tab states.
    6359         _.each( tabs, function( title, id ) {
    6360             this.state( 'iframe:' + id ).set( _.defaults({
    6361                 tab:     id,
    6362                 src:     tabUrl + '&tab=' + id,
    6363                 title:   title,
    6364                 content: 'iframe',
    6365                 menu:    'default'
    6366             }, options ) );
    6367         }, this );
    6368 
    6369         this.on( 'content:create:iframe', this.iframeContent, this );
    6370         this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
    6371         this.on( 'menu:render:default', this.iframeMenu, this );
    6372         this.on( 'open', this.hijackThickbox, this );
    6373         this.on( 'close', this.restoreThickbox, this );
    6374     },
    6375 
    6376     /**
    6377      * @param {Object} content
    6378      * @this wp.media.controller.Region
    6379      */
    6380     iframeContent: function( content ) {
    6381         this.$el.addClass('hide-toolbar');
    6382         content.view = new wp.media.view.Iframe({
    6383             controller: this
    6384         });
    6385     },
    6386 
    6387     iframeContentCleanup: function() {
    6388         this.$el.removeClass('hide-toolbar');
    6389     },
    6390 
    6391     iframeMenu: function( view ) {
    6392         var views = {};
    6393 
    6394         if ( ! view ) {
    6395             return;
    6396         }
    6397 
    6398         _.each( wp.media.view.settings.tabs, function( title, id ) {
    6399             views[ 'iframe:' + id ] = {
    6400                 text: this.state( 'iframe:' + id ).get('title'),
    6401                 priority: 200
    6402             };
    6403         }, this );
    6404 
    6405         view.set( views );
    6406     },
    6407 
    6408     hijackThickbox: function() {
    6409         var frame = this;
    6410 
    6411         if ( ! window.tb_remove || this._tb_remove ) {
    6412             return;
    6413         }
    6414 
    6415         this._tb_remove = window.tb_remove;
    6416         window.tb_remove = function() {
    6417             frame.close();
    6418             frame.reset();
    6419             frame.setState( frame.options.state );
    6420             frame._tb_remove.call( window );
    6421         };
    6422     },
    6423 
    6424     restoreThickbox: function() {
    6425         if ( ! this._tb_remove ) {
    6426             return;
    6427         }
    6428 
    6429         window.tb_remove = this._tb_remove;
    6430         delete this._tb_remove;
    6431     }
    6432 });
    6433 
    6434 // Map some of the modal's methods to the frame.
    6435 _.each(['open','close','attach','detach','escape'], function( method ) {
    6436     /**
    6437      * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6438      */
    6439     MediaFrame.prototype[ method ] = function() {
    6440         if ( this.modal ) {
    6441             this.modal[ method ].apply( this.modal, arguments );
    6442         }
    6443         return this;
    6444     };
    6445 });
    6446 
    6447 module.exports = MediaFrame;
    6448 
    6449 },{}],51:[function(require,module,exports){
    6450 /**
    6451  * wp.media.view.MenuItem
    6452  *
    6453  * @class
    6454  * @augments wp.media.View
    6455  * @augments wp.Backbone.View
    6456  * @augments Backbone.View
    6457  */
    6458 var $ = jQuery,
    6459     MenuItem;
    6460 
    6461 MenuItem = wp.media.View.extend({
    6462     tagName:   'a',
    6463     className: 'media-menu-item',
    6464 
    6465     attributes: {
    6466         href: '#'
    6467     },
    6468 
    6469     events: {
    6470         'click': '_click'
    6471     },
    6472     /**
    6473      * @param {Object} event
    6474      */
    6475     _click: function( event ) {
    6476         var clickOverride = this.options.click;
    6477 
    6478         if ( event ) {
    6479             event.preventDefault();
    6480         }
    6481 
    6482         if ( clickOverride ) {
    6483             clickOverride.call( this );
    6484         } else {
    6485             this.click();
    6486         }
    6487 
    6488         // When selecting a tab along the left side,
    6489         // focus should be transferred into the main panel
    6490         if ( ! wp.media.isTouchDevice ) {
    6491             $('.media-frame-content input').first().focus();
    6492         }
    6493     },
    6494 
    6495     click: function() {
    6496         var state = this.options.state;
    6497 
    6498         if ( state ) {
    6499             this.controller.setState( state );
    6500             this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
    6501         }
    6502     },
    6503     /**
    6504      * @returns {wp.media.view.MenuItem} returns itself to allow chaining
    6505      */
    6506     render: function() {
    6507         var options = this.options;
    6508 
    6509         if ( options.text ) {
    6510             this.$el.text( options.text );
    6511         } else if ( options.html ) {
    6512             this.$el.html( options.html );
    6513         }
    6514 
    6515         return this;
    6516     }
    6517 });
    6518 
    6519 module.exports = MenuItem;
    6520 
    6521 },{}],52:[function(require,module,exports){
    6522 /**
    6523  * wp.media.view.Menu
    6524  *
    6525  * @class
    6526  * @augments wp.media.view.PriorityList
    6527  * @augments wp.media.View
    6528  * @augments wp.Backbone.View
    6529  * @augments Backbone.View
    6530  */
    6531 var MenuItem = wp.media.view.MenuItem,
    6532     PriorityList = wp.media.view.PriorityList,
    6533     Menu;
    6534 
    6535 Menu = PriorityList.extend({
    6536     tagName:   'div',
    6537     className: 'media-menu',
    6538     property:  'state',
    6539     ItemView:  MenuItem,
    6540     region:    'menu',
    6541 
    6542     /* TODO: alternatively hide on any click anywhere
    6543     events: {
    6544         'click': 'click'
    6545     },
    6546 
    6547     click: function() {
    6548         this.$el.removeClass( 'visible' );
    6549     },
    6550     */
    6551 
    6552     /**
    6553      * @param {Object} options
    6554      * @param {string} id
    6555      * @returns {wp.media.View}
    6556      */
    6557     toView: function( options, id ) {
    6558         options = options || {};
    6559         options[ this.property ] = options[ this.property ] || id;
    6560         return new this.ItemView( options ).render();
    6561     },
    6562 
    6563     ready: function() {
    6564         /**
    6565          * call 'ready' directly on the parent class
    6566          */
    6567         PriorityList.prototype.ready.apply( this, arguments );
    6568         this.visibility();
    6569     },
    6570 
    6571     set: function() {
    6572         /**
    6573          * call 'set' directly on the parent class
    6574          */
    6575         PriorityList.prototype.set.apply( this, arguments );
    6576         this.visibility();
    6577     },
    6578 
    6579     unset: function() {
    6580         /**
    6581          * call 'unset' directly on the parent class
    6582          */
    6583         PriorityList.prototype.unset.apply( this, arguments );
    6584         this.visibility();
    6585     },
    6586 
    6587     visibility: function() {
    6588         var region = this.region,
    6589             view = this.controller[ region ].get(),
    6590             views = this.views.get(),
    6591             hide = ! views || views.length < 2;
    6592 
    6593         if ( this === view ) {
    6594             this.controller.$el.toggleClass( 'hide-' + region, hide );
    6595         }
    6596     },
    6597     /**
    6598      * @param {string} id
    6599      */
    6600     select: function( id ) {
    6601         var view = this.get( id );
    6602 
    6603         if ( ! view ) {
    6604             return;
    6605         }
    6606 
    6607         this.deselect();
    6608         view.$el.addClass('active');
    6609     },
    6610 
    6611     deselect: function() {
    6612         this.$el.children().removeClass('active');
    6613     },
    6614 
    6615     hide: function( id ) {
    6616         var view = this.get( id );
    6617 
    6618         if ( ! view ) {
    6619             return;
    6620         }
    6621 
    6622         view.$el.addClass('hidden');
    6623     },
    6624 
    6625     show: function( id ) {
    6626         var view = this.get( id );
    6627 
    6628         if ( ! view ) {
    6629             return;
    6630         }
    6631 
    6632         view.$el.removeClass('hidden');
    6633     }
    6634 });
    6635 
    6636 module.exports = Menu;
    6637 
    6638 },{}],53:[function(require,module,exports){
    6639 /**
    6640  * wp.media.view.Modal
    6641  *
    6642  * A modal view, which the media modal uses as its default container.
    6643  *
    6644  * @class
    6645  * @augments wp.media.View
    6646  * @augments wp.Backbone.View
    6647  * @augments Backbone.View
    6648  */
    6649 var $ = jQuery,
    6650     Modal;
    6651 
    6652 Modal = wp.media.View.extend({
    6653     tagName:  'div',
    6654     template: wp.template('media-modal'),
    6655 
    6656     attributes: {
    6657         tabindex: 0
    6658     },
    6659 
    6660     events: {
    6661         'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
    6662         'keydown': 'keydown'
    6663     },
    6664 
    6665     initialize: function() {
    6666         _.defaults( this.options, {
    6667             container: document.body,
    6668             title:     '',
    6669             propagate: true,
    6670             freeze:    true
    6671         });
    6672 
    6673         this.focusManager = new wp.media.view.FocusManager({
    6674             el: this.el
    6675         });
    6676     },
    6677     /**
    6678      * @returns {Object}
    6679      */
    6680     prepare: function() {
    6681         return {
    6682             title: this.options.title
    6683         };
    6684     },
    6685 
    6686     /**
    6687      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6688      */
    6689     attach: function() {
    6690         if ( this.views.attached ) {
    6691             return this;
    6692         }
    6693 
    6694         if ( ! this.views.rendered ) {
    6695             this.render();
    6696         }
    6697 
    6698         this.$el.appendTo( this.options.container );
    6699 
    6700         // Manually mark the view as attached and trigger ready.
    6701         this.views.attached = true;
    6702         this.views.ready();
    6703 
    6704         return this.propagate('attach');
    6705     },
    6706 
    6707     /**
    6708      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6709      */
    6710     detach: function() {
    6711         if ( this.$el.is(':visible') ) {
    6712             this.close();
    6713         }
    6714 
    6715         this.$el.detach();
    6716         this.views.attached = false;
    6717         return this.propagate('detach');
    6718     },
    6719 
    6720     /**
    6721      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6722      */
    6723     open: function() {
    6724         var $el = this.$el,
    6725             options = this.options,
    6726             mceEditor;
    6727 
    6728         if ( $el.is(':visible') ) {
    6729             return this;
    6730         }
    6731 
    6732         if ( ! this.views.attached ) {
    6733             this.attach();
    6734         }
    6735 
    6736         // If the `freeze` option is set, record the window's scroll position.
    6737         if ( options.freeze ) {
    6738             this._freeze = {
    6739                 scrollTop: $( window ).scrollTop()
    6740             };
    6741         }
    6742 
    6743         // Disable page scrolling.
    6744         $( 'body' ).addClass( 'modal-open' );
    6745 
    6746         $el.show();
    6747 
    6748         // Try to close the onscreen keyboard
    6749         if ( 'ontouchend' in document ) {
    6750             if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
    6751                 mceEditor.iframeElement.focus();
    6752                 mceEditor.iframeElement.blur();
    6753 
    6754                 setTimeout( function() {
    6755                     mceEditor.iframeElement.blur();
    6756                 }, 100 );
    6757             }
    6758         }
    6759 
    6760         this.$el.focus();
    6761 
    6762         return this.propagate('open');
    6763     },
    6764 
    6765     /**
    6766      * @param {Object} options
    6767      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6768      */
    6769     close: function( options ) {
    6770         var freeze = this._freeze;
    6771 
    6772         if ( ! this.views.attached || ! this.$el.is(':visible') ) {
    6773             return this;
    6774         }
    6775 
    6776         // Enable page scrolling.
    6777         $( 'body' ).removeClass( 'modal-open' );
    6778 
    6779         // Hide modal and remove restricted media modal tab focus once it's closed
    6780         this.$el.hide().undelegate( 'keydown' );
    6781 
    6782         // Put focus back in useful location once modal is closed
    6783         $('#wpbody-content').focus();
    6784 
    6785         this.propagate('close');
    6786 
    6787         // If the `freeze` option is set, restore the container's scroll position.
    6788         if ( freeze ) {
    6789             $( window ).scrollTop( freeze.scrollTop );
    6790         }
    6791 
    6792         if ( options && options.escape ) {
    6793             this.propagate('escape');
    6794         }
    6795 
    6796         return this;
    6797     },
    6798     /**
    6799      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6800      */
    6801     escape: function() {
    6802         return this.close({ escape: true });
    6803     },
    6804     /**
    6805      * @param {Object} event
    6806      */
    6807     escapeHandler: function( event ) {
    6808         event.preventDefault();
    6809         this.escape();
    6810     },
    6811 
    6812     /**
    6813      * @param {Array|Object} content Views to register to '.media-modal-content'
    6814      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6815      */
    6816     content: function( content ) {
    6817         this.views.set( '.media-modal-content', content );
    6818         return this;
    6819     },
    6820 
    6821     /**
    6822      * Triggers a modal event and if the `propagate` option is set,
    6823      * forwards events to the modal's controller.
    6824      *
    6825      * @param {string} id
    6826      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6827      */
    6828     propagate: function( id ) {
    6829         this.trigger( id );
    6830 
    6831         if ( this.options.propagate ) {
    6832             this.controller.trigger( id );
    6833         }
    6834 
    6835         return this;
    6836     },
    6837     /**
    6838      * @param {Object} event
    6839      */
    6840     keydown: function( event ) {
    6841         // Close the modal when escape is pressed.
    6842         if ( 27 === event.which && this.$el.is(':visible') ) {
    6843             this.escape();
    6844             event.stopImmediatePropagation();
    6845         }
    6846     }
    6847 });
    6848 
    6849 module.exports = Modal;
    6850 
    6851 },{}],54:[function(require,module,exports){
    6852 /**
    6853  * wp.media.view.PriorityList
    6854  *
    6855  * @class
    6856  * @augments wp.media.View
    6857  * @augments wp.Backbone.View
    6858  * @augments Backbone.View
    6859  */
    6860 var PriorityList = wp.media.View.extend({
    6861     tagName:   'div',
    6862 
    6863     initialize: function() {
    6864         this._views = {};
    6865 
    6866         this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
    6867         delete this.options.views;
    6868 
    6869         if ( ! this.options.silent ) {
    6870             this.render();
    6871         }
    6872     },
    6873     /**
    6874      * @param {string} id
    6875      * @param {wp.media.View|Object} view
    6876      * @param {Object} options
    6877      * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
    6878      */
    6879     set: function( id, view, options ) {
    6880         var priority, views, index;
    6881 
    6882         options = options || {};
    6883 
    6884         // Accept an object with an `id` : `view` mapping.
    6885         if ( _.isObject( id ) ) {
    6886             _.each( id, function( view, id ) {
    6887                 this.set( id, view );
    6888             }, this );
    6889             return this;
    6890         }
    6891 
    6892         if ( ! (view instanceof Backbone.View) ) {
    6893             view = this.toView( view, id, options );
    6894         }
    6895         view.controller = view.controller || this.controller;
    6896 
    6897         this.unset( id );
    6898 
    6899         priority = view.options.priority || 10;
    6900         views = this.views.get() || [];
    6901 
    6902         _.find( views, function( existing, i ) {
    6903             if ( existing.options.priority > priority ) {
    6904                 index = i;
    6905                 return true;
    6906             }
    6907         });
    6908 
    6909         this._views[ id ] = view;
    6910         this.views.add( view, {
    6911             at: _.isNumber( index ) ? index : views.length || 0
    6912         });
    6913 
    6914         return this;
    6915     },
    6916     /**
    6917      * @param {string} id
    6918      * @returns {wp.media.View}
    6919      */
    6920     get: function( id ) {
    6921         return this._views[ id ];
    6922     },
    6923     /**
    6924      * @param {string} id
    6925      * @returns {wp.media.view.PriorityList}
    6926      */
    6927     unset: function( id ) {
    6928         var view = this.get( id );
    6929 
    6930         if ( view ) {
    6931             view.remove();
    6932         }
    6933 
    6934         delete this._views[ id ];
    6935         return this;
    6936     },
    6937     /**
    6938      * @param {Object} options
    6939      * @returns {wp.media.View}
    6940      */
    6941     toView: function( options ) {
    6942         return new wp.media.View( options );
    6943     }
    6944 });
    6945 
    6946 module.exports = PriorityList;
    6947 
    6948 },{}],55:[function(require,module,exports){
    6949 /**
    6950  * wp.media.view.RouterItem
    6951  *
    6952  * @class
    6953  * @augments wp.media.view.MenuItem
    6954  * @augments wp.media.View
    6955  * @augments wp.Backbone.View
    6956  * @augments Backbone.View
    6957  */
    6958 var RouterItem = wp.media.view.MenuItem.extend({
    6959     /**
    6960      * On click handler to activate the content region's corresponding mode.
    6961      */
    6962     click: function() {
    6963         var contentMode = this.options.contentMode;
    6964         if ( contentMode ) {
    6965             this.controller.content.mode( contentMode );
    6966         }
    6967     }
    6968 });
    6969 
    6970 module.exports = RouterItem;
    6971 
    6972 },{}],56:[function(require,module,exports){
    6973 /**
    6974  * wp.media.view.Router
    6975  *
    6976  * @class
    6977  * @augments wp.media.view.Menu
    6978  * @augments wp.media.view.PriorityList
    6979  * @augments wp.media.View
    6980  * @augments wp.Backbone.View
    6981  * @augments Backbone.View
    6982  */
    6983 var Menu = wp.media.view.Menu,
    6984     Router;
    6985 
    6986 Router = Menu.extend({
    6987     tagName:   'div',
    6988     className: 'media-router',
    6989     property:  'contentMode',
    6990     ItemView:  wp.media.view.RouterItem,
    6991     region:    'router',
    6992 
    6993     initialize: function() {
    6994         this.controller.on( 'content:render', this.update, this );
    6995         // Call 'initialize' directly on the parent class.
    6996         Menu.prototype.initialize.apply( this, arguments );
    6997     },
    6998 
    6999     update: function() {
    7000         var mode = this.controller.content.mode();
    7001         if ( mode ) {
    7002             this.select( mode );
    7003         }
    7004     }
    7005 });
    7006 
    7007 module.exports = Router;
    7008 
    7009 },{}],57:[function(require,module,exports){
    7010 /**
    7011  * wp.media.view.Search
    7012  *
    7013  * @class
    7014  * @augments wp.media.View
    7015  * @augments wp.Backbone.View
    7016  * @augments Backbone.View
    7017  */
    7018 var l10n = wp.media.view.l10n,
    7019     Search;
    7020 
    7021 Search = wp.media.View.extend({
    7022     tagName:   'input',
    7023     className: 'search',
    7024     id:        'media-search-input',
    7025 
    7026     attributes: {
    7027         type:        'search',
    7028         placeholder: l10n.search
    7029     },
    7030 
    7031     events: {
    7032         'input':  'search',
    7033         'keyup':  'search',
    7034         'change': 'search',
    7035         'search': 'search'
    7036     },
    7037 
    7038     /**
    7039      * @returns {wp.media.view.Search} Returns itself to allow chaining
    7040      */
    7041     render: function() {
    7042         this.el.value = this.model.escape('search');
    7043         return this;
    7044     },
    7045 
    7046     search: function( event ) {
    7047         if ( event.target.value ) {
    7048             this.model.set( 'search', event.target.value );
    7049         } else {
    7050             this.model.unset('search');
    7051         }
    7052     }
    7053 });
    7054 
    7055 module.exports = Search;
    7056 
    7057 },{}],58:[function(require,module,exports){
    7058 /**
    7059  * wp.media.view.Selection
    7060  *
    7061  * @class
    7062  * @augments wp.media.View
    7063  * @augments wp.Backbone.View
    7064  * @augments Backbone.View
    7065  */
    7066 var l10n = wp.media.view.l10n,
    7067     Selection;
    7068 
    7069 Selection = wp.media.View.extend({
    7070     tagName:   'div',
    7071     className: 'media-selection',
    7072     template:  wp.template('media-selection'),
    7073 
    7074     events: {
    7075         'click .edit-selection':  'edit',
    7076         'click .clear-selection': 'clear'
    7077     },
    7078 
    7079     initialize: function() {
    7080         _.defaults( this.options, {
    7081             editable:  false,
    7082             clearable: true
    7083         });
    7084 
    7085         /**
    7086          * @member {wp.media.view.Attachments.Selection}
    7087          */
    7088         this.attachments = new wp.media.view.Attachments.Selection({
    7089             controller: this.controller,
    7090             collection: this.collection,
    7091             selection:  this.collection,
    7092             model:      new Backbone.Model()
    7093         });
    7094 
    7095         this.views.set( '.selection-view', this.attachments );
    7096         this.collection.on( 'add remove reset', this.refresh, this );
    7097         this.controller.on( 'content:activate', this.refresh, this );
    7098     },
    7099 
    7100     ready: function() {
    7101         this.refresh();
    7102     },
    7103 
    7104     refresh: function() {
    7105         // If the selection hasn't been rendered, bail.
    7106         if ( ! this.$el.children().length ) {
    7107             return;
    7108         }
    7109 
    7110         var collection = this.collection,
    7111             editing = 'edit-selection' === this.controller.content.mode();
    7112 
    7113         // If nothing is selected, display nothing.
    7114         this.$el.toggleClass( 'empty', ! collection.length );
    7115         this.$el.toggleClass( 'one', 1 === collection.length );
    7116         this.$el.toggleClass( 'editing', editing );
    7117 
    7118         this.$('.count').text( l10n.selected.replace('%d', collection.length) );
    7119     },
    7120 
    7121     edit: function( event ) {
    7122         event.preventDefault();
    7123         if ( this.options.editable ) {
    7124             this.options.editable.call( this, this.collection );
    7125         }
    7126     },
    7127 
    7128     clear: function( event ) {
    7129         event.preventDefault();
    7130         this.collection.reset();
    7131 
    7132         // Keep focus inside media modal
    7133         // after clear link is selected
    7134         this.controller.modal.focusManager.focus();
    7135     }
    7136 });
    7137 
    7138 module.exports = Selection;
    7139 
    7140 },{}],59:[function(require,module,exports){
    7141 /**
    7142  * wp.media.view.Settings
     8582 * wp.media.view.Cropper
     8583 *
     8584 * Uses the imgAreaSelect plugin to allow a user to crop an image.
     8585 *
     8586 * Takes imgAreaSelect options from
     8587 * wp.customize.HeaderControl.calculateImageSelectOptions via
     8588 * wp.customize.HeaderControl.openMM.
    71438589 *
    71448590 * @class
     
    71488594 */
    71498595var View = wp.media.View,
    7150     $ = Backbone.$,
    7151     Settings;
    7152 
    7153 Settings = View.extend({
    7154     events: {
    7155         'click button':    'updateHandler',
    7156         'change input':    'updateHandler',
    7157         'change select':   'updateHandler',
    7158         'change textarea': 'updateHandler'
    7159     },
    7160 
     8596    UploaderStatus = wp.media.view.UploaderStatus,
     8597    l10n = wp.media.view.l10n,
     8598    $ = jQuery,
     8599    Cropper;
     8600
     8601Cropper = View.extend({
     8602    className: 'crop-content',
     8603    template: wp.template('crop-content'),
    71618604    initialize: function() {
    7162         this.model = this.model || new Backbone.Model();
    7163         this.listenTo( this.model, 'change', this.updateChanges );
    7164     },
    7165 
     8605        _.bindAll(this, 'onImageLoad');
     8606    },
     8607    ready: function() {
     8608        this.controller.frame.on('content:error:crop', this.onError, this);
     8609        this.$image = this.$el.find('.crop-image');
     8610        this.$image.on('load', this.onImageLoad);
     8611        $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
     8612    },
     8613    remove: function() {
     8614        $(window).off('resize.cropper');
     8615        this.$el.remove();
     8616        this.$el.off();
     8617        View.prototype.remove.apply(this, arguments);
     8618    },
    71668619    prepare: function() {
    7167         return _.defaults({
    7168             model: this.model.toJSON()
    7169         }, this.options );
    7170     },
    7171     /**
    7172      * @returns {wp.media.view.Settings} Returns itself to allow chaining
    7173      */
    7174     render: function() {
    7175         View.prototype.render.apply( this, arguments );
    7176         // Select the correct values.
    7177         _( this.model.attributes ).chain().keys().each( this.update, this );
    7178         return this;
    7179     },
    7180     /**
    7181      * @param {string} key
    7182      */
    7183     update: function( key ) {
    7184         var value = this.model.get( key ),
    7185             $setting = this.$('[data-setting="' + key + '"]'),
    7186             $buttons, $value;
    7187 
    7188         // Bail if we didn't find a matching setting.
    7189         if ( ! $setting.length ) {
    7190             return;
    7191         }
    7192 
    7193         // Attempt to determine how the setting is rendered and update
    7194         // the selected value.
    7195 
    7196         // Handle dropdowns.
    7197         if ( $setting.is('select') ) {
    7198             $value = $setting.find('[value="' + value + '"]');
    7199 
    7200             if ( $value.length ) {
    7201                 $setting.find('option').prop( 'selected', false );
    7202                 $value.prop( 'selected', true );
    7203             } else {
    7204                 // If we can't find the desired value, record what *is* selected.
    7205                 this.model.set( key, $setting.find(':selected').val() );
    7206             }
    7207 
    7208         // Handle button groups.
    7209         } else if ( $setting.hasClass('button-group') ) {
    7210             $buttons = $setting.find('button').removeClass('active');
    7211             $buttons.filter( '[value="' + value + '"]' ).addClass('active');
    7212 
    7213         // Handle text inputs and textareas.
    7214         } else if ( $setting.is('input[type="text"], textarea') ) {
    7215             if ( ! $setting.is(':focus') ) {
    7216                 $setting.val( value );
    7217             }
    7218         // Handle checkboxes.
    7219         } else if ( $setting.is('input[type="checkbox"]') ) {
    7220             $setting.prop( 'checked', !! value && 'false' !== value );
    7221         }
    7222     },
    7223     /**
    7224      * @param {Object} event
    7225      */
    7226     updateHandler: function( event ) {
    7227         var $setting = $( event.target ).closest('[data-setting]'),
    7228             value = event.target.value,
    7229             userSetting;
    7230 
    7231         event.preventDefault();
    7232 
    7233         if ( ! $setting.length ) {
    7234             return;
    7235         }
    7236 
    7237         // Use the correct value for checkboxes.
    7238         if ( $setting.is('input[type="checkbox"]') ) {
    7239             value = $setting[0].checked;
    7240         }
    7241 
    7242         // Update the corresponding setting.
    7243         this.model.set( $setting.data('setting'), value );
    7244 
    7245         // If the setting has a corresponding user setting,
    7246         // update that as well.
    7247         if ( userSetting = $setting.data('userSetting') ) {
    7248             window.setUserSetting( userSetting, value );
    7249         }
    7250     },
    7251 
    7252     updateChanges: function( model ) {
    7253         if ( model.hasChanged() ) {
    7254             _( model.changed ).chain().keys().each( this.update, this );
    7255         }
     8620        return {
     8621            title: l10n.cropYourImage,
     8622            url: this.options.attachment.get('url')
     8623        };
     8624    },
     8625    onImageLoad: function() {
     8626        var imgOptions = this.controller.get('imgSelectOptions');
     8627        if (typeof imgOptions === 'function') {
     8628            imgOptions = imgOptions(this.options.attachment, this.controller);
     8629        }
     8630
     8631        imgOptions = _.extend(imgOptions, {parent: this.$el});
     8632        this.trigger('image-loaded');
     8633        this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
     8634    },
     8635    onError: function() {
     8636        var filename = this.options.attachment.get('filename');
     8637
     8638        this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     8639            filename: UploaderStatus.prototype.filename(filename),
     8640            message: window._wpMediaViewsL10n.cropError
     8641        }), { at: 0 });
    72568642    }
    72578643});
    72588644
    7259 module.exports = Settings;
    7260 
    7261 },{}],60:[function(require,module,exports){
    7262 /**
    7263  * wp.media.view.Settings.AttachmentDisplay
    7264  *
    7265  * @class
    7266  * @augments wp.media.view.Settings
    7267  * @augments wp.media.View
    7268  * @augments wp.Backbone.View
    7269  * @augments Backbone.View
    7270  */
    7271 var Settings = wp.media.view.Settings,
    7272     AttachmentDisplay;
    7273 
    7274 AttachmentDisplay = Settings.extend({
    7275     className: 'attachment-display-settings',
    7276     template:  wp.template('attachment-display-settings'),
    7277 
    7278     initialize: function() {
    7279         var attachment = this.options.attachment;
    7280 
    7281         _.defaults( this.options, {
    7282             userSettings: false
    7283         });
    7284         // Call 'initialize' directly on the parent class.
    7285         Settings.prototype.initialize.apply( this, arguments );
    7286         this.listenTo( this.model, 'change:link', this.updateLinkTo );
    7287 
    7288         if ( attachment ) {
    7289             attachment.on( 'change:uploading', this.render, this );
    7290         }
    7291     },
    7292 
    7293     dispose: function() {
    7294         var attachment = this.options.attachment;
    7295         if ( attachment ) {
    7296             attachment.off( null, null, this );
    7297         }
    7298         /**
    7299          * call 'dispose' directly on the parent class
    7300          */
    7301         Settings.prototype.dispose.apply( this, arguments );
    7302     },
    7303     /**
    7304      * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
    7305      */
    7306     render: function() {
    7307         var attachment = this.options.attachment;
    7308         if ( attachment ) {
    7309             _.extend( this.options, {
    7310                 sizes: attachment.get('sizes'),
    7311                 type:  attachment.get('type')
    7312             });
    7313         }
    7314         /**
    7315          * call 'render' directly on the parent class
    7316          */
    7317         Settings.prototype.render.call( this );
    7318         this.updateLinkTo();
    7319         return this;
    7320     },
    7321 
    7322     updateLinkTo: function() {
    7323         var linkTo = this.model.get('link'),
    7324             $input = this.$('.link-to-custom'),
    7325             attachment = this.options.attachment;
    7326 
    7327         if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
    7328             $input.addClass( 'hidden' );
    7329             return;
    7330         }
    7331 
    7332         if ( attachment ) {
    7333             if ( 'post' === linkTo ) {
    7334                 $input.val( attachment.get('link') );
    7335             } else if ( 'file' === linkTo ) {
    7336                 $input.val( attachment.get('url') );
    7337             } else if ( ! this.model.get('linkUrl') ) {
    7338                 $input.val('http://');
    7339             }
    7340 
    7341             $input.prop( 'readonly', 'custom' !== linkTo );
    7342         }
    7343 
    7344         $input.removeClass( 'hidden' );
    7345 
    7346         // If the input is visible, focus and select its contents.
    7347         if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
    7348             $input.focus()[0].select();
    7349         }
    7350     }
    7351 });
    7352 
    7353 module.exports = AttachmentDisplay;
    7354 
    7355 },{}],61:[function(require,module,exports){
    7356 /**
    7357  * wp.media.view.Settings.Gallery
    7358  *
    7359  * @class
    7360  * @augments wp.media.view.Settings
    7361  * @augments wp.media.View
    7362  * @augments wp.Backbone.View
    7363  * @augments Backbone.View
    7364  */
    7365 var Gallery = wp.media.view.Settings.extend({
    7366     className: 'collection-settings gallery-settings',
    7367     template:  wp.template('gallery-settings')
    7368 });
    7369 
    7370 module.exports = Gallery;
    7371 
    7372 },{}],62:[function(require,module,exports){
    7373 /**
    7374  * wp.media.view.Settings.Playlist
    7375  *
    7376  * @class
    7377  * @augments wp.media.view.Settings
    7378  * @augments wp.media.View
    7379  * @augments wp.Backbone.View
    7380  * @augments Backbone.View
    7381  */
    7382 var Playlist = wp.media.view.Settings.extend({
    7383     className: 'collection-settings playlist-settings',
    7384     template:  wp.template('playlist-settings')
    7385 });
    7386 
    7387 module.exports = Playlist;
    7388 
    7389 },{}],63:[function(require,module,exports){
    7390 /**
    7391  * wp.media.view.Sidebar
    7392  *
    7393  * @class
    7394  * @augments wp.media.view.PriorityList
    7395  * @augments wp.media.View
    7396  * @augments wp.Backbone.View
    7397  * @augments Backbone.View
    7398  */
    7399 var Sidebar = wp.media.view.PriorityList.extend({
    7400     className: 'media-sidebar'
    7401 });
    7402 
    7403 module.exports = Sidebar;
    7404 
    7405 },{}],64:[function(require,module,exports){
     8645module.exports = Cropper;
     8646
     8647
     8648/***/ }),
     8649/* 97 */
     8650/***/ (function(module, exports) {
     8651
    74068652/**
    74078653 * wp.media.view.SiteIconCropper
     
    74468692module.exports = SiteIconCropper;
    74478693
    7448 },{}],65:[function(require,module,exports){
     8694
     8695/***/ }),
     8696/* 98 */
     8697/***/ (function(module, exports) {
     8698
    74498699/**
    74508700 * wp.media.view.SiteIconPreview
     
    75028752module.exports = SiteIconPreview;
    75038753
    7504 },{}],66:[function(require,module,exports){
     8754
     8755/***/ }),
     8756/* 99 */
     8757/***/ (function(module, exports) {
     8758
     8759/**
     8760 * wp.media.view.EditImage
     8761 *
     8762 * @class
     8763 * @augments wp.media.View
     8764 * @augments wp.Backbone.View
     8765 * @augments Backbone.View
     8766 */
     8767var View = wp.media.View,
     8768    EditImage;
     8769
     8770EditImage = View.extend({
     8771    className: 'image-editor',
     8772    template: wp.template('image-editor'),
     8773
     8774    initialize: function( options ) {
     8775        this.editor = window.imageEdit;
     8776        this.controller = options.controller;
     8777        View.prototype.initialize.apply( this, arguments );
     8778    },
     8779
     8780    prepare: function() {
     8781        return this.model.toJSON();
     8782    },
     8783
     8784    loadEditor: function() {
     8785        var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
     8786        dfd.done( _.bind( this.focus, this ) );
     8787    },
     8788
     8789    focus: function() {
     8790        this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
     8791    },
     8792
     8793    back: function() {
     8794        var lastState = this.controller.lastState();
     8795        this.controller.setState( lastState );
     8796    },
     8797
     8798    refresh: function() {
     8799        this.model.fetch();
     8800    },
     8801
     8802    save: function() {
     8803        var lastState = this.controller.lastState();
     8804
     8805        this.model.fetch().done( _.bind( function() {
     8806            this.controller.setState( lastState );
     8807        }, this ) );
     8808    }
     8809
     8810});
     8811
     8812module.exports = EditImage;
     8813
     8814
     8815/***/ }),
     8816/* 100 */
     8817/***/ (function(module, exports) {
     8818
    75058819/**
    75068820 * wp.media.view.Spinner
     
    75378851module.exports = Spinner;
    75388852
    7539 },{}],67:[function(require,module,exports){
    7540 /**
    7541  * wp.media.view.Toolbar
    7542  *
    7543  * A toolbar which consists of a primary and a secondary section. Each sections
    7544  * can be filled with views.
    7545  *
    7546  * @class
    7547  * @augments wp.media.View
    7548  * @augments wp.Backbone.View
    7549  * @augments Backbone.View
    7550  */
    7551 var View = wp.media.View,
    7552     Toolbar;
    7553 
    7554 Toolbar = View.extend({
    7555     tagName:   'div',
    7556     className: 'media-toolbar',
    7557 
    7558     initialize: function() {
    7559         var state = this.controller.state(),
    7560             selection = this.selection = state.get('selection'),
    7561             library = this.library = state.get('library');
    7562 
    7563         this._views = {};
    7564 
    7565         // The toolbar is composed of two `PriorityList` views.
    7566         this.primary   = new wp.media.view.PriorityList();
    7567         this.secondary = new wp.media.view.PriorityList();
    7568         this.primary.$el.addClass('media-toolbar-primary search-form');
    7569         this.secondary.$el.addClass('media-toolbar-secondary');
    7570 
    7571         this.views.set([ this.secondary, this.primary ]);
    7572 
    7573         if ( this.options.items ) {
    7574             this.set( this.options.items, { silent: true });
    7575         }
    7576 
    7577         if ( ! this.options.silent ) {
    7578             this.render();
    7579         }
    7580 
    7581         if ( selection ) {
    7582             selection.on( 'add remove reset', this.refresh, this );
    7583         }
    7584 
    7585         if ( library ) {
    7586             library.on( 'add remove reset', this.refresh, this );
    7587         }
    7588     },
    7589     /**
    7590      * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
    7591      */
    7592     dispose: function() {
    7593         if ( this.selection ) {
    7594             this.selection.off( null, null, this );
    7595         }
    7596 
    7597         if ( this.library ) {
    7598             this.library.off( null, null, this );
    7599         }
    7600         /**
    7601          * call 'dispose' directly on the parent class
    7602          */
    7603         return View.prototype.dispose.apply( this, arguments );
    7604     },
    7605 
    7606     ready: function() {
    7607         this.refresh();
    7608     },
    7609 
    7610     /**
    7611      * @param {string} id
    7612      * @param {Backbone.View|Object} view
    7613      * @param {Object} [options={}]
    7614      * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
    7615      */
    7616     set: function( id, view, options ) {
    7617         var list;
    7618         options = options || {};
    7619 
    7620         // Accept an object with an `id` : `view` mapping.
    7621         if ( _.isObject( id ) ) {
    7622             _.each( id, function( view, id ) {
    7623                 this.set( id, view, { silent: true });
    7624             }, this );
    7625 
    7626         } else {
    7627             if ( ! ( view instanceof Backbone.View ) ) {
    7628                 view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
    7629                 view = new wp.media.view.Button( view ).render();
    7630             }
    7631 
    7632             view.controller = view.controller || this.controller;
    7633 
    7634             this._views[ id ] = view;
    7635 
    7636             list = view.options.priority < 0 ? 'secondary' : 'primary';
    7637             this[ list ].set( id, view, options );
    7638         }
    7639 
    7640         if ( ! options.silent ) {
    7641             this.refresh();
    7642         }
    7643 
    7644         return this;
    7645     },
    7646     /**
    7647      * @param {string} id
    7648      * @returns {wp.media.view.Button}
    7649      */
    7650     get: function( id ) {
    7651         return this._views[ id ];
    7652     },
    7653     /**
    7654      * @param {string} id
    7655      * @param {Object} options
    7656      * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
    7657      */
    7658     unset: function( id, options ) {
    7659         delete this._views[ id ];
    7660         this.primary.unset( id, options );
    7661         this.secondary.unset( id, options );
    7662 
    7663         if ( ! options || ! options.silent ) {
    7664             this.refresh();
    7665         }
    7666         return this;
    7667     },
    7668 
    7669     refresh: function() {
    7670         var state = this.controller.state(),
    7671             library = state.get('library'),
    7672             selection = state.get('selection');
    7673 
    7674         _.each( this._views, function( button ) {
    7675             if ( ! button.model || ! button.options || ! button.options.requires ) {
    7676                 return;
    7677             }
    7678 
    7679             var requires = button.options.requires,
    7680                 disabled = false;
    7681 
    7682             // Prevent insertion of attachments if any of them are still uploading
    7683             disabled = _.some( selection.models, function( attachment ) {
    7684                 return attachment.get('uploading') === true;
    7685             });
    7686 
    7687             if ( requires.selection && selection && ! selection.length ) {
    7688                 disabled = true;
    7689             } else if ( requires.library && library && ! library.length ) {
    7690                 disabled = true;
    7691             }
    7692             button.model.set( 'disabled', disabled );
    7693         });
    7694     }
    7695 });
    7696 
    7697 module.exports = Toolbar;
    7698 
    7699 },{}],68:[function(require,module,exports){
    7700 /**
    7701  * wp.media.view.Toolbar.Embed
    7702  *
    7703  * @class
    7704  * @augments wp.media.view.Toolbar.Select
    7705  * @augments wp.media.view.Toolbar
    7706  * @augments wp.media.View
    7707  * @augments wp.Backbone.View
    7708  * @augments Backbone.View
    7709  */
    7710 var Select = wp.media.view.Toolbar.Select,
    7711     l10n = wp.media.view.l10n,
    7712     Embed;
    7713 
    7714 Embed = Select.extend({
    7715     initialize: function() {
    7716         _.defaults( this.options, {
    7717             text: l10n.insertIntoPost,
    7718             requires: false
    7719         });
    7720         // Call 'initialize' directly on the parent class.
    7721         Select.prototype.initialize.apply( this, arguments );
    7722     },
    7723 
    7724     refresh: function() {
    7725         var url = this.controller.state().props.get('url');
    7726         this.get('select').model.set( 'disabled', ! url || url === 'http://' );
    7727         /**
    7728          * call 'refresh' directly on the parent class
    7729          */
    7730         Select.prototype.refresh.apply( this, arguments );
    7731     }
    7732 });
    7733 
    7734 module.exports = Embed;
    7735 
    7736 },{}],69:[function(require,module,exports){
    7737 /**
    7738  * wp.media.view.Toolbar.Select
    7739  *
    7740  * @class
    7741  * @augments wp.media.view.Toolbar
    7742  * @augments wp.media.View
    7743  * @augments wp.Backbone.View
    7744  * @augments Backbone.View
    7745  */
    7746 var Toolbar = wp.media.view.Toolbar,
    7747     l10n = wp.media.view.l10n,
    7748     Select;
    7749 
    7750 Select = Toolbar.extend({
    7751     initialize: function() {
    7752         var options = this.options;
    7753 
    7754         _.bindAll( this, 'clickSelect' );
    7755 
    7756         _.defaults( options, {
    7757             event: 'select',
    7758             state: false,
    7759             reset: true,
    7760             close: true,
    7761             text:  l10n.select,
    7762 
    7763             // Does the button rely on the selection?
    7764             requires: {
    7765                 selection: true
    7766             }
    7767         });
    7768 
    7769         options.items = _.defaults( options.items || {}, {
    7770             select: {
    7771                 style:    'primary',
    7772                 text:     options.text,
    7773                 priority: 80,
    7774                 click:    this.clickSelect,
    7775                 requires: options.requires
    7776             }
    7777         });
    7778         // Call 'initialize' directly on the parent class.
    7779         Toolbar.prototype.initialize.apply( this, arguments );
    7780     },
    7781 
    7782     clickSelect: function() {
    7783         var options = this.options,
    7784             controller = this.controller;
    7785 
    7786         if ( options.close ) {
    7787             controller.close();
    7788         }
    7789 
    7790         if ( options.event ) {
    7791             controller.state().trigger( options.event );
    7792         }
    7793 
    7794         if ( options.state ) {
    7795             controller.setState( options.state );
    7796         }
    7797 
    7798         if ( options.reset ) {
    7799             controller.reset();
    7800         }
    7801     }
    7802 });
    7803 
    7804 module.exports = Select;
    7805 
    7806 },{}],70:[function(require,module,exports){
    7807 /**
    7808  * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
    7809  * and relays drag'n'dropped files to a media workflow.
    7810  *
    7811  * wp.media.view.EditorUploader
    7812  *
    7813  * @class
    7814  * @augments wp.media.View
    7815  * @augments wp.Backbone.View
    7816  * @augments Backbone.View
    7817  */
    7818 var View = wp.media.View,
    7819     l10n = wp.media.view.l10n,
    7820     $ = jQuery,
    7821     EditorUploader;
    7822 
    7823 EditorUploader = View.extend({
    7824     tagName:   'div',
    7825     className: 'uploader-editor',
    7826     template:  wp.template( 'uploader-editor' ),
    7827 
    7828     localDrag: false,
    7829     overContainer: false,
    7830     overDropzone: false,
    7831     draggingFile: null,
    7832 
    7833     /**
    7834      * Bind drag'n'drop events to callbacks.
    7835      */
    7836     initialize: function() {
    7837         this.initialized = false;
    7838 
    7839         // Bail if not enabled or UA does not support drag'n'drop or File API.
    7840         if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
    7841             return this;
    7842         }
    7843 
    7844         this.$document = $(document);
    7845         this.dropzones = [];
    7846         this.files = [];
    7847 
    7848         this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
    7849         this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
    7850         this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
    7851         this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
    7852 
    7853         this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
    7854         this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
    7855 
    7856         this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
    7857             this.localDrag = event.type === 'dragstart';
    7858         }, this ) );
    7859 
    7860         this.initialized = true;
    7861         return this;
    7862     },
    7863 
    7864     /**
    7865      * Check browser support for drag'n'drop.
    7866      *
    7867      * @return Boolean
    7868      */
    7869     browserSupport: function() {
    7870         var supports = false, div = document.createElement('div');
    7871 
    7872         supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
    7873         supports = supports && !! ( window.File && window.FileList && window.FileReader );
    7874         return supports;
    7875     },
    7876 
    7877     isDraggingFile: function( event ) {
    7878         if ( this.draggingFile !== null ) {
    7879             return this.draggingFile;
    7880         }
    7881 
    7882         if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
    7883             return false;
    7884         }
    7885 
    7886         this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
    7887             _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
    7888 
    7889         return this.draggingFile;
    7890     },
    7891 
    7892     refresh: function( e ) {
    7893         var dropzone_id;
    7894         for ( dropzone_id in this.dropzones ) {
    7895             // Hide the dropzones only if dragging has left the screen.
    7896             this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
    7897         }
    7898 
    7899         if ( ! _.isUndefined( e ) ) {
    7900             $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
    7901         }
    7902 
    7903         if ( ! this.overContainer && ! this.overDropzone ) {
    7904             this.draggingFile = null;
    7905         }
    7906 
    7907         return this;
    7908     },
    7909 
    7910     render: function() {
    7911         if ( ! this.initialized ) {
    7912             return this;
    7913         }
    7914 
    7915         View.prototype.render.apply( this, arguments );
    7916         $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
    7917         return this;
    7918     },
    7919 
    7920     attach: function( index, editor ) {
    7921         // Attach a dropzone to an editor.
    7922         var dropzone = this.$el.clone();
    7923         this.dropzones.push( dropzone );
    7924         $( editor ).append( dropzone );
    7925         return this;
    7926     },
    7927 
    7928     /**
    7929      * When a file is dropped on the editor uploader, open up an editor media workflow
    7930      * and upload the file immediately.
    7931      *
    7932      * @param  {jQuery.Event} event The 'drop' event.
    7933      */
    7934     drop: function( event ) {
    7935         var $wrap, uploadView;
    7936 
    7937         this.containerDragleave( event );
    7938         this.dropzoneDragleave( event );
    7939 
    7940         this.files = event.originalEvent.dataTransfer.files;
    7941         if ( this.files.length < 1 ) {
    7942             return;
    7943         }
    7944 
    7945         // Set the active editor to the drop target.
    7946         $wrap = $( event.target ).parents( '.wp-editor-wrap' );
    7947         if ( $wrap.length > 0 && $wrap[0].id ) {
    7948             window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
    7949         }
    7950 
    7951         if ( ! this.workflow ) {
    7952             this.workflow = wp.media.editor.open( window.wpActiveEditor, {
    7953                 frame:    'post',
    7954                 state:    'insert',
    7955                 title:    l10n.addMedia,
    7956                 multiple: true
    7957             });
    7958 
    7959             uploadView = this.workflow.uploader;
    7960 
    7961             if ( uploadView.uploader && uploadView.uploader.ready ) {
    7962                 this.addFiles.apply( this );
    7963             } else {
    7964                 this.workflow.on( 'uploader:ready', this.addFiles, this );
    7965             }
    7966         } else {
    7967             this.workflow.state().reset();
    7968             this.addFiles.apply( this );
    7969             this.workflow.open();
    7970         }
    7971 
    7972         return false;
    7973     },
    7974 
    7975     /**
    7976      * Add the files to the uploader.
    7977      */
    7978     addFiles: function() {
    7979         if ( this.files.length ) {
    7980             this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
    7981             this.files = [];
    7982         }
    7983         return this;
    7984     },
    7985 
    7986     containerDragover: function( event ) {
    7987         if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    7988             return;
    7989         }
    7990 
    7991         this.overContainer = true;
    7992         this.refresh();
    7993     },
    7994 
    7995     containerDragleave: function() {
    7996         this.overContainer = false;
    7997 
    7998         // Throttle dragleave because it's called when bouncing from some elements to others.
    7999         _.delay( _.bind( this.refresh, this ), 50 );
    8000     },
    8001 
    8002     dropzoneDragover: function( event ) {
    8003         if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    8004             return;
    8005         }
    8006 
    8007         this.overDropzone = true;
    8008         this.refresh( event );
    8009         return false;
    8010     },
    8011 
    8012     dropzoneDragleave: function( e ) {
    8013         this.overDropzone = false;
    8014         _.delay( _.bind( this.refresh, this, e ), 50 );
    8015     },
    8016 
    8017     click: function( e ) {
    8018         // In the rare case where the dropzone gets stuck, hide it on click.
    8019         this.containerDragleave( e );
    8020         this.dropzoneDragleave( e );
    8021         this.localDrag = false;
    8022     }
    8023 });
    8024 
    8025 module.exports = EditorUploader;
    8026 
    8027 },{}],71:[function(require,module,exports){
    8028 /**
    8029  * wp.media.view.UploaderInline
    8030  *
    8031  * The inline uploader that shows up in the 'Upload Files' tab.
    8032  *
    8033  * @class
    8034  * @augments wp.media.View
    8035  * @augments wp.Backbone.View
    8036  * @augments Backbone.View
    8037  */
    8038 var View = wp.media.View,
    8039     UploaderInline;
    8040 
    8041 UploaderInline = View.extend({
    8042     tagName:   'div',
    8043     className: 'uploader-inline',
    8044     template:  wp.template('uploader-inline'),
    8045 
    8046     events: {
    8047         'click .close': 'hide'
    8048     },
    8049 
    8050     initialize: function() {
    8051         _.defaults( this.options, {
    8052             message: '',
    8053             status:  true,
    8054             canClose: false
    8055         });
    8056 
    8057         if ( ! this.options.$browser && this.controller.uploader ) {
    8058             this.options.$browser = this.controller.uploader.$browser;
    8059         }
    8060 
    8061         if ( _.isUndefined( this.options.postId ) ) {
    8062             this.options.postId = wp.media.view.settings.post.id;
    8063         }
    8064 
    8065         if ( this.options.status ) {
    8066             this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
    8067                 controller: this.controller
    8068             }) );
    8069         }
    8070     },
    8071 
    8072     prepare: function() {
    8073         var suggestedWidth = this.controller.state().get('suggestedWidth'),
    8074             suggestedHeight = this.controller.state().get('suggestedHeight'),
    8075             data = {};
    8076 
    8077         data.message = this.options.message;
    8078         data.canClose = this.options.canClose;
    8079 
    8080         if ( suggestedWidth && suggestedHeight ) {
    8081             data.suggestedWidth = suggestedWidth;
    8082             data.suggestedHeight = suggestedHeight;
    8083         }
    8084 
    8085         return data;
    8086     },
    8087     /**
    8088      * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    8089      */
    8090     dispose: function() {
    8091         if ( this.disposing ) {
    8092             /**
    8093              * call 'dispose' directly on the parent class
    8094              */
    8095             return View.prototype.dispose.apply( this, arguments );
    8096         }
    8097 
    8098         // Run remove on `dispose`, so we can be sure to refresh the
    8099         // uploader with a view-less DOM. Track whether we're disposing
    8100         // so we don't trigger an infinite loop.
    8101         this.disposing = true;
    8102         return this.remove();
    8103     },
    8104     /**
    8105      * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    8106      */
    8107     remove: function() {
    8108         /**
    8109          * call 'remove' directly on the parent class
    8110          */
    8111         var result = View.prototype.remove.apply( this, arguments );
    8112 
    8113         _.defer( _.bind( this.refresh, this ) );
    8114         return result;
    8115     },
    8116 
    8117     refresh: function() {
    8118         var uploader = this.controller.uploader;
    8119 
    8120         if ( uploader ) {
    8121             uploader.refresh();
    8122         }
    8123     },
    8124     /**
    8125      * @returns {wp.media.view.UploaderInline}
    8126      */
    8127     ready: function() {
    8128         var $browser = this.options.$browser,
    8129             $placeholder;
    8130 
    8131         if ( this.controller.uploader ) {
    8132             $placeholder = this.$('.browser');
    8133 
    8134             // Check if we've already replaced the placeholder.
    8135             if ( $placeholder[0] === $browser[0] ) {
    8136                 return;
    8137             }
    8138 
    8139             $browser.detach().text( $placeholder.text() );
    8140             $browser[0].className = $placeholder[0].className;
    8141             $placeholder.replaceWith( $browser.show() );
    8142         }
    8143 
    8144         this.refresh();
    8145         return this;
    8146     },
    8147     show: function() {
    8148         this.$el.removeClass( 'hidden' );
    8149     },
    8150     hide: function() {
    8151         this.$el.addClass( 'hidden' );
    8152     }
    8153 
    8154 });
    8155 
    8156 module.exports = UploaderInline;
    8157 
    8158 },{}],72:[function(require,module,exports){
    8159 /**
    8160  * wp.media.view.UploaderStatusError
    8161  *
    8162  * @class
    8163  * @augments wp.media.View
    8164  * @augments wp.Backbone.View
    8165  * @augments Backbone.View
    8166  */
    8167 var UploaderStatusError = wp.media.View.extend({
    8168     className: 'upload-error',
    8169     template:  wp.template('uploader-status-error')
    8170 });
    8171 
    8172 module.exports = UploaderStatusError;
    8173 
    8174 },{}],73:[function(require,module,exports){
    8175 /**
    8176  * wp.media.view.UploaderStatus
    8177  *
    8178  * An uploader status for on-going uploads.
    8179  *
    8180  * @class
    8181  * @augments wp.media.View
    8182  * @augments wp.Backbone.View
    8183  * @augments Backbone.View
    8184  */
    8185 var View = wp.media.View,
    8186     UploaderStatus;
    8187 
    8188 UploaderStatus = View.extend({
    8189     className: 'media-uploader-status',
    8190     template:  wp.template('uploader-status'),
    8191 
    8192     events: {
    8193         'click .upload-dismiss-errors': 'dismiss'
    8194     },
    8195 
    8196     initialize: function() {
    8197         this.queue = wp.Uploader.queue;
    8198         this.queue.on( 'add remove reset', this.visibility, this );
    8199         this.queue.on( 'add remove reset change:percent', this.progress, this );
    8200         this.queue.on( 'add remove reset change:uploading', this.info, this );
    8201 
    8202         this.errors = wp.Uploader.errors;
    8203         this.errors.reset();
    8204         this.errors.on( 'add remove reset', this.visibility, this );
    8205         this.errors.on( 'add', this.error, this );
    8206     },
    8207     /**
    8208      * @global wp.Uploader
    8209      * @returns {wp.media.view.UploaderStatus}
    8210      */
    8211     dispose: function() {
    8212         wp.Uploader.queue.off( null, null, this );
    8213         /**
    8214          * call 'dispose' directly on the parent class
    8215          */
    8216         View.prototype.dispose.apply( this, arguments );
    8217         return this;
    8218     },
    8219 
    8220     visibility: function() {
    8221         this.$el.toggleClass( 'uploading', !! this.queue.length );
    8222         this.$el.toggleClass( 'errors', !! this.errors.length );
    8223         this.$el.toggle( !! this.queue.length || !! this.errors.length );
    8224     },
    8225 
    8226     ready: function() {
    8227         _.each({
    8228             '$bar':      '.media-progress-bar div',
    8229             '$index':    '.upload-index',
    8230             '$total':    '.upload-total',
    8231             '$filename': '.upload-filename'
    8232         }, function( selector, key ) {
    8233             this[ key ] = this.$( selector );
    8234         }, this );
    8235 
    8236         this.visibility();
    8237         this.progress();
    8238         this.info();
    8239     },
    8240 
    8241     progress: function() {
    8242         var queue = this.queue,
    8243             $bar = this.$bar;
    8244 
    8245         if ( ! $bar || ! queue.length ) {
    8246             return;
    8247         }
    8248 
    8249         $bar.width( ( queue.reduce( function( memo, attachment ) {
    8250             if ( ! attachment.get('uploading') ) {
    8251                 return memo + 100;
    8252             }
    8253 
    8254             var percent = attachment.get('percent');
    8255             return memo + ( _.isNumber( percent ) ? percent : 100 );
    8256         }, 0 ) / queue.length ) + '%' );
    8257     },
    8258 
    8259     info: function() {
    8260         var queue = this.queue,
    8261             index = 0, active;
    8262 
    8263         if ( ! queue.length ) {
    8264             return;
    8265         }
    8266 
    8267         active = this.queue.find( function( attachment, i ) {
    8268             index = i;
    8269             return attachment.get('uploading');
    8270         });
    8271 
    8272         this.$index.text( index + 1 );
    8273         this.$total.text( queue.length );
    8274         this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
    8275     },
    8276     /**
    8277      * @param {string} filename
    8278      * @returns {string}
    8279      */
    8280     filename: function( filename ) {
    8281         return _.escape( filename );
    8282     },
    8283     /**
    8284      * @param {Backbone.Model} error
    8285      */
    8286     error: function( error ) {
    8287         this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    8288             filename: this.filename( error.get('file').name ),
    8289             message:  error.get('message')
    8290         }), { at: 0 });
    8291     },
    8292 
    8293     /**
    8294      * @global wp.Uploader
    8295      *
    8296      * @param {Object} event
    8297      */
    8298     dismiss: function( event ) {
    8299         var errors = this.views.get('.upload-errors');
    8300 
    8301         event.preventDefault();
    8302 
    8303         if ( errors ) {
    8304             _.invoke( errors, 'remove' );
    8305         }
    8306         wp.Uploader.errors.reset();
    8307     }
    8308 });
    8309 
    8310 module.exports = UploaderStatus;
    8311 
    8312 },{}],74:[function(require,module,exports){
    8313 /**
    8314  * wp.media.view.UploaderWindow
    8315  *
    8316  * An uploader window that allows for dragging and dropping media.
    8317  *
    8318  * @class
    8319  * @augments wp.media.View
    8320  * @augments wp.Backbone.View
    8321  * @augments Backbone.View
    8322  *
    8323  * @param {object} [options]                   Options hash passed to the view.
    8324  * @param {object} [options.uploader]          Uploader properties.
    8325  * @param {jQuery} [options.uploader.browser]
    8326  * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
    8327  * @param {object} [options.uploader.params]
    8328  */
    8329 var $ = jQuery,
    8330     UploaderWindow;
    8331 
    8332 UploaderWindow = wp.media.View.extend({
    8333     tagName:   'div',
    8334     className: 'uploader-window',
    8335     template:  wp.template('uploader-window'),
    8336 
    8337     initialize: function() {
    8338         var uploader;
    8339 
    8340         this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');
    8341 
    8342         uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
    8343             dropzone:  this.$el,
    8344             browser:   this.$browser,
    8345             params:    {}
    8346         });
    8347 
    8348         // Ensure the dropzone is a jQuery collection.
    8349         if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
    8350             uploader.dropzone = $( uploader.dropzone );
    8351         }
    8352 
    8353         this.controller.on( 'activate', this.refresh, this );
    8354 
    8355         this.controller.on( 'detach', function() {
    8356             this.$browser.remove();
    8357         }, this );
    8358     },
    8359 
    8360     refresh: function() {
    8361         if ( this.uploader ) {
    8362             this.uploader.refresh();
    8363         }
    8364     },
    8365 
    8366     ready: function() {
    8367         var postId = wp.media.view.settings.post.id,
    8368             dropzone;
    8369 
    8370         // If the uploader already exists, bail.
    8371         if ( this.uploader ) {
    8372             return;
    8373         }
    8374 
    8375         if ( postId ) {
    8376             this.options.uploader.params.post_id = postId;
    8377         }
    8378         this.uploader = new wp.Uploader( this.options.uploader );
    8379 
    8380         dropzone = this.uploader.dropzone;
    8381         dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
    8382         dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
    8383 
    8384         $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
    8385     },
    8386 
    8387     _ready: function() {
    8388         this.controller.trigger( 'uploader:ready' );
    8389     },
    8390 
    8391     show: function() {
    8392         var $el = this.$el.show();
    8393 
    8394         // Ensure that the animation is triggered by waiting until
    8395         // the transparent element is painted into the DOM.
    8396         _.defer( function() {
    8397             $el.css({ opacity: 1 });
    8398         });
    8399     },
    8400 
    8401     hide: function() {
    8402         var $el = this.$el.css({ opacity: 0 });
    8403 
    8404         wp.media.transition( $el ).done( function() {
    8405             // Transition end events are subject to race conditions.
    8406             // Make sure that the value is set as intended.
    8407             if ( '0' === $el.css('opacity') ) {
    8408                 $el.hide();
    8409             }
    8410         });
    8411 
    8412         // https://core.trac.wordpress.org/ticket/27341
    8413         _.delay( function() {
    8414             if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
    8415                 $el.hide();
    8416             }
    8417         }, 500 );
    8418     }
    8419 });
    8420 
    8421 module.exports = UploaderWindow;
    8422 
    8423 },{}],75:[function(require,module,exports){
    8424 /**
    8425  * wp.media.View
    8426  *
    8427  * The base view class for media.
    8428  *
    8429  * Undelegating events, removing events from the model, and
    8430  * removing events from the controller mirror the code for
    8431  * `Backbone.View.dispose` in Backbone 0.9.8 development.
    8432  *
    8433  * This behavior has since been removed, and should not be used
    8434  * outside of the media manager.
    8435  *
    8436  * @class
    8437  * @augments wp.Backbone.View
    8438  * @augments Backbone.View
    8439  */
    8440 var View = wp.Backbone.View.extend({
    8441     constructor: function( options ) {
    8442         if ( options && options.controller ) {
    8443             this.controller = options.controller;
    8444         }
    8445         wp.Backbone.View.apply( this, arguments );
    8446     },
    8447     /**
    8448      * @todo The internal comment mentions this might have been a stop-gap
    8449      *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
    8450      *       care of this in Backbone.View now.
    8451      *
    8452      * @returns {wp.media.View} Returns itself to allow chaining
    8453      */
    8454     dispose: function() {
    8455         // Undelegating events, removing events from the model, and
    8456         // removing events from the controller mirror the code for
    8457         // `Backbone.View.dispose` in Backbone 0.9.8 development.
    8458         this.undelegateEvents();
    8459 
    8460         if ( this.model && this.model.off ) {
    8461             this.model.off( null, null, this );
    8462         }
    8463 
    8464         if ( this.collection && this.collection.off ) {
    8465             this.collection.off( null, null, this );
    8466         }
    8467 
    8468         // Unbind controller events.
    8469         if ( this.controller && this.controller.off ) {
    8470             this.controller.off( null, null, this );
    8471         }
    8472 
    8473         return this;
    8474     },
    8475     /**
    8476      * @returns {wp.media.View} Returns itself to allow chaining
    8477      */
    8478     remove: function() {
    8479         this.dispose();
    8480         /**
    8481          * call 'remove' directly on the parent class
    8482          */
    8483         return wp.Backbone.View.prototype.remove.apply( this, arguments );
    8484     }
    8485 });
    8486 
    8487 module.exports = View;
    8488 
    8489 },{}]},{},[19]);
     8853
     8854/***/ })
     8855/******/ ]));
  • branches/4.3/src/wp-includes/pluggable.php

    r45982 r46499  
    10821082 */
    10831083function check_admin_referer( $action = -1, $query_arg = '_wpnonce' ) {
    1084     if ( -1 == $action )
    1085         _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2' );
     1084    if ( -1 === $action )
     1085        _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2.0' );
    10861086
    10871087    $adminurl = strtolower(admin_url());
     
    11001100    do_action( 'check_admin_referer', $action, $result );
    11011101
    1102     if ( ! $result && ! ( -1 == $action && strpos( $referer, $adminurl ) === 0 ) ) {
     1102    if ( ! $result && ! ( -1 === $action && strpos( $referer, $adminurl ) === 0 ) ) {
    11031103        wp_nonce_ays( $action );
    11041104        die();
     
    11251125 */
    11261126function check_ajax_referer( $action = -1, $query_arg = false, $die = true ) {
     1127    if ( -1 === $action )
     1128        _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2.0' );
     1129
    11271130    $nonce = '';
    11281131
     
    24072410}
    24082411endif;
    2409 
  • branches/4.3/src/wp-includes/query.php

    r39960 r46499  
    14011401            , 'attachment_id'
    14021402            , 'name'
    1403             , 'static'
    14041403            , 'pagename'
    14051404            , 'page_id'
     
    16051604            // post is being queried.
    16061605            $this->is_single = true;
    1607         } elseif ( '' != $qv['static'] || '' != $qv['pagename'] || !empty($qv['page_id']) ) {
     1606        } elseif ( '' != $qv['pagename'] || !empty($qv['page_id']) ) {
    16081607            $this->is_page = true;
    16091608            $this->is_single = false;
  • branches/4.3/tests/phpunit/tests/auth.php

    r33019 r46499  
    111111    }
    112112
     113    /**
     114     * @ticket 36361
     115     */
     116    public function test_check_admin_referer_with_no_action_triggers_doing_it_wrong() {
     117        $this->setExpectedIncorrectUsage( 'check_admin_referer' );
     118
     119        // A valid nonce needs to be set so the check doesn't die()
     120        $_REQUEST['_wpnonce'] = wp_create_nonce( -1 );
     121        $result = check_admin_referer();
     122        $this->assertSame( 1, $result );
     123
     124        unset( $_REQUEST['_wpnonce'] );
     125    }
     126
     127    /**
     128     * @ticket 36361
     129     */
     130    public function test_check_ajax_referer_with_no_action_triggers_doing_it_wrong() {
     131        $this->setExpectedIncorrectUsage( 'check_ajax_referer' );
     132
     133        // A valid nonce needs to be set so the check doesn't die()
     134        $_REQUEST['_wpnonce'] = wp_create_nonce( -1 );
     135        $result = check_ajax_referer();
     136        $this->assertSame( 1, $result );
     137
     138        unset( $_REQUEST['_wpnonce'] );
     139    }
     140
    113141    function test_password_length_limit() {
    114142        $passwords = array(
Note: See TracChangeset for help on using the changeset viewer.