Make WordPress Core

Changeset 46499 for branches/4.3


Ignore:
Timestamp:
10/14/2019 07:11:50 PM (7 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.