Make WordPress Core

Changeset 46498


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

Backporting several bug fixes.

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

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

Location:
branches/4.4
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • branches/4.4

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

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

    r43995 r46498  
    15801580    if ( file_exists( $target ) )
    15811581        return @is_dir( $target );
     1582
     1583    // Do not allow path traversals.
     1584    if ( false !== strpos( $target, '../' ) || false !== strpos( $target, '..' . DIRECTORY_SEPARATOR ) ) {
     1585        return false;
     1586    }
    15821587
    15831588    // We need to find the permissions of the parent folder that exists and inherit that.
  • branches/4.4/src/wp-includes/http.php

    r37116 r46498  
    532532        } else {
    533533            $ip = gethostbyname( $host );
    534             if ( $ip === $host ) // Error condition for gethostbyname()
    535                 $ip = false;
     534            if ( $ip === $host ) { // Error condition for gethostbyname()
     535                return false;
     536            }
    536537        }
    537538        if ( $ip ) {
  • branches/4.4/src/wp-includes/js/media-audiovideo.js

    r33337 r46498  
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     1/******/ (function(modules) { // webpackBootstrap
     2/******/    // The module cache
     3/******/    var installedModules = {};
     4/******/
     5/******/    // The require function
     6/******/    function __webpack_require__(moduleId) {
     7/******/
     8/******/        // Check if module is in cache
     9/******/        if(installedModules[moduleId]) {
     10/******/            return installedModules[moduleId].exports;
     11/******/        }
     12/******/        // Create a new module (and put it into the cache)
     13/******/        var module = installedModules[moduleId] = {
     14/******/            i: moduleId,
     15/******/            l: false,
     16/******/            exports: {}
     17/******/        };
     18/******/
     19/******/        // Execute the module function
     20/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/        // Flag the module as loaded
     23/******/        module.l = true;
     24/******/
     25/******/        // Return the exports of the module
     26/******/        return module.exports;
     27/******/    }
     28/******/
     29/******/
     30/******/    // expose the modules object (__webpack_modules__)
     31/******/    __webpack_require__.m = modules;
     32/******/
     33/******/    // expose the module cache
     34/******/    __webpack_require__.c = installedModules;
     35/******/
     36/******/    // define getter function for harmony exports
     37/******/    __webpack_require__.d = function(exports, name, getter) {
     38/******/        if(!__webpack_require__.o(exports, name)) {
     39/******/            Object.defineProperty(exports, name, {
     40/******/                configurable: false,
     41/******/                enumerable: true,
     42/******/                get: getter
     43/******/            });
     44/******/        }
     45/******/    };
     46/******/
     47/******/    // getDefaultExport function for compatibility with non-harmony modules
     48/******/    __webpack_require__.n = function(module) {
     49/******/        var getter = module && module.__esModule ?
     50/******/            function getDefault() { return module['default']; } :
     51/******/            function getModuleExports() { return module; };
     52/******/        __webpack_require__.d(getter, 'a', getter);
     53/******/        return getter;
     54/******/    };
     55/******/
     56/******/    // Object.prototype.hasOwnProperty.call
     57/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/    // __webpack_public_path__
     60/******/    __webpack_require__.p = "";
     61/******/
     62/******/    // Load entry module and return exports
     63/******/    return __webpack_require__(__webpack_require__.s = 0);
     64/******/ })
     65/************************************************************************/
     66/******/ ([
     67/* 0 */
     68/***/ (function(module, exports, __webpack_require__) {
     69
    270var media = wp.media,
    371    baseSettings = window._wpmejsSettings || {},
     
    206274};
    207275
    208 media.model.PostMedia = require( './models/post-media.js' );
    209 media.controller.AudioDetails = require( './controllers/audio-details.js' );
    210 media.controller.VideoDetails = require( './controllers/video-details.js' );
    211 media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' );
    212 media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' );
    213 media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' );
    214 media.view.MediaDetails = require( './views/media-details.js' );
    215 media.view.AudioDetails = require( './views/audio-details.js' );
    216 media.view.VideoDetails = require( './views/video-details.js' );
    217 
    218 },{"./controllers/audio-details.js":2,"./controllers/video-details.js":3,"./models/post-media.js":4,"./views/audio-details.js":5,"./views/frame/audio-details.js":6,"./views/frame/media-details.js":7,"./views/frame/video-details.js":8,"./views/media-details.js":9,"./views/video-details.js":10}],2:[function(require,module,exports){
     276media.model.PostMedia = __webpack_require__( 1 );
     277media.controller.AudioDetails = __webpack_require__( 2 );
     278media.controller.VideoDetails = __webpack_require__( 3 );
     279media.view.MediaFrame.MediaDetails = __webpack_require__( 4 );
     280media.view.MediaFrame.AudioDetails = __webpack_require__( 5 );
     281media.view.MediaFrame.VideoDetails = __webpack_require__( 6 );
     282media.view.MediaDetails = __webpack_require__( 7 );
     283media.view.AudioDetails = __webpack_require__( 8 );
     284media.view.VideoDetails = __webpack_require__( 9 );
     285
     286
     287/***/ }),
     288/* 1 */
     289/***/ (function(module, exports) {
     290
     291/**
     292 * wp.media.model.PostMedia
     293 *
     294 * Shared model class for audio and video. Updates the model after
     295 *   "Add Audio|Video Source" and "Replace Audio|Video" states return
     296 *
     297 * @class
     298 * @augments Backbone.Model
     299 */
     300var PostMedia = Backbone.Model.extend({
     301    initialize: function() {
     302        this.attachment = false;
     303    },
     304
     305    setSource: function( attachment ) {
     306        this.attachment = attachment;
     307        this.extension = attachment.get( 'filename' ).split('.').pop();
     308
     309        if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
     310            this.unset( 'src' );
     311        }
     312
     313        if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
     314            this.set( this.extension, this.attachment.get( 'url' ) );
     315        } else {
     316            this.unset( this.extension );
     317        }
     318    },
     319
     320    changeAttachment: function( attachment ) {
     321        this.setSource( attachment );
     322
     323        this.unset( 'src' );
     324        _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
     325            this.unset( ext );
     326        }, this );
     327    }
     328});
     329
     330module.exports = PostMedia;
     331
     332
     333/***/ }),
     334/* 2 */
     335/***/ (function(module, exports) {
     336
    219337/**
    220338 * wp.media.controller.AudioDetails
     
    249367module.exports = AudioDetails;
    250368
    251 },{}],3:[function(require,module,exports){
     369
     370/***/ }),
     371/* 3 */
     372/***/ (function(module, exports) {
     373
    252374/**
    253375 * wp.media.controller.VideoDetails
     
    282404module.exports = VideoDetails;
    283405
    284 },{}],4:[function(require,module,exports){
    285 /**
    286  * wp.media.model.PostMedia
    287  *
    288  * Shared model class for audio and video. Updates the model after
    289  *   "Add Audio|Video Source" and "Replace Audio|Video" states return
    290  *
    291  * @class
    292  * @augments Backbone.Model
    293  */
    294 var PostMedia = Backbone.Model.extend({
    295     initialize: function() {
    296         this.attachment = false;
    297     },
    298 
    299     setSource: function( attachment ) {
    300         this.attachment = attachment;
    301         this.extension = attachment.get( 'filename' ).split('.').pop();
    302 
    303         if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
    304             this.unset( 'src' );
    305         }
    306 
    307         if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
    308             this.set( this.extension, this.attachment.get( 'url' ) );
    309         } else {
    310             this.unset( this.extension );
    311         }
    312     },
    313 
    314     changeAttachment: function( attachment ) {
    315         this.setSource( attachment );
    316 
    317         this.unset( 'src' );
    318         _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
    319             this.unset( ext );
    320         }, this );
    321     }
    322 });
    323 
    324 module.exports = PostMedia;
    325 
    326 },{}],5:[function(require,module,exports){
    327 /**
    328  * wp.media.view.AudioDetails
    329  *
    330  * @class
    331  * @augments wp.media.view.MediaDetails
    332  * @augments wp.media.view.Settings.AttachmentDisplay
    333  * @augments wp.media.view.Settings
    334  * @augments wp.media.View
    335  * @augments wp.Backbone.View
    336  * @augments Backbone.View
    337  */
    338 var MediaDetails = wp.media.view.MediaDetails,
    339     AudioDetails;
    340 
    341 AudioDetails = MediaDetails.extend({
    342     className: 'audio-details',
    343     template:  wp.template('audio-details'),
    344 
    345     setMedia: function() {
    346         var audio = this.$('.wp-audio-shortcode');
    347 
    348         if ( audio.find( 'source' ).length ) {
    349             if ( audio.is(':hidden') ) {
    350                 audio.show();
    351             }
    352             this.media = MediaDetails.prepareSrc( audio.get(0) );
    353         } else {
    354             audio.hide();
    355             this.media = false;
    356         }
    357 
    358         return this;
    359     }
    360 });
    361 
    362 module.exports = AudioDetails;
    363 
    364 },{}],6:[function(require,module,exports){
    365 /**
    366  * wp.media.view.MediaFrame.AudioDetails
    367  *
    368  * @class
    369  * @augments wp.media.view.MediaFrame.MediaDetails
    370  * @augments wp.media.view.MediaFrame.Select
    371  * @augments wp.media.view.MediaFrame
    372  * @augments wp.media.view.Frame
    373  * @augments wp.media.View
    374  * @augments wp.Backbone.View
    375  * @augments Backbone.View
    376  * @mixes wp.media.controller.StateMachine
    377  */
    378 var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
    379     MediaLibrary = wp.media.controller.MediaLibrary,
    380 
    381     l10n = wp.media.view.l10n,
    382     AudioDetails;
    383 
    384 AudioDetails = MediaDetails.extend({
    385     defaults: {
    386         id:      'audio',
    387         url:     '',
    388         menu:    'audio-details',
    389         content: 'audio-details',
    390         toolbar: 'audio-details',
    391         type:    'link',
    392         title:    l10n.audioDetailsTitle,
    393         priority: 120
    394     },
    395 
    396     initialize: function( options ) {
    397         options.DetailsView = wp.media.view.AudioDetails;
    398         options.cancelText = l10n.audioDetailsCancel;
    399         options.addText = l10n.audioAddSourceTitle;
    400 
    401         MediaDetails.prototype.initialize.call( this, options );
    402     },
    403 
    404     bindHandlers: function() {
    405         MediaDetails.prototype.bindHandlers.apply( this, arguments );
    406 
    407         this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
    408         this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
    409     },
    410 
    411     createStates: function() {
    412         this.states.add([
    413             new wp.media.controller.AudioDetails( {
    414                 media: this.media
    415             } ),
    416 
    417             new MediaLibrary( {
    418                 type: 'audio',
    419                 id: 'replace-audio',
    420                 title: l10n.audioReplaceTitle,
    421                 toolbar: 'replace-audio',
    422                 media: this.media,
    423                 menu: 'audio-details'
    424             } ),
    425 
    426             new MediaLibrary( {
    427                 type: 'audio',
    428                 id: 'add-audio-source',
    429                 title: l10n.audioAddSourceTitle,
    430                 toolbar: 'add-audio-source',
    431                 media: this.media,
    432                 menu: false
    433             } )
    434         ]);
    435     }
    436 });
    437 
    438 module.exports = AudioDetails;
    439 
    440 },{}],7:[function(require,module,exports){
     406
     407/***/ }),
     408/* 4 */
     409/***/ (function(module, exports) {
     410
    441411/**
    442412 * wp.media.view.MediaFrame.MediaDetails
     
    568538module.exports = MediaDetails;
    569539
    570 },{}],8:[function(require,module,exports){
     540
     541/***/ }),
     542/* 5 */
     543/***/ (function(module, exports) {
     544
     545/**
     546 * wp.media.view.MediaFrame.AudioDetails
     547 *
     548 * @class
     549 * @augments wp.media.view.MediaFrame.MediaDetails
     550 * @augments wp.media.view.MediaFrame.Select
     551 * @augments wp.media.view.MediaFrame
     552 * @augments wp.media.view.Frame
     553 * @augments wp.media.View
     554 * @augments wp.Backbone.View
     555 * @augments Backbone.View
     556 * @mixes wp.media.controller.StateMachine
     557 */
     558var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
     559    MediaLibrary = wp.media.controller.MediaLibrary,
     560
     561    l10n = wp.media.view.l10n,
     562    AudioDetails;
     563
     564AudioDetails = MediaDetails.extend({
     565    defaults: {
     566        id:      'audio',
     567        url:     '',
     568        menu:    'audio-details',
     569        content: 'audio-details',
     570        toolbar: 'audio-details',
     571        type:    'link',
     572        title:    l10n.audioDetailsTitle,
     573        priority: 120
     574    },
     575
     576    initialize: function( options ) {
     577        options.DetailsView = wp.media.view.AudioDetails;
     578        options.cancelText = l10n.audioDetailsCancel;
     579        options.addText = l10n.audioAddSourceTitle;
     580
     581        MediaDetails.prototype.initialize.call( this, options );
     582    },
     583
     584    bindHandlers: function() {
     585        MediaDetails.prototype.bindHandlers.apply( this, arguments );
     586
     587        this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
     588        this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
     589    },
     590
     591    createStates: function() {
     592        this.states.add([
     593            new wp.media.controller.AudioDetails( {
     594                media: this.media
     595            } ),
     596
     597            new MediaLibrary( {
     598                type: 'audio',
     599                id: 'replace-audio',
     600                title: l10n.audioReplaceTitle,
     601                toolbar: 'replace-audio',
     602                media: this.media,
     603                menu: 'audio-details'
     604            } ),
     605
     606            new MediaLibrary( {
     607                type: 'audio',
     608                id: 'add-audio-source',
     609                title: l10n.audioAddSourceTitle,
     610                toolbar: 'add-audio-source',
     611                media: this.media,
     612                menu: false
     613            } )
     614        ]);
     615    }
     616});
     617
     618module.exports = AudioDetails;
     619
     620
     621/***/ }),
     622/* 6 */
     623/***/ (function(module, exports) {
     624
    571625/**
    572626 * wp.media.view.MediaFrame.VideoDetails
     
    703757module.exports = VideoDetails;
    704758
    705 },{}],9:[function(require,module,exports){
     759
     760/***/ }),
     761/* 7 */
     762/***/ (function(module, exports) {
     763
    706764/* global MediaElementPlayer */
    707765
     
    871929module.exports = MediaDetails;
    872930
    873 },{}],10:[function(require,module,exports){
     931
     932/***/ }),
     933/* 8 */
     934/***/ (function(module, exports) {
     935
     936/**
     937 * wp.media.view.AudioDetails
     938 *
     939 * @class
     940 * @augments wp.media.view.MediaDetails
     941 * @augments wp.media.view.Settings.AttachmentDisplay
     942 * @augments wp.media.view.Settings
     943 * @augments wp.media.View
     944 * @augments wp.Backbone.View
     945 * @augments Backbone.View
     946 */
     947var MediaDetails = wp.media.view.MediaDetails,
     948    AudioDetails;
     949
     950AudioDetails = MediaDetails.extend({
     951    className: 'audio-details',
     952    template:  wp.template('audio-details'),
     953
     954    setMedia: function() {
     955        var audio = this.$('.wp-audio-shortcode');
     956
     957        if ( audio.find( 'source' ).length ) {
     958            if ( audio.is(':hidden') ) {
     959                audio.show();
     960            }
     961            this.media = MediaDetails.prepareSrc( audio.get(0) );
     962        } else {
     963            audio.hide();
     964            this.media = false;
     965        }
     966
     967        return this;
     968    }
     969});
     970
     971module.exports = AudioDetails;
     972
     973
     974/***/ }),
     975/* 9 */
     976/***/ (function(module, exports) {
     977
    874978/**
    875979 * wp.media.view.VideoDetails
     
    9141018module.exports = VideoDetails;
    9151019
    916 },{}]},{},[1]);
     1020
     1021/***/ })
     1022/******/ ]);
  • branches/4.4/src/wp-includes/js/media-grid.js

    r33342 r46498  
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     1/******/ (function(modules) { // webpackBootstrap
     2/******/    // The module cache
     3/******/    var installedModules = {};
     4/******/
     5/******/    // The require function
     6/******/    function __webpack_require__(moduleId) {
     7/******/
     8/******/        // Check if module is in cache
     9/******/        if(installedModules[moduleId]) {
     10/******/            return installedModules[moduleId].exports;
     11/******/        }
     12/******/        // Create a new module (and put it into the cache)
     13/******/        var module = installedModules[moduleId] = {
     14/******/            i: moduleId,
     15/******/            l: false,
     16/******/            exports: {}
     17/******/        };
     18/******/
     19/******/        // Execute the module function
     20/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/        // Flag the module as loaded
     23/******/        module.l = true;
     24/******/
     25/******/        // Return the exports of the module
     26/******/        return module.exports;
     27/******/    }
     28/******/
     29/******/
     30/******/    // expose the modules object (__webpack_modules__)
     31/******/    __webpack_require__.m = modules;
     32/******/
     33/******/    // expose the module cache
     34/******/    __webpack_require__.c = installedModules;
     35/******/
     36/******/    // define getter function for harmony exports
     37/******/    __webpack_require__.d = function(exports, name, getter) {
     38/******/        if(!__webpack_require__.o(exports, name)) {
     39/******/            Object.defineProperty(exports, name, {
     40/******/                configurable: false,
     41/******/                enumerable: true,
     42/******/                get: getter
     43/******/            });
     44/******/        }
     45/******/    };
     46/******/
     47/******/    // getDefaultExport function for compatibility with non-harmony modules
     48/******/    __webpack_require__.n = function(module) {
     49/******/        var getter = module && module.__esModule ?
     50/******/            function getDefault() { return module['default']; } :
     51/******/            function getModuleExports() { return module; };
     52/******/        __webpack_require__.d(getter, 'a', getter);
     53/******/        return getter;
     54/******/    };
     55/******/
     56/******/    // Object.prototype.hasOwnProperty.call
     57/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/    // __webpack_public_path__
     60/******/    __webpack_require__.p = "";
     61/******/
     62/******/    // Load entry module and return exports
     63/******/    return __webpack_require__(__webpack_require__.s = 10);
     64/******/ })
     65/************************************************************************/
     66/******/ ([
     67/* 0 */,
     68/* 1 */,
     69/* 2 */,
     70/* 3 */,
     71/* 4 */,
     72/* 5 */,
     73/* 6 */,
     74/* 7 */,
     75/* 8 */,
     76/* 9 */,
     77/* 10 */
     78/***/ (function(module, exports, __webpack_require__) {
     79
     80var media = wp.media;
     81
     82media.controller.EditAttachmentMetadata = __webpack_require__( 11 );
     83media.view.MediaFrame.Manage = __webpack_require__( 12 );
     84media.view.Attachment.Details.TwoColumn = __webpack_require__( 13 );
     85media.view.MediaFrame.Manage.Router = __webpack_require__( 14 );
     86media.view.EditImage.Details = __webpack_require__( 15 );
     87media.view.MediaFrame.EditAttachments = __webpack_require__( 16 );
     88media.view.SelectModeToggleButton = __webpack_require__( 17 );
     89media.view.DeleteSelectedButton = __webpack_require__( 18 );
     90media.view.DeleteSelectedPermanentlyButton = __webpack_require__( 19 );
     91
     92
     93/***/ }),
     94/* 11 */
     95/***/ (function(module, exports) {
     96
    297/**
    398 * wp.media.controller.EditAttachmentMetadata
     
    27122module.exports = EditAttachmentMetadata;
    28123
    29 },{}],2:[function(require,module,exports){
    30 var media = wp.media;
    31 
    32 media.controller.EditAttachmentMetadata = require( './controllers/edit-attachment-metadata.js' );
    33 media.view.MediaFrame.Manage = require( './views/frame/manage.js' );
    34 media.view.Attachment.Details.TwoColumn = require( './views/attachment/details-two-column.js' );
    35 media.view.MediaFrame.Manage.Router = require( './routers/manage.js' );
    36 media.view.EditImage.Details = require( './views/edit-image-details.js' );
    37 media.view.MediaFrame.EditAttachments = require( './views/frame/edit-attachments.js' );
    38 media.view.SelectModeToggleButton = require( './views/button/select-mode-toggle.js' );
    39 media.view.DeleteSelectedButton = require( './views/button/delete-selected.js' );
    40 media.view.DeleteSelectedPermanentlyButton = require( './views/button/delete-selected-permanently.js' );
    41 
    42 },{"./controllers/edit-attachment-metadata.js":1,"./routers/manage.js":3,"./views/attachment/details-two-column.js":4,"./views/button/delete-selected-permanently.js":5,"./views/button/delete-selected.js":6,"./views/button/select-mode-toggle.js":7,"./views/edit-image-details.js":8,"./views/frame/edit-attachments.js":9,"./views/frame/manage.js":10}],3:[function(require,module,exports){
     124
     125/***/ }),
     126/* 12 */
     127/***/ (function(module, exports) {
     128
     129/**
     130 * wp.media.view.MediaFrame.Manage
     131 *
     132 * A generic management frame workflow.
     133 *
     134 * Used in the media grid view.
     135 *
     136 * @class
     137 * @augments wp.media.view.MediaFrame
     138 * @augments wp.media.view.Frame
     139 * @augments wp.media.View
     140 * @augments wp.Backbone.View
     141 * @augments Backbone.View
     142 * @mixes wp.media.controller.StateMachine
     143 */
     144var MediaFrame = wp.media.view.MediaFrame,
     145    Library = wp.media.controller.Library,
     146
     147    $ = Backbone.$,
     148    Manage;
     149
     150Manage = MediaFrame.extend({
     151    /**
     152     * @global wp.Uploader
     153     */
     154    initialize: function() {
     155        _.defaults( this.options, {
     156            title:     '',
     157            modal:     false,
     158            selection: [],
     159            library:   {}, // Options hash for the query to the media library.
     160            multiple:  'add',
     161            state:     'library',
     162            uploader:  true,
     163            mode:      [ 'grid', 'edit' ]
     164        });
     165
     166        this.$body = $( document.body );
     167        this.$window = $( window );
     168        this.$adminBar = $( '#wpadminbar' );
     169        this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) );
     170        $( document ).on( 'click', '.page-title-action', _.bind( this.addNewClickHandler, this ) );
     171
     172        // Ensure core and media grid view UI is enabled.
     173        this.$el.addClass('wp-core-ui');
     174
     175        // Force the uploader off if the upload limit has been exceeded or
     176        // if the browser isn't supported.
     177        if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     178            this.options.uploader = false;
     179        }
     180
     181        // Initialize a window-wide uploader.
     182        if ( this.options.uploader ) {
     183            this.uploader = new wp.media.view.UploaderWindow({
     184                controller: this,
     185                uploader: {
     186                    dropzone:  document.body,
     187                    container: document.body
     188                }
     189            }).render();
     190            this.uploader.ready();
     191            $('body').append( this.uploader.el );
     192
     193            this.options.uploader = false;
     194        }
     195
     196        this.gridRouter = new wp.media.view.MediaFrame.Manage.Router();
     197
     198        // Call 'initialize' directly on the parent class.
     199        MediaFrame.prototype.initialize.apply( this, arguments );
     200
     201        // Append the frame view directly the supplied container.
     202        this.$el.appendTo( this.options.container );
     203
     204        this.createStates();
     205        this.bindRegionModeHandlers();
     206        this.render();
     207        this.bindSearchHandler();
     208    },
     209
     210    bindSearchHandler: function() {
     211        var search = this.$( '#media-search-input' ),
     212            currentSearch = this.options.container.data( 'search' ),
     213            searchView = this.browserView.toolbar.get( 'search' ).$el,
     214            listMode = this.$( '.view-list' ),
     215
     216            input  = _.debounce( function (e) {
     217                var val = $( e.currentTarget ).val(),
     218                    url = '';
     219
     220                if ( val ) {
     221                    url += '?search=' + val;
     222                }
     223                this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
     224            }, 1000 );
     225
     226        // Update the URL when entering search string (at most once per second)
     227        search.on( 'input', _.bind( input, this ) );
     228        searchView.val( currentSearch ).trigger( 'input' );
     229
     230        this.gridRouter.on( 'route:search', function () {
     231            var href = window.location.href;
     232            if ( href.indexOf( 'mode=' ) > -1 ) {
     233                href = href.replace( /mode=[^&]+/g, 'mode=list' );
     234            } else {
     235                href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
     236            }
     237            href = href.replace( 'search=', 's=' );
     238            listMode.prop( 'href', href );
     239        } );
     240    },
     241
     242    /**
     243     * Create the default states for the frame.
     244     */
     245    createStates: function() {
     246        var options = this.options;
     247
     248        if ( this.options.states ) {
     249            return;
     250        }
     251
     252        // Add the default states.
     253        this.states.add([
     254            new Library({
     255                library:            wp.media.query( options.library ),
     256                multiple:           options.multiple,
     257                title:              options.title,
     258                content:            'browse',
     259                toolbar:            'select',
     260                contentUserSetting: false,
     261                filterable:         'all',
     262                autoSelect:         false
     263            })
     264        ]);
     265    },
     266
     267    /**
     268     * Bind region mode activation events to proper handlers.
     269     */
     270    bindRegionModeHandlers: function() {
     271        this.on( 'content:create:browse', this.browseContent, this );
     272
     273        // Handle a frame-level event for editing an attachment.
     274        this.on( 'edit:attachment', this.openEditAttachmentModal, this );
     275
     276        this.on( 'select:activate', this.bindKeydown, this );
     277        this.on( 'select:deactivate', this.unbindKeydown, this );
     278    },
     279
     280    handleKeydown: function( e ) {
     281        if ( 27 === e.which ) {
     282            e.preventDefault();
     283            this.deactivateMode( 'select' ).activateMode( 'edit' );
     284        }
     285    },
     286
     287    bindKeydown: function() {
     288        this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) );
     289    },
     290
     291    unbindKeydown: function() {
     292        this.$body.off( 'keydown.select' );
     293    },
     294
     295    fixPosition: function() {
     296        var $browser, $toolbar;
     297        if ( ! this.isModeActive( 'select' ) ) {
     298            return;
     299        }
     300
     301        $browser = this.$('.attachments-browser');
     302        $toolbar = $browser.find('.media-toolbar');
     303
     304        // Offset doesn't appear to take top margin into account, hence +16
     305        if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) {
     306            $browser.addClass( 'fixed' );
     307            $toolbar.css('width', $browser.width() + 'px');
     308        } else {
     309            $browser.removeClass( 'fixed' );
     310            $toolbar.css('width', '');
     311        }
     312    },
     313
     314    /**
     315     * Click handler for the `Add New` button.
     316     */
     317    addNewClickHandler: function( event ) {
     318        event.preventDefault();
     319        this.trigger( 'toggle:upload:attachment' );
     320    },
     321
     322    /**
     323     * Open the Edit Attachment modal.
     324     */
     325    openEditAttachmentModal: function( model ) {
     326        // Create a new EditAttachment frame, passing along the library and the attachment model.
     327        wp.media( {
     328            frame:       'edit-attachments',
     329            controller:  this,
     330            library:     this.state().get('library'),
     331            model:       model
     332        } );
     333    },
     334
     335    /**
     336     * Create an attachments browser view within the content region.
     337     *
     338     * @param {Object} contentRegion Basic object with a `view` property, which
     339     *                               should be set with the proper region view.
     340     * @this wp.media.controller.Region
     341     */
     342    browseContent: function( contentRegion ) {
     343        var state = this.state();
     344
     345        // Browse our library of attachments.
     346        this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({
     347            controller: this,
     348            collection: state.get('library'),
     349            selection:  state.get('selection'),
     350            model:      state,
     351            sortable:   state.get('sortable'),
     352            search:     state.get('searchable'),
     353            filters:    state.get('filterable'),
     354            date:       state.get('date'),
     355            display:    state.get('displaySettings'),
     356            dragInfo:   state.get('dragInfo'),
     357            sidebar:    'errors',
     358
     359            suggestedWidth:  state.get('suggestedWidth'),
     360            suggestedHeight: state.get('suggestedHeight'),
     361
     362            AttachmentView: state.get('AttachmentView'),
     363
     364            scrollElement: document
     365        });
     366        this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) );
     367
     368        this.errors = wp.Uploader.errors;
     369        this.errors.on( 'add remove reset', this.sidebarVisibility, this );
     370    },
     371
     372    sidebarVisibility: function() {
     373        this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length );
     374    },
     375
     376    bindDeferred: function() {
     377        if ( ! this.browserView.dfd ) {
     378            return;
     379        }
     380        this.browserView.dfd.done( _.bind( this.startHistory, this ) );
     381    },
     382
     383    startHistory: function() {
     384        // Verify pushState support and activate
     385        if ( window.history && window.history.pushState ) {
     386            Backbone.history.start( {
     387                root: window._wpMediaGridSettings.adminUrl,
     388                pushState: true
     389            } );
     390        }
     391    }
     392});
     393
     394module.exports = Manage;
     395
     396
     397/***/ }),
     398/* 13 */
     399/***/ (function(module, exports) {
     400
     401/**
     402 * wp.media.view.Attachment.Details.TwoColumn
     403 *
     404 * A similar view to media.view.Attachment.Details
     405 * for use in the Edit Attachment modal.
     406 *
     407 * @class
     408 * @augments wp.media.view.Attachment.Details
     409 * @augments wp.media.view.Attachment
     410 * @augments wp.media.View
     411 * @augments wp.Backbone.View
     412 * @augments Backbone.View
     413 */
     414var Details = wp.media.view.Attachment.Details,
     415    TwoColumn;
     416
     417TwoColumn = Details.extend({
     418    template: wp.template( 'attachment-details-two-column' ),
     419
     420    editAttachment: function( event ) {
     421        event.preventDefault();
     422        this.controller.content.mode( 'edit-image' );
     423    },
     424
     425    /**
     426     * Noop this from parent class, doesn't apply here.
     427     */
     428    toggleSelectionHandler: function() {},
     429
     430    render: function() {
     431        Details.prototype.render.apply( this, arguments );
     432
     433        wp.media.mixin.removeAllPlayers();
     434        this.$( 'audio, video' ).each( function (i, elem) {
     435            var el = wp.media.view.MediaDetails.prepareSrc( elem );
     436            new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
     437        } );
     438    }
     439});
     440
     441module.exports = TwoColumn;
     442
     443
     444/***/ }),
     445/* 14 */
     446/***/ (function(module, exports) {
     447
    43448/**
    44449 * wp.media.view.MediaFrame.Manage.Router
     
    88493module.exports = Router;
    89494
    90 },{}],4:[function(require,module,exports){
     495
     496/***/ }),
     497/* 15 */
     498/***/ (function(module, exports) {
     499
    91500/**
    92  * wp.media.view.Attachment.Details.TwoColumn
    93  *
    94  * A similar view to media.view.Attachment.Details
    95  * for use in the Edit Attachment modal.
     501 * wp.media.view.EditImage.Details
    96502 *
    97503 * @class
    98  * @augments wp.media.view.Attachment.Details
    99  * @augments wp.media.view.Attachment
     504 * @augments wp.media.view.EditImage
    100505 * @augments wp.media.View
    101506 * @augments wp.Backbone.View
    102507 * @augments Backbone.View
    103508 */
    104 var Details = wp.media.view.Attachment.Details,
    105     TwoColumn;
    106 
    107 TwoColumn = Details.extend({
    108     template: wp.template( 'attachment-details-two-column' ),
    109 
    110     editAttachment: function( event ) {
    111         event.preventDefault();
    112         this.controller.content.mode( 'edit-image' );
    113     },
    114 
    115     /**
    116      * Noop this from parent class, doesn't apply here.
    117      */
    118     toggleSelectionHandler: function() {},
    119 
    120     render: function() {
    121         Details.prototype.render.apply( this, arguments );
    122 
    123         wp.media.mixin.removeAllPlayers();
    124         this.$( 'audio, video' ).each( function (i, elem) {
    125             var el = wp.media.view.MediaDetails.prepareSrc( elem );
    126             new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
    127         } );
     509var View = wp.media.View,
     510    EditImage = wp.media.view.EditImage,
     511    Details;
     512
     513Details = EditImage.extend({
     514    initialize: function( options ) {
     515        this.editor = window.imageEdit;
     516        this.frame = options.frame;
     517        this.controller = options.controller;
     518        View.prototype.initialize.apply( this, arguments );
     519    },
     520
     521    back: function() {
     522        this.frame.content.mode( 'edit-metadata' );
     523    },
     524
     525    save: function() {
     526        this.model.fetch().done( _.bind( function() {
     527            this.frame.content.mode( 'edit-metadata' );
     528        }, this ) );
    128529    }
    129530});
    130531
    131 module.exports = TwoColumn;
    132 
    133 },{}],5:[function(require,module,exports){
     532module.exports = Details;
     533
     534
     535/***/ }),
     536/* 16 */
     537/***/ (function(module, exports) {
     538
    134539/**
    135  * wp.media.view.DeleteSelectedPermanentlyButton
    136  *
    137  * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
     540 * wp.media.view.MediaFrame.EditAttachments
     541 *
     542 * A frame for editing the details of a specific media item.
     543 *
     544 * Opens in a modal by default.
     545 *
     546 * Requires an attachment model to be passed in the options hash under `model`.
    138547 *
    139548 * @class
    140  * @augments wp.media.view.DeleteSelectedButton
    141  * @augments wp.media.view.Button
     549 * @augments wp.media.view.Frame
    142550 * @augments wp.media.View
    143551 * @augments wp.Backbone.View
    144552 * @augments Backbone.View
     553 * @mixes wp.media.controller.StateMachine
    145554 */
    146 var Button = wp.media.view.Button,
    147     DeleteSelected = wp.media.view.DeleteSelectedButton,
    148     DeleteSelectedPermanently;
    149 
    150 DeleteSelectedPermanently = DeleteSelected.extend({
     555var Frame = wp.media.view.Frame,
     556    MediaFrame = wp.media.view.MediaFrame,
     557
     558    $ = jQuery,
     559    EditAttachments;
     560
     561EditAttachments = MediaFrame.extend({
     562
     563    className: 'edit-attachment-frame',
     564    template:  wp.template( 'edit-attachment-frame' ),
     565    regions:   [ 'title', 'content' ],
     566
     567    events: {
     568        'click .left':  'previousMediaItem',
     569        'click .right': 'nextMediaItem'
     570    },
     571
    151572    initialize: function() {
    152         DeleteSelected.prototype.initialize.apply( this, arguments );
    153         this.listenTo( this.controller, 'select:activate', this.selectActivate );
    154         this.listenTo( this.controller, 'select:deactivate', this.selectDeactivate );
    155     },
    156 
    157     filterChange: function( model ) {
    158         this.canShow = ( 'trash' === model.get( 'status' ) );
    159     },
    160 
    161     selectActivate: function() {
    162         this.toggleDisabled();
    163         this.$el.toggleClass( 'hidden', ! this.canShow );
    164     },
    165 
    166     selectDeactivate: function() {
    167         this.toggleDisabled();
    168         this.$el.addClass( 'hidden' );
    169     },
    170 
    171     render: function() {
    172         Button.prototype.render.apply( this, arguments );
    173         this.selectActivate();
    174         return this;
     573        Frame.prototype.initialize.apply( this, arguments );
     574
     575        _.defaults( this.options, {
     576            modal: true,
     577            state: 'edit-attachment'
     578        });
     579
     580        this.controller = this.options.controller;
     581        this.gridRouter = this.controller.gridRouter;
     582        this.library = this.options.library;
     583
     584        if ( this.options.model ) {
     585            this.model = this.options.model;
     586        }
     587
     588        this.bindHandlers();
     589        this.createStates();
     590        this.createModal();
     591
     592        this.title.mode( 'default' );
     593        this.toggleNav();
     594    },
     595
     596    bindHandlers: function() {
     597        // Bind default title creation.
     598        this.on( 'title:create:default', this.createTitle, this );
     599
     600        // Close the modal if the attachment is deleted.
     601        this.listenTo( this.model, 'change:status destroy', this.close, this );
     602
     603        this.on( 'content:create:edit-metadata', this.editMetadataMode, this );
     604        this.on( 'content:create:edit-image', this.editImageMode, this );
     605        this.on( 'content:render:edit-image', this.editImageModeRender, this );
     606        this.on( 'close', this.detach );
     607    },
     608
     609    createModal: function() {
     610        // Initialize modal container view.
     611        if ( this.options.modal ) {
     612            this.modal = new wp.media.view.Modal({
     613                controller: this,
     614                title:      this.options.title
     615            });
     616
     617            this.modal.on( 'open', _.bind( function () {
     618                $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) );
     619            }, this ) );
     620
     621            // Completely destroy the modal DOM element when closing it.
     622            this.modal.on( 'close', _.bind( function() {
     623                this.modal.remove();
     624                $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
     625                // Restore the original focus item if possible
     626                $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus();
     627                this.resetRoute();
     628            }, this ) );
     629
     630            // Set this frame as the modal's content.
     631            this.modal.content( this );
     632            this.modal.open();
     633        }
     634    },
     635
     636    /**
     637     * Add the default states to the frame.
     638     */
     639    createStates: function() {
     640        this.states.add([
     641            new wp.media.controller.EditAttachmentMetadata( { model: this.model } )
     642        ]);
     643    },
     644
     645    /**
     646     * Content region rendering callback for the `edit-metadata` mode.
     647     *
     648     * @param {Object} contentRegion Basic object with a `view` property, which
     649     *                               should be set with the proper region view.
     650     */
     651    editMetadataMode: function( contentRegion ) {
     652        contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({
     653            controller: this,
     654            model:      this.model
     655        });
     656
     657        /**
     658         * Attach a subview to display fields added via the
     659         * `attachment_fields_to_edit` filter.
     660         */
     661        contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({
     662            controller: this,
     663            model:      this.model
     664        }) );
     665
     666        // Update browser url when navigating media details
     667        if ( this.model ) {
     668            this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
     669        }
     670    },
     671
     672    /**
     673     * Render the EditImage view into the frame's content region.
     674     *
     675     * @param {Object} contentRegion Basic object with a `view` property, which
     676     *                               should be set with the proper region view.
     677     */
     678    editImageMode: function( contentRegion ) {
     679        var editImageController = new wp.media.controller.EditImage( {
     680            model: this.model,
     681            frame: this
     682        } );
     683        // Noop some methods.
     684        editImageController._toolbar = function() {};
     685        editImageController._router = function() {};
     686        editImageController._menu = function() {};
     687
     688        contentRegion.view = new wp.media.view.EditImage.Details( {
     689            model: this.model,
     690            frame: this,
     691            controller: editImageController
     692        } );
     693    },
     694
     695    editImageModeRender: function( view ) {
     696        view.on( 'ready', view.loadEditor );
     697    },
     698
     699    toggleNav: function() {
     700        this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() );
     701        this.$('.right').toggleClass( 'disabled', ! this.hasNext() );
     702    },
     703
     704    /**
     705     * Rerender the view.
     706     */
     707    rerender: function() {
     708        // Only rerender the `content` region.
     709        if ( this.content.mode() !== 'edit-metadata' ) {
     710            this.content.mode( 'edit-metadata' );
     711        } else {
     712            this.content.render();
     713        }
     714
     715        this.toggleNav();
     716    },
     717
     718    /**
     719     * Click handler to switch to the previous media item.
     720     */
     721    previousMediaItem: function() {
     722        if ( ! this.hasPrevious() ) {
     723            this.$( '.left' ).blur();
     724            return;
     725        }
     726        this.model = this.library.at( this.getCurrentIndex() - 1 );
     727        this.rerender();
     728        this.$( '.left' ).focus();
     729    },
     730
     731    /**
     732     * Click handler to switch to the next media item.
     733     */
     734    nextMediaItem: function() {
     735        if ( ! this.hasNext() ) {
     736            this.$( '.right' ).blur();
     737            return;
     738        }
     739        this.model = this.library.at( this.getCurrentIndex() + 1 );
     740        this.rerender();
     741        this.$( '.right' ).focus();
     742    },
     743
     744    getCurrentIndex: function() {
     745        return this.library.indexOf( this.model );
     746    },
     747
     748    hasNext: function() {
     749        return ( this.getCurrentIndex() + 1 ) < this.library.length;
     750    },
     751
     752    hasPrevious: function() {
     753        return ( this.getCurrentIndex() - 1 ) > -1;
     754    },
     755    /**
     756     * Respond to the keyboard events: right arrow, left arrow, except when
     757     * focus is in a textarea or input field.
     758     */
     759    keyEvent: function( event ) {
     760        if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) {
     761            return;
     762        }
     763
     764        // The right arrow key
     765        if ( 39 === event.keyCode ) {
     766            this.nextMediaItem();
     767        }
     768        // The left arrow key
     769        if ( 37 === event.keyCode ) {
     770            this.previousMediaItem();
     771        }
     772    },
     773
     774    resetRoute: function() {
     775        this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) );
    175776    }
    176777});
    177778
    178 module.exports = DeleteSelectedPermanently;
    179 
    180 },{}],6:[function(require,module,exports){
    181 /**
    182  * wp.media.view.DeleteSelectedButton
    183  *
    184  * A button that handles bulk Delete/Trash logic
    185  *
    186  * @class
    187  * @augments wp.media.view.Button
    188  * @augments wp.media.View
    189  * @augments wp.Backbone.View
    190  * @augments Backbone.View
    191  */
    192 var Button = wp.media.view.Button,
    193     l10n = wp.media.view.l10n,
    194     DeleteSelected;
    195 
    196 DeleteSelected = Button.extend({
    197     initialize: function() {
    198         Button.prototype.initialize.apply( this, arguments );
    199         if ( this.options.filters ) {
    200             this.listenTo( this.options.filters.model, 'change', this.filterChange );
    201         }
    202         this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled );
    203     },
    204 
    205     filterChange: function( model ) {
    206         if ( 'trash' === model.get( 'status' ) ) {
    207             this.model.set( 'text', l10n.untrashSelected );
    208         } else if ( wp.media.view.settings.mediaTrash ) {
    209             this.model.set( 'text', l10n.trashSelected );
    210         } else {
    211             this.model.set( 'text', l10n.deleteSelected );
    212         }
    213     },
    214 
    215     toggleDisabled: function() {
    216         this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
    217     },
    218 
    219     render: function() {
    220         Button.prototype.render.apply( this, arguments );
    221         if ( this.controller.isModeActive( 'select' ) ) {
    222             this.$el.addClass( 'delete-selected-button' );
    223         } else {
    224             this.$el.addClass( 'delete-selected-button hidden' );
    225         }
    226         this.toggleDisabled();
    227         return this;
    228     }
    229 });
    230 
    231 module.exports = DeleteSelected;
    232 
    233 },{}],7:[function(require,module,exports){
     779module.exports = EditAttachments;
     780
     781
     782/***/ }),
     783/* 17 */
     784/***/ (function(module, exports) {
     785
    234786/**
    235787 * wp.media.view.SelectModeToggleButton
     
    305857module.exports = SelectModeToggle;
    306858
    307 },{}],8:[function(require,module,exports){
     859
     860/***/ }),
     861/* 18 */
     862/***/ (function(module, exports) {
     863
    308864/**
    309  * wp.media.view.EditImage.Details
     865 * wp.media.view.DeleteSelectedButton
     866 *
     867 * A button that handles bulk Delete/Trash logic
    310868 *
    311869 * @class
    312  * @augments wp.media.view.EditImage
     870 * @augments wp.media.view.Button
    313871 * @augments wp.media.View
    314872 * @augments wp.Backbone.View
    315873 * @augments Backbone.View
    316874 */
    317 var View = wp.media.View,
    318     EditImage = wp.media.view.EditImage,
    319     Details;
    320 
    321 Details = EditImage.extend({
    322     initialize: function( options ) {
    323         this.editor = window.imageEdit;
    324         this.frame = options.frame;
    325         this.controller = options.controller;
    326         View.prototype.initialize.apply( this, arguments );
    327     },
    328 
    329     back: function() {
    330         this.frame.content.mode( 'edit-metadata' );
    331     },
    332 
    333     save: function() {
    334         this.model.fetch().done( _.bind( function() {
    335             this.frame.content.mode( 'edit-metadata' );
    336         }, this ) );
     875var Button = wp.media.view.Button,
     876    l10n = wp.media.view.l10n,
     877    DeleteSelected;
     878
     879DeleteSelected = Button.extend({
     880    initialize: function() {
     881        Button.prototype.initialize.apply( this, arguments );
     882        if ( this.options.filters ) {
     883            this.listenTo( this.options.filters.model, 'change', this.filterChange );
     884        }
     885        this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled );
     886    },
     887
     888    filterChange: function( model ) {
     889        if ( 'trash' === model.get( 'status' ) ) {
     890            this.model.set( 'text', l10n.untrashSelected );
     891        } else if ( wp.media.view.settings.mediaTrash ) {
     892            this.model.set( 'text', l10n.trashSelected );
     893        } else {
     894            this.model.set( 'text', l10n.deleteSelected );
     895        }
     896    },
     897
     898    toggleDisabled: function() {
     899        this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
     900    },
     901
     902    render: function() {
     903        Button.prototype.render.apply( this, arguments );
     904        if ( this.controller.isModeActive( 'select' ) ) {
     905            this.$el.addClass( 'delete-selected-button' );
     906        } else {
     907            this.$el.addClass( 'delete-selected-button hidden' );
     908        }
     909        this.toggleDisabled();
     910        return this;
    337911    }
    338912});
    339913
    340 module.exports = Details;
    341 
    342 },{}],9:[function(require,module,exports){
     914module.exports = DeleteSelected;
     915
     916
     917/***/ }),
     918/* 19 */
     919/***/ (function(module, exports) {
     920
    343921/**
    344  * wp.media.view.MediaFrame.EditAttachments
    345  *
    346  * A frame for editing the details of a specific media item.
    347  *
    348  * Opens in a modal by default.
    349  *
    350  * Requires an attachment model to be passed in the options hash under `model`.
     922 * wp.media.view.DeleteSelectedPermanentlyButton
     923 *
     924 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
    351925 *
    352926 * @class
    353  * @augments wp.media.view.Frame
     927 * @augments wp.media.view.DeleteSelectedButton
     928 * @augments wp.media.view.Button
    354929 * @augments wp.media.View
    355930 * @augments wp.Backbone.View
    356931 * @augments Backbone.View
    357  * @mixes wp.media.controller.StateMachine
    358932 */
    359 var Frame = wp.media.view.Frame,
    360     MediaFrame = wp.media.view.MediaFrame,
    361 
    362     $ = jQuery,
    363     EditAttachments;
    364 
    365 EditAttachments = MediaFrame.extend({
    366 
    367     className: 'edit-attachment-frame',
    368     template:  wp.template( 'edit-attachment-frame' ),
    369     regions:   [ 'title', 'content' ],
    370 
    371     events: {
    372         'click .left':  'previousMediaItem',
    373         'click .right': 'nextMediaItem'
    374     },
    375 
     933var Button = wp.media.view.Button,
     934    DeleteSelected = wp.media.view.DeleteSelectedButton,
     935    DeleteSelectedPermanently;
     936
     937DeleteSelectedPermanently = DeleteSelected.extend({
    376938    initialize: function() {
    377         Frame.prototype.initialize.apply( this, arguments );
    378 
    379         _.defaults( this.options, {
    380             modal: true,
    381             state: 'edit-attachment'
    382         });
    383 
    384         this.controller = this.options.controller;
    385         this.gridRouter = this.controller.gridRouter;
    386         this.library = this.options.library;
    387 
    388         if ( this.options.model ) {
    389             this.model = this.options.model;
    390         }
    391 
    392         this.bindHandlers();
    393         this.createStates();
    394         this.createModal();
    395 
    396         this.title.mode( 'default' );
    397         this.toggleNav();
    398     },
    399 
    400     bindHandlers: function() {
    401         // Bind default title creation.
    402         this.on( 'title:create:default', this.createTitle, this );
    403 
    404         // Close the modal if the attachment is deleted.
    405         this.listenTo( this.model, 'change:status destroy', this.close, this );
    406 
    407         this.on( 'content:create:edit-metadata', this.editMetadataMode, this );
    408         this.on( 'content:create:edit-image', this.editImageMode, this );
    409         this.on( 'content:render:edit-image', this.editImageModeRender, this );
    410         this.on( 'close', this.detach );
    411     },
    412 
    413     createModal: function() {
    414         // Initialize modal container view.
    415         if ( this.options.modal ) {
    416             this.modal = new wp.media.view.Modal({
    417                 controller: this,
    418                 title:      this.options.title
    419             });
    420 
    421             this.modal.on( 'open', _.bind( function () {
    422                 $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) );
    423             }, this ) );
    424 
    425             // Completely destroy the modal DOM element when closing it.
    426             this.modal.on( 'close', _.bind( function() {
    427                 this.modal.remove();
    428                 $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
    429                 // Restore the original focus item if possible
    430                 $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus();
    431                 this.resetRoute();
    432             }, this ) );
    433 
    434             // Set this frame as the modal's content.
    435             this.modal.content( this );
    436             this.modal.open();
    437         }
    438     },
    439 
    440     /**
    441      * Add the default states to the frame.
    442      */
    443     createStates: function() {
    444         this.states.add([
    445             new wp.media.controller.EditAttachmentMetadata( { model: this.model } )
    446         ]);
    447     },
    448 
    449     /**
    450      * Content region rendering callback for the `edit-metadata` mode.
    451      *
    452      * @param {Object} contentRegion Basic object with a `view` property, which
    453      *                               should be set with the proper region view.
    454      */
    455     editMetadataMode: function( contentRegion ) {
    456         contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({
    457             controller: this,
    458             model:      this.model
    459         });
    460 
    461         /**
    462          * Attach a subview to display fields added via the
    463          * `attachment_fields_to_edit` filter.
    464          */
    465         contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({
    466             controller: this,
    467             model:      this.model
    468         }) );
    469 
    470         // Update browser url when navigating media details
    471         if ( this.model ) {
    472             this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
    473         }
    474     },
    475 
    476     /**
    477      * Render the EditImage view into the frame's content region.
    478      *
    479      * @param {Object} contentRegion Basic object with a `view` property, which
    480      *                               should be set with the proper region view.
    481      */
    482     editImageMode: function( contentRegion ) {
    483         var editImageController = new wp.media.controller.EditImage( {
    484             model: this.model,
    485             frame: this
    486         } );
    487         // Noop some methods.
    488         editImageController._toolbar = function() {};
    489         editImageController._router = function() {};
    490         editImageController._menu = function() {};
    491 
    492         contentRegion.view = new wp.media.view.EditImage.Details( {
    493             model: this.model,
    494             frame: this,
    495             controller: editImageController
    496         } );
    497     },
    498 
    499     editImageModeRender: function( view ) {
    500         view.on( 'ready', view.loadEditor );
    501     },
    502 
    503     toggleNav: function() {
    504         this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() );
    505         this.$('.right').toggleClass( 'disabled', ! this.hasNext() );
    506     },
    507 
    508     /**
    509      * Rerender the view.
    510      */
    511     rerender: function() {
    512         // Only rerender the `content` region.
    513         if ( this.content.mode() !== 'edit-metadata' ) {
    514             this.content.mode( 'edit-metadata' );
    515         } else {
    516             this.content.render();
    517         }
    518 
    519         this.toggleNav();
    520     },
    521 
    522     /**
    523      * Click handler to switch to the previous media item.
    524      */
    525     previousMediaItem: function() {
    526         if ( ! this.hasPrevious() ) {
    527             this.$( '.left' ).blur();
    528             return;
    529         }
    530         this.model = this.library.at( this.getCurrentIndex() - 1 );
    531         this.rerender();
    532         this.$( '.left' ).focus();
    533     },
    534 
    535     /**
    536      * Click handler to switch to the next media item.
    537      */
    538     nextMediaItem: function() {
    539         if ( ! this.hasNext() ) {
    540             this.$( '.right' ).blur();
    541             return;
    542         }
    543         this.model = this.library.at( this.getCurrentIndex() + 1 );
    544         this.rerender();
    545         this.$( '.right' ).focus();
    546     },
    547 
    548     getCurrentIndex: function() {
    549         return this.library.indexOf( this.model );
    550     },
    551 
    552     hasNext: function() {
    553         return ( this.getCurrentIndex() + 1 ) < this.library.length;
    554     },
    555 
    556     hasPrevious: function() {
    557         return ( this.getCurrentIndex() - 1 ) > -1;
    558     },
    559     /**
    560      * Respond to the keyboard events: right arrow, left arrow, except when
    561      * focus is in a textarea or input field.
    562      */
    563     keyEvent: function( event ) {
    564         if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) {
    565             return;
    566         }
    567 
    568         // The right arrow key
    569         if ( 39 === event.keyCode ) {
    570             this.nextMediaItem();
    571         }
    572         // The left arrow key
    573         if ( 37 === event.keyCode ) {
    574             this.previousMediaItem();
    575         }
    576     },
    577 
    578     resetRoute: function() {
    579         this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) );
     939        DeleteSelected.prototype.initialize.apply( this, arguments );
     940        this.listenTo( this.controller, 'select:activate', this.selectActivate );
     941        this.listenTo( this.controller, 'select:deactivate', this.selectDeactivate );
     942    },
     943
     944    filterChange: function( model ) {
     945        this.canShow = ( 'trash' === model.get( 'status' ) );
     946    },
     947
     948    selectActivate: function() {
     949        this.toggleDisabled();
     950        this.$el.toggleClass( 'hidden', ! this.canShow );
     951    },
     952
     953    selectDeactivate: function() {
     954        this.toggleDisabled();
     955        this.$el.addClass( 'hidden' );
     956    },
     957
     958    render: function() {
     959        Button.prototype.render.apply( this, arguments );
     960        this.selectActivate();
     961        return this;
    580962    }
    581963});
    582964
    583 module.exports = EditAttachments;
    584 
    585 },{}],10:[function(require,module,exports){
    586 /**
    587  * wp.media.view.MediaFrame.Manage
    588  *
    589  * A generic management frame workflow.
    590  *
    591  * Used in the media grid view.
    592  *
    593  * @class
    594  * @augments wp.media.view.MediaFrame
    595  * @augments wp.media.view.Frame
    596  * @augments wp.media.View
    597  * @augments wp.Backbone.View
    598  * @augments Backbone.View
    599  * @mixes wp.media.controller.StateMachine
    600  */
    601 var MediaFrame = wp.media.view.MediaFrame,
    602     Library = wp.media.controller.Library,
    603 
    604     $ = Backbone.$,
    605     Manage;
    606 
    607 Manage = MediaFrame.extend({
    608     /**
    609      * @global wp.Uploader
    610      */
    611     initialize: function() {
    612         _.defaults( this.options, {
    613             title:     '',
    614             modal:     false,
    615             selection: [],
    616             library:   {}, // Options hash for the query to the media library.
    617             multiple:  'add',
    618             state:     'library',
    619             uploader:  true,
    620             mode:      [ 'grid', 'edit' ]
    621         });
    622 
    623         this.$body = $( document.body );
    624         this.$window = $( window );
    625         this.$adminBar = $( '#wpadminbar' );
    626         this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) );
    627         $( document ).on( 'click', '.page-title-action', _.bind( this.addNewClickHandler, this ) );
    628 
    629         // Ensure core and media grid view UI is enabled.
    630         this.$el.addClass('wp-core-ui');
    631 
    632         // Force the uploader off if the upload limit has been exceeded or
    633         // if the browser isn't supported.
    634         if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
    635             this.options.uploader = false;
    636         }
    637 
    638         // Initialize a window-wide uploader.
    639         if ( this.options.uploader ) {
    640             this.uploader = new wp.media.view.UploaderWindow({
    641                 controller: this,
    642                 uploader: {
    643                     dropzone:  document.body,
    644                     container: document.body
    645                 }
    646             }).render();
    647             this.uploader.ready();
    648             $('body').append( this.uploader.el );
    649 
    650             this.options.uploader = false;
    651         }
    652 
    653         this.gridRouter = new wp.media.view.MediaFrame.Manage.Router();
    654 
    655         // Call 'initialize' directly on the parent class.
    656         MediaFrame.prototype.initialize.apply( this, arguments );
    657 
    658         // Append the frame view directly the supplied container.
    659         this.$el.appendTo( this.options.container );
    660 
    661         this.createStates();
    662         this.bindRegionModeHandlers();
    663         this.render();
    664         this.bindSearchHandler();
    665     },
    666 
    667     bindSearchHandler: function() {
    668         var search = this.$( '#media-search-input' ),
    669             currentSearch = this.options.container.data( 'search' ),
    670             searchView = this.browserView.toolbar.get( 'search' ).$el,
    671             listMode = this.$( '.view-list' ),
    672 
    673             input  = _.debounce( function (e) {
    674                 var val = $( e.currentTarget ).val(),
    675                     url = '';
    676 
    677                 if ( val ) {
    678                     url += '?search=' + val;
    679                 }
    680                 this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
    681             }, 1000 );
    682 
    683         // Update the URL when entering search string (at most once per second)
    684         search.on( 'input', _.bind( input, this ) );
    685         searchView.val( currentSearch ).trigger( 'input' );
    686 
    687         this.gridRouter.on( 'route:search', function () {
    688             var href = window.location.href;
    689             if ( href.indexOf( 'mode=' ) > -1 ) {
    690                 href = href.replace( /mode=[^&]+/g, 'mode=list' );
    691             } else {
    692                 href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
    693             }
    694             href = href.replace( 'search=', 's=' );
    695             listMode.prop( 'href', href );
    696         } );
    697     },
    698 
    699     /**
    700      * Create the default states for the frame.
    701      */
    702     createStates: function() {
    703         var options = this.options;
    704 
    705         if ( this.options.states ) {
    706             return;
    707         }
    708 
    709         // Add the default states.
    710         this.states.add([
    711             new Library({
    712                 library:            wp.media.query( options.library ),
    713                 multiple:           options.multiple,
    714                 title:              options.title,
    715                 content:            'browse',
    716                 toolbar:            'select',
    717                 contentUserSetting: false,
    718                 filterable:         'all',
    719                 autoSelect:         false
    720             })
    721         ]);
    722     },
    723 
    724     /**
    725      * Bind region mode activation events to proper handlers.
    726      */
    727     bindRegionModeHandlers: function() {
    728         this.on( 'content:create:browse', this.browseContent, this );
    729 
    730         // Handle a frame-level event for editing an attachment.
    731         this.on( 'edit:attachment', this.openEditAttachmentModal, this );
    732 
    733         this.on( 'select:activate', this.bindKeydown, this );
    734         this.on( 'select:deactivate', this.unbindKeydown, this );
    735     },
    736 
    737     handleKeydown: function( e ) {
    738         if ( 27 === e.which ) {
    739             e.preventDefault();
    740             this.deactivateMode( 'select' ).activateMode( 'edit' );
    741         }
    742     },
    743 
    744     bindKeydown: function() {
    745         this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) );
    746     },
    747 
    748     unbindKeydown: function() {
    749         this.$body.off( 'keydown.select' );
    750     },
    751 
    752     fixPosition: function() {
    753         var $browser, $toolbar;
    754         if ( ! this.isModeActive( 'select' ) ) {
    755             return;
    756         }
    757 
    758         $browser = this.$('.attachments-browser');
    759         $toolbar = $browser.find('.media-toolbar');
    760 
    761         // Offset doesn't appear to take top margin into account, hence +16
    762         if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) {
    763             $browser.addClass( 'fixed' );
    764             $toolbar.css('width', $browser.width() + 'px');
    765         } else {
    766             $browser.removeClass( 'fixed' );
    767             $toolbar.css('width', '');
    768         }
    769     },
    770 
    771     /**
    772      * Click handler for the `Add New` button.
    773      */
    774     addNewClickHandler: function( event ) {
    775         event.preventDefault();
    776         this.trigger( 'toggle:upload:attachment' );
    777     },
    778 
    779     /**
    780      * Open the Edit Attachment modal.
    781      */
    782     openEditAttachmentModal: function( model ) {
    783         // Create a new EditAttachment frame, passing along the library and the attachment model.
    784         wp.media( {
    785             frame:       'edit-attachments',
    786             controller:  this,
    787             library:     this.state().get('library'),
    788             model:       model
    789         } );
    790     },
    791 
    792     /**
    793      * Create an attachments browser view within the content region.
    794      *
    795      * @param {Object} contentRegion Basic object with a `view` property, which
    796      *                               should be set with the proper region view.
    797      * @this wp.media.controller.Region
    798      */
    799     browseContent: function( contentRegion ) {
    800         var state = this.state();
    801 
    802         // Browse our library of attachments.
    803         this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({
    804             controller: this,
    805             collection: state.get('library'),
    806             selection:  state.get('selection'),
    807             model:      state,
    808             sortable:   state.get('sortable'),
    809             search:     state.get('searchable'),
    810             filters:    state.get('filterable'),
    811             date:       state.get('date'),
    812             display:    state.get('displaySettings'),
    813             dragInfo:   state.get('dragInfo'),
    814             sidebar:    'errors',
    815 
    816             suggestedWidth:  state.get('suggestedWidth'),
    817             suggestedHeight: state.get('suggestedHeight'),
    818 
    819             AttachmentView: state.get('AttachmentView'),
    820 
    821             scrollElement: document
    822         });
    823         this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) );
    824 
    825         this.errors = wp.Uploader.errors;
    826         this.errors.on( 'add remove reset', this.sidebarVisibility, this );
    827     },
    828 
    829     sidebarVisibility: function() {
    830         this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length );
    831     },
    832 
    833     bindDeferred: function() {
    834         if ( ! this.browserView.dfd ) {
    835             return;
    836         }
    837         this.browserView.dfd.done( _.bind( this.startHistory, this ) );
    838     },
    839 
    840     startHistory: function() {
    841         // Verify pushState support and activate
    842         if ( window.history && window.history.pushState ) {
    843             Backbone.history.start( {
    844                 root: window._wpMediaGridSettings.adminUrl,
    845                 pushState: true
    846             } );
    847         }
    848     }
    849 });
    850 
    851 module.exports = Manage;
    852 
    853 },{}]},{},[2]);
     965module.exports = DeleteSelectedPermanently;
     966
     967
     968/***/ })
     969/******/ ]);
  • branches/4.4/src/wp-includes/js/media-views.js

    r36167 r46498  
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     1/******/ (function(modules) { // webpackBootstrap
     2/******/    // The module cache
     3/******/    var installedModules = {};
     4/******/
     5/******/    // The require function
     6/******/    function __webpack_require__(moduleId) {
     7/******/
     8/******/        // Check if module is in cache
     9/******/        if(installedModules[moduleId]) {
     10/******/            return installedModules[moduleId].exports;
     11/******/        }
     12/******/        // Create a new module (and put it into the cache)
     13/******/        var module = installedModules[moduleId] = {
     14/******/            i: moduleId,
     15/******/            l: false,
     16/******/            exports: {}
     17/******/        };
     18/******/
     19/******/        // Execute the module function
     20/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/        // Flag the module as loaded
     23/******/        module.l = true;
     24/******/
     25/******/        // Return the exports of the module
     26/******/        return module.exports;
     27/******/    }
     28/******/
     29/******/
     30/******/    // expose the modules object (__webpack_modules__)
     31/******/    __webpack_require__.m = modules;
     32/******/
     33/******/    // expose the module cache
     34/******/    __webpack_require__.c = installedModules;
     35/******/
     36/******/    // define getter function for harmony exports
     37/******/    __webpack_require__.d = function(exports, name, getter) {
     38/******/        if(!__webpack_require__.o(exports, name)) {
     39/******/            Object.defineProperty(exports, name, {
     40/******/                configurable: false,
     41/******/                enumerable: true,
     42/******/                get: getter
     43/******/            });
     44/******/        }
     45/******/    };
     46/******/
     47/******/    // getDefaultExport function for compatibility with non-harmony modules
     48/******/    __webpack_require__.n = function(module) {
     49/******/        var getter = module && module.__esModule ?
     50/******/            function getDefault() { return module['default']; } :
     51/******/            function getModuleExports() { return module; };
     52/******/        __webpack_require__.d(getter, 'a', getter);
     53/******/        return getter;
     54/******/    };
     55/******/
     56/******/    // Object.prototype.hasOwnProperty.call
     57/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/    // __webpack_public_path__
     60/******/    __webpack_require__.p = "";
     61/******/
     62/******/    // Load entry module and return exports
     63/******/    return __webpack_require__(__webpack_require__.s = 26);
     64/******/ })
     65/************************************************************************/
     66/******/ (Array(26).concat([
     67/* 26 */
     68/***/ (function(module, exports, __webpack_require__) {
     69
     70var media = wp.media,
     71    $ = jQuery,
     72    l10n;
     73
     74media.isTouchDevice = ( 'ontouchend' in document );
     75
     76// Link any localized strings.
     77l10n = media.view.l10n = window._wpMediaViewsL10n || {};
     78
     79// Link any settings.
     80media.view.settings = l10n.settings || {};
     81delete l10n.settings;
     82
     83// Copy the `post` setting over to the model settings.
     84media.model.settings.post = media.view.settings.post;
     85
     86// Check if the browser supports CSS 3.0 transitions
     87$.support.transition = (function(){
     88    var style = document.documentElement.style,
     89        transitions = {
     90            WebkitTransition: 'webkitTransitionEnd',
     91            MozTransition:    'transitionend',
     92            OTransition:      'oTransitionEnd otransitionend',
     93            transition:       'transitionend'
     94        }, transition;
     95
     96    transition = _.find( _.keys( transitions ), function( transition ) {
     97        return ! _.isUndefined( style[ transition ] );
     98    });
     99
     100    return transition && {
     101        end: transitions[ transition ]
     102    };
     103}());
     104
    2105/**
    3  * wp.media.controller.CollectionAdd
    4  *
    5  * A state for adding attachments to a collection (e.g. video playlist).
     106 * A shared event bus used to provide events into
     107 * the media workflows that 3rd-party devs can use to hook
     108 * in.
     109 */
     110media.events = _.extend( {}, Backbone.Events );
     111
     112/**
     113 * Makes it easier to bind events using transitions.
     114 *
     115 * @param {string} selector
     116 * @param {Number} sensitivity
     117 * @returns {Promise}
     118 */
     119media.transition = function( selector, sensitivity ) {
     120    var deferred = $.Deferred();
     121
     122    sensitivity = sensitivity || 2000;
     123
     124    if ( $.support.transition ) {
     125        if ( ! (selector instanceof $) ) {
     126            selector = $( selector );
     127        }
     128
     129        // Resolve the deferred when the first element finishes animating.
     130        selector.first().one( $.support.transition.end, deferred.resolve );
     131
     132        // Just in case the event doesn't trigger, fire a callback.
     133        _.delay( deferred.resolve, sensitivity );
     134
     135    // Otherwise, execute on the spot.
     136    } else {
     137        deferred.resolve();
     138    }
     139
     140    return deferred.promise();
     141};
     142
     143media.controller.Region = __webpack_require__( 27 );
     144media.controller.StateMachine = __webpack_require__( 28 );
     145media.controller.State = __webpack_require__( 29 );
     146
     147media.selectionSync = __webpack_require__( 30 );
     148media.controller.Library = __webpack_require__( 31 );
     149media.controller.ImageDetails = __webpack_require__( 32 );
     150media.controller.GalleryEdit = __webpack_require__( 33 );
     151media.controller.GalleryAdd = __webpack_require__( 34 );
     152media.controller.CollectionEdit = __webpack_require__( 35 );
     153media.controller.CollectionAdd = __webpack_require__( 36 );
     154media.controller.FeaturedImage = __webpack_require__( 37 );
     155media.controller.ReplaceImage = __webpack_require__( 38 );
     156media.controller.EditImage = __webpack_require__( 39 );
     157media.controller.MediaLibrary = __webpack_require__( 40 );
     158media.controller.Embed = __webpack_require__( 41 );
     159media.controller.Cropper = __webpack_require__( 42 );
     160media.controller.CustomizeImageCropper = __webpack_require__( 43 );
     161media.controller.SiteIconCropper = __webpack_require__( 44 );
     162
     163media.View = __webpack_require__( 45 );
     164media.view.Frame = __webpack_require__( 46 );
     165media.view.MediaFrame = __webpack_require__( 47 );
     166media.view.MediaFrame.Select = __webpack_require__( 48 );
     167media.view.MediaFrame.Post = __webpack_require__( 49 );
     168media.view.MediaFrame.ImageDetails = __webpack_require__( 50 );
     169media.view.Modal = __webpack_require__( 51 );
     170media.view.FocusManager = __webpack_require__( 52 );
     171media.view.UploaderWindow = __webpack_require__( 53 );
     172media.view.EditorUploader = __webpack_require__( 54 );
     173media.view.UploaderInline = __webpack_require__( 55 );
     174media.view.UploaderStatus = __webpack_require__( 56 );
     175media.view.UploaderStatusError = __webpack_require__( 57 );
     176media.view.Toolbar = __webpack_require__( 58 );
     177media.view.Toolbar.Select = __webpack_require__( 59 );
     178media.view.Toolbar.Embed = __webpack_require__( 60 );
     179media.view.Button = __webpack_require__( 61 );
     180media.view.ButtonGroup = __webpack_require__( 62 );
     181media.view.PriorityList = __webpack_require__( 63 );
     182media.view.MenuItem = __webpack_require__( 64 );
     183media.view.Menu = __webpack_require__( 65 );
     184media.view.RouterItem = __webpack_require__( 66 );
     185media.view.Router = __webpack_require__( 67 );
     186media.view.Sidebar = __webpack_require__( 68 );
     187media.view.Attachment = __webpack_require__( 69 );
     188media.view.Attachment.Library = __webpack_require__( 70 );
     189media.view.Attachment.EditLibrary = __webpack_require__( 71 );
     190media.view.Attachments = __webpack_require__( 72 );
     191media.view.Search = __webpack_require__( 73 );
     192media.view.AttachmentFilters = __webpack_require__( 74 );
     193media.view.DateFilter = __webpack_require__( 75 );
     194media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 );
     195media.view.AttachmentFilters.All = __webpack_require__( 77 );
     196media.view.AttachmentsBrowser = __webpack_require__( 78 );
     197media.view.Selection = __webpack_require__( 79 );
     198media.view.Attachment.Selection = __webpack_require__( 80 );
     199media.view.Attachments.Selection = __webpack_require__( 81 );
     200media.view.Attachment.EditSelection = __webpack_require__( 82 );
     201media.view.Settings = __webpack_require__( 83 );
     202media.view.Settings.AttachmentDisplay = __webpack_require__( 84 );
     203media.view.Settings.Gallery = __webpack_require__( 85 );
     204media.view.Settings.Playlist = __webpack_require__( 86 );
     205media.view.Attachment.Details = __webpack_require__( 87 );
     206media.view.AttachmentCompat = __webpack_require__( 88 );
     207media.view.Iframe = __webpack_require__( 89 );
     208media.view.Embed = __webpack_require__( 90 );
     209media.view.Label = __webpack_require__( 91 );
     210media.view.EmbedUrl = __webpack_require__( 92 );
     211media.view.EmbedLink = __webpack_require__( 93 );
     212media.view.EmbedImage = __webpack_require__( 94 );
     213media.view.ImageDetails = __webpack_require__( 95 );
     214media.view.Cropper = __webpack_require__( 96 );
     215media.view.SiteIconCropper = __webpack_require__( 97 );
     216media.view.SiteIconPreview = __webpack_require__( 98 );
     217media.view.EditImage = __webpack_require__( 99 );
     218media.view.Spinner = __webpack_require__( 100 );
     219
     220
     221/***/ }),
     222/* 27 */
     223/***/ (function(module, exports) {
     224
     225/**
     226 * wp.media.controller.Region
     227 *
     228 * A region is a persistent application layout area.
     229 *
     230 * A region assumes one mode at any time, and can be switched to another.
     231 *
     232 * When mode changes, events are triggered on the region's parent view.
     233 * The parent view will listen to specific events and fill the region with an
     234 * appropriate view depending on mode. For example, a frame listens for the
     235 * 'browse' mode t be activated on the 'content' view and then fills the region
     236 * with an AttachmentsBrowser view.
     237 *
     238 * @class
     239 *
     240 * @param {object}        options          Options hash for the region.
     241 * @param {string}        options.id       Unique identifier for the region.
     242 * @param {Backbone.View} options.view     A parent view the region exists within.
     243 * @param {string}        options.selector jQuery selector for the region within the parent view.
     244 */
     245var Region = function( options ) {
     246    _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
     247};
     248
     249// Use Backbone's self-propagating `extend` inheritance method.
     250Region.extend = Backbone.Model.extend;
     251
     252_.extend( Region.prototype, {
     253    /**
     254     * Activate a mode.
     255     *
     256     * @since 3.5.0
     257     *
     258     * @param {string} mode
     259     *
     260     * @fires this.view#{this.id}:activate:{this._mode}
     261     * @fires this.view#{this.id}:activate
     262     * @fires this.view#{this.id}:deactivate:{this._mode}
     263     * @fires this.view#{this.id}:deactivate
     264     *
     265     * @returns {wp.media.controller.Region} Returns itself to allow chaining.
     266     */
     267    mode: function( mode ) {
     268        if ( ! mode ) {
     269            return this._mode;
     270        }
     271        // Bail if we're trying to change to the current mode.
     272        if ( mode === this._mode ) {
     273            return this;
     274        }
     275
     276        /**
     277         * Region mode deactivation event.
     278         *
     279         * @event this.view#{this.id}:deactivate:{this._mode}
     280         * @event this.view#{this.id}:deactivate
     281         */
     282        this.trigger('deactivate');
     283
     284        this._mode = mode;
     285        this.render( mode );
     286
     287        /**
     288         * Region mode activation event.
     289         *
     290         * @event this.view#{this.id}:activate:{this._mode}
     291         * @event this.view#{this.id}:activate
     292         */
     293        this.trigger('activate');
     294        return this;
     295    },
     296    /**
     297     * Render a mode.
     298     *
     299     * @since 3.5.0
     300     *
     301     * @param {string} mode
     302     *
     303     * @fires this.view#{this.id}:create:{this._mode}
     304     * @fires this.view#{this.id}:create
     305     * @fires this.view#{this.id}:render:{this._mode}
     306     * @fires this.view#{this.id}:render
     307     *
     308     * @returns {wp.media.controller.Region} Returns itself to allow chaining
     309     */
     310    render: function( mode ) {
     311        // If the mode isn't active, activate it.
     312        if ( mode && mode !== this._mode ) {
     313            return this.mode( mode );
     314        }
     315
     316        var set = { view: null },
     317            view;
     318
     319        /**
     320         * Create region view event.
     321         *
     322         * Region view creation takes place in an event callback on the frame.
     323         *
     324         * @event this.view#{this.id}:create:{this._mode}
     325         * @event this.view#{this.id}:create
     326         */
     327        this.trigger( 'create', set );
     328        view = set.view;
     329
     330        /**
     331         * Render region view event.
     332         *
     333         * Region view creation takes place in an event callback on the frame.
     334         *
     335         * @event this.view#{this.id}:create:{this._mode}
     336         * @event this.view#{this.id}:create
     337         */
     338        this.trigger( 'render', view );
     339        if ( view ) {
     340            this.set( view );
     341        }
     342        return this;
     343    },
     344
     345    /**
     346     * Get the region's view.
     347     *
     348     * @since 3.5.0
     349     *
     350     * @returns {wp.media.View}
     351     */
     352    get: function() {
     353        return this.view.views.first( this.selector );
     354    },
     355
     356    /**
     357     * Set the region's view as a subview of the frame.
     358     *
     359     * @since 3.5.0
     360     *
     361     * @param {Array|Object} views
     362     * @param {Object} [options={}]
     363     * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
     364     */
     365    set: function( views, options ) {
     366        if ( options ) {
     367            options.add = false;
     368        }
     369        return this.view.views.set( this.selector, views, options );
     370    },
     371
     372    /**
     373     * Trigger regional view events on the frame.
     374     *
     375     * @since 3.5.0
     376     *
     377     * @param {string} event
     378     * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
     379     */
     380    trigger: function( event ) {
     381        var base, args;
     382
     383        if ( ! this._mode ) {
     384            return;
     385        }
     386
     387        args = _.toArray( arguments );
     388        base = this.id + ':' + event;
     389
     390        // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
     391        args[0] = base + ':' + this._mode;
     392        this.view.trigger.apply( this.view, args );
     393
     394        // Trigger `{this.id}:{event}` event on the frame.
     395        args[0] = base;
     396        this.view.trigger.apply( this.view, args );
     397        return this;
     398    }
     399});
     400
     401module.exports = Region;
     402
     403
     404/***/ }),
     405/* 28 */
     406/***/ (function(module, exports) {
     407
     408/**
     409 * wp.media.controller.StateMachine
     410 *
     411 * A state machine keeps track of state. It is in one state at a time,
     412 * and can change from one state to another.
     413 *
     414 * States are stored as models in a Backbone collection.
     415 *
     416 * @since 3.5.0
     417 *
     418 * @class
     419 * @augments Backbone.Model
     420 * @mixin
     421 * @mixes Backbone.Events
     422 *
     423 * @param {Array} states
     424 */
     425var StateMachine = function( states ) {
     426    // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
     427    this.states = new Backbone.Collection( states );
     428};
     429
     430// Use Backbone's self-propagating `extend` inheritance method.
     431StateMachine.extend = Backbone.Model.extend;
     432
     433_.extend( StateMachine.prototype, Backbone.Events, {
     434    /**
     435     * Fetch a state.
     436     *
     437     * If no `id` is provided, returns the active state.
     438     *
     439     * Implicitly creates states.
     440     *
     441     * Ensure that the `states` collection exists so the `StateMachine`
     442     *   can be used as a mixin.
     443     *
     444     * @since 3.5.0
     445     *
     446     * @param {string} id
     447     * @returns {wp.media.controller.State} Returns a State model
     448     *   from the StateMachine collection
     449     */
     450    state: function( id ) {
     451        this.states = this.states || new Backbone.Collection();
     452
     453        // Default to the active state.
     454        id = id || this._state;
     455
     456        if ( id && ! this.states.get( id ) ) {
     457            this.states.add({ id: id });
     458        }
     459        return this.states.get( id );
     460    },
     461
     462    /**
     463     * Sets the active state.
     464     *
     465     * Bail if we're trying to select the current state, if we haven't
     466     * created the `states` collection, or are trying to select a state
     467     * that does not exist.
     468     *
     469     * @since 3.5.0
     470     *
     471     * @param {string} id
     472     *
     473     * @fires wp.media.controller.State#deactivate
     474     * @fires wp.media.controller.State#activate
     475     *
     476     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
     477     */
     478    setState: function( id ) {
     479        var previous = this.state();
     480
     481        if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
     482            return this;
     483        }
     484
     485        if ( previous ) {
     486            previous.trigger('deactivate');
     487            this._lastState = previous.id;
     488        }
     489
     490        this._state = id;
     491        this.state().trigger('activate');
     492
     493        return this;
     494    },
     495
     496    /**
     497     * Returns the previous active state.
     498     *
     499     * Call the `state()` method with no parameters to retrieve the current
     500     * active state.
     501     *
     502     * @since 3.5.0
     503     *
     504     * @returns {wp.media.controller.State} Returns a State model
     505     *    from the StateMachine collection
     506     */
     507    lastState: function() {
     508        if ( this._lastState ) {
     509            return this.state( this._lastState );
     510        }
     511    }
     512});
     513
     514// Map all event binding and triggering on a StateMachine to its `states` collection.
     515_.each([ 'on', 'off', 'trigger' ], function( method ) {
     516    /**
     517     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     518     */
     519    StateMachine.prototype[ method ] = function() {
     520        // Ensure that the `states` collection exists so the `StateMachine`
     521        // can be used as a mixin.
     522        this.states = this.states || new Backbone.Collection();
     523        // Forward the method to the `states` collection.
     524        this.states[ method ].apply( this.states, arguments );
     525        return this;
     526    };
     527});
     528
     529module.exports = StateMachine;
     530
     531
     532/***/ }),
     533/* 29 */
     534/***/ (function(module, exports) {
     535
     536/**
     537 * wp.media.controller.State
     538 *
     539 * A state is a step in a workflow that when set will trigger the controllers
     540 * for the regions to be updated as specified in the frame.
     541 *
     542 * A state has an event-driven lifecycle:
     543 *
     544 *     'ready'      triggers when a state is added to a state machine's collection.
     545 *     'activate'   triggers when a state is activated by a state machine.
     546 *     'deactivate' triggers when a state is deactivated by a state machine.
     547 *     'reset'      is not triggered automatically. It should be invoked by the
     548 *                  proper controller to reset the state to its default.
     549 *
     550 * @class
     551 * @augments Backbone.Model
     552 */
     553var State = Backbone.Model.extend({
     554    /**
     555     * Constructor.
     556     *
     557     * @since 3.5.0
     558     */
     559    constructor: function() {
     560        this.on( 'activate', this._preActivate, this );
     561        this.on( 'activate', this.activate, this );
     562        this.on( 'activate', this._postActivate, this );
     563        this.on( 'deactivate', this._deactivate, this );
     564        this.on( 'deactivate', this.deactivate, this );
     565        this.on( 'reset', this.reset, this );
     566        this.on( 'ready', this._ready, this );
     567        this.on( 'ready', this.ready, this );
     568        /**
     569         * Call parent constructor with passed arguments
     570         */
     571        Backbone.Model.apply( this, arguments );
     572        this.on( 'change:menu', this._updateMenu, this );
     573    },
     574    /**
     575     * Ready event callback.
     576     *
     577     * @abstract
     578     * @since 3.5.0
     579     */
     580    ready: function() {},
     581
     582    /**
     583     * Activate event callback.
     584     *
     585     * @abstract
     586     * @since 3.5.0
     587     */
     588    activate: function() {},
     589
     590    /**
     591     * Deactivate event callback.
     592     *
     593     * @abstract
     594     * @since 3.5.0
     595     */
     596    deactivate: function() {},
     597
     598    /**
     599     * Reset event callback.
     600     *
     601     * @abstract
     602     * @since 3.5.0
     603     */
     604    reset: function() {},
     605
     606    /**
     607     * @access private
     608     * @since 3.5.0
     609     */
     610    _ready: function() {
     611        this._updateMenu();
     612    },
     613
     614    /**
     615     * @access private
     616     * @since 3.5.0
     617    */
     618    _preActivate: function() {
     619        this.active = true;
     620    },
     621
     622    /**
     623     * @access private
     624     * @since 3.5.0
     625     */
     626    _postActivate: function() {
     627        this.on( 'change:menu', this._menu, this );
     628        this.on( 'change:titleMode', this._title, this );
     629        this.on( 'change:content', this._content, this );
     630        this.on( 'change:toolbar', this._toolbar, this );
     631
     632        this.frame.on( 'title:render:default', this._renderTitle, this );
     633
     634        this._title();
     635        this._menu();
     636        this._toolbar();
     637        this._content();
     638        this._router();
     639    },
     640
     641    /**
     642     * @access private
     643     * @since 3.5.0
     644     */
     645    _deactivate: function() {
     646        this.active = false;
     647
     648        this.frame.off( 'title:render:default', this._renderTitle, this );
     649
     650        this.off( 'change:menu', this._menu, this );
     651        this.off( 'change:titleMode', this._title, this );
     652        this.off( 'change:content', this._content, this );
     653        this.off( 'change:toolbar', this._toolbar, this );
     654    },
     655
     656    /**
     657     * @access private
     658     * @since 3.5.0
     659     */
     660    _title: function() {
     661        this.frame.title.render( this.get('titleMode') || 'default' );
     662    },
     663
     664    /**
     665     * @access private
     666     * @since 3.5.0
     667     */
     668    _renderTitle: function( view ) {
     669        view.$el.text( this.get('title') || '' );
     670    },
     671
     672    /**
     673     * @access private
     674     * @since 3.5.0
     675     */
     676    _router: function() {
     677        var router = this.frame.router,
     678            mode = this.get('router'),
     679            view;
     680
     681        this.frame.$el.toggleClass( 'hide-router', ! mode );
     682        if ( ! mode ) {
     683            return;
     684        }
     685
     686        this.frame.router.render( mode );
     687
     688        view = router.get();
     689        if ( view && view.select ) {
     690            view.select( this.frame.content.mode() );
     691        }
     692    },
     693
     694    /**
     695     * @access private
     696     * @since 3.5.0
     697     */
     698    _menu: function() {
     699        var menu = this.frame.menu,
     700            mode = this.get('menu'),
     701            view;
     702
     703        this.frame.$el.toggleClass( 'hide-menu', ! mode );
     704        if ( ! mode ) {
     705            return;
     706        }
     707
     708        menu.mode( mode );
     709
     710        view = menu.get();
     711        if ( view && view.select ) {
     712            view.select( this.id );
     713        }
     714    },
     715
     716    /**
     717     * @access private
     718     * @since 3.5.0
     719     */
     720    _updateMenu: function() {
     721        var previous = this.previous('menu'),
     722            menu = this.get('menu');
     723
     724        if ( previous ) {
     725            this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
     726        }
     727
     728        if ( menu ) {
     729            this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
     730        }
     731    },
     732
     733    /**
     734     * Create a view in the media menu for the state.
     735     *
     736     * @access private
     737     * @since 3.5.0
     738     *
     739     * @param {media.view.Menu} view The menu view.
     740     */
     741    _renderMenu: function( view ) {
     742        var menuItem = this.get('menuItem'),
     743            title = this.get('title'),
     744            priority = this.get('priority');
     745
     746        if ( ! menuItem && title ) {
     747            menuItem = { text: title };
     748
     749            if ( priority ) {
     750                menuItem.priority = priority;
     751            }
     752        }
     753
     754        if ( ! menuItem ) {
     755            return;
     756        }
     757
     758        view.set( this.id, menuItem );
     759    }
     760});
     761
     762_.each(['toolbar','content'], function( region ) {
     763    /**
     764     * @access private
     765     */
     766    State.prototype[ '_' + region ] = function() {
     767        var mode = this.get( region );
     768        if ( mode ) {
     769            this.frame[ region ].render( mode );
     770        }
     771    };
     772});
     773
     774module.exports = State;
     775
     776
     777/***/ }),
     778/* 30 */
     779/***/ (function(module, exports) {
     780
     781/**
     782 * wp.media.selectionSync
     783 *
     784 * Sync an attachments selection in a state with another state.
     785 *
     786 * Allows for selecting multiple images in the Insert Media workflow, and then
     787 * switching to the Insert Gallery workflow while preserving the attachments selection.
     788 *
     789 * @mixin
     790 */
     791var selectionSync = {
     792    /**
     793     * @since 3.5.0
     794     */
     795    syncSelection: function() {
     796        var selection = this.get('selection'),
     797            manager = this.frame._selection;
     798
     799        if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     800            return;
     801        }
     802
     803        // If the selection supports multiple items, validate the stored
     804        // attachments based on the new selection's conditions. Record
     805        // the attachments that are not included; we'll maintain a
     806        // reference to those. Other attachments are considered in flux.
     807        if ( selection.multiple ) {
     808            selection.reset( [], { silent: true });
     809            selection.validateAll( manager.attachments );
     810            manager.difference = _.difference( manager.attachments.models, selection.models );
     811        }
     812
     813        // Sync the selection's single item with the master.
     814        selection.single( manager.single );
     815    },
     816
     817    /**
     818     * Record the currently active attachments, which is a combination
     819     * of the selection's attachments and the set of selected
     820     * attachments that this specific selection considered invalid.
     821     * Reset the difference and record the single attachment.
     822     *
     823     * @since 3.5.0
     824     */
     825    recordSelection: function() {
     826        var selection = this.get('selection'),
     827            manager = this.frame._selection;
     828
     829        if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     830            return;
     831        }
     832
     833        if ( selection.multiple ) {
     834            manager.attachments.reset( selection.toArray().concat( manager.difference ) );
     835            manager.difference = [];
     836        } else {
     837            manager.attachments.add( selection.toArray() );
     838        }
     839
     840        manager.single = selection._single;
     841    }
     842};
     843
     844module.exports = selectionSync;
     845
     846
     847/***/ }),
     848/* 31 */
     849/***/ (function(module, exports) {
     850
     851/**
     852 * wp.media.controller.Library
     853 *
     854 * A state for choosing an attachment or group of attachments from the media library.
     855 *
     856 * @class
     857 * @augments wp.media.controller.State
     858 * @augments Backbone.Model
     859 * @mixes media.selectionSync
     860 *
     861 * @param {object}                          [attributes]                         The attributes hash passed to the state.
     862 * @param {string}                          [attributes.id=library]              Unique identifier.
     863 * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
     864 * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
     865 *                                                                               If one is not supplied, a collection of all attachments will be created.
     866 * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
     867 *                                                                               If the 'selection' attribute is a plain JS object,
     868 *                                                                               a Selection will be created using its values as the selection instance's `props` model.
     869 *                                                                               Otherwise, it will copy the library's `props` model.
     870 * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
     871 * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
     872 *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
     873 * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
     874 * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
     875 * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
     876 * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
     877 * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
     878 *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
     879 * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     880 * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     881 * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     882 * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     883 * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
     884 */
     885var l10n = wp.media.view.l10n,
     886    getUserSetting = window.getUserSetting,
     887    setUserSetting = window.setUserSetting,
     888    Library;
     889
     890Library = wp.media.controller.State.extend({
     891    defaults: {
     892        id:                 'library',
     893        title:              l10n.mediaLibraryTitle,
     894        multiple:           false,
     895        content:            'upload',
     896        menu:               'default',
     897        router:             'browse',
     898        toolbar:            'select',
     899        searchable:         true,
     900        filterable:         false,
     901        sortable:           true,
     902        autoSelect:         true,
     903        describe:           false,
     904        contentUserSetting: true,
     905        syncSelection:      true
     906    },
     907
     908    /**
     909     * If a library isn't provided, query all media items.
     910     * If a selection instance isn't provided, create one.
     911     *
     912     * @since 3.5.0
     913     */
     914    initialize: function() {
     915        var selection = this.get('selection'),
     916            props;
     917
     918        if ( ! this.get('library') ) {
     919            this.set( 'library', wp.media.query() );
     920        }
     921
     922        if ( ! ( selection instanceof wp.media.model.Selection ) ) {
     923            props = selection;
     924
     925            if ( ! props ) {
     926                props = this.get('library').props.toJSON();
     927                props = _.omit( props, 'orderby', 'query' );
     928            }
     929
     930            this.set( 'selection', new wp.media.model.Selection( null, {
     931                multiple: this.get('multiple'),
     932                props: props
     933            }) );
     934        }
     935
     936        this.resetDisplays();
     937    },
     938
     939    /**
     940     * @since 3.5.0
     941     */
     942    activate: function() {
     943        this.syncSelection();
     944
     945        wp.Uploader.queue.on( 'add', this.uploading, this );
     946
     947        this.get('selection').on( 'add remove reset', this.refreshContent, this );
     948
     949        if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
     950            this.frame.on( 'content:activate', this.saveContentMode, this );
     951            this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
     952        }
     953    },
     954
     955    /**
     956     * @since 3.5.0
     957     */
     958    deactivate: function() {
     959        this.recordSelection();
     960
     961        this.frame.off( 'content:activate', this.saveContentMode, this );
     962
     963        // Unbind all event handlers that use this state as the context
     964        // from the selection.
     965        this.get('selection').off( null, null, this );
     966
     967        wp.Uploader.queue.off( null, null, this );
     968    },
     969
     970    /**
     971     * Reset the library to its initial state.
     972     *
     973     * @since 3.5.0
     974     */
     975    reset: function() {
     976        this.get('selection').reset();
     977        this.resetDisplays();
     978        this.refreshContent();
     979    },
     980
     981    /**
     982     * Reset the attachment display settings defaults to the site options.
     983     *
     984     * If site options don't define them, fall back to a persistent user setting.
     985     *
     986     * @since 3.5.0
     987     */
     988    resetDisplays: function() {
     989        var defaultProps = wp.media.view.settings.defaultProps;
     990        this._displays = [];
     991        this._defaultDisplaySettings = {
     992            align: getUserSetting( 'align', defaultProps.align ) || 'none',
     993            size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
     994            link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
     995        };
     996    },
     997
     998    /**
     999     * Create a model to represent display settings (alignment, etc.) for an attachment.
     1000     *
     1001     * @since 3.5.0
     1002     *
     1003     * @param {wp.media.model.Attachment} attachment
     1004     * @returns {Backbone.Model}
     1005     */
     1006    display: function( attachment ) {
     1007        var displays = this._displays;
     1008
     1009        if ( ! displays[ attachment.cid ] ) {
     1010            displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
     1011        }
     1012        return displays[ attachment.cid ];
     1013    },
     1014
     1015    /**
     1016     * Given an attachment, create attachment display settings properties.
     1017     *
     1018     * @since 3.6.0
     1019     *
     1020     * @param {wp.media.model.Attachment} attachment
     1021     * @returns {Object}
     1022     */
     1023    defaultDisplaySettings: function( attachment ) {
     1024        var settings = _.clone( this._defaultDisplaySettings );
     1025
     1026        if ( settings.canEmbed = this.canEmbed( attachment ) ) {
     1027            settings.link = 'embed';
     1028        } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
     1029            settings.link = 'file';
     1030        }
     1031
     1032        return settings;
     1033    },
     1034
     1035    /**
     1036     * Whether an attachment is image.
     1037     *
     1038     * @since 4.4.1
     1039     *
     1040     * @param {wp.media.model.Attachment} attachment
     1041     * @returns {Boolean}
     1042     */
     1043    isImageAttachment: function( attachment ) {
     1044        // If uploading, we know the filename but not the mime type.
     1045        if ( attachment.get('uploading') ) {
     1046            return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
     1047        }
     1048
     1049        return attachment.get('type') === 'image';
     1050    },
     1051
     1052    /**
     1053     * Whether an attachment can be embedded (audio or video).
     1054     *
     1055     * @since 3.6.0
     1056     *
     1057     * @param {wp.media.model.Attachment} attachment
     1058     * @returns {Boolean}
     1059     */
     1060    canEmbed: function( attachment ) {
     1061        // If uploading, we know the filename but not the mime type.
     1062        if ( ! attachment.get('uploading') ) {
     1063            var type = attachment.get('type');
     1064            if ( type !== 'audio' && type !== 'video' ) {
     1065                return false;
     1066            }
     1067        }
     1068
     1069        return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
     1070    },
     1071
     1072
     1073    /**
     1074     * If the state is active, no items are selected, and the current
     1075     * content mode is not an option in the state's router (provided
     1076     * the state has a router), reset the content mode to the default.
     1077     *
     1078     * @since 3.5.0
     1079     */
     1080    refreshContent: function() {
     1081        var selection = this.get('selection'),
     1082            frame = this.frame,
     1083            router = frame.router.get(),
     1084            mode = frame.content.mode();
     1085
     1086        if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
     1087            this.frame.content.render( this.get('content') );
     1088        }
     1089    },
     1090
     1091    /**
     1092     * Callback handler when an attachment is uploaded.
     1093     *
     1094     * Switch to the Media Library if uploaded from the 'Upload Files' tab.
     1095     *
     1096     * Adds any uploading attachments to the selection.
     1097     *
     1098     * If the state only supports one attachment to be selected and multiple
     1099     * attachments are uploaded, the last attachment in the upload queue will
     1100     * be selected.
     1101     *
     1102     * @since 3.5.0
     1103     *
     1104     * @param {wp.media.model.Attachment} attachment
     1105     */
     1106    uploading: function( attachment ) {
     1107        var content = this.frame.content;
     1108
     1109        if ( 'upload' === content.mode() ) {
     1110            this.frame.content.mode('browse');
     1111        }
     1112
     1113        if ( this.get( 'autoSelect' ) ) {
     1114            this.get('selection').add( attachment );
     1115            this.frame.trigger( 'library:selection:add' );
     1116        }
     1117    },
     1118
     1119    /**
     1120     * Persist the mode of the content region as a user setting.
     1121     *
     1122     * @since 3.5.0
     1123     */
     1124    saveContentMode: function() {
     1125        if ( 'browse' !== this.get('router') ) {
     1126            return;
     1127        }
     1128
     1129        var mode = this.frame.content.mode(),
     1130            view = this.frame.router.get();
     1131
     1132        if ( view && view.get( mode ) ) {
     1133            setUserSetting( 'libraryContent', mode );
     1134        }
     1135    }
     1136});
     1137
     1138// Make selectionSync available on any Media Library state.
     1139_.extend( Library.prototype, wp.media.selectionSync );
     1140
     1141module.exports = Library;
     1142
     1143
     1144/***/ }),
     1145/* 32 */
     1146/***/ (function(module, exports) {
     1147
     1148/**
     1149 * wp.media.controller.ImageDetails
     1150 *
     1151 * A state for editing the attachment display settings of an image that's been
     1152 * inserted into the editor.
     1153 *
     1154 * @class
     1155 * @augments wp.media.controller.State
     1156 * @augments Backbone.Model
     1157 *
     1158 * @param {object}                    [attributes]                       The attributes hash passed to the state.
     1159 * @param {string}                    [attributes.id=image-details]      Unique identifier.
     1160 * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
     1161 * @param {wp.media.model.Attachment} attributes.image                   The image's model.
     1162 * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
     1163 * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
     1164 * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
     1165 * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
     1166 * @param {boolean}                   [attributes.editing=false]         Unused.
     1167 * @param {int}                       [attributes.priority=60]           Unused.
     1168 *
     1169 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
     1170 *       however this may not do anything.
     1171 */
     1172var State = wp.media.controller.State,
     1173    Library = wp.media.controller.Library,
     1174    l10n = wp.media.view.l10n,
     1175    ImageDetails;
     1176
     1177ImageDetails = State.extend({
     1178    defaults: _.defaults({
     1179        id:       'image-details',
     1180        title:    l10n.imageDetailsTitle,
     1181        content:  'image-details',
     1182        menu:     false,
     1183        router:   false,
     1184        toolbar:  'image-details',
     1185        editing:  false,
     1186        priority: 60
     1187    }, Library.prototype.defaults ),
     1188
     1189    /**
     1190     * @since 3.9.0
     1191     *
     1192     * @param options Attributes
     1193     */
     1194    initialize: function( options ) {
     1195        this.image = options.image;
     1196        State.prototype.initialize.apply( this, arguments );
     1197    },
     1198
     1199    /**
     1200     * @since 3.9.0
     1201     */
     1202    activate: function() {
     1203        this.frame.modal.$el.addClass('image-details');
     1204    }
     1205});
     1206
     1207module.exports = ImageDetails;
     1208
     1209
     1210/***/ }),
     1211/* 33 */
     1212/***/ (function(module, exports) {
     1213
     1214/**
     1215 * wp.media.controller.GalleryEdit
     1216 *
     1217 * A state for editing a gallery's images and settings.
    61218 *
    71219 * @class
     
    101222 * @augments Backbone.Model
    111223 *
     1224 * @param {object}                     [attributes]                       The attributes hash passed to the state.
     1225 * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
     1226 * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
     1227 * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
     1228 *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
     1229 * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
     1230 * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
     1231 * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1232 * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
     1233 * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
     1234 * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
     1235 * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     1236 * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
     1237 * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
     1238 * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
     1239 * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
     1240 * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
     1241 * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
     1242 *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
     1243 * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
     1244 *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     1245 */
     1246var Library = wp.media.controller.Library,
     1247    l10n = wp.media.view.l10n,
     1248    GalleryEdit;
     1249
     1250GalleryEdit = Library.extend({
     1251    defaults: {
     1252        id:               'gallery-edit',
     1253        title:            l10n.editGalleryTitle,
     1254        multiple:         false,
     1255        searchable:       false,
     1256        sortable:         true,
     1257        date:             false,
     1258        display:          false,
     1259        content:          'browse',
     1260        toolbar:          'gallery-edit',
     1261        describe:         true,
     1262        displaySettings:  true,
     1263        dragInfo:         true,
     1264        idealColumnWidth: 170,
     1265        editing:          false,
     1266        priority:         60,
     1267        syncSelection:    false
     1268    },
     1269
     1270    /**
     1271     * @since 3.5.0
     1272     */
     1273    initialize: function() {
     1274        // If we haven't been provided a `library`, create a `Selection`.
     1275        if ( ! this.get('library') ) {
     1276            this.set( 'library', new wp.media.model.Selection() );
     1277        }
     1278
     1279        // The single `Attachment` view to be used in the `Attachments` view.
     1280        if ( ! this.get('AttachmentView') ) {
     1281            this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     1282        }
     1283
     1284        Library.prototype.initialize.apply( this, arguments );
     1285    },
     1286
     1287    /**
     1288     * @since 3.5.0
     1289     */
     1290    activate: function() {
     1291        var library = this.get('library');
     1292
     1293        // Limit the library to images only.
     1294        library.props.set( 'type', 'image' );
     1295
     1296        // Watch for uploaded attachments.
     1297        this.get('library').observe( wp.Uploader.queue );
     1298
     1299        this.frame.on( 'content:render:browse', this.gallerySettings, this );
     1300
     1301        Library.prototype.activate.apply( this, arguments );
     1302    },
     1303
     1304    /**
     1305     * @since 3.5.0
     1306     */
     1307    deactivate: function() {
     1308        // Stop watching for uploaded attachments.
     1309        this.get('library').unobserve( wp.Uploader.queue );
     1310
     1311        this.frame.off( 'content:render:browse', this.gallerySettings, this );
     1312
     1313        Library.prototype.deactivate.apply( this, arguments );
     1314    },
     1315
     1316    /**
     1317     * @since 3.5.0
     1318     *
     1319     * @param browser
     1320     */
     1321    gallerySettings: function( browser ) {
     1322        if ( ! this.get('displaySettings') ) {
     1323            return;
     1324        }
     1325
     1326        var library = this.get('library');
     1327
     1328        if ( ! library || ! browser ) {
     1329            return;
     1330        }
     1331
     1332        library.gallery = library.gallery || new Backbone.Model();
     1333
     1334        browser.sidebar.set({
     1335            gallery: new wp.media.view.Settings.Gallery({
     1336                controller: this,
     1337                model:      library.gallery,
     1338                priority:   40
     1339            })
     1340        });
     1341
     1342        browser.toolbar.set( 'reverse', {
     1343            text:     l10n.reverseOrder,
     1344            priority: 80,
     1345
     1346            click: function() {
     1347                library.reset( library.toArray().reverse() );
     1348            }
     1349        });
     1350    }
     1351});
     1352
     1353module.exports = GalleryEdit;
     1354
     1355
     1356/***/ }),
     1357/* 34 */
     1358/***/ (function(module, exports) {
     1359
     1360/**
     1361 * wp.media.controller.GalleryAdd
     1362 *
     1363 * A state for selecting more images to add to a gallery.
     1364 *
     1365 * @class
     1366 * @augments wp.media.controller.Library
     1367 * @augments wp.media.controller.State
     1368 * @augments Backbone.Model
     1369 *
    121370 * @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.
     1371 * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
     1372 * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
    151373 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    161374 * @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.
     1375 *                                                                          If one is not supplied, a collection of all images will be created.
    181376 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    191377 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     
    301388 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    311389 *                                                                          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').
    341390 */
    351391var Selection = wp.media.model.Selection,
    361392    Library = wp.media.controller.Library,
    37     CollectionAdd;
    38 
    39 CollectionAdd = Library.extend({
    40     defaults: _.defaults( {
    41         // Selection defaults. @see media.model.Selection
     1393    l10n = wp.media.view.l10n,
     1394    GalleryAdd;
     1395
     1396GalleryAdd = Library.extend({
     1397    defaults: _.defaults({
     1398        id:            'gallery-library',
     1399        title:         l10n.addToGalleryTitle,
    421400        multiple:      'add',
    43         // Attachments browser defaults. @see media.view.AttachmentsBrowser
    441401        filterable:    'uploaded',
    45 
     1402        menu:          'gallery',
     1403        toolbar:       'gallery-add',
    461404        priority:      100,
    471405        syncSelection: false
     
    491407
    501408    /**
    51      * @since 3.9.0
     1409     * @since 3.5.0
    521410     */
    531411    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`.
     1412        // If a library wasn't supplied, create a library of images.
    651413        if ( ! this.get('library') ) {
    66             this.set( 'library', wp.media.query({ type: this.get('type') }) );
    67         }
     1414            this.set( 'library', wp.media.query({ type: 'image' }) );
     1415        }
     1416
    681417        Library.prototype.initialize.apply( this, arguments );
    691418    },
    701419
    711420    /**
    72      * @since 3.9.0
     1421     * @since 3.5.0
    731422     */
    741423    activate: function() {
    751424        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 );
     1425            edit    = this.frame.state('gallery-edit').get('library');
     1426
     1427        if ( this.editLibrary && this.editLibrary !== edit ) {
     1428            library.unobserve( this.editLibrary );
    811429        }
    821430
     
    921440        library.reset( library.mirroring.models, { silent: true });
    931441        library.observe( edit );
    94         this.set('editLibrary', edit);
     1442        this.editLibrary = edit;
    951443
    961444        Library.prototype.activate.apply( this, arguments );
     
    981446});
    991447
    100 module.exports = CollectionAdd;
    101 
    102 },{}],2:[function(require,module,exports){
     1448module.exports = GalleryAdd;
     1449
     1450
     1451/***/ }),
     1452/* 35 */
     1453/***/ (function(module, exports) {
     1454
    1031455/**
    1041456 * wp.media.controller.CollectionEdit
     
    2621614module.exports = CollectionEdit;
    2631615
    264 },{}],3:[function(require,module,exports){
     1616
     1617/***/ }),
     1618/* 36 */
     1619/***/ (function(module, exports) {
     1620
    2651621/**
    266  * wp.media.controller.Cropper
    267  *
    268  * A state for cropping an image.
     1622 * wp.media.controller.CollectionAdd
     1623 *
     1624 * A state for adding attachments to a collection (e.g. video playlist).
    2691625 *
    2701626 * @class
     1627 * @augments wp.media.controller.Library
    2711628 * @augments wp.media.controller.State
    2721629 * @augments Backbone.Model
     1630 *
     1631 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     1632 * @param {string}                     [attributes.id=library]      Unique identifier.
     1633 * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
     1634 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     1635 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     1636 *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
     1637 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     1638 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     1639 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     1640 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     1641 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     1642 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     1643 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     1644 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     1645 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1646 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     1647 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     1648 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     1649 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     1650 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     1651 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     1652 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
    2731653 */
    274 var l10n = wp.media.view.l10n,
    275     Cropper;
    276 
    277 Cropper = wp.media.controller.State.extend({
    278     defaults: {
    279         id:          'cropper',
    280         title:       l10n.cropImage,
    281         // Region mode defaults.
    282         toolbar:     'crop',
    283         content:     'crop',
    284         router:      false,
    285 
    286         canSkipCrop: false
    287     },
    288 
     1654var Selection = wp.media.model.Selection,
     1655    Library = wp.media.controller.Library,
     1656    CollectionAdd;
     1657
     1658CollectionAdd = Library.extend({
     1659    defaults: _.defaults( {
     1660        // Selection defaults. @see media.model.Selection
     1661        multiple:      'add',
     1662        // Attachments browser defaults. @see media.view.AttachmentsBrowser
     1663        filterable:    'uploaded',
     1664
     1665        priority:      100,
     1666        syncSelection: false
     1667    }, Library.prototype.defaults ),
     1668
     1669    /**
     1670     * @since 3.9.0
     1671     */
     1672    initialize: function() {
     1673        var collectionType = this.get('collectionType');
     1674
     1675        if ( 'video' === this.get( 'type' ) ) {
     1676            collectionType = 'video-' + collectionType;
     1677        }
     1678
     1679        this.set( 'id', collectionType + '-library' );
     1680        this.set( 'toolbar', collectionType + '-add' );
     1681        this.set( 'menu', collectionType );
     1682
     1683        // If we haven't been provided a `library`, create a `Selection`.
     1684        if ( ! this.get('library') ) {
     1685            this.set( 'library', wp.media.query({ type: this.get('type') }) );
     1686        }
     1687        Library.prototype.initialize.apply( this, arguments );
     1688    },
     1689
     1690    /**
     1691     * @since 3.9.0
     1692     */
    2891693    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             }
     1694        var library = this.get('library'),
     1695            editLibrary = this.get('editLibrary'),
     1696            edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
     1697
     1698        if ( editLibrary && editLibrary !== edit ) {
     1699            library.unobserve( editLibrary );
     1700        }
     1701
     1702        // Accepts attachments that exist in the original library and
     1703        // that do not exist in gallery's library.
     1704        library.validator = function( attachment ) {
     1705            return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    3471706        };
    3481707
    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         } );
     1708        // Reset the library to ensure that all attachments are re-added
     1709        // to the collection. Do so silently, as calling `observe` will
     1710        // trigger the `reset` event.
     1711        library.reset( library.mirroring.models, { silent: true });
     1712        library.observe( edit );
     1713        this.set('editLibrary', edit);
     1714
     1715        Library.prototype.activate.apply( this, arguments );
    3751716    }
    3761717});
    3771718
    378 module.exports = Cropper;
    379 
    380 },{}],4:[function(require,module,exports){
    381 /**
    382  * wp.media.controller.CustomizeImageCropper
    383  *
    384  * A state for cropping an image.
    385  *
    386  * @class
    387  * @augments wp.media.controller.Cropper
    388  * @augments wp.media.controller.State
    389  * @augments Backbone.Model
    390  */
    391 var Controller = wp.media.controller,
    392     CustomizeImageCropper;
    393 
    394 CustomizeImageCropper = Controller.Cropper.extend({
    395     doCrop: function( attachment ) {
    396         var cropDetails = attachment.get( 'cropDetails' ),
    397             control = this.get( 'control' );
    398 
    399         cropDetails.dst_width  = control.params.width;
    400         cropDetails.dst_height = control.params.height;
    401 
    402         return wp.ajax.post( 'crop-image', {
    403             wp_customize: 'on',
    404             nonce: attachment.get( 'nonces' ).edit,
    405             id: attachment.get( 'id' ),
    406             context: control.id,
    407             cropDetails: cropDetails
    408         } );
    409     }
    410 });
    411 
    412 module.exports = CustomizeImageCropper;
    413 
    414 },{}],5:[function(require,module,exports){
    415 /**
    416  * wp.media.controller.EditImage
    417  *
    418  * A state for editing (cropping, etc.) an image.
    419  *
    420  * @class
    421  * @augments wp.media.controller.State
    422  * @augments Backbone.Model
    423  *
    424  * @param {object}                    attributes                      The attributes hash passed to the state.
    425  * @param {wp.media.model.Attachment} attributes.model                The attachment.
    426  * @param {string}                    [attributes.id=edit-image]      Unique identifier.
    427  * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
    428  * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
    429  * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
    430  * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
    431  * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
    432  */
    433 var l10n = wp.media.view.l10n,
    434     EditImage;
    435 
    436 EditImage = wp.media.controller.State.extend({
    437     defaults: {
    438         id:      'edit-image',
    439         title:   l10n.editImage,
    440         menu:    false,
    441         toolbar: 'edit-image',
    442         content: 'edit-image',
    443         url:     ''
    444     },
    445 
    446     /**
    447      * @since 3.9.0
    448      */
    449     activate: function() {
    450         this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
    451     },
    452 
    453     /**
    454      * @since 3.9.0
    455      */
    456     deactivate: function() {
    457         this.stopListening( this.frame );
    458     },
    459 
    460     /**
    461      * @since 3.9.0
    462      */
    463     toolbar: function() {
    464         var frame = this.frame,
    465             lastState = frame.lastState(),
    466             previous = lastState && lastState.id;
    467 
    468         frame.toolbar.set( new wp.media.view.Toolbar({
    469             controller: frame,
    470             items: {
    471                 back: {
    472                     style: 'primary',
    473                     text:     l10n.back,
    474                     priority: 20,
    475                     click:    function() {
    476                         if ( previous ) {
    477                             frame.setState( previous );
    478                         } else {
    479                             frame.close();
    480                         }
    481                     }
    482                 }
    483             }
    484         }) );
    485     }
    486 });
    487 
    488 module.exports = EditImage;
    489 
    490 },{}],6:[function(require,module,exports){
    491 /**
    492  * wp.media.controller.Embed
    493  *
    494  * A state for embedding media from a URL.
    495  *
    496  * @class
    497  * @augments wp.media.controller.State
    498  * @augments Backbone.Model
    499  *
    500  * @param {object} attributes                         The attributes hash passed to the state.
    501  * @param {string} [attributes.id=embed]              Unique identifier.
    502  * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
    503  * @param {string} [attributes.content=embed]         Initial mode for the content region.
    504  * @param {string} [attributes.menu=default]          Initial mode for the menu region.
    505  * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
    506  * @param {string} [attributes.menu=false]            Initial mode for the menu region.
    507  * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
    508  * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
    509  * @param {string} [attributes.url]                   The embed URL.
    510  * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
    511  */
    512 var l10n = wp.media.view.l10n,
    513     $ = Backbone.$,
    514     Embed;
    515 
    516 Embed = wp.media.controller.State.extend({
    517     defaults: {
    518         id:       'embed',
    519         title:    l10n.insertFromUrlTitle,
    520         content:  'embed',
    521         menu:     'default',
    522         toolbar:  'main-embed',
    523         priority: 120,
    524         type:     'link',
    525         url:      '',
    526         metadata: {}
    527     },
    528 
    529     // The amount of time used when debouncing the scan.
    530     sensitivity: 400,
    531 
    532     initialize: function(options) {
    533         this.metadata = options.metadata;
    534         this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
    535         this.props = new Backbone.Model( this.metadata || { url: '' });
    536         this.props.on( 'change:url', this.debouncedScan, this );
    537         this.props.on( 'change:url', this.refresh, this );
    538         this.on( 'scan', this.scanImage, this );
    539     },
    540 
    541     /**
    542      * Trigger a scan of the embedded URL's content for metadata required to embed.
    543      *
    544      * @fires wp.media.controller.Embed#scan
    545      */
    546     scan: function() {
    547         var scanners,
    548             embed = this,
    549             attributes = {
    550                 type: 'link',
    551                 scanners: []
    552             };
    553 
    554         // Scan is triggered with the list of `attributes` to set on the
    555         // state, useful for the 'type' attribute and 'scanners' attribute,
    556         // an array of promise objects for asynchronous scan operations.
    557         if ( this.props.get('url') ) {
    558             this.trigger( 'scan', attributes );
    559         }
    560 
    561         if ( attributes.scanners.length ) {
    562             scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
    563             scanners.always( function() {
    564                 if ( embed.get('scanners') === scanners ) {
    565                     embed.set( 'loading', false );
    566                 }
    567             });
    568         } else {
    569             attributes.scanners = null;
    570         }
    571 
    572         attributes.loading = !! attributes.scanners;
    573         this.set( attributes );
    574     },
    575     /**
    576      * Try scanning the embed as an image to discover its dimensions.
    577      *
    578      * @param {Object} attributes
    579      */
    580     scanImage: function( attributes ) {
    581         var frame = this.frame,
    582             state = this,
    583             url = this.props.get('url'),
    584             image = new Image(),
    585             deferred = $.Deferred();
    586 
    587         attributes.scanners.push( deferred.promise() );
    588 
    589         // Try to load the image and find its width/height.
    590         image.onload = function() {
    591             deferred.resolve();
    592 
    593             if ( state !== frame.state() || url !== state.props.get('url') ) {
    594                 return;
    595             }
    596 
    597             state.set({
    598                 type: 'image'
    599             });
    600 
    601             state.props.set({
    602                 width:  image.width,
    603                 height: image.height
    604             });
    605         };
    606 
    607         image.onerror = deferred.reject;
    608         image.src = url;
    609     },
    610 
    611     refresh: function() {
    612         this.frame.toolbar.get().refresh();
    613     },
    614 
    615     reset: function() {
    616         this.props.clear().set({ url: '' });
    617 
    618         if ( this.active ) {
    619             this.refresh();
    620         }
    621     }
    622 });
    623 
    624 module.exports = Embed;
    625 
    626 },{}],7:[function(require,module,exports){
     1719module.exports = CollectionAdd;
     1720
     1721
     1722/***/ }),
     1723/* 37 */
     1724/***/ (function(module, exports) {
     1725
    6271726/**
    6281727 * wp.media.controller.FeaturedImage
     
    7461845module.exports = FeaturedImage;
    7471846
    748 },{}],8:[function(require,module,exports){
    749 /**
    750  * wp.media.controller.GalleryAdd
    751  *
    752  * A state for selecting more images to add to a gallery.
    753  *
    754  * @class
    755  * @augments wp.media.controller.Library
    756  * @augments wp.media.controller.State
    757  * @augments Backbone.Model
    758  *
    759  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    760  * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
    761  * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
    762  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    763  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    764  *                                                                          If one is not supplied, a collection of all images will be created.
    765  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    766  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    767  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    768  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    769  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    770  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    771  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    772  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    773  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    774  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    775  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    776  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    777  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    778  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    779  */
    780 var Selection = wp.media.model.Selection,
    781     Library = wp.media.controller.Library,
    782     l10n = wp.media.view.l10n,
    783     GalleryAdd;
    784 
    785 GalleryAdd = Library.extend({
    786     defaults: _.defaults({
    787         id:            'gallery-library',
    788         title:         l10n.addToGalleryTitle,
    789         multiple:      'add',
    790         filterable:    'uploaded',
    791         menu:          'gallery',
    792         toolbar:       'gallery-add',
    793         priority:      100,
    794         syncSelection: false
    795     }, Library.prototype.defaults ),
    796 
    797     /**
    798      * @since 3.5.0
    799      */
    800     initialize: function() {
    801         // If a library wasn't supplied, create a library of images.
    802         if ( ! this.get('library') ) {
    803             this.set( 'library', wp.media.query({ type: 'image' }) );
    804         }
    805 
    806         Library.prototype.initialize.apply( this, arguments );
    807     },
    808 
    809     /**
    810      * @since 3.5.0
    811      */
    812     activate: function() {
    813         var library = this.get('library'),
    814             edit    = this.frame.state('gallery-edit').get('library');
    815 
    816         if ( this.editLibrary && this.editLibrary !== edit ) {
    817             library.unobserve( this.editLibrary );
    818         }
    819 
    820         // Accepts attachments that exist in the original library and
    821         // that do not exist in gallery's library.
    822         library.validator = function( attachment ) {
    823             return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    824         };
    825 
    826         // Reset the library to ensure that all attachments are re-added
    827         // to the collection. Do so silently, as calling `observe` will
    828         // trigger the `reset` event.
    829         library.reset( library.mirroring.models, { silent: true });
    830         library.observe( edit );
    831         this.editLibrary = edit;
    832 
    833         Library.prototype.activate.apply( this, arguments );
    834     }
    835 });
    836 
    837 module.exports = GalleryAdd;
    838 
    839 },{}],9:[function(require,module,exports){
    840 /**
    841  * wp.media.controller.GalleryEdit
    842  *
    843  * A state for editing a gallery's images and settings.
    844  *
    845  * @class
    846  * @augments wp.media.controller.Library
    847  * @augments wp.media.controller.State
    848  * @augments Backbone.Model
    849  *
    850  * @param {object}                     [attributes]                       The attributes hash passed to the state.
    851  * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
    852  * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
    853  * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
    854  *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
    855  * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
    856  * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
    857  * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    858  * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
    859  * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
    860  * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
    861  * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    862  * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
    863  * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
    864  * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
    865  * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
    866  * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
    867  * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
    868  *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
    869  * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
    870  *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    871  */
    872 var Library = wp.media.controller.Library,
    873     l10n = wp.media.view.l10n,
    874     GalleryEdit;
    875 
    876 GalleryEdit = Library.extend({
    877     defaults: {
    878         id:               'gallery-edit',
    879         title:            l10n.editGalleryTitle,
    880         multiple:         false,
    881         searchable:       false,
    882         sortable:         true,
    883         date:             false,
    884         display:          false,
    885         content:          'browse',
    886         toolbar:          'gallery-edit',
    887         describe:         true,
    888         displaySettings:  true,
    889         dragInfo:         true,
    890         idealColumnWidth: 170,
    891         editing:          false,
    892         priority:         60,
    893         syncSelection:    false
    894     },
    895 
    896     /**
    897      * @since 3.5.0
    898      */
    899     initialize: function() {
    900         // If we haven't been provided a `library`, create a `Selection`.
    901         if ( ! this.get('library') ) {
    902             this.set( 'library', new wp.media.model.Selection() );
    903         }
    904 
    905         // The single `Attachment` view to be used in the `Attachments` view.
    906         if ( ! this.get('AttachmentView') ) {
    907             this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    908         }
    909 
    910         Library.prototype.initialize.apply( this, arguments );
    911     },
    912 
    913     /**
    914      * @since 3.5.0
    915      */
    916     activate: function() {
    917         var library = this.get('library');
    918 
    919         // Limit the library to images only.
    920         library.props.set( 'type', 'image' );
    921 
    922         // Watch for uploaded attachments.
    923         this.get('library').observe( wp.Uploader.queue );
    924 
    925         this.frame.on( 'content:render:browse', this.gallerySettings, this );
    926 
    927         Library.prototype.activate.apply( this, arguments );
    928     },
    929 
    930     /**
    931      * @since 3.5.0
    932      */
    933     deactivate: function() {
    934         // Stop watching for uploaded attachments.
    935         this.get('library').unobserve( wp.Uploader.queue );
    936 
    937         this.frame.off( 'content:render:browse', this.gallerySettings, this );
    938 
    939         Library.prototype.deactivate.apply( this, arguments );
    940     },
    941 
    942     /**
    943      * @since 3.5.0
    944      *
    945      * @param browser
    946      */
    947     gallerySettings: function( browser ) {
    948         if ( ! this.get('displaySettings') ) {
    949             return;
    950         }
    951 
    952         var library = this.get('library');
    953 
    954         if ( ! library || ! browser ) {
    955             return;
    956         }
    957 
    958         library.gallery = library.gallery || new Backbone.Model();
    959 
    960         browser.sidebar.set({
    961             gallery: new wp.media.view.Settings.Gallery({
    962                 controller: this,
    963                 model:      library.gallery,
    964                 priority:   40
    965             })
    966         });
    967 
    968         browser.toolbar.set( 'reverse', {
    969             text:     l10n.reverseOrder,
    970             priority: 80,
    971 
    972             click: function() {
    973                 library.reset( library.toArray().reverse() );
    974             }
    975         });
    976     }
    977 });
    978 
    979 module.exports = GalleryEdit;
    980 
    981 },{}],10:[function(require,module,exports){
    982 /**
    983  * wp.media.controller.ImageDetails
    984  *
    985  * A state for editing the attachment display settings of an image that's been
    986  * inserted into the editor.
    987  *
    988  * @class
    989  * @augments wp.media.controller.State
    990  * @augments Backbone.Model
    991  *
    992  * @param {object}                    [attributes]                       The attributes hash passed to the state.
    993  * @param {string}                    [attributes.id=image-details]      Unique identifier.
    994  * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
    995  * @param {wp.media.model.Attachment} attributes.image                   The image's model.
    996  * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
    997  * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
    998  * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
    999  * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
    1000  * @param {boolean}                   [attributes.editing=false]         Unused.
    1001  * @param {int}                       [attributes.priority=60]           Unused.
    1002  *
    1003  * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
    1004  *       however this may not do anything.
    1005  */
    1006 var State = wp.media.controller.State,
    1007     Library = wp.media.controller.Library,
    1008     l10n = wp.media.view.l10n,
    1009     ImageDetails;
    1010 
    1011 ImageDetails = State.extend({
    1012     defaults: _.defaults({
    1013         id:       'image-details',
    1014         title:    l10n.imageDetailsTitle,
    1015         content:  'image-details',
    1016         menu:     false,
    1017         router:   false,
    1018         toolbar:  'image-details',
    1019         editing:  false,
    1020         priority: 60
    1021     }, Library.prototype.defaults ),
    1022 
    1023     /**
    1024      * @since 3.9.0
    1025      *
    1026      * @param options Attributes
    1027      */
    1028     initialize: function( options ) {
    1029         this.image = options.image;
    1030         State.prototype.initialize.apply( this, arguments );
    1031     },
    1032 
    1033     /**
    1034      * @since 3.9.0
    1035      */
    1036     activate: function() {
    1037         this.frame.modal.$el.addClass('image-details');
    1038     }
    1039 });
    1040 
    1041 module.exports = ImageDetails;
    1042 
    1043 },{}],11:[function(require,module,exports){
    1044 /**
    1045  * wp.media.controller.Library
    1046  *
    1047  * A state for choosing an attachment or group of attachments from the media library.
    1048  *
    1049  * @class
    1050  * @augments wp.media.controller.State
    1051  * @augments Backbone.Model
    1052  * @mixes media.selectionSync
    1053  *
    1054  * @param {object}                          [attributes]                         The attributes hash passed to the state.
    1055  * @param {string}                          [attributes.id=library]              Unique identifier.
    1056  * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
    1057  * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
    1058  *                                                                               If one is not supplied, a collection of all attachments will be created.
    1059  * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
    1060  *                                                                               If the 'selection' attribute is a plain JS object,
    1061  *                                                                               a Selection will be created using its values as the selection instance's `props` model.
    1062  *                                                                               Otherwise, it will copy the library's `props` model.
    1063  * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
    1064  * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
    1065  *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
    1066  * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
    1067  * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
    1068  * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
    1069  * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
    1070  * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
    1071  *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
    1072  * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1073  * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1074  * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1075  * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1076  * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    1077  */
    1078 var l10n = wp.media.view.l10n,
    1079     getUserSetting = window.getUserSetting,
    1080     setUserSetting = window.setUserSetting,
    1081     Library;
    1082 
    1083 Library = wp.media.controller.State.extend({
    1084     defaults: {
    1085         id:                 'library',
    1086         title:              l10n.mediaLibraryTitle,
    1087         multiple:           false,
    1088         content:            'upload',
    1089         menu:               'default',
    1090         router:             'browse',
    1091         toolbar:            'select',
    1092         searchable:         true,
    1093         filterable:         false,
    1094         sortable:           true,
    1095         autoSelect:         true,
    1096         describe:           false,
    1097         contentUserSetting: true,
    1098         syncSelection:      true
    1099     },
    1100 
    1101     /**
    1102      * If a library isn't provided, query all media items.
    1103      * If a selection instance isn't provided, create one.
    1104      *
    1105      * @since 3.5.0
    1106      */
    1107     initialize: function() {
    1108         var selection = this.get('selection'),
    1109             props;
    1110 
    1111         if ( ! this.get('library') ) {
    1112             this.set( 'library', wp.media.query() );
    1113         }
    1114 
    1115         if ( ! ( selection instanceof wp.media.model.Selection ) ) {
    1116             props = selection;
    1117 
    1118             if ( ! props ) {
    1119                 props = this.get('library').props.toJSON();
    1120                 props = _.omit( props, 'orderby', 'query' );
    1121             }
    1122 
    1123             this.set( 'selection', new wp.media.model.Selection( null, {
    1124                 multiple: this.get('multiple'),
    1125                 props: props
    1126             }) );
    1127         }
    1128 
    1129         this.resetDisplays();
    1130     },
    1131 
    1132     /**
    1133      * @since 3.5.0
    1134      */
    1135     activate: function() {
    1136         this.syncSelection();
    1137 
    1138         wp.Uploader.queue.on( 'add', this.uploading, this );
    1139 
    1140         this.get('selection').on( 'add remove reset', this.refreshContent, this );
    1141 
    1142         if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
    1143             this.frame.on( 'content:activate', this.saveContentMode, this );
    1144             this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
    1145         }
    1146     },
    1147 
    1148     /**
    1149      * @since 3.5.0
    1150      */
    1151     deactivate: function() {
    1152         this.recordSelection();
    1153 
    1154         this.frame.off( 'content:activate', this.saveContentMode, this );
    1155 
    1156         // Unbind all event handlers that use this state as the context
    1157         // from the selection.
    1158         this.get('selection').off( null, null, this );
    1159 
    1160         wp.Uploader.queue.off( null, null, this );
    1161     },
    1162 
    1163     /**
    1164      * Reset the library to its initial state.
    1165      *
    1166      * @since 3.5.0
    1167      */
    1168     reset: function() {
    1169         this.get('selection').reset();
    1170         this.resetDisplays();
    1171         this.refreshContent();
    1172     },
    1173 
    1174     /**
    1175      * Reset the attachment display settings defaults to the site options.
    1176      *
    1177      * If site options don't define them, fall back to a persistent user setting.
    1178      *
    1179      * @since 3.5.0
    1180      */
    1181     resetDisplays: function() {
    1182         var defaultProps = wp.media.view.settings.defaultProps;
    1183         this._displays = [];
    1184         this._defaultDisplaySettings = {
    1185             align: getUserSetting( 'align', defaultProps.align ) || 'none',
    1186             size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
    1187             link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
    1188         };
    1189     },
    1190 
    1191     /**
    1192      * Create a model to represent display settings (alignment, etc.) for an attachment.
    1193      *
    1194      * @since 3.5.0
    1195      *
    1196      * @param {wp.media.model.Attachment} attachment
    1197      * @returns {Backbone.Model}
    1198      */
    1199     display: function( attachment ) {
    1200         var displays = this._displays;
    1201 
    1202         if ( ! displays[ attachment.cid ] ) {
    1203             displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
    1204         }
    1205         return displays[ attachment.cid ];
    1206     },
    1207 
    1208     /**
    1209      * Given an attachment, create attachment display settings properties.
    1210      *
    1211      * @since 3.6.0
    1212      *
    1213      * @param {wp.media.model.Attachment} attachment
    1214      * @returns {Object}
    1215      */
    1216     defaultDisplaySettings: function( attachment ) {
    1217         var settings = _.clone( this._defaultDisplaySettings );
    1218 
    1219         if ( settings.canEmbed = this.canEmbed( attachment ) ) {
    1220             settings.link = 'embed';
    1221         } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
    1222             settings.link = 'file';
    1223         }
    1224 
    1225         return settings;
    1226     },
    1227 
    1228     /**
    1229      * Whether an attachment is image.
    1230      *
    1231      * @since 4.4.1
    1232      *
    1233      * @param {wp.media.model.Attachment} attachment
    1234      * @returns {Boolean}
    1235      */
    1236     isImageAttachment: function( attachment ) {
    1237         // If uploading, we know the filename but not the mime type.
    1238         if ( attachment.get('uploading') ) {
    1239             return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
    1240         }
    1241 
    1242         return attachment.get('type') === 'image';
    1243     },
    1244 
    1245     /**
    1246      * Whether an attachment can be embedded (audio or video).
    1247      *
    1248      * @since 3.6.0
    1249      *
    1250      * @param {wp.media.model.Attachment} attachment
    1251      * @returns {Boolean}
    1252      */
    1253     canEmbed: function( attachment ) {
    1254         // If uploading, we know the filename but not the mime type.
    1255         if ( ! attachment.get('uploading') ) {
    1256             var type = attachment.get('type');
    1257             if ( type !== 'audio' && type !== 'video' ) {
    1258                 return false;
    1259             }
    1260         }
    1261 
    1262         return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
    1263     },
    1264 
    1265 
    1266     /**
    1267      * If the state is active, no items are selected, and the current
    1268      * content mode is not an option in the state's router (provided
    1269      * the state has a router), reset the content mode to the default.
    1270      *
    1271      * @since 3.5.0
    1272      */
    1273     refreshContent: function() {
    1274         var selection = this.get('selection'),
    1275             frame = this.frame,
    1276             router = frame.router.get(),
    1277             mode = frame.content.mode();
    1278 
    1279         if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
    1280             this.frame.content.render( this.get('content') );
    1281         }
    1282     },
    1283 
    1284     /**
    1285      * Callback handler when an attachment is uploaded.
    1286      *
    1287      * Switch to the Media Library if uploaded from the 'Upload Files' tab.
    1288      *
    1289      * Adds any uploading attachments to the selection.
    1290      *
    1291      * If the state only supports one attachment to be selected and multiple
    1292      * attachments are uploaded, the last attachment in the upload queue will
    1293      * be selected.
    1294      *
    1295      * @since 3.5.0
    1296      *
    1297      * @param {wp.media.model.Attachment} attachment
    1298      */
    1299     uploading: function( attachment ) {
    1300         var content = this.frame.content;
    1301 
    1302         if ( 'upload' === content.mode() ) {
    1303             this.frame.content.mode('browse');
    1304         }
    1305 
    1306         if ( this.get( 'autoSelect' ) ) {
    1307             this.get('selection').add( attachment );
    1308             this.frame.trigger( 'library:selection:add' );
    1309         }
    1310     },
    1311 
    1312     /**
    1313      * Persist the mode of the content region as a user setting.
    1314      *
    1315      * @since 3.5.0
    1316      */
    1317     saveContentMode: function() {
    1318         if ( 'browse' !== this.get('router') ) {
    1319             return;
    1320         }
    1321 
    1322         var mode = this.frame.content.mode(),
    1323             view = this.frame.router.get();
    1324 
    1325         if ( view && view.get( mode ) ) {
    1326             setUserSetting( 'libraryContent', mode );
    1327         }
    1328     }
    1329 });
    1330 
    1331 // Make selectionSync available on any Media Library state.
    1332 _.extend( Library.prototype, wp.media.selectionSync );
    1333 
    1334 module.exports = Library;
    1335 
    1336 },{}],12:[function(require,module,exports){
    1337 /**
    1338  * wp.media.controller.MediaLibrary
    1339  *
    1340  * @class
    1341  * @augments wp.media.controller.Library
    1342  * @augments wp.media.controller.State
    1343  * @augments Backbone.Model
    1344  */
    1345 var Library = wp.media.controller.Library,
    1346     MediaLibrary;
    1347 
    1348 MediaLibrary = Library.extend({
    1349     defaults: _.defaults({
    1350         // Attachments browser defaults. @see media.view.AttachmentsBrowser
    1351         filterable:      'uploaded',
    1352 
    1353         displaySettings: false,
    1354         priority:        80,
    1355         syncSelection:   false
    1356     }, Library.prototype.defaults ),
    1357 
    1358     /**
    1359      * @since 3.9.0
    1360      *
    1361      * @param options
    1362      */
    1363     initialize: function( options ) {
    1364         this.media = options.media;
    1365         this.type = options.type;
    1366         this.set( 'library', wp.media.query({ type: this.type }) );
    1367 
    1368         Library.prototype.initialize.apply( this, arguments );
    1369     },
    1370 
    1371     /**
    1372      * @since 3.9.0
    1373      */
    1374     activate: function() {
    1375         // @todo this should use this.frame.
    1376         if ( wp.media.frame.lastMime ) {
    1377             this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
    1378             delete wp.media.frame.lastMime;
    1379         }
    1380         Library.prototype.activate.apply( this, arguments );
    1381     }
    1382 });
    1383 
    1384 module.exports = MediaLibrary;
    1385 
    1386 },{}],13:[function(require,module,exports){
    1387 /**
    1388  * wp.media.controller.Region
    1389  *
    1390  * A region is a persistent application layout area.
    1391  *
    1392  * A region assumes one mode at any time, and can be switched to another.
    1393  *
    1394  * When mode changes, events are triggered on the region's parent view.
    1395  * The parent view will listen to specific events and fill the region with an
    1396  * appropriate view depending on mode. For example, a frame listens for the
    1397  * 'browse' mode t be activated on the 'content' view and then fills the region
    1398  * with an AttachmentsBrowser view.
    1399  *
    1400  * @class
    1401  *
    1402  * @param {object}        options          Options hash for the region.
    1403  * @param {string}        options.id       Unique identifier for the region.
    1404  * @param {Backbone.View} options.view     A parent view the region exists within.
    1405  * @param {string}        options.selector jQuery selector for the region within the parent view.
    1406  */
    1407 var Region = function( options ) {
    1408     _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
    1409 };
    1410 
    1411 // Use Backbone's self-propagating `extend` inheritance method.
    1412 Region.extend = Backbone.Model.extend;
    1413 
    1414 _.extend( Region.prototype, {
    1415     /**
    1416      * Activate a mode.
    1417      *
    1418      * @since 3.5.0
    1419      *
    1420      * @param {string} mode
    1421      *
    1422      * @fires this.view#{this.id}:activate:{this._mode}
    1423      * @fires this.view#{this.id}:activate
    1424      * @fires this.view#{this.id}:deactivate:{this._mode}
    1425      * @fires this.view#{this.id}:deactivate
    1426      *
    1427      * @returns {wp.media.controller.Region} Returns itself to allow chaining.
    1428      */
    1429     mode: function( mode ) {
    1430         if ( ! mode ) {
    1431             return this._mode;
    1432         }
    1433         // Bail if we're trying to change to the current mode.
    1434         if ( mode === this._mode ) {
    1435             return this;
    1436         }
    1437 
    1438         /**
    1439          * Region mode deactivation event.
    1440          *
    1441          * @event this.view#{this.id}:deactivate:{this._mode}
    1442          * @event this.view#{this.id}:deactivate
    1443          */
    1444         this.trigger('deactivate');
    1445 
    1446         this._mode = mode;
    1447         this.render( mode );
    1448 
    1449         /**
    1450          * Region mode activation event.
    1451          *
    1452          * @event this.view#{this.id}:activate:{this._mode}
    1453          * @event this.view#{this.id}:activate
    1454          */
    1455         this.trigger('activate');
    1456         return this;
    1457     },
    1458     /**
    1459      * Render a mode.
    1460      *
    1461      * @since 3.5.0
    1462      *
    1463      * @param {string} mode
    1464      *
    1465      * @fires this.view#{this.id}:create:{this._mode}
    1466      * @fires this.view#{this.id}:create
    1467      * @fires this.view#{this.id}:render:{this._mode}
    1468      * @fires this.view#{this.id}:render
    1469      *
    1470      * @returns {wp.media.controller.Region} Returns itself to allow chaining
    1471      */
    1472     render: function( mode ) {
    1473         // If the mode isn't active, activate it.
    1474         if ( mode && mode !== this._mode ) {
    1475             return this.mode( mode );
    1476         }
    1477 
    1478         var set = { view: null },
    1479             view;
    1480 
    1481         /**
    1482          * Create region view event.
    1483          *
    1484          * Region view creation takes place in an event callback on the frame.
    1485          *
    1486          * @event this.view#{this.id}:create:{this._mode}
    1487          * @event this.view#{this.id}:create
    1488          */
    1489         this.trigger( 'create', set );
    1490         view = set.view;
    1491 
    1492         /**
    1493          * Render region view event.
    1494          *
    1495          * Region view creation takes place in an event callback on the frame.
    1496          *
    1497          * @event this.view#{this.id}:create:{this._mode}
    1498          * @event this.view#{this.id}:create
    1499          */
    1500         this.trigger( 'render', view );
    1501         if ( view ) {
    1502             this.set( view );
    1503         }
    1504         return this;
    1505     },
    1506 
    1507     /**
    1508      * Get the region's view.
    1509      *
    1510      * @since 3.5.0
    1511      *
    1512      * @returns {wp.media.View}
    1513      */
    1514     get: function() {
    1515         return this.view.views.first( this.selector );
    1516     },
    1517 
    1518     /**
    1519      * Set the region's view as a subview of the frame.
    1520      *
    1521      * @since 3.5.0
    1522      *
    1523      * @param {Array|Object} views
    1524      * @param {Object} [options={}]
    1525      * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
    1526      */
    1527     set: function( views, options ) {
    1528         if ( options ) {
    1529             options.add = false;
    1530         }
    1531         return this.view.views.set( this.selector, views, options );
    1532     },
    1533 
    1534     /**
    1535      * Trigger regional view events on the frame.
    1536      *
    1537      * @since 3.5.0
    1538      *
    1539      * @param {string} event
    1540      * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
    1541      */
    1542     trigger: function( event ) {
    1543         var base, args;
    1544 
    1545         if ( ! this._mode ) {
    1546             return;
    1547         }
    1548 
    1549         args = _.toArray( arguments );
    1550         base = this.id + ':' + event;
    1551 
    1552         // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
    1553         args[0] = base + ':' + this._mode;
    1554         this.view.trigger.apply( this.view, args );
    1555 
    1556         // Trigger `{this.id}:{event}` event on the frame.
    1557         args[0] = base;
    1558         this.view.trigger.apply( this.view, args );
    1559         return this;
    1560     }
    1561 });
    1562 
    1563 module.exports = Region;
    1564 
    1565 },{}],14:[function(require,module,exports){
     1847
     1848/***/ }),
     1849/* 38 */
     1850/***/ (function(module, exports) {
     1851
    15661852/**
    15671853 * wp.media.controller.ReplaceImage
     
    16711957module.exports = ReplaceImage;
    16721958
    1673 },{}],15:[function(require,module,exports){
     1959
     1960/***/ }),
     1961/* 39 */
     1962/***/ (function(module, exports) {
     1963
     1964/**
     1965 * wp.media.controller.EditImage
     1966 *
     1967 * A state for editing (cropping, etc.) an image.
     1968 *
     1969 * @class
     1970 * @augments wp.media.controller.State
     1971 * @augments Backbone.Model
     1972 *
     1973 * @param {object}                    attributes                      The attributes hash passed to the state.
     1974 * @param {wp.media.model.Attachment} attributes.model                The attachment.
     1975 * @param {string}                    [attributes.id=edit-image]      Unique identifier.
     1976 * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
     1977 * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
     1978 * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
     1979 * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
     1980 * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
     1981 */
     1982var l10n = wp.media.view.l10n,
     1983    EditImage;
     1984
     1985EditImage = wp.media.controller.State.extend({
     1986    defaults: {
     1987        id:      'edit-image',
     1988        title:   l10n.editImage,
     1989        menu:    false,
     1990        toolbar: 'edit-image',
     1991        content: 'edit-image',
     1992        url:     ''
     1993    },
     1994
     1995    /**
     1996     * @since 3.9.0
     1997     */
     1998    activate: function() {
     1999        this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
     2000    },
     2001
     2002    /**
     2003     * @since 3.9.0
     2004     */
     2005    deactivate: function() {
     2006        this.stopListening( this.frame );
     2007    },
     2008
     2009    /**
     2010     * @since 3.9.0
     2011     */
     2012    toolbar: function() {
     2013        var frame = this.frame,
     2014            lastState = frame.lastState(),
     2015            previous = lastState && lastState.id;
     2016
     2017        frame.toolbar.set( new wp.media.view.Toolbar({
     2018            controller: frame,
     2019            items: {
     2020                back: {
     2021                    style: 'primary',
     2022                    text:     l10n.back,
     2023                    priority: 20,
     2024                    click:    function() {
     2025                        if ( previous ) {
     2026                            frame.setState( previous );
     2027                        } else {
     2028                            frame.close();
     2029                        }
     2030                    }
     2031                }
     2032            }
     2033        }) );
     2034    }
     2035});
     2036
     2037module.exports = EditImage;
     2038
     2039
     2040/***/ }),
     2041/* 40 */
     2042/***/ (function(module, exports) {
     2043
     2044/**
     2045 * wp.media.controller.MediaLibrary
     2046 *
     2047 * @class
     2048 * @augments wp.media.controller.Library
     2049 * @augments wp.media.controller.State
     2050 * @augments Backbone.Model
     2051 */
     2052var Library = wp.media.controller.Library,
     2053    MediaLibrary;
     2054
     2055MediaLibrary = Library.extend({
     2056    defaults: _.defaults({
     2057        // Attachments browser defaults. @see media.view.AttachmentsBrowser
     2058        filterable:      'uploaded',
     2059
     2060        displaySettings: false,
     2061        priority:        80,
     2062        syncSelection:   false
     2063    }, Library.prototype.defaults ),
     2064
     2065    /**
     2066     * @since 3.9.0
     2067     *
     2068     * @param options
     2069     */
     2070    initialize: function( options ) {
     2071        this.media = options.media;
     2072        this.type = options.type;
     2073        this.set( 'library', wp.media.query({ type: this.type }) );
     2074
     2075        Library.prototype.initialize.apply( this, arguments );
     2076    },
     2077
     2078    /**
     2079     * @since 3.9.0
     2080     */
     2081    activate: function() {
     2082        // @todo this should use this.frame.
     2083        if ( wp.media.frame.lastMime ) {
     2084            this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
     2085            delete wp.media.frame.lastMime;
     2086        }
     2087        Library.prototype.activate.apply( this, arguments );
     2088    }
     2089});
     2090
     2091module.exports = MediaLibrary;
     2092
     2093
     2094/***/ }),
     2095/* 41 */
     2096/***/ (function(module, exports) {
     2097
     2098/**
     2099 * wp.media.controller.Embed
     2100 *
     2101 * A state for embedding media from a URL.
     2102 *
     2103 * @class
     2104 * @augments wp.media.controller.State
     2105 * @augments Backbone.Model
     2106 *
     2107 * @param {object} attributes                         The attributes hash passed to the state.
     2108 * @param {string} [attributes.id=embed]              Unique identifier.
     2109 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
     2110 * @param {string} [attributes.content=embed]         Initial mode for the content region.
     2111 * @param {string} [attributes.menu=default]          Initial mode for the menu region.
     2112 * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
     2113 * @param {string} [attributes.menu=false]            Initial mode for the menu region.
     2114 * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
     2115 * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
     2116 * @param {string} [attributes.url]                   The embed URL.
     2117 * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
     2118 */
     2119var l10n = wp.media.view.l10n,
     2120    $ = Backbone.$,
     2121    Embed;
     2122
     2123Embed = wp.media.controller.State.extend({
     2124    defaults: {
     2125        id:       'embed',
     2126        title:    l10n.insertFromUrlTitle,
     2127        content:  'embed',
     2128        menu:     'default',
     2129        toolbar:  'main-embed',
     2130        priority: 120,
     2131        type:     'link',
     2132        url:      '',
     2133        metadata: {}
     2134    },
     2135
     2136    // The amount of time used when debouncing the scan.
     2137    sensitivity: 400,
     2138
     2139    initialize: function(options) {
     2140        this.metadata = options.metadata;
     2141        this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
     2142        this.props = new Backbone.Model( this.metadata || { url: '' });
     2143        this.props.on( 'change:url', this.debouncedScan, this );
     2144        this.props.on( 'change:url', this.refresh, this );
     2145        this.on( 'scan', this.scanImage, this );
     2146    },
     2147
     2148    /**
     2149     * Trigger a scan of the embedded URL's content for metadata required to embed.
     2150     *
     2151     * @fires wp.media.controller.Embed#scan
     2152     */
     2153    scan: function() {
     2154        var scanners,
     2155            embed = this,
     2156            attributes = {
     2157                type: 'link',
     2158                scanners: []
     2159            };
     2160
     2161        // Scan is triggered with the list of `attributes` to set on the
     2162        // state, useful for the 'type' attribute and 'scanners' attribute,
     2163        // an array of promise objects for asynchronous scan operations.
     2164        if ( this.props.get('url') ) {
     2165            this.trigger( 'scan', attributes );
     2166        }
     2167
     2168        if ( attributes.scanners.length ) {
     2169            scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
     2170            scanners.always( function() {
     2171                if ( embed.get('scanners') === scanners ) {
     2172                    embed.set( 'loading', false );
     2173                }
     2174            });
     2175        } else {
     2176            attributes.scanners = null;
     2177        }
     2178
     2179        attributes.loading = !! attributes.scanners;
     2180        this.set( attributes );
     2181    },
     2182    /**
     2183     * Try scanning the embed as an image to discover its dimensions.
     2184     *
     2185     * @param {Object} attributes
     2186     */
     2187    scanImage: function( attributes ) {
     2188        var frame = this.frame,
     2189            state = this,
     2190            url = this.props.get('url'),
     2191            image = new Image(),
     2192            deferred = $.Deferred();
     2193
     2194        attributes.scanners.push( deferred.promise() );
     2195
     2196        // Try to load the image and find its width/height.
     2197        image.onload = function() {
     2198            deferred.resolve();
     2199
     2200            if ( state !== frame.state() || url !== state.props.get('url') ) {
     2201                return;
     2202            }
     2203
     2204            state.set({
     2205                type: 'image'
     2206            });
     2207
     2208            state.props.set({
     2209                width:  image.width,
     2210                height: image.height
     2211            });
     2212        };
     2213
     2214        image.onerror = deferred.reject;
     2215        image.src = url;
     2216    },
     2217
     2218    refresh: function() {
     2219        this.frame.toolbar.get().refresh();
     2220    },
     2221
     2222    reset: function() {
     2223        this.props.clear().set({ url: '' });
     2224
     2225        if ( this.active ) {
     2226            this.refresh();
     2227        }
     2228    }
     2229});
     2230
     2231module.exports = Embed;
     2232
     2233
     2234/***/ }),
     2235/* 42 */
     2236/***/ (function(module, exports) {
     2237
     2238/**
     2239 * wp.media.controller.Cropper
     2240 *
     2241 * A state for cropping an image.
     2242 *
     2243 * @class
     2244 * @augments wp.media.controller.State
     2245 * @augments Backbone.Model
     2246 */
     2247var l10n = wp.media.view.l10n,
     2248    Cropper;
     2249
     2250Cropper = wp.media.controller.State.extend({
     2251    defaults: {
     2252        id:          'cropper',
     2253        title:       l10n.cropImage,
     2254        // Region mode defaults.
     2255        toolbar:     'crop',
     2256        content:     'crop',
     2257        router:      false,
     2258
     2259        canSkipCrop: false
     2260    },
     2261
     2262    activate: function() {
     2263        this.frame.on( 'content:create:crop', this.createCropContent, this );
     2264        this.frame.on( 'close', this.removeCropper, this );
     2265        this.set('selection', new Backbone.Collection(this.frame._selection.single));
     2266    },
     2267
     2268    deactivate: function() {
     2269        this.frame.toolbar.mode('browse');
     2270    },
     2271
     2272    createCropContent: function() {
     2273        this.cropperView = new wp.media.view.Cropper({
     2274            controller: this,
     2275            attachment: this.get('selection').first()
     2276        });
     2277        this.cropperView.on('image-loaded', this.createCropToolbar, this);
     2278        this.frame.content.set(this.cropperView);
     2279
     2280    },
     2281    removeCropper: function() {
     2282        this.imgSelect.cancelSelection();
     2283        this.imgSelect.setOptions({remove: true});
     2284        this.imgSelect.update();
     2285        this.cropperView.remove();
     2286    },
     2287    createCropToolbar: function() {
     2288        var canSkipCrop, toolbarOptions;
     2289
     2290        canSkipCrop = this.get('canSkipCrop') || false;
     2291
     2292        toolbarOptions = {
     2293            controller: this.frame,
     2294            items: {
     2295                insert: {
     2296                    style:    'primary',
     2297                    text:     l10n.cropImage,
     2298                    priority: 80,
     2299                    requires: { library: false, selection: false },
     2300
     2301                    click: function() {
     2302                        var controller = this.controller,
     2303                            selection;
     2304
     2305                        selection = controller.state().get('selection').first();
     2306                        selection.set({cropDetails: controller.state().imgSelect.getSelection()});
     2307
     2308                        this.$el.text(l10n.cropping);
     2309                        this.$el.attr('disabled', true);
     2310
     2311                        controller.state().doCrop( selection ).done( function( croppedImage ) {
     2312                            controller.trigger('cropped', croppedImage );
     2313                            controller.close();
     2314                        }).fail( function() {
     2315                            controller.trigger('content:error:crop');
     2316                        });
     2317                    }
     2318                }
     2319            }
     2320        };
     2321
     2322        if ( canSkipCrop ) {
     2323            _.extend( toolbarOptions.items, {
     2324                skip: {
     2325                    style:      'secondary',
     2326                    text:       l10n.skipCropping,
     2327                    priority:   70,
     2328                    requires:   { library: false, selection: false },
     2329                    click:      function() {
     2330                        var selection = this.controller.state().get('selection').first();
     2331                        this.controller.state().cropperView.remove();
     2332                        this.controller.trigger('skippedcrop', selection);
     2333                        this.controller.close();
     2334                    }
     2335                }
     2336            });
     2337        }
     2338
     2339        this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
     2340    },
     2341
     2342    doCrop: function( attachment ) {
     2343        return wp.ajax.post( 'custom-header-crop', {
     2344            nonce: attachment.get('nonces').edit,
     2345            id: attachment.get('id'),
     2346            cropDetails: attachment.get('cropDetails')
     2347        } );
     2348    }
     2349});
     2350
     2351module.exports = Cropper;
     2352
     2353
     2354/***/ }),
     2355/* 43 */
     2356/***/ (function(module, exports) {
     2357
     2358/**
     2359 * wp.media.controller.CustomizeImageCropper
     2360 *
     2361 * A state for cropping an image.
     2362 *
     2363 * @class
     2364 * @augments wp.media.controller.Cropper
     2365 * @augments wp.media.controller.State
     2366 * @augments Backbone.Model
     2367 */
     2368var Controller = wp.media.controller,
     2369    CustomizeImageCropper;
     2370
     2371CustomizeImageCropper = Controller.Cropper.extend({
     2372    doCrop: function( attachment ) {
     2373        var cropDetails = attachment.get( 'cropDetails' ),
     2374            control = this.get( 'control' );
     2375
     2376        cropDetails.dst_width  = control.params.width;
     2377        cropDetails.dst_height = control.params.height;
     2378
     2379        return wp.ajax.post( 'crop-image', {
     2380            wp_customize: 'on',
     2381            nonce: attachment.get( 'nonces' ).edit,
     2382            id: attachment.get( 'id' ),
     2383            context: control.id,
     2384            cropDetails: cropDetails
     2385        } );
     2386    }
     2387});
     2388
     2389module.exports = CustomizeImageCropper;
     2390
     2391
     2392/***/ }),
     2393/* 44 */
     2394/***/ (function(module, exports) {
     2395
    16742396/**
    16752397 * wp.media.controller.SiteIconCropper
     
    17202442module.exports = SiteIconCropper;
    17212443
    1722 },{}],16:[function(require,module,exports){
     2444
     2445/***/ }),
     2446/* 45 */
     2447/***/ (function(module, exports) {
     2448
    17232449/**
    1724  * wp.media.controller.StateMachine
    1725  *
    1726  * A state machine keeps track of state. It is in one state at a time,
    1727  * and can change from one state to another.
    1728  *
    1729  * States are stored as models in a Backbone collection.
    1730  *
    1731  * @since 3.5.0
     2450 * wp.media.View
     2451 *
     2452 * The base view class for media.
     2453 *
     2454 * Undelegating events, removing events from the model, and
     2455 * removing events from the controller mirror the code for
     2456 * `Backbone.View.dispose` in Backbone 0.9.8 development.
     2457 *
     2458 * This behavior has since been removed, and should not be used
     2459 * outside of the media manager.
    17322460 *
    17332461 * @class
    1734  * @augments Backbone.Model
    1735  * @mixin
    1736  * @mixes Backbone.Events
    1737  *
    1738  * @param {Array} states
     2462 * @augments wp.Backbone.View
     2463 * @augments Backbone.View
    17392464 */
    1740 var StateMachine = function( states ) {
    1741     // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
    1742     this.states = new Backbone.Collection( states );
    1743 };
    1744 
    1745 // Use Backbone's self-propagating `extend` inheritance method.
    1746 StateMachine.extend = Backbone.Model.extend;
    1747 
    1748 _.extend( StateMachine.prototype, Backbone.Events, {
    1749     /**
    1750      * Fetch a state.
     2465var View = wp.Backbone.View.extend({
     2466    constructor: function( options ) {
     2467        if ( options && options.controller ) {
     2468            this.controller = options.controller;
     2469        }
     2470        wp.Backbone.View.apply( this, arguments );
     2471    },
     2472    /**
     2473     * @todo The internal comment mentions this might have been a stop-gap
     2474     *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
     2475     *       care of this in Backbone.View now.
    17512476     *
    1752      * If no `id` is provided, returns the active state.
    1753      *
    1754      * Implicitly creates states.
    1755      *
    1756      * Ensure that the `states` collection exists so the `StateMachine`
    1757      *   can be used as a mixin.
    1758      *
    1759      * @since 3.5.0
    1760      *
    1761      * @param {string} id
    1762      * @returns {wp.media.controller.State} Returns a State model
    1763      *   from the StateMachine collection
    1764      */
    1765     state: function( id ) {
    1766         this.states = this.states || new Backbone.Collection();
    1767 
    1768         // Default to the active state.
    1769         id = id || this._state;
    1770 
    1771         if ( id && ! this.states.get( id ) ) {
    1772             this.states.add({ id: id });
    1773         }
    1774         return this.states.get( id );
    1775     },
    1776 
    1777     /**
    1778      * Sets the active state.
    1779      *
    1780      * Bail if we're trying to select the current state, if we haven't
    1781      * created the `states` collection, or are trying to select a state
    1782      * that does not exist.
    1783      *
    1784      * @since 3.5.0
    1785      *
    1786      * @param {string} id
    1787      *
    1788      * @fires wp.media.controller.State#deactivate
    1789      * @fires wp.media.controller.State#activate
    1790      *
    1791      * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
    1792      */
    1793     setState: function( id ) {
    1794         var previous = this.state();
    1795 
    1796         if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
    1797             return this;
    1798         }
    1799 
    1800         if ( previous ) {
    1801             previous.trigger('deactivate');
    1802             this._lastState = previous.id;
    1803         }
    1804 
    1805         this._state = id;
    1806         this.state().trigger('activate');
     2477     * @returns {wp.media.View} Returns itself to allow chaining
     2478     */
     2479    dispose: function() {
     2480        // Undelegating events, removing events from the model, and
     2481        // removing events from the controller mirror the code for
     2482        // `Backbone.View.dispose` in Backbone 0.9.8 development.
     2483        this.undelegateEvents();
     2484
     2485        if ( this.model && this.model.off ) {
     2486            this.model.off( null, null, this );
     2487        }
     2488
     2489        if ( this.collection && this.collection.off ) {
     2490            this.collection.off( null, null, this );
     2491        }
     2492
     2493        // Unbind controller events.
     2494        if ( this.controller && this.controller.off ) {
     2495            this.controller.off( null, null, this );
     2496        }
    18072497
    18082498        return this;
    18092499    },
    1810 
    1811     /**
    1812      * Returns the previous active state.
    1813      *
    1814      * Call the `state()` method with no parameters to retrieve the current
    1815      * active state.
    1816      *
    1817      * @since 3.5.0
    1818      *
    1819      * @returns {wp.media.controller.State} Returns a State model
    1820      *    from the StateMachine collection
    1821      */
    1822     lastState: function() {
    1823         if ( this._lastState ) {
    1824             return this.state( this._lastState );
    1825         }
     2500    /**
     2501     * @returns {wp.media.View} Returns itself to allow chaining
     2502     */
     2503    remove: function() {
     2504        this.dispose();
     2505        /**
     2506         * call 'remove' directly on the parent class
     2507         */
     2508        return wp.Backbone.View.prototype.remove.apply( this, arguments );
    18262509    }
    18272510});
    18282511
    1829 // Map all event binding and triggering on a StateMachine to its `states` collection.
    1830 _.each([ 'on', 'off', 'trigger' ], function( method ) {
    1831     /**
    1832      * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    1833      */
    1834     StateMachine.prototype[ method ] = function() {
    1835         // Ensure that the `states` collection exists so the `StateMachine`
    1836         // can be used as a mixin.
    1837         this.states = this.states || new Backbone.Collection();
    1838         // Forward the method to the `states` collection.
    1839         this.states[ method ].apply( this.states, arguments );
     2512module.exports = View;
     2513
     2514
     2515/***/ }),
     2516/* 46 */
     2517/***/ (function(module, exports) {
     2518
     2519/**
     2520 * wp.media.view.Frame
     2521 *
     2522 * A frame is a composite view consisting of one or more regions and one or more
     2523 * states.
     2524 *
     2525 * @see wp.media.controller.State
     2526 * @see wp.media.controller.Region
     2527 *
     2528 * @class
     2529 * @augments wp.media.View
     2530 * @augments wp.Backbone.View
     2531 * @augments Backbone.View
     2532 * @mixes wp.media.controller.StateMachine
     2533 */
     2534var Frame = wp.media.View.extend({
     2535    initialize: function() {
     2536        _.defaults( this.options, {
     2537            mode: [ 'select' ]
     2538        });
     2539        this._createRegions();
     2540        this._createStates();
     2541        this._createModes();
     2542    },
     2543
     2544    _createRegions: function() {
     2545        // Clone the regions array.
     2546        this.regions = this.regions ? this.regions.slice() : [];
     2547
     2548        // Initialize regions.
     2549        _.each( this.regions, function( region ) {
     2550            this[ region ] = new wp.media.controller.Region({
     2551                view:     this,
     2552                id:       region,
     2553                selector: '.media-frame-' + region
     2554            });
     2555        }, this );
     2556    },
     2557    /**
     2558     * Create the frame's states.
     2559     *
     2560     * @see wp.media.controller.State
     2561     * @see wp.media.controller.StateMachine
     2562     *
     2563     * @fires wp.media.controller.State#ready
     2564     */
     2565    _createStates: function() {
     2566        // Create the default `states` collection.
     2567        this.states = new Backbone.Collection( null, {
     2568            model: wp.media.controller.State
     2569        });
     2570
     2571        // Ensure states have a reference to the frame.
     2572        this.states.on( 'add', function( model ) {
     2573            model.frame = this;
     2574            model.trigger('ready');
     2575        }, this );
     2576
     2577        if ( this.options.states ) {
     2578            this.states.add( this.options.states );
     2579        }
     2580    },
     2581
     2582    /**
     2583     * A frame can be in a mode or multiple modes at one time.
     2584     *
     2585     * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
     2586     */
     2587    _createModes: function() {
     2588        // Store active "modes" that the frame is in. Unrelated to region modes.
     2589        this.activeModes = new Backbone.Collection();
     2590        this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
     2591
     2592        _.each( this.options.mode, function( mode ) {
     2593            this.activateMode( mode );
     2594        }, this );
     2595    },
     2596    /**
     2597     * Reset all states on the frame to their defaults.
     2598     *
     2599     * @returns {wp.media.view.Frame} Returns itself to allow chaining
     2600     */
     2601    reset: function() {
     2602        this.states.invoke( 'trigger', 'reset' );
     2603        return this;
     2604    },
     2605    /**
     2606     * Map activeMode collection events to the frame.
     2607     */
     2608    triggerModeEvents: function( model, collection, options ) {
     2609        var collectionEvent,
     2610            modeEventMap = {
     2611                add: 'activate',
     2612                remove: 'deactivate'
     2613            },
     2614            eventToTrigger;
     2615        // Probably a better way to do this.
     2616        _.each( options, function( value, key ) {
     2617            if ( value ) {
     2618                collectionEvent = key;
     2619            }
     2620        } );
     2621
     2622        if ( ! _.has( modeEventMap, collectionEvent ) ) {
     2623            return;
     2624        }
     2625
     2626        eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
     2627        this.trigger( eventToTrigger );
     2628    },
     2629    /**
     2630     * Activate a mode on the frame.
     2631     *
     2632     * @param string mode Mode ID.
     2633     * @returns {this} Returns itself to allow chaining.
     2634     */
     2635    activateMode: function( mode ) {
     2636        // Bail if the mode is already active.
     2637        if ( this.isModeActive( mode ) ) {
     2638            return;
     2639        }
     2640        this.activeModes.add( [ { id: mode } ] );
     2641        // Add a CSS class to the frame so elements can be styled for the mode.
     2642        this.$el.addClass( 'mode-' + mode );
     2643
     2644        return this;
     2645    },
     2646    /**
     2647     * Deactivate a mode on the frame.
     2648     *
     2649     * @param string mode Mode ID.
     2650     * @returns {this} Returns itself to allow chaining.
     2651     */
     2652    deactivateMode: function( mode ) {
     2653        // Bail if the mode isn't active.
     2654        if ( ! this.isModeActive( mode ) ) {
     2655            return this;
     2656        }
     2657        this.activeModes.remove( this.activeModes.where( { id: mode } ) );
     2658        this.$el.removeClass( 'mode-' + mode );
     2659        /**
     2660         * Frame mode deactivation event.
     2661         *
     2662         * @event this#{mode}:deactivate
     2663         */
     2664        this.trigger( mode + ':deactivate' );
     2665
     2666        return this;
     2667    },
     2668    /**
     2669     * Check if a mode is enabled on the frame.
     2670     *
     2671     * @param  string mode Mode ID.
     2672     * @return bool
     2673     */
     2674    isModeActive: function( mode ) {
     2675        return Boolean( this.activeModes.where( { id: mode } ).length );
     2676    }
     2677});
     2678
     2679// Make the `Frame` a `StateMachine`.
     2680_.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
     2681
     2682module.exports = Frame;
     2683
     2684
     2685/***/ }),
     2686/* 47 */
     2687/***/ (function(module, exports) {
     2688
     2689/**
     2690 * wp.media.view.MediaFrame
     2691 *
     2692 * The frame used to create the media modal.
     2693 *
     2694 * @class
     2695 * @augments wp.media.view.Frame
     2696 * @augments wp.media.View
     2697 * @augments wp.Backbone.View
     2698 * @augments Backbone.View
     2699 * @mixes wp.media.controller.StateMachine
     2700 */
     2701var Frame = wp.media.view.Frame,
     2702    $ = jQuery,
     2703    MediaFrame;
     2704
     2705MediaFrame = Frame.extend({
     2706    className: 'media-frame',
     2707    template:  wp.template('media-frame'),
     2708    regions:   ['menu','title','content','toolbar','router'],
     2709
     2710    events: {
     2711        'click div.media-frame-title h1': 'toggleMenu'
     2712    },
     2713
     2714    /**
     2715     * @global wp.Uploader
     2716     */
     2717    initialize: function() {
     2718        Frame.prototype.initialize.apply( this, arguments );
     2719
     2720        _.defaults( this.options, {
     2721            title:    '',
     2722            modal:    true,
     2723            uploader: true
     2724        });
     2725
     2726        // Ensure core UI is enabled.
     2727        this.$el.addClass('wp-core-ui');
     2728
     2729        // Initialize modal container view.
     2730        if ( this.options.modal ) {
     2731            this.modal = new wp.media.view.Modal({
     2732                controller: this,
     2733                title:      this.options.title
     2734            });
     2735
     2736            this.modal.content( this );
     2737        }
     2738
     2739        // Force the uploader off if the upload limit has been exceeded or
     2740        // if the browser isn't supported.
     2741        if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     2742            this.options.uploader = false;
     2743        }
     2744
     2745        // Initialize window-wide uploader.
     2746        if ( this.options.uploader ) {
     2747            this.uploader = new wp.media.view.UploaderWindow({
     2748                controller: this,
     2749                uploader: {
     2750                    dropzone:  this.modal ? this.modal.$el : this.$el,
     2751                    container: this.$el
     2752                }
     2753            });
     2754            this.views.set( '.media-frame-uploader', this.uploader );
     2755        }
     2756
     2757        this.on( 'attach', _.bind( this.views.ready, this.views ), this );
     2758
     2759        // Bind default title creation.
     2760        this.on( 'title:create:default', this.createTitle, this );
     2761        this.title.mode('default');
     2762
     2763        this.on( 'title:render', function( view ) {
     2764            view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
     2765        });
     2766
     2767        // Bind default menu.
     2768        this.on( 'menu:create:default', this.createMenu, this );
     2769    },
     2770    /**
     2771     * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     2772     */
     2773    render: function() {
     2774        // Activate the default state if no active state exists.
     2775        if ( ! this.state() && this.options.state ) {
     2776            this.setState( this.options.state );
     2777        }
     2778        /**
     2779         * call 'render' directly on the parent class
     2780         */
     2781        return Frame.prototype.render.apply( this, arguments );
     2782    },
     2783    /**
     2784     * @param {Object} title
     2785     * @this wp.media.controller.Region
     2786     */
     2787    createTitle: function( title ) {
     2788        title.view = new wp.media.View({
     2789            controller: this,
     2790            tagName: 'h1'
     2791        });
     2792    },
     2793    /**
     2794     * @param {Object} menu
     2795     * @this wp.media.controller.Region
     2796     */
     2797    createMenu: function( menu ) {
     2798        menu.view = new wp.media.view.Menu({
     2799            controller: this
     2800        });
     2801    },
     2802
     2803    toggleMenu: function() {
     2804        this.$el.find( '.media-menu' ).toggleClass( 'visible' );
     2805    },
     2806
     2807    /**
     2808     * @param {Object} toolbar
     2809     * @this wp.media.controller.Region
     2810     */
     2811    createToolbar: function( toolbar ) {
     2812        toolbar.view = new wp.media.view.Toolbar({
     2813            controller: this
     2814        });
     2815    },
     2816    /**
     2817     * @param {Object} router
     2818     * @this wp.media.controller.Region
     2819     */
     2820    createRouter: function( router ) {
     2821        router.view = new wp.media.view.Router({
     2822            controller: this
     2823        });
     2824    },
     2825    /**
     2826     * @param {Object} options
     2827     */
     2828    createIframeStates: function( options ) {
     2829        var settings = wp.media.view.settings,
     2830            tabs = settings.tabs,
     2831            tabUrl = settings.tabUrl,
     2832            $postId;
     2833
     2834        if ( ! tabs || ! tabUrl ) {
     2835            return;
     2836        }
     2837
     2838        // Add the post ID to the tab URL if it exists.
     2839        $postId = $('#post_ID');
     2840        if ( $postId.length ) {
     2841            tabUrl += '&post_id=' + $postId.val();
     2842        }
     2843
     2844        // Generate the tab states.
     2845        _.each( tabs, function( title, id ) {
     2846            this.state( 'iframe:' + id ).set( _.defaults({
     2847                tab:     id,
     2848                src:     tabUrl + '&tab=' + id,
     2849                title:   title,
     2850                content: 'iframe',
     2851                menu:    'default'
     2852            }, options ) );
     2853        }, this );
     2854
     2855        this.on( 'content:create:iframe', this.iframeContent, this );
     2856        this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
     2857        this.on( 'menu:render:default', this.iframeMenu, this );
     2858        this.on( 'open', this.hijackThickbox, this );
     2859        this.on( 'close', this.restoreThickbox, this );
     2860    },
     2861
     2862    /**
     2863     * @param {Object} content
     2864     * @this wp.media.controller.Region
     2865     */
     2866    iframeContent: function( content ) {
     2867        this.$el.addClass('hide-toolbar');
     2868        content.view = new wp.media.view.Iframe({
     2869            controller: this
     2870        });
     2871    },
     2872
     2873    iframeContentCleanup: function() {
     2874        this.$el.removeClass('hide-toolbar');
     2875    },
     2876
     2877    iframeMenu: function( view ) {
     2878        var views = {};
     2879
     2880        if ( ! view ) {
     2881            return;
     2882        }
     2883
     2884        _.each( wp.media.view.settings.tabs, function( title, id ) {
     2885            views[ 'iframe:' + id ] = {
     2886                text: this.state( 'iframe:' + id ).get('title'),
     2887                priority: 200
     2888            };
     2889        }, this );
     2890
     2891        view.set( views );
     2892    },
     2893
     2894    hijackThickbox: function() {
     2895        var frame = this;
     2896
     2897        if ( ! window.tb_remove || this._tb_remove ) {
     2898            return;
     2899        }
     2900
     2901        this._tb_remove = window.tb_remove;
     2902        window.tb_remove = function() {
     2903            frame.close();
     2904            frame.reset();
     2905            frame.setState( frame.options.state );
     2906            frame._tb_remove.call( window );
     2907        };
     2908    },
     2909
     2910    restoreThickbox: function() {
     2911        if ( ! this._tb_remove ) {
     2912            return;
     2913        }
     2914
     2915        window.tb_remove = this._tb_remove;
     2916        delete this._tb_remove;
     2917    }
     2918});
     2919
     2920// Map some of the modal's methods to the frame.
     2921_.each(['open','close','attach','detach','escape'], function( method ) {
     2922    /**
     2923     * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     2924     */
     2925    MediaFrame.prototype[ method ] = function() {
     2926        if ( this.modal ) {
     2927            this.modal[ method ].apply( this.modal, arguments );
     2928        }
    18402929        return this;
    18412930    };
    18422931});
    18432932
    1844 module.exports = StateMachine;
    1845 
    1846 },{}],17:[function(require,module,exports){
     2933module.exports = MediaFrame;
     2934
     2935
     2936/***/ }),
     2937/* 48 */
     2938/***/ (function(module, exports) {
     2939
    18472940/**
    1848  * wp.media.controller.State
    1849  *
    1850  * A state is a step in a workflow that when set will trigger the controllers
    1851  * for the regions to be updated as specified in the frame.
    1852  *
    1853  * A state has an event-driven lifecycle:
    1854  *
    1855  *     'ready'      triggers when a state is added to a state machine's collection.
    1856  *     'activate'   triggers when a state is activated by a state machine.
    1857  *     'deactivate' triggers when a state is deactivated by a state machine.
    1858  *     'reset'      is not triggered automatically. It should be invoked by the
    1859  *                  proper controller to reset the state to its default.
     2941 * wp.media.view.MediaFrame.Select
     2942 *
     2943 * A frame for selecting an item or items from the media library.
    18602944 *
    18612945 * @class
    1862  * @augments Backbone.Model
     2946 * @augments wp.media.view.MediaFrame
     2947 * @augments wp.media.view.Frame
     2948 * @augments wp.media.View
     2949 * @augments wp.Backbone.View
     2950 * @augments Backbone.View
     2951 * @mixes wp.media.controller.StateMachine
    18632952 */
    1864 var State = Backbone.Model.extend({
    1865     /**
    1866      * Constructor.
     2953
     2954var MediaFrame = wp.media.view.MediaFrame,
     2955    l10n = wp.media.view.l10n,
     2956    Select;
     2957
     2958Select = MediaFrame.extend({
     2959    initialize: function() {
     2960        // Call 'initialize' directly on the parent class.
     2961        MediaFrame.prototype.initialize.apply( this, arguments );
     2962
     2963        _.defaults( this.options, {
     2964            selection: [],
     2965            library:   {},
     2966            multiple:  false,
     2967            state:    'library'
     2968        });
     2969
     2970        this.createSelection();
     2971        this.createStates();
     2972        this.bindHandlers();
     2973    },
     2974
     2975    /**
     2976     * Attach a selection collection to the frame.
    18672977     *
    1868      * @since 3.5.0
    1869      */
    1870     constructor: function() {
    1871         this.on( 'activate', this._preActivate, this );
    1872         this.on( 'activate', this.activate, this );
    1873         this.on( 'activate', this._postActivate, this );
    1874         this.on( 'deactivate', this._deactivate, this );
    1875         this.on( 'deactivate', this.deactivate, this );
    1876         this.on( 'reset', this.reset, this );
    1877         this.on( 'ready', this._ready, this );
    1878         this.on( 'ready', this.ready, this );
    1879         /**
    1880          * Call parent constructor with passed arguments
    1881          */
    1882         Backbone.Model.apply( this, arguments );
    1883         this.on( 'change:menu', this._updateMenu, this );
    1884     },
    1885     /**
    1886      * Ready event callback.
     2978     * A selection is a collection of attachments used for a specific purpose
     2979     * by a media frame. e.g. Selecting an attachment (or many) to insert into
     2980     * post content.
    18872981     *
    1888      * @abstract
    1889      * @since 3.5.0
    1890      */
    1891     ready: function() {},
    1892 
    1893     /**
    1894      * Activate event callback.
     2982     * @see media.model.Selection
     2983     */
     2984    createSelection: function() {
     2985        var selection = this.options.selection;
     2986
     2987        if ( ! (selection instanceof wp.media.model.Selection) ) {
     2988            this.options.selection = new wp.media.model.Selection( selection, {
     2989                multiple: this.options.multiple
     2990            });
     2991        }
     2992
     2993        this._selection = {
     2994            attachments: new wp.media.model.Attachments(),
     2995            difference: []
     2996        };
     2997    },
     2998
     2999    /**
     3000     * Create the default states on the frame.
     3001     */
     3002    createStates: function() {
     3003        var options = this.options;
     3004
     3005        if ( this.options.states ) {
     3006            return;
     3007        }
     3008
     3009        // Add the default states.
     3010        this.states.add([
     3011            // Main states.
     3012            new wp.media.controller.Library({
     3013                library:   wp.media.query( options.library ),
     3014                multiple:  options.multiple,
     3015                title:     options.title,
     3016                priority:  20
     3017            })
     3018        ]);
     3019    },
     3020
     3021    /**
     3022     * Bind region mode event callbacks.
    18953023     *
    1896      * @abstract
    1897      * @since 3.5.0
    1898      */
    1899     activate: function() {},
    1900 
    1901     /**
    1902      * Deactivate event callback.
     3024     * @see media.controller.Region.render
     3025     */
     3026    bindHandlers: function() {
     3027        this.on( 'router:create:browse', this.createRouter, this );
     3028        this.on( 'router:render:browse', this.browseRouter, this );
     3029        this.on( 'content:create:browse', this.browseContent, this );
     3030        this.on( 'content:render:upload', this.uploadContent, this );
     3031        this.on( 'toolbar:create:select', this.createSelectToolbar, this );
     3032    },
     3033
     3034    /**
     3035     * Render callback for the router region in the `browse` mode.
    19033036     *
    1904      * @abstract
    1905      * @since 3.5.0
    1906      */
    1907     deactivate: function() {},
    1908 
    1909     /**
    1910      * Reset event callback.
     3037     * @param {wp.media.view.Router} routerView
     3038     */
     3039    browseRouter: function( routerView ) {
     3040        routerView.set({
     3041            upload: {
     3042                text:     l10n.uploadFilesTitle,
     3043                priority: 20
     3044            },
     3045            browse: {
     3046                text:     l10n.mediaLibraryTitle,
     3047                priority: 40
     3048            }
     3049        });
     3050    },
     3051
     3052    /**
     3053     * Render callback for the content region in the `browse` mode.
    19113054     *
    1912      * @abstract
    1913      * @since 3.5.0
    1914      */
    1915     reset: function() {},
    1916 
    1917     /**
    1918      * @access private
    1919      * @since 3.5.0
    1920      */
    1921     _ready: function() {
    1922         this._updateMenu();
    1923     },
    1924 
    1925     /**
    1926      * @access private
    1927      * @since 3.5.0
    1928     */
    1929     _preActivate: function() {
    1930         this.active = true;
    1931     },
    1932 
    1933     /**
    1934      * @access private
    1935      * @since 3.5.0
    1936      */
    1937     _postActivate: function() {
    1938         this.on( 'change:menu', this._menu, this );
    1939         this.on( 'change:titleMode', this._title, this );
    1940         this.on( 'change:content', this._content, this );
    1941         this.on( 'change:toolbar', this._toolbar, this );
    1942 
    1943         this.frame.on( 'title:render:default', this._renderTitle, this );
    1944 
    1945         this._title();
    1946         this._menu();
    1947         this._toolbar();
    1948         this._content();
    1949         this._router();
    1950     },
    1951 
    1952     /**
    1953      * @access private
    1954      * @since 3.5.0
    1955      */
    1956     _deactivate: function() {
    1957         this.active = false;
    1958 
    1959         this.frame.off( 'title:render:default', this._renderTitle, this );
    1960 
    1961         this.off( 'change:menu', this._menu, this );
    1962         this.off( 'change:titleMode', this._title, this );
    1963         this.off( 'change:content', this._content, this );
    1964         this.off( 'change:toolbar', this._toolbar, this );
    1965     },
    1966 
    1967     /**
    1968      * @access private
    1969      * @since 3.5.0
    1970      */
    1971     _title: function() {
    1972         this.frame.title.render( this.get('titleMode') || 'default' );
    1973     },
    1974 
    1975     /**
    1976      * @access private
    1977      * @since 3.5.0
    1978      */
    1979     _renderTitle: function( view ) {
    1980         view.$el.text( this.get('title') || '' );
    1981     },
    1982 
    1983     /**
    1984      * @access private
    1985      * @since 3.5.0
    1986      */
    1987     _router: function() {
    1988         var router = this.frame.router,
    1989             mode = this.get('router'),
    1990             view;
    1991 
    1992         this.frame.$el.toggleClass( 'hide-router', ! mode );
    1993         if ( ! mode ) {
    1994             return;
    1995         }
    1996 
    1997         this.frame.router.render( mode );
    1998 
    1999         view = router.get();
    2000         if ( view && view.select ) {
    2001             view.select( this.frame.content.mode() );
    2002         }
    2003     },
    2004 
    2005     /**
    2006      * @access private
    2007      * @since 3.5.0
    2008      */
    2009     _menu: function() {
    2010         var menu = this.frame.menu,
    2011             mode = this.get('menu'),
    2012             view;
    2013 
    2014         this.frame.$el.toggleClass( 'hide-menu', ! mode );
    2015         if ( ! mode ) {
    2016             return;
    2017         }
    2018 
    2019         menu.mode( mode );
    2020 
    2021         view = menu.get();
    2022         if ( view && view.select ) {
    2023             view.select( this.id );
    2024         }
    2025     },
    2026 
    2027     /**
    2028      * @access private
    2029      * @since 3.5.0
    2030      */
    2031     _updateMenu: function() {
    2032         var previous = this.previous('menu'),
    2033             menu = this.get('menu');
    2034 
    2035         if ( previous ) {
    2036             this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
    2037         }
    2038 
    2039         if ( menu ) {
    2040             this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
    2041         }
    2042     },
    2043 
    2044     /**
    2045      * Create a view in the media menu for the state.
     3055     * @param {wp.media.controller.Region} contentRegion
     3056     */
     3057    browseContent: function( contentRegion ) {
     3058        var state = this.state();
     3059
     3060        this.$el.removeClass('hide-toolbar');
     3061
     3062        // Browse our library of attachments.
     3063        contentRegion.view = new wp.media.view.AttachmentsBrowser({
     3064            controller: this,
     3065            collection: state.get('library'),
     3066            selection:  state.get('selection'),
     3067            model:      state,
     3068            sortable:   state.get('sortable'),
     3069            search:     state.get('searchable'),
     3070            filters:    state.get('filterable'),
     3071            date:       state.get('date'),
     3072            display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
     3073            dragInfo:   state.get('dragInfo'),
     3074
     3075            idealColumnWidth: state.get('idealColumnWidth'),
     3076            suggestedWidth:   state.get('suggestedWidth'),
     3077            suggestedHeight:  state.get('suggestedHeight'),
     3078
     3079            AttachmentView: state.get('AttachmentView')
     3080        });
     3081    },
     3082
     3083    /**
     3084     * Render callback for the content region in the `upload` mode.
     3085     */
     3086    uploadContent: function() {
     3087        this.$el.removeClass( 'hide-toolbar' );
     3088        this.content.set( new wp.media.view.UploaderInline({
     3089            controller: this
     3090        }) );
     3091    },
     3092
     3093    /**
     3094     * Toolbars
    20463095     *
    2047      * @access private
    2048      * @since 3.5.0
    2049      *
    2050      * @param {media.view.Menu} view The menu view.
    2051      */
    2052     _renderMenu: function( view ) {
    2053         var menuItem = this.get('menuItem'),
    2054             title = this.get('title'),
    2055             priority = this.get('priority');
    2056 
    2057         if ( ! menuItem && title ) {
    2058             menuItem = { text: title };
    2059 
    2060             if ( priority ) {
    2061                 menuItem.priority = priority;
    2062             }
    2063         }
    2064 
    2065         if ( ! menuItem ) {
    2066             return;
    2067         }
    2068 
    2069         view.set( this.id, menuItem );
     3096     * @param {Object} toolbar
     3097     * @param {Object} [options={}]
     3098     * @this wp.media.controller.Region
     3099     */
     3100    createSelectToolbar: function( toolbar, options ) {
     3101        options = options || this.options.button || {};
     3102        options.controller = this;
     3103
     3104        toolbar.view = new wp.media.view.Toolbar.Select( options );
    20703105    }
    20713106});
    20723107
    2073 _.each(['toolbar','content'], function( region ) {
    2074     /**
    2075      * @access private
    2076      */
    2077     State.prototype[ '_' + region ] = function() {
    2078         var mode = this.get( region );
    2079         if ( mode ) {
    2080             this.frame[ region ].render( mode );
    2081         }
    2082     };
     3108module.exports = Select;
     3109
     3110
     3111/***/ }),
     3112/* 49 */
     3113/***/ (function(module, exports) {
     3114
     3115/**
     3116 * wp.media.view.MediaFrame.Post
     3117 *
     3118 * The frame for manipulating media on the Edit Post page.
     3119 *
     3120 * @class
     3121 * @augments wp.media.view.MediaFrame.Select
     3122 * @augments wp.media.view.MediaFrame
     3123 * @augments wp.media.view.Frame
     3124 * @augments wp.media.View
     3125 * @augments wp.Backbone.View
     3126 * @augments Backbone.View
     3127 * @mixes wp.media.controller.StateMachine
     3128 */
     3129var Select = wp.media.view.MediaFrame.Select,
     3130    Library = wp.media.controller.Library,
     3131    l10n = wp.media.view.l10n,
     3132    Post;
     3133
     3134Post = Select.extend({
     3135    initialize: function() {
     3136        this.counts = {
     3137            audio: {
     3138                count: wp.media.view.settings.attachmentCounts.audio,
     3139                state: 'playlist'
     3140            },
     3141            video: {
     3142                count: wp.media.view.settings.attachmentCounts.video,
     3143                state: 'video-playlist'
     3144            }
     3145        };
     3146
     3147        _.defaults( this.options, {
     3148            multiple:  true,
     3149            editing:   false,
     3150            state:    'insert',
     3151            metadata:  {}
     3152        });
     3153
     3154        // Call 'initialize' directly on the parent class.
     3155        Select.prototype.initialize.apply( this, arguments );
     3156        this.createIframeStates();
     3157
     3158    },
     3159
     3160    /**
     3161     * Create the default states.
     3162     */
     3163    createStates: function() {
     3164        var options = this.options;
     3165
     3166        this.states.add([
     3167            // Main states.
     3168            new Library({
     3169                id:         'insert',
     3170                title:      l10n.insertMediaTitle,
     3171                priority:   20,
     3172                toolbar:    'main-insert',
     3173                filterable: 'all',
     3174                library:    wp.media.query( options.library ),
     3175                multiple:   options.multiple ? 'reset' : false,
     3176                editable:   true,
     3177
     3178                // If the user isn't allowed to edit fields,
     3179                // can they still edit it locally?
     3180                allowLocalEdits: true,
     3181
     3182                // Show the attachment display settings.
     3183                displaySettings: true,
     3184                // Update user settings when users adjust the
     3185                // attachment display settings.
     3186                displayUserSettings: true
     3187            }),
     3188
     3189            new Library({
     3190                id:         'gallery',
     3191                title:      l10n.createGalleryTitle,
     3192                priority:   40,
     3193                toolbar:    'main-gallery',
     3194                filterable: 'uploaded',
     3195                multiple:   'add',
     3196                editable:   false,
     3197
     3198                library:  wp.media.query( _.defaults({
     3199                    type: 'image'
     3200                }, options.library ) )
     3201            }),
     3202
     3203            // Embed states.
     3204            new wp.media.controller.Embed( { metadata: options.metadata } ),
     3205
     3206            new wp.media.controller.EditImage( { model: options.editImage } ),
     3207
     3208            // Gallery states.
     3209            new wp.media.controller.GalleryEdit({
     3210                library: options.selection,
     3211                editing: options.editing,
     3212                menu:    'gallery'
     3213            }),
     3214
     3215            new wp.media.controller.GalleryAdd(),
     3216
     3217            new Library({
     3218                id:         'playlist',
     3219                title:      l10n.createPlaylistTitle,
     3220                priority:   60,
     3221                toolbar:    'main-playlist',
     3222                filterable: 'uploaded',
     3223                multiple:   'add',
     3224                editable:   false,
     3225
     3226                library:  wp.media.query( _.defaults({
     3227                    type: 'audio'
     3228                }, options.library ) )
     3229            }),
     3230
     3231            // Playlist states.
     3232            new wp.media.controller.CollectionEdit({
     3233                type: 'audio',
     3234                collectionType: 'playlist',
     3235                title:          l10n.editPlaylistTitle,
     3236                SettingsView:   wp.media.view.Settings.Playlist,
     3237                library:        options.selection,
     3238                editing:        options.editing,
     3239                menu:           'playlist',
     3240                dragInfoText:   l10n.playlistDragInfo,
     3241                dragInfo:       false
     3242            }),
     3243
     3244            new wp.media.controller.CollectionAdd({
     3245                type: 'audio',
     3246                collectionType: 'playlist',
     3247                title: l10n.addToPlaylistTitle
     3248            }),
     3249
     3250            new Library({
     3251                id:         'video-playlist',
     3252                title:      l10n.createVideoPlaylistTitle,
     3253                priority:   60,
     3254                toolbar:    'main-video-playlist',
     3255                filterable: 'uploaded',
     3256                multiple:   'add',
     3257                editable:   false,
     3258
     3259                library:  wp.media.query( _.defaults({
     3260                    type: 'video'
     3261                }, options.library ) )
     3262            }),
     3263
     3264            new wp.media.controller.CollectionEdit({
     3265                type: 'video',
     3266                collectionType: 'playlist',
     3267                title:          l10n.editVideoPlaylistTitle,
     3268                SettingsView:   wp.media.view.Settings.Playlist,
     3269                library:        options.selection,
     3270                editing:        options.editing,
     3271                menu:           'video-playlist',
     3272                dragInfoText:   l10n.videoPlaylistDragInfo,
     3273                dragInfo:       false
     3274            }),
     3275
     3276            new wp.media.controller.CollectionAdd({
     3277                type: 'video',
     3278                collectionType: 'playlist',
     3279                title: l10n.addToVideoPlaylistTitle
     3280            })
     3281        ]);
     3282
     3283        if ( wp.media.view.settings.post.featuredImageId ) {
     3284            this.states.add( new wp.media.controller.FeaturedImage() );
     3285        }
     3286    },
     3287
     3288    bindHandlers: function() {
     3289        var handlers, checkCounts;
     3290
     3291        Select.prototype.bindHandlers.apply( this, arguments );
     3292
     3293        this.on( 'activate', this.activate, this );
     3294
     3295        // Only bother checking media type counts if one of the counts is zero
     3296        checkCounts = _.find( this.counts, function( type ) {
     3297            return type.count === 0;
     3298        } );
     3299
     3300        if ( typeof checkCounts !== 'undefined' ) {
     3301            this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
     3302        }
     3303
     3304        this.on( 'menu:create:gallery', this.createMenu, this );
     3305        this.on( 'menu:create:playlist', this.createMenu, this );
     3306        this.on( 'menu:create:video-playlist', this.createMenu, this );
     3307        this.on( 'toolbar:create:main-insert', this.createToolbar, this );
     3308        this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
     3309        this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
     3310        this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
     3311        this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
     3312        this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
     3313
     3314        handlers = {
     3315            menu: {
     3316                'default': 'mainMenu',
     3317                'gallery': 'galleryMenu',
     3318                'playlist': 'playlistMenu',
     3319                'video-playlist': 'videoPlaylistMenu'
     3320            },
     3321
     3322            content: {
     3323                'embed':          'embedContent',
     3324                'edit-image':     'editImageContent',
     3325                'edit-selection': 'editSelectionContent'
     3326            },
     3327
     3328            toolbar: {
     3329                'main-insert':      'mainInsertToolbar',
     3330                'main-gallery':     'mainGalleryToolbar',
     3331                'gallery-edit':     'galleryEditToolbar',
     3332                'gallery-add':      'galleryAddToolbar',
     3333                'main-playlist':    'mainPlaylistToolbar',
     3334                'playlist-edit':    'playlistEditToolbar',
     3335                'playlist-add':     'playlistAddToolbar',
     3336                'main-video-playlist': 'mainVideoPlaylistToolbar',
     3337                'video-playlist-edit': 'videoPlaylistEditToolbar',
     3338                'video-playlist-add': 'videoPlaylistAddToolbar'
     3339            }
     3340        };
     3341
     3342        _.each( handlers, function( regionHandlers, region ) {
     3343            _.each( regionHandlers, function( callback, handler ) {
     3344                this.on( region + ':render:' + handler, this[ callback ], this );
     3345            }, this );
     3346        }, this );
     3347    },
     3348
     3349    activate: function() {
     3350        // Hide menu items for states tied to particular media types if there are no items
     3351        _.each( this.counts, function( type ) {
     3352            if ( type.count < 1 ) {
     3353                this.menuItemVisibility( type.state, 'hide' );
     3354            }
     3355        }, this );
     3356    },
     3357
     3358    mediaTypeCounts: function( model, attr ) {
     3359        if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
     3360            this.counts[ attr ].count++;
     3361            this.menuItemVisibility( this.counts[ attr ].state, 'show' );
     3362        }
     3363    },
     3364
     3365    // Menus
     3366    /**
     3367     * @param {wp.Backbone.View} view
     3368     */
     3369    mainMenu: function( view ) {
     3370        view.set({
     3371            'library-separator': new wp.media.View({
     3372                className: 'separator',
     3373                priority: 100
     3374            })
     3375        });
     3376    },
     3377
     3378    menuItemVisibility: function( state, visibility ) {
     3379        var menu = this.menu.get();
     3380        if ( visibility === 'hide' ) {
     3381            menu.hide( state );
     3382        } else if ( visibility === 'show' ) {
     3383            menu.show( state );
     3384        }
     3385    },
     3386    /**
     3387     * @param {wp.Backbone.View} view
     3388     */
     3389    galleryMenu: function( view ) {
     3390        var lastState = this.lastState(),
     3391            previous = lastState && lastState.id,
     3392            frame = this;
     3393
     3394        view.set({
     3395            cancel: {
     3396                text:     l10n.cancelGalleryTitle,
     3397                priority: 20,
     3398                click:    function() {
     3399                    if ( previous ) {
     3400                        frame.setState( previous );
     3401                    } else {
     3402                        frame.close();
     3403                    }
     3404
     3405                    // Keep focus inside media modal
     3406                    // after canceling a gallery
     3407                    this.controller.modal.focusManager.focus();
     3408                }
     3409            },
     3410            separateCancel: new wp.media.View({
     3411                className: 'separator',
     3412                priority: 40
     3413            })
     3414        });
     3415    },
     3416
     3417    playlistMenu: function( view ) {
     3418        var lastState = this.lastState(),
     3419            previous = lastState && lastState.id,
     3420            frame = this;
     3421
     3422        view.set({
     3423            cancel: {
     3424                text:     l10n.cancelPlaylistTitle,
     3425                priority: 20,
     3426                click:    function() {
     3427                    if ( previous ) {
     3428                        frame.setState( previous );
     3429                    } else {
     3430                        frame.close();
     3431                    }
     3432                }
     3433            },
     3434            separateCancel: new wp.media.View({
     3435                className: 'separator',
     3436                priority: 40
     3437            })
     3438        });
     3439    },
     3440
     3441    videoPlaylistMenu: function( view ) {
     3442        var lastState = this.lastState(),
     3443            previous = lastState && lastState.id,
     3444            frame = this;
     3445
     3446        view.set({
     3447            cancel: {
     3448                text:     l10n.cancelVideoPlaylistTitle,
     3449                priority: 20,
     3450                click:    function() {
     3451                    if ( previous ) {
     3452                        frame.setState( previous );
     3453                    } else {
     3454                        frame.close();
     3455                    }
     3456                }
     3457            },
     3458            separateCancel: new wp.media.View({
     3459                className: 'separator',
     3460                priority: 40
     3461            })
     3462        });
     3463    },
     3464
     3465    // Content
     3466    embedContent: function() {
     3467        var view = new wp.media.view.Embed({
     3468            controller: this,
     3469            model:      this.state()
     3470        }).render();
     3471
     3472        this.content.set( view );
     3473
     3474        if ( ! wp.media.isTouchDevice ) {
     3475            view.url.focus();
     3476        }
     3477    },
     3478
     3479    editSelectionContent: function() {
     3480        var state = this.state(),
     3481            selection = state.get('selection'),
     3482            view;
     3483
     3484        view = new wp.media.view.AttachmentsBrowser({
     3485            controller: this,
     3486            collection: selection,
     3487            selection:  selection,
     3488            model:      state,
     3489            sortable:   true,
     3490            search:     false,
     3491            date:       false,
     3492            dragInfo:   true,
     3493
     3494            AttachmentView: wp.media.view.Attachments.EditSelection
     3495        }).render();
     3496
     3497        view.toolbar.set( 'backToLibrary', {
     3498            text:     l10n.returnToLibrary,
     3499            priority: -100,
     3500
     3501            click: function() {
     3502                this.controller.content.mode('browse');
     3503            }
     3504        });
     3505
     3506        // Browse our library of attachments.
     3507        this.content.set( view );
     3508
     3509        // Trigger the controller to set focus
     3510        this.trigger( 'edit:selection', this );
     3511    },
     3512
     3513    editImageContent: function() {
     3514        var image = this.state().get('image'),
     3515            view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
     3516
     3517        this.content.set( view );
     3518
     3519        // after creating the wrapper view, load the actual editor via an ajax call
     3520        view.loadEditor();
     3521
     3522    },
     3523
     3524    // Toolbars
     3525
     3526    /**
     3527     * @param {wp.Backbone.View} view
     3528     */
     3529    selectionStatusToolbar: function( view ) {
     3530        var editable = this.state().get('editable');
     3531
     3532        view.set( 'selection', new wp.media.view.Selection({
     3533            controller: this,
     3534            collection: this.state().get('selection'),
     3535            priority:   -40,
     3536
     3537            // If the selection is editable, pass the callback to
     3538            // switch the content mode.
     3539            editable: editable && function() {
     3540                this.controller.content.mode('edit-selection');
     3541            }
     3542        }).render() );
     3543    },
     3544
     3545    /**
     3546     * @param {wp.Backbone.View} view
     3547     */
     3548    mainInsertToolbar: function( view ) {
     3549        var controller = this;
     3550
     3551        this.selectionStatusToolbar( view );
     3552
     3553        view.set( 'insert', {
     3554            style:    'primary',
     3555            priority: 80,
     3556            text:     l10n.insertIntoPost,
     3557            requires: { selection: true },
     3558
     3559            /**
     3560             * @fires wp.media.controller.State#insert
     3561             */
     3562            click: function() {
     3563                var state = controller.state(),
     3564                    selection = state.get('selection');
     3565
     3566                controller.close();
     3567                state.trigger( 'insert', selection ).reset();
     3568            }
     3569        });
     3570    },
     3571
     3572    /**
     3573     * @param {wp.Backbone.View} view
     3574     */
     3575    mainGalleryToolbar: function( view ) {
     3576        var controller = this;
     3577
     3578        this.selectionStatusToolbar( view );
     3579
     3580        view.set( 'gallery', {
     3581            style:    'primary',
     3582            text:     l10n.createNewGallery,
     3583            priority: 60,
     3584            requires: { selection: true },
     3585
     3586            click: function() {
     3587                var selection = controller.state().get('selection'),
     3588                    edit = controller.state('gallery-edit'),
     3589                    models = selection.where({ type: 'image' });
     3590
     3591                edit.set( 'library', new wp.media.model.Selection( models, {
     3592                    props:    selection.props.toJSON(),
     3593                    multiple: true
     3594                }) );
     3595
     3596                this.controller.setState('gallery-edit');
     3597
     3598                // Keep focus inside media modal
     3599                // after jumping to gallery view
     3600                this.controller.modal.focusManager.focus();
     3601            }
     3602        });
     3603    },
     3604
     3605    mainPlaylistToolbar: function( view ) {
     3606        var controller = this;
     3607
     3608        this.selectionStatusToolbar( view );
     3609
     3610        view.set( 'playlist', {
     3611            style:    'primary',
     3612            text:     l10n.createNewPlaylist,
     3613            priority: 100,
     3614            requires: { selection: true },
     3615
     3616            click: function() {
     3617                var selection = controller.state().get('selection'),
     3618                    edit = controller.state('playlist-edit'),
     3619                    models = selection.where({ type: 'audio' });
     3620
     3621                edit.set( 'library', new wp.media.model.Selection( models, {
     3622                    props:    selection.props.toJSON(),
     3623                    multiple: true
     3624                }) );
     3625
     3626                this.controller.setState('playlist-edit');
     3627
     3628                // Keep focus inside media modal
     3629                // after jumping to playlist view
     3630                this.controller.modal.focusManager.focus();
     3631            }
     3632        });
     3633    },
     3634
     3635    mainVideoPlaylistToolbar: function( view ) {
     3636        var controller = this;
     3637
     3638        this.selectionStatusToolbar( view );
     3639
     3640        view.set( 'video-playlist', {
     3641            style:    'primary',
     3642            text:     l10n.createNewVideoPlaylist,
     3643            priority: 100,
     3644            requires: { selection: true },
     3645
     3646            click: function() {
     3647                var selection = controller.state().get('selection'),
     3648                    edit = controller.state('video-playlist-edit'),
     3649                    models = selection.where({ type: 'video' });
     3650
     3651                edit.set( 'library', new wp.media.model.Selection( models, {
     3652                    props:    selection.props.toJSON(),
     3653                    multiple: true
     3654                }) );
     3655
     3656                this.controller.setState('video-playlist-edit');
     3657
     3658                // Keep focus inside media modal
     3659                // after jumping to video playlist view
     3660                this.controller.modal.focusManager.focus();
     3661            }
     3662        });
     3663    },
     3664
     3665    featuredImageToolbar: function( toolbar ) {
     3666        this.createSelectToolbar( toolbar, {
     3667            text:  l10n.setFeaturedImage,
     3668            state: this.options.state
     3669        });
     3670    },
     3671
     3672    mainEmbedToolbar: function( toolbar ) {
     3673        toolbar.view = new wp.media.view.Toolbar.Embed({
     3674            controller: this
     3675        });
     3676    },
     3677
     3678    galleryEditToolbar: function() {
     3679        var editing = this.state().get('editing');
     3680        this.toolbar.set( new wp.media.view.Toolbar({
     3681            controller: this,
     3682            items: {
     3683                insert: {
     3684                    style:    'primary',
     3685                    text:     editing ? l10n.updateGallery : l10n.insertGallery,
     3686                    priority: 80,
     3687                    requires: { library: true },
     3688
     3689                    /**
     3690                     * @fires wp.media.controller.State#update
     3691                     */
     3692                    click: function() {
     3693                        var controller = this.controller,
     3694                            state = controller.state();
     3695
     3696                        controller.close();
     3697                        state.trigger( 'update', state.get('library') );
     3698
     3699                        // Restore and reset the default state.
     3700                        controller.setState( controller.options.state );
     3701                        controller.reset();
     3702                    }
     3703                }
     3704            }
     3705        }) );
     3706    },
     3707
     3708    galleryAddToolbar: function() {
     3709        this.toolbar.set( new wp.media.view.Toolbar({
     3710            controller: this,
     3711            items: {
     3712                insert: {
     3713                    style:    'primary',
     3714                    text:     l10n.addToGallery,
     3715                    priority: 80,
     3716                    requires: { selection: true },
     3717
     3718                    /**
     3719                     * @fires wp.media.controller.State#reset
     3720                     */
     3721                    click: function() {
     3722                        var controller = this.controller,
     3723                            state = controller.state(),
     3724                            edit = controller.state('gallery-edit');
     3725
     3726                        edit.get('library').add( state.get('selection').models );
     3727                        state.trigger('reset');
     3728                        controller.setState('gallery-edit');
     3729                    }
     3730                }
     3731            }
     3732        }) );
     3733    },
     3734
     3735    playlistEditToolbar: function() {
     3736        var editing = this.state().get('editing');
     3737        this.toolbar.set( new wp.media.view.Toolbar({
     3738            controller: this,
     3739            items: {
     3740                insert: {
     3741                    style:    'primary',
     3742                    text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
     3743                    priority: 80,
     3744                    requires: { library: true },
     3745
     3746                    /**
     3747                     * @fires wp.media.controller.State#update
     3748                     */
     3749                    click: function() {
     3750                        var controller = this.controller,
     3751                            state = controller.state();
     3752
     3753                        controller.close();
     3754                        state.trigger( 'update', state.get('library') );
     3755
     3756                        // Restore and reset the default state.
     3757                        controller.setState( controller.options.state );
     3758                        controller.reset();
     3759                    }
     3760                }
     3761            }
     3762        }) );
     3763    },
     3764
     3765    playlistAddToolbar: function() {
     3766        this.toolbar.set( new wp.media.view.Toolbar({
     3767            controller: this,
     3768            items: {
     3769                insert: {
     3770                    style:    'primary',
     3771                    text:     l10n.addToPlaylist,
     3772                    priority: 80,
     3773                    requires: { selection: true },
     3774
     3775                    /**
     3776                     * @fires wp.media.controller.State#reset
     3777                     */
     3778                    click: function() {
     3779                        var controller = this.controller,
     3780                            state = controller.state(),
     3781                            edit = controller.state('playlist-edit');
     3782
     3783                        edit.get('library').add( state.get('selection').models );
     3784                        state.trigger('reset');
     3785                        controller.setState('playlist-edit');
     3786                    }
     3787                }
     3788            }
     3789        }) );
     3790    },
     3791
     3792    videoPlaylistEditToolbar: function() {
     3793        var editing = this.state().get('editing');
     3794        this.toolbar.set( new wp.media.view.Toolbar({
     3795            controller: this,
     3796            items: {
     3797                insert: {
     3798                    style:    'primary',
     3799                    text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
     3800                    priority: 140,
     3801                    requires: { library: true },
     3802
     3803                    click: function() {
     3804                        var controller = this.controller,
     3805                            state = controller.state(),
     3806                            library = state.get('library');
     3807
     3808                        library.type = 'video';
     3809
     3810                        controller.close();
     3811                        state.trigger( 'update', library );
     3812
     3813                        // Restore and reset the default state.
     3814                        controller.setState( controller.options.state );
     3815                        controller.reset();
     3816                    }
     3817                }
     3818            }
     3819        }) );
     3820    },
     3821
     3822    videoPlaylistAddToolbar: function() {
     3823        this.toolbar.set( new wp.media.view.Toolbar({
     3824            controller: this,
     3825            items: {
     3826                insert: {
     3827                    style:    'primary',
     3828                    text:     l10n.addToVideoPlaylist,
     3829                    priority: 140,
     3830                    requires: { selection: true },
     3831
     3832                    click: function() {
     3833                        var controller = this.controller,
     3834                            state = controller.state(),
     3835                            edit = controller.state('video-playlist-edit');
     3836
     3837                        edit.get('library').add( state.get('selection').models );
     3838                        state.trigger('reset');
     3839                        controller.setState('video-playlist-edit');
     3840                    }
     3841                }
     3842            }
     3843        }) );
     3844    }
    20833845});
    20843846
    2085 module.exports = State;
    2086 
    2087 },{}],18:[function(require,module,exports){
     3847module.exports = Post;
     3848
     3849
     3850/***/ }),
     3851/* 50 */
     3852/***/ (function(module, exports) {
     3853
    20883854/**
    2089  * wp.media.selectionSync
    2090  *
    2091  * Sync an attachments selection in a state with another state.
    2092  *
    2093  * Allows for selecting multiple images in the Insert Media workflow, and then
    2094  * switching to the Insert Gallery workflow while preserving the attachments selection.
    2095  *
    2096  * @mixin
     3855 * wp.media.view.MediaFrame.ImageDetails
     3856 *
     3857 * A media frame for manipulating an image that's already been inserted
     3858 * into a post.
     3859 *
     3860 * @class
     3861 * @augments wp.media.view.MediaFrame.Select
     3862 * @augments wp.media.view.MediaFrame
     3863 * @augments wp.media.view.Frame
     3864 * @augments wp.media.View
     3865 * @augments wp.Backbone.View
     3866 * @augments Backbone.View
     3867 * @mixes wp.media.controller.StateMachine
    20973868 */
    2098 var selectionSync = {
    2099     /**
    2100      * @since 3.5.0
    2101      */
    2102     syncSelection: function() {
    2103         var selection = this.get('selection'),
    2104             manager = this.frame._selection;
    2105 
    2106         if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     3869var Select = wp.media.view.MediaFrame.Select,
     3870    l10n = wp.media.view.l10n,
     3871    ImageDetails;
     3872
     3873ImageDetails = Select.extend({
     3874    defaults: {
     3875        id:      'image',
     3876        url:     '',
     3877        menu:    'image-details',
     3878        content: 'image-details',
     3879        toolbar: 'image-details',
     3880        type:    'link',
     3881        title:    l10n.imageDetailsTitle,
     3882        priority: 120
     3883    },
     3884
     3885    initialize: function( options ) {
     3886        this.image = new wp.media.model.PostImage( options.metadata );
     3887        this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
     3888        Select.prototype.initialize.apply( this, arguments );
     3889    },
     3890
     3891    bindHandlers: function() {
     3892        Select.prototype.bindHandlers.apply( this, arguments );
     3893        this.on( 'menu:create:image-details', this.createMenu, this );
     3894        this.on( 'content:create:image-details', this.imageDetailsContent, this );
     3895        this.on( 'content:render:edit-image', this.editImageContent, this );
     3896        this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
     3897        // override the select toolbar
     3898        this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
     3899    },
     3900
     3901    createStates: function() {
     3902        this.states.add([
     3903            new wp.media.controller.ImageDetails({
     3904                image: this.image,
     3905                editable: false
     3906            }),
     3907            new wp.media.controller.ReplaceImage({
     3908                id: 'replace-image',
     3909                library: wp.media.query( { type: 'image' } ),
     3910                image: this.image,
     3911                multiple:  false,
     3912                title:     l10n.imageReplaceTitle,
     3913                toolbar: 'replace',
     3914                priority:  80,
     3915                displaySettings: true
     3916            }),
     3917            new wp.media.controller.EditImage( {
     3918                image: this.image,
     3919                selection: this.options.selection
     3920            } )
     3921        ]);
     3922    },
     3923
     3924    imageDetailsContent: function( options ) {
     3925        options.view = new wp.media.view.ImageDetails({
     3926            controller: this,
     3927            model: this.state().image,
     3928            attachment: this.state().image.attachment
     3929        });
     3930    },
     3931
     3932    editImageContent: function() {
     3933        var state = this.state(),
     3934            model = state.get('image'),
     3935            view;
     3936
     3937        if ( ! model ) {
    21073938            return;
    21083939        }
    21093940
    2110         // If the selection supports multiple items, validate the stored
    2111         // attachments based on the new selection's conditions. Record
    2112         // the attachments that are not included; we'll maintain a
    2113         // reference to those. Other attachments are considered in flux.
    2114         if ( selection.multiple ) {
    2115             selection.reset( [], { silent: true });
    2116             selection.validateAll( manager.attachments );
    2117             manager.difference = _.difference( manager.attachments.models, selection.models );
    2118         }
    2119 
    2120         // Sync the selection's single item with the master.
    2121         selection.single( manager.single );
    2122     },
    2123 
    2124     /**
    2125      * Record the currently active attachments, which is a combination
    2126      * of the selection's attachments and the set of selected
    2127      * attachments that this specific selection considered invalid.
    2128      * Reset the difference and record the single attachment.
     3941        view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
     3942
     3943        this.content.set( view );
     3944
     3945        // after bringing in the frame, load the actual editor via an ajax call
     3946        view.loadEditor();
     3947
     3948    },
     3949
     3950    renderImageDetailsToolbar: function() {
     3951        this.toolbar.set( new wp.media.view.Toolbar({
     3952            controller: this,
     3953            items: {
     3954                select: {
     3955                    style:    'primary',
     3956                    text:     l10n.update,
     3957                    priority: 80,
     3958
     3959                    click: function() {
     3960                        var controller = this.controller,
     3961                            state = controller.state();
     3962
     3963                        controller.close();
     3964
     3965                        // not sure if we want to use wp.media.string.image which will create a shortcode or
     3966                        // perhaps wp.html.string to at least to build the <img />
     3967                        state.trigger( 'update', controller.image.toJSON() );
     3968
     3969                        // Restore and reset the default state.
     3970                        controller.setState( controller.options.state );
     3971                        controller.reset();
     3972                    }
     3973                }
     3974            }
     3975        }) );
     3976    },
     3977
     3978    renderReplaceImageToolbar: function() {
     3979        var frame = this,
     3980            lastState = frame.lastState(),
     3981            previous = lastState && lastState.id;
     3982
     3983        this.toolbar.set( new wp.media.view.Toolbar({
     3984            controller: this,
     3985            items: {
     3986                back: {
     3987                    text:     l10n.back,
     3988                    priority: 20,
     3989                    click:    function() {
     3990                        if ( previous ) {
     3991                            frame.setState( previous );
     3992                        } else {
     3993                            frame.close();
     3994                        }
     3995                    }
     3996                },
     3997
     3998                replace: {
     3999                    style:    'primary',
     4000                    text:     l10n.replace,
     4001                    priority: 80,
     4002
     4003                    click: function() {
     4004                        var controller = this.controller,
     4005                            state = controller.state(),
     4006                            selection = state.get( 'selection' ),
     4007                            attachment = selection.single();
     4008
     4009                        controller.close();
     4010
     4011                        controller.image.changeAttachment( attachment, state.display( attachment ) );
     4012
     4013                        // not sure if we want to use wp.media.string.image which will create a shortcode or
     4014                        // perhaps wp.html.string to at least to build the <img />
     4015                        state.trigger( 'replace', controller.image.toJSON() );
     4016
     4017                        // Restore and reset the default state.
     4018                        controller.setState( controller.options.state );
     4019                        controller.reset();
     4020                    }
     4021                }
     4022            }
     4023        }) );
     4024    }
     4025
     4026});
     4027
     4028module.exports = ImageDetails;
     4029
     4030
     4031/***/ }),
     4032/* 51 */
     4033/***/ (function(module, exports) {
     4034
     4035/**
     4036 * wp.media.view.Modal
     4037 *
     4038 * A modal view, which the media modal uses as its default container.
     4039 *
     4040 * @class
     4041 * @augments wp.media.View
     4042 * @augments wp.Backbone.View
     4043 * @augments Backbone.View
     4044 */
     4045var $ = jQuery,
     4046    Modal;
     4047
     4048Modal = wp.media.View.extend({
     4049    tagName:  'div',
     4050    template: wp.template('media-modal'),
     4051
     4052    attributes: {
     4053        tabindex: 0
     4054    },
     4055
     4056    events: {
     4057        'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
     4058        'keydown': 'keydown'
     4059    },
     4060
     4061    initialize: function() {
     4062        _.defaults( this.options, {
     4063            container: document.body,
     4064            title:     '',
     4065            propagate: true,
     4066            freeze:    true
     4067        });
     4068
     4069        this.focusManager = new wp.media.view.FocusManager({
     4070            el: this.el
     4071        });
     4072    },
     4073    /**
     4074     * @returns {Object}
     4075     */
     4076    prepare: function() {
     4077        return {
     4078            title: this.options.title
     4079        };
     4080    },
     4081
     4082    /**
     4083     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4084     */
     4085    attach: function() {
     4086        if ( this.views.attached ) {
     4087            return this;
     4088        }
     4089
     4090        if ( ! this.views.rendered ) {
     4091            this.render();
     4092        }
     4093
     4094        this.$el.appendTo( this.options.container );
     4095
     4096        // Manually mark the view as attached and trigger ready.
     4097        this.views.attached = true;
     4098        this.views.ready();
     4099
     4100        return this.propagate('attach');
     4101    },
     4102
     4103    /**
     4104     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4105     */
     4106    detach: function() {
     4107        if ( this.$el.is(':visible') ) {
     4108            this.close();
     4109        }
     4110
     4111        this.$el.detach();
     4112        this.views.attached = false;
     4113        return this.propagate('detach');
     4114    },
     4115
     4116    /**
     4117     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4118     */
     4119    open: function() {
     4120        var $el = this.$el,
     4121            options = this.options,
     4122            mceEditor;
     4123
     4124        if ( $el.is(':visible') ) {
     4125            return this;
     4126        }
     4127
     4128        if ( ! this.views.attached ) {
     4129            this.attach();
     4130        }
     4131
     4132        // If the `freeze` option is set, record the window's scroll position.
     4133        if ( options.freeze ) {
     4134            this._freeze = {
     4135                scrollTop: $( window ).scrollTop()
     4136            };
     4137        }
     4138
     4139        // Disable page scrolling.
     4140        $( 'body' ).addClass( 'modal-open' );
     4141
     4142        $el.show();
     4143
     4144        // Try to close the onscreen keyboard
     4145        if ( 'ontouchend' in document ) {
     4146            if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
     4147                mceEditor.iframeElement.focus();
     4148                mceEditor.iframeElement.blur();
     4149
     4150                setTimeout( function() {
     4151                    mceEditor.iframeElement.blur();
     4152                }, 100 );
     4153            }
     4154        }
     4155
     4156        this.$el.focus();
     4157
     4158        return this.propagate('open');
     4159    },
     4160
     4161    /**
     4162     * @param {Object} options
     4163     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4164     */
     4165    close: function( options ) {
     4166        var freeze = this._freeze;
     4167
     4168        if ( ! this.views.attached || ! this.$el.is(':visible') ) {
     4169            return this;
     4170        }
     4171
     4172        // Enable page scrolling.
     4173        $( 'body' ).removeClass( 'modal-open' );
     4174
     4175        // Hide modal and remove restricted media modal tab focus once it's closed
     4176        this.$el.hide().undelegate( 'keydown' );
     4177
     4178        // Put focus back in useful location once modal is closed
     4179        $('#wpbody-content').focus();
     4180
     4181        this.propagate('close');
     4182
     4183        // If the `freeze` option is set, restore the container's scroll position.
     4184        if ( freeze ) {
     4185            $( window ).scrollTop( freeze.scrollTop );
     4186        }
     4187
     4188        if ( options && options.escape ) {
     4189            this.propagate('escape');
     4190        }
     4191
     4192        return this;
     4193    },
     4194    /**
     4195     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4196     */
     4197    escape: function() {
     4198        return this.close({ escape: true });
     4199    },
     4200    /**
     4201     * @param {Object} event
     4202     */
     4203    escapeHandler: function( event ) {
     4204        event.preventDefault();
     4205        this.escape();
     4206    },
     4207
     4208    /**
     4209     * @param {Array|Object} content Views to register to '.media-modal-content'
     4210     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4211     */
     4212    content: function( content ) {
     4213        this.views.set( '.media-modal-content', content );
     4214        return this;
     4215    },
     4216
     4217    /**
     4218     * Triggers a modal event and if the `propagate` option is set,
     4219     * forwards events to the modal's controller.
    21294220     *
    2130      * @since 3.5.0
    2131      */
    2132     recordSelection: function() {
    2133         var selection = this.get('selection'),
    2134             manager = this.frame._selection;
    2135 
    2136         if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     4221     * @param {string} id
     4222     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4223     */
     4224    propagate: function( id ) {
     4225        this.trigger( id );
     4226
     4227        if ( this.options.propagate ) {
     4228            this.controller.trigger( id );
     4229        }
     4230
     4231        return this;
     4232    },
     4233    /**
     4234     * @param {Object} event
     4235     */
     4236    keydown: function( event ) {
     4237        // Close the modal when escape is pressed.
     4238        if ( 27 === event.which && this.$el.is(':visible') ) {
     4239            this.escape();
     4240            event.stopImmediatePropagation();
     4241        }
     4242    }
     4243});
     4244
     4245module.exports = Modal;
     4246
     4247
     4248/***/ }),
     4249/* 52 */
     4250/***/ (function(module, exports) {
     4251
     4252/**
     4253 * wp.media.view.FocusManager
     4254 *
     4255 * @class
     4256 * @augments wp.media.View
     4257 * @augments wp.Backbone.View
     4258 * @augments Backbone.View
     4259 */
     4260var FocusManager = wp.media.View.extend({
     4261
     4262    events: {
     4263        'keydown': 'constrainTabbing'
     4264    },
     4265
     4266    focus: function() { // Reset focus on first left menu item
     4267        this.$('.media-menu-item').first().focus();
     4268    },
     4269    /**
     4270     * @param {Object} event
     4271     */
     4272    constrainTabbing: function( event ) {
     4273        var tabbables;
     4274
     4275        // Look for the tab key.
     4276        if ( 9 !== event.keyCode ) {
    21374277            return;
    21384278        }
    21394279
    2140         if ( selection.multiple ) {
    2141             manager.attachments.reset( selection.toArray().concat( manager.difference ) );
    2142             manager.difference = [];
    2143         } else {
    2144             manager.attachments.add( selection.toArray() );
    2145         }
    2146 
    2147         manager.single = selection._single;
     4280        // Skip the file input added by Plupload.
     4281        tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
     4282
     4283        // Keep tab focus within media modal while it's open
     4284        if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
     4285            tabbables.first().focus();
     4286            return false;
     4287        } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
     4288            tabbables.last().focus();
     4289            return false;
     4290        }
    21484291    }
    2149 };
    2150 
    2151 module.exports = selectionSync;
    2152 
    2153 },{}],19:[function(require,module,exports){
    2154 var media = wp.media,
    2155     $ = jQuery,
    2156     l10n;
    2157 
    2158 media.isTouchDevice = ( 'ontouchend' in document );
    2159 
    2160 // Link any localized strings.
    2161 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
    2162 
    2163 // Link any settings.
    2164 media.view.settings = l10n.settings || {};
    2165 delete l10n.settings;
    2166 
    2167 // Copy the `post` setting over to the model settings.
    2168 media.model.settings.post = media.view.settings.post;
    2169 
    2170 // Check if the browser supports CSS 3.0 transitions
    2171 $.support.transition = (function(){
    2172     var style = document.documentElement.style,
    2173         transitions = {
    2174             WebkitTransition: 'webkitTransitionEnd',
    2175             MozTransition:    'transitionend',
    2176             OTransition:      'oTransitionEnd otransitionend',
    2177             transition:       'transitionend'
    2178         }, transition;
    2179 
    2180     transition = _.find( _.keys( transitions ), function( transition ) {
    2181         return ! _.isUndefined( style[ transition ] );
    2182     });
    2183 
    2184     return transition && {
    2185         end: transitions[ transition ]
    2186     };
    2187 }());
     4292
     4293});
     4294
     4295module.exports = FocusManager;
     4296
     4297
     4298/***/ }),
     4299/* 53 */
     4300/***/ (function(module, exports) {
    21884301
    21894302/**
    2190  * A shared event bus used to provide events into
    2191  * the media workflows that 3rd-party devs can use to hook
    2192  * in.
     4303 * wp.media.view.UploaderWindow
     4304 *
     4305 * An uploader window that allows for dragging and dropping media.
     4306 *
     4307 * @class
     4308 * @augments wp.media.View
     4309 * @augments wp.Backbone.View
     4310 * @augments Backbone.View
     4311 *
     4312 * @param {object} [options]                   Options hash passed to the view.
     4313 * @param {object} [options.uploader]          Uploader properties.
     4314 * @param {jQuery} [options.uploader.browser]
     4315 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
     4316 * @param {object} [options.uploader.params]
    21934317 */
    2194 media.events = _.extend( {}, Backbone.Events );
     4318var $ = jQuery,
     4319    UploaderWindow;
     4320
     4321UploaderWindow = wp.media.View.extend({
     4322    tagName:   'div',
     4323    className: 'uploader-window',
     4324    template:  wp.template('uploader-window'),
     4325
     4326    initialize: function() {
     4327        var uploader;
     4328
     4329        this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');
     4330
     4331        uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
     4332            dropzone:  this.$el,
     4333            browser:   this.$browser,
     4334            params:    {}
     4335        });
     4336
     4337        // Ensure the dropzone is a jQuery collection.
     4338        if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
     4339            uploader.dropzone = $( uploader.dropzone );
     4340        }
     4341
     4342        this.controller.on( 'activate', this.refresh, this );
     4343
     4344        this.controller.on( 'detach', function() {
     4345            this.$browser.remove();
     4346        }, this );
     4347    },
     4348
     4349    refresh: function() {
     4350        if ( this.uploader ) {
     4351            this.uploader.refresh();
     4352        }
     4353    },
     4354
     4355    ready: function() {
     4356        var postId = wp.media.view.settings.post.id,
     4357            dropzone;
     4358
     4359        // If the uploader already exists, bail.
     4360        if ( this.uploader ) {
     4361            return;
     4362        }
     4363
     4364        if ( postId ) {
     4365            this.options.uploader.params.post_id = postId;
     4366        }
     4367        this.uploader = new wp.Uploader( this.options.uploader );
     4368
     4369        dropzone = this.uploader.dropzone;
     4370        dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
     4371        dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
     4372
     4373        $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
     4374    },
     4375
     4376    _ready: function() {
     4377        this.controller.trigger( 'uploader:ready' );
     4378    },
     4379
     4380    show: function() {
     4381        var $el = this.$el.show();
     4382
     4383        // Ensure that the animation is triggered by waiting until
     4384        // the transparent element is painted into the DOM.
     4385        _.defer( function() {
     4386            $el.css({ opacity: 1 });
     4387        });
     4388    },
     4389
     4390    hide: function() {
     4391        var $el = this.$el.css({ opacity: 0 });
     4392
     4393        wp.media.transition( $el ).done( function() {
     4394            // Transition end events are subject to race conditions.
     4395            // Make sure that the value is set as intended.
     4396            if ( '0' === $el.css('opacity') ) {
     4397                $el.hide();
     4398            }
     4399        });
     4400
     4401        // https://core.trac.wordpress.org/ticket/27341
     4402        _.delay( function() {
     4403            if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
     4404                $el.hide();
     4405            }
     4406        }, 500 );
     4407    }
     4408});
     4409
     4410module.exports = UploaderWindow;
     4411
     4412
     4413/***/ }),
     4414/* 54 */
     4415/***/ (function(module, exports) {
    21954416
    21964417/**
    2197  * Makes it easier to bind events using transitions.
    2198  *
    2199  * @param {string} selector
    2200  * @param {Number} sensitivity
    2201  * @returns {Promise}
    2202  */
    2203 media.transition = function( selector, sensitivity ) {
    2204     var deferred = $.Deferred();
    2205 
    2206     sensitivity = sensitivity || 2000;
    2207 
    2208     if ( $.support.transition ) {
    2209         if ( ! (selector instanceof $) ) {
    2210             selector = $( selector );
    2211         }
    2212 
    2213         // Resolve the deferred when the first element finishes animating.
    2214         selector.first().one( $.support.transition.end, deferred.resolve );
    2215 
    2216         // Just in case the event doesn't trigger, fire a callback.
    2217         _.delay( deferred.resolve, sensitivity );
    2218 
    2219     // Otherwise, execute on the spot.
    2220     } else {
    2221         deferred.resolve();
    2222     }
    2223 
    2224     return deferred.promise();
    2225 };
    2226 
    2227 media.controller.Region = require( './controllers/region.js' );
    2228 media.controller.StateMachine = require( './controllers/state-machine.js' );
    2229 media.controller.State = require( './controllers/state.js' );
    2230 
    2231 media.selectionSync = require( './utils/selection-sync.js' );
    2232 media.controller.Library = require( './controllers/library.js' );
    2233 media.controller.ImageDetails = require( './controllers/image-details.js' );
    2234 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
    2235 media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
    2236 media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
    2237 media.controller.CollectionAdd = require( './controllers/collection-add.js' );
    2238 media.controller.FeaturedImage = require( './controllers/featured-image.js' );
    2239 media.controller.ReplaceImage = require( './controllers/replace-image.js' );
    2240 media.controller.EditImage = require( './controllers/edit-image.js' );
    2241 media.controller.MediaLibrary = require( './controllers/media-library.js' );
    2242 media.controller.Embed = require( './controllers/embed.js' );
    2243 media.controller.Cropper = require( './controllers/cropper.js' );
    2244 media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' );
    2245 media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' );
    2246 
    2247 media.View = require( './views/view.js' );
    2248 media.view.Frame = require( './views/frame.js' );
    2249 media.view.MediaFrame = require( './views/media-frame.js' );
    2250 media.view.MediaFrame.Select = require( './views/frame/select.js' );
    2251 media.view.MediaFrame.Post = require( './views/frame/post.js' );
    2252 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
    2253 media.view.Modal = require( './views/modal.js' );
    2254 media.view.FocusManager = require( './views/focus-manager.js' );
    2255 media.view.UploaderWindow = require( './views/uploader/window.js' );
    2256 media.view.EditorUploader = require( './views/uploader/editor.js' );
    2257 media.view.UploaderInline = require( './views/uploader/inline.js' );
    2258 media.view.UploaderStatus = require( './views/uploader/status.js' );
    2259 media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
    2260 media.view.Toolbar = require( './views/toolbar.js' );
    2261 media.view.Toolbar.Select = require( './views/toolbar/select.js' );
    2262 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
    2263 media.view.Button = require( './views/button.js' );
    2264 media.view.ButtonGroup = require( './views/button-group.js' );
    2265 media.view.PriorityList = require( './views/priority-list.js' );
    2266 media.view.MenuItem = require( './views/menu-item.js' );
    2267 media.view.Menu = require( './views/menu.js' );
    2268 media.view.RouterItem = require( './views/router-item.js' );
    2269 media.view.Router = require( './views/router.js' );
    2270 media.view.Sidebar = require( './views/sidebar.js' );
    2271 media.view.Attachment = require( './views/attachment.js' );
    2272 media.view.Attachment.Library = require( './views/attachment/library.js' );
    2273 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
    2274 media.view.Attachments = require( './views/attachments.js' );
    2275 media.view.Search = require( './views/search.js' );
    2276 media.view.AttachmentFilters = require( './views/attachment-filters.js' );
    2277 media.view.DateFilter = require( './views/attachment-filters/date.js' );
    2278 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
    2279 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
    2280 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
    2281 media.view.Selection = require( './views/selection.js' );
    2282 media.view.Attachment.Selection = require( './views/attachment/selection.js' );
    2283 media.view.Attachments.Selection = require( './views/attachments/selection.js' );
    2284 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
    2285 media.view.Settings = require( './views/settings.js' );
    2286 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
    2287 media.view.Settings.Gallery = require( './views/settings/gallery.js' );
    2288 media.view.Settings.Playlist = require( './views/settings/playlist.js' );
    2289 media.view.Attachment.Details = require( './views/attachment/details.js' );
    2290 media.view.AttachmentCompat = require( './views/attachment-compat.js' );
    2291 media.view.Iframe = require( './views/iframe.js' );
    2292 media.view.Embed = require( './views/embed.js' );
    2293 media.view.Label = require( './views/label.js' );
    2294 media.view.EmbedUrl = require( './views/embed/url.js' );
    2295 media.view.EmbedLink = require( './views/embed/link.js' );
    2296 media.view.EmbedImage = require( './views/embed/image.js' );
    2297 media.view.ImageDetails = require( './views/image-details.js' );
    2298 media.view.Cropper = require( './views/cropper.js' );
    2299 media.view.SiteIconCropper = require( './views/site-icon-cropper.js' );
    2300 media.view.SiteIconPreview = require( './views/site-icon-preview.js' );
    2301 media.view.EditImage = require( './views/edit-image.js' );
    2302 media.view.Spinner = require( './views/spinner.js' );
    2303 
    2304 },{"./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){
    2305 /**
    2306  * wp.media.view.AttachmentCompat
    2307  *
    2308  * A view to display fields added via the `attachment_fields_to_edit` filter.
     4418 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
     4419 * and relays drag'n'dropped files to a media workflow.
     4420 *
     4421 * wp.media.view.EditorUploader
    23094422 *
    23104423 * @class
     
    23144427 */
    23154428var View = wp.media.View,
    2316     AttachmentCompat;
    2317 
    2318 AttachmentCompat = View.extend({
    2319     tagName:   'form',
    2320     className: 'compat-item',
     4429    l10n = wp.media.view.l10n,
     4430    $ = jQuery,
     4431    EditorUploader;
     4432
     4433EditorUploader = View.extend({
     4434    tagName:   'div',
     4435    className: 'uploader-editor',
     4436    template:  wp.template( 'uploader-editor' ),
     4437
     4438    localDrag: false,
     4439    overContainer: false,
     4440    overDropzone: false,
     4441    draggingFile: null,
     4442
     4443    /**
     4444     * Bind drag'n'drop events to callbacks.
     4445     */
     4446    initialize: function() {
     4447        this.initialized = false;
     4448
     4449        // Bail if not enabled or UA does not support drag'n'drop or File API.
     4450        if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
     4451            return this;
     4452        }
     4453
     4454        this.$document = $(document);
     4455        this.dropzones = [];
     4456        this.files = [];
     4457
     4458        this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
     4459        this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
     4460        this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
     4461        this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
     4462
     4463        this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
     4464        this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
     4465
     4466        this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
     4467            this.localDrag = event.type === 'dragstart';
     4468
     4469            if ( event.type === 'drop' ) {
     4470                this.containerDragleave();
     4471            }
     4472        }, this ) );
     4473
     4474        this.initialized = true;
     4475        return this;
     4476    },
     4477
     4478    /**
     4479     * Check browser support for drag'n'drop.
     4480     *
     4481     * @return Boolean
     4482     */
     4483    browserSupport: function() {
     4484        var supports = false, div = document.createElement('div');
     4485
     4486        supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
     4487        supports = supports && !! ( window.File && window.FileList && window.FileReader );
     4488        return supports;
     4489    },
     4490
     4491    isDraggingFile: function( event ) {
     4492        if ( this.draggingFile !== null ) {
     4493            return this.draggingFile;
     4494        }
     4495
     4496        if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
     4497            return false;
     4498        }
     4499
     4500        this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
     4501            _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
     4502
     4503        return this.draggingFile;
     4504    },
     4505
     4506    refresh: function( e ) {
     4507        var dropzone_id;
     4508        for ( dropzone_id in this.dropzones ) {
     4509            // Hide the dropzones only if dragging has left the screen.
     4510            this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
     4511        }
     4512
     4513        if ( ! _.isUndefined( e ) ) {
     4514            $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
     4515        }
     4516
     4517        if ( ! this.overContainer && ! this.overDropzone ) {
     4518            this.draggingFile = null;
     4519        }
     4520
     4521        return this;
     4522    },
     4523
     4524    render: function() {
     4525        if ( ! this.initialized ) {
     4526            return this;
     4527        }
     4528
     4529        View.prototype.render.apply( this, arguments );
     4530        $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
     4531        return this;
     4532    },
     4533
     4534    attach: function( index, editor ) {
     4535        // Attach a dropzone to an editor.
     4536        var dropzone = this.$el.clone();
     4537        this.dropzones.push( dropzone );
     4538        $( editor ).append( dropzone );
     4539        return this;
     4540    },
     4541
     4542    /**
     4543     * When a file is dropped on the editor uploader, open up an editor media workflow
     4544     * and upload the file immediately.
     4545     *
     4546     * @param  {jQuery.Event} event The 'drop' event.
     4547     */
     4548    drop: function( event ) {
     4549        var $wrap, uploadView;
     4550
     4551        this.containerDragleave( event );
     4552        this.dropzoneDragleave( event );
     4553
     4554        this.files = event.originalEvent.dataTransfer.files;
     4555        if ( this.files.length < 1 ) {
     4556            return;
     4557        }
     4558
     4559        // Set the active editor to the drop target.
     4560        $wrap = $( event.target ).parents( '.wp-editor-wrap' );
     4561        if ( $wrap.length > 0 && $wrap[0].id ) {
     4562            window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
     4563        }
     4564
     4565        if ( ! this.workflow ) {
     4566            this.workflow = wp.media.editor.open( window.wpActiveEditor, {
     4567                frame:    'post',
     4568                state:    'insert',
     4569                title:    l10n.addMedia,
     4570                multiple: true
     4571            });
     4572
     4573            uploadView = this.workflow.uploader;
     4574
     4575            if ( uploadView.uploader && uploadView.uploader.ready ) {
     4576                this.addFiles.apply( this );
     4577            } else {
     4578                this.workflow.on( 'uploader:ready', this.addFiles, this );
     4579            }
     4580        } else {
     4581            this.workflow.state().reset();
     4582            this.addFiles.apply( this );
     4583            this.workflow.open();
     4584        }
     4585
     4586        return false;
     4587    },
     4588
     4589    /**
     4590     * Add the files to the uploader.
     4591     */
     4592    addFiles: function() {
     4593        if ( this.files.length ) {
     4594            this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
     4595            this.files = [];
     4596        }
     4597        return this;
     4598    },
     4599
     4600    containerDragover: function( event ) {
     4601        if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     4602            return;
     4603        }
     4604
     4605        this.overContainer = true;
     4606        this.refresh();
     4607    },
     4608
     4609    containerDragleave: function() {
     4610        this.overContainer = false;
     4611
     4612        // Throttle dragleave because it's called when bouncing from some elements to others.
     4613        _.delay( _.bind( this.refresh, this ), 50 );
     4614    },
     4615
     4616    dropzoneDragover: function( event ) {
     4617        if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     4618            return;
     4619        }
     4620
     4621        this.overDropzone = true;
     4622        this.refresh( event );
     4623        return false;
     4624    },
     4625
     4626    dropzoneDragleave: function( e ) {
     4627        this.overDropzone = false;
     4628        _.delay( _.bind( this.refresh, this, e ), 50 );
     4629    },
     4630
     4631    click: function( e ) {
     4632        // In the rare case where the dropzone gets stuck, hide it on click.
     4633        this.containerDragleave( e );
     4634        this.dropzoneDragleave( e );
     4635        this.localDrag = false;
     4636    }
     4637});
     4638
     4639module.exports = EditorUploader;
     4640
     4641
     4642/***/ }),
     4643/* 55 */
     4644/***/ (function(module, exports) {
     4645
     4646/**
     4647 * wp.media.view.UploaderInline
     4648 *
     4649 * The inline uploader that shows up in the 'Upload Files' tab.
     4650 *
     4651 * @class
     4652 * @augments wp.media.View
     4653 * @augments wp.Backbone.View
     4654 * @augments Backbone.View
     4655 */
     4656var View = wp.media.View,
     4657    UploaderInline;
     4658
     4659UploaderInline = View.extend({
     4660    tagName:   'div',
     4661    className: 'uploader-inline',
     4662    template:  wp.template('uploader-inline'),
    23214663
    23224664    events: {
    2323         'submit':          'preventDefault',
    2324         'change input':    'save',
    2325         'change select':   'save',
    2326         'change textarea': 'save'
     4665        'click .close': 'hide'
    23274666    },
    23284667
    23294668    initialize: function() {
    2330         this.listenTo( this.model, 'change:compat', this.render );
    2331     },
    2332     /**
    2333      * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     4669        _.defaults( this.options, {
     4670            message: '',
     4671            status:  true,
     4672            canClose: false
     4673        });
     4674
     4675        if ( ! this.options.$browser && this.controller.uploader ) {
     4676            this.options.$browser = this.controller.uploader.$browser;
     4677        }
     4678
     4679        if ( _.isUndefined( this.options.postId ) ) {
     4680            this.options.postId = wp.media.view.settings.post.id;
     4681        }
     4682
     4683        if ( this.options.status ) {
     4684            this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
     4685                controller: this.controller
     4686            }) );
     4687        }
     4688    },
     4689
     4690    prepare: function() {
     4691        var suggestedWidth = this.controller.state().get('suggestedWidth'),
     4692            suggestedHeight = this.controller.state().get('suggestedHeight'),
     4693            data = {};
     4694
     4695        data.message = this.options.message;
     4696        data.canClose = this.options.canClose;
     4697
     4698        if ( suggestedWidth && suggestedHeight ) {
     4699            data.suggestedWidth = suggestedWidth;
     4700            data.suggestedHeight = suggestedHeight;
     4701        }
     4702
     4703        return data;
     4704    },
     4705    /**
     4706     * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    23344707     */
    23354708    dispose: function() {
    2336         if ( this.$(':focus').length ) {
    2337             this.save();
     4709        if ( this.disposing ) {
     4710            /**
     4711             * call 'dispose' directly on the parent class
     4712             */
     4713            return View.prototype.dispose.apply( this, arguments );
     4714        }
     4715
     4716        // Run remove on `dispose`, so we can be sure to refresh the
     4717        // uploader with a view-less DOM. Track whether we're disposing
     4718        // so we don't trigger an infinite loop.
     4719        this.disposing = true;
     4720        return this.remove();
     4721    },
     4722    /**
     4723     * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
     4724     */
     4725    remove: function() {
     4726        /**
     4727         * call 'remove' directly on the parent class
     4728         */
     4729        var result = View.prototype.remove.apply( this, arguments );
     4730
     4731        _.defer( _.bind( this.refresh, this ) );
     4732        return result;
     4733    },
     4734
     4735    refresh: function() {
     4736        var uploader = this.controller.uploader;
     4737
     4738        if ( uploader ) {
     4739            uploader.refresh();
     4740        }
     4741    },
     4742    /**
     4743     * @returns {wp.media.view.UploaderInline}
     4744     */
     4745    ready: function() {
     4746        var $browser = this.options.$browser,
     4747            $placeholder;
     4748
     4749        if ( this.controller.uploader ) {
     4750            $placeholder = this.$('.browser');
     4751
     4752            // Check if we've already replaced the placeholder.
     4753            if ( $placeholder[0] === $browser[0] ) {
     4754                return;
     4755            }
     4756
     4757            $browser.detach().text( $placeholder.text() );
     4758            $browser[0].className = $placeholder[0].className;
     4759            $placeholder.replaceWith( $browser.show() );
     4760        }
     4761
     4762        this.refresh();
     4763        return this;
     4764    },
     4765    show: function() {
     4766        this.$el.removeClass( 'hidden' );
     4767    },
     4768    hide: function() {
     4769        this.$el.addClass( 'hidden' );
     4770    }
     4771
     4772});
     4773
     4774module.exports = UploaderInline;
     4775
     4776
     4777/***/ }),
     4778/* 56 */
     4779/***/ (function(module, exports) {
     4780
     4781/**
     4782 * wp.media.view.UploaderStatus
     4783 *
     4784 * An uploader status for on-going uploads.
     4785 *
     4786 * @class
     4787 * @augments wp.media.View
     4788 * @augments wp.Backbone.View
     4789 * @augments Backbone.View
     4790 */
     4791var View = wp.media.View,
     4792    UploaderStatus;
     4793
     4794UploaderStatus = View.extend({
     4795    className: 'media-uploader-status',
     4796    template:  wp.template('uploader-status'),
     4797
     4798    events: {
     4799        'click .upload-dismiss-errors': 'dismiss'
     4800    },
     4801
     4802    initialize: function() {
     4803        this.queue = wp.Uploader.queue;
     4804        this.queue.on( 'add remove reset', this.visibility, this );
     4805        this.queue.on( 'add remove reset change:percent', this.progress, this );
     4806        this.queue.on( 'add remove reset change:uploading', this.info, this );
     4807
     4808        this.errors = wp.Uploader.errors;
     4809        this.errors.reset();
     4810        this.errors.on( 'add remove reset', this.visibility, this );
     4811        this.errors.on( 'add', this.error, this );
     4812    },
     4813    /**
     4814     * @global wp.Uploader
     4815     * @returns {wp.media.view.UploaderStatus}
     4816     */
     4817    dispose: function() {
     4818        wp.Uploader.queue.off( null, null, this );
     4819        /**
     4820         * call 'dispose' directly on the parent class
     4821         */
     4822        View.prototype.dispose.apply( this, arguments );
     4823        return this;
     4824    },
     4825
     4826    visibility: function() {
     4827        this.$el.toggleClass( 'uploading', !! this.queue.length );
     4828        this.$el.toggleClass( 'errors', !! this.errors.length );
     4829        this.$el.toggle( !! this.queue.length || !! this.errors.length );
     4830    },
     4831
     4832    ready: function() {
     4833        _.each({
     4834            '$bar':      '.media-progress-bar div',
     4835            '$index':    '.upload-index',
     4836            '$total':    '.upload-total',
     4837            '$filename': '.upload-filename'
     4838        }, function( selector, key ) {
     4839            this[ key ] = this.$( selector );
     4840        }, this );
     4841
     4842        this.visibility();
     4843        this.progress();
     4844        this.info();
     4845    },
     4846
     4847    progress: function() {
     4848        var queue = this.queue,
     4849            $bar = this.$bar;
     4850
     4851        if ( ! $bar || ! queue.length ) {
     4852            return;
     4853        }
     4854
     4855        $bar.width( ( queue.reduce( function( memo, attachment ) {
     4856            if ( ! attachment.get('uploading') ) {
     4857                return memo + 100;
     4858            }
     4859
     4860            var percent = attachment.get('percent');
     4861            return memo + ( _.isNumber( percent ) ? percent : 100 );
     4862        }, 0 ) / queue.length ) + '%' );
     4863    },
     4864
     4865    info: function() {
     4866        var queue = this.queue,
     4867            index = 0, active;
     4868
     4869        if ( ! queue.length ) {
     4870            return;
     4871        }
     4872
     4873        active = this.queue.find( function( attachment, i ) {
     4874            index = i;
     4875            return attachment.get('uploading');
     4876        });
     4877
     4878        this.$index.text( index + 1 );
     4879        this.$total.text( queue.length );
     4880        this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
     4881    },
     4882    /**
     4883     * @param {string} filename
     4884     * @returns {string}
     4885     */
     4886    filename: function( filename ) {
     4887        return _.escape( filename );
     4888    },
     4889    /**
     4890     * @param {Backbone.Model} error
     4891     */
     4892    error: function( error ) {
     4893        this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     4894            filename: this.filename( error.get('file').name ),
     4895            message:  error.get('message')
     4896        }), { at: 0 });
     4897    },
     4898
     4899    /**
     4900     * @global wp.Uploader
     4901     *
     4902     * @param {Object} event
     4903     */
     4904    dismiss: function( event ) {
     4905        var errors = this.views.get('.upload-errors');
     4906
     4907        event.preventDefault();
     4908
     4909        if ( errors ) {
     4910            _.invoke( errors, 'remove' );
     4911        }
     4912        wp.Uploader.errors.reset();
     4913    }
     4914});
     4915
     4916module.exports = UploaderStatus;
     4917
     4918
     4919/***/ }),
     4920/* 57 */
     4921/***/ (function(module, exports) {
     4922
     4923/**
     4924 * wp.media.view.UploaderStatusError
     4925 *
     4926 * @class
     4927 * @augments wp.media.View
     4928 * @augments wp.Backbone.View
     4929 * @augments Backbone.View
     4930 */
     4931var UploaderStatusError = wp.media.View.extend({
     4932    className: 'upload-error',
     4933    template:  wp.template('uploader-status-error')
     4934});
     4935
     4936module.exports = UploaderStatusError;
     4937
     4938
     4939/***/ }),
     4940/* 58 */
     4941/***/ (function(module, exports) {
     4942
     4943/**
     4944 * wp.media.view.Toolbar
     4945 *
     4946 * A toolbar which consists of a primary and a secondary section. Each sections
     4947 * can be filled with views.
     4948 *
     4949 * @class
     4950 * @augments wp.media.View
     4951 * @augments wp.Backbone.View
     4952 * @augments Backbone.View
     4953 */
     4954var View = wp.media.View,
     4955    Toolbar;
     4956
     4957Toolbar = View.extend({
     4958    tagName:   'div',
     4959    className: 'media-toolbar',
     4960
     4961    initialize: function() {
     4962        var state = this.controller.state(),
     4963            selection = this.selection = state.get('selection'),
     4964            library = this.library = state.get('library');
     4965
     4966        this._views = {};
     4967
     4968        // The toolbar is composed of two `PriorityList` views.
     4969        this.primary   = new wp.media.view.PriorityList();
     4970        this.secondary = new wp.media.view.PriorityList();
     4971        this.primary.$el.addClass('media-toolbar-primary search-form');
     4972        this.secondary.$el.addClass('media-toolbar-secondary');
     4973
     4974        this.views.set([ this.secondary, this.primary ]);
     4975
     4976        if ( this.options.items ) {
     4977            this.set( this.options.items, { silent: true });
     4978        }
     4979
     4980        if ( ! this.options.silent ) {
     4981            this.render();
     4982        }
     4983
     4984        if ( selection ) {
     4985            selection.on( 'add remove reset', this.refresh, this );
     4986        }
     4987
     4988        if ( library ) {
     4989            library.on( 'add remove reset', this.refresh, this );
     4990        }
     4991    },
     4992    /**
     4993     * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
     4994     */
     4995    dispose: function() {
     4996        if ( this.selection ) {
     4997            this.selection.off( null, null, this );
     4998        }
     4999
     5000        if ( this.library ) {
     5001            this.library.off( null, null, this );
    23385002        }
    23395003        /**
     
    23425006        return View.prototype.dispose.apply( this, arguments );
    23435007    },
    2344     /**
    2345      * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     5008
     5009    ready: function() {
     5010        this.refresh();
     5011    },
     5012
     5013    /**
     5014     * @param {string} id
     5015     * @param {Backbone.View|Object} view
     5016     * @param {Object} [options={}]
     5017     * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     5018     */
     5019    set: function( id, view, options ) {
     5020        var list;
     5021        options = options || {};
     5022
     5023        // Accept an object with an `id` : `view` mapping.
     5024        if ( _.isObject( id ) ) {
     5025            _.each( id, function( view, id ) {
     5026                this.set( id, view, { silent: true });
     5027            }, this );
     5028
     5029        } else {
     5030            if ( ! ( view instanceof Backbone.View ) ) {
     5031                view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
     5032                view = new wp.media.view.Button( view ).render();
     5033            }
     5034
     5035            view.controller = view.controller || this.controller;
     5036
     5037            this._views[ id ] = view;
     5038
     5039            list = view.options.priority < 0 ? 'secondary' : 'primary';
     5040            this[ list ].set( id, view, options );
     5041        }
     5042
     5043        if ( ! options.silent ) {
     5044            this.refresh();
     5045        }
     5046
     5047        return this;
     5048    },
     5049    /**
     5050     * @param {string} id
     5051     * @returns {wp.media.view.Button}
     5052     */
     5053    get: function( id ) {
     5054        return this._views[ id ];
     5055    },
     5056    /**
     5057     * @param {string} id
     5058     * @param {Object} options
     5059     * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     5060     */
     5061    unset: function( id, options ) {
     5062        delete this._views[ id ];
     5063        this.primary.unset( id, options );
     5064        this.secondary.unset( id, options );
     5065
     5066        if ( ! options || ! options.silent ) {
     5067            this.refresh();
     5068        }
     5069        return this;
     5070    },
     5071
     5072    refresh: function() {
     5073        var state = this.controller.state(),
     5074            library = state.get('library'),
     5075            selection = state.get('selection');
     5076
     5077        _.each( this._views, function( button ) {
     5078            if ( ! button.model || ! button.options || ! button.options.requires ) {
     5079                return;
     5080            }
     5081
     5082            var requires = button.options.requires,
     5083                disabled = false;
     5084
     5085            // Prevent insertion of attachments if any of them are still uploading
     5086            disabled = _.some( selection.models, function( attachment ) {
     5087                return attachment.get('uploading') === true;
     5088            });
     5089
     5090            if ( requires.selection && selection && ! selection.length ) {
     5091                disabled = true;
     5092            } else if ( requires.library && library && ! library.length ) {
     5093                disabled = true;
     5094            }
     5095            button.model.set( 'disabled', disabled );
     5096        });
     5097    }
     5098});
     5099
     5100module.exports = Toolbar;
     5101
     5102
     5103/***/ }),
     5104/* 59 */
     5105/***/ (function(module, exports) {
     5106
     5107/**
     5108 * wp.media.view.Toolbar.Select
     5109 *
     5110 * @class
     5111 * @augments wp.media.view.Toolbar
     5112 * @augments wp.media.View
     5113 * @augments wp.Backbone.View
     5114 * @augments Backbone.View
     5115 */
     5116var Toolbar = wp.media.view.Toolbar,
     5117    l10n = wp.media.view.l10n,
     5118    Select;
     5119
     5120Select = Toolbar.extend({
     5121    initialize: function() {
     5122        var options = this.options;
     5123
     5124        _.bindAll( this, 'clickSelect' );
     5125
     5126        _.defaults( options, {
     5127            event: 'select',
     5128            state: false,
     5129            reset: true,
     5130            close: true,
     5131            text:  l10n.select,
     5132
     5133            // Does the button rely on the selection?
     5134            requires: {
     5135                selection: true
     5136            }
     5137        });
     5138
     5139        options.items = _.defaults( options.items || {}, {
     5140            select: {
     5141                style:    'primary',
     5142                text:     options.text,
     5143                priority: 80,
     5144                click:    this.clickSelect,
     5145                requires: options.requires
     5146            }
     5147        });
     5148        // Call 'initialize' directly on the parent class.
     5149        Toolbar.prototype.initialize.apply( this, arguments );
     5150    },
     5151
     5152    clickSelect: function() {
     5153        var options = this.options,
     5154            controller = this.controller;
     5155
     5156        if ( options.close ) {
     5157            controller.close();
     5158        }
     5159
     5160        if ( options.event ) {
     5161            controller.state().trigger( options.event );
     5162        }
     5163
     5164        if ( options.state ) {
     5165            controller.setState( options.state );
     5166        }
     5167
     5168        if ( options.reset ) {
     5169            controller.reset();
     5170        }
     5171    }
     5172});
     5173
     5174module.exports = Select;
     5175
     5176
     5177/***/ }),
     5178/* 60 */
     5179/***/ (function(module, exports) {
     5180
     5181/**
     5182 * wp.media.view.Toolbar.Embed
     5183 *
     5184 * @class
     5185 * @augments wp.media.view.Toolbar.Select
     5186 * @augments wp.media.view.Toolbar
     5187 * @augments wp.media.View
     5188 * @augments wp.Backbone.View
     5189 * @augments Backbone.View
     5190 */
     5191var Select = wp.media.view.Toolbar.Select,
     5192    l10n = wp.media.view.l10n,
     5193    Embed;
     5194
     5195Embed = Select.extend({
     5196    initialize: function() {
     5197        _.defaults( this.options, {
     5198            text: l10n.insertIntoPost,
     5199            requires: false
     5200        });
     5201        // Call 'initialize' directly on the parent class.
     5202        Select.prototype.initialize.apply( this, arguments );
     5203    },
     5204
     5205    refresh: function() {
     5206        var url = this.controller.state().props.get('url');
     5207        this.get('select').model.set( 'disabled', ! url || url === 'http://' );
     5208        /**
     5209         * call 'refresh' directly on the parent class
     5210         */
     5211        Select.prototype.refresh.apply( this, arguments );
     5212    }
     5213});
     5214
     5215module.exports = Embed;
     5216
     5217
     5218/***/ }),
     5219/* 61 */
     5220/***/ (function(module, exports) {
     5221
     5222/**
     5223 * wp.media.view.Button
     5224 *
     5225 * @class
     5226 * @augments wp.media.View
     5227 * @augments wp.Backbone.View
     5228 * @augments Backbone.View
     5229 */
     5230var Button = wp.media.View.extend({
     5231    tagName:    'button',
     5232    className:  'media-button',
     5233    attributes: { type: 'button' },
     5234
     5235    events: {
     5236        'click': 'click'
     5237    },
     5238
     5239    defaults: {
     5240        text:     '',
     5241        style:    '',
     5242        size:     'large',
     5243        disabled: false
     5244    },
     5245
     5246    initialize: function() {
     5247        /**
     5248         * Create a model with the provided `defaults`.
     5249         *
     5250         * @member {Backbone.Model}
     5251         */
     5252        this.model = new Backbone.Model( this.defaults );
     5253
     5254        // If any of the `options` have a key from `defaults`, apply its
     5255        // value to the `model` and remove it from the `options object.
     5256        _.each( this.defaults, function( def, key ) {
     5257            var value = this.options[ key ];
     5258            if ( _.isUndefined( value ) ) {
     5259                return;
     5260            }
     5261
     5262            this.model.set( key, value );
     5263            delete this.options[ key ];
     5264        }, this );
     5265
     5266        this.listenTo( this.model, 'change', this.render );
     5267    },
     5268    /**
     5269     * @returns {wp.media.view.Button} Returns itself to allow chaining
    23465270     */
    23475271    render: function() {
    2348         var compat = this.model.get('compat');
    2349         if ( ! compat || ! compat.item ) {
    2350             return;
    2351         }
    2352 
    2353         this.views.detach();
    2354         this.$el.html( compat.item );
    2355         this.views.render();
     5272        var classes = [ 'button', this.className ],
     5273            model = this.model.toJSON();
     5274
     5275        if ( model.style ) {
     5276            classes.push( 'button-' + model.style );
     5277        }
     5278
     5279        if ( model.size ) {
     5280            classes.push( 'button-' + model.size );
     5281        }
     5282
     5283        classes = _.uniq( classes.concat( this.options.classes ) );
     5284        this.el.className = classes.join(' ');
     5285
     5286        this.$el.attr( 'disabled', model.disabled );
     5287        this.$el.text( this.model.get('text') );
     5288
    23565289        return this;
    23575290    },
     
    23595292     * @param {Object} event
    23605293     */
    2361     preventDefault: function( event ) {
    2362         event.preventDefault();
     5294    click: function( event ) {
     5295        if ( '#' === this.attributes.href ) {
     5296            event.preventDefault();
     5297        }
     5298
     5299        if ( this.options.click && ! this.model.get('disabled') ) {
     5300            this.options.click.apply( this, arguments );
     5301        }
     5302    }
     5303});
     5304
     5305module.exports = Button;
     5306
     5307
     5308/***/ }),
     5309/* 62 */
     5310/***/ (function(module, exports) {
     5311
     5312/**
     5313 * wp.media.view.ButtonGroup
     5314 *
     5315 * @class
     5316 * @augments wp.media.View
     5317 * @augments wp.Backbone.View
     5318 * @augments Backbone.View
     5319 */
     5320var $ = Backbone.$,
     5321    ButtonGroup;
     5322
     5323ButtonGroup = wp.media.View.extend({
     5324    tagName:   'div',
     5325    className: 'button-group button-large media-button-group',
     5326
     5327    initialize: function() {
     5328        /**
     5329         * @member {wp.media.view.Button[]}
     5330         */
     5331        this.buttons = _.map( this.options.buttons || [], function( button ) {
     5332            if ( button instanceof Backbone.View ) {
     5333                return button;
     5334            } else {
     5335                return new wp.media.view.Button( button ).render();
     5336            }
     5337        });
     5338
     5339        delete this.options.buttons;
     5340
     5341        if ( this.options.classes ) {
     5342            this.$el.addClass( this.options.classes );
     5343        }
     5344    },
     5345
     5346    /**
     5347     * @returns {wp.media.view.ButtonGroup}
     5348     */
     5349    render: function() {
     5350        this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
     5351        return this;
     5352    }
     5353});
     5354
     5355module.exports = ButtonGroup;
     5356
     5357
     5358/***/ }),
     5359/* 63 */
     5360/***/ (function(module, exports) {
     5361
     5362/**
     5363 * wp.media.view.PriorityList
     5364 *
     5365 * @class
     5366 * @augments wp.media.View
     5367 * @augments wp.Backbone.View
     5368 * @augments Backbone.View
     5369 */
     5370var PriorityList = wp.media.View.extend({
     5371    tagName:   'div',
     5372
     5373    initialize: function() {
     5374        this._views = {};
     5375
     5376        this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
     5377        delete this.options.views;
     5378
     5379        if ( ! this.options.silent ) {
     5380            this.render();
     5381        }
     5382    },
     5383    /**
     5384     * @param {string} id
     5385     * @param {wp.media.View|Object} view
     5386     * @param {Object} options
     5387     * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
     5388     */
     5389    set: function( id, view, options ) {
     5390        var priority, views, index;
     5391
     5392        options = options || {};
     5393
     5394        // Accept an object with an `id` : `view` mapping.
     5395        if ( _.isObject( id ) ) {
     5396            _.each( id, function( view, id ) {
     5397                this.set( id, view );
     5398            }, this );
     5399            return this;
     5400        }
     5401
     5402        if ( ! (view instanceof Backbone.View) ) {
     5403            view = this.toView( view, id, options );
     5404        }
     5405        view.controller = view.controller || this.controller;
     5406
     5407        this.unset( id );
     5408
     5409        priority = view.options.priority || 10;
     5410        views = this.views.get() || [];
     5411
     5412        _.find( views, function( existing, i ) {
     5413            if ( existing.options.priority > priority ) {
     5414                index = i;
     5415                return true;
     5416            }
     5417        });
     5418
     5419        this._views[ id ] = view;
     5420        this.views.add( view, {
     5421            at: _.isNumber( index ) ? index : views.length || 0
     5422        });
     5423
     5424        return this;
     5425    },
     5426    /**
     5427     * @param {string} id
     5428     * @returns {wp.media.View}
     5429     */
     5430    get: function( id ) {
     5431        return this._views[ id ];
     5432    },
     5433    /**
     5434     * @param {string} id
     5435     * @returns {wp.media.view.PriorityList}
     5436     */
     5437    unset: function( id ) {
     5438        var view = this.get( id );
     5439
     5440        if ( view ) {
     5441            view.remove();
     5442        }
     5443
     5444        delete this._views[ id ];
     5445        return this;
     5446    },
     5447    /**
     5448     * @param {Object} options
     5449     * @returns {wp.media.View}
     5450     */
     5451    toView: function( options ) {
     5452        return new wp.media.View( options );
     5453    }
     5454});
     5455
     5456module.exports = PriorityList;
     5457
     5458
     5459/***/ }),
     5460/* 64 */
     5461/***/ (function(module, exports) {
     5462
     5463/**
     5464 * wp.media.view.MenuItem
     5465 *
     5466 * @class
     5467 * @augments wp.media.View
     5468 * @augments wp.Backbone.View
     5469 * @augments Backbone.View
     5470 */
     5471var $ = jQuery,
     5472    MenuItem;
     5473
     5474MenuItem = wp.media.View.extend({
     5475    tagName:   'a',
     5476    className: 'media-menu-item',
     5477
     5478    attributes: {
     5479        href: '#'
     5480    },
     5481
     5482    events: {
     5483        'click': '_click'
    23635484    },
    23645485    /**
    23655486     * @param {Object} event
    23665487     */
    2367     save: function( event ) {
    2368         var data = {};
     5488    _click: function( event ) {
     5489        var clickOverride = this.options.click;
    23695490
    23705491        if ( event ) {
     
    23725493        }
    23735494
    2374         _.each( this.$el.serializeArray(), function( pair ) {
    2375             data[ pair.name ] = pair.value;
    2376         });
    2377 
    2378         this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
    2379         this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
    2380     },
    2381 
    2382     postSave: function() {
    2383         this.controller.trigger( 'attachment:compat:ready', ['ready'] );
     5495        if ( clickOverride ) {
     5496            clickOverride.call( this );
     5497        } else {
     5498            this.click();
     5499        }
     5500
     5501        // When selecting a tab along the left side,
     5502        // focus should be transferred into the main panel
     5503        if ( ! wp.media.isTouchDevice ) {
     5504            $('.media-frame-content input').first().focus();
     5505        }
     5506    },
     5507
     5508    click: function() {
     5509        var state = this.options.state;
     5510
     5511        if ( state ) {
     5512            this.controller.setState( state );
     5513            this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
     5514        }
     5515    },
     5516    /**
     5517     * @returns {wp.media.view.MenuItem} returns itself to allow chaining
     5518     */
     5519    render: function() {
     5520        var options = this.options;
     5521
     5522        if ( options.text ) {
     5523            this.$el.text( options.text );
     5524        } else if ( options.html ) {
     5525            this.$el.html( options.html );
     5526        }
     5527
     5528        return this;
    23845529    }
    23855530});
    23865531
    2387 module.exports = AttachmentCompat;
    2388 
    2389 },{}],21:[function(require,module,exports){
     5532module.exports = MenuItem;
     5533
     5534
     5535/***/ }),
     5536/* 65 */
     5537/***/ (function(module, exports) {
     5538
     5539/**
     5540 * wp.media.view.Menu
     5541 *
     5542 * @class
     5543 * @augments wp.media.view.PriorityList
     5544 * @augments wp.media.View
     5545 * @augments wp.Backbone.View
     5546 * @augments Backbone.View
     5547 */
     5548var MenuItem = wp.media.view.MenuItem,
     5549    PriorityList = wp.media.view.PriorityList,
     5550    Menu;
     5551
     5552Menu = PriorityList.extend({
     5553    tagName:   'div',
     5554    className: 'media-menu',
     5555    property:  'state',
     5556    ItemView:  MenuItem,
     5557    region:    'menu',
     5558
     5559    /* TODO: alternatively hide on any click anywhere
     5560    events: {
     5561        'click': 'click'
     5562    },
     5563
     5564    click: function() {
     5565        this.$el.removeClass( 'visible' );
     5566    },
     5567    */
     5568
     5569    /**
     5570     * @param {Object} options
     5571     * @param {string} id
     5572     * @returns {wp.media.View}
     5573     */
     5574    toView: function( options, id ) {
     5575        options = options || {};
     5576        options[ this.property ] = options[ this.property ] || id;
     5577        return new this.ItemView( options ).render();
     5578    },
     5579
     5580    ready: function() {
     5581        /**
     5582         * call 'ready' directly on the parent class
     5583         */
     5584        PriorityList.prototype.ready.apply( this, arguments );
     5585        this.visibility();
     5586    },
     5587
     5588    set: function() {
     5589        /**
     5590         * call 'set' directly on the parent class
     5591         */
     5592        PriorityList.prototype.set.apply( this, arguments );
     5593        this.visibility();
     5594    },
     5595
     5596    unset: function() {
     5597        /**
     5598         * call 'unset' directly on the parent class
     5599         */
     5600        PriorityList.prototype.unset.apply( this, arguments );
     5601        this.visibility();
     5602    },
     5603
     5604    visibility: function() {
     5605        var region = this.region,
     5606            view = this.controller[ region ].get(),
     5607            views = this.views.get(),
     5608            hide = ! views || views.length < 2;
     5609
     5610        if ( this === view ) {
     5611            this.controller.$el.toggleClass( 'hide-' + region, hide );
     5612        }
     5613    },
     5614    /**
     5615     * @param {string} id
     5616     */
     5617    select: function( id ) {
     5618        var view = this.get( id );
     5619
     5620        if ( ! view ) {
     5621            return;
     5622        }
     5623
     5624        this.deselect();
     5625        view.$el.addClass('active');
     5626    },
     5627
     5628    deselect: function() {
     5629        this.$el.children().removeClass('active');
     5630    },
     5631
     5632    hide: function( id ) {
     5633        var view = this.get( id );
     5634
     5635        if ( ! view ) {
     5636            return;
     5637        }
     5638
     5639        view.$el.addClass('hidden');
     5640    },
     5641
     5642    show: function( id ) {
     5643        var view = this.get( id );
     5644
     5645        if ( ! view ) {
     5646            return;
     5647        }
     5648
     5649        view.$el.removeClass('hidden');
     5650    }
     5651});
     5652
     5653module.exports = Menu;
     5654
     5655
     5656/***/ }),
     5657/* 66 */
     5658/***/ (function(module, exports) {
     5659
     5660/**
     5661 * wp.media.view.RouterItem
     5662 *
     5663 * @class
     5664 * @augments wp.media.view.MenuItem
     5665 * @augments wp.media.View
     5666 * @augments wp.Backbone.View
     5667 * @augments Backbone.View
     5668 */
     5669var RouterItem = wp.media.view.MenuItem.extend({
     5670    /**
     5671     * On click handler to activate the content region's corresponding mode.
     5672     */
     5673    click: function() {
     5674        var contentMode = this.options.contentMode;
     5675        if ( contentMode ) {
     5676            this.controller.content.mode( contentMode );
     5677        }
     5678    }
     5679});
     5680
     5681module.exports = RouterItem;
     5682
     5683
     5684/***/ }),
     5685/* 67 */
     5686/***/ (function(module, exports) {
     5687
     5688/**
     5689 * wp.media.view.Router
     5690 *
     5691 * @class
     5692 * @augments wp.media.view.Menu
     5693 * @augments wp.media.view.PriorityList
     5694 * @augments wp.media.View
     5695 * @augments wp.Backbone.View
     5696 * @augments Backbone.View
     5697 */
     5698var Menu = wp.media.view.Menu,
     5699    Router;
     5700
     5701Router = Menu.extend({
     5702    tagName:   'div',
     5703    className: 'media-router',
     5704    property:  'contentMode',
     5705    ItemView:  wp.media.view.RouterItem,
     5706    region:    'router',
     5707
     5708    initialize: function() {
     5709        this.controller.on( 'content:render', this.update, this );
     5710        // Call 'initialize' directly on the parent class.
     5711        Menu.prototype.initialize.apply( this, arguments );
     5712    },
     5713
     5714    update: function() {
     5715        var mode = this.controller.content.mode();
     5716        if ( mode ) {
     5717            this.select( mode );
     5718        }
     5719    }
     5720});
     5721
     5722module.exports = Router;
     5723
     5724
     5725/***/ }),
     5726/* 68 */
     5727/***/ (function(module, exports) {
     5728
     5729/**
     5730 * wp.media.view.Sidebar
     5731 *
     5732 * @class
     5733 * @augments wp.media.view.PriorityList
     5734 * @augments wp.media.View
     5735 * @augments wp.Backbone.View
     5736 * @augments Backbone.View
     5737 */
     5738var Sidebar = wp.media.view.PriorityList.extend({
     5739    className: 'media-sidebar'
     5740});
     5741
     5742module.exports = Sidebar;
     5743
     5744
     5745/***/ }),
     5746/* 69 */
     5747/***/ (function(module, exports) {
     5748
     5749/**
     5750 * wp.media.view.Attachment
     5751 *
     5752 * @class
     5753 * @augments wp.media.View
     5754 * @augments wp.Backbone.View
     5755 * @augments Backbone.View
     5756 */
     5757var View = wp.media.View,
     5758    $ = jQuery,
     5759    Attachment;
     5760
     5761Attachment = View.extend({
     5762    tagName:   'li',
     5763    className: 'attachment',
     5764    template:  wp.template('attachment'),
     5765
     5766    attributes: function() {
     5767        return {
     5768            'tabIndex':     0,
     5769            'role':         'checkbox',
     5770            'aria-label':   this.model.get( 'title' ),
     5771            'aria-checked': false,
     5772            'data-id':      this.model.get( 'id' )
     5773        };
     5774    },
     5775
     5776    events: {
     5777        'click .js--select-attachment':   'toggleSelectionHandler',
     5778        'change [data-setting]':          'updateSetting',
     5779        'change [data-setting] input':    'updateSetting',
     5780        'change [data-setting] select':   'updateSetting',
     5781        'change [data-setting] textarea': 'updateSetting',
     5782        'click .attachment-close':        'removeFromLibrary',
     5783        'click .check':                   'checkClickHandler',
     5784        'keydown':                        'toggleSelectionHandler'
     5785    },
     5786
     5787    buttons: {},
     5788
     5789    initialize: function() {
     5790        var selection = this.options.selection,
     5791            options = _.defaults( this.options, {
     5792                rerenderOnModelChange: true
     5793            } );
     5794
     5795        if ( options.rerenderOnModelChange ) {
     5796            this.listenTo( this.model, 'change', this.render );
     5797        } else {
     5798            this.listenTo( this.model, 'change:percent', this.progress );
     5799        }
     5800        this.listenTo( this.model, 'change:title', this._syncTitle );
     5801        this.listenTo( this.model, 'change:caption', this._syncCaption );
     5802        this.listenTo( this.model, 'change:artist', this._syncArtist );
     5803        this.listenTo( this.model, 'change:album', this._syncAlbum );
     5804
     5805        // Update the selection.
     5806        this.listenTo( this.model, 'add', this.select );
     5807        this.listenTo( this.model, 'remove', this.deselect );
     5808        if ( selection ) {
     5809            selection.on( 'reset', this.updateSelect, this );
     5810            // Update the model's details view.
     5811            this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
     5812            this.details( this.model, this.controller.state().get('selection') );
     5813        }
     5814
     5815        this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
     5816    },
     5817    /**
     5818     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     5819     */
     5820    dispose: function() {
     5821        var selection = this.options.selection;
     5822
     5823        // Make sure all settings are saved before removing the view.
     5824        this.updateAll();
     5825
     5826        if ( selection ) {
     5827            selection.off( null, null, this );
     5828        }
     5829        /**
     5830         * call 'dispose' directly on the parent class
     5831         */
     5832        View.prototype.dispose.apply( this, arguments );
     5833        return this;
     5834    },
     5835    /**
     5836     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     5837     */
     5838    render: function() {
     5839        var options = _.defaults( this.model.toJSON(), {
     5840                orientation:   'landscape',
     5841                uploading:     false,
     5842                type:          '',
     5843                subtype:       '',
     5844                icon:          '',
     5845                filename:      '',
     5846                caption:       '',
     5847                title:         '',
     5848                dateFormatted: '',
     5849                width:         '',
     5850                height:        '',
     5851                compat:        false,
     5852                alt:           '',
     5853                description:   ''
     5854            }, this.options );
     5855
     5856        options.buttons  = this.buttons;
     5857        options.describe = this.controller.state().get('describe');
     5858
     5859        if ( 'image' === options.type ) {
     5860            options.size = this.imageSize();
     5861        }
     5862
     5863        options.can = {};
     5864        if ( options.nonces ) {
     5865            options.can.remove = !! options.nonces['delete'];
     5866            options.can.save = !! options.nonces.update;
     5867        }
     5868
     5869        if ( this.controller.state().get('allowLocalEdits') ) {
     5870            options.allowLocalEdits = true;
     5871        }
     5872
     5873        if ( options.uploading && ! options.percent ) {
     5874            options.percent = 0;
     5875        }
     5876
     5877        this.views.detach();
     5878        this.$el.html( this.template( options ) );
     5879
     5880        this.$el.toggleClass( 'uploading', options.uploading );
     5881
     5882        if ( options.uploading ) {
     5883            this.$bar = this.$('.media-progress-bar div');
     5884        } else {
     5885            delete this.$bar;
     5886        }
     5887
     5888        // Check if the model is selected.
     5889        this.updateSelect();
     5890
     5891        // Update the save status.
     5892        this.updateSave();
     5893
     5894        this.views.render();
     5895
     5896        return this;
     5897    },
     5898
     5899    progress: function() {
     5900        if ( this.$bar && this.$bar.length ) {
     5901            this.$bar.width( this.model.get('percent') + '%' );
     5902        }
     5903    },
     5904
     5905    /**
     5906     * @param {Object} event
     5907     */
     5908    toggleSelectionHandler: function( event ) {
     5909        var method;
     5910
     5911        // Don't do anything inside inputs and on the attachment check and remove buttons.
     5912        if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
     5913            return;
     5914        }
     5915
     5916        // Catch arrow events
     5917        if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     5918            this.controller.trigger( 'attachment:keydown:arrow', event );
     5919            return;
     5920        }
     5921
     5922        // Catch enter and space events
     5923        if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     5924            return;
     5925        }
     5926
     5927        event.preventDefault();
     5928
     5929        // In the grid view, bubble up an edit:attachment event to the controller.
     5930        if ( this.controller.isModeActive( 'grid' ) ) {
     5931            if ( this.controller.isModeActive( 'edit' ) ) {
     5932                // Pass the current target to restore focus when closing
     5933                this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
     5934                return;
     5935            }
     5936
     5937            if ( this.controller.isModeActive( 'select' ) ) {
     5938                method = 'toggle';
     5939            }
     5940        }
     5941
     5942        if ( event.shiftKey ) {
     5943            method = 'between';
     5944        } else if ( event.ctrlKey || event.metaKey ) {
     5945            method = 'toggle';
     5946        }
     5947
     5948        this.toggleSelection({
     5949            method: method
     5950        });
     5951
     5952        this.controller.trigger( 'selection:toggle' );
     5953    },
     5954    /**
     5955     * @param {Object} options
     5956     */
     5957    toggleSelection: function( options ) {
     5958        var collection = this.collection,
     5959            selection = this.options.selection,
     5960            model = this.model,
     5961            method = options && options.method,
     5962            single, models, singleIndex, modelIndex;
     5963
     5964        if ( ! selection ) {
     5965            return;
     5966        }
     5967
     5968        single = selection.single();
     5969        method = _.isUndefined( method ) ? selection.multiple : method;
     5970
     5971        // If the `method` is set to `between`, select all models that
     5972        // exist between the current and the selected model.
     5973        if ( 'between' === method && single && selection.multiple ) {
     5974            // If the models are the same, short-circuit.
     5975            if ( single === model ) {
     5976                return;
     5977            }
     5978
     5979            singleIndex = collection.indexOf( single );
     5980            modelIndex  = collection.indexOf( this.model );
     5981
     5982            if ( singleIndex < modelIndex ) {
     5983                models = collection.models.slice( singleIndex, modelIndex + 1 );
     5984            } else {
     5985                models = collection.models.slice( modelIndex, singleIndex + 1 );
     5986            }
     5987
     5988            selection.add( models );
     5989            selection.single( model );
     5990            return;
     5991
     5992        // If the `method` is set to `toggle`, just flip the selection
     5993        // status, regardless of whether the model is the single model.
     5994        } else if ( 'toggle' === method ) {
     5995            selection[ this.selected() ? 'remove' : 'add' ]( model );
     5996            selection.single( model );
     5997            return;
     5998        } else if ( 'add' === method ) {
     5999            selection.add( model );
     6000            selection.single( model );
     6001            return;
     6002        }
     6003
     6004        // Fixes bug that loses focus when selecting a featured image
     6005        if ( ! method ) {
     6006            method = 'add';
     6007        }
     6008
     6009        if ( method !== 'add' ) {
     6010            method = 'reset';
     6011        }
     6012
     6013        if ( this.selected() ) {
     6014            // If the model is the single model, remove it.
     6015            // If it is not the same as the single model,
     6016            // it now becomes the single model.
     6017            selection[ single === model ? 'remove' : 'single' ]( model );
     6018        } else {
     6019            // If the model is not selected, run the `method` on the
     6020            // selection. By default, we `reset` the selection, but the
     6021            // `method` can be set to `add` the model to the selection.
     6022            selection[ method ]( model );
     6023            selection.single( model );
     6024        }
     6025    },
     6026
     6027    updateSelect: function() {
     6028        this[ this.selected() ? 'select' : 'deselect' ]();
     6029    },
     6030    /**
     6031     * @returns {unresolved|Boolean}
     6032     */
     6033    selected: function() {
     6034        var selection = this.options.selection;
     6035        if ( selection ) {
     6036            return !! selection.get( this.model.cid );
     6037        }
     6038    },
     6039    /**
     6040     * @param {Backbone.Model} model
     6041     * @param {Backbone.Collection} collection
     6042     */
     6043    select: function( model, collection ) {
     6044        var selection = this.options.selection,
     6045            controller = this.controller;
     6046
     6047        // Check if a selection exists and if it's the collection provided.
     6048        // If they're not the same collection, bail; we're in another
     6049        // selection's event loop.
     6050        if ( ! selection || ( collection && collection !== selection ) ) {
     6051            return;
     6052        }
     6053
     6054        // Bail if the model is already selected.
     6055        if ( this.$el.hasClass( 'selected' ) ) {
     6056            return;
     6057        }
     6058
     6059        // Add 'selected' class to model, set aria-checked to true.
     6060        this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
     6061        //  Make the checkbox tabable, except in media grid (bulk select mode).
     6062        if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
     6063            this.$( '.check' ).attr( 'tabindex', '0' );
     6064        }
     6065    },
     6066    /**
     6067     * @param {Backbone.Model} model
     6068     * @param {Backbone.Collection} collection
     6069     */
     6070    deselect: function( model, collection ) {
     6071        var selection = this.options.selection;
     6072
     6073        // Check if a selection exists and if it's the collection provided.
     6074        // If they're not the same collection, bail; we're in another
     6075        // selection's event loop.
     6076        if ( ! selection || ( collection && collection !== selection ) ) {
     6077            return;
     6078        }
     6079        this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
     6080            .find( '.check' ).attr( 'tabindex', '-1' );
     6081    },
     6082    /**
     6083     * @param {Backbone.Model} model
     6084     * @param {Backbone.Collection} collection
     6085     */
     6086    details: function( model, collection ) {
     6087        var selection = this.options.selection,
     6088            details;
     6089
     6090        if ( selection !== collection ) {
     6091            return;
     6092        }
     6093
     6094        details = selection.single();
     6095        this.$el.toggleClass( 'details', details === this.model );
     6096    },
     6097    /**
     6098     * @param {string} size
     6099     * @returns {Object}
     6100     */
     6101    imageSize: function( size ) {
     6102        var sizes = this.model.get('sizes'), matched = false;
     6103
     6104        size = size || 'medium';
     6105
     6106        // Use the provided image size if possible.
     6107        if ( sizes ) {
     6108            if ( sizes[ size ] ) {
     6109                matched = sizes[ size ];
     6110            } else if ( sizes.large ) {
     6111                matched = sizes.large;
     6112            } else if ( sizes.thumbnail ) {
     6113                matched = sizes.thumbnail;
     6114            } else if ( sizes.full ) {
     6115                matched = sizes.full;
     6116            }
     6117
     6118            if ( matched ) {
     6119                return _.clone( matched );
     6120            }
     6121        }
     6122
     6123        return {
     6124            url:         this.model.get('url'),
     6125            width:       this.model.get('width'),
     6126            height:      this.model.get('height'),
     6127            orientation: this.model.get('orientation')
     6128        };
     6129    },
     6130    /**
     6131     * @param {Object} event
     6132     */
     6133    updateSetting: function( event ) {
     6134        var $setting = $( event.target ).closest('[data-setting]'),
     6135            setting, value;
     6136
     6137        if ( ! $setting.length ) {
     6138            return;
     6139        }
     6140
     6141        setting = $setting.data('setting');
     6142        value   = event.target.value;
     6143
     6144        if ( this.model.get( setting ) !== value ) {
     6145            this.save( setting, value );
     6146        }
     6147    },
     6148
     6149    /**
     6150     * Pass all the arguments to the model's save method.
     6151     *
     6152     * Records the aggregate status of all save requests and updates the
     6153     * view's classes accordingly.
     6154     */
     6155    save: function() {
     6156        var view = this,
     6157            save = this._save = this._save || { status: 'ready' },
     6158            request = this.model.save.apply( this.model, arguments ),
     6159            requests = save.requests ? $.when( request, save.requests ) : request;
     6160
     6161        // If we're waiting to remove 'Saved.', stop.
     6162        if ( save.savedTimer ) {
     6163            clearTimeout( save.savedTimer );
     6164        }
     6165
     6166        this.updateSave('waiting');
     6167        save.requests = requests;
     6168        requests.always( function() {
     6169            // If we've performed another request since this one, bail.
     6170            if ( save.requests !== requests ) {
     6171                return;
     6172            }
     6173
     6174            view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
     6175            save.savedTimer = setTimeout( function() {
     6176                view.updateSave('ready');
     6177                delete save.savedTimer;
     6178            }, 2000 );
     6179        });
     6180    },
     6181    /**
     6182     * @param {string} status
     6183     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6184     */
     6185    updateSave: function( status ) {
     6186        var save = this._save = this._save || { status: 'ready' };
     6187
     6188        if ( status && status !== save.status ) {
     6189            this.$el.removeClass( 'save-' + save.status );
     6190            save.status = status;
     6191        }
     6192
     6193        this.$el.addClass( 'save-' + save.status );
     6194        return this;
     6195    },
     6196
     6197    updateAll: function() {
     6198        var $settings = this.$('[data-setting]'),
     6199            model = this.model,
     6200            changed;
     6201
     6202        changed = _.chain( $settings ).map( function( el ) {
     6203            var $input = $('input, textarea, select, [value]', el ),
     6204                setting, value;
     6205
     6206            if ( ! $input.length ) {
     6207                return;
     6208            }
     6209
     6210            setting = $(el).data('setting');
     6211            value = $input.val();
     6212
     6213            // Record the value if it changed.
     6214            if ( model.get( setting ) !== value ) {
     6215                return [ setting, value ];
     6216            }
     6217        }).compact().object().value();
     6218
     6219        if ( ! _.isEmpty( changed ) ) {
     6220            model.save( changed );
     6221        }
     6222    },
     6223    /**
     6224     * @param {Object} event
     6225     */
     6226    removeFromLibrary: function( event ) {
     6227        // Catch enter and space events
     6228        if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     6229            return;
     6230        }
     6231
     6232        // Stop propagation so the model isn't selected.
     6233        event.stopPropagation();
     6234
     6235        this.collection.remove( this.model );
     6236    },
     6237
     6238    /**
     6239     * Add the model if it isn't in the selection, if it is in the selection,
     6240     * remove it.
     6241     *
     6242     * @param  {[type]} event [description]
     6243     * @return {[type]}       [description]
     6244     */
     6245    checkClickHandler: function ( event ) {
     6246        var selection = this.options.selection;
     6247        if ( ! selection ) {
     6248            return;
     6249        }
     6250        event.stopPropagation();
     6251        if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
     6252            selection.remove( this.model );
     6253            // Move focus back to the attachment tile (from the check).
     6254            this.$el.focus();
     6255        } else {
     6256            selection.add( this.model );
     6257        }
     6258    }
     6259});
     6260
     6261// Ensure settings remain in sync between attachment views.
     6262_.each({
     6263    caption: '_syncCaption',
     6264    title:   '_syncTitle',
     6265    artist:  '_syncArtist',
     6266    album:   '_syncAlbum'
     6267}, function( method, setting ) {
     6268    /**
     6269     * @param {Backbone.Model} model
     6270     * @param {string} value
     6271     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6272     */
     6273    Attachment.prototype[ method ] = function( model, value ) {
     6274        var $setting = this.$('[data-setting="' + setting + '"]');
     6275
     6276        if ( ! $setting.length ) {
     6277            return this;
     6278        }
     6279
     6280        // If the updated value is in sync with the value in the DOM, there
     6281        // is no need to re-render. If we're currently editing the value,
     6282        // it will automatically be in sync, suppressing the re-render for
     6283        // the view we're editing, while updating any others.
     6284        if ( value === $setting.find('input, textarea, select, [value]').val() ) {
     6285            return this;
     6286        }
     6287
     6288        return this.render();
     6289    };
     6290});
     6291
     6292module.exports = Attachment;
     6293
     6294
     6295/***/ }),
     6296/* 70 */
     6297/***/ (function(module, exports) {
     6298
     6299/**
     6300 * wp.media.view.Attachment.Library
     6301 *
     6302 * @class
     6303 * @augments wp.media.view.Attachment
     6304 * @augments wp.media.View
     6305 * @augments wp.Backbone.View
     6306 * @augments Backbone.View
     6307 */
     6308var Library = wp.media.view.Attachment.extend({
     6309    buttons: {
     6310        check: true
     6311    }
     6312});
     6313
     6314module.exports = Library;
     6315
     6316
     6317/***/ }),
     6318/* 71 */
     6319/***/ (function(module, exports) {
     6320
     6321/**
     6322 * wp.media.view.Attachment.EditLibrary
     6323 *
     6324 * @class
     6325 * @augments wp.media.view.Attachment
     6326 * @augments wp.media.View
     6327 * @augments wp.Backbone.View
     6328 * @augments Backbone.View
     6329 */
     6330var EditLibrary = wp.media.view.Attachment.extend({
     6331    buttons: {
     6332        close: true
     6333    }
     6334});
     6335
     6336module.exports = EditLibrary;
     6337
     6338
     6339/***/ }),
     6340/* 72 */
     6341/***/ (function(module, exports) {
     6342
     6343/**
     6344 * wp.media.view.Attachments
     6345 *
     6346 * @class
     6347 * @augments wp.media.View
     6348 * @augments wp.Backbone.View
     6349 * @augments Backbone.View
     6350 */
     6351var View = wp.media.View,
     6352    $ = jQuery,
     6353    Attachments;
     6354
     6355Attachments = View.extend({
     6356    tagName:   'ul',
     6357    className: 'attachments',
     6358
     6359    attributes: {
     6360        tabIndex: -1
     6361    },
     6362
     6363    initialize: function() {
     6364        this.el.id = _.uniqueId('__attachments-view-');
     6365
     6366        _.defaults( this.options, {
     6367            refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
     6368            refreshThreshold:   3,
     6369            AttachmentView:     wp.media.view.Attachment,
     6370            sortable:           false,
     6371            resize:             true,
     6372            idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
     6373        });
     6374
     6375        this._viewsByCid = {};
     6376        this.$window = $( window );
     6377        this.resizeEvent = 'resize.media-modal-columns';
     6378
     6379        this.collection.on( 'add', function( attachment ) {
     6380            this.views.add( this.createAttachmentView( attachment ), {
     6381                at: this.collection.indexOf( attachment )
     6382            });
     6383        }, this );
     6384
     6385        this.collection.on( 'remove', function( attachment ) {
     6386            var view = this._viewsByCid[ attachment.cid ];
     6387            delete this._viewsByCid[ attachment.cid ];
     6388
     6389            if ( view ) {
     6390                view.remove();
     6391            }
     6392        }, this );
     6393
     6394        this.collection.on( 'reset', this.render, this );
     6395
     6396        this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
     6397
     6398        // Throttle the scroll handler and bind this.
     6399        this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
     6400
     6401        this.options.scrollElement = this.options.scrollElement || this.el;
     6402        $( this.options.scrollElement ).on( 'scroll', this.scroll );
     6403
     6404        this.initSortable();
     6405
     6406        _.bindAll( this, 'setColumns' );
     6407
     6408        if ( this.options.resize ) {
     6409            this.on( 'ready', this.bindEvents );
     6410            this.controller.on( 'open', this.setColumns );
     6411
     6412            // Call this.setColumns() after this view has been rendered in the DOM so
     6413            // attachments get proper width applied.
     6414            _.defer( this.setColumns, this );
     6415        }
     6416    },
     6417
     6418    bindEvents: function() {
     6419        this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
     6420    },
     6421
     6422    attachmentFocus: function() {
     6423        this.$( 'li:first' ).focus();
     6424    },
     6425
     6426    restoreFocus: function() {
     6427        this.$( 'li.selected:first' ).focus();
     6428    },
     6429
     6430    arrowEvent: function( event ) {
     6431        var attachments = this.$el.children( 'li' ),
     6432            perRow = this.columns,
     6433            index = attachments.filter( ':focus' ).index(),
     6434            row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
     6435
     6436        if ( index === -1 ) {
     6437            return;
     6438        }
     6439
     6440        // Left arrow
     6441        if ( 37 === event.keyCode ) {
     6442            if ( 0 === index ) {
     6443                return;
     6444            }
     6445            attachments.eq( index - 1 ).focus();
     6446        }
     6447
     6448        // Up arrow
     6449        if ( 38 === event.keyCode ) {
     6450            if ( 1 === row ) {
     6451                return;
     6452            }
     6453            attachments.eq( index - perRow ).focus();
     6454        }
     6455
     6456        // Right arrow
     6457        if ( 39 === event.keyCode ) {
     6458            if ( attachments.length === index ) {
     6459                return;
     6460            }
     6461            attachments.eq( index + 1 ).focus();
     6462        }
     6463
     6464        // Down arrow
     6465        if ( 40 === event.keyCode ) {
     6466            if ( Math.ceil( attachments.length / perRow ) === row ) {
     6467                return;
     6468            }
     6469            attachments.eq( index + perRow ).focus();
     6470        }
     6471    },
     6472
     6473    dispose: function() {
     6474        this.collection.props.off( null, null, this );
     6475        if ( this.options.resize ) {
     6476            this.$window.off( this.resizeEvent );
     6477        }
     6478
     6479        /**
     6480         * call 'dispose' directly on the parent class
     6481         */
     6482        View.prototype.dispose.apply( this, arguments );
     6483    },
     6484
     6485    setColumns: function() {
     6486        var prev = this.columns,
     6487            width = this.$el.width();
     6488
     6489        if ( width ) {
     6490            this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
     6491
     6492            if ( ! prev || prev !== this.columns ) {
     6493                this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
     6494            }
     6495        }
     6496    },
     6497
     6498    initSortable: function() {
     6499        var collection = this.collection;
     6500
     6501        if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
     6502            return;
     6503        }
     6504
     6505        this.$el.sortable( _.extend({
     6506            // If the `collection` has a `comparator`, disable sorting.
     6507            disabled: !! collection.comparator,
     6508
     6509            // Change the position of the attachment as soon as the
     6510            // mouse pointer overlaps a thumbnail.
     6511            tolerance: 'pointer',
     6512
     6513            // Record the initial `index` of the dragged model.
     6514            start: function( event, ui ) {
     6515                ui.item.data('sortableIndexStart', ui.item.index());
     6516            },
     6517
     6518            // Update the model's index in the collection.
     6519            // Do so silently, as the view is already accurate.
     6520            update: function( event, ui ) {
     6521                var model = collection.at( ui.item.data('sortableIndexStart') ),
     6522                    comparator = collection.comparator;
     6523
     6524                // Temporarily disable the comparator to prevent `add`
     6525                // from re-sorting.
     6526                delete collection.comparator;
     6527
     6528                // Silently shift the model to its new index.
     6529                collection.remove( model, {
     6530                    silent: true
     6531                });
     6532                collection.add( model, {
     6533                    silent: true,
     6534                    at:     ui.item.index()
     6535                });
     6536
     6537                // Restore the comparator.
     6538                collection.comparator = comparator;
     6539
     6540                // Fire the `reset` event to ensure other collections sync.
     6541                collection.trigger( 'reset', collection );
     6542
     6543                // If the collection is sorted by menu order,
     6544                // update the menu order.
     6545                collection.saveMenuOrder();
     6546            }
     6547        }, this.options.sortable ) );
     6548
     6549        // If the `orderby` property is changed on the `collection`,
     6550        // check to see if we have a `comparator`. If so, disable sorting.
     6551        collection.props.on( 'change:orderby', function() {
     6552            this.$el.sortable( 'option', 'disabled', !! collection.comparator );
     6553        }, this );
     6554
     6555        this.collection.props.on( 'change:orderby', this.refreshSortable, this );
     6556        this.refreshSortable();
     6557    },
     6558
     6559    refreshSortable: function() {
     6560        if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
     6561            return;
     6562        }
     6563
     6564        // If the `collection` has a `comparator`, disable sorting.
     6565        var collection = this.collection,
     6566            orderby = collection.props.get('orderby'),
     6567            enabled = 'menuOrder' === orderby || ! collection.comparator;
     6568
     6569        this.$el.sortable( 'option', 'disabled', ! enabled );
     6570    },
     6571
     6572    /**
     6573     * @param {wp.media.model.Attachment} attachment
     6574     * @returns {wp.media.View}
     6575     */
     6576    createAttachmentView: function( attachment ) {
     6577        var view = new this.options.AttachmentView({
     6578            controller:           this.controller,
     6579            model:                attachment,
     6580            collection:           this.collection,
     6581            selection:            this.options.selection
     6582        });
     6583
     6584        return this._viewsByCid[ attachment.cid ] = view;
     6585    },
     6586
     6587    prepare: function() {
     6588        // Create all of the Attachment views, and replace
     6589        // the list in a single DOM operation.
     6590        if ( this.collection.length ) {
     6591            this.views.set( this.collection.map( this.createAttachmentView, this ) );
     6592
     6593        // If there are no elements, clear the views and load some.
     6594        } else {
     6595            this.views.unset();
     6596            this.collection.more().done( this.scroll );
     6597        }
     6598    },
     6599
     6600    ready: function() {
     6601        // Trigger the scroll event to check if we're within the
     6602        // threshold to query for additional attachments.
     6603        this.scroll();
     6604    },
     6605
     6606    scroll: function() {
     6607        var view = this,
     6608            el = this.options.scrollElement,
     6609            scrollTop = el.scrollTop,
     6610            toolbar;
     6611
     6612        // The scroll event occurs on the document, but the element
     6613        // that should be checked is the document body.
     6614        if ( el === document ) {
     6615            el = document.body;
     6616            scrollTop = $(document).scrollTop();
     6617        }
     6618
     6619        if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
     6620            return;
     6621        }
     6622
     6623        toolbar = this.views.parent.toolbar;
     6624
     6625        // Show the spinner only if we are close to the bottom.
     6626        if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
     6627            toolbar.get('spinner').show();
     6628        }
     6629
     6630        if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
     6631            this.collection.more().done(function() {
     6632                view.scroll();
     6633                toolbar.get('spinner').hide();
     6634            });
     6635        }
     6636    }
     6637});
     6638
     6639module.exports = Attachments;
     6640
     6641
     6642/***/ }),
     6643/* 73 */
     6644/***/ (function(module, exports) {
     6645
     6646/**
     6647 * wp.media.view.Search
     6648 *
     6649 * @class
     6650 * @augments wp.media.View
     6651 * @augments wp.Backbone.View
     6652 * @augments Backbone.View
     6653 */
     6654var l10n = wp.media.view.l10n,
     6655    Search;
     6656
     6657Search = wp.media.View.extend({
     6658    tagName:   'input',
     6659    className: 'search',
     6660    id:        'media-search-input',
     6661
     6662    attributes: {
     6663        type:        'search',
     6664        placeholder: l10n.search
     6665    },
     6666
     6667    events: {
     6668        'input':  'search',
     6669        'keyup':  'search',
     6670        'change': 'search',
     6671        'search': 'search'
     6672    },
     6673
     6674    /**
     6675     * @returns {wp.media.view.Search} Returns itself to allow chaining
     6676     */
     6677    render: function() {
     6678        this.el.value = this.model.escape('search');
     6679        return this;
     6680    },
     6681
     6682    search: function( event ) {
     6683        if ( event.target.value ) {
     6684            this.model.set( 'search', event.target.value );
     6685        } else {
     6686            this.model.unset('search');
     6687        }
     6688    }
     6689});
     6690
     6691module.exports = Search;
     6692
     6693
     6694/***/ }),
     6695/* 74 */
     6696/***/ (function(module, exports) {
     6697
    23906698/**
    23916699 * wp.media.view.AttachmentFilters
     
    24646772module.exports = AttachmentFilters;
    24656773
    2466 },{}],22:[function(require,module,exports){
    2467 /**
    2468  * wp.media.view.AttachmentFilters.All
    2469  *
    2470  * @class
    2471  * @augments wp.media.view.AttachmentFilters
    2472  * @augments wp.media.View
    2473  * @augments wp.Backbone.View
    2474  * @augments Backbone.View
    2475  */
    2476 var l10n = wp.media.view.l10n,
    2477     All;
    2478 
    2479 All = wp.media.view.AttachmentFilters.extend({
    2480     createFilters: function() {
    2481         var filters = {};
    2482 
    2483         _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
    2484             filters[ key ] = {
    2485                 text: text,
    2486                 props: {
    2487                     status:  null,
    2488                     type:    key,
    2489                     uploadedTo: null,
    2490                     orderby: 'date',
    2491                     order:   'DESC'
    2492                 }
    2493             };
    2494         });
    2495 
    2496         filters.all = {
    2497             text:  l10n.allMediaItems,
    2498             props: {
    2499                 status:  null,
    2500                 type:    null,
    2501                 uploadedTo: null,
    2502                 orderby: 'date',
    2503                 order:   'DESC'
    2504             },
    2505             priority: 10
    2506         };
    2507 
    2508         if ( wp.media.view.settings.post.id ) {
    2509             filters.uploaded = {
    2510                 text:  l10n.uploadedToThisPost,
    2511                 props: {
    2512                     status:  null,
    2513                     type:    null,
    2514                     uploadedTo: wp.media.view.settings.post.id,
    2515                     orderby: 'menuOrder',
    2516                     order:   'ASC'
    2517                 },
    2518                 priority: 20
    2519             };
    2520         }
    2521 
    2522         filters.unattached = {
    2523             text:  l10n.unattached,
    2524             props: {
    2525                 status:     null,
    2526                 uploadedTo: 0,
    2527                 type:       null,
    2528                 orderby:    'menuOrder',
    2529                 order:      'ASC'
    2530             },
    2531             priority: 50
    2532         };
    2533 
    2534         if ( wp.media.view.settings.mediaTrash &&
    2535             this.controller.isModeActive( 'grid' ) ) {
    2536 
    2537             filters.trash = {
    2538                 text:  l10n.trash,
    2539                 props: {
    2540                     uploadedTo: null,
    2541                     status:     'trash',
    2542                     type:       null,
    2543                     orderby:    'date',
    2544                     order:      'DESC'
    2545                 },
    2546                 priority: 50
    2547             };
    2548         }
    2549 
    2550         this.filters = filters;
    2551     }
    2552 });
    2553 
    2554 module.exports = All;
    2555 
    2556 },{}],23:[function(require,module,exports){
     6774
     6775/***/ }),
     6776/* 75 */
     6777/***/ (function(module, exports) {
     6778
    25576779/**
    25586780 * A filter dropdown for month/dates.
     
    25956817module.exports = DateFilter;
    25966818
    2597 },{}],24:[function(require,module,exports){
     6819
     6820/***/ }),
     6821/* 76 */
     6822/***/ (function(module, exports) {
     6823
    25986824/**
    25996825 * wp.media.view.AttachmentFilters.Uploaded
     
    26546880module.exports = Uploaded;
    26556881
    2656 },{}],25:[function(require,module,exports){
     6882
     6883/***/ }),
     6884/* 77 */
     6885/***/ (function(module, exports) {
     6886
    26576887/**
    2658  * wp.media.view.Attachment
     6888 * wp.media.view.AttachmentFilters.All
    26596889 *
    26606890 * @class
     6891 * @augments wp.media.view.AttachmentFilters
    26616892 * @augments wp.media.View
    26626893 * @augments wp.Backbone.View
    26636894 * @augments Backbone.View
    26646895 */
    2665 var View = wp.media.View,
    2666     $ = jQuery,
    2667     Attachment;
    2668 
    2669 Attachment = View.extend({
    2670     tagName:   'li',
    2671     className: 'attachment',
    2672     template:  wp.template('attachment'),
    2673 
    2674     attributes: function() {
    2675         return {
    2676             'tabIndex':     0,
    2677             'role':         'checkbox',
    2678             'aria-label':   this.model.get( 'title' ),
    2679             'aria-checked': false,
    2680             'data-id':      this.model.get( 'id' )
     6896var l10n = wp.media.view.l10n,
     6897    All;
     6898
     6899All = wp.media.view.AttachmentFilters.extend({
     6900    createFilters: function() {
     6901        var filters = {};
     6902
     6903        _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
     6904            filters[ key ] = {
     6905                text: text,
     6906                props: {
     6907                    status:  null,
     6908                    type:    key,
     6909                    uploadedTo: null,
     6910                    orderby: 'date',
     6911                    order:   'DESC'
     6912                }
     6913            };
     6914        });
     6915
     6916        filters.all = {
     6917            text:  l10n.allMediaItems,
     6918            props: {
     6919                status:  null,
     6920                type:    null,
     6921                uploadedTo: null,
     6922                orderby: 'date',
     6923                order:   'DESC'
     6924            },
     6925            priority: 10
    26816926        };
    2682     },
    2683 
    2684     events: {
    2685         'click .js--select-attachment':   'toggleSelectionHandler',
    2686         'change [data-setting]':          'updateSetting',
    2687         'change [data-setting] input':    'updateSetting',
    2688         'change [data-setting] select':   'updateSetting',
    2689         'change [data-setting] textarea': 'updateSetting',
    2690         'click .attachment-close':        'removeFromLibrary',
    2691         'click .check':                   'checkClickHandler',
    2692         'keydown':                        'toggleSelectionHandler'
    2693     },
    2694 
    2695     buttons: {},
    2696 
    2697     initialize: function() {
    2698         var selection = this.options.selection,
    2699             options = _.defaults( this.options, {
    2700                 rerenderOnModelChange: true
    2701             } );
    2702 
    2703         if ( options.rerenderOnModelChange ) {
    2704             this.listenTo( this.model, 'change', this.render );
    2705         } else {
    2706             this.listenTo( this.model, 'change:percent', this.progress );
    2707         }
    2708         this.listenTo( this.model, 'change:title', this._syncTitle );
    2709         this.listenTo( this.model, 'change:caption', this._syncCaption );
    2710         this.listenTo( this.model, 'change:artist', this._syncArtist );
    2711         this.listenTo( this.model, 'change:album', this._syncAlbum );
    2712 
    2713         // Update the selection.
    2714         this.listenTo( this.model, 'add', this.select );
    2715         this.listenTo( this.model, 'remove', this.deselect );
    2716         if ( selection ) {
    2717             selection.on( 'reset', this.updateSelect, this );
    2718             // Update the model's details view.
    2719             this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
    2720             this.details( this.model, this.controller.state().get('selection') );
    2721         }
    2722 
    2723         this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
    2724     },
    2725     /**
    2726      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    2727      */
    2728     dispose: function() {
    2729         var selection = this.options.selection;
    2730 
    2731         // Make sure all settings are saved before removing the view.
    2732         this.updateAll();
    2733 
    2734         if ( selection ) {
    2735             selection.off( null, null, this );
    2736         }
    2737         /**
    2738          * call 'dispose' directly on the parent class
    2739          */
    2740         View.prototype.dispose.apply( this, arguments );
    2741         return this;
    2742     },
    2743     /**
    2744      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    2745      */
    2746     render: function() {
    2747         var options = _.defaults( this.model.toJSON(), {
    2748                 orientation:   'landscape',
    2749                 uploading:     false,
    2750                 type:          '',
    2751                 subtype:       '',
    2752                 icon:          '',
    2753                 filename:      '',
    2754                 caption:       '',
    2755                 title:         '',
    2756                 dateFormatted: '',
    2757                 width:         '',
    2758                 height:        '',
    2759                 compat:        false,
    2760                 alt:           '',
    2761                 description:   ''
    2762             }, this.options );
    2763 
    2764         options.buttons  = this.buttons;
    2765         options.describe = this.controller.state().get('describe');
    2766 
    2767         if ( 'image' === options.type ) {
    2768             options.size = this.imageSize();
    2769         }
    2770 
    2771         options.can = {};
    2772         if ( options.nonces ) {
    2773             options.can.remove = !! options.nonces['delete'];
    2774             options.can.save = !! options.nonces.update;
    2775         }
    2776 
    2777         if ( this.controller.state().get('allowLocalEdits') ) {
    2778             options.allowLocalEdits = true;
    2779         }
    2780 
    2781         if ( options.uploading && ! options.percent ) {
    2782             options.percent = 0;
    2783         }
    2784 
    2785         this.views.detach();
    2786         this.$el.html( this.template( options ) );
    2787 
    2788         this.$el.toggleClass( 'uploading', options.uploading );
    2789 
    2790         if ( options.uploading ) {
    2791             this.$bar = this.$('.media-progress-bar div');
    2792         } else {
    2793             delete this.$bar;
    2794         }
    2795 
    2796         // Check if the model is selected.
    2797         this.updateSelect();
    2798 
    2799         // Update the save status.
    2800         this.updateSave();
    2801 
    2802         this.views.render();
    2803 
    2804         return this;
    2805     },
    2806 
    2807     progress: function() {
    2808         if ( this.$bar && this.$bar.length ) {
    2809             this.$bar.width( this.model.get('percent') + '%' );
    2810         }
    2811     },
    2812 
    2813     /**
    2814      * @param {Object} event
    2815      */
    2816     toggleSelectionHandler: function( event ) {
    2817         var method;
    2818 
    2819         // Don't do anything inside inputs and on the attachment check and remove buttons.
    2820         if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
    2821             return;
    2822         }
    2823 
    2824         // Catch arrow events
    2825         if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    2826             this.controller.trigger( 'attachment:keydown:arrow', event );
    2827             return;
    2828         }
    2829 
    2830         // Catch enter and space events
    2831         if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    2832             return;
    2833         }
    2834 
    2835         event.preventDefault();
    2836 
    2837         // In the grid view, bubble up an edit:attachment event to the controller.
    2838         if ( this.controller.isModeActive( 'grid' ) ) {
    2839             if ( this.controller.isModeActive( 'edit' ) ) {
    2840                 // Pass the current target to restore focus when closing
    2841                 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
    2842                 return;
    2843             }
    2844 
    2845             if ( this.controller.isModeActive( 'select' ) ) {
    2846                 method = 'toggle';
    2847             }
    2848         }
    2849 
    2850         if ( event.shiftKey ) {
    2851             method = 'between';
    2852         } else if ( event.ctrlKey || event.metaKey ) {
    2853             method = 'toggle';
    2854         }
    2855 
    2856         this.toggleSelection({
    2857             method: method
    2858         });
    2859 
    2860         this.controller.trigger( 'selection:toggle' );
    2861     },
    2862     /**
    2863      * @param {Object} options
    2864      */
    2865     toggleSelection: function( options ) {
    2866         var collection = this.collection,
    2867             selection = this.options.selection,
    2868             model = this.model,
    2869             method = options && options.method,
    2870             single, models, singleIndex, modelIndex;
    2871 
    2872         if ( ! selection ) {
    2873             return;
    2874         }
    2875 
    2876         single = selection.single();
    2877         method = _.isUndefined( method ) ? selection.multiple : method;
    2878 
    2879         // If the `method` is set to `between`, select all models that
    2880         // exist between the current and the selected model.
    2881         if ( 'between' === method && single && selection.multiple ) {
    2882             // If the models are the same, short-circuit.
    2883             if ( single === model ) {
    2884                 return;
    2885             }
    2886 
    2887             singleIndex = collection.indexOf( single );
    2888             modelIndex  = collection.indexOf( this.model );
    2889 
    2890             if ( singleIndex < modelIndex ) {
    2891                 models = collection.models.slice( singleIndex, modelIndex + 1 );
    2892             } else {
    2893                 models = collection.models.slice( modelIndex, singleIndex + 1 );
    2894             }
    2895 
    2896             selection.add( models );
    2897             selection.single( model );
    2898             return;
    2899 
    2900         // If the `method` is set to `toggle`, just flip the selection
    2901         // status, regardless of whether the model is the single model.
    2902         } else if ( 'toggle' === method ) {
    2903             selection[ this.selected() ? 'remove' : 'add' ]( model );
    2904             selection.single( model );
    2905             return;
    2906         } else if ( 'add' === method ) {
    2907             selection.add( model );
    2908             selection.single( model );
    2909             return;
    2910         }
    2911 
    2912         // Fixes bug that loses focus when selecting a featured image
    2913         if ( ! method ) {
    2914             method = 'add';
    2915         }
    2916 
    2917         if ( method !== 'add' ) {
    2918             method = 'reset';
    2919         }
    2920 
    2921         if ( this.selected() ) {
    2922             // If the model is the single model, remove it.
    2923             // If it is not the same as the single model,
    2924             // it now becomes the single model.
    2925             selection[ single === model ? 'remove' : 'single' ]( model );
    2926         } else {
    2927             // If the model is not selected, run the `method` on the
    2928             // selection. By default, we `reset` the selection, but the
    2929             // `method` can be set to `add` the model to the selection.
    2930             selection[ method ]( model );
    2931             selection.single( model );
    2932         }
    2933     },
    2934 
    2935     updateSelect: function() {
    2936         this[ this.selected() ? 'select' : 'deselect' ]();
    2937     },
    2938     /**
    2939      * @returns {unresolved|Boolean}
    2940      */
    2941     selected: function() {
    2942         var selection = this.options.selection;
    2943         if ( selection ) {
    2944             return !! selection.get( this.model.cid );
    2945         }
    2946     },
    2947     /**
    2948      * @param {Backbone.Model} model
    2949      * @param {Backbone.Collection} collection
    2950      */
    2951     select: function( model, collection ) {
    2952         var selection = this.options.selection,
    2953             controller = this.controller;
    2954 
    2955         // Check if a selection exists and if it's the collection provided.
    2956         // If they're not the same collection, bail; we're in another
    2957         // selection's event loop.
    2958         if ( ! selection || ( collection && collection !== selection ) ) {
    2959             return;
    2960         }
    2961 
    2962         // Bail if the model is already selected.
    2963         if ( this.$el.hasClass( 'selected' ) ) {
    2964             return;
    2965         }
    2966 
    2967         // Add 'selected' class to model, set aria-checked to true.
    2968         this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
    2969         //  Make the checkbox tabable, except in media grid (bulk select mode).
    2970         if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
    2971             this.$( '.check' ).attr( 'tabindex', '0' );
    2972         }
    2973     },
    2974     /**
    2975      * @param {Backbone.Model} model
    2976      * @param {Backbone.Collection} collection
    2977      */
    2978     deselect: function( model, collection ) {
    2979         var selection = this.options.selection;
    2980 
    2981         // Check if a selection exists and if it's the collection provided.
    2982         // If they're not the same collection, bail; we're in another
    2983         // selection's event loop.
    2984         if ( ! selection || ( collection && collection !== selection ) ) {
    2985             return;
    2986         }
    2987         this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
    2988             .find( '.check' ).attr( 'tabindex', '-1' );
    2989     },
    2990     /**
    2991      * @param {Backbone.Model} model
    2992      * @param {Backbone.Collection} collection
    2993      */
    2994     details: function( model, collection ) {
    2995         var selection = this.options.selection,
    2996             details;
    2997 
    2998         if ( selection !== collection ) {
    2999             return;
    3000         }
    3001 
    3002         details = selection.single();
    3003         this.$el.toggleClass( 'details', details === this.model );
    3004     },
    3005     /**
    3006      * @param {string} size
    3007      * @returns {Object}
    3008      */
    3009     imageSize: function( size ) {
    3010         var sizes = this.model.get('sizes'), matched = false;
    3011 
    3012         size = size || 'medium';
    3013 
    3014         // Use the provided image size if possible.
    3015         if ( sizes ) {
    3016             if ( sizes[ size ] ) {
    3017                 matched = sizes[ size ];
    3018             } else if ( sizes.large ) {
    3019                 matched = sizes.large;
    3020             } else if ( sizes.thumbnail ) {
    3021                 matched = sizes.thumbnail;
    3022             } else if ( sizes.full ) {
    3023                 matched = sizes.full;
    3024             }
    3025 
    3026             if ( matched ) {
    3027                 return _.clone( matched );
    3028             }
    3029         }
    3030 
    3031         return {
    3032             url:         this.model.get('url'),
    3033             width:       this.model.get('width'),
    3034             height:      this.model.get('height'),
    3035             orientation: this.model.get('orientation')
     6927
     6928        if ( wp.media.view.settings.post.id ) {
     6929            filters.uploaded = {
     6930                text:  l10n.uploadedToThisPost,
     6931                props: {
     6932                    status:  null,
     6933                    type:    null,
     6934                    uploadedTo: wp.media.view.settings.post.id,
     6935                    orderby: 'menuOrder',
     6936                    order:   'ASC'
     6937                },
     6938                priority: 20
     6939            };
     6940        }
     6941
     6942        filters.unattached = {
     6943            text:  l10n.unattached,
     6944            props: {
     6945                status:     null,
     6946                uploadedTo: 0,
     6947                type:       null,
     6948                orderby:    'menuOrder',
     6949                order:      'ASC'
     6950            },
     6951            priority: 50
    30366952        };
    3037     },
    3038     /**
    3039      * @param {Object} event
    3040      */
    3041     updateSetting: function( event ) {
    3042         var $setting = $( event.target ).closest('[data-setting]'),
    3043             setting, value;
    3044 
    3045         if ( ! $setting.length ) {
    3046             return;
    3047         }
    3048 
    3049         setting = $setting.data('setting');
    3050         value   = event.target.value;
    3051 
    3052         if ( this.model.get( setting ) !== value ) {
    3053             this.save( setting, value );
    3054         }
    3055     },
    3056 
    3057     /**
    3058      * Pass all the arguments to the model's save method.
    3059      *
    3060      * Records the aggregate status of all save requests and updates the
    3061      * view's classes accordingly.
    3062      */
    3063     save: function() {
    3064         var view = this,
    3065             save = this._save = this._save || { status: 'ready' },
    3066             request = this.model.save.apply( this.model, arguments ),
    3067             requests = save.requests ? $.when( request, save.requests ) : request;
    3068 
    3069         // If we're waiting to remove 'Saved.', stop.
    3070         if ( save.savedTimer ) {
    3071             clearTimeout( save.savedTimer );
    3072         }
    3073 
    3074         this.updateSave('waiting');
    3075         save.requests = requests;
    3076         requests.always( function() {
    3077             // If we've performed another request since this one, bail.
    3078             if ( save.requests !== requests ) {
    3079                 return;
    3080             }
    3081 
    3082             view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
    3083             save.savedTimer = setTimeout( function() {
    3084                 view.updateSave('ready');
    3085                 delete save.savedTimer;
    3086             }, 2000 );
    3087         });
    3088     },
    3089     /**
    3090      * @param {string} status
    3091      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3092      */
    3093     updateSave: function( status ) {
    3094         var save = this._save = this._save || { status: 'ready' };
    3095 
    3096         if ( status && status !== save.status ) {
    3097             this.$el.removeClass( 'save-' + save.status );
    3098             save.status = status;
    3099         }
    3100 
    3101         this.$el.addClass( 'save-' + save.status );
    3102         return this;
    3103     },
    3104 
    3105     updateAll: function() {
    3106         var $settings = this.$('[data-setting]'),
    3107             model = this.model,
    3108             changed;
    3109 
    3110         changed = _.chain( $settings ).map( function( el ) {
    3111             var $input = $('input, textarea, select, [value]', el ),
    3112                 setting, value;
    3113 
    3114             if ( ! $input.length ) {
    3115                 return;
    3116             }
    3117 
    3118             setting = $(el).data('setting');
    3119             value = $input.val();
    3120 
    3121             // Record the value if it changed.
    3122             if ( model.get( setting ) !== value ) {
    3123                 return [ setting, value ];
    3124             }
    3125         }).compact().object().value();
    3126 
    3127         if ( ! _.isEmpty( changed ) ) {
    3128             model.save( changed );
    3129         }
    3130     },
    3131     /**
    3132      * @param {Object} event
    3133      */
    3134     removeFromLibrary: function( event ) {
    3135         // Catch enter and space events
    3136         if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    3137             return;
    3138         }
    3139 
    3140         // Stop propagation so the model isn't selected.
    3141         event.stopPropagation();
    3142 
    3143         this.collection.remove( this.model );
    3144     },
    3145 
    3146     /**
    3147      * Add the model if it isn't in the selection, if it is in the selection,
    3148      * remove it.
    3149      *
    3150      * @param  {[type]} event [description]
    3151      * @return {[type]}       [description]
    3152      */
    3153     checkClickHandler: function ( event ) {
    3154         var selection = this.options.selection;
    3155         if ( ! selection ) {
    3156             return;
    3157         }
    3158         event.stopPropagation();
    3159         if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
    3160             selection.remove( this.model );
    3161             // Move focus back to the attachment tile (from the check).
    3162             this.$el.focus();
    3163         } else {
    3164             selection.add( this.model );
    3165         }
     6953
     6954        if ( wp.media.view.settings.mediaTrash &&
     6955            this.controller.isModeActive( 'grid' ) ) {
     6956
     6957            filters.trash = {
     6958                text:  l10n.trash,
     6959                props: {
     6960                    uploadedTo: null,
     6961                    status:     'trash',
     6962                    type:       null,
     6963                    orderby:    'date',
     6964                    order:      'DESC'
     6965                },
     6966                priority: 50
     6967            };
     6968        }
     6969
     6970        this.filters = filters;
    31666971    }
    31676972});
    31686973
    3169 // Ensure settings remain in sync between attachment views.
    3170 _.each({
    3171     caption: '_syncCaption',
    3172     title:   '_syncTitle',
    3173     artist:  '_syncArtist',
    3174     album:   '_syncAlbum'
    3175 }, function( method, setting ) {
    3176     /**
    3177      * @param {Backbone.Model} model
    3178      * @param {string} value
    3179      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3180      */
    3181     Attachment.prototype[ method ] = function( model, value ) {
    3182         var $setting = this.$('[data-setting="' + setting + '"]');
    3183 
    3184         if ( ! $setting.length ) {
    3185             return this;
    3186         }
    3187 
    3188         // If the updated value is in sync with the value in the DOM, there
    3189         // is no need to re-render. If we're currently editing the value,
    3190         // it will automatically be in sync, suppressing the re-render for
    3191         // the view we're editing, while updating any others.
    3192         if ( value === $setting.find('input, textarea, select, [value]').val() ) {
    3193             return this;
    3194         }
    3195 
    3196         return this.render();
    3197     };
    3198 });
    3199 
    3200 module.exports = Attachment;
    3201 
    3202 },{}],26:[function(require,module,exports){
    3203 /**
    3204  * wp.media.view.Attachment.Details
    3205  *
    3206  * @class
    3207  * @augments wp.media.view.Attachment
    3208  * @augments wp.media.View
    3209  * @augments wp.Backbone.View
    3210  * @augments Backbone.View
    3211  */
    3212 var Attachment = wp.media.view.Attachment,
    3213     l10n = wp.media.view.l10n,
    3214     Details;
    3215 
    3216 Details = Attachment.extend({
    3217     tagName:   'div',
    3218     className: 'attachment-details',
    3219     template:  wp.template('attachment-details'),
    3220 
    3221     attributes: function() {
    3222         return {
    3223             'tabIndex':     0,
    3224             'data-id':      this.model.get( 'id' )
    3225         };
    3226     },
    3227 
    3228     events: {
    3229         'change [data-setting]':          'updateSetting',
    3230         'change [data-setting] input':    'updateSetting',
    3231         'change [data-setting] select':   'updateSetting',
    3232         'change [data-setting] textarea': 'updateSetting',
    3233         'click .delete-attachment':       'deleteAttachment',
    3234         'click .trash-attachment':        'trashAttachment',
    3235         'click .untrash-attachment':      'untrashAttachment',
    3236         'click .edit-attachment':         'editAttachment',
    3237         'keydown':                        'toggleSelectionHandler'
    3238     },
    3239 
    3240     initialize: function() {
    3241         this.options = _.defaults( this.options, {
    3242             rerenderOnModelChange: false
    3243         });
    3244 
    3245         this.on( 'ready', this.initialFocus );
    3246         // Call 'initialize' directly on the parent class.
    3247         Attachment.prototype.initialize.apply( this, arguments );
    3248     },
    3249 
    3250     initialFocus: function() {
    3251         if ( ! wp.media.isTouchDevice ) {
    3252             /*
    3253             Previously focused the first ':input' (the readonly URL text field).
    3254             Since the first ':input' is now a button (delete/trash): when pressing
    3255             spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
    3256             as soon as focus is moved. Explicitly target the first text field for now.
    3257             @todo change initial focus logic, also for accessibility.
    3258             */
    3259             this.$( 'input[type="text"]' ).eq( 0 ).focus();
    3260         }
    3261     },
    3262     /**
    3263      * @param {Object} event
    3264      */
    3265     deleteAttachment: function( event ) {
    3266         event.preventDefault();
    3267 
    3268         if ( window.confirm( l10n.warnDelete ) ) {
    3269             this.model.destroy();
    3270             // Keep focus inside media modal
    3271             // after image is deleted
    3272             this.controller.modal.focusManager.focus();
    3273         }
    3274     },
    3275     /**
    3276      * @param {Object} event
    3277      */
    3278     trashAttachment: function( event ) {
    3279         var library = this.controller.library;
    3280         event.preventDefault();
    3281 
    3282         if ( wp.media.view.settings.mediaTrash &&
    3283             'edit-metadata' === this.controller.content.mode() ) {
    3284 
    3285             this.model.set( 'status', 'trash' );
    3286             this.model.save().done( function() {
    3287                 library._requery( true );
    3288             } );
    3289         }  else {
    3290             this.model.destroy();
    3291         }
    3292     },
    3293     /**
    3294      * @param {Object} event
    3295      */
    3296     untrashAttachment: function( event ) {
    3297         var library = this.controller.library;
    3298         event.preventDefault();
    3299 
    3300         this.model.set( 'status', 'inherit' );
    3301         this.model.save().done( function() {
    3302             library._requery( true );
    3303         } );
    3304     },
    3305     /**
    3306      * @param {Object} event
    3307      */
    3308     editAttachment: function( event ) {
    3309         var editState = this.controller.states.get( 'edit-image' );
    3310         if ( window.imageEdit && editState ) {
    3311             event.preventDefault();
    3312 
    3313             editState.set( 'image', this.model );
    3314             this.controller.setState( 'edit-image' );
    3315         } else {
    3316             this.$el.addClass('needs-refresh');
    3317         }
    3318     },
    3319     /**
    3320      * When reverse tabbing(shift+tab) out of the right details panel, deliver
    3321      * the focus to the item in the list that was being edited.
    3322      *
    3323      * @param {Object} event
    3324      */
    3325     toggleSelectionHandler: function( event ) {
    3326         if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
    3327             this.controller.trigger( 'attachment:details:shift-tab', event );
    3328             return false;
    3329         }
    3330 
    3331         if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    3332             this.controller.trigger( 'attachment:keydown:arrow', event );
    3333             return;
    3334         }
    3335     }
    3336 });
    3337 
    3338 module.exports = Details;
    3339 
    3340 },{}],27:[function(require,module,exports){
    3341 /**
    3342  * wp.media.view.Attachment.EditLibrary
    3343  *
    3344  * @class
    3345  * @augments wp.media.view.Attachment
    3346  * @augments wp.media.View
    3347  * @augments wp.Backbone.View
    3348  * @augments Backbone.View
    3349  */
    3350 var EditLibrary = wp.media.view.Attachment.extend({
    3351     buttons: {
    3352         close: true
    3353     }
    3354 });
    3355 
    3356 module.exports = EditLibrary;
    3357 
    3358 },{}],28:[function(require,module,exports){
    3359 /**
    3360  * wp.media.view.Attachments.EditSelection
    3361  *
    3362  * @class
    3363  * @augments wp.media.view.Attachment.Selection
    3364  * @augments wp.media.view.Attachment
    3365  * @augments wp.media.View
    3366  * @augments wp.Backbone.View
    3367  * @augments Backbone.View
    3368  */
    3369 var EditSelection = wp.media.view.Attachment.Selection.extend({
    3370     buttons: {
    3371         close: true
    3372     }
    3373 });
    3374 
    3375 module.exports = EditSelection;
    3376 
    3377 },{}],29:[function(require,module,exports){
    3378 /**
    3379  * wp.media.view.Attachment.Library
    3380  *
    3381  * @class
    3382  * @augments wp.media.view.Attachment
    3383  * @augments wp.media.View
    3384  * @augments wp.Backbone.View
    3385  * @augments Backbone.View
    3386  */
    3387 var Library = wp.media.view.Attachment.extend({
    3388     buttons: {
    3389         check: true
    3390     }
    3391 });
    3392 
    3393 module.exports = Library;
    3394 
    3395 },{}],30:[function(require,module,exports){
    3396 /**
    3397  * wp.media.view.Attachment.Selection
    3398  *
    3399  * @class
    3400  * @augments wp.media.view.Attachment
    3401  * @augments wp.media.View
    3402  * @augments wp.Backbone.View
    3403  * @augments Backbone.View
    3404  */
    3405 var Selection = wp.media.view.Attachment.extend({
    3406     className: 'attachment selection',
    3407 
    3408     // On click, just select the model, instead of removing the model from
    3409     // the selection.
    3410     toggleSelection: function() {
    3411         this.options.selection.single( this.model );
    3412     }
    3413 });
    3414 
    3415 module.exports = Selection;
    3416 
    3417 },{}],31:[function(require,module,exports){
    3418 /**
    3419  * wp.media.view.Attachments
    3420  *
    3421  * @class
    3422  * @augments wp.media.View
    3423  * @augments wp.Backbone.View
    3424  * @augments Backbone.View
    3425  */
    3426 var View = wp.media.View,
    3427     $ = jQuery,
    3428     Attachments;
    3429 
    3430 Attachments = View.extend({
    3431     tagName:   'ul',
    3432     className: 'attachments',
    3433 
    3434     attributes: {
    3435         tabIndex: -1
    3436     },
    3437 
    3438     initialize: function() {
    3439         this.el.id = _.uniqueId('__attachments-view-');
    3440 
    3441         _.defaults( this.options, {
    3442             refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
    3443             refreshThreshold:   3,
    3444             AttachmentView:     wp.media.view.Attachment,
    3445             sortable:           false,
    3446             resize:             true,
    3447             idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
    3448         });
    3449 
    3450         this._viewsByCid = {};
    3451         this.$window = $( window );
    3452         this.resizeEvent = 'resize.media-modal-columns';
    3453 
    3454         this.collection.on( 'add', function( attachment ) {
    3455             this.views.add( this.createAttachmentView( attachment ), {
    3456                 at: this.collection.indexOf( attachment )
    3457             });
    3458         }, this );
    3459 
    3460         this.collection.on( 'remove', function( attachment ) {
    3461             var view = this._viewsByCid[ attachment.cid ];
    3462             delete this._viewsByCid[ attachment.cid ];
    3463 
    3464             if ( view ) {
    3465                 view.remove();
    3466             }
    3467         }, this );
    3468 
    3469         this.collection.on( 'reset', this.render, this );
    3470 
    3471         this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
    3472 
    3473         // Throttle the scroll handler and bind this.
    3474         this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
    3475 
    3476         this.options.scrollElement = this.options.scrollElement || this.el;
    3477         $( this.options.scrollElement ).on( 'scroll', this.scroll );
    3478 
    3479         this.initSortable();
    3480 
    3481         _.bindAll( this, 'setColumns' );
    3482 
    3483         if ( this.options.resize ) {
    3484             this.on( 'ready', this.bindEvents );
    3485             this.controller.on( 'open', this.setColumns );
    3486 
    3487             // Call this.setColumns() after this view has been rendered in the DOM so
    3488             // attachments get proper width applied.
    3489             _.defer( this.setColumns, this );
    3490         }
    3491     },
    3492 
    3493     bindEvents: function() {
    3494         this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
    3495     },
    3496 
    3497     attachmentFocus: function() {
    3498         this.$( 'li:first' ).focus();
    3499     },
    3500 
    3501     restoreFocus: function() {
    3502         this.$( 'li.selected:first' ).focus();
    3503     },
    3504 
    3505     arrowEvent: function( event ) {
    3506         var attachments = this.$el.children( 'li' ),
    3507             perRow = this.columns,
    3508             index = attachments.filter( ':focus' ).index(),
    3509             row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
    3510 
    3511         if ( index === -1 ) {
    3512             return;
    3513         }
    3514 
    3515         // Left arrow
    3516         if ( 37 === event.keyCode ) {
    3517             if ( 0 === index ) {
    3518                 return;
    3519             }
    3520             attachments.eq( index - 1 ).focus();
    3521         }
    3522 
    3523         // Up arrow
    3524         if ( 38 === event.keyCode ) {
    3525             if ( 1 === row ) {
    3526                 return;
    3527             }
    3528             attachments.eq( index - perRow ).focus();
    3529         }
    3530 
    3531         // Right arrow
    3532         if ( 39 === event.keyCode ) {
    3533             if ( attachments.length === index ) {
    3534                 return;
    3535             }
    3536             attachments.eq( index + 1 ).focus();
    3537         }
    3538 
    3539         // Down arrow
    3540         if ( 40 === event.keyCode ) {
    3541             if ( Math.ceil( attachments.length / perRow ) === row ) {
    3542                 return;
    3543             }
    3544             attachments.eq( index + perRow ).focus();
    3545         }
    3546     },
    3547 
    3548     dispose: function() {
    3549         this.collection.props.off( null, null, this );
    3550         if ( this.options.resize ) {
    3551             this.$window.off( this.resizeEvent );
    3552         }
    3553 
    3554         /**
    3555          * call 'dispose' directly on the parent class
    3556          */
    3557         View.prototype.dispose.apply( this, arguments );
    3558     },
    3559 
    3560     setColumns: function() {
    3561         var prev = this.columns,
    3562             width = this.$el.width();
    3563 
    3564         if ( width ) {
    3565             this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
    3566 
    3567             if ( ! prev || prev !== this.columns ) {
    3568                 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
    3569             }
    3570         }
    3571     },
    3572 
    3573     initSortable: function() {
    3574         var collection = this.collection;
    3575 
    3576         if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
    3577             return;
    3578         }
    3579 
    3580         this.$el.sortable( _.extend({
    3581             // If the `collection` has a `comparator`, disable sorting.
    3582             disabled: !! collection.comparator,
    3583 
    3584             // Change the position of the attachment as soon as the
    3585             // mouse pointer overlaps a thumbnail.
    3586             tolerance: 'pointer',
    3587 
    3588             // Record the initial `index` of the dragged model.
    3589             start: function( event, ui ) {
    3590                 ui.item.data('sortableIndexStart', ui.item.index());
    3591             },
    3592 
    3593             // Update the model's index in the collection.
    3594             // Do so silently, as the view is already accurate.
    3595             update: function( event, ui ) {
    3596                 var model = collection.at( ui.item.data('sortableIndexStart') ),
    3597                     comparator = collection.comparator;
    3598 
    3599                 // Temporarily disable the comparator to prevent `add`
    3600                 // from re-sorting.
    3601                 delete collection.comparator;
    3602 
    3603                 // Silently shift the model to its new index.
    3604                 collection.remove( model, {
    3605                     silent: true
    3606                 });
    3607                 collection.add( model, {
    3608                     silent: true,
    3609                     at:     ui.item.index()
    3610                 });
    3611 
    3612                 // Restore the comparator.
    3613                 collection.comparator = comparator;
    3614 
    3615                 // Fire the `reset` event to ensure other collections sync.
    3616                 collection.trigger( 'reset', collection );
    3617 
    3618                 // If the collection is sorted by menu order,
    3619                 // update the menu order.
    3620                 collection.saveMenuOrder();
    3621             }
    3622         }, this.options.sortable ) );
    3623 
    3624         // If the `orderby` property is changed on the `collection`,
    3625         // check to see if we have a `comparator`. If so, disable sorting.
    3626         collection.props.on( 'change:orderby', function() {
    3627             this.$el.sortable( 'option', 'disabled', !! collection.comparator );
    3628         }, this );
    3629 
    3630         this.collection.props.on( 'change:orderby', this.refreshSortable, this );
    3631         this.refreshSortable();
    3632     },
    3633 
    3634     refreshSortable: function() {
    3635         if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
    3636             return;
    3637         }
    3638 
    3639         // If the `collection` has a `comparator`, disable sorting.
    3640         var collection = this.collection,
    3641             orderby = collection.props.get('orderby'),
    3642             enabled = 'menuOrder' === orderby || ! collection.comparator;
    3643 
    3644         this.$el.sortable( 'option', 'disabled', ! enabled );
    3645     },
    3646 
    3647     /**
    3648      * @param {wp.media.model.Attachment} attachment
    3649      * @returns {wp.media.View}
    3650      */
    3651     createAttachmentView: function( attachment ) {
    3652         var view = new this.options.AttachmentView({
    3653             controller:           this.controller,
    3654             model:                attachment,
    3655             collection:           this.collection,
    3656             selection:            this.options.selection
    3657         });
    3658 
    3659         return this._viewsByCid[ attachment.cid ] = view;
    3660     },
    3661 
    3662     prepare: function() {
    3663         // Create all of the Attachment views, and replace
    3664         // the list in a single DOM operation.
    3665         if ( this.collection.length ) {
    3666             this.views.set( this.collection.map( this.createAttachmentView, this ) );
    3667 
    3668         // If there are no elements, clear the views and load some.
    3669         } else {
    3670             this.views.unset();
    3671             this.collection.more().done( this.scroll );
    3672         }
    3673     },
    3674 
    3675     ready: function() {
    3676         // Trigger the scroll event to check if we're within the
    3677         // threshold to query for additional attachments.
    3678         this.scroll();
    3679     },
    3680 
    3681     scroll: function() {
    3682         var view = this,
    3683             el = this.options.scrollElement,
    3684             scrollTop = el.scrollTop,
    3685             toolbar;
    3686 
    3687         // The scroll event occurs on the document, but the element
    3688         // that should be checked is the document body.
    3689         if ( el === document ) {
    3690             el = document.body;
    3691             scrollTop = $(document).scrollTop();
    3692         }
    3693 
    3694         if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
    3695             return;
    3696         }
    3697 
    3698         toolbar = this.views.parent.toolbar;
    3699 
    3700         // Show the spinner only if we are close to the bottom.
    3701         if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
    3702             toolbar.get('spinner').show();
    3703         }
    3704 
    3705         if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
    3706             this.collection.more().done(function() {
    3707                 view.scroll();
    3708                 toolbar.get('spinner').hide();
    3709             });
    3710         }
    3711     }
    3712 });
    3713 
    3714 module.exports = Attachments;
    3715 
    3716 },{}],32:[function(require,module,exports){
     6974module.exports = All;
     6975
     6976
     6977/***/ }),
     6978/* 78 */
     6979/***/ (function(module, exports) {
     6980
    37176981/**
    37186982 * wp.media.view.AttachmentsBrowser
     
    41587422module.exports = AttachmentsBrowser;
    41597423
    4160 },{}],33:[function(require,module,exports){
     7424
     7425/***/ }),
     7426/* 79 */
     7427/***/ (function(module, exports) {
     7428
     7429/**
     7430 * wp.media.view.Selection
     7431 *
     7432 * @class
     7433 * @augments wp.media.View
     7434 * @augments wp.Backbone.View
     7435 * @augments Backbone.View
     7436 */
     7437var l10n = wp.media.view.l10n,
     7438    Selection;
     7439
     7440Selection = wp.media.View.extend({
     7441    tagName:   'div',
     7442    className: 'media-selection',
     7443    template:  wp.template('media-selection'),
     7444
     7445    events: {
     7446        'click .edit-selection':  'edit',
     7447        'click .clear-selection': 'clear'
     7448    },
     7449
     7450    initialize: function() {
     7451        _.defaults( this.options, {
     7452            editable:  false,
     7453            clearable: true
     7454        });
     7455
     7456        /**
     7457         * @member {wp.media.view.Attachments.Selection}
     7458         */
     7459        this.attachments = new wp.media.view.Attachments.Selection({
     7460            controller: this.controller,
     7461            collection: this.collection,
     7462            selection:  this.collection,
     7463            model:      new Backbone.Model()
     7464        });
     7465
     7466        this.views.set( '.selection-view', this.attachments );
     7467        this.collection.on( 'add remove reset', this.refresh, this );
     7468        this.controller.on( 'content:activate', this.refresh, this );
     7469    },
     7470
     7471    ready: function() {
     7472        this.refresh();
     7473    },
     7474
     7475    refresh: function() {
     7476        // If the selection hasn't been rendered, bail.
     7477        if ( ! this.$el.children().length ) {
     7478            return;
     7479        }
     7480
     7481        var collection = this.collection,
     7482            editing = 'edit-selection' === this.controller.content.mode();
     7483
     7484        // If nothing is selected, display nothing.
     7485        this.$el.toggleClass( 'empty', ! collection.length );
     7486        this.$el.toggleClass( 'one', 1 === collection.length );
     7487        this.$el.toggleClass( 'editing', editing );
     7488
     7489        this.$('.count').text( l10n.selected.replace('%d', collection.length) );
     7490    },
     7491
     7492    edit: function( event ) {
     7493        event.preventDefault();
     7494        if ( this.options.editable ) {
     7495            this.options.editable.call( this, this.collection );
     7496        }
     7497    },
     7498
     7499    clear: function( event ) {
     7500        event.preventDefault();
     7501        this.collection.reset();
     7502
     7503        // Keep focus inside media modal
     7504        // after clear link is selected
     7505        this.controller.modal.focusManager.focus();
     7506    }
     7507});
     7508
     7509module.exports = Selection;
     7510
     7511
     7512/***/ }),
     7513/* 80 */
     7514/***/ (function(module, exports) {
     7515
     7516/**
     7517 * wp.media.view.Attachment.Selection
     7518 *
     7519 * @class
     7520 * @augments wp.media.view.Attachment
     7521 * @augments wp.media.View
     7522 * @augments wp.Backbone.View
     7523 * @augments Backbone.View
     7524 */
     7525var Selection = wp.media.view.Attachment.extend({
     7526    className: 'attachment selection',
     7527
     7528    // On click, just select the model, instead of removing the model from
     7529    // the selection.
     7530    toggleSelection: function() {
     7531        this.options.selection.single( this.model );
     7532    }
     7533});
     7534
     7535module.exports = Selection;
     7536
     7537
     7538/***/ }),
     7539/* 81 */
     7540/***/ (function(module, exports) {
     7541
    41617542/**
    41627543 * wp.media.view.Attachments.Selection
     
    41887569module.exports = Selection;
    41897570
    4190 },{}],34:[function(require,module,exports){
     7571
     7572/***/ }),
     7573/* 82 */
     7574/***/ (function(module, exports) {
     7575
    41917576/**
    4192  * wp.media.view.ButtonGroup
     7577 * wp.media.view.Attachments.EditSelection
    41937578 *
    41947579 * @class
     7580 * @augments wp.media.view.Attachment.Selection
     7581 * @augments wp.media.view.Attachment
    41957582 * @augments wp.media.View
    41967583 * @augments wp.Backbone.View
    41977584 * @augments Backbone.View
    41987585 */
    4199 var $ = Backbone.$,
    4200     ButtonGroup;
    4201 
    4202 ButtonGroup = wp.media.View.extend({
    4203     tagName:   'div',
    4204     className: 'button-group button-large media-button-group',
    4205 
    4206     initialize: function() {
    4207         /**
    4208          * @member {wp.media.view.Button[]}
    4209          */
    4210         this.buttons = _.map( this.options.buttons || [], function( button ) {
    4211             if ( button instanceof Backbone.View ) {
    4212                 return button;
    4213             } else {
    4214                 return new wp.media.view.Button( button ).render();
    4215             }
    4216         });
    4217 
    4218         delete this.options.buttons;
    4219 
    4220         if ( this.options.classes ) {
    4221             this.$el.addClass( this.options.classes );
    4222         }
    4223     },
    4224 
    4225     /**
    4226      * @returns {wp.media.view.ButtonGroup}
    4227      */
    4228     render: function() {
    4229         this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
    4230         return this;
     7586var EditSelection = wp.media.view.Attachment.Selection.extend({
     7587    buttons: {
     7588        close: true
    42317589    }
    42327590});
    42337591
    4234 module.exports = ButtonGroup;
    4235 
    4236 },{}],35:[function(require,module,exports){
     7592module.exports = EditSelection;
     7593
     7594
     7595/***/ }),
     7596/* 83 */
     7597/***/ (function(module, exports) {
     7598
    42377599/**
    4238  * wp.media.view.Button
    4239  *
    4240  * @class
    4241  * @augments wp.media.View
    4242  * @augments wp.Backbone.View
    4243  * @augments Backbone.View
    4244  */
    4245 var Button = wp.media.View.extend({
    4246     tagName:    'button',
    4247     className:  'media-button',
    4248     attributes: { type: 'button' },
    4249 
    4250     events: {
    4251         'click': 'click'
    4252     },
    4253 
    4254     defaults: {
    4255         text:     '',
    4256         style:    '',
    4257         size:     'large',
    4258         disabled: false
    4259     },
    4260 
    4261     initialize: function() {
    4262         /**
    4263          * Create a model with the provided `defaults`.
    4264          *
    4265          * @member {Backbone.Model}
    4266          */
    4267         this.model = new Backbone.Model( this.defaults );
    4268 
    4269         // If any of the `options` have a key from `defaults`, apply its
    4270         // value to the `model` and remove it from the `options object.
    4271         _.each( this.defaults, function( def, key ) {
    4272             var value = this.options[ key ];
    4273             if ( _.isUndefined( value ) ) {
    4274                 return;
    4275             }
    4276 
    4277             this.model.set( key, value );
    4278             delete this.options[ key ];
    4279         }, this );
    4280 
    4281         this.listenTo( this.model, 'change', this.render );
    4282     },
    4283     /**
    4284      * @returns {wp.media.view.Button} Returns itself to allow chaining
    4285      */
    4286     render: function() {
    4287         var classes = [ 'button', this.className ],
    4288             model = this.model.toJSON();
    4289 
    4290         if ( model.style ) {
    4291             classes.push( 'button-' + model.style );
    4292         }
    4293 
    4294         if ( model.size ) {
    4295             classes.push( 'button-' + model.size );
    4296         }
    4297 
    4298         classes = _.uniq( classes.concat( this.options.classes ) );
    4299         this.el.className = classes.join(' ');
    4300 
    4301         this.$el.attr( 'disabled', model.disabled );
    4302         this.$el.text( this.model.get('text') );
    4303 
    4304         return this;
    4305     },
    4306     /**
    4307      * @param {Object} event
    4308      */
    4309     click: function( event ) {
    4310         if ( '#' === this.attributes.href ) {
    4311             event.preventDefault();
    4312         }
    4313 
    4314         if ( this.options.click && ! this.model.get('disabled') ) {
    4315             this.options.click.apply( this, arguments );
    4316         }
    4317     }
    4318 });
    4319 
    4320 module.exports = Button;
    4321 
    4322 },{}],36:[function(require,module,exports){
    4323 /**
    4324  * wp.media.view.Cropper
    4325  *
    4326  * Uses the imgAreaSelect plugin to allow a user to crop an image.
    4327  *
    4328  * Takes imgAreaSelect options from
    4329  * wp.customize.HeaderControl.calculateImageSelectOptions via
    4330  * wp.customize.HeaderControl.openMM.
     7600 * wp.media.view.Settings
    43317601 *
    43327602 * @class
     
    43367606 */
    43377607var View = wp.media.View,
    4338     UploaderStatus = wp.media.view.UploaderStatus,
    4339     l10n = wp.media.view.l10n,
    4340     $ = jQuery,
    4341     Cropper;
    4342 
    4343 Cropper = View.extend({
    4344     className: 'crop-content',
    4345     template: wp.template('crop-content'),
     7608    $ = Backbone.$,
     7609    Settings;
     7610
     7611Settings = View.extend({
     7612    events: {
     7613        'click button':    'updateHandler',
     7614        'change input':    'updateHandler',
     7615        'change select':   'updateHandler',
     7616        'change textarea': 'updateHandler'
     7617    },
     7618
    43467619    initialize: function() {
    4347         _.bindAll(this, 'onImageLoad');
    4348     },
    4349     ready: function() {
    4350         this.controller.frame.on('content:error:crop', this.onError, this);
    4351         this.$image = this.$el.find('.crop-image');
    4352         this.$image.on('load', this.onImageLoad);
    4353         $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
    4354     },
    4355     remove: function() {
    4356         $(window).off('resize.cropper');
    4357         this.$el.remove();
    4358         this.$el.off();
    4359         View.prototype.remove.apply(this, arguments);
    4360     },
     7620        this.model = this.model || new Backbone.Model();
     7621        this.listenTo( this.model, 'change', this.updateChanges );
     7622    },
     7623
    43617624    prepare: function() {
    4362         return {
    4363             title: l10n.cropYourImage,
    4364             url: this.options.attachment.get('url')
    4365         };
    4366     },
    4367     onImageLoad: function() {
    4368         var imgOptions = this.controller.get('imgSelectOptions');
    4369         if (typeof imgOptions === 'function') {
    4370             imgOptions = imgOptions(this.options.attachment, this.controller);
    4371         }
    4372 
    4373         imgOptions = _.extend(imgOptions, {parent: this.$el});
    4374         this.trigger('image-loaded');
    4375         this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
    4376     },
    4377     onError: function() {
    4378         var filename = this.options.attachment.get('filename');
    4379 
    4380         this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    4381             filename: UploaderStatus.prototype.filename(filename),
    4382             message: window._wpMediaViewsL10n.cropError
    4383         }), { at: 0 });
     7625        return _.defaults({
     7626            model: this.model.toJSON()
     7627        }, this.options );
     7628    },
     7629    /**
     7630     * @returns {wp.media.view.Settings} Returns itself to allow chaining
     7631     */
     7632    render: function() {
     7633        View.prototype.render.apply( this, arguments );
     7634        // Select the correct values.
     7635        _( this.model.attributes ).chain().keys().each( this.update, this );
     7636        return this;
     7637    },
     7638    /**
     7639     * @param {string} key
     7640     */
     7641    update: function( key ) {
     7642        var value = this.model.get( key ),
     7643            $setting = this.$('[data-setting="' + key + '"]'),
     7644            $buttons, $value;
     7645
     7646        // Bail if we didn't find a matching setting.
     7647        if ( ! $setting.length ) {
     7648            return;
     7649        }
     7650
     7651        // Attempt to determine how the setting is rendered and update
     7652        // the selected value.
     7653
     7654        // Handle dropdowns.
     7655        if ( $setting.is('select') ) {
     7656            $value = $setting.find('[value="' + value + '"]');
     7657
     7658            if ( $value.length ) {
     7659                $setting.find('option').prop( 'selected', false );
     7660                $value.prop( 'selected', true );
     7661            } else {
     7662                // If we can't find the desired value, record what *is* selected.
     7663                this.model.set( key, $setting.find(':selected').val() );
     7664            }
     7665
     7666        // Handle button groups.
     7667        } else if ( $setting.hasClass('button-group') ) {
     7668            $buttons = $setting.find('button').removeClass('active');
     7669            $buttons.filter( '[value="' + value + '"]' ).addClass('active');
     7670
     7671        // Handle text inputs and textareas.
     7672        } else if ( $setting.is('input[type="text"], textarea') ) {
     7673            if ( ! $setting.is(':focus') ) {
     7674                $setting.val( value );
     7675            }
     7676        // Handle checkboxes.
     7677        } else if ( $setting.is('input[type="checkbox"]') ) {
     7678            $setting.prop( 'checked', !! value && 'false' !== value );
     7679        }
     7680    },
     7681    /**
     7682     * @param {Object} event
     7683     */
     7684    updateHandler: function( event ) {
     7685        var $setting = $( event.target ).closest('[data-setting]'),
     7686            value = event.target.value,
     7687            userSetting;
     7688
     7689        event.preventDefault();
     7690
     7691        if ( ! $setting.length ) {
     7692            return;
     7693        }
     7694
     7695        // Use the correct value for checkboxes.
     7696        if ( $setting.is('input[type="checkbox"]') ) {
     7697            value = $setting[0].checked;
     7698        }
     7699
     7700        // Update the corresponding setting.
     7701        this.model.set( $setting.data('setting'), value );
     7702
     7703        // If the setting has a corresponding user setting,
     7704        // update that as well.
     7705        if ( userSetting = $setting.data('userSetting') ) {
     7706            window.setUserSetting( userSetting, value );
     7707        }
     7708    },
     7709
     7710    updateChanges: function( model ) {
     7711        if ( model.hasChanged() ) {
     7712            _( model.changed ).chain().keys().each( this.update, this );
     7713        }
    43847714    }
    43857715});
    43867716
    4387 module.exports = Cropper;
    4388 
    4389 },{}],37:[function(require,module,exports){
     7717module.exports = Settings;
     7718
     7719
     7720/***/ }),
     7721/* 84 */
     7722/***/ (function(module, exports) {
     7723
    43907724/**
    4391  * wp.media.view.EditImage
     7725 * wp.media.view.Settings.AttachmentDisplay
     7726 *
     7727 * @class
     7728 * @augments wp.media.view.Settings
     7729 * @augments wp.media.View
     7730 * @augments wp.Backbone.View
     7731 * @augments Backbone.View
     7732 */
     7733var Settings = wp.media.view.Settings,
     7734    AttachmentDisplay;
     7735
     7736AttachmentDisplay = Settings.extend({
     7737    className: 'attachment-display-settings',
     7738    template:  wp.template('attachment-display-settings'),
     7739
     7740    initialize: function() {
     7741        var attachment = this.options.attachment;
     7742
     7743        _.defaults( this.options, {
     7744            userSettings: false
     7745        });
     7746        // Call 'initialize' directly on the parent class.
     7747        Settings.prototype.initialize.apply( this, arguments );
     7748        this.listenTo( this.model, 'change:link', this.updateLinkTo );
     7749
     7750        if ( attachment ) {
     7751            attachment.on( 'change:uploading', this.render, this );
     7752        }
     7753    },
     7754
     7755    dispose: function() {
     7756        var attachment = this.options.attachment;
     7757        if ( attachment ) {
     7758            attachment.off( null, null, this );
     7759        }
     7760        /**
     7761         * call 'dispose' directly on the parent class
     7762         */
     7763        Settings.prototype.dispose.apply( this, arguments );
     7764    },
     7765    /**
     7766     * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
     7767     */
     7768    render: function() {
     7769        var attachment = this.options.attachment;
     7770        if ( attachment ) {
     7771            _.extend( this.options, {
     7772                sizes: attachment.get('sizes'),
     7773                type:  attachment.get('type')
     7774            });
     7775        }
     7776        /**
     7777         * call 'render' directly on the parent class
     7778         */
     7779        Settings.prototype.render.call( this );
     7780        this.updateLinkTo();
     7781        return this;
     7782    },
     7783
     7784    updateLinkTo: function() {
     7785        var linkTo = this.model.get('link'),
     7786            $input = this.$('.link-to-custom'),
     7787            attachment = this.options.attachment;
     7788
     7789        if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
     7790            $input.addClass( 'hidden' );
     7791            return;
     7792        }
     7793
     7794        if ( attachment ) {
     7795            if ( 'post' === linkTo ) {
     7796                $input.val( attachment.get('link') );
     7797            } else if ( 'file' === linkTo ) {
     7798                $input.val( attachment.get('url') );
     7799            } else if ( ! this.model.get('linkUrl') ) {
     7800                $input.val('http://');
     7801            }
     7802
     7803            $input.prop( 'readonly', 'custom' !== linkTo );
     7804        }
     7805
     7806        $input.removeClass( 'hidden' );
     7807
     7808        // If the input is visible, focus and select its contents.
     7809        if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
     7810            $input.focus()[0].select();
     7811        }
     7812    }
     7813});
     7814
     7815module.exports = AttachmentDisplay;
     7816
     7817
     7818/***/ }),
     7819/* 85 */
     7820/***/ (function(module, exports) {
     7821
     7822/**
     7823 * wp.media.view.Settings.Gallery
     7824 *
     7825 * @class
     7826 * @augments wp.media.view.Settings
     7827 * @augments wp.media.View
     7828 * @augments wp.Backbone.View
     7829 * @augments Backbone.View
     7830 */
     7831var Gallery = wp.media.view.Settings.extend({
     7832    className: 'collection-settings gallery-settings',
     7833    template:  wp.template('gallery-settings')
     7834});
     7835
     7836module.exports = Gallery;
     7837
     7838
     7839/***/ }),
     7840/* 86 */
     7841/***/ (function(module, exports) {
     7842
     7843/**
     7844 * wp.media.view.Settings.Playlist
     7845 *
     7846 * @class
     7847 * @augments wp.media.view.Settings
     7848 * @augments wp.media.View
     7849 * @augments wp.Backbone.View
     7850 * @augments Backbone.View
     7851 */
     7852var Playlist = wp.media.view.Settings.extend({
     7853    className: 'collection-settings playlist-settings',
     7854    template:  wp.template('playlist-settings')
     7855});
     7856
     7857module.exports = Playlist;
     7858
     7859
     7860/***/ }),
     7861/* 87 */
     7862/***/ (function(module, exports) {
     7863
     7864/**
     7865 * wp.media.view.Attachment.Details
     7866 *
     7867 * @class
     7868 * @augments wp.media.view.Attachment
     7869 * @augments wp.media.View
     7870 * @augments wp.Backbone.View
     7871 * @augments Backbone.View
     7872 */
     7873var Attachment = wp.media.view.Attachment,
     7874    l10n = wp.media.view.l10n,
     7875    Details;
     7876
     7877Details = Attachment.extend({
     7878    tagName:   'div',
     7879    className: 'attachment-details',
     7880    template:  wp.template('attachment-details'),
     7881
     7882    attributes: function() {
     7883        return {
     7884            'tabIndex':     0,
     7885            'data-id':      this.model.get( 'id' )
     7886        };
     7887    },
     7888
     7889    events: {
     7890        'change [data-setting]':          'updateSetting',
     7891        'change [data-setting] input':    'updateSetting',
     7892        'change [data-setting] select':   'updateSetting',
     7893        'change [data-setting] textarea': 'updateSetting',
     7894        'click .delete-attachment':       'deleteAttachment',
     7895        'click .trash-attachment':        'trashAttachment',
     7896        'click .untrash-attachment':      'untrashAttachment',
     7897        'click .edit-attachment':         'editAttachment',
     7898        'keydown':                        'toggleSelectionHandler'
     7899    },
     7900
     7901    initialize: function() {
     7902        this.options = _.defaults( this.options, {
     7903            rerenderOnModelChange: false
     7904        });
     7905
     7906        this.on( 'ready', this.initialFocus );
     7907        // Call 'initialize' directly on the parent class.
     7908        Attachment.prototype.initialize.apply( this, arguments );
     7909    },
     7910
     7911    initialFocus: function() {
     7912        if ( ! wp.media.isTouchDevice ) {
     7913            /*
     7914            Previously focused the first ':input' (the readonly URL text field).
     7915            Since the first ':input' is now a button (delete/trash): when pressing
     7916            spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
     7917            as soon as focus is moved. Explicitly target the first text field for now.
     7918            @todo change initial focus logic, also for accessibility.
     7919            */
     7920            this.$( 'input[type="text"]' ).eq( 0 ).focus();
     7921        }
     7922    },
     7923    /**
     7924     * @param {Object} event
     7925     */
     7926    deleteAttachment: function( event ) {
     7927        event.preventDefault();
     7928
     7929        if ( window.confirm( l10n.warnDelete ) ) {
     7930            this.model.destroy();
     7931            // Keep focus inside media modal
     7932            // after image is deleted
     7933            this.controller.modal.focusManager.focus();
     7934        }
     7935    },
     7936    /**
     7937     * @param {Object} event
     7938     */
     7939    trashAttachment: function( event ) {
     7940        var library = this.controller.library;
     7941        event.preventDefault();
     7942
     7943        if ( wp.media.view.settings.mediaTrash &&
     7944            'edit-metadata' === this.controller.content.mode() ) {
     7945
     7946            this.model.set( 'status', 'trash' );
     7947            this.model.save().done( function() {
     7948                library._requery( true );
     7949            } );
     7950        }  else {
     7951            this.model.destroy();
     7952        }
     7953    },
     7954    /**
     7955     * @param {Object} event
     7956     */
     7957    untrashAttachment: function( event ) {
     7958        var library = this.controller.library;
     7959        event.preventDefault();
     7960
     7961        this.model.set( 'status', 'inherit' );
     7962        this.model.save().done( function() {
     7963            library._requery( true );
     7964        } );
     7965    },
     7966    /**
     7967     * @param {Object} event
     7968     */
     7969    editAttachment: function( event ) {
     7970        var editState = this.controller.states.get( 'edit-image' );
     7971        if ( window.imageEdit && editState ) {
     7972            event.preventDefault();
     7973
     7974            editState.set( 'image', this.model );
     7975            this.controller.setState( 'edit-image' );
     7976        } else {
     7977            this.$el.addClass('needs-refresh');
     7978        }
     7979    },
     7980    /**
     7981     * When reverse tabbing(shift+tab) out of the right details panel, deliver
     7982     * the focus to the item in the list that was being edited.
     7983     *
     7984     * @param {Object} event
     7985     */
     7986    toggleSelectionHandler: function( event ) {
     7987        if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
     7988            this.controller.trigger( 'attachment:details:shift-tab', event );
     7989            return false;
     7990        }
     7991
     7992        if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     7993            this.controller.trigger( 'attachment:keydown:arrow', event );
     7994            return;
     7995        }
     7996    }
     7997});
     7998
     7999module.exports = Details;
     8000
     8001
     8002/***/ }),
     8003/* 88 */
     8004/***/ (function(module, exports) {
     8005
     8006/**
     8007 * wp.media.view.AttachmentCompat
     8008 *
     8009 * A view to display fields added via the `attachment_fields_to_edit` filter.
    43928010 *
    43938011 * @class
     
    43978015 */
    43988016var View = wp.media.View,
    4399     EditImage;
    4400 
    4401 EditImage = View.extend({
    4402     className: 'image-editor',
    4403     template: wp.template('image-editor'),
    4404 
    4405     initialize: function( options ) {
    4406         this.editor = window.imageEdit;
    4407         this.controller = options.controller;
    4408         View.prototype.initialize.apply( this, arguments );
    4409     },
    4410 
    4411     prepare: function() {
    4412         return this.model.toJSON();
    4413     },
    4414 
    4415     loadEditor: function() {
    4416         var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
    4417         dfd.done( _.bind( this.focus, this ) );
    4418     },
    4419 
    4420     focus: function() {
    4421         this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
    4422     },
    4423 
    4424     back: function() {
    4425         var lastState = this.controller.lastState();
    4426         this.controller.setState( lastState );
    4427     },
    4428 
    4429     refresh: function() {
    4430         this.model.fetch();
    4431     },
    4432 
    4433     save: function() {
    4434         var lastState = this.controller.lastState();
    4435 
    4436         this.model.fetch().done( _.bind( function() {
    4437             this.controller.setState( lastState );
    4438         }, this ) );
     8017    AttachmentCompat;
     8018
     8019AttachmentCompat = View.extend({
     8020    tagName:   'form',
     8021    className: 'compat-item',
     8022
     8023    events: {
     8024        'submit':          'preventDefault',
     8025        'change input':    'save',
     8026        'change select':   'save',
     8027        'change textarea': 'save'
     8028    },
     8029
     8030    initialize: function() {
     8031        this.listenTo( this.model, 'change:compat', this.render );
     8032    },
     8033    /**
     8034     * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     8035     */
     8036    dispose: function() {
     8037        if ( this.$(':focus').length ) {
     8038            this.save();
     8039        }
     8040        /**
     8041         * call 'dispose' directly on the parent class
     8042         */
     8043        return View.prototype.dispose.apply( this, arguments );
     8044    },
     8045    /**
     8046     * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     8047     */
     8048    render: function() {
     8049        var compat = this.model.get('compat');
     8050        if ( ! compat || ! compat.item ) {
     8051            return;
     8052        }
     8053
     8054        this.views.detach();
     8055        this.$el.html( compat.item );
     8056        this.views.render();
     8057        return this;
     8058    },
     8059    /**
     8060     * @param {Object} event
     8061     */
     8062    preventDefault: function( event ) {
     8063        event.preventDefault();
     8064    },
     8065    /**
     8066     * @param {Object} event
     8067     */
     8068    save: function( event ) {
     8069        var data = {};
     8070
     8071        if ( event ) {
     8072            event.preventDefault();
     8073        }
     8074
     8075        _.each( this.$el.serializeArray(), function( pair ) {
     8076            data[ pair.name ] = pair.value;
     8077        });
     8078
     8079        this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
     8080        this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
     8081    },
     8082
     8083    postSave: function() {
     8084        this.controller.trigger( 'attachment:compat:ready', ['ready'] );
    44398085    }
    4440 
    44418086});
    44428087
    4443 module.exports = EditImage;
    4444 
    4445 },{}],38:[function(require,module,exports){
     8088module.exports = AttachmentCompat;
     8089
     8090
     8091/***/ }),
     8092/* 89 */
     8093/***/ (function(module, exports) {
     8094
     8095/**
     8096 * wp.media.view.Iframe
     8097 *
     8098 * @class
     8099 * @augments wp.media.View
     8100 * @augments wp.Backbone.View
     8101 * @augments Backbone.View
     8102 */
     8103var Iframe = wp.media.View.extend({
     8104    className: 'media-iframe',
     8105    /**
     8106     * @returns {wp.media.view.Iframe} Returns itself to allow chaining
     8107     */
     8108    render: function() {
     8109        this.views.detach();
     8110        this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
     8111        this.views.render();
     8112        return this;
     8113    }
     8114});
     8115
     8116module.exports = Iframe;
     8117
     8118
     8119/***/ }),
     8120/* 90 */
     8121/***/ (function(module, exports) {
     8122
    44468123/**
    44478124 * wp.media.view.Embed
     
    45078184module.exports = Embed;
    45088185
    4509 },{}],39:[function(require,module,exports){
     8186
     8187/***/ }),
     8188/* 91 */
     8189/***/ (function(module, exports) {
     8190
    45108191/**
    4511  * wp.media.view.EmbedImage
     8192 * wp.media.view.Label
    45128193 *
    45138194 * @class
    4514  * @augments wp.media.view.Settings.AttachmentDisplay
    4515  * @augments wp.media.view.Settings
    45168195 * @augments wp.media.View
    45178196 * @augments wp.Backbone.View
    45188197 * @augments Backbone.View
    45198198 */
    4520 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    4521     EmbedImage;
    4522 
    4523 EmbedImage = AttachmentDisplay.extend({
    4524     className: 'embed-media-settings',
    4525     template:  wp.template('embed-image-settings'),
     8199var Label = wp.media.View.extend({
     8200    tagName: 'label',
     8201    className: 'screen-reader-text',
    45268202
    45278203    initialize: function() {
    4528         /**
    4529          * Call `initialize` directly on parent class with passed arguments
    4530          */
    4531         AttachmentDisplay.prototype.initialize.apply( this, arguments );
    4532         this.listenTo( this.model, 'change:url', this.updateImage );
    4533     },
    4534 
    4535     updateImage: function() {
    4536         this.$('img').attr( 'src', this.model.get('url') );
     8204        this.value = this.options.value;
     8205    },
     8206
     8207    render: function() {
     8208        this.$el.html( this.value );
     8209
     8210        return this;
    45378211    }
    45388212});
    45398213
    4540 module.exports = EmbedImage;
    4541 
    4542 },{}],40:[function(require,module,exports){
     8214module.exports = Label;
     8215
     8216
     8217/***/ }),
     8218/* 92 */
     8219/***/ (function(module, exports) {
     8220
     8221/**
     8222 * wp.media.view.EmbedUrl
     8223 *
     8224 * @class
     8225 * @augments wp.media.View
     8226 * @augments wp.Backbone.View
     8227 * @augments Backbone.View
     8228 */
     8229var View = wp.media.View,
     8230    $ = jQuery,
     8231    EmbedUrl;
     8232
     8233EmbedUrl = View.extend({
     8234    tagName:   'label',
     8235    className: 'embed-url',
     8236
     8237    events: {
     8238        'input':  'url',
     8239        'keyup':  'url',
     8240        'change': 'url'
     8241    },
     8242
     8243    initialize: function() {
     8244        this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
     8245        this.input = this.$input[0];
     8246
     8247        this.spinner = $('<span class="spinner" />')[0];
     8248        this.$el.append([ this.input, this.spinner ]);
     8249
     8250        this.listenTo( this.model, 'change:url', this.render );
     8251
     8252        if ( this.model.get( 'url' ) ) {
     8253            _.delay( _.bind( function () {
     8254                this.model.trigger( 'change:url' );
     8255            }, this ), 500 );
     8256        }
     8257    },
     8258    /**
     8259     * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
     8260     */
     8261    render: function() {
     8262        var $input = this.$input;
     8263
     8264        if ( $input.is(':focus') ) {
     8265            return;
     8266        }
     8267
     8268        this.input.value = this.model.get('url') || 'http://';
     8269        /**
     8270         * Call `render` directly on parent class with passed arguments
     8271         */
     8272        View.prototype.render.apply( this, arguments );
     8273        return this;
     8274    },
     8275
     8276    ready: function() {
     8277        if ( ! wp.media.isTouchDevice ) {
     8278            this.focus();
     8279        }
     8280    },
     8281
     8282    url: function( event ) {
     8283        this.model.set( 'url', event.target.value );
     8284    },
     8285
     8286    /**
     8287     * If the input is visible, focus and select its contents.
     8288     */
     8289    focus: function() {
     8290        var $input = this.$input;
     8291        if ( $input.is(':visible') ) {
     8292            $input.focus()[0].select();
     8293        }
     8294    }
     8295});
     8296
     8297module.exports = EmbedUrl;
     8298
     8299
     8300/***/ }),
     8301/* 93 */
     8302/***/ (function(module, exports) {
     8303
    45438304/**
    45448305 * wp.media.view.EmbedLink
     
    46298390module.exports = EmbedLink;
    46308391
    4631 },{}],41:[function(require,module,exports){
     8392
     8393/***/ }),
     8394/* 94 */
     8395/***/ (function(module, exports) {
     8396
    46328397/**
    4633  * wp.media.view.EmbedUrl
     8398 * wp.media.view.EmbedImage
    46348399 *
    46358400 * @class
     8401 * @augments wp.media.view.Settings.AttachmentDisplay
     8402 * @augments wp.media.view.Settings
    46368403 * @augments wp.media.View
    46378404 * @augments wp.Backbone.View
    46388405 * @augments Backbone.View
    46398406 */
    4640 var View = wp.media.View,
    4641     $ = jQuery,
    4642     EmbedUrl;
    4643 
    4644 EmbedUrl = View.extend({
    4645     tagName:   'label',
    4646     className: 'embed-url',
    4647 
    4648     events: {
    4649         'input':  'url',
    4650         'keyup':  'url',
    4651         'change': 'url'
    4652     },
     8407var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     8408    EmbedImage;
     8409
     8410EmbedImage = AttachmentDisplay.extend({
     8411    className: 'embed-media-settings',
     8412    template:  wp.template('embed-image-settings'),
    46538413
    46548414    initialize: function() {
    4655         this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
    4656         this.input = this.$input[0];
    4657 
    4658         this.spinner = $('<span class="spinner" />')[0];
    4659         this.$el.append([ this.input, this.spinner ]);
    4660 
    4661         this.listenTo( this.model, 'change:url', this.render );
    4662 
    4663         if ( this.model.get( 'url' ) ) {
    4664             _.delay( _.bind( function () {
    4665                 this.model.trigger( 'change:url' );
    4666             }, this ), 500 );
    4667         }
    4668     },
    4669     /**
    4670      * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
    4671      */
    4672     render: function() {
    4673         var $input = this.$input;
    4674 
    4675         if ( $input.is(':focus') ) {
    4676             return;
    4677         }
    4678 
    4679         this.input.value = this.model.get('url') || 'http://';
    46808415        /**
    4681          * Call `render` directly on parent class with passed arguments
     8416         * Call `initialize` directly on parent class with passed arguments
    46828417         */
    4683         View.prototype.render.apply( this, arguments );
    4684         return this;
    4685     },
    4686 
    4687     ready: function() {
    4688         if ( ! wp.media.isTouchDevice ) {
    4689             this.focus();
    4690         }
    4691     },
    4692 
    4693     url: function( event ) {
    4694         this.model.set( 'url', event.target.value );
    4695     },
    4696 
    4697     /**
    4698      * If the input is visible, focus and select its contents.
    4699      */
    4700     focus: function() {
    4701         var $input = this.$input;
    4702         if ( $input.is(':visible') ) {
    4703             $input.focus()[0].select();
    4704         }
     8418        AttachmentDisplay.prototype.initialize.apply( this, arguments );
     8419        this.listenTo( this.model, 'change:url', this.updateImage );
     8420    },
     8421
     8422    updateImage: function() {
     8423        this.$('img').attr( 'src', this.model.get('url') );
    47058424    }
    47068425});
    47078426
    4708 module.exports = EmbedUrl;
    4709 
    4710 },{}],42:[function(require,module,exports){
    4711 /**
    4712  * wp.media.view.FocusManager
    4713  *
    4714  * @class
    4715  * @augments wp.media.View
    4716  * @augments wp.Backbone.View
    4717  * @augments Backbone.View
    4718  */
    4719 var FocusManager = wp.media.View.extend({
    4720 
    4721     events: {
    4722         'keydown': 'constrainTabbing'
    4723     },
    4724 
    4725     focus: function() { // Reset focus on first left menu item
    4726         this.$('.media-menu-item').first().focus();
    4727     },
    4728     /**
    4729      * @param {Object} event
    4730      */
    4731     constrainTabbing: function( event ) {
    4732         var tabbables;
    4733 
    4734         // Look for the tab key.
    4735         if ( 9 !== event.keyCode ) {
    4736             return;
    4737         }
    4738 
    4739         // Skip the file input added by Plupload.
    4740         tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
    4741 
    4742         // Keep tab focus within media modal while it's open
    4743         if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
    4744             tabbables.first().focus();
    4745             return false;
    4746         } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
    4747             tabbables.last().focus();
    4748             return false;
    4749         }
    4750     }
    4751 
    4752 });
    4753 
    4754 module.exports = FocusManager;
    4755 
    4756 },{}],43:[function(require,module,exports){
    4757 /**
    4758  * wp.media.view.Frame
    4759  *
    4760  * A frame is a composite view consisting of one or more regions and one or more
    4761  * states.
    4762  *
    4763  * @see wp.media.controller.State
    4764  * @see wp.media.controller.Region
    4765  *
    4766  * @class
    4767  * @augments wp.media.View
    4768  * @augments wp.Backbone.View
    4769  * @augments Backbone.View
    4770  * @mixes wp.media.controller.StateMachine
    4771  */
    4772 var Frame = wp.media.View.extend({
    4773     initialize: function() {
    4774         _.defaults( this.options, {
    4775             mode: [ 'select' ]
    4776         });
    4777         this._createRegions();
    4778         this._createStates();
    4779         this._createModes();
    4780     },
    4781 
    4782     _createRegions: function() {
    4783         // Clone the regions array.
    4784         this.regions = this.regions ? this.regions.slice() : [];
    4785 
    4786         // Initialize regions.
    4787         _.each( this.regions, function( region ) {
    4788             this[ region ] = new wp.media.controller.Region({
    4789                 view:     this,
    4790                 id:       region,
    4791                 selector: '.media-frame-' + region
    4792             });
    4793         }, this );
    4794     },
    4795     /**
    4796      * Create the frame's states.
    4797      *
    4798      * @see wp.media.controller.State
    4799      * @see wp.media.controller.StateMachine
    4800      *
    4801      * @fires wp.media.controller.State#ready
    4802      */
    4803     _createStates: function() {
    4804         // Create the default `states` collection.
    4805         this.states = new Backbone.Collection( null, {
    4806             model: wp.media.controller.State
    4807         });
    4808 
    4809         // Ensure states have a reference to the frame.
    4810         this.states.on( 'add', function( model ) {
    4811             model.frame = this;
    4812             model.trigger('ready');
    4813         }, this );
    4814 
    4815         if ( this.options.states ) {
    4816             this.states.add( this.options.states );
    4817         }
    4818     },
    4819 
    4820     /**
    4821      * A frame can be in a mode or multiple modes at one time.
    4822      *
    4823      * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
    4824      */
    4825     _createModes: function() {
    4826         // Store active "modes" that the frame is in. Unrelated to region modes.
    4827         this.activeModes = new Backbone.Collection();
    4828         this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
    4829 
    4830         _.each( this.options.mode, function( mode ) {
    4831             this.activateMode( mode );
    4832         }, this );
    4833     },
    4834     /**
    4835      * Reset all states on the frame to their defaults.
    4836      *
    4837      * @returns {wp.media.view.Frame} Returns itself to allow chaining
    4838      */
    4839     reset: function() {
    4840         this.states.invoke( 'trigger', 'reset' );
    4841         return this;
    4842     },
    4843     /**
    4844      * Map activeMode collection events to the frame.
    4845      */
    4846     triggerModeEvents: function( model, collection, options ) {
    4847         var collectionEvent,
    4848             modeEventMap = {
    4849                 add: 'activate',
    4850                 remove: 'deactivate'
    4851             },
    4852             eventToTrigger;
    4853         // Probably a better way to do this.
    4854         _.each( options, function( value, key ) {
    4855             if ( value ) {
    4856                 collectionEvent = key;
    4857             }
    4858         } );
    4859 
    4860         if ( ! _.has( modeEventMap, collectionEvent ) ) {
    4861             return;
    4862         }
    4863 
    4864         eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
    4865         this.trigger( eventToTrigger );
    4866     },
    4867     /**
    4868      * Activate a mode on the frame.
    4869      *
    4870      * @param string mode Mode ID.
    4871      * @returns {this} Returns itself to allow chaining.
    4872      */
    4873     activateMode: function( mode ) {
    4874         // Bail if the mode is already active.
    4875         if ( this.isModeActive( mode ) ) {
    4876             return;
    4877         }
    4878         this.activeModes.add( [ { id: mode } ] );
    4879         // Add a CSS class to the frame so elements can be styled for the mode.
    4880         this.$el.addClass( 'mode-' + mode );
    4881 
    4882         return this;
    4883     },
    4884     /**
    4885      * Deactivate a mode on the frame.
    4886      *
    4887      * @param string mode Mode ID.
    4888      * @returns {this} Returns itself to allow chaining.
    4889      */
    4890     deactivateMode: function( mode ) {
    4891         // Bail if the mode isn't active.
    4892         if ( ! this.isModeActive( mode ) ) {
    4893             return this;
    4894         }
    4895         this.activeModes.remove( this.activeModes.where( { id: mode } ) );
    4896         this.$el.removeClass( 'mode-' + mode );
    4897         /**
    4898          * Frame mode deactivation event.
    4899          *
    4900          * @event this#{mode}:deactivate
    4901          */
    4902         this.trigger( mode + ':deactivate' );
    4903 
    4904         return this;
    4905     },
    4906     /**
    4907      * Check if a mode is enabled on the frame.
    4908      *
    4909      * @param  string mode Mode ID.
    4910      * @return bool
    4911      */
    4912     isModeActive: function( mode ) {
    4913         return Boolean( this.activeModes.where( { id: mode } ).length );
    4914     }
    4915 });
    4916 
    4917 // Make the `Frame` a `StateMachine`.
    4918 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
    4919 
    4920 module.exports = Frame;
    4921 
    4922 },{}],44:[function(require,module,exports){
    4923 /**
    4924  * wp.media.view.MediaFrame.ImageDetails
    4925  *
    4926  * A media frame for manipulating an image that's already been inserted
    4927  * into a post.
    4928  *
    4929  * @class
    4930  * @augments wp.media.view.MediaFrame.Select
    4931  * @augments wp.media.view.MediaFrame
    4932  * @augments wp.media.view.Frame
    4933  * @augments wp.media.View
    4934  * @augments wp.Backbone.View
    4935  * @augments Backbone.View
    4936  * @mixes wp.media.controller.StateMachine
    4937  */
    4938 var Select = wp.media.view.MediaFrame.Select,
    4939     l10n = wp.media.view.l10n,
    4940     ImageDetails;
    4941 
    4942 ImageDetails = Select.extend({
    4943     defaults: {
    4944         id:      'image',
    4945         url:     '',
    4946         menu:    'image-details',
    4947         content: 'image-details',
    4948         toolbar: 'image-details',
    4949         type:    'link',
    4950         title:    l10n.imageDetailsTitle,
    4951         priority: 120
    4952     },
    4953 
    4954     initialize: function( options ) {
    4955         this.image = new wp.media.model.PostImage( options.metadata );
    4956         this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
    4957         Select.prototype.initialize.apply( this, arguments );
    4958     },
    4959 
    4960     bindHandlers: function() {
    4961         Select.prototype.bindHandlers.apply( this, arguments );
    4962         this.on( 'menu:create:image-details', this.createMenu, this );
    4963         this.on( 'content:create:image-details', this.imageDetailsContent, this );
    4964         this.on( 'content:render:edit-image', this.editImageContent, this );
    4965         this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
    4966         // override the select toolbar
    4967         this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
    4968     },
    4969 
    4970     createStates: function() {
    4971         this.states.add([
    4972             new wp.media.controller.ImageDetails({
    4973                 image: this.image,
    4974                 editable: false
    4975             }),
    4976             new wp.media.controller.ReplaceImage({
    4977                 id: 'replace-image',
    4978                 library: wp.media.query( { type: 'image' } ),
    4979                 image: this.image,
    4980                 multiple:  false,
    4981                 title:     l10n.imageReplaceTitle,
    4982                 toolbar: 'replace',
    4983                 priority:  80,
    4984                 displaySettings: true
    4985             }),
    4986             new wp.media.controller.EditImage( {
    4987                 image: this.image,
    4988                 selection: this.options.selection
    4989             } )
    4990         ]);
    4991     },
    4992 
    4993     imageDetailsContent: function( options ) {
    4994         options.view = new wp.media.view.ImageDetails({
    4995             controller: this,
    4996             model: this.state().image,
    4997             attachment: this.state().image.attachment
    4998         });
    4999     },
    5000 
    5001     editImageContent: function() {
    5002         var state = this.state(),
    5003             model = state.get('image'),
    5004             view;
    5005 
    5006         if ( ! model ) {
    5007             return;
    5008         }
    5009 
    5010         view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
    5011 
    5012         this.content.set( view );
    5013 
    5014         // after bringing in the frame, load the actual editor via an ajax call
    5015         view.loadEditor();
    5016 
    5017     },
    5018 
    5019     renderImageDetailsToolbar: function() {
    5020         this.toolbar.set( new wp.media.view.Toolbar({
    5021             controller: this,
    5022             items: {
    5023                 select: {
    5024                     style:    'primary',
    5025                     text:     l10n.update,
    5026                     priority: 80,
    5027 
    5028                     click: function() {
    5029                         var controller = this.controller,
    5030                             state = controller.state();
    5031 
    5032                         controller.close();
    5033 
    5034                         // not sure if we want to use wp.media.string.image which will create a shortcode or
    5035                         // perhaps wp.html.string to at least to build the <img />
    5036                         state.trigger( 'update', controller.image.toJSON() );
    5037 
    5038                         // Restore and reset the default state.
    5039                         controller.setState( controller.options.state );
    5040                         controller.reset();
    5041                     }
    5042                 }
    5043             }
    5044         }) );
    5045     },
    5046 
    5047     renderReplaceImageToolbar: function() {
    5048         var frame = this,
    5049             lastState = frame.lastState(),
    5050             previous = lastState && lastState.id;
    5051 
    5052         this.toolbar.set( new wp.media.view.Toolbar({
    5053             controller: this,
    5054             items: {
    5055                 back: {
    5056                     text:     l10n.back,
    5057                     priority: 20,
    5058                     click:    function() {
    5059                         if ( previous ) {
    5060                             frame.setState( previous );
    5061                         } else {
    5062                             frame.close();
    5063                         }
    5064                     }
    5065                 },
    5066 
    5067                 replace: {
    5068                     style:    'primary',
    5069                     text:     l10n.replace,
    5070                     priority: 80,
    5071 
    5072                     click: function() {
    5073                         var controller = this.controller,
    5074                             state = controller.state(),
    5075                             selection = state.get( 'selection' ),
    5076                             attachment = selection.single();
    5077 
    5078                         controller.close();
    5079 
    5080                         controller.image.changeAttachment( attachment, state.display( attachment ) );
    5081 
    5082                         // not sure if we want to use wp.media.string.image which will create a shortcode or
    5083                         // perhaps wp.html.string to at least to build the <img />
    5084                         state.trigger( 'replace', controller.image.toJSON() );
    5085 
    5086                         // Restore and reset the default state.
    5087                         controller.setState( controller.options.state );
    5088                         controller.reset();
    5089                     }
    5090                 }
    5091             }
    5092         }) );
    5093     }
    5094 
    5095 });
    5096 
    5097 module.exports = ImageDetails;
    5098 
    5099 },{}],45:[function(require,module,exports){
    5100 /**
    5101  * wp.media.view.MediaFrame.Post
    5102  *
    5103  * The frame for manipulating media on the Edit Post page.
    5104  *
    5105  * @class
    5106  * @augments wp.media.view.MediaFrame.Select
    5107  * @augments wp.media.view.MediaFrame
    5108  * @augments wp.media.view.Frame
    5109  * @augments wp.media.View
    5110  * @augments wp.Backbone.View
    5111  * @augments Backbone.View
    5112  * @mixes wp.media.controller.StateMachine
    5113  */
    5114 var Select = wp.media.view.MediaFrame.Select,
    5115     Library = wp.media.controller.Library,
    5116     l10n = wp.media.view.l10n,
    5117     Post;
    5118 
    5119 Post = Select.extend({
    5120     initialize: function() {
    5121         this.counts = {
    5122             audio: {
    5123                 count: wp.media.view.settings.attachmentCounts.audio,
    5124                 state: 'playlist'
    5125             },
    5126             video: {
    5127                 count: wp.media.view.settings.attachmentCounts.video,
    5128                 state: 'video-playlist'
    5129             }
    5130         };
    5131 
    5132         _.defaults( this.options, {
    5133             multiple:  true,
    5134             editing:   false,
    5135             state:    'insert',
    5136             metadata:  {}
    5137         });
    5138 
    5139         // Call 'initialize' directly on the parent class.
    5140         Select.prototype.initialize.apply( this, arguments );
    5141         this.createIframeStates();
    5142 
    5143     },
    5144 
    5145     /**
    5146      * Create the default states.
    5147      */
    5148     createStates: function() {
    5149         var options = this.options;
    5150 
    5151         this.states.add([
    5152             // Main states.
    5153             new Library({
    5154                 id:         'insert',
    5155                 title:      l10n.insertMediaTitle,
    5156                 priority:   20,
    5157                 toolbar:    'main-insert',
    5158                 filterable: 'all',
    5159                 library:    wp.media.query( options.library ),
    5160                 multiple:   options.multiple ? 'reset' : false,
    5161                 editable:   true,
    5162 
    5163                 // If the user isn't allowed to edit fields,
    5164                 // can they still edit it locally?
    5165                 allowLocalEdits: true,
    5166 
    5167                 // Show the attachment display settings.
    5168                 displaySettings: true,
    5169                 // Update user settings when users adjust the
    5170                 // attachment display settings.
    5171                 displayUserSettings: true
    5172             }),
    5173 
    5174             new Library({
    5175                 id:         'gallery',
    5176                 title:      l10n.createGalleryTitle,
    5177                 priority:   40,
    5178                 toolbar:    'main-gallery',
    5179                 filterable: 'uploaded',
    5180                 multiple:   'add',
    5181                 editable:   false,
    5182 
    5183                 library:  wp.media.query( _.defaults({
    5184                     type: 'image'
    5185                 }, options.library ) )
    5186             }),
    5187 
    5188             // Embed states.
    5189             new wp.media.controller.Embed( { metadata: options.metadata } ),
    5190 
    5191             new wp.media.controller.EditImage( { model: options.editImage } ),
    5192 
    5193             // Gallery states.
    5194             new wp.media.controller.GalleryEdit({
    5195                 library: options.selection,
    5196                 editing: options.editing,
    5197                 menu:    'gallery'
    5198             }),
    5199 
    5200             new wp.media.controller.GalleryAdd(),
    5201 
    5202             new Library({
    5203                 id:         'playlist',
    5204                 title:      l10n.createPlaylistTitle,
    5205                 priority:   60,
    5206                 toolbar:    'main-playlist',
    5207                 filterable: 'uploaded',
    5208                 multiple:   'add',
    5209                 editable:   false,
    5210 
    5211                 library:  wp.media.query( _.defaults({
    5212                     type: 'audio'
    5213                 }, options.library ) )
    5214             }),
    5215 
    5216             // Playlist states.
    5217             new wp.media.controller.CollectionEdit({
    5218                 type: 'audio',
    5219                 collectionType: 'playlist',
    5220                 title:          l10n.editPlaylistTitle,
    5221                 SettingsView:   wp.media.view.Settings.Playlist,
    5222                 library:        options.selection,
    5223                 editing:        options.editing,
    5224                 menu:           'playlist',
    5225                 dragInfoText:   l10n.playlistDragInfo,
    5226                 dragInfo:       false
    5227             }),
    5228 
    5229             new wp.media.controller.CollectionAdd({
    5230                 type: 'audio',
    5231                 collectionType: 'playlist',
    5232                 title: l10n.addToPlaylistTitle
    5233             }),
    5234 
    5235             new Library({
    5236                 id:         'video-playlist',
    5237                 title:      l10n.createVideoPlaylistTitle,
    5238                 priority:   60,
    5239                 toolbar:    'main-video-playlist',
    5240                 filterable: 'uploaded',
    5241                 multiple:   'add',
    5242                 editable:   false,
    5243 
    5244                 library:  wp.media.query( _.defaults({
    5245                     type: 'video'
    5246                 }, options.library ) )
    5247             }),
    5248 
    5249             new wp.media.controller.CollectionEdit({
    5250                 type: 'video',
    5251                 collectionType: 'playlist',
    5252                 title:          l10n.editVideoPlaylistTitle,
    5253                 SettingsView:   wp.media.view.Settings.Playlist,
    5254                 library:        options.selection,
    5255                 editing:        options.editing,
    5256                 menu:           'video-playlist',
    5257                 dragInfoText:   l10n.videoPlaylistDragInfo,
    5258                 dragInfo:       false
    5259             }),
    5260 
    5261             new wp.media.controller.CollectionAdd({
    5262                 type: 'video',
    5263                 collectionType: 'playlist',
    5264                 title: l10n.addToVideoPlaylistTitle
    5265             })
    5266         ]);
    5267 
    5268         if ( wp.media.view.settings.post.featuredImageId ) {
    5269             this.states.add( new wp.media.controller.FeaturedImage() );
    5270         }
    5271     },
    5272 
    5273     bindHandlers: function() {
    5274         var handlers, checkCounts;
    5275 
    5276         Select.prototype.bindHandlers.apply( this, arguments );
    5277 
    5278         this.on( 'activate', this.activate, this );
    5279 
    5280         // Only bother checking media type counts if one of the counts is zero
    5281         checkCounts = _.find( this.counts, function( type ) {
    5282             return type.count === 0;
    5283         } );
    5284 
    5285         if ( typeof checkCounts !== 'undefined' ) {
    5286             this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
    5287         }
    5288 
    5289         this.on( 'menu:create:gallery', this.createMenu, this );
    5290         this.on( 'menu:create:playlist', this.createMenu, this );
    5291         this.on( 'menu:create:video-playlist', this.createMenu, this );
    5292         this.on( 'toolbar:create:main-insert', this.createToolbar, this );
    5293         this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
    5294         this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
    5295         this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
    5296         this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
    5297         this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
    5298 
    5299         handlers = {
    5300             menu: {
    5301                 'default': 'mainMenu',
    5302                 'gallery': 'galleryMenu',
    5303                 'playlist': 'playlistMenu',
    5304                 'video-playlist': 'videoPlaylistMenu'
    5305             },
    5306 
    5307             content: {
    5308                 'embed':          'embedContent',
    5309                 'edit-image':     'editImageContent',
    5310                 'edit-selection': 'editSelectionContent'
    5311             },
    5312 
    5313             toolbar: {
    5314                 'main-insert':      'mainInsertToolbar',
    5315                 'main-gallery':     'mainGalleryToolbar',
    5316                 'gallery-edit':     'galleryEditToolbar',
    5317                 'gallery-add':      'galleryAddToolbar',
    5318                 'main-playlist':    'mainPlaylistToolbar',
    5319                 'playlist-edit':    'playlistEditToolbar',
    5320                 'playlist-add':     'playlistAddToolbar',
    5321                 'main-video-playlist': 'mainVideoPlaylistToolbar',
    5322                 'video-playlist-edit': 'videoPlaylistEditToolbar',
    5323                 'video-playlist-add': 'videoPlaylistAddToolbar'
    5324             }
    5325         };
    5326 
    5327         _.each( handlers, function( regionHandlers, region ) {
    5328             _.each( regionHandlers, function( callback, handler ) {
    5329                 this.on( region + ':render:' + handler, this[ callback ], this );
    5330             }, this );
    5331         }, this );
    5332     },
    5333 
    5334     activate: function() {
    5335         // Hide menu items for states tied to particular media types if there are no items
    5336         _.each( this.counts, function( type ) {
    5337             if ( type.count < 1 ) {
    5338                 this.menuItemVisibility( type.state, 'hide' );
    5339             }
    5340         }, this );
    5341     },
    5342 
    5343     mediaTypeCounts: function( model, attr ) {
    5344         if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
    5345             this.counts[ attr ].count++;
    5346             this.menuItemVisibility( this.counts[ attr ].state, 'show' );
    5347         }
    5348     },
    5349 
    5350     // Menus
    5351     /**
    5352      * @param {wp.Backbone.View} view
    5353      */
    5354     mainMenu: function( view ) {
    5355         view.set({
    5356             'library-separator': new wp.media.View({
    5357                 className: 'separator',
    5358                 priority: 100
    5359             })
    5360         });
    5361     },
    5362 
    5363     menuItemVisibility: function( state, visibility ) {
    5364         var menu = this.menu.get();
    5365         if ( visibility === 'hide' ) {
    5366             menu.hide( state );
    5367         } else if ( visibility === 'show' ) {
    5368             menu.show( state );
    5369         }
    5370     },
    5371     /**
    5372      * @param {wp.Backbone.View} view
    5373      */
    5374     galleryMenu: function( view ) {
    5375         var lastState = this.lastState(),
    5376             previous = lastState && lastState.id,
    5377             frame = this;
    5378 
    5379         view.set({
    5380             cancel: {
    5381                 text:     l10n.cancelGalleryTitle,
    5382                 priority: 20,
    5383                 click:    function() {
    5384                     if ( previous ) {
    5385                         frame.setState( previous );
    5386                     } else {
    5387                         frame.close();
    5388                     }
    5389 
    5390                     // Keep focus inside media modal
    5391                     // after canceling a gallery
    5392                     this.controller.modal.focusManager.focus();
    5393                 }
    5394             },
    5395             separateCancel: new wp.media.View({
    5396                 className: 'separator',
    5397                 priority: 40
    5398             })
    5399         });
    5400     },
    5401 
    5402     playlistMenu: function( view ) {
    5403         var lastState = this.lastState(),
    5404             previous = lastState && lastState.id,
    5405             frame = this;
    5406 
    5407         view.set({
    5408             cancel: {
    5409                 text:     l10n.cancelPlaylistTitle,
    5410                 priority: 20,
    5411                 click:    function() {
    5412                     if ( previous ) {
    5413                         frame.setState( previous );
    5414                     } else {
    5415                         frame.close();
    5416                     }
    5417                 }
    5418             },
    5419             separateCancel: new wp.media.View({
    5420                 className: 'separator',
    5421                 priority: 40
    5422             })
    5423         });
    5424     },
    5425 
    5426     videoPlaylistMenu: function( view ) {
    5427         var lastState = this.lastState(),
    5428             previous = lastState && lastState.id,
    5429             frame = this;
    5430 
    5431         view.set({
    5432             cancel: {
    5433                 text:     l10n.cancelVideoPlaylistTitle,
    5434                 priority: 20,
    5435                 click:    function() {
    5436                     if ( previous ) {
    5437                         frame.setState( previous );
    5438                     } else {
    5439                         frame.close();
    5440                     }
    5441                 }
    5442             },
    5443             separateCancel: new wp.media.View({
    5444                 className: 'separator',
    5445                 priority: 40
    5446             })
    5447         });
    5448     },
    5449 
    5450     // Content
    5451     embedContent: function() {
    5452         var view = new wp.media.view.Embed({
    5453             controller: this,
    5454             model:      this.state()
    5455         }).render();
    5456 
    5457         this.content.set( view );
    5458 
    5459         if ( ! wp.media.isTouchDevice ) {
    5460             view.url.focus();
    5461         }
    5462     },
    5463 
    5464     editSelectionContent: function() {
    5465         var state = this.state(),
    5466             selection = state.get('selection'),
    5467             view;
    5468 
    5469         view = new wp.media.view.AttachmentsBrowser({
    5470             controller: this,
    5471             collection: selection,
    5472             selection:  selection,
    5473             model:      state,
    5474             sortable:   true,
    5475             search:     false,
    5476             date:       false,
    5477             dragInfo:   true,
    5478 
    5479             AttachmentView: wp.media.view.Attachments.EditSelection
    5480         }).render();
    5481 
    5482         view.toolbar.set( 'backToLibrary', {
    5483             text:     l10n.returnToLibrary,
    5484             priority: -100,
    5485 
    5486             click: function() {
    5487                 this.controller.content.mode('browse');
    5488             }
    5489         });
    5490 
    5491         // Browse our library of attachments.
    5492         this.content.set( view );
    5493 
    5494         // Trigger the controller to set focus
    5495         this.trigger( 'edit:selection', this );
    5496     },
    5497 
    5498     editImageContent: function() {
    5499         var image = this.state().get('image'),
    5500             view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
    5501 
    5502         this.content.set( view );
    5503 
    5504         // after creating the wrapper view, load the actual editor via an ajax call
    5505         view.loadEditor();
    5506 
    5507     },
    5508 
    5509     // Toolbars
    5510 
    5511     /**
    5512      * @param {wp.Backbone.View} view
    5513      */
    5514     selectionStatusToolbar: function( view ) {
    5515         var editable = this.state().get('editable');
    5516 
    5517         view.set( 'selection', new wp.media.view.Selection({
    5518             controller: this,
    5519             collection: this.state().get('selection'),
    5520             priority:   -40,
    5521 
    5522             // If the selection is editable, pass the callback to
    5523             // switch the content mode.
    5524             editable: editable && function() {
    5525                 this.controller.content.mode('edit-selection');
    5526             }
    5527         }).render() );
    5528     },
    5529 
    5530     /**
    5531      * @param {wp.Backbone.View} view
    5532      */
    5533     mainInsertToolbar: function( view ) {
    5534         var controller = this;
    5535 
    5536         this.selectionStatusToolbar( view );
    5537 
    5538         view.set( 'insert', {
    5539             style:    'primary',
    5540             priority: 80,
    5541             text:     l10n.insertIntoPost,
    5542             requires: { selection: true },
    5543 
    5544             /**
    5545              * @fires wp.media.controller.State#insert
    5546              */
    5547             click: function() {
    5548                 var state = controller.state(),
    5549                     selection = state.get('selection');
    5550 
    5551                 controller.close();
    5552                 state.trigger( 'insert', selection ).reset();
    5553             }
    5554         });
    5555     },
    5556 
    5557     /**
    5558      * @param {wp.Backbone.View} view
    5559      */
    5560     mainGalleryToolbar: function( view ) {
    5561         var controller = this;
    5562 
    5563         this.selectionStatusToolbar( view );
    5564 
    5565         view.set( 'gallery', {
    5566             style:    'primary',
    5567             text:     l10n.createNewGallery,
    5568             priority: 60,
    5569             requires: { selection: true },
    5570 
    5571             click: function() {
    5572                 var selection = controller.state().get('selection'),
    5573                     edit = controller.state('gallery-edit'),
    5574                     models = selection.where({ type: 'image' });
    5575 
    5576                 edit.set( 'library', new wp.media.model.Selection( models, {
    5577                     props:    selection.props.toJSON(),
    5578                     multiple: true
    5579                 }) );
    5580 
    5581                 this.controller.setState('gallery-edit');
    5582 
    5583                 // Keep focus inside media modal
    5584                 // after jumping to gallery view
    5585                 this.controller.modal.focusManager.focus();
    5586             }
    5587         });
    5588     },
    5589 
    5590     mainPlaylistToolbar: function( view ) {
    5591         var controller = this;
    5592 
    5593         this.selectionStatusToolbar( view );
    5594 
    5595         view.set( 'playlist', {
    5596             style:    'primary',
    5597             text:     l10n.createNewPlaylist,
    5598             priority: 100,
    5599             requires: { selection: true },
    5600 
    5601             click: function() {
    5602                 var selection = controller.state().get('selection'),
    5603                     edit = controller.state('playlist-edit'),
    5604                     models = selection.where({ type: 'audio' });
    5605 
    5606                 edit.set( 'library', new wp.media.model.Selection( models, {
    5607                     props:    selection.props.toJSON(),
    5608                     multiple: true
    5609                 }) );
    5610 
    5611                 this.controller.setState('playlist-edit');
    5612 
    5613                 // Keep focus inside media modal
    5614                 // after jumping to playlist view
    5615                 this.controller.modal.focusManager.focus();
    5616             }
    5617         });
    5618     },
    5619 
    5620     mainVideoPlaylistToolbar: function( view ) {
    5621         var controller = this;
    5622 
    5623         this.selectionStatusToolbar( view );
    5624 
    5625         view.set( 'video-playlist', {
    5626             style:    'primary',
    5627             text:     l10n.createNewVideoPlaylist,
    5628             priority: 100,
    5629             requires: { selection: true },
    5630 
    5631             click: function() {
    5632                 var selection = controller.state().get('selection'),
    5633                     edit = controller.state('video-playlist-edit'),
    5634                     models = selection.where({ type: 'video' });
    5635 
    5636                 edit.set( 'library', new wp.media.model.Selection( models, {
    5637                     props:    selection.props.toJSON(),
    5638                     multiple: true
    5639                 }) );
    5640 
    5641                 this.controller.setState('video-playlist-edit');
    5642 
    5643                 // Keep focus inside media modal
    5644                 // after jumping to video playlist view
    5645                 this.controller.modal.focusManager.focus();
    5646             }
    5647         });
    5648     },
    5649 
    5650     featuredImageToolbar: function( toolbar ) {
    5651         this.createSelectToolbar( toolbar, {
    5652             text:  l10n.setFeaturedImage,
    5653             state: this.options.state
    5654         });
    5655     },
    5656 
    5657     mainEmbedToolbar: function( toolbar ) {
    5658         toolbar.view = new wp.media.view.Toolbar.Embed({
    5659             controller: this
    5660         });
    5661     },
    5662 
    5663     galleryEditToolbar: function() {
    5664         var editing = this.state().get('editing');
    5665         this.toolbar.set( new wp.media.view.Toolbar({
    5666             controller: this,
    5667             items: {
    5668                 insert: {
    5669                     style:    'primary',
    5670                     text:     editing ? l10n.updateGallery : l10n.insertGallery,
    5671                     priority: 80,
    5672                     requires: { library: true },
    5673 
    5674                     /**
    5675                      * @fires wp.media.controller.State#update
    5676                      */
    5677                     click: function() {
    5678                         var controller = this.controller,
    5679                             state = controller.state();
    5680 
    5681                         controller.close();
    5682                         state.trigger( 'update', state.get('library') );
    5683 
    5684                         // Restore and reset the default state.
    5685                         controller.setState( controller.options.state );
    5686                         controller.reset();
    5687                     }
    5688                 }
    5689             }
    5690         }) );
    5691     },
    5692 
    5693     galleryAddToolbar: function() {
    5694         this.toolbar.set( new wp.media.view.Toolbar({
    5695             controller: this,
    5696             items: {
    5697                 insert: {
    5698                     style:    'primary',
    5699                     text:     l10n.addToGallery,
    5700                     priority: 80,
    5701                     requires: { selection: true },
    5702 
    5703                     /**
    5704                      * @fires wp.media.controller.State#reset
    5705                      */
    5706                     click: function() {
    5707                         var controller = this.controller,
    5708                             state = controller.state(),
    5709                             edit = controller.state('gallery-edit');
    5710 
    5711                         edit.get('library').add( state.get('selection').models );
    5712                         state.trigger('reset');
    5713                         controller.setState('gallery-edit');
    5714                     }
    5715                 }
    5716             }
    5717         }) );
    5718     },
    5719 
    5720     playlistEditToolbar: function() {
    5721         var editing = this.state().get('editing');
    5722         this.toolbar.set( new wp.media.view.Toolbar({
    5723             controller: this,
    5724             items: {
    5725                 insert: {
    5726                     style:    'primary',
    5727                     text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
    5728                     priority: 80,
    5729                     requires: { library: true },
    5730 
    5731                     /**
    5732                      * @fires wp.media.controller.State#update
    5733                      */
    5734                     click: function() {
    5735                         var controller = this.controller,
    5736                             state = controller.state();
    5737 
    5738                         controller.close();
    5739                         state.trigger( 'update', state.get('library') );
    5740 
    5741                         // Restore and reset the default state.
    5742                         controller.setState( controller.options.state );
    5743                         controller.reset();
    5744                     }
    5745                 }
    5746             }
    5747         }) );
    5748     },
    5749 
    5750     playlistAddToolbar: function() {
    5751         this.toolbar.set( new wp.media.view.Toolbar({
    5752             controller: this,
    5753             items: {
    5754                 insert: {
    5755                     style:    'primary',
    5756                     text:     l10n.addToPlaylist,
    5757                     priority: 80,
    5758                     requires: { selection: true },
    5759 
    5760                     /**
    5761                      * @fires wp.media.controller.State#reset
    5762                      */
    5763                     click: function() {
    5764                         var controller = this.controller,
    5765                             state = controller.state(),
    5766                             edit = controller.state('playlist-edit');
    5767 
    5768                         edit.get('library').add( state.get('selection').models );
    5769                         state.trigger('reset');
    5770                         controller.setState('playlist-edit');
    5771                     }
    5772                 }
    5773             }
    5774         }) );
    5775     },
    5776 
    5777     videoPlaylistEditToolbar: function() {
    5778         var editing = this.state().get('editing');
    5779         this.toolbar.set( new wp.media.view.Toolbar({
    5780             controller: this,
    5781             items: {
    5782                 insert: {
    5783                     style:    'primary',
    5784                     text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
    5785                     priority: 140,
    5786                     requires: { library: true },
    5787 
    5788                     click: function() {
    5789                         var controller = this.controller,
    5790                             state = controller.state(),
    5791                             library = state.get('library');
    5792 
    5793                         library.type = 'video';
    5794 
    5795                         controller.close();
    5796                         state.trigger( 'update', library );
    5797 
    5798                         // Restore and reset the default state.
    5799                         controller.setState( controller.options.state );
    5800                         controller.reset();
    5801                     }
    5802                 }
    5803             }
    5804         }) );
    5805     },
    5806 
    5807     videoPlaylistAddToolbar: function() {
    5808         this.toolbar.set( new wp.media.view.Toolbar({
    5809             controller: this,
    5810             items: {
    5811                 insert: {
    5812                     style:    'primary',
    5813                     text:     l10n.addToVideoPlaylist,
    5814                     priority: 140,
    5815                     requires: { selection: true },
    5816 
    5817                     click: function() {
    5818                         var controller = this.controller,
    5819                             state = controller.state(),
    5820                             edit = controller.state('video-playlist-edit');
    5821 
    5822                         edit.get('library').add( state.get('selection').models );
    5823                         state.trigger('reset');
    5824                         controller.setState('video-playlist-edit');
    5825                     }
    5826                 }
    5827             }
    5828         }) );
    5829     }
    5830 });
    5831 
    5832 module.exports = Post;
    5833 
    5834 },{}],46:[function(require,module,exports){
    5835 /**
    5836  * wp.media.view.MediaFrame.Select
    5837  *
    5838  * A frame for selecting an item or items from the media library.
    5839  *
    5840  * @class
    5841  * @augments wp.media.view.MediaFrame
    5842  * @augments wp.media.view.Frame
    5843  * @augments wp.media.View
    5844  * @augments wp.Backbone.View
    5845  * @augments Backbone.View
    5846  * @mixes wp.media.controller.StateMachine
    5847  */
    5848 
    5849 var MediaFrame = wp.media.view.MediaFrame,
    5850     l10n = wp.media.view.l10n,
    5851     Select;
    5852 
    5853 Select = MediaFrame.extend({
    5854     initialize: function() {
    5855         // Call 'initialize' directly on the parent class.
    5856         MediaFrame.prototype.initialize.apply( this, arguments );
    5857 
    5858         _.defaults( this.options, {
    5859             selection: [],
    5860             library:   {},
    5861             multiple:  false,
    5862             state:    'library'
    5863         });
    5864 
    5865         this.createSelection();
    5866         this.createStates();
    5867         this.bindHandlers();
    5868     },
    5869 
    5870     /**
    5871      * Attach a selection collection to the frame.
    5872      *
    5873      * A selection is a collection of attachments used for a specific purpose
    5874      * by a media frame. e.g. Selecting an attachment (or many) to insert into
    5875      * post content.
    5876      *
    5877      * @see media.model.Selection
    5878      */
    5879     createSelection: function() {
    5880         var selection = this.options.selection;
    5881 
    5882         if ( ! (selection instanceof wp.media.model.Selection) ) {
    5883             this.options.selection = new wp.media.model.Selection( selection, {
    5884                 multiple: this.options.multiple
    5885             });
    5886         }
    5887 
    5888         this._selection = {
    5889             attachments: new wp.media.model.Attachments(),
    5890             difference: []
    5891         };
    5892     },
    5893 
    5894     /**
    5895      * Create the default states on the frame.
    5896      */
    5897     createStates: function() {
    5898         var options = this.options;
    5899 
    5900         if ( this.options.states ) {
    5901             return;
    5902         }
    5903 
    5904         // Add the default states.
    5905         this.states.add([
    5906             // Main states.
    5907             new wp.media.controller.Library({
    5908                 library:   wp.media.query( options.library ),
    5909                 multiple:  options.multiple,
    5910                 title:     options.title,
    5911                 priority:  20
    5912             })
    5913         ]);
    5914     },
    5915 
    5916     /**
    5917      * Bind region mode event callbacks.
    5918      *
    5919      * @see media.controller.Region.render
    5920      */
    5921     bindHandlers: function() {
    5922         this.on( 'router:create:browse', this.createRouter, this );
    5923         this.on( 'router:render:browse', this.browseRouter, this );
    5924         this.on( 'content:create:browse', this.browseContent, this );
    5925         this.on( 'content:render:upload', this.uploadContent, this );
    5926         this.on( 'toolbar:create:select', this.createSelectToolbar, this );
    5927     },
    5928 
    5929     /**
    5930      * Render callback for the router region in the `browse` mode.
    5931      *
    5932      * @param {wp.media.view.Router} routerView
    5933      */
    5934     browseRouter: function( routerView ) {
    5935         routerView.set({
    5936             upload: {
    5937                 text:     l10n.uploadFilesTitle,
    5938                 priority: 20
    5939             },
    5940             browse: {
    5941                 text:     l10n.mediaLibraryTitle,
    5942                 priority: 40
    5943             }
    5944         });
    5945     },
    5946 
    5947     /**
    5948      * Render callback for the content region in the `browse` mode.
    5949      *
    5950      * @param {wp.media.controller.Region} contentRegion
    5951      */
    5952     browseContent: function( contentRegion ) {
    5953         var state = this.state();
    5954 
    5955         this.$el.removeClass('hide-toolbar');
    5956 
    5957         // Browse our library of attachments.
    5958         contentRegion.view = new wp.media.view.AttachmentsBrowser({
    5959             controller: this,
    5960             collection: state.get('library'),
    5961             selection:  state.get('selection'),
    5962             model:      state,
    5963             sortable:   state.get('sortable'),
    5964             search:     state.get('searchable'),
    5965             filters:    state.get('filterable'),
    5966             date:       state.get('date'),
    5967             display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
    5968             dragInfo:   state.get('dragInfo'),
    5969 
    5970             idealColumnWidth: state.get('idealColumnWidth'),
    5971             suggestedWidth:   state.get('suggestedWidth'),
    5972             suggestedHeight:  state.get('suggestedHeight'),
    5973 
    5974             AttachmentView: state.get('AttachmentView')
    5975         });
    5976     },
    5977 
    5978     /**
    5979      * Render callback for the content region in the `upload` mode.
    5980      */
    5981     uploadContent: function() {
    5982         this.$el.removeClass( 'hide-toolbar' );
    5983         this.content.set( new wp.media.view.UploaderInline({
    5984             controller: this
    5985         }) );
    5986     },
    5987 
    5988     /**
    5989      * Toolbars
    5990      *
    5991      * @param {Object} toolbar
    5992      * @param {Object} [options={}]
    5993      * @this wp.media.controller.Region
    5994      */
    5995     createSelectToolbar: function( toolbar, options ) {
    5996         options = options || this.options.button || {};
    5997         options.controller = this;
    5998 
    5999         toolbar.view = new wp.media.view.Toolbar.Select( options );
    6000     }
    6001 });
    6002 
    6003 module.exports = Select;
    6004 
    6005 },{}],47:[function(require,module,exports){
    6006 /**
    6007  * wp.media.view.Iframe
    6008  *
    6009  * @class
    6010  * @augments wp.media.View
    6011  * @augments wp.Backbone.View
    6012  * @augments Backbone.View
    6013  */
    6014 var Iframe = wp.media.View.extend({
    6015     className: 'media-iframe',
    6016     /**
    6017      * @returns {wp.media.view.Iframe} Returns itself to allow chaining
    6018      */
    6019     render: function() {
    6020         this.views.detach();
    6021         this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
    6022         this.views.render();
    6023         return this;
    6024     }
    6025 });
    6026 
    6027 module.exports = Iframe;
    6028 
    6029 },{}],48:[function(require,module,exports){
     8427module.exports = EmbedImage;
     8428
     8429
     8430/***/ }),
     8431/* 95 */
     8432/***/ (function(module, exports) {
     8433
    60308434/**
    60318435 * wp.media.view.ImageDetails
     
    61958599module.exports = ImageDetails;
    61968600
    6197 },{}],49:[function(require,module,exports){
     8601
     8602/***/ }),
     8603/* 96 */
     8604/***/ (function(module, exports) {
     8605
    61988606/**
    6199  * wp.media.view.Label
    6200  *
    6201  * @class
    6202  * @augments wp.media.View
    6203  * @augments wp.Backbone.View
    6204  * @augments Backbone.View
    6205  */
    6206 var Label = wp.media.View.extend({
    6207     tagName: 'label',
    6208     className: 'screen-reader-text',
    6209 
    6210     initialize: function() {
    6211         this.value = this.options.value;
    6212     },
    6213 
    6214     render: function() {
    6215         this.$el.html( this.value );
    6216 
    6217         return this;
    6218     }
    6219 });
    6220 
    6221 module.exports = Label;
    6222 
    6223 },{}],50:[function(require,module,exports){
    6224 /**
    6225  * wp.media.view.MediaFrame
    6226  *
    6227  * The frame used to create the media modal.
    6228  *
    6229  * @class
    6230  * @augments wp.media.view.Frame
    6231  * @augments wp.media.View
    6232  * @augments wp.Backbone.View
    6233  * @augments Backbone.View
    6234  * @mixes wp.media.controller.StateMachine
    6235  */
    6236 var Frame = wp.media.view.Frame,
    6237     $ = jQuery,
    6238     MediaFrame;
    6239 
    6240 MediaFrame = Frame.extend({
    6241     className: 'media-frame',
    6242     template:  wp.template('media-frame'),
    6243     regions:   ['menu','title','content','toolbar','router'],
    6244 
    6245     events: {
    6246         'click div.media-frame-title h1': 'toggleMenu'
    6247     },
    6248 
    6249     /**
    6250      * @global wp.Uploader
    6251      */
    6252     initialize: function() {
    6253         Frame.prototype.initialize.apply( this, arguments );
    6254 
    6255         _.defaults( this.options, {
    6256             title:    '',
    6257             modal:    true,
    6258             uploader: true
    6259         });
    6260 
    6261         // Ensure core UI is enabled.
    6262         this.$el.addClass('wp-core-ui');
    6263 
    6264         // Initialize modal container view.
    6265         if ( this.options.modal ) {
    6266             this.modal = new wp.media.view.Modal({
    6267                 controller: this,
    6268                 title:      this.options.title
    6269             });
    6270 
    6271             this.modal.content( this );
    6272         }
    6273 
    6274         // Force the uploader off if the upload limit has been exceeded or
    6275         // if the browser isn't supported.
    6276         if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
    6277             this.options.uploader = false;
    6278         }
    6279 
    6280         // Initialize window-wide uploader.
    6281         if ( this.options.uploader ) {
    6282             this.uploader = new wp.media.view.UploaderWindow({
    6283                 controller: this,
    6284                 uploader: {
    6285                     dropzone:  this.modal ? this.modal.$el : this.$el,
    6286                     container: this.$el
    6287                 }
    6288             });
    6289             this.views.set( '.media-frame-uploader', this.uploader );
    6290         }
    6291 
    6292         this.on( 'attach', _.bind( this.views.ready, this.views ), this );
    6293 
    6294         // Bind default title creation.
    6295         this.on( 'title:create:default', this.createTitle, this );
    6296         this.title.mode('default');
    6297 
    6298         this.on( 'title:render', function( view ) {
    6299             view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
    6300         });
    6301 
    6302         // Bind default menu.
    6303         this.on( 'menu:create:default', this.createMenu, this );
    6304     },
    6305     /**
    6306      * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6307      */
    6308     render: function() {
    6309         // Activate the default state if no active state exists.
    6310         if ( ! this.state() && this.options.state ) {
    6311             this.setState( this.options.state );
    6312         }
    6313         /**
    6314          * call 'render' directly on the parent class
    6315          */
    6316         return Frame.prototype.render.apply( this, arguments );
    6317     },
    6318     /**
    6319      * @param {Object} title
    6320      * @this wp.media.controller.Region
    6321      */
    6322     createTitle: function( title ) {
    6323         title.view = new wp.media.View({
    6324             controller: this,
    6325             tagName: 'h1'
    6326         });
    6327     },
    6328     /**
    6329      * @param {Object} menu
    6330      * @this wp.media.controller.Region
    6331      */
    6332     createMenu: function( menu ) {
    6333         menu.view = new wp.media.view.Menu({
    6334             controller: this
    6335         });
    6336     },
    6337 
    6338     toggleMenu: function() {
    6339         this.$el.find( '.media-menu' ).toggleClass( 'visible' );
    6340     },
    6341 
    6342     /**
    6343      * @param {Object} toolbar
    6344      * @this wp.media.controller.Region
    6345      */
    6346     createToolbar: function( toolbar ) {
    6347         toolbar.view = new wp.media.view.Toolbar({
    6348             controller: this
    6349         });
    6350     },
    6351     /**
    6352      * @param {Object} router
    6353      * @this wp.media.controller.Region
    6354      */
    6355     createRouter: function( router ) {
    6356         router.view = new wp.media.view.Router({
    6357             controller: this
    6358         });
    6359     },
    6360     /**
    6361      * @param {Object} options
    6362      */
    6363     createIframeStates: function( options ) {
    6364         var settings = wp.media.view.settings,
    6365             tabs = settings.tabs,
    6366             tabUrl = settings.tabUrl,
    6367             $postId;
    6368 
    6369         if ( ! tabs || ! tabUrl ) {
    6370             return;
    6371         }
    6372 
    6373         // Add the post ID to the tab URL if it exists.
    6374         $postId = $('#post_ID');
    6375         if ( $postId.length ) {
    6376             tabUrl += '&post_id=' + $postId.val();
    6377         }
    6378 
    6379         // Generate the tab states.
    6380         _.each( tabs, function( title, id ) {
    6381             this.state( 'iframe:' + id ).set( _.defaults({
    6382                 tab:     id,
    6383                 src:     tabUrl + '&tab=' + id,
    6384                 title:   title,
    6385                 content: 'iframe',
    6386                 menu:    'default'
    6387             }, options ) );
    6388         }, this );
    6389 
    6390         this.on( 'content:create:iframe', this.iframeContent, this );
    6391         this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
    6392         this.on( 'menu:render:default', this.iframeMenu, this );
    6393         this.on( 'open', this.hijackThickbox, this );
    6394         this.on( 'close', this.restoreThickbox, this );
    6395     },
    6396 
    6397     /**
    6398      * @param {Object} content
    6399      * @this wp.media.controller.Region
    6400      */
    6401     iframeContent: function( content ) {
    6402         this.$el.addClass('hide-toolbar');
    6403         content.view = new wp.media.view.Iframe({
    6404             controller: this
    6405         });
    6406     },
    6407 
    6408     iframeContentCleanup: function() {
    6409         this.$el.removeClass('hide-toolbar');
    6410     },
    6411 
    6412     iframeMenu: function( view ) {
    6413         var views = {};
    6414 
    6415         if ( ! view ) {
    6416             return;
    6417         }
    6418 
    6419         _.each( wp.media.view.settings.tabs, function( title, id ) {
    6420             views[ 'iframe:' + id ] = {
    6421                 text: this.state( 'iframe:' + id ).get('title'),
    6422                 priority: 200
    6423             };
    6424         }, this );
    6425 
    6426         view.set( views );
    6427     },
    6428 
    6429     hijackThickbox: function() {
    6430         var frame = this;
    6431 
    6432         if ( ! window.tb_remove || this._tb_remove ) {
    6433             return;
    6434         }
    6435 
    6436         this._tb_remove = window.tb_remove;
    6437         window.tb_remove = function() {
    6438             frame.close();
    6439             frame.reset();
    6440             frame.setState( frame.options.state );
    6441             frame._tb_remove.call( window );
    6442         };
    6443     },
    6444 
    6445     restoreThickbox: function() {
    6446         if ( ! this._tb_remove ) {
    6447             return;
    6448         }
    6449 
    6450         window.tb_remove = this._tb_remove;
    6451         delete this._tb_remove;
    6452     }
    6453 });
    6454 
    6455 // Map some of the modal's methods to the frame.
    6456 _.each(['open','close','attach','detach','escape'], function( method ) {
    6457     /**
    6458      * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6459      */
    6460     MediaFrame.prototype[ method ] = function() {
    6461         if ( this.modal ) {
    6462             this.modal[ method ].apply( this.modal, arguments );
    6463         }
    6464         return this;
    6465     };
    6466 });
    6467 
    6468 module.exports = MediaFrame;
    6469 
    6470 },{}],51:[function(require,module,exports){
    6471 /**
    6472  * wp.media.view.MenuItem
    6473  *
    6474  * @class
    6475  * @augments wp.media.View
    6476  * @augments wp.Backbone.View
    6477  * @augments Backbone.View
    6478  */
    6479 var $ = jQuery,
    6480     MenuItem;
    6481 
    6482 MenuItem = wp.media.View.extend({
    6483     tagName:   'a',
    6484     className: 'media-menu-item',
    6485 
    6486     attributes: {
    6487         href: '#'
    6488     },
    6489 
    6490     events: {
    6491         'click': '_click'
    6492     },
    6493     /**
    6494      * @param {Object} event
    6495      */
    6496     _click: function( event ) {
    6497         var clickOverride = this.options.click;
    6498 
    6499         if ( event ) {
    6500             event.preventDefault();
    6501         }
    6502 
    6503         if ( clickOverride ) {
    6504             clickOverride.call( this );
    6505         } else {
    6506             this.click();
    6507         }
    6508 
    6509         // When selecting a tab along the left side,
    6510         // focus should be transferred into the main panel
    6511         if ( ! wp.media.isTouchDevice ) {
    6512             $('.media-frame-content input').first().focus();
    6513         }
    6514     },
    6515 
    6516     click: function() {
    6517         var state = this.options.state;
    6518 
    6519         if ( state ) {
    6520             this.controller.setState( state );
    6521             this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
    6522         }
    6523     },
    6524     /**
    6525      * @returns {wp.media.view.MenuItem} returns itself to allow chaining
    6526      */
    6527     render: function() {
    6528         var options = this.options;
    6529 
    6530         if ( options.text ) {
    6531             this.$el.text( options.text );
    6532         } else if ( options.html ) {
    6533             this.$el.html( options.html );
    6534         }
    6535 
    6536         return this;
    6537     }
    6538 });
    6539 
    6540 module.exports = MenuItem;
    6541 
    6542 },{}],52:[function(require,module,exports){
    6543 /**
    6544  * wp.media.view.Menu
    6545  *
    6546  * @class
    6547  * @augments wp.media.view.PriorityList
    6548  * @augments wp.media.View
    6549  * @augments wp.Backbone.View
    6550  * @augments Backbone.View
    6551  */
    6552 var MenuItem = wp.media.view.MenuItem,
    6553     PriorityList = wp.media.view.PriorityList,
    6554     Menu;
    6555 
    6556 Menu = PriorityList.extend({
    6557     tagName:   'div',
    6558     className: 'media-menu',
    6559     property:  'state',
    6560     ItemView:  MenuItem,
    6561     region:    'menu',
    6562 
    6563     /* TODO: alternatively hide on any click anywhere
    6564     events: {
    6565         'click': 'click'
    6566     },
    6567 
    6568     click: function() {
    6569         this.$el.removeClass( 'visible' );
    6570     },
    6571     */
    6572 
    6573     /**
    6574      * @param {Object} options
    6575      * @param {string} id
    6576      * @returns {wp.media.View}
    6577      */
    6578     toView: function( options, id ) {
    6579         options = options || {};
    6580         options[ this.property ] = options[ this.property ] || id;
    6581         return new this.ItemView( options ).render();
    6582     },
    6583 
    6584     ready: function() {
    6585         /**
    6586          * call 'ready' directly on the parent class
    6587          */
    6588         PriorityList.prototype.ready.apply( this, arguments );
    6589         this.visibility();
    6590     },
    6591 
    6592     set: function() {
    6593         /**
    6594          * call 'set' directly on the parent class
    6595          */
    6596         PriorityList.prototype.set.apply( this, arguments );
    6597         this.visibility();
    6598     },
    6599 
    6600     unset: function() {
    6601         /**
    6602          * call 'unset' directly on the parent class
    6603          */
    6604         PriorityList.prototype.unset.apply( this, arguments );
    6605         this.visibility();
    6606     },
    6607 
    6608     visibility: function() {
    6609         var region = this.region,
    6610             view = this.controller[ region ].get(),
    6611             views = this.views.get(),
    6612             hide = ! views || views.length < 2;
    6613 
    6614         if ( this === view ) {
    6615             this.controller.$el.toggleClass( 'hide-' + region, hide );
    6616         }
    6617     },
    6618     /**
    6619      * @param {string} id
    6620      */
    6621     select: function( id ) {
    6622         var view = this.get( id );
    6623 
    6624         if ( ! view ) {
    6625             return;
    6626         }
    6627 
    6628         this.deselect();
    6629         view.$el.addClass('active');
    6630     },
    6631 
    6632     deselect: function() {
    6633         this.$el.children().removeClass('active');
    6634     },
    6635 
    6636     hide: function( id ) {
    6637         var view = this.get( id );
    6638 
    6639         if ( ! view ) {
    6640             return;
    6641         }
    6642 
    6643         view.$el.addClass('hidden');
    6644     },
    6645 
    6646     show: function( id ) {
    6647         var view = this.get( id );
    6648 
    6649         if ( ! view ) {
    6650             return;
    6651         }
    6652 
    6653         view.$el.removeClass('hidden');
    6654     }
    6655 });
    6656 
    6657 module.exports = Menu;
    6658 
    6659 },{}],53:[function(require,module,exports){
    6660 /**
    6661  * wp.media.view.Modal
    6662  *
    6663  * A modal view, which the media modal uses as its default container.
    6664  *
    6665  * @class
    6666  * @augments wp.media.View
    6667  * @augments wp.Backbone.View
    6668  * @augments Backbone.View
    6669  */
    6670 var $ = jQuery,
    6671     Modal;
    6672 
    6673 Modal = wp.media.View.extend({
    6674     tagName:  'div',
    6675     template: wp.template('media-modal'),
    6676 
    6677     attributes: {
    6678         tabindex: 0
    6679     },
    6680 
    6681     events: {
    6682         'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
    6683         'keydown': 'keydown'
    6684     },
    6685 
    6686     initialize: function() {
    6687         _.defaults( this.options, {
    6688             container: document.body,
    6689             title:     '',
    6690             propagate: true,
    6691             freeze:    true
    6692         });
    6693 
    6694         this.focusManager = new wp.media.view.FocusManager({
    6695             el: this.el
    6696         });
    6697     },
    6698     /**
    6699      * @returns {Object}
    6700      */
    6701     prepare: function() {
    6702         return {
    6703             title: this.options.title
    6704         };
    6705     },
    6706 
    6707     /**
    6708      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6709      */
    6710     attach: function() {
    6711         if ( this.views.attached ) {
    6712             return this;
    6713         }
    6714 
    6715         if ( ! this.views.rendered ) {
    6716             this.render();
    6717         }
    6718 
    6719         this.$el.appendTo( this.options.container );
    6720 
    6721         // Manually mark the view as attached and trigger ready.
    6722         this.views.attached = true;
    6723         this.views.ready();
    6724 
    6725         return this.propagate('attach');
    6726     },
    6727 
    6728     /**
    6729      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6730      */
    6731     detach: function() {
    6732         if ( this.$el.is(':visible') ) {
    6733             this.close();
    6734         }
    6735 
    6736         this.$el.detach();
    6737         this.views.attached = false;
    6738         return this.propagate('detach');
    6739     },
    6740 
    6741     /**
    6742      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6743      */
    6744     open: function() {
    6745         var $el = this.$el,
    6746             options = this.options,
    6747             mceEditor;
    6748 
    6749         if ( $el.is(':visible') ) {
    6750             return this;
    6751         }
    6752 
    6753         if ( ! this.views.attached ) {
    6754             this.attach();
    6755         }
    6756 
    6757         // If the `freeze` option is set, record the window's scroll position.
    6758         if ( options.freeze ) {
    6759             this._freeze = {
    6760                 scrollTop: $( window ).scrollTop()
    6761             };
    6762         }
    6763 
    6764         // Disable page scrolling.
    6765         $( 'body' ).addClass( 'modal-open' );
    6766 
    6767         $el.show();
    6768 
    6769         // Try to close the onscreen keyboard
    6770         if ( 'ontouchend' in document ) {
    6771             if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
    6772                 mceEditor.iframeElement.focus();
    6773                 mceEditor.iframeElement.blur();
    6774 
    6775                 setTimeout( function() {
    6776                     mceEditor.iframeElement.blur();
    6777                 }, 100 );
    6778             }
    6779         }
    6780 
    6781         this.$el.focus();
    6782 
    6783         return this.propagate('open');
    6784     },
    6785 
    6786     /**
    6787      * @param {Object} options
    6788      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6789      */
    6790     close: function( options ) {
    6791         var freeze = this._freeze;
    6792 
    6793         if ( ! this.views.attached || ! this.$el.is(':visible') ) {
    6794             return this;
    6795         }
    6796 
    6797         // Enable page scrolling.
    6798         $( 'body' ).removeClass( 'modal-open' );
    6799 
    6800         // Hide modal and remove restricted media modal tab focus once it's closed
    6801         this.$el.hide().undelegate( 'keydown' );
    6802 
    6803         // Put focus back in useful location once modal is closed
    6804         $('#wpbody-content').focus();
    6805 
    6806         this.propagate('close');
    6807 
    6808         // If the `freeze` option is set, restore the container's scroll position.
    6809         if ( freeze ) {
    6810             $( window ).scrollTop( freeze.scrollTop );
    6811         }
    6812 
    6813         if ( options && options.escape ) {
    6814             this.propagate('escape');
    6815         }
    6816 
    6817         return this;
    6818     },
    6819     /**
    6820      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6821      */
    6822     escape: function() {
    6823         return this.close({ escape: true });
    6824     },
    6825     /**
    6826      * @param {Object} event
    6827      */
    6828     escapeHandler: function( event ) {
    6829         event.preventDefault();
    6830         this.escape();
    6831     },
    6832 
    6833     /**
    6834      * @param {Array|Object} content Views to register to '.media-modal-content'
    6835      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6836      */
    6837     content: function( content ) {
    6838         this.views.set( '.media-modal-content', content );
    6839         return this;
    6840     },
    6841 
    6842     /**
    6843      * Triggers a modal event and if the `propagate` option is set,
    6844      * forwards events to the modal's controller.
    6845      *
    6846      * @param {string} id
    6847      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6848      */
    6849     propagate: function( id ) {
    6850         this.trigger( id );
    6851 
    6852         if ( this.options.propagate ) {
    6853             this.controller.trigger( id );
    6854         }
    6855 
    6856         return this;
    6857     },
    6858     /**
    6859      * @param {Object} event
    6860      */
    6861     keydown: function( event ) {
    6862         // Close the modal when escape is pressed.
    6863         if ( 27 === event.which && this.$el.is(':visible') ) {
    6864             this.escape();
    6865             event.stopImmediatePropagation();
    6866         }
    6867     }
    6868 });
    6869 
    6870 module.exports = Modal;
    6871 
    6872 },{}],54:[function(require,module,exports){
    6873 /**
    6874  * wp.media.view.PriorityList
    6875  *
    6876  * @class
    6877  * @augments wp.media.View
    6878  * @augments wp.Backbone.View
    6879  * @augments Backbone.View
    6880  */
    6881 var PriorityList = wp.media.View.extend({
    6882     tagName:   'div',
    6883 
    6884     initialize: function() {
    6885         this._views = {};
    6886 
    6887         this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
    6888         delete this.options.views;
    6889 
    6890         if ( ! this.options.silent ) {
    6891             this.render();
    6892         }
    6893     },
    6894     /**
    6895      * @param {string} id
    6896      * @param {wp.media.View|Object} view
    6897      * @param {Object} options
    6898      * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
    6899      */
    6900     set: function( id, view, options ) {
    6901         var priority, views, index;
    6902 
    6903         options = options || {};
    6904 
    6905         // Accept an object with an `id` : `view` mapping.
    6906         if ( _.isObject( id ) ) {
    6907             _.each( id, function( view, id ) {
    6908                 this.set( id, view );
    6909             }, this );
    6910             return this;
    6911         }
    6912 
    6913         if ( ! (view instanceof Backbone.View) ) {
    6914             view = this.toView( view, id, options );
    6915         }
    6916         view.controller = view.controller || this.controller;
    6917 
    6918         this.unset( id );
    6919 
    6920         priority = view.options.priority || 10;
    6921         views = this.views.get() || [];
    6922 
    6923         _.find( views, function( existing, i ) {
    6924             if ( existing.options.priority > priority ) {
    6925                 index = i;
    6926                 return true;
    6927             }
    6928         });
    6929 
    6930         this._views[ id ] = view;
    6931         this.views.add( view, {
    6932             at: _.isNumber( index ) ? index : views.length || 0
    6933         });
    6934 
    6935         return this;
    6936     },
    6937     /**
    6938      * @param {string} id
    6939      * @returns {wp.media.View}
    6940      */
    6941     get: function( id ) {
    6942         return this._views[ id ];
    6943     },
    6944     /**
    6945      * @param {string} id
    6946      * @returns {wp.media.view.PriorityList}
    6947      */
    6948     unset: function( id ) {
    6949         var view = this.get( id );
    6950 
    6951         if ( view ) {
    6952             view.remove();
    6953         }
    6954 
    6955         delete this._views[ id ];
    6956         return this;
    6957     },
    6958     /**
    6959      * @param {Object} options
    6960      * @returns {wp.media.View}
    6961      */
    6962     toView: function( options ) {
    6963         return new wp.media.View( options );
    6964     }
    6965 });
    6966 
    6967 module.exports = PriorityList;
    6968 
    6969 },{}],55:[function(require,module,exports){
    6970 /**
    6971  * wp.media.view.RouterItem
    6972  *
    6973  * @class
    6974  * @augments wp.media.view.MenuItem
    6975  * @augments wp.media.View
    6976  * @augments wp.Backbone.View
    6977  * @augments Backbone.View
    6978  */
    6979 var RouterItem = wp.media.view.MenuItem.extend({
    6980     /**
    6981      * On click handler to activate the content region's corresponding mode.
    6982      */
    6983     click: function() {
    6984         var contentMode = this.options.contentMode;
    6985         if ( contentMode ) {
    6986             this.controller.content.mode( contentMode );
    6987         }
    6988     }
    6989 });
    6990 
    6991 module.exports = RouterItem;
    6992 
    6993 },{}],56:[function(require,module,exports){
    6994 /**
    6995  * wp.media.view.Router
    6996  *
    6997  * @class
    6998  * @augments wp.media.view.Menu
    6999  * @augments wp.media.view.PriorityList
    7000  * @augments wp.media.View
    7001  * @augments wp.Backbone.View
    7002  * @augments Backbone.View
    7003  */
    7004 var Menu = wp.media.view.Menu,
    7005     Router;
    7006 
    7007 Router = Menu.extend({
    7008     tagName:   'div',
    7009     className: 'media-router',
    7010     property:  'contentMode',
    7011     ItemView:  wp.media.view.RouterItem,
    7012     region:    'router',
    7013 
    7014     initialize: function() {
    7015         this.controller.on( 'content:render', this.update, this );
    7016         // Call 'initialize' directly on the parent class.
    7017         Menu.prototype.initialize.apply( this, arguments );
    7018     },
    7019 
    7020     update: function() {
    7021         var mode = this.controller.content.mode();
    7022         if ( mode ) {
    7023             this.select( mode );
    7024         }
    7025     }
    7026 });
    7027 
    7028 module.exports = Router;
    7029 
    7030 },{}],57:[function(require,module,exports){
    7031 /**
    7032  * wp.media.view.Search
    7033  *
    7034  * @class
    7035  * @augments wp.media.View
    7036  * @augments wp.Backbone.View
    7037  * @augments Backbone.View
    7038  */
    7039 var l10n = wp.media.view.l10n,
    7040     Search;
    7041 
    7042 Search = wp.media.View.extend({
    7043     tagName:   'input',
    7044     className: 'search',
    7045     id:        'media-search-input',
    7046 
    7047     attributes: {
    7048         type:        'search',
    7049         placeholder: l10n.search
    7050     },
    7051 
    7052     events: {
    7053         'input':  'search',
    7054         'keyup':  'search',
    7055         'change': 'search',
    7056         'search': 'search'
    7057     },
    7058 
    7059     /**
    7060      * @returns {wp.media.view.Search} Returns itself to allow chaining
    7061      */
    7062     render: function() {
    7063         this.el.value = this.model.escape('search');
    7064         return this;
    7065     },
    7066 
    7067     search: function( event ) {
    7068         if ( event.target.value ) {
    7069             this.model.set( 'search', event.target.value );
    7070         } else {
    7071             this.model.unset('search');
    7072         }
    7073     }
    7074 });
    7075 
    7076 module.exports = Search;
    7077 
    7078 },{}],58:[function(require,module,exports){
    7079 /**
    7080  * wp.media.view.Selection
    7081  *
    7082  * @class
    7083  * @augments wp.media.View
    7084  * @augments wp.Backbone.View
    7085  * @augments Backbone.View
    7086  */
    7087 var l10n = wp.media.view.l10n,
    7088     Selection;
    7089 
    7090 Selection = wp.media.View.extend({
    7091     tagName:   'div',
    7092     className: 'media-selection',
    7093     template:  wp.template('media-selection'),
    7094 
    7095     events: {
    7096         'click .edit-selection':  'edit',
    7097         'click .clear-selection': 'clear'
    7098     },
    7099 
    7100     initialize: function() {
    7101         _.defaults( this.options, {
    7102             editable:  false,
    7103             clearable: true
    7104         });
    7105 
    7106         /**
    7107          * @member {wp.media.view.Attachments.Selection}
    7108          */
    7109         this.attachments = new wp.media.view.Attachments.Selection({
    7110             controller: this.controller,
    7111             collection: this.collection,
    7112             selection:  this.collection,
    7113             model:      new Backbone.Model()
    7114         });
    7115 
    7116         this.views.set( '.selection-view', this.attachments );
    7117         this.collection.on( 'add remove reset', this.refresh, this );
    7118         this.controller.on( 'content:activate', this.refresh, this );
    7119     },
    7120 
    7121     ready: function() {
    7122         this.refresh();
    7123     },
    7124 
    7125     refresh: function() {
    7126         // If the selection hasn't been rendered, bail.
    7127         if ( ! this.$el.children().length ) {
    7128             return;
    7129         }
    7130 
    7131         var collection = this.collection,
    7132             editing = 'edit-selection' === this.controller.content.mode();
    7133 
    7134         // If nothing is selected, display nothing.
    7135         this.$el.toggleClass( 'empty', ! collection.length );
    7136         this.$el.toggleClass( 'one', 1 === collection.length );
    7137         this.$el.toggleClass( 'editing', editing );
    7138 
    7139         this.$('.count').text( l10n.selected.replace('%d', collection.length) );
    7140     },
    7141 
    7142     edit: function( event ) {
    7143         event.preventDefault();
    7144         if ( this.options.editable ) {
    7145             this.options.editable.call( this, this.collection );
    7146         }
    7147     },
    7148 
    7149     clear: function( event ) {
    7150         event.preventDefault();
    7151         this.collection.reset();
    7152 
    7153         // Keep focus inside media modal
    7154         // after clear link is selected
    7155         this.controller.modal.focusManager.focus();
    7156     }
    7157 });
    7158 
    7159 module.exports = Selection;
    7160 
    7161 },{}],59:[function(require,module,exports){
    7162 /**
    7163  * wp.media.view.Settings
     8607 * wp.media.view.Cropper
     8608 *
     8609 * Uses the imgAreaSelect plugin to allow a user to crop an image.
     8610 *
     8611 * Takes imgAreaSelect options from
     8612 * wp.customize.HeaderControl.calculateImageSelectOptions via
     8613 * wp.customize.HeaderControl.openMM.
    71648614 *
    71658615 * @class
     
    71698619 */
    71708620var View = wp.media.View,
    7171     $ = Backbone.$,
    7172     Settings;
    7173 
    7174 Settings = View.extend({
    7175     events: {
    7176         'click button':    'updateHandler',
    7177         'change input':    'updateHandler',
    7178         'change select':   'updateHandler',
    7179         'change textarea': 'updateHandler'
    7180     },
    7181 
     8621    UploaderStatus = wp.media.view.UploaderStatus,
     8622    l10n = wp.media.view.l10n,
     8623    $ = jQuery,
     8624    Cropper;
     8625
     8626Cropper = View.extend({
     8627    className: 'crop-content',
     8628    template: wp.template('crop-content'),
    71828629    initialize: function() {
    7183         this.model = this.model || new Backbone.Model();
    7184         this.listenTo( this.model, 'change', this.updateChanges );
    7185     },
    7186 
     8630        _.bindAll(this, 'onImageLoad');
     8631    },
     8632    ready: function() {
     8633        this.controller.frame.on('content:error:crop', this.onError, this);
     8634        this.$image = this.$el.find('.crop-image');
     8635        this.$image.on('load', this.onImageLoad);
     8636        $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
     8637    },
     8638    remove: function() {
     8639        $(window).off('resize.cropper');
     8640        this.$el.remove();
     8641        this.$el.off();
     8642        View.prototype.remove.apply(this, arguments);
     8643    },
    71878644    prepare: function() {
    7188         return _.defaults({
    7189             model: this.model.toJSON()
    7190         }, this.options );
    7191     },
    7192     /**
    7193      * @returns {wp.media.view.Settings} Returns itself to allow chaining
    7194      */
    7195     render: function() {
    7196         View.prototype.render.apply( this, arguments );
    7197         // Select the correct values.
    7198         _( this.model.attributes ).chain().keys().each( this.update, this );
    7199         return this;
    7200     },
    7201     /**
    7202      * @param {string} key
    7203      */
    7204     update: function( key ) {
    7205         var value = this.model.get( key ),
    7206             $setting = this.$('[data-setting="' + key + '"]'),
    7207             $buttons, $value;
    7208 
    7209         // Bail if we didn't find a matching setting.
    7210         if ( ! $setting.length ) {
    7211             return;
    7212         }
    7213 
    7214         // Attempt to determine how the setting is rendered and update
    7215         // the selected value.
    7216 
    7217         // Handle dropdowns.
    7218         if ( $setting.is('select') ) {
    7219             $value = $setting.find('[value="' + value + '"]');
    7220 
    7221             if ( $value.length ) {
    7222                 $setting.find('option').prop( 'selected', false );
    7223                 $value.prop( 'selected', true );
    7224             } else {
    7225                 // If we can't find the desired value, record what *is* selected.
    7226                 this.model.set( key, $setting.find(':selected').val() );
    7227             }
    7228 
    7229         // Handle button groups.
    7230         } else if ( $setting.hasClass('button-group') ) {
    7231             $buttons = $setting.find('button').removeClass('active');
    7232             $buttons.filter( '[value="' + value + '"]' ).addClass('active');
    7233 
    7234         // Handle text inputs and textareas.
    7235         } else if ( $setting.is('input[type="text"], textarea') ) {
    7236             if ( ! $setting.is(':focus') ) {
    7237                 $setting.val( value );
    7238             }
    7239         // Handle checkboxes.
    7240         } else if ( $setting.is('input[type="checkbox"]') ) {
    7241             $setting.prop( 'checked', !! value && 'false' !== value );
    7242         }
    7243     },
    7244     /**
    7245      * @param {Object} event
    7246      */
    7247     updateHandler: function( event ) {
    7248         var $setting = $( event.target ).closest('[data-setting]'),
    7249             value = event.target.value,
    7250             userSetting;
    7251 
    7252         event.preventDefault();
    7253 
    7254         if ( ! $setting.length ) {
    7255             return;
    7256         }
    7257 
    7258         // Use the correct value for checkboxes.
    7259         if ( $setting.is('input[type="checkbox"]') ) {
    7260             value = $setting[0].checked;
    7261         }
    7262 
    7263         // Update the corresponding setting.
    7264         this.model.set( $setting.data('setting'), value );
    7265 
    7266         // If the setting has a corresponding user setting,
    7267         // update that as well.
    7268         if ( userSetting = $setting.data('userSetting') ) {
    7269             window.setUserSetting( userSetting, value );
    7270         }
    7271     },
    7272 
    7273     updateChanges: function( model ) {
    7274         if ( model.hasChanged() ) {
    7275             _( model.changed ).chain().keys().each( this.update, this );
    7276         }
     8645        return {
     8646            title: l10n.cropYourImage,
     8647            url: this.options.attachment.get('url')
     8648        };
     8649    },
     8650    onImageLoad: function() {
     8651        var imgOptions = this.controller.get('imgSelectOptions');
     8652        if (typeof imgOptions === 'function') {
     8653            imgOptions = imgOptions(this.options.attachment, this.controller);
     8654        }
     8655
     8656        imgOptions = _.extend(imgOptions, {parent: this.$el});
     8657        this.trigger('image-loaded');
     8658        this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
     8659    },
     8660    onError: function() {
     8661        var filename = this.options.attachment.get('filename');
     8662
     8663        this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     8664            filename: UploaderStatus.prototype.filename(filename),
     8665            message: window._wpMediaViewsL10n.cropError
     8666        }), { at: 0 });
    72778667    }
    72788668});
    72798669
    7280 module.exports = Settings;
    7281 
    7282 },{}],60:[function(require,module,exports){
    7283 /**
    7284  * wp.media.view.Settings.AttachmentDisplay
    7285  *
    7286  * @class
    7287  * @augments wp.media.view.Settings
    7288  * @augments wp.media.View
    7289  * @augments wp.Backbone.View
    7290  * @augments Backbone.View
    7291  */
    7292 var Settings = wp.media.view.Settings,
    7293     AttachmentDisplay;
    7294 
    7295 AttachmentDisplay = Settings.extend({
    7296     className: 'attachment-display-settings',
    7297     template:  wp.template('attachment-display-settings'),
    7298 
    7299     initialize: function() {
    7300         var attachment = this.options.attachment;
    7301 
    7302         _.defaults( this.options, {
    7303             userSettings: false
    7304         });
    7305         // Call 'initialize' directly on the parent class.
    7306         Settings.prototype.initialize.apply( this, arguments );
    7307         this.listenTo( this.model, 'change:link', this.updateLinkTo );
    7308 
    7309         if ( attachment ) {
    7310             attachment.on( 'change:uploading', this.render, this );
    7311         }
    7312     },
    7313 
    7314     dispose: function() {
    7315         var attachment = this.options.attachment;
    7316         if ( attachment ) {
    7317             attachment.off( null, null, this );
    7318         }
    7319         /**
    7320          * call 'dispose' directly on the parent class
    7321          */
    7322         Settings.prototype.dispose.apply( this, arguments );
    7323     },
    7324     /**
    7325      * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
    7326      */
    7327     render: function() {
    7328         var attachment = this.options.attachment;
    7329         if ( attachment ) {
    7330             _.extend( this.options, {
    7331                 sizes: attachment.get('sizes'),
    7332                 type:  attachment.get('type')
    7333             });
    7334         }
    7335         /**
    7336          * call 'render' directly on the parent class
    7337          */
    7338         Settings.prototype.render.call( this );
    7339         this.updateLinkTo();
    7340         return this;
    7341     },
    7342 
    7343     updateLinkTo: function() {
    7344         var linkTo = this.model.get('link'),
    7345             $input = this.$('.link-to-custom'),
    7346             attachment = this.options.attachment;
    7347 
    7348         if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
    7349             $input.addClass( 'hidden' );
    7350             return;
    7351         }
    7352 
    7353         if ( attachment ) {
    7354             if ( 'post' === linkTo ) {
    7355                 $input.val( attachment.get('link') );
    7356             } else if ( 'file' === linkTo ) {
    7357                 $input.val( attachment.get('url') );
    7358             } else if ( ! this.model.get('linkUrl') ) {
    7359                 $input.val('http://');
    7360             }
    7361 
    7362             $input.prop( 'readonly', 'custom' !== linkTo );
    7363         }
    7364 
    7365         $input.removeClass( 'hidden' );
    7366 
    7367         // If the input is visible, focus and select its contents.
    7368         if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
    7369             $input.focus()[0].select();
    7370         }
    7371     }
    7372 });
    7373 
    7374 module.exports = AttachmentDisplay;
    7375 
    7376 },{}],61:[function(require,module,exports){
    7377 /**
    7378  * wp.media.view.Settings.Gallery
    7379  *
    7380  * @class
    7381  * @augments wp.media.view.Settings
    7382  * @augments wp.media.View
    7383  * @augments wp.Backbone.View
    7384  * @augments Backbone.View
    7385  */
    7386 var Gallery = wp.media.view.Settings.extend({
    7387     className: 'collection-settings gallery-settings',
    7388     template:  wp.template('gallery-settings')
    7389 });
    7390 
    7391 module.exports = Gallery;
    7392 
    7393 },{}],62:[function(require,module,exports){
    7394 /**
    7395  * wp.media.view.Settings.Playlist
    7396  *
    7397  * @class
    7398  * @augments wp.media.view.Settings
    7399  * @augments wp.media.View
    7400  * @augments wp.Backbone.View
    7401  * @augments Backbone.View
    7402  */
    7403 var Playlist = wp.media.view.Settings.extend({
    7404     className: 'collection-settings playlist-settings',
    7405     template:  wp.template('playlist-settings')
    7406 });
    7407 
    7408 module.exports = Playlist;
    7409 
    7410 },{}],63:[function(require,module,exports){
    7411 /**
    7412  * wp.media.view.Sidebar
    7413  *
    7414  * @class
    7415  * @augments wp.media.view.PriorityList
    7416  * @augments wp.media.View
    7417  * @augments wp.Backbone.View
    7418  * @augments Backbone.View
    7419  */
    7420 var Sidebar = wp.media.view.PriorityList.extend({
    7421     className: 'media-sidebar'
    7422 });
    7423 
    7424 module.exports = Sidebar;
    7425 
    7426 },{}],64:[function(require,module,exports){
     8670module.exports = Cropper;
     8671
     8672
     8673/***/ }),
     8674/* 97 */
     8675/***/ (function(module, exports) {
     8676
    74278677/**
    74288678 * wp.media.view.SiteIconCropper
     
    74678717module.exports = SiteIconCropper;
    74688718
    7469 },{}],65:[function(require,module,exports){
     8719
     8720/***/ }),
     8721/* 98 */
     8722/***/ (function(module, exports) {
     8723
    74708724/**
    74718725 * wp.media.view.SiteIconPreview
     
    75238777module.exports = SiteIconPreview;
    75248778
    7525 },{}],66:[function(require,module,exports){
     8779
     8780/***/ }),
     8781/* 99 */
     8782/***/ (function(module, exports) {
     8783
     8784/**
     8785 * wp.media.view.EditImage
     8786 *
     8787 * @class
     8788 * @augments wp.media.View
     8789 * @augments wp.Backbone.View
     8790 * @augments Backbone.View
     8791 */
     8792var View = wp.media.View,
     8793    EditImage;
     8794
     8795EditImage = View.extend({
     8796    className: 'image-editor',
     8797    template: wp.template('image-editor'),
     8798
     8799    initialize: function( options ) {
     8800        this.editor = window.imageEdit;
     8801        this.controller = options.controller;
     8802        View.prototype.initialize.apply( this, arguments );
     8803    },
     8804
     8805    prepare: function() {
     8806        return this.model.toJSON();
     8807    },
     8808
     8809    loadEditor: function() {
     8810        var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
     8811        dfd.done( _.bind( this.focus, this ) );
     8812    },
     8813
     8814    focus: function() {
     8815        this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
     8816    },
     8817
     8818    back: function() {
     8819        var lastState = this.controller.lastState();
     8820        this.controller.setState( lastState );
     8821    },
     8822
     8823    refresh: function() {
     8824        this.model.fetch();
     8825    },
     8826
     8827    save: function() {
     8828        var lastState = this.controller.lastState();
     8829
     8830        this.model.fetch().done( _.bind( function() {
     8831            this.controller.setState( lastState );
     8832        }, this ) );
     8833    }
     8834
     8835});
     8836
     8837module.exports = EditImage;
     8838
     8839
     8840/***/ }),
     8841/* 100 */
     8842/***/ (function(module, exports) {
     8843
    75268844/**
    75278845 * wp.media.view.Spinner
     
    75588876module.exports = Spinner;
    75598877
    7560 },{}],67:[function(require,module,exports){
    7561 /**
    7562  * wp.media.view.Toolbar
    7563  *
    7564  * A toolbar which consists of a primary and a secondary section. Each sections
    7565  * can be filled with views.
    7566  *
    7567  * @class
    7568  * @augments wp.media.View
    7569  * @augments wp.Backbone.View
    7570  * @augments Backbone.View
    7571  */
    7572 var View = wp.media.View,
    7573     Toolbar;
    7574 
    7575 Toolbar = View.extend({
    7576     tagName:   'div',
    7577     className: 'media-toolbar',
    7578 
    7579     initialize: function() {
    7580         var state = this.controller.state(),
    7581             selection = this.selection = state.get('selection'),
    7582             library = this.library = state.get('library');
    7583 
    7584         this._views = {};
    7585 
    7586         // The toolbar is composed of two `PriorityList` views.
    7587         this.primary   = new wp.media.view.PriorityList();
    7588         this.secondary = new wp.media.view.PriorityList();
    7589         this.primary.$el.addClass('media-toolbar-primary search-form');
    7590         this.secondary.$el.addClass('media-toolbar-secondary');
    7591 
    7592         this.views.set([ this.secondary, this.primary ]);
    7593 
    7594         if ( this.options.items ) {
    7595             this.set( this.options.items, { silent: true });
    7596         }
    7597 
    7598         if ( ! this.options.silent ) {
    7599             this.render();
    7600         }
    7601 
    7602         if ( selection ) {
    7603             selection.on( 'add remove reset', this.refresh, this );
    7604         }
    7605 
    7606         if ( library ) {
    7607             library.on( 'add remove reset', this.refresh, this );
    7608         }
    7609     },
    7610     /**
    7611      * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
    7612      */
    7613     dispose: function() {
    7614         if ( this.selection ) {
    7615             this.selection.off( null, null, this );
    7616         }
    7617 
    7618         if ( this.library ) {
    7619             this.library.off( null, null, this );
    7620         }
    7621         /**
    7622          * call 'dispose' directly on the parent class
    7623          */
    7624         return View.prototype.dispose.apply( this, arguments );
    7625     },
    7626 
    7627     ready: function() {
    7628         this.refresh();
    7629     },
    7630 
    7631     /**
    7632      * @param {string} id
    7633      * @param {Backbone.View|Object} view
    7634      * @param {Object} [options={}]
    7635      * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
    7636      */
    7637     set: function( id, view, options ) {
    7638         var list;
    7639         options = options || {};
    7640 
    7641         // Accept an object with an `id` : `view` mapping.
    7642         if ( _.isObject( id ) ) {
    7643             _.each( id, function( view, id ) {
    7644                 this.set( id, view, { silent: true });
    7645             }, this );
    7646 
    7647         } else {
    7648             if ( ! ( view instanceof Backbone.View ) ) {
    7649                 view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
    7650                 view = new wp.media.view.Button( view ).render();
    7651             }
    7652 
    7653             view.controller = view.controller || this.controller;
    7654 
    7655             this._views[ id ] = view;
    7656 
    7657             list = view.options.priority < 0 ? 'secondary' : 'primary';
    7658             this[ list ].set( id, view, options );
    7659         }
    7660 
    7661         if ( ! options.silent ) {
    7662             this.refresh();
    7663         }
    7664 
    7665         return this;
    7666     },
    7667     /**
    7668      * @param {string} id
    7669      * @returns {wp.media.view.Button}
    7670      */
    7671     get: function( id ) {
    7672         return this._views[ id ];
    7673     },
    7674     /**
    7675      * @param {string} id
    7676      * @param {Object} options
    7677      * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
    7678      */
    7679     unset: function( id, options ) {
    7680         delete this._views[ id ];
    7681         this.primary.unset( id, options );
    7682         this.secondary.unset( id, options );
    7683 
    7684         if ( ! options || ! options.silent ) {
    7685             this.refresh();
    7686         }
    7687         return this;
    7688     },
    7689 
    7690     refresh: function() {
    7691         var state = this.controller.state(),
    7692             library = state.get('library'),
    7693             selection = state.get('selection');
    7694 
    7695         _.each( this._views, function( button ) {
    7696             if ( ! button.model || ! button.options || ! button.options.requires ) {
    7697                 return;
    7698             }
    7699 
    7700             var requires = button.options.requires,
    7701                 disabled = false;
    7702 
    7703             // Prevent insertion of attachments if any of them are still uploading
    7704             disabled = _.some( selection.models, function( attachment ) {
    7705                 return attachment.get('uploading') === true;
    7706             });
    7707 
    7708             if ( requires.selection && selection && ! selection.length ) {
    7709                 disabled = true;
    7710             } else if ( requires.library && library && ! library.length ) {
    7711                 disabled = true;
    7712             }
    7713             button.model.set( 'disabled', disabled );
    7714         });
    7715     }
    7716 });
    7717 
    7718 module.exports = Toolbar;
    7719 
    7720 },{}],68:[function(require,module,exports){
    7721 /**
    7722  * wp.media.view.Toolbar.Embed
    7723  *
    7724  * @class
    7725  * @augments wp.media.view.Toolbar.Select
    7726  * @augments wp.media.view.Toolbar
    7727  * @augments wp.media.View
    7728  * @augments wp.Backbone.View
    7729  * @augments Backbone.View
    7730  */
    7731 var Select = wp.media.view.Toolbar.Select,
    7732     l10n = wp.media.view.l10n,
    7733     Embed;
    7734 
    7735 Embed = Select.extend({
    7736     initialize: function() {
    7737         _.defaults( this.options, {
    7738             text: l10n.insertIntoPost,
    7739             requires: false
    7740         });
    7741         // Call 'initialize' directly on the parent class.
    7742         Select.prototype.initialize.apply( this, arguments );
    7743     },
    7744 
    7745     refresh: function() {
    7746         var url = this.controller.state().props.get('url');
    7747         this.get('select').model.set( 'disabled', ! url || url === 'http://' );
    7748         /**
    7749          * call 'refresh' directly on the parent class
    7750          */
    7751         Select.prototype.refresh.apply( this, arguments );
    7752     }
    7753 });
    7754 
    7755 module.exports = Embed;
    7756 
    7757 },{}],69:[function(require,module,exports){
    7758 /**
    7759  * wp.media.view.Toolbar.Select
    7760  *
    7761  * @class
    7762  * @augments wp.media.view.Toolbar
    7763  * @augments wp.media.View
    7764  * @augments wp.Backbone.View
    7765  * @augments Backbone.View
    7766  */
    7767 var Toolbar = wp.media.view.Toolbar,
    7768     l10n = wp.media.view.l10n,
    7769     Select;
    7770 
    7771 Select = Toolbar.extend({
    7772     initialize: function() {
    7773         var options = this.options;
    7774 
    7775         _.bindAll( this, 'clickSelect' );
    7776 
    7777         _.defaults( options, {
    7778             event: 'select',
    7779             state: false,
    7780             reset: true,
    7781             close: true,
    7782             text:  l10n.select,
    7783 
    7784             // Does the button rely on the selection?
    7785             requires: {
    7786                 selection: true
    7787             }
    7788         });
    7789 
    7790         options.items = _.defaults( options.items || {}, {
    7791             select: {
    7792                 style:    'primary',
    7793                 text:     options.text,
    7794                 priority: 80,
    7795                 click:    this.clickSelect,
    7796                 requires: options.requires
    7797             }
    7798         });
    7799         // Call 'initialize' directly on the parent class.
    7800         Toolbar.prototype.initialize.apply( this, arguments );
    7801     },
    7802 
    7803     clickSelect: function() {
    7804         var options = this.options,
    7805             controller = this.controller;
    7806 
    7807         if ( options.close ) {
    7808             controller.close();
    7809         }
    7810 
    7811         if ( options.event ) {
    7812             controller.state().trigger( options.event );
    7813         }
    7814 
    7815         if ( options.state ) {
    7816             controller.setState( options.state );
    7817         }
    7818 
    7819         if ( options.reset ) {
    7820             controller.reset();
    7821         }
    7822     }
    7823 });
    7824 
    7825 module.exports = Select;
    7826 
    7827 },{}],70:[function(require,module,exports){
    7828 /**
    7829  * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
    7830  * and relays drag'n'dropped files to a media workflow.
    7831  *
    7832  * wp.media.view.EditorUploader
    7833  *
    7834  * @class
    7835  * @augments wp.media.View
    7836  * @augments wp.Backbone.View
    7837  * @augments Backbone.View
    7838  */
    7839 var View = wp.media.View,
    7840     l10n = wp.media.view.l10n,
    7841     $ = jQuery,
    7842     EditorUploader;
    7843 
    7844 EditorUploader = View.extend({
    7845     tagName:   'div',
    7846     className: 'uploader-editor',
    7847     template:  wp.template( 'uploader-editor' ),
    7848 
    7849     localDrag: false,
    7850     overContainer: false,
    7851     overDropzone: false,
    7852     draggingFile: null,
    7853 
    7854     /**
    7855      * Bind drag'n'drop events to callbacks.
    7856      */
    7857     initialize: function() {
    7858         this.initialized = false;
    7859 
    7860         // Bail if not enabled or UA does not support drag'n'drop or File API.
    7861         if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
    7862             return this;
    7863         }
    7864 
    7865         this.$document = $(document);
    7866         this.dropzones = [];
    7867         this.files = [];
    7868 
    7869         this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
    7870         this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
    7871         this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
    7872         this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
    7873 
    7874         this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
    7875         this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
    7876 
    7877         this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
    7878             this.localDrag = event.type === 'dragstart';
    7879 
    7880             if ( event.type === 'drop' ) {
    7881                 this.containerDragleave();
    7882             }
    7883         }, this ) );
    7884 
    7885         this.initialized = true;
    7886         return this;
    7887     },
    7888 
    7889     /**
    7890      * Check browser support for drag'n'drop.
    7891      *
    7892      * @return Boolean
    7893      */
    7894     browserSupport: function() {
    7895         var supports = false, div = document.createElement('div');
    7896 
    7897         supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
    7898         supports = supports && !! ( window.File && window.FileList && window.FileReader );
    7899         return supports;
    7900     },
    7901 
    7902     isDraggingFile: function( event ) {
    7903         if ( this.draggingFile !== null ) {
    7904             return this.draggingFile;
    7905         }
    7906 
    7907         if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
    7908             return false;
    7909         }
    7910 
    7911         this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
    7912             _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
    7913 
    7914         return this.draggingFile;
    7915     },
    7916 
    7917     refresh: function( e ) {
    7918         var dropzone_id;
    7919         for ( dropzone_id in this.dropzones ) {
    7920             // Hide the dropzones only if dragging has left the screen.
    7921             this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
    7922         }
    7923 
    7924         if ( ! _.isUndefined( e ) ) {
    7925             $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
    7926         }
    7927 
    7928         if ( ! this.overContainer && ! this.overDropzone ) {
    7929             this.draggingFile = null;
    7930         }
    7931 
    7932         return this;
    7933     },
    7934 
    7935     render: function() {
    7936         if ( ! this.initialized ) {
    7937             return this;
    7938         }
    7939 
    7940         View.prototype.render.apply( this, arguments );
    7941         $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
    7942         return this;
    7943     },
    7944 
    7945     attach: function( index, editor ) {
    7946         // Attach a dropzone to an editor.
    7947         var dropzone = this.$el.clone();
    7948         this.dropzones.push( dropzone );
    7949         $( editor ).append( dropzone );
    7950         return this;
    7951     },
    7952 
    7953     /**
    7954      * When a file is dropped on the editor uploader, open up an editor media workflow
    7955      * and upload the file immediately.
    7956      *
    7957      * @param  {jQuery.Event} event The 'drop' event.
    7958      */
    7959     drop: function( event ) {
    7960         var $wrap, uploadView;
    7961 
    7962         this.containerDragleave( event );
    7963         this.dropzoneDragleave( event );
    7964 
    7965         this.files = event.originalEvent.dataTransfer.files;
    7966         if ( this.files.length < 1 ) {
    7967             return;
    7968         }
    7969 
    7970         // Set the active editor to the drop target.
    7971         $wrap = $( event.target ).parents( '.wp-editor-wrap' );
    7972         if ( $wrap.length > 0 && $wrap[0].id ) {
    7973             window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
    7974         }
    7975 
    7976         if ( ! this.workflow ) {
    7977             this.workflow = wp.media.editor.open( window.wpActiveEditor, {
    7978                 frame:    'post',
    7979                 state:    'insert',
    7980                 title:    l10n.addMedia,
    7981                 multiple: true
    7982             });
    7983 
    7984             uploadView = this.workflow.uploader;
    7985 
    7986             if ( uploadView.uploader && uploadView.uploader.ready ) {
    7987                 this.addFiles.apply( this );
    7988             } else {
    7989                 this.workflow.on( 'uploader:ready', this.addFiles, this );
    7990             }
    7991         } else {
    7992             this.workflow.state().reset();
    7993             this.addFiles.apply( this );
    7994             this.workflow.open();
    7995         }
    7996 
    7997         return false;
    7998     },
    7999 
    8000     /**
    8001      * Add the files to the uploader.
    8002      */
    8003     addFiles: function() {
    8004         if ( this.files.length ) {
    8005             this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
    8006             this.files = [];
    8007         }
    8008         return this;
    8009     },
    8010 
    8011     containerDragover: function( event ) {
    8012         if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    8013             return;
    8014         }
    8015 
    8016         this.overContainer = true;
    8017         this.refresh();
    8018     },
    8019 
    8020     containerDragleave: function() {
    8021         this.overContainer = false;
    8022 
    8023         // Throttle dragleave because it's called when bouncing from some elements to others.
    8024         _.delay( _.bind( this.refresh, this ), 50 );
    8025     },
    8026 
    8027     dropzoneDragover: function( event ) {
    8028         if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    8029             return;
    8030         }
    8031 
    8032         this.overDropzone = true;
    8033         this.refresh( event );
    8034         return false;
    8035     },
    8036 
    8037     dropzoneDragleave: function( e ) {
    8038         this.overDropzone = false;
    8039         _.delay( _.bind( this.refresh, this, e ), 50 );
    8040     },
    8041 
    8042     click: function( e ) {
    8043         // In the rare case where the dropzone gets stuck, hide it on click.
    8044         this.containerDragleave( e );
    8045         this.dropzoneDragleave( e );
    8046         this.localDrag = false;
    8047     }
    8048 });
    8049 
    8050 module.exports = EditorUploader;
    8051 
    8052 },{}],71:[function(require,module,exports){
    8053 /**
    8054  * wp.media.view.UploaderInline
    8055  *
    8056  * The inline uploader that shows up in the 'Upload Files' tab.
    8057  *
    8058  * @class
    8059  * @augments wp.media.View
    8060  * @augments wp.Backbone.View
    8061  * @augments Backbone.View
    8062  */
    8063 var View = wp.media.View,
    8064     UploaderInline;
    8065 
    8066 UploaderInline = View.extend({
    8067     tagName:   'div',
    8068     className: 'uploader-inline',
    8069     template:  wp.template('uploader-inline'),
    8070 
    8071     events: {
    8072         'click .close': 'hide'
    8073     },
    8074 
    8075     initialize: function() {
    8076         _.defaults( this.options, {
    8077             message: '',
    8078             status:  true,
    8079             canClose: false
    8080         });
    8081 
    8082         if ( ! this.options.$browser && this.controller.uploader ) {
    8083             this.options.$browser = this.controller.uploader.$browser;
    8084         }
    8085 
    8086         if ( _.isUndefined( this.options.postId ) ) {
    8087             this.options.postId = wp.media.view.settings.post.id;
    8088         }
    8089 
    8090         if ( this.options.status ) {
    8091             this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
    8092                 controller: this.controller
    8093             }) );
    8094         }
    8095     },
    8096 
    8097     prepare: function() {
    8098         var suggestedWidth = this.controller.state().get('suggestedWidth'),
    8099             suggestedHeight = this.controller.state().get('suggestedHeight'),
    8100             data = {};
    8101 
    8102         data.message = this.options.message;
    8103         data.canClose = this.options.canClose;
    8104 
    8105         if ( suggestedWidth && suggestedHeight ) {
    8106             data.suggestedWidth = suggestedWidth;
    8107             data.suggestedHeight = suggestedHeight;
    8108         }
    8109 
    8110         return data;
    8111     },
    8112     /**
    8113      * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    8114      */
    8115     dispose: function() {
    8116         if ( this.disposing ) {
    8117             /**
    8118              * call 'dispose' directly on the parent class
    8119              */
    8120             return View.prototype.dispose.apply( this, arguments );
    8121         }
    8122 
    8123         // Run remove on `dispose`, so we can be sure to refresh the
    8124         // uploader with a view-less DOM. Track whether we're disposing
    8125         // so we don't trigger an infinite loop.
    8126         this.disposing = true;
    8127         return this.remove();
    8128     },
    8129     /**
    8130      * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    8131      */
    8132     remove: function() {
    8133         /**
    8134          * call 'remove' directly on the parent class
    8135          */
    8136         var result = View.prototype.remove.apply( this, arguments );
    8137 
    8138         _.defer( _.bind( this.refresh, this ) );
    8139         return result;
    8140     },
    8141 
    8142     refresh: function() {
    8143         var uploader = this.controller.uploader;
    8144 
    8145         if ( uploader ) {
    8146             uploader.refresh();
    8147         }
    8148     },
    8149     /**
    8150      * @returns {wp.media.view.UploaderInline}
    8151      */
    8152     ready: function() {
    8153         var $browser = this.options.$browser,
    8154             $placeholder;
    8155 
    8156         if ( this.controller.uploader ) {
    8157             $placeholder = this.$('.browser');
    8158 
    8159             // Check if we've already replaced the placeholder.
    8160             if ( $placeholder[0] === $browser[0] ) {
    8161                 return;
    8162             }
    8163 
    8164             $browser.detach().text( $placeholder.text() );
    8165             $browser[0].className = $placeholder[0].className;
    8166             $placeholder.replaceWith( $browser.show() );
    8167         }
    8168 
    8169         this.refresh();
    8170         return this;
    8171     },
    8172     show: function() {
    8173         this.$el.removeClass( 'hidden' );
    8174     },
    8175     hide: function() {
    8176         this.$el.addClass( 'hidden' );
    8177     }
    8178 
    8179 });
    8180 
    8181 module.exports = UploaderInline;
    8182 
    8183 },{}],72:[function(require,module,exports){
    8184 /**
    8185  * wp.media.view.UploaderStatusError
    8186  *
    8187  * @class
    8188  * @augments wp.media.View
    8189  * @augments wp.Backbone.View
    8190  * @augments Backbone.View
    8191  */
    8192 var UploaderStatusError = wp.media.View.extend({
    8193     className: 'upload-error',
    8194     template:  wp.template('uploader-status-error')
    8195 });
    8196 
    8197 module.exports = UploaderStatusError;
    8198 
    8199 },{}],73:[function(require,module,exports){
    8200 /**
    8201  * wp.media.view.UploaderStatus
    8202  *
    8203  * An uploader status for on-going uploads.
    8204  *
    8205  * @class
    8206  * @augments wp.media.View
    8207  * @augments wp.Backbone.View
    8208  * @augments Backbone.View
    8209  */
    8210 var View = wp.media.View,
    8211     UploaderStatus;
    8212 
    8213 UploaderStatus = View.extend({
    8214     className: 'media-uploader-status',
    8215     template:  wp.template('uploader-status'),
    8216 
    8217     events: {
    8218         'click .upload-dismiss-errors': 'dismiss'
    8219     },
    8220 
    8221     initialize: function() {
    8222         this.queue = wp.Uploader.queue;
    8223         this.queue.on( 'add remove reset', this.visibility, this );
    8224         this.queue.on( 'add remove reset change:percent', this.progress, this );
    8225         this.queue.on( 'add remove reset change:uploading', this.info, this );
    8226 
    8227         this.errors = wp.Uploader.errors;
    8228         this.errors.reset();
    8229         this.errors.on( 'add remove reset', this.visibility, this );
    8230         this.errors.on( 'add', this.error, this );
    8231     },
    8232     /**
    8233      * @global wp.Uploader
    8234      * @returns {wp.media.view.UploaderStatus}
    8235      */
    8236     dispose: function() {
    8237         wp.Uploader.queue.off( null, null, this );
    8238         /**
    8239          * call 'dispose' directly on the parent class
    8240          */
    8241         View.prototype.dispose.apply( this, arguments );
    8242         return this;
    8243     },
    8244 
    8245     visibility: function() {
    8246         this.$el.toggleClass( 'uploading', !! this.queue.length );
    8247         this.$el.toggleClass( 'errors', !! this.errors.length );
    8248         this.$el.toggle( !! this.queue.length || !! this.errors.length );
    8249     },
    8250 
    8251     ready: function() {
    8252         _.each({
    8253             '$bar':      '.media-progress-bar div',
    8254             '$index':    '.upload-index',
    8255             '$total':    '.upload-total',
    8256             '$filename': '.upload-filename'
    8257         }, function( selector, key ) {
    8258             this[ key ] = this.$( selector );
    8259         }, this );
    8260 
    8261         this.visibility();
    8262         this.progress();
    8263         this.info();
    8264     },
    8265 
    8266     progress: function() {
    8267         var queue = this.queue,
    8268             $bar = this.$bar;
    8269 
    8270         if ( ! $bar || ! queue.length ) {
    8271             return;
    8272         }
    8273 
    8274         $bar.width( ( queue.reduce( function( memo, attachment ) {
    8275             if ( ! attachment.get('uploading') ) {
    8276                 return memo + 100;
    8277             }
    8278 
    8279             var percent = attachment.get('percent');
    8280             return memo + ( _.isNumber( percent ) ? percent : 100 );
    8281         }, 0 ) / queue.length ) + '%' );
    8282     },
    8283 
    8284     info: function() {
    8285         var queue = this.queue,
    8286             index = 0, active;
    8287 
    8288         if ( ! queue.length ) {
    8289             return;
    8290         }
    8291 
    8292         active = this.queue.find( function( attachment, i ) {
    8293             index = i;
    8294             return attachment.get('uploading');
    8295         });
    8296 
    8297         this.$index.text( index + 1 );
    8298         this.$total.text( queue.length );
    8299         this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
    8300     },
    8301     /**
    8302      * @param {string} filename
    8303      * @returns {string}
    8304      */
    8305     filename: function( filename ) {
    8306         return _.escape( filename );
    8307     },
    8308     /**
    8309      * @param {Backbone.Model} error
    8310      */
    8311     error: function( error ) {
    8312         this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    8313             filename: this.filename( error.get('file').name ),
    8314             message:  error.get('message')
    8315         }), { at: 0 });
    8316     },
    8317 
    8318     /**
    8319      * @global wp.Uploader
    8320      *
    8321      * @param {Object} event
    8322      */
    8323     dismiss: function( event ) {
    8324         var errors = this.views.get('.upload-errors');
    8325 
    8326         event.preventDefault();
    8327 
    8328         if ( errors ) {
    8329             _.invoke( errors, 'remove' );
    8330         }
    8331         wp.Uploader.errors.reset();
    8332     }
    8333 });
    8334 
    8335 module.exports = UploaderStatus;
    8336 
    8337 },{}],74:[function(require,module,exports){
    8338 /**
    8339  * wp.media.view.UploaderWindow
    8340  *
    8341  * An uploader window that allows for dragging and dropping media.
    8342  *
    8343  * @class
    8344  * @augments wp.media.View
    8345  * @augments wp.Backbone.View
    8346  * @augments Backbone.View
    8347  *
    8348  * @param {object} [options]                   Options hash passed to the view.
    8349  * @param {object} [options.uploader]          Uploader properties.
    8350  * @param {jQuery} [options.uploader.browser]
    8351  * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
    8352  * @param {object} [options.uploader.params]
    8353  */
    8354 var $ = jQuery,
    8355     UploaderWindow;
    8356 
    8357 UploaderWindow = wp.media.View.extend({
    8358     tagName:   'div',
    8359     className: 'uploader-window',
    8360     template:  wp.template('uploader-window'),
    8361 
    8362     initialize: function() {
    8363         var uploader;
    8364 
    8365         this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');
    8366 
    8367         uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
    8368             dropzone:  this.$el,
    8369             browser:   this.$browser,
    8370             params:    {}
    8371         });
    8372 
    8373         // Ensure the dropzone is a jQuery collection.
    8374         if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
    8375             uploader.dropzone = $( uploader.dropzone );
    8376         }
    8377 
    8378         this.controller.on( 'activate', this.refresh, this );
    8379 
    8380         this.controller.on( 'detach', function() {
    8381             this.$browser.remove();
    8382         }, this );
    8383     },
    8384 
    8385     refresh: function() {
    8386         if ( this.uploader ) {
    8387             this.uploader.refresh();
    8388         }
    8389     },
    8390 
    8391     ready: function() {
    8392         var postId = wp.media.view.settings.post.id,
    8393             dropzone;
    8394 
    8395         // If the uploader already exists, bail.
    8396         if ( this.uploader ) {
    8397             return;
    8398         }
    8399 
    8400         if ( postId ) {
    8401             this.options.uploader.params.post_id = postId;
    8402         }
    8403         this.uploader = new wp.Uploader( this.options.uploader );
    8404 
    8405         dropzone = this.uploader.dropzone;
    8406         dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
    8407         dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
    8408 
    8409         $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
    8410     },
    8411 
    8412     _ready: function() {
    8413         this.controller.trigger( 'uploader:ready' );
    8414     },
    8415 
    8416     show: function() {
    8417         var $el = this.$el.show();
    8418 
    8419         // Ensure that the animation is triggered by waiting until
    8420         // the transparent element is painted into the DOM.
    8421         _.defer( function() {
    8422             $el.css({ opacity: 1 });
    8423         });
    8424     },
    8425 
    8426     hide: function() {
    8427         var $el = this.$el.css({ opacity: 0 });
    8428 
    8429         wp.media.transition( $el ).done( function() {
    8430             // Transition end events are subject to race conditions.
    8431             // Make sure that the value is set as intended.
    8432             if ( '0' === $el.css('opacity') ) {
    8433                 $el.hide();
    8434             }
    8435         });
    8436 
    8437         // https://core.trac.wordpress.org/ticket/27341
    8438         _.delay( function() {
    8439             if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
    8440                 $el.hide();
    8441             }
    8442         }, 500 );
    8443     }
    8444 });
    8445 
    8446 module.exports = UploaderWindow;
    8447 
    8448 },{}],75:[function(require,module,exports){
    8449 /**
    8450  * wp.media.View
    8451  *
    8452  * The base view class for media.
    8453  *
    8454  * Undelegating events, removing events from the model, and
    8455  * removing events from the controller mirror the code for
    8456  * `Backbone.View.dispose` in Backbone 0.9.8 development.
    8457  *
    8458  * This behavior has since been removed, and should not be used
    8459  * outside of the media manager.
    8460  *
    8461  * @class
    8462  * @augments wp.Backbone.View
    8463  * @augments Backbone.View
    8464  */
    8465 var View = wp.Backbone.View.extend({
    8466     constructor: function( options ) {
    8467         if ( options && options.controller ) {
    8468             this.controller = options.controller;
    8469         }
    8470         wp.Backbone.View.apply( this, arguments );
    8471     },
    8472     /**
    8473      * @todo The internal comment mentions this might have been a stop-gap
    8474      *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
    8475      *       care of this in Backbone.View now.
    8476      *
    8477      * @returns {wp.media.View} Returns itself to allow chaining
    8478      */
    8479     dispose: function() {
    8480         // Undelegating events, removing events from the model, and
    8481         // removing events from the controller mirror the code for
    8482         // `Backbone.View.dispose` in Backbone 0.9.8 development.
    8483         this.undelegateEvents();
    8484 
    8485         if ( this.model && this.model.off ) {
    8486             this.model.off( null, null, this );
    8487         }
    8488 
    8489         if ( this.collection && this.collection.off ) {
    8490             this.collection.off( null, null, this );
    8491         }
    8492 
    8493         // Unbind controller events.
    8494         if ( this.controller && this.controller.off ) {
    8495             this.controller.off( null, null, this );
    8496         }
    8497 
    8498         return this;
    8499     },
    8500     /**
    8501      * @returns {wp.media.View} Returns itself to allow chaining
    8502      */
    8503     remove: function() {
    8504         this.dispose();
    8505         /**
    8506          * call 'remove' directly on the parent class
    8507          */
    8508         return wp.Backbone.View.prototype.remove.apply( this, arguments );
    8509     }
    8510 });
    8511 
    8512 module.exports = View;
    8513 
    8514 },{}]},{},[19]);
     8878
     8879/***/ })
     8880/******/ ]));
  • branches/4.4/src/wp-includes/pluggable.php

    r45981 r46498  
    11021102 */
    11031103function check_admin_referer( $action = -1, $query_arg = '_wpnonce' ) {
    1104     if ( -1 == $action )
    1105         _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2' );
     1104    if ( -1 === $action )
     1105        _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2.0' );
    11061106
    11071107    $adminurl = strtolower(admin_url());
     
    11201120    do_action( 'check_admin_referer', $action, $result );
    11211121
    1122     if ( ! $result && ! ( -1 == $action && strpos( $referer, $adminurl ) === 0 ) ) {
     1122    if ( ! $result && ! ( -1 === $action && strpos( $referer, $adminurl ) === 0 ) ) {
    11231123        wp_nonce_ays( $action );
    11241124        die();
     
    11451145 */
    11461146function check_ajax_referer( $action = -1, $query_arg = false, $die = true ) {
     1147    if ( -1 === $action )
     1148        _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2.0' );
     1149
    11471150    $nonce = '';
    11481151
     
    24942497}
    24952498endif;
    2496 
  • branches/4.4/src/wp-includes/query.php

    r39959 r46498  
    14581458            , 'attachment_id'
    14591459            , 'name'
    1460             , 'static'
    14611460            , 'pagename'
    14621461            , 'page_id'
     
    16691668            // post is being queried.
    16701669            $this->is_single = true;
    1671         } elseif ( '' != $qv['static'] || '' != $qv['pagename'] || !empty($qv['page_id']) ) {
     1670        } elseif ( '' != $qv['pagename'] || !empty($qv['page_id']) ) {
    16721671            $this->is_page = true;
    16731672            $this->is_single = false;
  • branches/4.4/src/wp-includes/rest-api.php

    r35718 r46498  
    377377        header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' );
    378378        header( 'Access-Control-Allow-Credentials: true' );
     379        header( 'Vary: Origin', false );
     380    } elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) {
     381        header( 'Vary: Origin', false );
    379382    }
    380383
  • branches/4.4/tests/phpunit/tests/auth.php

    r35224 r46498  
    142142    }
    143143
     144    public function test_check_admin_referer_with_default_action_as_string_not_doing_it_wrong() {
     145        // A valid nonce needs to be set so the check doesn't die()
     146        $_REQUEST['_wpnonce'] = wp_create_nonce( '-1' );
     147        $result               = check_admin_referer( '-1' );
     148        $this->assertSame( 1, $result );
     149
     150        unset( $_REQUEST['_wpnonce'] );
     151    }
     152
    144153    /**
    145154     * @ticket 24030
     
    152161
    153162        $this->assertEquals( $count, did_action( $this->nonce_failure_hook ) );
     163    }
     164
     165    /**
     166     * @ticket 36361
     167     */
     168    public function test_check_admin_referer_with_no_action_triggers_doing_it_wrong() {
     169        $this->setExpectedIncorrectUsage( 'check_admin_referer' );
     170
     171        // A valid nonce needs to be set so the check doesn't die()
     172        $_REQUEST['_wpnonce'] = wp_create_nonce( -1 );
     173        $result = check_admin_referer();
     174        $this->assertSame( 1, $result );
     175
     176        unset( $_REQUEST['_wpnonce'] );
     177    }
     178
     179    /**
     180     * @ticket 36361
     181     */
     182    public function test_check_ajax_referer_with_no_action_triggers_doing_it_wrong() {
     183        $this->setExpectedIncorrectUsage( 'check_ajax_referer' );
     184
     185        // A valid nonce needs to be set so the check doesn't die()
     186        $_REQUEST['_wpnonce'] = wp_create_nonce( -1 );
     187        $result = check_ajax_referer();
     188        $this->assertSame( 1, $result );
     189
     190        unset( $_REQUEST['_wpnonce'] );
    154191    }
    155192
  • branches/4.4/tests/phpunit/tests/query/vars.php

    r36052 r46498  
    1717        do_action( 'init' );
    1818
    19         $this->assertEquals( array(
     19        $this->assertEquals(
     20            array(
    2021
    21             // Static public query vars:
    22             'm',
    23             'p',
    24             'posts',
    25             'w',
    26             'cat',
    27             'withcomments',
    28             'withoutcomments',
    29             's',
    30             'search',
    31             'exact',
    32             'sentence',
    33             'calendar',
    34             'page',
    35             'paged',
    36             'more',
    37             'tb',
    38             'pb',
    39             'author',
    40             'order',
    41             'orderby',
    42             'year',
    43             'monthnum',
    44             'day',
    45             'hour',
    46             'minute',
    47             'second',
    48             'name',
    49             'category_name',
    50             'tag',
    51             'feed',
    52             'author_name',
    53             'static',
    54             'pagename',
    55             'page_id',
    56             'error',
    57             'comments_popup',
    58             'attachment',
    59             'attachment_id',
    60             'subpost',
    61             'subpost_id',
    62             'preview',
    63             'robots',
    64             'taxonomy',
    65             'term',
    66             'cpage',
    67             'post_type',
    68             'embed',
     22                // Static public query vars:
     23                'm',
     24                'p',
     25                'posts',
     26                'w',
     27                'cat',
     28                'withcomments',
     29                'withoutcomments',
     30                's',
     31                'search',
     32                'exact',
     33                'sentence',
     34                'calendar',
     35                'page',
     36                'paged',
     37                'more',
     38                'tb',
     39                'pb',
     40                'author',
     41                'order',
     42                'orderby',
     43                'year',
     44                'monthnum',
     45                'day',
     46                'hour',
     47                'minute',
     48                'second',
     49                'name',
     50                'category_name',
     51                'tag',
     52                'feed',
     53                'author_name',
     54                'pagename',
     55                'page_id',
     56                'error',
     57                'attachment',
     58                'attachment_id',
     59                'subpost',
     60                'subpost_id',
     61                'preview',
     62                'robots',
     63                'taxonomy',
     64                'term',
     65                'cpage',
     66                'post_type',
     67                'embed',
    6968
    70             // Dynamically added public query vars:
    71             'post_format',
    72             'rest_route',
     69                // Dynamically added public query vars:
     70                'post_format',
     71                'rest_route',
    7372
    74         ), $wp->public_query_vars, 'Care should be taken when introducing new public query vars. See https://core.trac.wordpress.org/ticket/35115' );
     73            ),
     74            $wp->public_query_vars,
     75            'Care should be taken when introducing new public query vars. See https://core.trac.wordpress.org/ticket/35115'
     76        );
    7577    }
    7678
Note: See TracChangeset for help on using the changeset viewer.