Make WordPress Core

Changeset 50019


Ignore:
Timestamp:
01/25/2021 08:15:27 PM (5 years ago)
Author:
desrosj
Message:

Build/Test Tools: Correct JavaScript file in the 4.3 branch.

In [46499], a JavaScript file was unintentionally changed. This restores that file to the correct state.

Partially reverts [46499].
See #52367.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/4.3/src/wp-includes/js/media-views.js

    r46499 r50019  
    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 
    70 var media = wp.media,
     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){
     2/**
     3 * wp.media.controller.CollectionAdd
     4 *
     5 * A state for adding attachments to a collection (e.g. video playlist).
     6 *
     7 * @class
     8 * @augments wp.media.controller.Library
     9 * @augments wp.media.controller.State
     10 * @augments Backbone.Model
     11 *
     12 * @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.
     15 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     16 * @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.
     18 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     19 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     20 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     21 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     22 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     23 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     24 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     25 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     26 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     27 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     28 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     29 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     30 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     31 *                                                                          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').
     34 */
     35var Selection = wp.media.model.Selection,
     36    Library = wp.media.controller.Library,
     37    CollectionAdd;
     38
     39CollectionAdd = Library.extend({
     40    defaults: _.defaults( {
     41        // Selection defaults. @see media.model.Selection
     42        multiple:      'add',
     43        // Attachments browser defaults. @see media.view.AttachmentsBrowser
     44        filterable:    'uploaded',
     45
     46        priority:      100,
     47        syncSelection: false
     48    }, Library.prototype.defaults ),
     49
     50    /**
     51     * @since 3.9.0
     52     */
     53    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`.
     65        if ( ! this.get('library') ) {
     66            this.set( 'library', wp.media.query({ type: this.get('type') }) );
     67        }
     68        Library.prototype.initialize.apply( this, arguments );
     69    },
     70
     71    /**
     72     * @since 3.9.0
     73     */
     74    activate: function() {
     75        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 );
     81        }
     82
     83        // Accepts attachments that exist in the original library and
     84        // that do not exist in gallery's library.
     85        library.validator = function( attachment ) {
     86            return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     87        };
     88
     89        // Reset the library to ensure that all attachments are re-added
     90        // to the collection. Do so silently, as calling `observe` will
     91        // trigger the `reset` event.
     92        library.reset( library.mirroring.models, { silent: true });
     93        library.observe( edit );
     94        this.set('editLibrary', edit);
     95
     96        Library.prototype.activate.apply( this, arguments );
     97    }
     98});
     99
     100module.exports = CollectionAdd;
     101
     102},{}],2:[function(require,module,exports){
     103/**
     104 * wp.media.controller.CollectionEdit
     105 *
     106 * A state for editing a collection, which is used by audio and video playlists,
     107 * and can be used for other collections.
     108 *
     109 * @class
     110 * @augments wp.media.controller.Library
     111 * @augments wp.media.controller.State
     112 * @augments Backbone.Model
     113 *
     114 * @param {object}                     [attributes]                      The attributes hash passed to the state.
     115 * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
     116 * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
     117 *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
     118 * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
     119 * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
     120 * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
     121 * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
     122 * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     123 * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
     124 * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
     125 * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
     126 * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
     127 * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
     128 * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
     129 * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
     130 * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
     131 *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
     132 * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
     133 * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
     134 *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     135 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     136 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     137 */
     138var Library = wp.media.controller.Library,
     139    l10n = wp.media.view.l10n,
    71140    $ = jQuery,
    72     l10n;
    73 
    74 media.isTouchDevice = ( 'ontouchend' in document );
    75 
    76 // Link any localized strings.
    77 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
    78 
    79 // Link any settings.
    80 media.view.settings = l10n.settings || {};
    81 delete l10n.settings;
    82 
    83 // Copy the `post` setting over to the model settings.
    84 media.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 
     141    CollectionEdit;
     142
     143CollectionEdit = Library.extend({
     144    defaults: {
     145        multiple:         false,
     146        sortable:         true,
     147        date:             false,
     148        searchable:       false,
     149        content:          'browse',
     150        describe:         true,
     151        dragInfo:         true,
     152        idealColumnWidth: 170,
     153        editing:          false,
     154        priority:         60,
     155        SettingsView:     false,
     156        syncSelection:    false
     157    },
     158
     159    /**
     160     * @since 3.9.0
     161     */
     162    initialize: function() {
     163        var collectionType = this.get('collectionType');
     164
     165        if ( 'video' === this.get( 'type' ) ) {
     166            collectionType = 'video-' + collectionType;
     167        }
     168
     169        this.set( 'id', collectionType + '-edit' );
     170        this.set( 'toolbar', collectionType + '-edit' );
     171
     172        // If we haven't been provided a `library`, create a `Selection`.
     173        if ( ! this.get('library') ) {
     174            this.set( 'library', new wp.media.model.Selection() );
     175        }
     176        // The single `Attachment` view to be used in the `Attachments` view.
     177        if ( ! this.get('AttachmentView') ) {
     178            this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     179        }
     180        Library.prototype.initialize.apply( this, arguments );
     181    },
     182
     183    /**
     184     * @since 3.9.0
     185     */
     186    activate: function() {
     187        var library = this.get('library');
     188
     189        // Limit the library to images only.
     190        library.props.set( 'type', this.get( 'type' ) );
     191
     192        // Watch for uploaded attachments.
     193        this.get('library').observe( wp.Uploader.queue );
     194
     195        this.frame.on( 'content:render:browse', this.renderSettings, this );
     196
     197        Library.prototype.activate.apply( this, arguments );
     198    },
     199
     200    /**
     201     * @since 3.9.0
     202     */
     203    deactivate: function() {
     204        // Stop watching for uploaded attachments.
     205        this.get('library').unobserve( wp.Uploader.queue );
     206
     207        this.frame.off( 'content:render:browse', this.renderSettings, this );
     208
     209        Library.prototype.deactivate.apply( this, arguments );
     210    },
     211
     212    /**
     213     * Render the collection embed settings view in the browser sidebar.
     214     *
     215     * @todo This is against the pattern elsewhere in media. Typically the frame
     216     *       is responsible for adding region mode callbacks. Explain.
     217     *
     218     * @since 3.9.0
     219     *
     220     * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
     221     */
     222    renderSettings: function( attachmentsBrowserView ) {
     223        var library = this.get('library'),
     224            collectionType = this.get('collectionType'),
     225            dragInfoText = this.get('dragInfoText'),
     226            SettingsView = this.get('SettingsView'),
     227            obj = {};
     228
     229        if ( ! library || ! attachmentsBrowserView ) {
     230            return;
     231        }
     232
     233        library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
     234
     235        obj[ collectionType ] = new SettingsView({
     236            controller: this,
     237            model:      library[ collectionType ],
     238            priority:   40
     239        });
     240
     241        attachmentsBrowserView.sidebar.set( obj );
     242
     243        if ( dragInfoText ) {
     244            attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
     245                el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
     246                priority: -40
     247            }) );
     248        }
     249
     250        // Add the 'Reverse order' button to the toolbar.
     251        attachmentsBrowserView.toolbar.set( 'reverse', {
     252            text:     l10n.reverseOrder,
     253            priority: 80,
     254
     255            click: function() {
     256                library.reset( library.toArray().reverse() );
     257            }
     258        });
     259    }
     260});
     261
     262module.exports = CollectionEdit;
     263
     264},{}],3:[function(require,module,exports){
    105265/**
    106  * A shared event bus used to provide events into
    107  * the media workflows that 3rd-party devs can use to hook
    108  * in.
     266 * wp.media.controller.Cropper
     267 *
     268 * A state for cropping an image.
     269 *
     270 * @class
     271 * @augments wp.media.controller.State
     272 * @augments Backbone.Model
    109273 */
    110 media.events = _.extend( {}, Backbone.Events );
    111 
     274var l10n = wp.media.view.l10n,
     275    Cropper;
     276
     277Cropper = 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
     289    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            }
     347        };
     348
     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        } );
     375    }
     376});
     377
     378module.exports = Cropper;
     379
     380},{}],4:[function(require,module,exports){
    112381/**
    113  * Makes it easier to bind events using transitions.
    114  *
    115  * @param {string} selector
    116  * @param {Number} sensitivity
    117  * @returns {Promise}
     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
    118390 */
    119 media.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();
     391var Controller = wp.media.controller,
     392    CustomizeImageCropper;
     393
     394CustomizeImageCropper = 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        } );
    138409    }
    139 
    140     return deferred.promise();
    141 };
    142 
    143 media.controller.Region = __webpack_require__( 27 );
    144 media.controller.StateMachine = __webpack_require__( 28 );
    145 media.controller.State = __webpack_require__( 29 );
    146 
    147 media.selectionSync = __webpack_require__( 30 );
    148 media.controller.Library = __webpack_require__( 31 );
    149 media.controller.ImageDetails = __webpack_require__( 32 );
    150 media.controller.GalleryEdit = __webpack_require__( 33 );
    151 media.controller.GalleryAdd = __webpack_require__( 34 );
    152 media.controller.CollectionEdit = __webpack_require__( 35 );
    153 media.controller.CollectionAdd = __webpack_require__( 36 );
    154 media.controller.FeaturedImage = __webpack_require__( 37 );
    155 media.controller.ReplaceImage = __webpack_require__( 38 );
    156 media.controller.EditImage = __webpack_require__( 39 );
    157 media.controller.MediaLibrary = __webpack_require__( 40 );
    158 media.controller.Embed = __webpack_require__( 41 );
    159 media.controller.Cropper = __webpack_require__( 42 );
    160 media.controller.CustomizeImageCropper = __webpack_require__( 43 );
    161 media.controller.SiteIconCropper = __webpack_require__( 44 );
    162 
    163 media.View = __webpack_require__( 45 );
    164 media.view.Frame = __webpack_require__( 46 );
    165 media.view.MediaFrame = __webpack_require__( 47 );
    166 media.view.MediaFrame.Select = __webpack_require__( 48 );
    167 media.view.MediaFrame.Post = __webpack_require__( 49 );
    168 media.view.MediaFrame.ImageDetails = __webpack_require__( 50 );
    169 media.view.Modal = __webpack_require__( 51 );
    170 media.view.FocusManager = __webpack_require__( 52 );
    171 media.view.UploaderWindow = __webpack_require__( 53 );
    172 media.view.EditorUploader = __webpack_require__( 54 );
    173 media.view.UploaderInline = __webpack_require__( 55 );
    174 media.view.UploaderStatus = __webpack_require__( 56 );
    175 media.view.UploaderStatusError = __webpack_require__( 57 );
    176 media.view.Toolbar = __webpack_require__( 58 );
    177 media.view.Toolbar.Select = __webpack_require__( 59 );
    178 media.view.Toolbar.Embed = __webpack_require__( 60 );
    179 media.view.Button = __webpack_require__( 61 );
    180 media.view.ButtonGroup = __webpack_require__( 62 );
    181 media.view.PriorityList = __webpack_require__( 63 );
    182 media.view.MenuItem = __webpack_require__( 64 );
    183 media.view.Menu = __webpack_require__( 65 );
    184 media.view.RouterItem = __webpack_require__( 66 );
    185 media.view.Router = __webpack_require__( 67 );
    186 media.view.Sidebar = __webpack_require__( 68 );
    187 media.view.Attachment = __webpack_require__( 69 );
    188 media.view.Attachment.Library = __webpack_require__( 70 );
    189 media.view.Attachment.EditLibrary = __webpack_require__( 71 );
    190 media.view.Attachments = __webpack_require__( 72 );
    191 media.view.Search = __webpack_require__( 73 );
    192 media.view.AttachmentFilters = __webpack_require__( 74 );
    193 media.view.DateFilter = __webpack_require__( 75 );
    194 media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 );
    195 media.view.AttachmentFilters.All = __webpack_require__( 77 );
    196 media.view.AttachmentsBrowser = __webpack_require__( 78 );
    197 media.view.Selection = __webpack_require__( 79 );
    198 media.view.Attachment.Selection = __webpack_require__( 80 );
    199 media.view.Attachments.Selection = __webpack_require__( 81 );
    200 media.view.Attachment.EditSelection = __webpack_require__( 82 );
    201 media.view.Settings = __webpack_require__( 83 );
    202 media.view.Settings.AttachmentDisplay = __webpack_require__( 84 );
    203 media.view.Settings.Gallery = __webpack_require__( 85 );
    204 media.view.Settings.Playlist = __webpack_require__( 86 );
    205 media.view.Attachment.Details = __webpack_require__( 87 );
    206 media.view.AttachmentCompat = __webpack_require__( 88 );
    207 media.view.Iframe = __webpack_require__( 89 );
    208 media.view.Embed = __webpack_require__( 90 );
    209 media.view.Label = __webpack_require__( 91 );
    210 media.view.EmbedUrl = __webpack_require__( 92 );
    211 media.view.EmbedLink = __webpack_require__( 93 );
    212 media.view.EmbedImage = __webpack_require__( 94 );
    213 media.view.ImageDetails = __webpack_require__( 95 );
    214 media.view.Cropper = __webpack_require__( 96 );
    215 media.view.SiteIconCropper = __webpack_require__( 97 );
    216 media.view.SiteIconPreview = __webpack_require__( 98 );
    217 media.view.EditImage = __webpack_require__( 99 );
    218 media.view.Spinner = __webpack_require__( 100 );
    219 
    220 
    221 /***/ }),
    222 /* 27 */
    223 /***/ (function(module, exports) {
    224 
     410});
     411
     412module.exports = CustomizeImageCropper;
     413
     414},{}],5:[function(require,module,exports){
    225415/**
    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.
     416 * wp.media.controller.EditImage
     417 *
     418 * A state for editing (cropping, etc.) an image.
    237419 *
    238420 * @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.
     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.
    244432 */
    245 var Region = function( options ) {
    246     _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
    247 };
    248 
    249 // Use Backbone's self-propagating `extend` inheritance method.
    250 Region.extend = Backbone.Model.extend;
    251 
    252 _.extend( Region.prototype, {
    253     /**
    254      * Activate a mode.
     433var l10n = wp.media.view.l10n,
     434    EditImage;
     435
     436EditImage = 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
     488module.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 */
     512var l10n = wp.media.view.l10n,
     513    $ = Backbone.$,
     514    Embed;
     515
     516Embed = 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.
    255543     *
     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
     624module.exports = Embed;
     625
     626},{}],7:[function(require,module,exports){
     627/**
     628 * wp.media.controller.FeaturedImage
     629 *
     630 * A state for selecting a featured image for a post.
     631 *
     632 * @class
     633 * @augments wp.media.controller.Library
     634 * @augments wp.media.controller.State
     635 * @augments Backbone.Model
     636 *
     637 * @param {object}                     [attributes]                          The attributes hash passed to the state.
     638 * @param {string}                     [attributes.id=featured-image]        Unique identifier.
     639 * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
     640 * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
     641 *                                                                           If one is not supplied, a collection of all images will be created.
     642 * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
     643 * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
     644 *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
     645 * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
     646 * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
     647 * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
     648 * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
     649 * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
     650 * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
     651 *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
     652 * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     653 * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
     654 * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     655 * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
     656 * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
     657 */
     658var Attachment = wp.media.model.Attachment,
     659    Library = wp.media.controller.Library,
     660    l10n = wp.media.view.l10n,
     661    FeaturedImage;
     662
     663FeaturedImage = Library.extend({
     664    defaults: _.defaults({
     665        id:            'featured-image',
     666        title:         l10n.setFeaturedImageTitle,
     667        multiple:      false,
     668        filterable:    'uploaded',
     669        toolbar:       'featured-image',
     670        priority:      60,
     671        syncSelection: true
     672    }, Library.prototype.defaults ),
     673
     674    /**
     675     * @since 3.5.0
     676     */
     677    initialize: function() {
     678        var library, comparator;
     679
     680        // If we haven't been provided a `library`, create a `Selection`.
     681        if ( ! this.get('library') ) {
     682            this.set( 'library', wp.media.query({ type: 'image' }) );
     683        }
     684
     685        Library.prototype.initialize.apply( this, arguments );
     686
     687        library    = this.get('library');
     688        comparator = library.comparator;
     689
     690        // Overload the library's comparator to push items that are not in
     691        // the mirrored query to the front of the aggregate collection.
     692        library.comparator = function( a, b ) {
     693            var aInQuery = !! this.mirroring.get( a.cid ),
     694                bInQuery = !! this.mirroring.get( b.cid );
     695
     696            if ( ! aInQuery && bInQuery ) {
     697                return -1;
     698            } else if ( aInQuery && ! bInQuery ) {
     699                return 1;
     700            } else {
     701                return comparator.apply( this, arguments );
     702            }
     703        };
     704
     705        // Add all items in the selection to the library, so any featured
     706        // images that are not initially loaded still appear.
     707        library.observe( this.get('selection') );
     708    },
     709
     710    /**
     711     * @since 3.5.0
     712     */
     713    activate: function() {
     714        this.updateSelection();
     715        this.frame.on( 'open', this.updateSelection, this );
     716
     717        Library.prototype.activate.apply( this, arguments );
     718    },
     719
     720    /**
     721     * @since 3.5.0
     722     */
     723    deactivate: function() {
     724        this.frame.off( 'open', this.updateSelection, this );
     725
     726        Library.prototype.deactivate.apply( this, arguments );
     727    },
     728
     729    /**
     730     * @since 3.5.0
     731     */
     732    updateSelection: function() {
     733        var selection = this.get('selection'),
     734            id = wp.media.view.settings.post.featuredImageId,
     735            attachment;
     736
     737        if ( '' !== id && -1 !== id ) {
     738            attachment = Attachment.get( id );
     739            attachment.fetch();
     740        }
     741
     742        selection.reset( attachment ? [ attachment ] : [] );
     743    }
     744});
     745
     746module.exports = FeaturedImage;
     747
     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 */
     780var Selection = wp.media.model.Selection,
     781    Library = wp.media.controller.Library,
     782    l10n = wp.media.view.l10n,
     783    GalleryAdd;
     784
     785GalleryAdd = 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
     837module.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 */
     872var Library = wp.media.controller.Library,
     873    l10n = wp.media.view.l10n,
     874    GalleryEdit;
     875
     876GalleryEdit = 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    /**
    256943     * @since 3.5.0
    257944     *
    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 ) {
     945     * @param browser
     946     */
     947    gallerySettings: function( browser ) {
     948        if ( ! this.get('displaySettings') ) {
    384949            return;
    385950        }
    386951
    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;
     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        });
    398976    }
    399977});
    400978
    401 module.exports = Region;
    402 
    403 
    404 /***/ }),
    405 /* 28 */
    406 /***/ (function(module, exports) {
    407 
     979module.exports = GalleryEdit;
     980
     981},{}],10:[function(require,module,exports){
    408982/**
    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
     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.
    417987 *
    418988 * @class
     989 * @augments wp.media.controller.State
    419990 * @augments Backbone.Model
    420  * @mixin
    421  * @mixes Backbone.Events
    422  *
    423  * @param {Array} states
     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.
    4241005 */
    425 var 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.
    431 StateMachine.extend = Backbone.Model.extend;
    432 
    433 _.extend( StateMachine.prototype, Backbone.Events, {
    434     /**
    435      * Fetch a state.
     1006var State = wp.media.controller.State,
     1007    Library = wp.media.controller.Library,
     1008    l10n = wp.media.view.l10n,
     1009    ImageDetails;
     1010
     1011ImageDetails = 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
    4361025     *
    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         }
     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');
    5111038    }
    5121039});
    5131040
    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 
    529 module.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  */
    553 var 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 
    774 module.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  */
    791 var 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 
    844 module.exports = selectionSync;
    845 
    846 
    847 /***/ }),
    848 /* 31 */
    849 /***/ (function(module, exports) {
    850 
     1041module.exports = ImageDetails;
     1042
     1043},{}],11:[function(require,module,exports){
    8511044/**
    8521045 * wp.media.controller.Library
     
    11201313module.exports = Library;
    11211314
    1122 
    1123 /***/ }),
    1124 /* 32 */
    1125 /***/ (function(module, exports) {
    1126 
     1315},{}],12:[function(require,module,exports){
    11271316/**
    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  */
    1151 var State = wp.media.controller.State,
    1152     Library = wp.media.controller.Library,
    1153     l10n = wp.media.view.l10n,
    1154     ImageDetails;
    1155 
    1156 ImageDetails = 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 
    1186 module.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.
     1317 * wp.media.controller.MediaLibrary
    11971318 *
    11981319 * @class
     
    12001321 * @augments wp.media.controller.State
    12011322 * @augments Backbone.Model
    1202  *
    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.
    12241323 */
    12251324var Library = wp.media.controller.Library,
    1226     l10n = wp.media.view.l10n,
    1227     GalleryEdit;
    1228 
    1229 GalleryEdit = 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         }
     1325    MediaLibrary;
     1326
     1327MediaLibrary = 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 }) );
    12621346
    12631347        Library.prototype.initialize.apply( this, arguments );
     
    12651349
    12661350    /**
    1267      * @since 3.5.0
     1351     * @since 3.9.0
    12681352     */
    12691353    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 
    1332 module.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  *
    1349  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    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.
    1352  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    1353  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    1354  *                                                                          If one is not supplied, a collection of all images will be created.
    1355  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    1356  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    1357  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    1358  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    1359  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    1360  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    1361  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    1362  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    1363  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1364  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1365  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1366  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    1367  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    1368  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    1369  */
    1370 var Selection = wp.media.model.Selection,
    1371     Library = wp.media.controller.Library,
    1372     l10n = wp.media.view.l10n,
    1373     GalleryAdd;
    1374 
    1375 GalleryAdd = Library.extend({
    1376     defaults: _.defaults({
    1377         id:            'gallery-library',
    1378         title:         l10n.addToGalleryTitle,
    1379         multiple:      'add',
    1380         filterable:    'uploaded',
    1381         menu:          'gallery',
    1382         toolbar:       'gallery-add',
    1383         priority:      100,
    1384         syncSelection: false
    1385     }, Library.prototype.defaults ),
    1386 
    1387     /**
    1388      * @since 3.5.0
    1389      */
    1390     initialize: function() {
    1391         // If a library wasn't supplied, create a library of images.
    1392         if ( ! this.get('library') ) {
    1393             this.set( 'library', wp.media.query({ type: 'image' }) );
    1394         }
    1395 
    1396         Library.prototype.initialize.apply( this, arguments );
    1397     },
    1398 
    1399     /**
    1400      * @since 3.5.0
    1401      */
    1402     activate: function() {
    1403         var library = this.get('library'),
    1404             edit    = this.frame.state('gallery-edit').get('library');
    1405 
    1406         if ( this.editLibrary && this.editLibrary !== edit ) {
    1407             library.unobserve( this.editLibrary );
    1408         }
    1409 
    1410         // Accepts attachments that exist in the original library and
    1411         // that do not exist in gallery's library.
    1412         library.validator = function( attachment ) {
    1413             return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    1414         };
    1415 
    1416         // Reset the library to ensure that all attachments are re-added
    1417         // to the collection. Do so silently, as calling `observe` will
    1418         // trigger the `reset` event.
    1419         library.reset( library.mirroring.models, { silent: true });
    1420         library.observe( edit );
    1421         this.editLibrary = edit;
    1422 
     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        }
    14231359        Library.prototype.activate.apply( this, arguments );
    14241360    }
    14251361});
    14261362
    1427 module.exports = GalleryAdd;
    1428 
    1429 
    1430 /***/ }),
    1431 /* 35 */
    1432 /***/ (function(module, exports) {
    1433 
     1363module.exports = MediaLibrary;
     1364
     1365},{}],13:[function(require,module,exports){
    14341366/**
    1435  * wp.media.controller.CollectionEdit
    1436  *
    1437  * A state for editing a collection, which is used by audio and video playlists,
    1438  * and can be used for other collections.
     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.
    14391378 *
    14401379 * @class
    1441  * @augments wp.media.controller.Library
    1442  * @augments wp.media.controller.State
    1443  * @augments Backbone.Model
    1444  *
    1445  * @param {object}                     [attributes]                      The attributes hash passed to the state.
    1446  * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
    1447  * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
    1448  *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
    1449  * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
    1450  * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
    1451  * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
    1452  * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
    1453  * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1454  * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
    1455  * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
    1456  * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
    1457  * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
    1458  * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
    1459  * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
    1460  * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
    1461  * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
    1462  *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
    1463  * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
    1464  * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
    1465  *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    1466  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    1467  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     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.
    14681385 */
    1469 var Library = wp.media.controller.Library,
    1470     l10n = wp.media.view.l10n,
    1471     $ = jQuery,
    1472     CollectionEdit;
    1473 
    1474 CollectionEdit = Library.extend({
    1475     defaults: {
    1476         multiple:         false,
    1477         sortable:         true,
    1478         date:             false,
    1479         searchable:       false,
    1480         content:          'browse',
    1481         describe:         true,
    1482         dragInfo:         true,
    1483         idealColumnWidth: 170,
    1484         editing:          false,
    1485         priority:         60,
    1486         SettingsView:     false,
    1487         syncSelection:    false
    1488     },
    1489 
    1490     /**
    1491      * @since 3.9.0
    1492      */
    1493     initialize: function() {
    1494         var collectionType = this.get('collectionType');
    1495 
    1496         if ( 'video' === this.get( 'type' ) ) {
    1497             collectionType = 'video-' + collectionType;
    1498         }
    1499 
    1500         this.set( 'id', collectionType + '-edit' );
    1501         this.set( 'toolbar', collectionType + '-edit' );
    1502 
    1503         // If we haven't been provided a `library`, create a `Selection`.
    1504         if ( ! this.get('library') ) {
    1505             this.set( 'library', new wp.media.model.Selection() );
    1506         }
    1507         // The single `Attachment` view to be used in the `Attachments` view.
    1508         if ( ! this.get('AttachmentView') ) {
    1509             this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    1510         }
    1511         Library.prototype.initialize.apply( this, arguments );
    1512     },
    1513 
    1514     /**
    1515      * @since 3.9.0
    1516      */
    1517     activate: function() {
    1518         var library = this.get('library');
    1519 
    1520         // Limit the library to images only.
    1521         library.props.set( 'type', this.get( 'type' ) );
    1522 
    1523         // Watch for uploaded attachments.
    1524         this.get('library').observe( wp.Uploader.queue );
    1525 
    1526         this.frame.on( 'content:render:browse', this.renderSettings, this );
    1527 
    1528         Library.prototype.activate.apply( this, arguments );
    1529     },
    1530 
    1531     /**
    1532      * @since 3.9.0
    1533      */
    1534     deactivate: function() {
    1535         // Stop watching for uploaded attachments.
    1536         this.get('library').unobserve( wp.Uploader.queue );
    1537 
    1538         this.frame.off( 'content:render:browse', this.renderSettings, this );
    1539 
    1540         Library.prototype.deactivate.apply( this, arguments );
    1541     },
    1542 
    1543     /**
    1544      * Render the collection embed settings view in the browser sidebar.
     1386var Region = function( options ) {
     1387    _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
     1388};
     1389
     1390// Use Backbone's self-propagating `extend` inheritance method.
     1391Region.extend = Backbone.Model.extend;
     1392
     1393_.extend( Region.prototype, {
     1394    /**
     1395     * Activate a mode.
    15451396     *
    1546      * @todo This is against the pattern elsewhere in media. Typically the frame
    1547      *       is responsible for adding region mode callbacks. Explain.
     1397     * @since 3.5.0
    15481398     *
    1549      * @since 3.9.0
     1399     * @param {string} mode
    15501400     *
    1551      * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
    1552      */
    1553     renderSettings: function( attachmentsBrowserView ) {
    1554         var library = this.get('library'),
    1555             collectionType = this.get('collectionType'),
    1556             dragInfoText = this.get('dragInfoText'),
    1557             SettingsView = this.get('SettingsView'),
    1558             obj = {};
    1559 
    1560         if ( ! library || ! attachmentsBrowserView ) {
     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 ) {
    15611525            return;
    15621526        }
    15631527
    1564         library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
    1565 
    1566         obj[ collectionType ] = new SettingsView({
    1567             controller: this,
    1568             model:      library[ collectionType ],
    1569             priority:   40
    1570         });
    1571 
    1572         attachmentsBrowserView.sidebar.set( obj );
    1573 
    1574         if ( dragInfoText ) {
    1575             attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
    1576                 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
    1577                 priority: -40
    1578             }) );
    1579         }
    1580 
    1581         // Add the 'Reverse order' button to the toolbar.
    1582         attachmentsBrowserView.toolbar.set( 'reverse', {
    1583             text:     l10n.reverseOrder,
    1584             priority: 80,
    1585 
    1586             click: function() {
    1587                 library.reset( library.toArray().reverse() );
    1588             }
    1589         });
     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;
    15901539    }
    15911540});
    15921541
    1593 module.exports = CollectionEdit;
    1594 
    1595 
    1596 /***/ }),
    1597 /* 36 */
    1598 /***/ (function(module, exports) {
    1599 
    1600 /**
    1601  * wp.media.controller.CollectionAdd
    1602  *
    1603  * A state for adding attachments to a collection (e.g. video playlist).
    1604  *
    1605  * @class
    1606  * @augments wp.media.controller.Library
    1607  * @augments wp.media.controller.State
    1608  * @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').
    1632  */
    1633 var Selection = wp.media.model.Selection,
    1634     Library = wp.media.controller.Library,
    1635     CollectionAdd;
    1636 
    1637 CollectionAdd = 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      */
    1672     activate: function() {
    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 );
    1685         };
    1686 
    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 );
    1695     }
    1696 });
    1697 
    1698 module.exports = CollectionAdd;
    1699 
    1700 
    1701 /***/ }),
    1702 /* 37 */
    1703 /***/ (function(module, exports) {
    1704 
    1705 /**
    1706  * wp.media.controller.FeaturedImage
    1707  *
    1708  * A state for selecting a featured image for a post.
    1709  *
    1710  * @class
    1711  * @augments wp.media.controller.Library
    1712  * @augments wp.media.controller.State
    1713  * @augments Backbone.Model
    1714  *
    1715  * @param {object}                     [attributes]                          The attributes hash passed to the state.
    1716  * @param {string}                     [attributes.id=featured-image]        Unique identifier.
    1717  * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
    1718  * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
    1719  *                                                                           If one is not supplied, a collection of all images will be created.
    1720  * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
    1721  * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
    1722  *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
    1723  * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
    1724  * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
    1725  * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
    1726  * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
    1727  * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
    1728  * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
    1729  *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
    1730  * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1731  * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
    1732  * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1733  * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
    1734  * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
    1735  */
    1736 var Attachment = wp.media.model.Attachment,
    1737     Library = wp.media.controller.Library,
    1738     l10n = wp.media.view.l10n,
    1739     FeaturedImage;
    1740 
    1741 FeaturedImage = Library.extend({
    1742     defaults: _.defaults({
    1743         id:            'featured-image',
    1744         title:         l10n.setFeaturedImageTitle,
    1745         multiple:      false,
    1746         filterable:    'uploaded',
    1747         toolbar:       'featured-image',
    1748         priority:      60,
    1749         syncSelection: true
    1750     }, Library.prototype.defaults ),
    1751 
    1752     /**
    1753      * @since 3.5.0
    1754      */
    1755     initialize: function() {
    1756         var library, comparator;
    1757 
    1758         // If we haven't been provided a `library`, create a `Selection`.
    1759         if ( ! this.get('library') ) {
    1760             this.set( 'library', wp.media.query({ type: 'image' }) );
    1761         }
    1762 
    1763         Library.prototype.initialize.apply( this, arguments );
    1764 
    1765         library    = this.get('library');
    1766         comparator = library.comparator;
    1767 
    1768         // Overload the library's comparator to push items that are not in
    1769         // the mirrored query to the front of the aggregate collection.
    1770         library.comparator = function( a, b ) {
    1771             var aInQuery = !! this.mirroring.get( a.cid ),
    1772                 bInQuery = !! this.mirroring.get( b.cid );
    1773 
    1774             if ( ! aInQuery && bInQuery ) {
    1775                 return -1;
    1776             } else if ( aInQuery && ! bInQuery ) {
    1777                 return 1;
    1778             } else {
    1779                 return comparator.apply( this, arguments );
    1780             }
    1781         };
    1782 
    1783         // Add all items in the selection to the library, so any featured
    1784         // images that are not initially loaded still appear.
    1785         library.observe( this.get('selection') );
    1786     },
    1787 
    1788     /**
    1789      * @since 3.5.0
    1790      */
    1791     activate: function() {
    1792         this.updateSelection();
    1793         this.frame.on( 'open', this.updateSelection, this );
    1794 
    1795         Library.prototype.activate.apply( this, arguments );
    1796     },
    1797 
    1798     /**
    1799      * @since 3.5.0
    1800      */
    1801     deactivate: function() {
    1802         this.frame.off( 'open', this.updateSelection, this );
    1803 
    1804         Library.prototype.deactivate.apply( this, arguments );
    1805     },
    1806 
    1807     /**
    1808      * @since 3.5.0
    1809      */
    1810     updateSelection: function() {
    1811         var selection = this.get('selection'),
    1812             id = wp.media.view.settings.post.featuredImageId,
    1813             attachment;
    1814 
    1815         if ( '' !== id && -1 !== id ) {
    1816             attachment = Attachment.get( id );
    1817             attachment.fetch();
    1818         }
    1819 
    1820         selection.reset( attachment ? [ attachment ] : [] );
    1821     }
    1822 });
    1823 
    1824 module.exports = FeaturedImage;
    1825 
    1826 
    1827 /***/ }),
    1828 /* 38 */
    1829 /***/ (function(module, exports) {
    1830 
     1542module.exports = Region;
     1543
     1544},{}],14:[function(require,module,exports){
    18311545/**
    18321546 * wp.media.controller.ReplaceImage
     
    19361650module.exports = ReplaceImage;
    19371651
    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  */
    1961 var l10n = wp.media.view.l10n,
    1962     EditImage;
    1963 
    1964 EditImage = 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 
    2016 module.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  */
    2031 var Library = wp.media.controller.Library,
    2032     MediaLibrary;
    2033 
    2034 MediaLibrary = 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 
    2070 module.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  */
    2098 var l10n = wp.media.view.l10n,
    2099     $ = Backbone.$,
    2100     Embed;
    2101 
    2102 Embed = 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 
    2210 module.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  */
    2226 var l10n = wp.media.view.l10n,
    2227     Cropper;
    2228 
    2229 Cropper = 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 
    2330 module.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  */
    2347 var Controller = wp.media.controller,
    2348     CustomizeImageCropper;
    2349 
    2350 CustomizeImageCropper = 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 
    2368 module.exports = CustomizeImageCropper;
    2369 
    2370 
    2371 /***/ }),
    2372 /* 44 */
    2373 /***/ (function(module, exports) {
    2374 
     1652},{}],15:[function(require,module,exports){
    23751653/**
    23761654 * wp.media.controller.SiteIconCropper
     
    24211699module.exports = SiteIconCropper;
    24221700
    2423 
    2424 /***/ }),
    2425 /* 45 */
    2426 /***/ (function(module, exports) {
    2427 
     1701},{}],16:[function(require,module,exports){
    24281702/**
    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.
     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
    24391711 *
    24401712 * @class
    2441  * @augments wp.Backbone.View
    2442  * @augments Backbone.View
     1713 * @augments Backbone.Model
     1714 * @mixin
     1715 * @mixes Backbone.Events
     1716 *
     1717 * @param {Array} states
    24431718 */
    2444 var 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.
     1719var 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.
     1725StateMachine.extend = Backbone.Model.extend;
     1726
     1727_.extend( StateMachine.prototype, Backbone.Events, {
     1728    /**
     1729     * Fetch a state.
    24551730     *
    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         }
     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');
    24761786
    24771787        return this;
    24781788    },
    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 );
     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        }
    24881805    }
    24891806});
    24901807
    2491 module.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  */
    2513 var 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 
    2661 module.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  */
    2680 var Frame = wp.media.view.Frame,
    2681     $ = jQuery,
    2682     MediaFrame;
    2683 
    2684 MediaFrame = 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         }
     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 );
    29081819        return this;
    29091820    };
    29101821});
    29111822
    2912 module.exports = MediaFrame;
    2913 
    2914 
    2915 /***/ }),
    2916 /* 48 */
    2917 /***/ (function(module, exports) {
    2918 
     1823module.exports = StateMachine;
     1824
     1825},{}],17:[function(require,module,exports){
    29191826/**
    2920  * wp.media.view.MediaFrame.Select
    2921  *
    2922  * A frame for selecting an item or items from the media library.
     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.
    29231839 *
    29241840 * @class
    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
     1841 * @augments Backbone.Model
    29311842 */
    2932 
    2933 var MediaFrame = wp.media.view.MediaFrame,
    2934     l10n = wp.media.view.l10n,
    2935     Select;
    2936 
    2937 Select = 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.
     1843var State = Backbone.Model.extend({
     1844    /**
     1845     * Constructor.
    29561846     *
    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.
     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.
    29601866     *
    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 ) {
     1867     * @abstract
     1868     * @since 3.5.0
     1869     */
     1870    ready: function() {},
     1871
     1872    /**
     1873     * Activate event callback.
     1874     *
     1875     * @abstract
     1876     * @since 3.5.0
     1877     */
     1878    activate: function() {},
     1879
     1880    /**
     1881     * Deactivate event callback.
     1882     *
     1883     * @abstract
     1884     * @since 3.5.0
     1885     */
     1886    deactivate: function() {},
     1887
     1888    /**
     1889     * Reset event callback.
     1890     *
     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 ) {
    29851973            return;
    29861974        }
    29871975
    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.
     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.
    30022025     *
    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.
     2026     * @access private
     2027     * @since 3.5.0
    30152028     *
    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
     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;
    30272041            }
    3028         });
    3029     },
    3030 
    3031     /**
    3032      * Render callback for the content region in the `browse` mode.
    3033      *
    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
    3074      *
    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 );
     2042        }
     2043
     2044        if ( ! menuItem ) {
     2045            return;
     2046        }
     2047
     2048        view.set( this.id, menuItem );
    30842049    }
    30852050});
    30862051
    3087 module.exports = Select;
    3088 
    3089 
    3090 /***/ }),
    3091 /* 49 */
    3092 /***/ (function(module, exports) {
    3093 
     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    };
     2062});
     2063
     2064module.exports = State;
     2065
     2066},{}],18:[function(require,module,exports){
    30942067/**
    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
     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
    31072076 */
    3108 var Select = wp.media.view.MediaFrame.Select,
    3109     Library = wp.media.controller.Library,
    3110     l10n = wp.media.view.l10n,
    3111     Post;
    3112 
    3113 Post = 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         }) );
     2077var 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 ) {
     2086            return;
     2087        }
     2088
     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.
     2108     *
     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 ) {
     2116            return;
     2117        }
     2118
     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;
    38232127    }
    3824 });
    3825 
    3826 module.exports = Post;
    3827 
    3828 
    3829 /***/ }),
    3830 /* 50 */
    3831 /***/ (function(module, exports) {
     2128};
     2129
     2130module.exports = selectionSync;
     2131
     2132},{}],19:[function(require,module,exports){
     2133var media = wp.media,
     2134    $ = jQuery,
     2135    l10n;
     2136
     2137media.isTouchDevice = ( 'ontouchend' in document );
     2138
     2139// Link any localized strings.
     2140l10n = media.view.l10n = window._wpMediaViewsL10n || {};
     2141
     2142// Link any settings.
     2143media.view.settings = l10n.settings || {};
     2144delete l10n.settings;
     2145
     2146// Copy the `post` setting over to the model settings.
     2147media.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}());
    38322167
    38332168/**
    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
     2169 * A shared event bus used to provide events into
     2170 * the media workflows that 3rd-party devs can use to hook
     2171 * in.
    38472172 */
    3848 var Select = wp.media.view.MediaFrame.Select,
    3849     l10n = wp.media.view.l10n,
    3850     ImageDetails;
    3851 
    3852 ImageDetails = 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 ) {
    3917             return;
    3918         }
    3919 
    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         }) );
     2173media.events = _.extend( {}, Backbone.Events );
     2174
     2175/**
     2176 * Makes it easier to bind events using transitions.
     2177 *
     2178 * @param {string} selector
     2179 * @param {Number} sensitivity
     2180 * @returns {Promise}
     2181 */
     2182media.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();
    40032201    }
    40042202
    4005 });
    4006 
    4007 module.exports = ImageDetails;
    4008 
    4009 
    4010 /***/ }),
    4011 /* 51 */
    4012 /***/ (function(module, exports) {
    4013 
     2203    return deferred.promise();
     2204};
     2205
     2206media.controller.Region = require( './controllers/region.js' );
     2207media.controller.StateMachine = require( './controllers/state-machine.js' );
     2208media.controller.State = require( './controllers/state.js' );
     2209
     2210media.selectionSync = require( './utils/selection-sync.js' );
     2211media.controller.Library = require( './controllers/library.js' );
     2212media.controller.ImageDetails = require( './controllers/image-details.js' );
     2213media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
     2214media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
     2215media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
     2216media.controller.CollectionAdd = require( './controllers/collection-add.js' );
     2217media.controller.FeaturedImage = require( './controllers/featured-image.js' );
     2218media.controller.ReplaceImage = require( './controllers/replace-image.js' );
     2219media.controller.EditImage = require( './controllers/edit-image.js' );
     2220media.controller.MediaLibrary = require( './controllers/media-library.js' );
     2221media.controller.Embed = require( './controllers/embed.js' );
     2222media.controller.Cropper = require( './controllers/cropper.js' );
     2223media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' );
     2224media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' );
     2225
     2226media.View = require( './views/view.js' );
     2227media.view.Frame = require( './views/frame.js' );
     2228media.view.MediaFrame = require( './views/media-frame.js' );
     2229media.view.MediaFrame.Select = require( './views/frame/select.js' );
     2230media.view.MediaFrame.Post = require( './views/frame/post.js' );
     2231media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
     2232media.view.Modal = require( './views/modal.js' );
     2233media.view.FocusManager = require( './views/focus-manager.js' );
     2234media.view.UploaderWindow = require( './views/uploader/window.js' );
     2235media.view.EditorUploader = require( './views/uploader/editor.js' );
     2236media.view.UploaderInline = require( './views/uploader/inline.js' );
     2237media.view.UploaderStatus = require( './views/uploader/status.js' );
     2238media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
     2239media.view.Toolbar = require( './views/toolbar.js' );
     2240media.view.Toolbar.Select = require( './views/toolbar/select.js' );
     2241media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
     2242media.view.Button = require( './views/button.js' );
     2243media.view.ButtonGroup = require( './views/button-group.js' );
     2244media.view.PriorityList = require( './views/priority-list.js' );
     2245media.view.MenuItem = require( './views/menu-item.js' );
     2246media.view.Menu = require( './views/menu.js' );
     2247media.view.RouterItem = require( './views/router-item.js' );
     2248media.view.Router = require( './views/router.js' );
     2249media.view.Sidebar = require( './views/sidebar.js' );
     2250media.view.Attachment = require( './views/attachment.js' );
     2251media.view.Attachment.Library = require( './views/attachment/library.js' );
     2252media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
     2253media.view.Attachments = require( './views/attachments.js' );
     2254media.view.Search = require( './views/search.js' );
     2255media.view.AttachmentFilters = require( './views/attachment-filters.js' );
     2256media.view.DateFilter = require( './views/attachment-filters/date.js' );
     2257media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
     2258media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
     2259media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
     2260media.view.Selection = require( './views/selection.js' );
     2261media.view.Attachment.Selection = require( './views/attachment/selection.js' );
     2262media.view.Attachments.Selection = require( './views/attachments/selection.js' );
     2263media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
     2264media.view.Settings = require( './views/settings.js' );
     2265media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
     2266media.view.Settings.Gallery = require( './views/settings/gallery.js' );
     2267media.view.Settings.Playlist = require( './views/settings/playlist.js' );
     2268media.view.Attachment.Details = require( './views/attachment/details.js' );
     2269media.view.AttachmentCompat = require( './views/attachment-compat.js' );
     2270media.view.Iframe = require( './views/iframe.js' );
     2271media.view.Embed = require( './views/embed.js' );
     2272media.view.Label = require( './views/label.js' );
     2273media.view.EmbedUrl = require( './views/embed/url.js' );
     2274media.view.EmbedLink = require( './views/embed/link.js' );
     2275media.view.EmbedImage = require( './views/embed/image.js' );
     2276media.view.ImageDetails = require( './views/image-details.js' );
     2277media.view.Cropper = require( './views/cropper.js' );
     2278media.view.SiteIconCropper = require( './views/site-icon-cropper.js' );
     2279media.view.SiteIconPreview = require( './views/site-icon-preview.js' );
     2280media.view.EditImage = require( './views/edit-image.js' );
     2281media.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){
    40142284/**
    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  */
    4024 var $ = jQuery,
    4025     Modal;
    4026 
    4027 Modal = 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.
    4199      *
    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 
    4224 module.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  */
    4239 var 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 ) {
    4256             return;
    4257         }
    4258 
    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         }
    4270     }
    4271 
    4272 });
    4273 
    4274 module.exports = FocusManager;
    4275 
    4276 
    4277 /***/ }),
    4278 /* 53 */
    4279 /***/ (function(module, exports) {
    4280 
    4281 /**
    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]
    4296  */
    4297 var $ = jQuery,
    4298     UploaderWindow;
    4299 
    4300 UploaderWindow = 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 
    4389 module.exports = UploaderWindow;
    4390 
    4391 
    4392 /***/ }),
    4393 /* 54 */
    4394 /***/ (function(module, exports) {
    4395 
    4396 /**
    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
     2285 * wp.media.view.AttachmentCompat
     2286 *
     2287 * A view to display fields added via the `attachment_fields_to_edit` filter.
    44012288 *
    44022289 * @class
     
    44062293 */
    44072294var View = wp.media.View,
    4408     l10n = wp.media.view.l10n,
    4409     $ = jQuery,
    4410     EditorUploader;
    4411 
    4412 EditorUploader = 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      */
     2295    AttachmentCompat;
     2296
     2297AttachmentCompat = View.extend({
     2298    tagName:   'form',
     2299    className: 'compat-item',
     2300
     2301    events: {
     2302        'submit':          'preventDefault',
     2303        'change input':    'save',
     2304        'change select':   'save',
     2305        'change textarea': 'save'
     2306    },
     2307
    44252308    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 
    4614 module.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  */
    4631 var View = wp.media.View,
    4632     UploaderInline;
    4633 
    4634 UploaderInline = View.extend({
    4635     tagName:   'div',
    4636     className: 'uploader-inline',
    4637     template:  wp.template('uploader-inline'),
    4638 
    4639     events: {
    4640         'click .close': 'hide'
    4641     },
    4642 
    4643     initialize: function() {
    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
     2309        this.listenTo( this.model, 'change:compat', this.render );
     2310    },
     2311    /**
     2312     * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    46822313     */
    46832314    dispose: function() {
    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 
    4749 module.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  */
    4766 var View = wp.media.View,
    4767     UploaderStatus;
    4768 
    4769 UploaderStatus = 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 
    4891 module.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  */
    4906 var UploaderStatusError = wp.media.View.extend({
    4907     className: 'upload-error',
    4908     template:  wp.template('uploader-status-error')
    4909 });
    4910 
    4911 module.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  */
    4929 var View = wp.media.View,
    4930     Toolbar;
    4931 
    4932 Toolbar = 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 );
     2315        if ( this.$(':focus').length ) {
     2316            this.save();
    49772317        }
    49782318        /**
     
    49812321        return View.prototype.dispose.apply( this, arguments );
    49822322    },
    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 
     2323    /**
     2324     * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     2325     */
     2326    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();
    50222335        return this;
    50232336    },
    50242337    /**
    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 );
     2338     * @param {Object} event
     2339     */
     2340    preventDefault: function( event ) {
     2341        event.preventDefault();
     2342    },
     2343    /**
     2344     * @param {Object} event
     2345     */
     2346    save: function( event ) {
     2347        var data = {};
     2348
     2349        if ( event ) {
     2350            event.preventDefault();
     2351        }
     2352
     2353        _.each( this.$el.serializeArray(), function( pair ) {
     2354            data[ pair.name ] = pair.value;
    50712355        });
     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'] );
    50722363    }
    50732364});
    50742365
    5075 module.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  */
    5091 var Toolbar = wp.media.view.Toolbar,
    5092     l10n = wp.media.view.l10n,
    5093     Select;
    5094 
    5095 Select = 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 
    5149 module.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  */
    5166 var Select = wp.media.view.Toolbar.Select,
    5167     l10n = wp.media.view.l10n,
    5168     Embed;
    5169 
    5170 Embed = 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 
    5190 module.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  */
    5205 var 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
    5245      */
    5246     render: function() {
    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 
    5264         return this;
    5265     },
    5266     /**
    5267      * @param {Object} event
    5268      */
    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 
    5280 module.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  */
    5295 var $ = Backbone.$,
    5296     ButtonGroup;
    5297 
    5298 ButtonGroup = 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 
    5330 module.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  */
    5345 var 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 
    5431 module.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  */
    5446 var $ = jQuery,
    5447     MenuItem;
    5448 
    5449 MenuItem = wp.media.View.extend({
    5450     tagName:   'a',
    5451     className: 'media-menu-item',
    5452 
    5453     attributes: {
    5454         href: '#'
    5455     },
    5456 
    5457     events: {
    5458         'click': '_click'
    5459     },
    5460     /**
    5461      * @param {Object} event
    5462      */
    5463     _click: function( event ) {
    5464         var clickOverride = this.options.click;
    5465 
    5466         if ( event ) {
    5467             event.preventDefault();
    5468         }
    5469 
    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;
    5504     }
    5505 });
    5506 
    5507 module.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  */
    5523 var MenuItem = wp.media.view.MenuItem,
    5524     PriorityList = wp.media.view.PriorityList,
    5525     Menu;
    5526 
    5527 Menu = 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 
    5628 module.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  */
    5644 var 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 
    5656 module.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  */
    5673 var Menu = wp.media.view.Menu,
    5674     Router;
    5675 
    5676 Router = 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 
    5697 module.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  */
    5713 var Sidebar = wp.media.view.PriorityList.extend({
    5714     className: 'media-sidebar'
    5715 });
    5716 
    5717 module.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  */
    5732 var View = wp.media.View,
    5733     $ = jQuery,
    5734     Attachment;
    5735 
    5736 Attachment = 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 
    6267 module.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  */
    6283 var Library = wp.media.view.Attachment.extend({
    6284     buttons: {
    6285         check: true
    6286     }
    6287 });
    6288 
    6289 module.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  */
    6305 var EditLibrary = wp.media.view.Attachment.extend({
    6306     buttons: {
    6307         close: true
    6308     }
    6309 });
    6310 
    6311 module.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  */
    6326 var View = wp.media.View,
    6327     $ = jQuery,
    6328     Attachments;
    6329 
    6330 Attachments = 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 
    6614 module.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  */
    6629 var l10n = wp.media.view.l10n,
    6630     Search;
    6631 
    6632 Search = 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 
    6666 module.exports = Search;
    6667 
    6668 
    6669 /***/ }),
    6670 /* 74 */
    6671 /***/ (function(module, exports) {
    6672 
     2366module.exports = AttachmentCompat;
     2367
     2368},{}],21:[function(require,module,exports){
    66732369/**
    66742370 * wp.media.view.AttachmentFilters
     
    67472443module.exports = AttachmentFilters;
    67482444
    6749 
    6750 /***/ }),
    6751 /* 75 */
    6752 /***/ (function(module, exports) {
    6753 
     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 */
     2455var l10n = wp.media.view.l10n,
     2456    All;
     2457
     2458All = 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
     2533module.exports = All;
     2534
     2535},{}],23:[function(require,module,exports){
    67542536/**
    67552537 * A filter dropdown for month/dates.
     
    67922574module.exports = DateFilter;
    67932575
    6794 
    6795 /***/ }),
    6796 /* 76 */
    6797 /***/ (function(module, exports) {
    6798 
     2576},{}],24:[function(require,module,exports){
    67992577/**
    68002578 * wp.media.view.AttachmentFilters.Uploaded
     
    68552633module.exports = Uploaded;
    68562634
    6857 
    6858 /***/ }),
    6859 /* 77 */
    6860 /***/ (function(module, exports) {
    6861 
     2635},{}],25:[function(require,module,exports){
    68622636/**
    6863  * wp.media.view.AttachmentFilters.All
     2637 * wp.media.view.Attachment
    68642638 *
    68652639 * @class
    6866  * @augments wp.media.view.AttachmentFilters
    68672640 * @augments wp.media.View
    68682641 * @augments wp.Backbone.View
    68692642 * @augments Backbone.View
    68702643 */
    6871 var l10n = wp.media.view.l10n,
    6872     All;
    6873 
    6874 All = 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             };
     2644var View = wp.media.View,
     2645    $ = jQuery,
     2646    Attachment;
     2647
     2648Attachment = 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' )
     2660        };
     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
    68892837        });
    68902838
    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
     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')
    69013015        };
    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
    6927         };
    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;
     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        }
    69463145    }
    69473146});
    69483147
    6949 module.exports = All;
    6950 
    6951 
    6952 /***/ }),
    6953 /* 78 */
    6954 /***/ (function(module, exports) {
    6955 
     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
     3179module.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 */
     3191var Attachment = wp.media.view.Attachment,
     3192    l10n = wp.media.view.l10n,
     3193    Details;
     3194
     3195Details = 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
     3317module.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 */
     3329var EditLibrary = wp.media.view.Attachment.extend({
     3330    buttons: {
     3331        close: true
     3332    }
     3333});
     3334
     3335module.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 */
     3348var EditSelection = wp.media.view.Attachment.Selection.extend({
     3349    buttons: {
     3350        close: true
     3351    }
     3352});
     3353
     3354module.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 */
     3366var Library = wp.media.view.Attachment.extend({
     3367    buttons: {
     3368        check: true
     3369    }
     3370});
     3371
     3372module.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 */
     3384var 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
     3394module.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 */
     3405var View = wp.media.View,
     3406    $ = jQuery,
     3407    Attachments;
     3408
     3409Attachments = 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
     3693module.exports = Attachments;
     3694
     3695},{}],32:[function(require,module,exports){
    69563696/**
    69573697 * wp.media.view.AttachmentsBrowser
     
    73974137module.exports = AttachmentsBrowser;
    73984138
    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  */
    7412 var l10n = wp.media.view.l10n,
    7413     Selection;
    7414 
    7415 Selection = 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 
    7484 module.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  */
    7500 var 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 
    7510 module.exports = Selection;
    7511 
    7512 
    7513 /***/ }),
    7514 /* 81 */
    7515 /***/ (function(module, exports) {
    7516 
     4139},{}],33:[function(require,module,exports){
    75174140/**
    75184141 * wp.media.view.Attachments.Selection
     
    75444167module.exports = Selection;
    75454168
    7546 
    7547 /***/ }),
    7548 /* 82 */
    7549 /***/ (function(module, exports) {
    7550 
     4169},{}],34:[function(require,module,exports){
    75514170/**
    7552  * wp.media.view.Attachments.EditSelection
     4171 * wp.media.view.ButtonGroup
    75534172 *
    75544173 * @class
    7555  * @augments wp.media.view.Attachment.Selection
    7556  * @augments wp.media.view.Attachment
    75574174 * @augments wp.media.View
    75584175 * @augments wp.Backbone.View
    75594176 * @augments Backbone.View
    75604177 */
    7561 var EditSelection = wp.media.view.Attachment.Selection.extend({
    7562     buttons: {
    7563         close: true
     4178var $ = Backbone.$,
     4179    ButtonGroup;
     4180
     4181ButtonGroup = 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;
    75644210    }
    75654211});
    75664212
    7567 module.exports = EditSelection;
    7568 
    7569 
    7570 /***/ }),
    7571 /* 83 */
    7572 /***/ (function(module, exports) {
    7573 
     4213module.exports = ButtonGroup;
     4214
     4215},{}],35:[function(require,module,exports){
    75744216/**
    7575  * wp.media.view.Settings
     4217 * wp.media.view.Button
     4218 *
     4219 * @class
     4220 * @augments wp.media.View
     4221 * @augments wp.Backbone.View
     4222 * @augments Backbone.View
     4223 */
     4224var 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
     4299module.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.
    75764310 *
    75774311 * @class
     
    75814315 */
    75824316var View = wp.media.View,
    7583     $ = Backbone.$,
    7584     Settings;
    7585 
    7586 Settings = View.extend({
    7587     events: {
    7588         'click button':    'updateHandler',
    7589         'change input':    'updateHandler',
    7590         'change select':   'updateHandler',
    7591         'change textarea': 'updateHandler'
    7592     },
    7593 
     4317    UploaderStatus = wp.media.view.UploaderStatus,
     4318    l10n = wp.media.view.l10n,
     4319    $ = jQuery,
     4320    Cropper;
     4321
     4322Cropper = View.extend({
     4323    className: 'crop-content',
     4324    template: wp.template('crop-content'),
    75944325    initialize: function() {
    7595         this.model = this.model || new Backbone.Model();
    7596         this.listenTo( this.model, 'change', this.updateChanges );
    7597     },
    7598 
     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    },
    75994340    prepare: function() {
    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         }
     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 });
    76894363    }
    76904364});
    76914365
    7692 module.exports = Settings;
    7693 
    7694 
    7695 /***/ }),
    7696 /* 84 */
    7697 /***/ (function(module, exports) {
    7698 
     4366module.exports = Cropper;
     4367
     4368},{}],37:[function(require,module,exports){
    76994369/**
    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  */
    7708 var Settings = wp.media.view.Settings,
    7709     AttachmentDisplay;
    7710 
    7711 AttachmentDisplay = 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 
    7790 module.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  */
    7806 var Gallery = wp.media.view.Settings.extend({
    7807     className: 'collection-settings gallery-settings',
    7808     template:  wp.template('gallery-settings')
    7809 });
    7810 
    7811 module.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  */
    7827 var Playlist = wp.media.view.Settings.extend({
    7828     className: 'collection-settings playlist-settings',
    7829     template:  wp.template('playlist-settings')
    7830 });
    7831 
    7832 module.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  */
    7848 var Attachment = wp.media.view.Attachment,
    7849     l10n = wp.media.view.l10n,
    7850     Details;
    7851 
    7852 Details = 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 
    7974 module.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.
     4370 * wp.media.view.EditImage
    79854371 *
    79864372 * @class
     
    79904376 */
    79914377var View = wp.media.View,
    7992     AttachmentCompat;
    7993 
    7994 AttachmentCompat = 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'] );
     4378    EditImage;
     4379
     4380EditImage = 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 ) );
    80604418    }
     4419
    80614420});
    80624421
    8063 module.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  */
    8078 var 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 
    8091 module.exports = Iframe;
    8092 
    8093 
    8094 /***/ }),
    8095 /* 90 */
    8096 /***/ (function(module, exports) {
    8097 
     4422module.exports = EditImage;
     4423
     4424},{}],38:[function(require,module,exports){
    80984425/**
    80994426 * wp.media.view.Embed
     
    81594486module.exports = Embed;
    81604487
    8161 
    8162 /***/ }),
    8163 /* 91 */
    8164 /***/ (function(module, exports) {
    8165 
     4488},{}],39:[function(require,module,exports){
    81664489/**
    8167  * wp.media.view.Label
     4490 * wp.media.view.EmbedImage
    81684491 *
    81694492 * @class
     4493 * @augments wp.media.view.Settings.AttachmentDisplay
     4494 * @augments wp.media.view.Settings
    81704495 * @augments wp.media.View
    81714496 * @augments wp.Backbone.View
    81724497 * @augments Backbone.View
    81734498 */
    8174 var Label = wp.media.View.extend({
    8175     tagName: 'label',
    8176     className: 'screen-reader-text',
     4499var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     4500    EmbedImage;
     4501
     4502EmbedImage = AttachmentDisplay.extend({
     4503    className: 'embed-media-settings',
     4504    template:  wp.template('embed-image-settings'),
    81774505
    81784506    initialize: function() {
    8179         this.value = this.options.value;
    8180     },
    8181 
    8182     render: function() {
    8183         this.$el.html( this.value );
    8184 
    8185         return this;
     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') );
    81864516    }
    81874517});
    81884518
    8189 module.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  */
    8204 var View = wp.media.View,
    8205     $ = jQuery,
    8206     EmbedUrl;
    8207 
    8208 EmbedUrl = 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 
    8272 module.exports = EmbedUrl;
    8273 
    8274 
    8275 /***/ }),
    8276 /* 93 */
    8277 /***/ (function(module, exports) {
    8278 
     4519module.exports = EmbedImage;
     4520
     4521},{}],40:[function(require,module,exports){
    82794522/**
    82804523 * wp.media.view.EmbedLink
     
    83654608module.exports = EmbedLink;
    83664609
    8367 
    8368 /***/ }),
    8369 /* 94 */
    8370 /***/ (function(module, exports) {
    8371 
     4610},{}],41:[function(require,module,exports){
    83724611/**
    8373  * wp.media.view.EmbedImage
     4612 * wp.media.view.EmbedUrl
    83744613 *
    83754614 * @class
    8376  * @augments wp.media.view.Settings.AttachmentDisplay
    8377  * @augments wp.media.view.Settings
    83784615 * @augments wp.media.View
    83794616 * @augments wp.Backbone.View
    83804617 * @augments Backbone.View
    83814618 */
    8382 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    8383     EmbedImage;
    8384 
    8385 EmbedImage = AttachmentDisplay.extend({
    8386     className: 'embed-media-settings',
    8387     template:  wp.template('embed-image-settings'),
     4619var View = wp.media.View,
     4620    $ = jQuery,
     4621    EmbedUrl;
     4622
     4623EmbedUrl = View.extend({
     4624    tagName:   'label',
     4625    className: 'embed-url',
     4626
     4627    events: {
     4628        'input':  'url',
     4629        'keyup':  'url',
     4630        'change': 'url'
     4631    },
    83884632
    83894633    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://';
    83904659        /**
    8391          * Call `initialize` directly on parent class with passed arguments
     4660         * Call `render` directly on parent class with passed arguments
    83924661         */
    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') );
     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        }
    83994684    }
    84004685});
    84014686
    8402 module.exports = EmbedImage;
    8403 
    8404 
    8405 /***/ }),
    8406 /* 95 */
    8407 /***/ (function(module, exports) {
    8408 
     4687module.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 */
     4698var 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
     4733module.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 */
     4751var 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
     4899module.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 */
     4917var Select = wp.media.view.MediaFrame.Select,
     4918    l10n = wp.media.view.l10n,
     4919    ImageDetails;
     4920
     4921ImageDetails = 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
     5076module.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 */
     5093var Select = wp.media.view.MediaFrame.Select,
     5094    Library = wp.media.controller.Library,
     5095    l10n = wp.media.view.l10n,
     5096    Post;
     5097
     5098Post = 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
     5811module.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
     5828var MediaFrame = wp.media.view.MediaFrame,
     5829    l10n = wp.media.view.l10n,
     5830    Select;
     5831
     5832Select = 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
     5982module.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 */
     5993var 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
     6006module.exports = Iframe;
     6007
     6008},{}],48:[function(require,module,exports){
    84096009/**
    84106010 * wp.media.view.ImageDetails
     
    85746174module.exports = ImageDetails;
    85756175
    8576 
    8577 /***/ }),
    8578 /* 96 */
    8579 /***/ (function(module, exports) {
    8580 
     6176},{}],49:[function(require,module,exports){
    85816177/**
    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.
     6178 * wp.media.view.Label
     6179 *
     6180 * @class
     6181 * @augments wp.media.View
     6182 * @augments wp.Backbone.View
     6183 * @augments Backbone.View
     6184 */
     6185var 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
     6200module.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 */
     6215var Frame = wp.media.view.Frame,
     6216    $ = jQuery,
     6217    MediaFrame;
     6218
     6219MediaFrame = 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
     6447module.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 */
     6458var $ = jQuery,
     6459    MenuItem;
     6460
     6461MenuItem = 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
     6519module.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 */
     6531var MenuItem = wp.media.view.MenuItem,
     6532    PriorityList = wp.media.view.PriorityList,
     6533    Menu;
     6534
     6535Menu = 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
     6636module.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 */
     6649var $ = jQuery,
     6650    Modal;
     6651
     6652Modal = 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
     6849module.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 */
     6860var 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
     6946module.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 */
     6958var 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
     6970module.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 */
     6983var Menu = wp.media.view.Menu,
     6984    Router;
     6985
     6986Router = 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
     7007module.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 */
     7018var l10n = wp.media.view.l10n,
     7019    Search;
     7020
     7021Search = 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
     7055module.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 */
     7066var l10n = wp.media.view.l10n,
     7067    Selection;
     7068
     7069Selection = 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
     7138module.exports = Selection;
     7139
     7140},{}],59:[function(require,module,exports){
     7141/**
     7142 * wp.media.view.Settings
    85897143 *
    85907144 * @class
     
    85947148 */
    85957149var View = wp.media.View,
    8596     UploaderStatus = wp.media.view.UploaderStatus,
    8597     l10n = wp.media.view.l10n,
    8598     $ = jQuery,
    8599     Cropper;
    8600 
    8601 Cropper = View.extend({
    8602     className: 'crop-content',
    8603     template: wp.template('crop-content'),
     7150    $ = Backbone.$,
     7151    Settings;
     7152
     7153Settings = View.extend({
     7154    events: {
     7155        'click button':    'updateHandler',
     7156        'change input':    'updateHandler',
     7157        'change select':   'updateHandler',
     7158        'change textarea': 'updateHandler'
     7159    },
     7160
    86047161    initialize: function() {
    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     },
     7162        this.model = this.model || new Backbone.Model();
     7163        this.listenTo( this.model, 'change', this.updateChanges );
     7164    },
     7165
    86197166    prepare: function() {
    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 });
     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        }
    86427256    }
    86437257});
    86447258
    8645 module.exports = Cropper;
    8646 
    8647 
    8648 /***/ }),
    8649 /* 97 */
    8650 /***/ (function(module, exports) {
    8651 
     7259module.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 */
     7271var Settings = wp.media.view.Settings,
     7272    AttachmentDisplay;
     7273
     7274AttachmentDisplay = 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
     7353module.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 */
     7365var Gallery = wp.media.view.Settings.extend({
     7366    className: 'collection-settings gallery-settings',
     7367    template:  wp.template('gallery-settings')
     7368});
     7369
     7370module.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 */
     7382var Playlist = wp.media.view.Settings.extend({
     7383    className: 'collection-settings playlist-settings',
     7384    template:  wp.template('playlist-settings')
     7385});
     7386
     7387module.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 */
     7399var Sidebar = wp.media.view.PriorityList.extend({
     7400    className: 'media-sidebar'
     7401});
     7402
     7403module.exports = Sidebar;
     7404
     7405},{}],64:[function(require,module,exports){
    86527406/**
    86537407 * wp.media.view.SiteIconCropper
     
    86927446module.exports = SiteIconCropper;
    86937447
    8694 
    8695 /***/ }),
    8696 /* 98 */
    8697 /***/ (function(module, exports) {
    8698 
     7448},{}],65:[function(require,module,exports){
    86997449/**
    87007450 * wp.media.view.SiteIconPreview
     
    87527502module.exports = SiteIconPreview;
    87537503
    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  */
    8767 var View = wp.media.View,
    8768     EditImage;
    8769 
    8770 EditImage = 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 
    8812 module.exports = EditImage;
    8813 
    8814 
    8815 /***/ }),
    8816 /* 100 */
    8817 /***/ (function(module, exports) {
    8818 
     7504},{}],66:[function(require,module,exports){
    88197505/**
    88207506 * wp.media.view.Spinner
     
    88517537module.exports = Spinner;
    88527538
    8853 
    8854 /***/ })
    8855 /******/ ]));
     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 */
     7551var View = wp.media.View,
     7552    Toolbar;
     7553
     7554Toolbar = 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
     7697module.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 */
     7710var Select = wp.media.view.Toolbar.Select,
     7711    l10n = wp.media.view.l10n,
     7712    Embed;
     7713
     7714Embed = 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
     7734module.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 */
     7746var Toolbar = wp.media.view.Toolbar,
     7747    l10n = wp.media.view.l10n,
     7748    Select;
     7749
     7750Select = 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
     7804module.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 */
     7818var View = wp.media.View,
     7819    l10n = wp.media.view.l10n,
     7820    $ = jQuery,
     7821    EditorUploader;
     7822
     7823EditorUploader = 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
     8025module.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 */
     8038var View = wp.media.View,
     8039    UploaderInline;
     8040
     8041UploaderInline = 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
     8156module.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 */
     8167var UploaderStatusError = wp.media.View.extend({
     8168    className: 'upload-error',
     8169    template:  wp.template('uploader-status-error')
     8170});
     8171
     8172module.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 */
     8185var View = wp.media.View,
     8186    UploaderStatus;
     8187
     8188UploaderStatus = 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
     8310module.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 */
     8329var $ = jQuery,
     8330    UploaderWindow;
     8331
     8332UploaderWindow = 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
     8421module.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 */
     8440var 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
     8487module.exports = View;
     8488
     8489},{}]},{},[19]);
Note: See TracChangeset for help on using the changeset viewer.