Make WordPress Core

Changeset 50018


Ignore:
Timestamp:
01/25/2021 08:14:35 PM (4 years ago)
Author:
desrosj
Message:

Build/Test Tools: Correct JavaScript files in the 4.4 branch.

In [46498], some JavaScript files were unintentionally changed. This restores those files to their correct state.

Partially reverts [46498].
See #52367.

Location:
branches/4.4/src/wp-includes/js
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • branches/4.4/src/wp-includes/js/media-audiovideo.js

    r46498 r50018  
    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 
     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){
    702var media = wp.media,
    713    baseSettings = window._wpmejsSettings || {},
     
    274206};
    275207
    276 media.model.PostMedia = __webpack_require__( 1 );
    277 media.controller.AudioDetails = __webpack_require__( 2 );
    278 media.controller.VideoDetails = __webpack_require__( 3 );
    279 media.view.MediaFrame.MediaDetails = __webpack_require__( 4 );
    280 media.view.MediaFrame.AudioDetails = __webpack_require__( 5 );
    281 media.view.MediaFrame.VideoDetails = __webpack_require__( 6 );
    282 media.view.MediaDetails = __webpack_require__( 7 );
    283 media.view.AudioDetails = __webpack_require__( 8 );
    284 media.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  */
    300 var 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 
    330 module.exports = PostMedia;
    331 
    332 
    333 /***/ }),
    334 /* 2 */
    335 /***/ (function(module, exports) {
    336 
     208media.model.PostMedia = require( './models/post-media.js' );
     209media.controller.AudioDetails = require( './controllers/audio-details.js' );
     210media.controller.VideoDetails = require( './controllers/video-details.js' );
     211media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' );
     212media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' );
     213media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' );
     214media.view.MediaDetails = require( './views/media-details.js' );
     215media.view.AudioDetails = require( './views/audio-details.js' );
     216media.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){
    337219/**
    338220 * wp.media.controller.AudioDetails
     
    367249module.exports = AudioDetails;
    368250
    369 
    370 /***/ }),
    371 /* 3 */
    372 /***/ (function(module, exports) {
    373 
     251},{}],3:[function(require,module,exports){
    374252/**
    375253 * wp.media.controller.VideoDetails
     
    404282module.exports = VideoDetails;
    405283
    406 
    407 /***/ }),
    408 /* 4 */
    409 /***/ (function(module, exports) {
    410 
     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 */
     294var 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
     324module.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 */
     338var MediaDetails = wp.media.view.MediaDetails,
     339    AudioDetails;
     340
     341AudioDetails = 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
     362module.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 */
     378var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
     379    MediaLibrary = wp.media.controller.MediaLibrary,
     380
     381    l10n = wp.media.view.l10n,
     382    AudioDetails;
     383
     384AudioDetails = 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
     438module.exports = AudioDetails;
     439
     440},{}],7:[function(require,module,exports){
    411441/**
    412442 * wp.media.view.MediaFrame.MediaDetails
     
    538568module.exports = MediaDetails;
    539569
    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  */
    558 var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
    559     MediaLibrary = wp.media.controller.MediaLibrary,
    560 
    561     l10n = wp.media.view.l10n,
    562     AudioDetails;
    563 
    564 AudioDetails = 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 
    618 module.exports = AudioDetails;
    619 
    620 
    621 /***/ }),
    622 /* 6 */
    623 /***/ (function(module, exports) {
    624 
     570},{}],8:[function(require,module,exports){
    625571/**
    626572 * wp.media.view.MediaFrame.VideoDetails
     
    757703module.exports = VideoDetails;
    758704
    759 
    760 /***/ }),
    761 /* 7 */
    762 /***/ (function(module, exports) {
    763 
     705},{}],9:[function(require,module,exports){
    764706/* global MediaElementPlayer */
    765707
     
    929871module.exports = MediaDetails;
    930872
    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  */
    947 var MediaDetails = wp.media.view.MediaDetails,
    948     AudioDetails;
    949 
    950 AudioDetails = 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 
    971 module.exports = AudioDetails;
    972 
    973 
    974 /***/ }),
    975 /* 9 */
    976 /***/ (function(module, exports) {
    977 
     873},{}],10:[function(require,module,exports){
    978874/**
    979875 * wp.media.view.VideoDetails
     
    1018914module.exports = VideoDetails;
    1019915
    1020 
    1021 /***/ })
    1022 /******/ ]);
     916},{}]},{},[1]);
  • branches/4.4/src/wp-includes/js/media-grid.js

    r46498 r50018  
    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 
    80 var media = wp.media;
    81 
    82 media.controller.EditAttachmentMetadata = __webpack_require__( 11 );
    83 media.view.MediaFrame.Manage = __webpack_require__( 12 );
    84 media.view.Attachment.Details.TwoColumn = __webpack_require__( 13 );
    85 media.view.MediaFrame.Manage.Router = __webpack_require__( 14 );
    86 media.view.EditImage.Details = __webpack_require__( 15 );
    87 media.view.MediaFrame.EditAttachments = __webpack_require__( 16 );
    88 media.view.SelectModeToggleButton = __webpack_require__( 17 );
    89 media.view.DeleteSelectedButton = __webpack_require__( 18 );
    90 media.view.DeleteSelectedPermanentlyButton = __webpack_require__( 19 );
    91 
    92 
    93 /***/ }),
    94 /* 11 */
    95 /***/ (function(module, exports) {
    96 
     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){
    972/**
    983 * wp.media.controller.EditAttachmentMetadata
     
    12227module.exports = EditAttachmentMetadata;
    12328
    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  */
    144 var MediaFrame = wp.media.view.MediaFrame,
    145     Library = wp.media.controller.Library,
    146 
    147     $ = Backbone.$,
    148     Manage;
    149 
    150 Manage = 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 
    394 module.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  */
    414 var Details = wp.media.view.Attachment.Details,
    415     TwoColumn;
    416 
    417 TwoColumn = 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 
    441 module.exports = TwoColumn;
    442 
    443 
    444 /***/ }),
    445 /* 14 */
    446 /***/ (function(module, exports) {
    447 
     29},{}],2:[function(require,module,exports){
     30var media = wp.media;
     31
     32media.controller.EditAttachmentMetadata = require( './controllers/edit-attachment-metadata.js' );
     33media.view.MediaFrame.Manage = require( './views/frame/manage.js' );
     34media.view.Attachment.Details.TwoColumn = require( './views/attachment/details-two-column.js' );
     35media.view.MediaFrame.Manage.Router = require( './routers/manage.js' );
     36media.view.EditImage.Details = require( './views/edit-image-details.js' );
     37media.view.MediaFrame.EditAttachments = require( './views/frame/edit-attachments.js' );
     38media.view.SelectModeToggleButton = require( './views/button/select-mode-toggle.js' );
     39media.view.DeleteSelectedButton = require( './views/button/delete-selected.js' );
     40media.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){
    44843/**
    44944 * wp.media.view.MediaFrame.Manage.Router
     
    49388module.exports = Router;
    49489
    495 
    496 /***/ }),
    497 /* 15 */
    498 /***/ (function(module, exports) {
    499 
     90},{}],4:[function(require,module,exports){
    50091/**
    501  * wp.media.view.EditImage.Details
     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.
    50296 *
    50397 * @class
    504  * @augments wp.media.view.EditImage
     98 * @augments wp.media.view.Attachment.Details
     99 * @augments wp.media.view.Attachment
    505100 * @augments wp.media.View
    506101 * @augments wp.Backbone.View
    507102 * @augments Backbone.View
    508103 */
    509 var View = wp.media.View,
    510     EditImage = wp.media.view.EditImage,
    511     Details;
    512 
    513 Details = 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 ) );
     104var Details = wp.media.view.Attachment.Details,
     105    TwoColumn;
     106
     107TwoColumn = 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        } );
    529128    }
    530129});
    531130
    532 module.exports = Details;
    533 
    534 
    535 /***/ }),
    536 /* 16 */
    537 /***/ (function(module, exports) {
    538 
     131module.exports = TwoColumn;
     132
     133},{}],5:[function(require,module,exports){
    539134/**
    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`.
     135 * wp.media.view.DeleteSelectedPermanentlyButton
     136 *
     137 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
    547138 *
    548139 * @class
    549  * @augments wp.media.view.Frame
     140 * @augments wp.media.view.DeleteSelectedButton
     141 * @augments wp.media.view.Button
    550142 * @augments wp.media.View
    551143 * @augments wp.Backbone.View
    552144 * @augments Backbone.View
    553  * @mixes wp.media.controller.StateMachine
    554145 */
    555 var Frame = wp.media.view.Frame,
    556     MediaFrame = wp.media.view.MediaFrame,
    557 
    558     $ = jQuery,
    559     EditAttachments;
    560 
    561 EditAttachments = 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 
     146var Button = wp.media.view.Button,
     147    DeleteSelected = wp.media.view.DeleteSelectedButton,
     148    DeleteSelectedPermanently;
     149
     150DeleteSelectedPermanently = DeleteSelected.extend({
    572151    initialize: function() {
    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( '' ) );
     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;
    776175    }
    777176});
    778177
    779 module.exports = EditAttachments;
    780 
    781 
    782 /***/ }),
    783 /* 17 */
    784 /***/ (function(module, exports) {
    785 
     178module.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 */
     192var Button = wp.media.view.Button,
     193    l10n = wp.media.view.l10n,
     194    DeleteSelected;
     195
     196DeleteSelected = 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
     231module.exports = DeleteSelected;
     232
     233},{}],7:[function(require,module,exports){
    786234/**
    787235 * wp.media.view.SelectModeToggleButton
     
    857305module.exports = SelectModeToggle;
    858306
    859 
    860 /***/ }),
    861 /* 18 */
    862 /***/ (function(module, exports) {
    863 
     307},{}],8:[function(require,module,exports){
    864308/**
    865  * wp.media.view.DeleteSelectedButton
    866  *
    867  * A button that handles bulk Delete/Trash logic
     309 * wp.media.view.EditImage.Details
    868310 *
    869311 * @class
    870  * @augments wp.media.view.Button
     312 * @augments wp.media.view.EditImage
    871313 * @augments wp.media.View
    872314 * @augments wp.Backbone.View
    873315 * @augments Backbone.View
    874316 */
    875 var Button = wp.media.view.Button,
    876     l10n = wp.media.view.l10n,
    877     DeleteSelected;
    878 
    879 DeleteSelected = 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;
     317var View = wp.media.View,
     318    EditImage = wp.media.view.EditImage,
     319    Details;
     320
     321Details = 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 ) );
    911337    }
    912338});
    913339
    914 module.exports = DeleteSelected;
    915 
    916 
    917 /***/ }),
    918 /* 19 */
    919 /***/ (function(module, exports) {
    920 
     340module.exports = Details;
     341
     342},{}],9:[function(require,module,exports){
    921343/**
    922  * wp.media.view.DeleteSelectedPermanentlyButton
    923  *
    924  * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
     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`.
    925351 *
    926352 * @class
    927  * @augments wp.media.view.DeleteSelectedButton
    928  * @augments wp.media.view.Button
     353 * @augments wp.media.view.Frame
    929354 * @augments wp.media.View
    930355 * @augments wp.Backbone.View
    931356 * @augments Backbone.View
     357 * @mixes wp.media.controller.StateMachine
    932358 */
    933 var Button = wp.media.view.Button,
    934     DeleteSelected = wp.media.view.DeleteSelectedButton,
    935     DeleteSelectedPermanently;
    936 
    937 DeleteSelectedPermanently = DeleteSelected.extend({
     359var Frame = wp.media.view.Frame,
     360    MediaFrame = wp.media.view.MediaFrame,
     361
     362    $ = jQuery,
     363    EditAttachments;
     364
     365EditAttachments = 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
    938376    initialize: function() {
    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;
     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( '' ) );
    962580    }
    963581});
    964582
    965 module.exports = DeleteSelectedPermanently;
    966 
    967 
    968 /***/ })
    969 /******/ ]);
     583module.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 */
     601var MediaFrame = wp.media.view.MediaFrame,
     602    Library = wp.media.controller.Library,
     603
     604    $ = Backbone.$,
     605    Manage;
     606
     607Manage = 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
     851module.exports = Manage;
     852
     853},{}]},{},[2]);
  • branches/4.4/src/wp-includes/js/media-views.js

    r46498 r50018  
    1 /******/ (function(modules) { // webpackBootstrap
    2 /******/    // The module cache
    3 /******/    var installedModules = {};
    4 /******/
    5 /******/    // The require function
    6 /******/    function __webpack_require__(moduleId) {
    7 /******/
    8 /******/        // Check if module is in cache
    9 /******/        if(installedModules[moduleId]) {
    10 /******/            return installedModules[moduleId].exports;
    11 /******/        }
    12 /******/        // Create a new module (and put it into the cache)
    13 /******/        var module = installedModules[moduleId] = {
    14 /******/            i: moduleId,
    15 /******/            l: false,
    16 /******/            exports: {}
    17 /******/        };
    18 /******/
    19 /******/        // Execute the module function
    20 /******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    21 /******/
    22 /******/        // Flag the module as loaded
    23 /******/        module.l = true;
    24 /******/
    25 /******/        // Return the exports of the module
    26 /******/        return module.exports;
    27 /******/    }
    28 /******/
    29 /******/
    30 /******/    // expose the modules object (__webpack_modules__)
    31 /******/    __webpack_require__.m = modules;
    32 /******/
    33 /******/    // expose the module cache
    34 /******/    __webpack_require__.c = installedModules;
    35 /******/
    36 /******/    // define getter function for harmony exports
    37 /******/    __webpack_require__.d = function(exports, name, getter) {
    38 /******/        if(!__webpack_require__.o(exports, name)) {
    39 /******/            Object.defineProperty(exports, name, {
    40 /******/                configurable: false,
    41 /******/                enumerable: true,
    42 /******/                get: getter
    43 /******/            });
    44 /******/        }
    45 /******/    };
    46 /******/
    47 /******/    // getDefaultExport function for compatibility with non-harmony modules
    48 /******/    __webpack_require__.n = function(module) {
    49 /******/        var getter = module && module.__esModule ?
    50 /******/            function getDefault() { return module['default']; } :
    51 /******/            function getModuleExports() { return module; };
    52 /******/        __webpack_require__.d(getter, 'a', getter);
    53 /******/        return getter;
    54 /******/    };
    55 /******/
    56 /******/    // Object.prototype.hasOwnProperty.call
    57 /******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
    58 /******/
    59 /******/    // __webpack_public_path__
    60 /******/    __webpack_require__.p = "";
    61 /******/
    62 /******/    // Load entry module and return exports
    63 /******/    return __webpack_require__(__webpack_require__.s = 26);
    64 /******/ })
    65 /************************************************************************/
    66 /******/ (Array(26).concat([
    67 /* 26 */
    68 /***/ (function(module, exports, __webpack_require__) {
    69 
    70 var media = wp.media,
     1(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     2/**
     3 * wp.media.controller.CollectionAdd
     4 *
     5 * A state for adding attachments to a collection (e.g. video playlist).
     6 *
     7 * @class
     8 * @augments wp.media.controller.Library
     9 * @augments wp.media.controller.State
     10 * @augments Backbone.Model
     11 *
     12 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     13 * @param {string}                     [attributes.id=library]      Unique identifier.
     14 * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
     15 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     16 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     17 *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
     18 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     19 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     20 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     21 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     22 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     23 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     24 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     25 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     26 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     27 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     28 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     29 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     30 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     31 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     32 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     33 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     34 */
     35var Selection = wp.media.model.Selection,
     36    Library = wp.media.controller.Library,
     37    CollectionAdd;
     38
     39CollectionAdd = Library.extend({
     40    defaults: _.defaults( {
     41        // Selection defaults. @see media.model.Selection
     42        multiple:      'add',
     43        // Attachments browser defaults. @see media.view.AttachmentsBrowser
     44        filterable:    'uploaded',
     45
     46        priority:      100,
     47        syncSelection: false
     48    }, Library.prototype.defaults ),
     49
     50    /**
     51     * @since 3.9.0
     52     */
     53    initialize: function() {
     54        var collectionType = this.get('collectionType');
     55
     56        if ( 'video' === this.get( 'type' ) ) {
     57            collectionType = 'video-' + collectionType;
     58        }
     59
     60        this.set( 'id', collectionType + '-library' );
     61        this.set( 'toolbar', collectionType + '-add' );
     62        this.set( 'menu', collectionType );
     63
     64        // If we haven't been provided a `library`, create a `Selection`.
     65        if ( ! this.get('library') ) {
     66            this.set( 'library', wp.media.query({ type: this.get('type') }) );
     67        }
     68        Library.prototype.initialize.apply( this, arguments );
     69    },
     70
     71    /**
     72     * @since 3.9.0
     73     */
     74    activate: function() {
     75        var library = this.get('library'),
     76            editLibrary = this.get('editLibrary'),
     77            edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
     78
     79        if ( editLibrary && editLibrary !== edit ) {
     80            library.unobserve( editLibrary );
     81        }
     82
     83        // Accepts attachments that exist in the original library and
     84        // that do not exist in gallery's library.
     85        library.validator = function( attachment ) {
     86            return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     87        };
     88
     89        // Reset the library to ensure that all attachments are re-added
     90        // to the collection. Do so silently, as calling `observe` will
     91        // trigger the `reset` event.
     92        library.reset( library.mirroring.models, { silent: true });
     93        library.observe( edit );
     94        this.set('editLibrary', edit);
     95
     96        Library.prototype.activate.apply( this, arguments );
     97    }
     98});
     99
     100module.exports = CollectionAdd;
     101
     102},{}],2:[function(require,module,exports){
     103/**
     104 * wp.media.controller.CollectionEdit
     105 *
     106 * A state for editing a collection, which is used by audio and video playlists,
     107 * and can be used for other collections.
     108 *
     109 * @class
     110 * @augments wp.media.controller.Library
     111 * @augments wp.media.controller.State
     112 * @augments Backbone.Model
     113 *
     114 * @param {object}                     [attributes]                      The attributes hash passed to the state.
     115 * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
     116 * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
     117 *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
     118 * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
     119 * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
     120 * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
     121 * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
     122 * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     123 * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
     124 * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
     125 * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
     126 * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
     127 * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
     128 * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
     129 * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
     130 * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
     131 *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
     132 * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
     133 * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
     134 *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     135 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     136 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     137 */
     138var Library = wp.media.controller.Library,
     139    l10n = wp.media.view.l10n,
    71140    $ = jQuery,
    72     l10n;
    73 
    74 media.isTouchDevice = ( 'ontouchend' in document );
    75 
    76 // Link any localized strings.
    77 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
    78 
    79 // Link any settings.
    80 media.view.settings = l10n.settings || {};
    81 delete l10n.settings;
    82 
    83 // Copy the `post` setting over to the model settings.
    84 media.model.settings.post = media.view.settings.post;
    85 
    86 // Check if the browser supports CSS 3.0 transitions
    87 $.support.transition = (function(){
    88     var style = document.documentElement.style,
    89         transitions = {
    90             WebkitTransition: 'webkitTransitionEnd',
    91             MozTransition:    'transitionend',
    92             OTransition:      'oTransitionEnd otransitionend',
    93             transition:       'transitionend'
    94         }, transition;
    95 
    96     transition = _.find( _.keys( transitions ), function( transition ) {
    97         return ! _.isUndefined( style[ transition ] );
    98     });
    99 
    100     return transition && {
    101         end: transitions[ transition ]
    102     };
    103 }());
    104 
     141    CollectionEdit;
     142
     143CollectionEdit = Library.extend({
     144    defaults: {
     145        multiple:         false,
     146        sortable:         true,
     147        date:             false,
     148        searchable:       false,
     149        content:          'browse',
     150        describe:         true,
     151        dragInfo:         true,
     152        idealColumnWidth: 170,
     153        editing:          false,
     154        priority:         60,
     155        SettingsView:     false,
     156        syncSelection:    false
     157    },
     158
     159    /**
     160     * @since 3.9.0
     161     */
     162    initialize: function() {
     163        var collectionType = this.get('collectionType');
     164
     165        if ( 'video' === this.get( 'type' ) ) {
     166            collectionType = 'video-' + collectionType;
     167        }
     168
     169        this.set( 'id', collectionType + '-edit' );
     170        this.set( 'toolbar', collectionType + '-edit' );
     171
     172        // If we haven't been provided a `library`, create a `Selection`.
     173        if ( ! this.get('library') ) {
     174            this.set( 'library', new wp.media.model.Selection() );
     175        }
     176        // The single `Attachment` view to be used in the `Attachments` view.
     177        if ( ! this.get('AttachmentView') ) {
     178            this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     179        }
     180        Library.prototype.initialize.apply( this, arguments );
     181    },
     182
     183    /**
     184     * @since 3.9.0
     185     */
     186    activate: function() {
     187        var library = this.get('library');
     188
     189        // Limit the library to images only.
     190        library.props.set( 'type', this.get( 'type' ) );
     191
     192        // Watch for uploaded attachments.
     193        this.get('library').observe( wp.Uploader.queue );
     194
     195        this.frame.on( 'content:render:browse', this.renderSettings, this );
     196
     197        Library.prototype.activate.apply( this, arguments );
     198    },
     199
     200    /**
     201     * @since 3.9.0
     202     */
     203    deactivate: function() {
     204        // Stop watching for uploaded attachments.
     205        this.get('library').unobserve( wp.Uploader.queue );
     206
     207        this.frame.off( 'content:render:browse', this.renderSettings, this );
     208
     209        Library.prototype.deactivate.apply( this, arguments );
     210    },
     211
     212    /**
     213     * Render the collection embed settings view in the browser sidebar.
     214     *
     215     * @todo This is against the pattern elsewhere in media. Typically the frame
     216     *       is responsible for adding region mode callbacks. Explain.
     217     *
     218     * @since 3.9.0
     219     *
     220     * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
     221     */
     222    renderSettings: function( attachmentsBrowserView ) {
     223        var library = this.get('library'),
     224            collectionType = this.get('collectionType'),
     225            dragInfoText = this.get('dragInfoText'),
     226            SettingsView = this.get('SettingsView'),
     227            obj = {};
     228
     229        if ( ! library || ! attachmentsBrowserView ) {
     230            return;
     231        }
     232
     233        library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
     234
     235        obj[ collectionType ] = new SettingsView({
     236            controller: this,
     237            model:      library[ collectionType ],
     238            priority:   40
     239        });
     240
     241        attachmentsBrowserView.sidebar.set( obj );
     242
     243        if ( dragInfoText ) {
     244            attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
     245                el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
     246                priority: -40
     247            }) );
     248        }
     249
     250        // Add the 'Reverse order' button to the toolbar.
     251        attachmentsBrowserView.toolbar.set( 'reverse', {
     252            text:     l10n.reverseOrder,
     253            priority: 80,
     254
     255            click: function() {
     256                library.reset( library.toArray().reverse() );
     257            }
     258        });
     259    }
     260});
     261
     262module.exports = CollectionEdit;
     263
     264},{}],3:[function(require,module,exports){
    105265/**
    106  * A shared event bus used to provide events into
    107  * the media workflows that 3rd-party devs can use to hook
    108  * in.
     266 * wp.media.controller.Cropper
     267 *
     268 * A state for cropping an image.
     269 *
     270 * @class
     271 * @augments wp.media.controller.State
     272 * @augments Backbone.Model
    109273 */
    110 media.events = _.extend( {}, Backbone.Events );
    111 
     274var l10n = wp.media.view.l10n,
     275    Cropper;
     276
     277Cropper = wp.media.controller.State.extend({
     278    defaults: {
     279        id:          'cropper',
     280        title:       l10n.cropImage,
     281        // Region mode defaults.
     282        toolbar:     'crop',
     283        content:     'crop',
     284        router:      false,
     285
     286        canSkipCrop: false
     287    },
     288
     289    activate: function() {
     290        this.frame.on( 'content:create:crop', this.createCropContent, this );
     291        this.frame.on( 'close', this.removeCropper, this );
     292        this.set('selection', new Backbone.Collection(this.frame._selection.single));
     293    },
     294
     295    deactivate: function() {
     296        this.frame.toolbar.mode('browse');
     297    },
     298
     299    createCropContent: function() {
     300        this.cropperView = new wp.media.view.Cropper({
     301            controller: this,
     302            attachment: this.get('selection').first()
     303        });
     304        this.cropperView.on('image-loaded', this.createCropToolbar, this);
     305        this.frame.content.set(this.cropperView);
     306
     307    },
     308    removeCropper: function() {
     309        this.imgSelect.cancelSelection();
     310        this.imgSelect.setOptions({remove: true});
     311        this.imgSelect.update();
     312        this.cropperView.remove();
     313    },
     314    createCropToolbar: function() {
     315        var canSkipCrop, toolbarOptions;
     316
     317        canSkipCrop = this.get('canSkipCrop') || false;
     318
     319        toolbarOptions = {
     320            controller: this.frame,
     321            items: {
     322                insert: {
     323                    style:    'primary',
     324                    text:     l10n.cropImage,
     325                    priority: 80,
     326                    requires: { library: false, selection: false },
     327
     328                    click: function() {
     329                        var controller = this.controller,
     330                            selection;
     331
     332                        selection = controller.state().get('selection').first();
     333                        selection.set({cropDetails: controller.state().imgSelect.getSelection()});
     334
     335                        this.$el.text(l10n.cropping);
     336                        this.$el.attr('disabled', true);
     337
     338                        controller.state().doCrop( selection ).done( function( croppedImage ) {
     339                            controller.trigger('cropped', croppedImage );
     340                            controller.close();
     341                        }).fail( function() {
     342                            controller.trigger('content:error:crop');
     343                        });
     344                    }
     345                }
     346            }
     347        };
     348
     349        if ( canSkipCrop ) {
     350            _.extend( toolbarOptions.items, {
     351                skip: {
     352                    style:      'secondary',
     353                    text:       l10n.skipCropping,
     354                    priority:   70,
     355                    requires:   { library: false, selection: false },
     356                    click:      function() {
     357                        var selection = this.controller.state().get('selection').first();
     358                        this.controller.state().cropperView.remove();
     359                        this.controller.trigger('skippedcrop', selection);
     360                        this.controller.close();
     361                    }
     362                }
     363            });
     364        }
     365
     366        this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
     367    },
     368
     369    doCrop: function( attachment ) {
     370        return wp.ajax.post( 'custom-header-crop', {
     371            nonce: attachment.get('nonces').edit,
     372            id: attachment.get('id'),
     373            cropDetails: attachment.get('cropDetails')
     374        } );
     375    }
     376});
     377
     378module.exports = Cropper;
     379
     380},{}],4:[function(require,module,exports){
    112381/**
    113  * Makes it easier to bind events using transitions.
    114  *
    115  * @param {string} selector
    116  * @param {Number} sensitivity
    117  * @returns {Promise}
     382 * wp.media.controller.CustomizeImageCropper
     383 *
     384 * A state for cropping an image.
     385 *
     386 * @class
     387 * @augments wp.media.controller.Cropper
     388 * @augments wp.media.controller.State
     389 * @augments Backbone.Model
    118390 */
    119 media.transition = function( selector, sensitivity ) {
    120     var deferred = $.Deferred();
    121 
    122     sensitivity = sensitivity || 2000;
    123 
    124     if ( $.support.transition ) {
    125         if ( ! (selector instanceof $) ) {
    126             selector = $( selector );
    127         }
    128 
    129         // Resolve the deferred when the first element finishes animating.
    130         selector.first().one( $.support.transition.end, deferred.resolve );
    131 
    132         // Just in case the event doesn't trigger, fire a callback.
    133         _.delay( deferred.resolve, sensitivity );
    134 
    135     // Otherwise, execute on the spot.
    136     } else {
    137         deferred.resolve();
     391var Controller = wp.media.controller,
     392    CustomizeImageCropper;
     393
     394CustomizeImageCropper = Controller.Cropper.extend({
     395    doCrop: function( attachment ) {
     396        var cropDetails = attachment.get( 'cropDetails' ),
     397            control = this.get( 'control' );
     398
     399        cropDetails.dst_width  = control.params.width;
     400        cropDetails.dst_height = control.params.height;
     401
     402        return wp.ajax.post( 'crop-image', {
     403            wp_customize: 'on',
     404            nonce: attachment.get( 'nonces' ).edit,
     405            id: attachment.get( 'id' ),
     406            context: control.id,
     407            cropDetails: cropDetails
     408        } );
    138409    }
    139 
    140     return deferred.promise();
    141 };
    142 
    143 media.controller.Region = __webpack_require__( 27 );
    144 media.controller.StateMachine = __webpack_require__( 28 );
    145 media.controller.State = __webpack_require__( 29 );
    146 
    147 media.selectionSync = __webpack_require__( 30 );
    148 media.controller.Library = __webpack_require__( 31 );
    149 media.controller.ImageDetails = __webpack_require__( 32 );
    150 media.controller.GalleryEdit = __webpack_require__( 33 );
    151 media.controller.GalleryAdd = __webpack_require__( 34 );
    152 media.controller.CollectionEdit = __webpack_require__( 35 );
    153 media.controller.CollectionAdd = __webpack_require__( 36 );
    154 media.controller.FeaturedImage = __webpack_require__( 37 );
    155 media.controller.ReplaceImage = __webpack_require__( 38 );
    156 media.controller.EditImage = __webpack_require__( 39 );
    157 media.controller.MediaLibrary = __webpack_require__( 40 );
    158 media.controller.Embed = __webpack_require__( 41 );
    159 media.controller.Cropper = __webpack_require__( 42 );
    160 media.controller.CustomizeImageCropper = __webpack_require__( 43 );
    161 media.controller.SiteIconCropper = __webpack_require__( 44 );
    162 
    163 media.View = __webpack_require__( 45 );
    164 media.view.Frame = __webpack_require__( 46 );
    165 media.view.MediaFrame = __webpack_require__( 47 );
    166 media.view.MediaFrame.Select = __webpack_require__( 48 );
    167 media.view.MediaFrame.Post = __webpack_require__( 49 );
    168 media.view.MediaFrame.ImageDetails = __webpack_require__( 50 );
    169 media.view.Modal = __webpack_require__( 51 );
    170 media.view.FocusManager = __webpack_require__( 52 );
    171 media.view.UploaderWindow = __webpack_require__( 53 );
    172 media.view.EditorUploader = __webpack_require__( 54 );
    173 media.view.UploaderInline = __webpack_require__( 55 );
    174 media.view.UploaderStatus = __webpack_require__( 56 );
    175 media.view.UploaderStatusError = __webpack_require__( 57 );
    176 media.view.Toolbar = __webpack_require__( 58 );
    177 media.view.Toolbar.Select = __webpack_require__( 59 );
    178 media.view.Toolbar.Embed = __webpack_require__( 60 );
    179 media.view.Button = __webpack_require__( 61 );
    180 media.view.ButtonGroup = __webpack_require__( 62 );
    181 media.view.PriorityList = __webpack_require__( 63 );
    182 media.view.MenuItem = __webpack_require__( 64 );
    183 media.view.Menu = __webpack_require__( 65 );
    184 media.view.RouterItem = __webpack_require__( 66 );
    185 media.view.Router = __webpack_require__( 67 );
    186 media.view.Sidebar = __webpack_require__( 68 );
    187 media.view.Attachment = __webpack_require__( 69 );
    188 media.view.Attachment.Library = __webpack_require__( 70 );
    189 media.view.Attachment.EditLibrary = __webpack_require__( 71 );
    190 media.view.Attachments = __webpack_require__( 72 );
    191 media.view.Search = __webpack_require__( 73 );
    192 media.view.AttachmentFilters = __webpack_require__( 74 );
    193 media.view.DateFilter = __webpack_require__( 75 );
    194 media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 );
    195 media.view.AttachmentFilters.All = __webpack_require__( 77 );
    196 media.view.AttachmentsBrowser = __webpack_require__( 78 );
    197 media.view.Selection = __webpack_require__( 79 );
    198 media.view.Attachment.Selection = __webpack_require__( 80 );
    199 media.view.Attachments.Selection = __webpack_require__( 81 );
    200 media.view.Attachment.EditSelection = __webpack_require__( 82 );
    201 media.view.Settings = __webpack_require__( 83 );
    202 media.view.Settings.AttachmentDisplay = __webpack_require__( 84 );
    203 media.view.Settings.Gallery = __webpack_require__( 85 );
    204 media.view.Settings.Playlist = __webpack_require__( 86 );
    205 media.view.Attachment.Details = __webpack_require__( 87 );
    206 media.view.AttachmentCompat = __webpack_require__( 88 );
    207 media.view.Iframe = __webpack_require__( 89 );
    208 media.view.Embed = __webpack_require__( 90 );
    209 media.view.Label = __webpack_require__( 91 );
    210 media.view.EmbedUrl = __webpack_require__( 92 );
    211 media.view.EmbedLink = __webpack_require__( 93 );
    212 media.view.EmbedImage = __webpack_require__( 94 );
    213 media.view.ImageDetails = __webpack_require__( 95 );
    214 media.view.Cropper = __webpack_require__( 96 );
    215 media.view.SiteIconCropper = __webpack_require__( 97 );
    216 media.view.SiteIconPreview = __webpack_require__( 98 );
    217 media.view.EditImage = __webpack_require__( 99 );
    218 media.view.Spinner = __webpack_require__( 100 );
    219 
    220 
    221 /***/ }),
    222 /* 27 */
    223 /***/ (function(module, exports) {
    224 
     410});
     411
     412module.exports = CustomizeImageCropper;
     413
     414},{}],5:[function(require,module,exports){
    225415/**
    226  * wp.media.controller.Region
    227  *
    228  * A region is a persistent application layout area.
    229  *
    230  * A region assumes one mode at any time, and can be switched to another.
    231  *
    232  * When mode changes, events are triggered on the region's parent view.
    233  * The parent view will listen to specific events and fill the region with an
    234  * appropriate view depending on mode. For example, a frame listens for the
    235  * 'browse' mode t be activated on the 'content' view and then fills the region
    236  * with an AttachmentsBrowser view.
     416 * wp.media.controller.EditImage
     417 *
     418 * A state for editing (cropping, etc.) an image.
    237419 *
    238420 * @class
    239  *
    240  * @param {object}        options          Options hash for the region.
    241  * @param {string}        options.id       Unique identifier for the region.
    242  * @param {Backbone.View} options.view     A parent view the region exists within.
    243  * @param {string}        options.selector jQuery selector for the region within the parent view.
     421 * @augments wp.media.controller.State
     422 * @augments Backbone.Model
     423 *
     424 * @param {object}                    attributes                      The attributes hash passed to the state.
     425 * @param {wp.media.model.Attachment} attributes.model                The attachment.
     426 * @param {string}                    [attributes.id=edit-image]      Unique identifier.
     427 * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
     428 * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
     429 * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
     430 * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
     431 * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
    244432 */
    245 var Region = function( options ) {
    246     _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
    247 };
    248 
    249 // Use Backbone's self-propagating `extend` inheritance method.
    250 Region.extend = Backbone.Model.extend;
    251 
    252 _.extend( Region.prototype, {
    253     /**
    254      * Activate a mode.
     433var l10n = wp.media.view.l10n,
     434    EditImage;
     435
     436EditImage = wp.media.controller.State.extend({
     437    defaults: {
     438        id:      'edit-image',
     439        title:   l10n.editImage,
     440        menu:    false,
     441        toolbar: 'edit-image',
     442        content: 'edit-image',
     443        url:     ''
     444    },
     445
     446    /**
     447     * @since 3.9.0
     448     */
     449    activate: function() {
     450        this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
     451    },
     452
     453    /**
     454     * @since 3.9.0
     455     */
     456    deactivate: function() {
     457        this.stopListening( this.frame );
     458    },
     459
     460    /**
     461     * @since 3.9.0
     462     */
     463    toolbar: function() {
     464        var frame = this.frame,
     465            lastState = frame.lastState(),
     466            previous = lastState && lastState.id;
     467
     468        frame.toolbar.set( new wp.media.view.Toolbar({
     469            controller: frame,
     470            items: {
     471                back: {
     472                    style: 'primary',
     473                    text:     l10n.back,
     474                    priority: 20,
     475                    click:    function() {
     476                        if ( previous ) {
     477                            frame.setState( previous );
     478                        } else {
     479                            frame.close();
     480                        }
     481                    }
     482                }
     483            }
     484        }) );
     485    }
     486});
     487
     488module.exports = EditImage;
     489
     490},{}],6:[function(require,module,exports){
     491/**
     492 * wp.media.controller.Embed
     493 *
     494 * A state for embedding media from a URL.
     495 *
     496 * @class
     497 * @augments wp.media.controller.State
     498 * @augments Backbone.Model
     499 *
     500 * @param {object} attributes                         The attributes hash passed to the state.
     501 * @param {string} [attributes.id=embed]              Unique identifier.
     502 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
     503 * @param {string} [attributes.content=embed]         Initial mode for the content region.
     504 * @param {string} [attributes.menu=default]          Initial mode for the menu region.
     505 * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
     506 * @param {string} [attributes.menu=false]            Initial mode for the menu region.
     507 * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
     508 * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
     509 * @param {string} [attributes.url]                   The embed URL.
     510 * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
     511 */
     512var l10n = wp.media.view.l10n,
     513    $ = Backbone.$,
     514    Embed;
     515
     516Embed = wp.media.controller.State.extend({
     517    defaults: {
     518        id:       'embed',
     519        title:    l10n.insertFromUrlTitle,
     520        content:  'embed',
     521        menu:     'default',
     522        toolbar:  'main-embed',
     523        priority: 120,
     524        type:     'link',
     525        url:      '',
     526        metadata: {}
     527    },
     528
     529    // The amount of time used when debouncing the scan.
     530    sensitivity: 400,
     531
     532    initialize: function(options) {
     533        this.metadata = options.metadata;
     534        this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
     535        this.props = new Backbone.Model( this.metadata || { url: '' });
     536        this.props.on( 'change:url', this.debouncedScan, this );
     537        this.props.on( 'change:url', this.refresh, this );
     538        this.on( 'scan', this.scanImage, this );
     539    },
     540
     541    /**
     542     * Trigger a scan of the embedded URL's content for metadata required to embed.
    255543     *
     544     * @fires wp.media.controller.Embed#scan
     545     */
     546    scan: function() {
     547        var scanners,
     548            embed = this,
     549            attributes = {
     550                type: 'link',
     551                scanners: []
     552            };
     553
     554        // Scan is triggered with the list of `attributes` to set on the
     555        // state, useful for the 'type' attribute and 'scanners' attribute,
     556        // an array of promise objects for asynchronous scan operations.
     557        if ( this.props.get('url') ) {
     558            this.trigger( 'scan', attributes );
     559        }
     560
     561        if ( attributes.scanners.length ) {
     562            scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
     563            scanners.always( function() {
     564                if ( embed.get('scanners') === scanners ) {
     565                    embed.set( 'loading', false );
     566                }
     567            });
     568        } else {
     569            attributes.scanners = null;
     570        }
     571
     572        attributes.loading = !! attributes.scanners;
     573        this.set( attributes );
     574    },
     575    /**
     576     * Try scanning the embed as an image to discover its dimensions.
     577     *
     578     * @param {Object} attributes
     579     */
     580    scanImage: function( attributes ) {
     581        var frame = this.frame,
     582            state = this,
     583            url = this.props.get('url'),
     584            image = new Image(),
     585            deferred = $.Deferred();
     586
     587        attributes.scanners.push( deferred.promise() );
     588
     589        // Try to load the image and find its width/height.
     590        image.onload = function() {
     591            deferred.resolve();
     592
     593            if ( state !== frame.state() || url !== state.props.get('url') ) {
     594                return;
     595            }
     596
     597            state.set({
     598                type: 'image'
     599            });
     600
     601            state.props.set({
     602                width:  image.width,
     603                height: image.height
     604            });
     605        };
     606
     607        image.onerror = deferred.reject;
     608        image.src = url;
     609    },
     610
     611    refresh: function() {
     612        this.frame.toolbar.get().refresh();
     613    },
     614
     615    reset: function() {
     616        this.props.clear().set({ url: '' });
     617
     618        if ( this.active ) {
     619            this.refresh();
     620        }
     621    }
     622});
     623
     624module.exports = Embed;
     625
     626},{}],7:[function(require,module,exports){
     627/**
     628 * wp.media.controller.FeaturedImage
     629 *
     630 * A state for selecting a featured image for a post.
     631 *
     632 * @class
     633 * @augments wp.media.controller.Library
     634 * @augments wp.media.controller.State
     635 * @augments Backbone.Model
     636 *
     637 * @param {object}                     [attributes]                          The attributes hash passed to the state.
     638 * @param {string}                     [attributes.id=featured-image]        Unique identifier.
     639 * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
     640 * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
     641 *                                                                           If one is not supplied, a collection of all images will be created.
     642 * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
     643 * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
     644 *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
     645 * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
     646 * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
     647 * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
     648 * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
     649 * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
     650 * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
     651 *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
     652 * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     653 * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
     654 * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     655 * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
     656 * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
     657 */
     658var Attachment = wp.media.model.Attachment,
     659    Library = wp.media.controller.Library,
     660    l10n = wp.media.view.l10n,
     661    FeaturedImage;
     662
     663FeaturedImage = Library.extend({
     664    defaults: _.defaults({
     665        id:            'featured-image',
     666        title:         l10n.setFeaturedImageTitle,
     667        multiple:      false,
     668        filterable:    'uploaded',
     669        toolbar:       'featured-image',
     670        priority:      60,
     671        syncSelection: true
     672    }, Library.prototype.defaults ),
     673
     674    /**
     675     * @since 3.5.0
     676     */
     677    initialize: function() {
     678        var library, comparator;
     679
     680        // If we haven't been provided a `library`, create a `Selection`.
     681        if ( ! this.get('library') ) {
     682            this.set( 'library', wp.media.query({ type: 'image' }) );
     683        }
     684
     685        Library.prototype.initialize.apply( this, arguments );
     686
     687        library    = this.get('library');
     688        comparator = library.comparator;
     689
     690        // Overload the library's comparator to push items that are not in
     691        // the mirrored query to the front of the aggregate collection.
     692        library.comparator = function( a, b ) {
     693            var aInQuery = !! this.mirroring.get( a.cid ),
     694                bInQuery = !! this.mirroring.get( b.cid );
     695
     696            if ( ! aInQuery && bInQuery ) {
     697                return -1;
     698            } else if ( aInQuery && ! bInQuery ) {
     699                return 1;
     700            } else {
     701                return comparator.apply( this, arguments );
     702            }
     703        };
     704
     705        // Add all items in the selection to the library, so any featured
     706        // images that are not initially loaded still appear.
     707        library.observe( this.get('selection') );
     708    },
     709
     710    /**
     711     * @since 3.5.0
     712     */
     713    activate: function() {
     714        this.updateSelection();
     715        this.frame.on( 'open', this.updateSelection, this );
     716
     717        Library.prototype.activate.apply( this, arguments );
     718    },
     719
     720    /**
     721     * @since 3.5.0
     722     */
     723    deactivate: function() {
     724        this.frame.off( 'open', this.updateSelection, this );
     725
     726        Library.prototype.deactivate.apply( this, arguments );
     727    },
     728
     729    /**
     730     * @since 3.5.0
     731     */
     732    updateSelection: function() {
     733        var selection = this.get('selection'),
     734            id = wp.media.view.settings.post.featuredImageId,
     735            attachment;
     736
     737        if ( '' !== id && -1 !== id ) {
     738            attachment = Attachment.get( id );
     739            attachment.fetch();
     740        }
     741
     742        selection.reset( attachment ? [ attachment ] : [] );
     743    }
     744});
     745
     746module.exports = FeaturedImage;
     747
     748},{}],8:[function(require,module,exports){
     749/**
     750 * wp.media.controller.GalleryAdd
     751 *
     752 * A state for selecting more images to add to a gallery.
     753 *
     754 * @class
     755 * @augments wp.media.controller.Library
     756 * @augments wp.media.controller.State
     757 * @augments Backbone.Model
     758 *
     759 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     760 * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
     761 * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
     762 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     763 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     764 *                                                                          If one is not supplied, a collection of all images will be created.
     765 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     766 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     767 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     768 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     769 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     770 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     771 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     772 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     773 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     774 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     775 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     776 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     777 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     778 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     779 */
     780var Selection = wp.media.model.Selection,
     781    Library = wp.media.controller.Library,
     782    l10n = wp.media.view.l10n,
     783    GalleryAdd;
     784
     785GalleryAdd = Library.extend({
     786    defaults: _.defaults({
     787        id:            'gallery-library',
     788        title:         l10n.addToGalleryTitle,
     789        multiple:      'add',
     790        filterable:    'uploaded',
     791        menu:          'gallery',
     792        toolbar:       'gallery-add',
     793        priority:      100,
     794        syncSelection: false
     795    }, Library.prototype.defaults ),
     796
     797    /**
     798     * @since 3.5.0
     799     */
     800    initialize: function() {
     801        // If a library wasn't supplied, create a library of images.
     802        if ( ! this.get('library') ) {
     803            this.set( 'library', wp.media.query({ type: 'image' }) );
     804        }
     805
     806        Library.prototype.initialize.apply( this, arguments );
     807    },
     808
     809    /**
     810     * @since 3.5.0
     811     */
     812    activate: function() {
     813        var library = this.get('library'),
     814            edit    = this.frame.state('gallery-edit').get('library');
     815
     816        if ( this.editLibrary && this.editLibrary !== edit ) {
     817            library.unobserve( this.editLibrary );
     818        }
     819
     820        // Accepts attachments that exist in the original library and
     821        // that do not exist in gallery's library.
     822        library.validator = function( attachment ) {
     823            return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     824        };
     825
     826        // Reset the library to ensure that all attachments are re-added
     827        // to the collection. Do so silently, as calling `observe` will
     828        // trigger the `reset` event.
     829        library.reset( library.mirroring.models, { silent: true });
     830        library.observe( edit );
     831        this.editLibrary = edit;
     832
     833        Library.prototype.activate.apply( this, arguments );
     834    }
     835});
     836
     837module.exports = GalleryAdd;
     838
     839},{}],9:[function(require,module,exports){
     840/**
     841 * wp.media.controller.GalleryEdit
     842 *
     843 * A state for editing a gallery's images and settings.
     844 *
     845 * @class
     846 * @augments wp.media.controller.Library
     847 * @augments wp.media.controller.State
     848 * @augments Backbone.Model
     849 *
     850 * @param {object}                     [attributes]                       The attributes hash passed to the state.
     851 * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
     852 * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
     853 * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
     854 *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
     855 * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
     856 * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
     857 * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     858 * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
     859 * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
     860 * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
     861 * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     862 * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
     863 * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
     864 * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
     865 * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
     866 * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
     867 * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
     868 *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
     869 * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
     870 *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     871 */
     872var Library = wp.media.controller.Library,
     873    l10n = wp.media.view.l10n,
     874    GalleryEdit;
     875
     876GalleryEdit = Library.extend({
     877    defaults: {
     878        id:               'gallery-edit',
     879        title:            l10n.editGalleryTitle,
     880        multiple:         false,
     881        searchable:       false,
     882        sortable:         true,
     883        date:             false,
     884        display:          false,
     885        content:          'browse',
     886        toolbar:          'gallery-edit',
     887        describe:         true,
     888        displaySettings:  true,
     889        dragInfo:         true,
     890        idealColumnWidth: 170,
     891        editing:          false,
     892        priority:         60,
     893        syncSelection:    false
     894    },
     895
     896    /**
     897     * @since 3.5.0
     898     */
     899    initialize: function() {
     900        // If we haven't been provided a `library`, create a `Selection`.
     901        if ( ! this.get('library') ) {
     902            this.set( 'library', new wp.media.model.Selection() );
     903        }
     904
     905        // The single `Attachment` view to be used in the `Attachments` view.
     906        if ( ! this.get('AttachmentView') ) {
     907            this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     908        }
     909
     910        Library.prototype.initialize.apply( this, arguments );
     911    },
     912
     913    /**
     914     * @since 3.5.0
     915     */
     916    activate: function() {
     917        var library = this.get('library');
     918
     919        // Limit the library to images only.
     920        library.props.set( 'type', 'image' );
     921
     922        // Watch for uploaded attachments.
     923        this.get('library').observe( wp.Uploader.queue );
     924
     925        this.frame.on( 'content:render:browse', this.gallerySettings, this );
     926
     927        Library.prototype.activate.apply( this, arguments );
     928    },
     929
     930    /**
     931     * @since 3.5.0
     932     */
     933    deactivate: function() {
     934        // Stop watching for uploaded attachments.
     935        this.get('library').unobserve( wp.Uploader.queue );
     936
     937        this.frame.off( 'content:render:browse', this.gallerySettings, this );
     938
     939        Library.prototype.deactivate.apply( this, arguments );
     940    },
     941
     942    /**
    256943     * @since 3.5.0
    257944     *
    258      * @param {string} mode
    259      *
    260      * @fires this.view#{this.id}:activate:{this._mode}
    261      * @fires this.view#{this.id}:activate
    262      * @fires this.view#{this.id}:deactivate:{this._mode}
    263      * @fires this.view#{this.id}:deactivate
    264      *
    265      * @returns {wp.media.controller.Region} Returns itself to allow chaining.
    266      */
    267     mode: function( mode ) {
    268         if ( ! mode ) {
    269             return this._mode;
    270         }
    271         // Bail if we're trying to change to the current mode.
    272         if ( mode === this._mode ) {
    273             return this;
    274         }
    275 
    276         /**
    277          * Region mode deactivation event.
    278          *
    279          * @event this.view#{this.id}:deactivate:{this._mode}
    280          * @event this.view#{this.id}:deactivate
    281          */
    282         this.trigger('deactivate');
    283 
    284         this._mode = mode;
    285         this.render( mode );
    286 
    287         /**
    288          * Region mode activation event.
    289          *
    290          * @event this.view#{this.id}:activate:{this._mode}
    291          * @event this.view#{this.id}:activate
    292          */
    293         this.trigger('activate');
    294         return this;
    295     },
    296     /**
    297      * Render a mode.
    298      *
    299      * @since 3.5.0
    300      *
    301      * @param {string} mode
    302      *
    303      * @fires this.view#{this.id}:create:{this._mode}
    304      * @fires this.view#{this.id}:create
    305      * @fires this.view#{this.id}:render:{this._mode}
    306      * @fires this.view#{this.id}:render
    307      *
    308      * @returns {wp.media.controller.Region} Returns itself to allow chaining
    309      */
    310     render: function( mode ) {
    311         // If the mode isn't active, activate it.
    312         if ( mode && mode !== this._mode ) {
    313             return this.mode( mode );
    314         }
    315 
    316         var set = { view: null },
    317             view;
    318 
    319         /**
    320          * Create region view event.
    321          *
    322          * Region view creation takes place in an event callback on the frame.
    323          *
    324          * @event this.view#{this.id}:create:{this._mode}
    325          * @event this.view#{this.id}:create
    326          */
    327         this.trigger( 'create', set );
    328         view = set.view;
    329 
    330         /**
    331          * Render region view event.
    332          *
    333          * Region view creation takes place in an event callback on the frame.
    334          *
    335          * @event this.view#{this.id}:create:{this._mode}
    336          * @event this.view#{this.id}:create
    337          */
    338         this.trigger( 'render', view );
    339         if ( view ) {
    340             this.set( view );
    341         }
    342         return this;
    343     },
    344 
    345     /**
    346      * Get the region's view.
    347      *
    348      * @since 3.5.0
    349      *
    350      * @returns {wp.media.View}
    351      */
    352     get: function() {
    353         return this.view.views.first( this.selector );
    354     },
    355 
    356     /**
    357      * Set the region's view as a subview of the frame.
    358      *
    359      * @since 3.5.0
    360      *
    361      * @param {Array|Object} views
    362      * @param {Object} [options={}]
    363      * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
    364      */
    365     set: function( views, options ) {
    366         if ( options ) {
    367             options.add = false;
    368         }
    369         return this.view.views.set( this.selector, views, options );
    370     },
    371 
    372     /**
    373      * Trigger regional view events on the frame.
    374      *
    375      * @since 3.5.0
    376      *
    377      * @param {string} event
    378      * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
    379      */
    380     trigger: function( event ) {
    381         var base, args;
    382 
    383         if ( ! this._mode ) {
     945     * @param browser
     946     */
     947    gallerySettings: function( browser ) {
     948        if ( ! this.get('displaySettings') ) {
    384949            return;
    385950        }
    386951
    387         args = _.toArray( arguments );
    388         base = this.id + ':' + event;
    389 
    390         // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
    391         args[0] = base + ':' + this._mode;
    392         this.view.trigger.apply( this.view, args );
    393 
    394         // Trigger `{this.id}:{event}` event on the frame.
    395         args[0] = base;
    396         this.view.trigger.apply( this.view, args );
    397         return this;
     952        var library = this.get('library');
     953
     954        if ( ! library || ! browser ) {
     955            return;
     956        }
     957
     958        library.gallery = library.gallery || new Backbone.Model();
     959
     960        browser.sidebar.set({
     961            gallery: new wp.media.view.Settings.Gallery({
     962                controller: this,
     963                model:      library.gallery,
     964                priority:   40
     965            })
     966        });
     967
     968        browser.toolbar.set( 'reverse', {
     969            text:     l10n.reverseOrder,
     970            priority: 80,
     971
     972            click: function() {
     973                library.reset( library.toArray().reverse() );
     974            }
     975        });
    398976    }
    399977});
    400978
    401 module.exports = Region;
    402 
    403 
    404 /***/ }),
    405 /* 28 */
    406 /***/ (function(module, exports) {
    407 
     979module.exports = GalleryEdit;
     980
     981},{}],10:[function(require,module,exports){
    408982/**
    409  * wp.media.controller.StateMachine
    410  *
    411  * A state machine keeps track of state. It is in one state at a time,
    412  * and can change from one state to another.
    413  *
    414  * States are stored as models in a Backbone collection.
    415  *
    416  * @since 3.5.0
     983 * wp.media.controller.ImageDetails
     984 *
     985 * A state for editing the attachment display settings of an image that's been
     986 * inserted into the editor.
    417987 *
    418988 * @class
     989 * @augments wp.media.controller.State
    419990 * @augments Backbone.Model
    420  * @mixin
    421  * @mixes Backbone.Events
    422  *
    423  * @param {Array} states
     991 *
     992 * @param {object}                    [attributes]                       The attributes hash passed to the state.
     993 * @param {string}                    [attributes.id=image-details]      Unique identifier.
     994 * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
     995 * @param {wp.media.model.Attachment} attributes.image                   The image's model.
     996 * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
     997 * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
     998 * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
     999 * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
     1000 * @param {boolean}                   [attributes.editing=false]         Unused.
     1001 * @param {int}                       [attributes.priority=60]           Unused.
     1002 *
     1003 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
     1004 *       however this may not do anything.
    4241005 */
    425 var StateMachine = function( states ) {
    426     // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
    427     this.states = new Backbone.Collection( states );
    428 };
    429 
    430 // Use Backbone's self-propagating `extend` inheritance method.
    431 StateMachine.extend = Backbone.Model.extend;
    432 
    433 _.extend( StateMachine.prototype, Backbone.Events, {
    434     /**
    435      * Fetch a state.
     1006var State = wp.media.controller.State,
     1007    Library = wp.media.controller.Library,
     1008    l10n = wp.media.view.l10n,
     1009    ImageDetails;
     1010
     1011ImageDetails = State.extend({
     1012    defaults: _.defaults({
     1013        id:       'image-details',
     1014        title:    l10n.imageDetailsTitle,
     1015        content:  'image-details',
     1016        menu:     false,
     1017        router:   false,
     1018        toolbar:  'image-details',
     1019        editing:  false,
     1020        priority: 60
     1021    }, Library.prototype.defaults ),
     1022
     1023    /**
     1024     * @since 3.9.0
    4361025     *
    437      * If no `id` is provided, returns the active state.
    438      *
    439      * Implicitly creates states.
    440      *
    441      * Ensure that the `states` collection exists so the `StateMachine`
    442      *   can be used as a mixin.
    443      *
    444      * @since 3.5.0
    445      *
    446      * @param {string} id
    447      * @returns {wp.media.controller.State} Returns a State model
    448      *   from the StateMachine collection
    449      */
    450     state: function( id ) {
    451         this.states = this.states || new Backbone.Collection();
    452 
    453         // Default to the active state.
    454         id = id || this._state;
    455 
    456         if ( id && ! this.states.get( id ) ) {
    457             this.states.add({ id: id });
    458         }
    459         return this.states.get( id );
    460     },
    461 
    462     /**
    463      * Sets the active state.
    464      *
    465      * Bail if we're trying to select the current state, if we haven't
    466      * created the `states` collection, or are trying to select a state
    467      * that does not exist.
    468      *
    469      * @since 3.5.0
    470      *
    471      * @param {string} id
    472      *
    473      * @fires wp.media.controller.State#deactivate
    474      * @fires wp.media.controller.State#activate
    475      *
    476      * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
    477      */
    478     setState: function( id ) {
    479         var previous = this.state();
    480 
    481         if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
    482             return this;
    483         }
    484 
    485         if ( previous ) {
    486             previous.trigger('deactivate');
    487             this._lastState = previous.id;
    488         }
    489 
    490         this._state = id;
    491         this.state().trigger('activate');
    492 
    493         return this;
    494     },
    495 
    496     /**
    497      * Returns the previous active state.
    498      *
    499      * Call the `state()` method with no parameters to retrieve the current
    500      * active state.
    501      *
    502      * @since 3.5.0
    503      *
    504      * @returns {wp.media.controller.State} Returns a State model
    505      *    from the StateMachine collection
    506      */
    507     lastState: function() {
    508         if ( this._lastState ) {
    509             return this.state( this._lastState );
    510         }
     1026     * @param options Attributes
     1027     */
     1028    initialize: function( options ) {
     1029        this.image = options.image;
     1030        State.prototype.initialize.apply( this, arguments );
     1031    },
     1032
     1033    /**
     1034     * @since 3.9.0
     1035     */
     1036    activate: function() {
     1037        this.frame.modal.$el.addClass('image-details');
    5111038    }
    5121039});
    5131040
    514 // Map all event binding and triggering on a StateMachine to its `states` collection.
    515 _.each([ 'on', 'off', 'trigger' ], function( method ) {
    516     /**
    517      * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    518      */
    519     StateMachine.prototype[ method ] = function() {
    520         // Ensure that the `states` collection exists so the `StateMachine`
    521         // can be used as a mixin.
    522         this.states = this.states || new Backbone.Collection();
    523         // Forward the method to the `states` collection.
    524         this.states[ method ].apply( this.states, arguments );
    525         return this;
    526     };
    527 });
    528 
    529 module.exports = StateMachine;
    530 
    531 
    532 /***/ }),
    533 /* 29 */
    534 /***/ (function(module, exports) {
    535 
    536 /**
    537  * wp.media.controller.State
    538  *
    539  * A state is a step in a workflow that when set will trigger the controllers
    540  * for the regions to be updated as specified in the frame.
    541  *
    542  * A state has an event-driven lifecycle:
    543  *
    544  *     'ready'      triggers when a state is added to a state machine's collection.
    545  *     'activate'   triggers when a state is activated by a state machine.
    546  *     'deactivate' triggers when a state is deactivated by a state machine.
    547  *     'reset'      is not triggered automatically. It should be invoked by the
    548  *                  proper controller to reset the state to its default.
    549  *
    550  * @class
    551  * @augments Backbone.Model
    552  */
    553 var State = Backbone.Model.extend({
    554     /**
    555      * Constructor.
    556      *
    557      * @since 3.5.0
    558      */
    559     constructor: function() {
    560         this.on( 'activate', this._preActivate, this );
    561         this.on( 'activate', this.activate, this );
    562         this.on( 'activate', this._postActivate, this );
    563         this.on( 'deactivate', this._deactivate, this );
    564         this.on( 'deactivate', this.deactivate, this );
    565         this.on( 'reset', this.reset, this );
    566         this.on( 'ready', this._ready, this );
    567         this.on( 'ready', this.ready, this );
    568         /**
    569          * Call parent constructor with passed arguments
    570          */
    571         Backbone.Model.apply( this, arguments );
    572         this.on( 'change:menu', this._updateMenu, this );
    573     },
    574     /**
    575      * Ready event callback.
    576      *
    577      * @abstract
    578      * @since 3.5.0
    579      */
    580     ready: function() {},
    581 
    582     /**
    583      * Activate event callback.
    584      *
    585      * @abstract
    586      * @since 3.5.0
    587      */
    588     activate: function() {},
    589 
    590     /**
    591      * Deactivate event callback.
    592      *
    593      * @abstract
    594      * @since 3.5.0
    595      */
    596     deactivate: function() {},
    597 
    598     /**
    599      * Reset event callback.
    600      *
    601      * @abstract
    602      * @since 3.5.0
    603      */
    604     reset: function() {},
    605 
    606     /**
    607      * @access private
    608      * @since 3.5.0
    609      */
    610     _ready: function() {
    611         this._updateMenu();
    612     },
    613 
    614     /**
    615      * @access private
    616      * @since 3.5.0
    617     */
    618     _preActivate: function() {
    619         this.active = true;
    620     },
    621 
    622     /**
    623      * @access private
    624      * @since 3.5.0
    625      */
    626     _postActivate: function() {
    627         this.on( 'change:menu', this._menu, this );
    628         this.on( 'change:titleMode', this._title, this );
    629         this.on( 'change:content', this._content, this );
    630         this.on( 'change:toolbar', this._toolbar, this );
    631 
    632         this.frame.on( 'title:render:default', this._renderTitle, this );
    633 
    634         this._title();
    635         this._menu();
    636         this._toolbar();
    637         this._content();
    638         this._router();
    639     },
    640 
    641     /**
    642      * @access private
    643      * @since 3.5.0
    644      */
    645     _deactivate: function() {
    646         this.active = false;
    647 
    648         this.frame.off( 'title:render:default', this._renderTitle, this );
    649 
    650         this.off( 'change:menu', this._menu, this );
    651         this.off( 'change:titleMode', this._title, this );
    652         this.off( 'change:content', this._content, this );
    653         this.off( 'change:toolbar', this._toolbar, this );
    654     },
    655 
    656     /**
    657      * @access private
    658      * @since 3.5.0
    659      */
    660     _title: function() {
    661         this.frame.title.render( this.get('titleMode') || 'default' );
    662     },
    663 
    664     /**
    665      * @access private
    666      * @since 3.5.0
    667      */
    668     _renderTitle: function( view ) {
    669         view.$el.text( this.get('title') || '' );
    670     },
    671 
    672     /**
    673      * @access private
    674      * @since 3.5.0
    675      */
    676     _router: function() {
    677         var router = this.frame.router,
    678             mode = this.get('router'),
    679             view;
    680 
    681         this.frame.$el.toggleClass( 'hide-router', ! mode );
    682         if ( ! mode ) {
    683             return;
    684         }
    685 
    686         this.frame.router.render( mode );
    687 
    688         view = router.get();
    689         if ( view && view.select ) {
    690             view.select( this.frame.content.mode() );
    691         }
    692     },
    693 
    694     /**
    695      * @access private
    696      * @since 3.5.0
    697      */
    698     _menu: function() {
    699         var menu = this.frame.menu,
    700             mode = this.get('menu'),
    701             view;
    702 
    703         this.frame.$el.toggleClass( 'hide-menu', ! mode );
    704         if ( ! mode ) {
    705             return;
    706         }
    707 
    708         menu.mode( mode );
    709 
    710         view = menu.get();
    711         if ( view && view.select ) {
    712             view.select( this.id );
    713         }
    714     },
    715 
    716     /**
    717      * @access private
    718      * @since 3.5.0
    719      */
    720     _updateMenu: function() {
    721         var previous = this.previous('menu'),
    722             menu = this.get('menu');
    723 
    724         if ( previous ) {
    725             this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
    726         }
    727 
    728         if ( menu ) {
    729             this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
    730         }
    731     },
    732 
    733     /**
    734      * Create a view in the media menu for the state.
    735      *
    736      * @access private
    737      * @since 3.5.0
    738      *
    739      * @param {media.view.Menu} view The menu view.
    740      */
    741     _renderMenu: function( view ) {
    742         var menuItem = this.get('menuItem'),
    743             title = this.get('title'),
    744             priority = this.get('priority');
    745 
    746         if ( ! menuItem && title ) {
    747             menuItem = { text: title };
    748 
    749             if ( priority ) {
    750                 menuItem.priority = priority;
    751             }
    752         }
    753 
    754         if ( ! menuItem ) {
    755             return;
    756         }
    757 
    758         view.set( this.id, menuItem );
    759     }
    760 });
    761 
    762 _.each(['toolbar','content'], function( region ) {
    763     /**
    764      * @access private
    765      */
    766     State.prototype[ '_' + region ] = function() {
    767         var mode = this.get( region );
    768         if ( mode ) {
    769             this.frame[ region ].render( mode );
    770         }
    771     };
    772 });
    773 
    774 module.exports = State;
    775 
    776 
    777 /***/ }),
    778 /* 30 */
    779 /***/ (function(module, exports) {
    780 
    781 /**
    782  * wp.media.selectionSync
    783  *
    784  * Sync an attachments selection in a state with another state.
    785  *
    786  * Allows for selecting multiple images in the Insert Media workflow, and then
    787  * switching to the Insert Gallery workflow while preserving the attachments selection.
    788  *
    789  * @mixin
    790  */
    791 var selectionSync = {
    792     /**
    793      * @since 3.5.0
    794      */
    795     syncSelection: function() {
    796         var selection = this.get('selection'),
    797             manager = this.frame._selection;
    798 
    799         if ( ! this.get('syncSelection') || ! manager || ! selection ) {
    800             return;
    801         }
    802 
    803         // If the selection supports multiple items, validate the stored
    804         // attachments based on the new selection's conditions. Record
    805         // the attachments that are not included; we'll maintain a
    806         // reference to those. Other attachments are considered in flux.
    807         if ( selection.multiple ) {
    808             selection.reset( [], { silent: true });
    809             selection.validateAll( manager.attachments );
    810             manager.difference = _.difference( manager.attachments.models, selection.models );
    811         }
    812 
    813         // Sync the selection's single item with the master.
    814         selection.single( manager.single );
    815     },
    816 
    817     /**
    818      * Record the currently active attachments, which is a combination
    819      * of the selection's attachments and the set of selected
    820      * attachments that this specific selection considered invalid.
    821      * Reset the difference and record the single attachment.
    822      *
    823      * @since 3.5.0
    824      */
    825     recordSelection: function() {
    826         var selection = this.get('selection'),
    827             manager = this.frame._selection;
    828 
    829         if ( ! this.get('syncSelection') || ! manager || ! selection ) {
    830             return;
    831         }
    832 
    833         if ( selection.multiple ) {
    834             manager.attachments.reset( selection.toArray().concat( manager.difference ) );
    835             manager.difference = [];
    836         } else {
    837             manager.attachments.add( selection.toArray() );
    838         }
    839 
    840         manager.single = selection._single;
    841     }
    842 };
    843 
    844 module.exports = selectionSync;
    845 
    846 
    847 /***/ }),
    848 /* 31 */
    849 /***/ (function(module, exports) {
    850 
     1041module.exports = ImageDetails;
     1042
     1043},{}],11:[function(require,module,exports){
    8511044/**
    8521045 * wp.media.controller.Library
     
    11411334module.exports = Library;
    11421335
    1143 
    1144 /***/ }),
    1145 /* 32 */
    1146 /***/ (function(module, exports) {
    1147 
     1336},{}],12:[function(require,module,exports){
    11481337/**
    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  */
    1172 var State = wp.media.controller.State,
    1173     Library = wp.media.controller.Library,
    1174     l10n = wp.media.view.l10n,
    1175     ImageDetails;
    1176 
    1177 ImageDetails = 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 
    1207 module.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.
     1338 * wp.media.controller.MediaLibrary
    12181339 *
    12191340 * @class
     
    12211342 * @augments wp.media.controller.State
    12221343 * @augments Backbone.Model
    1223  *
    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.
    12451344 */
    12461345var Library = wp.media.controller.Library,
    1247     l10n = wp.media.view.l10n,
    1248     GalleryEdit;
    1249 
    1250 GalleryEdit = 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         }
     1346    MediaLibrary;
     1347
     1348MediaLibrary = 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 }) );
    12831367
    12841368        Library.prototype.initialize.apply( this, arguments );
     
    12861370
    12871371    /**
    1288      * @since 3.5.0
     1372     * @since 3.9.0
    12891373     */
    12901374    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 
    1353 module.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  *
    1370  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    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.
    1373  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    1374  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    1375  *                                                                          If one is not supplied, a collection of all images will be created.
    1376  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    1377  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    1378  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    1379  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    1380  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    1381  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    1382  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    1383  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    1384  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1385  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1386  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1387  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    1388  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    1389  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    1390  */
    1391 var Selection = wp.media.model.Selection,
    1392     Library = wp.media.controller.Library,
    1393     l10n = wp.media.view.l10n,
    1394     GalleryAdd;
    1395 
    1396 GalleryAdd = Library.extend({
    1397     defaults: _.defaults({
    1398         id:            'gallery-library',
    1399         title:         l10n.addToGalleryTitle,
    1400         multiple:      'add',
    1401         filterable:    'uploaded',
    1402         menu:          'gallery',
    1403         toolbar:       'gallery-add',
    1404         priority:      100,
    1405         syncSelection: false
    1406     }, Library.prototype.defaults ),
    1407 
    1408     /**
    1409      * @since 3.5.0
    1410      */
    1411     initialize: function() {
    1412         // If a library wasn't supplied, create a library of images.
    1413         if ( ! this.get('library') ) {
    1414             this.set( 'library', wp.media.query({ type: 'image' }) );
    1415         }
    1416 
    1417         Library.prototype.initialize.apply( this, arguments );
    1418     },
    1419 
    1420     /**
    1421      * @since 3.5.0
    1422      */
    1423     activate: function() {
    1424         var library = this.get('library'),
    1425             edit    = this.frame.state('gallery-edit').get('library');
    1426 
    1427         if ( this.editLibrary && this.editLibrary !== edit ) {
    1428             library.unobserve( this.editLibrary );
    1429         }
    1430 
    1431         // Accepts attachments that exist in the original library and
    1432         // that do not exist in gallery's library.
    1433         library.validator = function( attachment ) {
    1434             return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    1435         };
    1436 
    1437         // Reset the library to ensure that all attachments are re-added
    1438         // to the collection. Do so silently, as calling `observe` will
    1439         // trigger the `reset` event.
    1440         library.reset( library.mirroring.models, { silent: true });
    1441         library.observe( edit );
    1442         this.editLibrary = edit;
    1443 
     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        }
    14441380        Library.prototype.activate.apply( this, arguments );
    14451381    }
    14461382});
    14471383
    1448 module.exports = GalleryAdd;
    1449 
    1450 
    1451 /***/ }),
    1452 /* 35 */
    1453 /***/ (function(module, exports) {
    1454 
     1384module.exports = MediaLibrary;
     1385
     1386},{}],13:[function(require,module,exports){
    14551387/**
    1456  * wp.media.controller.CollectionEdit
    1457  *
    1458  * A state for editing a collection, which is used by audio and video playlists,
    1459  * and can be used for other collections.
     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.
    14601399 *
    14611400 * @class
    1462  * @augments wp.media.controller.Library
    1463  * @augments wp.media.controller.State
    1464  * @augments Backbone.Model
    1465  *
    1466  * @param {object}                     [attributes]                      The attributes hash passed to the state.
    1467  * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
    1468  * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
    1469  *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
    1470  * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
    1471  * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
    1472  * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
    1473  * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
    1474  * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1475  * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
    1476  * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
    1477  * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
    1478  * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
    1479  * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
    1480  * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
    1481  * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
    1482  * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
    1483  *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
    1484  * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
    1485  * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
    1486  *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    1487  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    1488  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     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.
    14891406 */
    1490 var Library = wp.media.controller.Library,
    1491     l10n = wp.media.view.l10n,
    1492     $ = jQuery,
    1493     CollectionEdit;
    1494 
    1495 CollectionEdit = Library.extend({
    1496     defaults: {
    1497         multiple:         false,
    1498         sortable:         true,
    1499         date:             false,
    1500         searchable:       false,
    1501         content:          'browse',
    1502         describe:         true,
    1503         dragInfo:         true,
    1504         idealColumnWidth: 170,
    1505         editing:          false,
    1506         priority:         60,
    1507         SettingsView:     false,
    1508         syncSelection:    false
    1509     },
    1510 
    1511     /**
    1512      * @since 3.9.0
    1513      */
    1514     initialize: function() {
    1515         var collectionType = this.get('collectionType');
    1516 
    1517         if ( 'video' === this.get( 'type' ) ) {
    1518             collectionType = 'video-' + collectionType;
    1519         }
    1520 
    1521         this.set( 'id', collectionType + '-edit' );
    1522         this.set( 'toolbar', collectionType + '-edit' );
    1523 
    1524         // If we haven't been provided a `library`, create a `Selection`.
    1525         if ( ! this.get('library') ) {
    1526             this.set( 'library', new wp.media.model.Selection() );
    1527         }
    1528         // The single `Attachment` view to be used in the `Attachments` view.
    1529         if ( ! this.get('AttachmentView') ) {
    1530             this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    1531         }
    1532         Library.prototype.initialize.apply( this, arguments );
    1533     },
    1534 
    1535     /**
    1536      * @since 3.9.0
    1537      */
    1538     activate: function() {
    1539         var library = this.get('library');
    1540 
    1541         // Limit the library to images only.
    1542         library.props.set( 'type', this.get( 'type' ) );
    1543 
    1544         // Watch for uploaded attachments.
    1545         this.get('library').observe( wp.Uploader.queue );
    1546 
    1547         this.frame.on( 'content:render:browse', this.renderSettings, this );
    1548 
    1549         Library.prototype.activate.apply( this, arguments );
    1550     },
    1551 
    1552     /**
    1553      * @since 3.9.0
    1554      */
    1555     deactivate: function() {
    1556         // Stop watching for uploaded attachments.
    1557         this.get('library').unobserve( wp.Uploader.queue );
    1558 
    1559         this.frame.off( 'content:render:browse', this.renderSettings, this );
    1560 
    1561         Library.prototype.deactivate.apply( this, arguments );
    1562     },
    1563 
    1564     /**
    1565      * Render the collection embed settings view in the browser sidebar.
     1407var Region = function( options ) {
     1408    _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
     1409};
     1410
     1411// Use Backbone's self-propagating `extend` inheritance method.
     1412Region.extend = Backbone.Model.extend;
     1413
     1414_.extend( Region.prototype, {
     1415    /**
     1416     * Activate a mode.
    15661417     *
    1567      * @todo This is against the pattern elsewhere in media. Typically the frame
    1568      *       is responsible for adding region mode callbacks. Explain.
     1418     * @since 3.5.0
    15691419     *
    1570      * @since 3.9.0
     1420     * @param {string} mode
    15711421     *
    1572      * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
    1573      */
    1574     renderSettings: function( attachmentsBrowserView ) {
    1575         var library = this.get('library'),
    1576             collectionType = this.get('collectionType'),
    1577             dragInfoText = this.get('dragInfoText'),
    1578             SettingsView = this.get('SettingsView'),
    1579             obj = {};
    1580 
    1581         if ( ! library || ! attachmentsBrowserView ) {
     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 ) {
    15821546            return;
    15831547        }
    15841548
    1585         library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
    1586 
    1587         obj[ collectionType ] = new SettingsView({
    1588             controller: this,
    1589             model:      library[ collectionType ],
    1590             priority:   40
    1591         });
    1592 
    1593         attachmentsBrowserView.sidebar.set( obj );
    1594 
    1595         if ( dragInfoText ) {
    1596             attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
    1597                 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
    1598                 priority: -40
    1599             }) );
    1600         }
    1601 
    1602         // Add the 'Reverse order' button to the toolbar.
    1603         attachmentsBrowserView.toolbar.set( 'reverse', {
    1604             text:     l10n.reverseOrder,
    1605             priority: 80,
    1606 
    1607             click: function() {
    1608                 library.reset( library.toArray().reverse() );
    1609             }
    1610         });
     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;
    16111560    }
    16121561});
    16131562
    1614 module.exports = CollectionEdit;
    1615 
    1616 
    1617 /***/ }),
    1618 /* 36 */
    1619 /***/ (function(module, exports) {
    1620 
    1621 /**
    1622  * wp.media.controller.CollectionAdd
    1623  *
    1624  * A state for adding attachments to a collection (e.g. video playlist).
    1625  *
    1626  * @class
    1627  * @augments wp.media.controller.Library
    1628  * @augments wp.media.controller.State
    1629  * @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').
    1653  */
    1654 var Selection = wp.media.model.Selection,
    1655     Library = wp.media.controller.Library,
    1656     CollectionAdd;
    1657 
    1658 CollectionAdd = 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      */
    1693     activate: function() {
    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 );
    1706         };
    1707 
    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 );
    1716     }
    1717 });
    1718 
    1719 module.exports = CollectionAdd;
    1720 
    1721 
    1722 /***/ }),
    1723 /* 37 */
    1724 /***/ (function(module, exports) {
    1725 
    1726 /**
    1727  * wp.media.controller.FeaturedImage
    1728  *
    1729  * A state for selecting a featured image for a post.
    1730  *
    1731  * @class
    1732  * @augments wp.media.controller.Library
    1733  * @augments wp.media.controller.State
    1734  * @augments Backbone.Model
    1735  *
    1736  * @param {object}                     [attributes]                          The attributes hash passed to the state.
    1737  * @param {string}                     [attributes.id=featured-image]        Unique identifier.
    1738  * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
    1739  * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
    1740  *                                                                           If one is not supplied, a collection of all images will be created.
    1741  * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
    1742  * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
    1743  *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
    1744  * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
    1745  * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
    1746  * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
    1747  * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
    1748  * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
    1749  * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
    1750  *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
    1751  * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1752  * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
    1753  * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1754  * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
    1755  * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
    1756  */
    1757 var Attachment = wp.media.model.Attachment,
    1758     Library = wp.media.controller.Library,
    1759     l10n = wp.media.view.l10n,
    1760     FeaturedImage;
    1761 
    1762 FeaturedImage = Library.extend({
    1763     defaults: _.defaults({
    1764         id:            'featured-image',
    1765         title:         l10n.setFeaturedImageTitle,
    1766         multiple:      false,
    1767         filterable:    'uploaded',
    1768         toolbar:       'featured-image',
    1769         priority:      60,
    1770         syncSelection: true
    1771     }, Library.prototype.defaults ),
    1772 
    1773     /**
    1774      * @since 3.5.0
    1775      */
    1776     initialize: function() {
    1777         var library, comparator;
    1778 
    1779         // If we haven't been provided a `library`, create a `Selection`.
    1780         if ( ! this.get('library') ) {
    1781             this.set( 'library', wp.media.query({ type: 'image' }) );
    1782         }
    1783 
    1784         Library.prototype.initialize.apply( this, arguments );
    1785 
    1786         library    = this.get('library');
    1787         comparator = library.comparator;
    1788 
    1789         // Overload the library's comparator to push items that are not in
    1790         // the mirrored query to the front of the aggregate collection.
    1791         library.comparator = function( a, b ) {
    1792             var aInQuery = !! this.mirroring.get( a.cid ),
    1793                 bInQuery = !! this.mirroring.get( b.cid );
    1794 
    1795             if ( ! aInQuery && bInQuery ) {
    1796                 return -1;
    1797             } else if ( aInQuery && ! bInQuery ) {
    1798                 return 1;
    1799             } else {
    1800                 return comparator.apply( this, arguments );
    1801             }
    1802         };
    1803 
    1804         // Add all items in the selection to the library, so any featured
    1805         // images that are not initially loaded still appear.
    1806         library.observe( this.get('selection') );
    1807     },
    1808 
    1809     /**
    1810      * @since 3.5.0
    1811      */
    1812     activate: function() {
    1813         this.updateSelection();
    1814         this.frame.on( 'open', this.updateSelection, this );
    1815 
    1816         Library.prototype.activate.apply( this, arguments );
    1817     },
    1818 
    1819     /**
    1820      * @since 3.5.0
    1821      */
    1822     deactivate: function() {
    1823         this.frame.off( 'open', this.updateSelection, this );
    1824 
    1825         Library.prototype.deactivate.apply( this, arguments );
    1826     },
    1827 
    1828     /**
    1829      * @since 3.5.0
    1830      */
    1831     updateSelection: function() {
    1832         var selection = this.get('selection'),
    1833             id = wp.media.view.settings.post.featuredImageId,
    1834             attachment;
    1835 
    1836         if ( '' !== id && -1 !== id ) {
    1837             attachment = Attachment.get( id );
    1838             attachment.fetch();
    1839         }
    1840 
    1841         selection.reset( attachment ? [ attachment ] : [] );
    1842     }
    1843 });
    1844 
    1845 module.exports = FeaturedImage;
    1846 
    1847 
    1848 /***/ }),
    1849 /* 38 */
    1850 /***/ (function(module, exports) {
    1851 
     1563module.exports = Region;
     1564
     1565},{}],14:[function(require,module,exports){
    18521566/**
    18531567 * wp.media.controller.ReplaceImage
     
    19571671module.exports = ReplaceImage;
    19581672
    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  */
    1982 var l10n = wp.media.view.l10n,
    1983     EditImage;
    1984 
    1985 EditImage = 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 
    2037 module.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  */
    2052 var Library = wp.media.controller.Library,
    2053     MediaLibrary;
    2054 
    2055 MediaLibrary = 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 
    2091 module.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  */
    2119 var l10n = wp.media.view.l10n,
    2120     $ = Backbone.$,
    2121     Embed;
    2122 
    2123 Embed = 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 
    2231 module.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  */
    2247 var l10n = wp.media.view.l10n,
    2248     Cropper;
    2249 
    2250 Cropper = 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 
    2351 module.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  */
    2368 var Controller = wp.media.controller,
    2369     CustomizeImageCropper;
    2370 
    2371 CustomizeImageCropper = 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 
    2389 module.exports = CustomizeImageCropper;
    2390 
    2391 
    2392 /***/ }),
    2393 /* 44 */
    2394 /***/ (function(module, exports) {
    2395 
     1673},{}],15:[function(require,module,exports){
    23961674/**
    23971675 * wp.media.controller.SiteIconCropper
     
    24421720module.exports = SiteIconCropper;
    24431721
    2444 
    2445 /***/ }),
    2446 /* 45 */
    2447 /***/ (function(module, exports) {
    2448 
     1722},{}],16:[function(require,module,exports){
    24491723/**
    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.
     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
    24601732 *
    24611733 * @class
    2462  * @augments wp.Backbone.View
    2463  * @augments Backbone.View
     1734 * @augments Backbone.Model
     1735 * @mixin
     1736 * @mixes Backbone.Events
     1737 *
     1738 * @param {Array} states
    24641739 */
    2465 var 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.
     1740var 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.
     1746StateMachine.extend = Backbone.Model.extend;
     1747
     1748_.extend( StateMachine.prototype, Backbone.Events, {
     1749    /**
     1750     * Fetch a state.
    24761751     *
    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         }
     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');
    24971807
    24981808        return this;
    24991809    },
    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 );
     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        }
    25091826    }
    25101827});
    25111828
    2512 module.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  */
    2534 var 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 
    2682 module.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  */
    2701 var Frame = wp.media.view.Frame,
    2702     $ = jQuery,
    2703     MediaFrame;
    2704 
    2705 MediaFrame = 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         }
     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 );
    29291840        return this;
    29301841    };
    29311842});
    29321843
    2933 module.exports = MediaFrame;
    2934 
    2935 
    2936 /***/ }),
    2937 /* 48 */
    2938 /***/ (function(module, exports) {
    2939 
     1844module.exports = StateMachine;
     1845
     1846},{}],17:[function(require,module,exports){
    29401847/**
    2941  * wp.media.view.MediaFrame.Select
    2942  *
    2943  * A frame for selecting an item or items from the media library.
     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.
    29441860 *
    29451861 * @class
    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
     1862 * @augments Backbone.Model
    29521863 */
    2953 
    2954 var MediaFrame = wp.media.view.MediaFrame,
    2955     l10n = wp.media.view.l10n,
    2956     Select;
    2957 
    2958 Select = 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.
     1864var State = Backbone.Model.extend({
     1865    /**
     1866     * Constructor.
    29771867     *
    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.
     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.
    29811887     *
    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 ) {
     1888     * @abstract
     1889     * @since 3.5.0
     1890     */
     1891    ready: function() {},
     1892
     1893    /**
     1894     * Activate event callback.
     1895     *
     1896     * @abstract
     1897     * @since 3.5.0
     1898     */
     1899    activate: function() {},
     1900
     1901    /**
     1902     * Deactivate event callback.
     1903     *
     1904     * @abstract
     1905     * @since 3.5.0
     1906     */
     1907    deactivate: function() {},
     1908
     1909    /**
     1910     * Reset event callback.
     1911     *
     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 ) {
    30061994            return;
    30071995        }
    30081996
    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.
     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.
    30232046     *
    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.
     2047     * @access private
     2048     * @since 3.5.0
    30362049     *
    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
     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;
    30482062            }
    3049         });
    3050     },
    3051 
    3052     /**
    3053      * Render callback for the content region in the `browse` mode.
    3054      *
    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
    3095      *
    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 );
     2063        }
     2064
     2065        if ( ! menuItem ) {
     2066            return;
     2067        }
     2068
     2069        view.set( this.id, menuItem );
    31052070    }
    31062071});
    31072072
    3108 module.exports = Select;
    3109 
    3110 
    3111 /***/ }),
    3112 /* 49 */
    3113 /***/ (function(module, exports) {
    3114 
     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    };
     2083});
     2084
     2085module.exports = State;
     2086
     2087},{}],18:[function(require,module,exports){
    31152088/**
    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
     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
    31282097 */
    3129 var Select = wp.media.view.MediaFrame.Select,
    3130     Library = wp.media.controller.Library,
    3131     l10n = wp.media.view.l10n,
    3132     Post;
    3133 
    3134 Post = 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         }) );
     2098var 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 ) {
     2107            return;
     2108        }
     2109
     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.
     2129     *
     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 ) {
     2137            return;
     2138        }
     2139
     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;
    38442148    }
    3845 });
    3846 
    3847 module.exports = Post;
    3848 
    3849 
    3850 /***/ }),
    3851 /* 50 */
    3852 /***/ (function(module, exports) {
     2149};
     2150
     2151module.exports = selectionSync;
     2152
     2153},{}],19:[function(require,module,exports){
     2154var media = wp.media,
     2155    $ = jQuery,
     2156    l10n;
     2157
     2158media.isTouchDevice = ( 'ontouchend' in document );
     2159
     2160// Link any localized strings.
     2161l10n = media.view.l10n = window._wpMediaViewsL10n || {};
     2162
     2163// Link any settings.
     2164media.view.settings = l10n.settings || {};
     2165delete l10n.settings;
     2166
     2167// Copy the `post` setting over to the model settings.
     2168media.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}());
    38532188
    38542189/**
    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
     2190 * A shared event bus used to provide events into
     2191 * the media workflows that 3rd-party devs can use to hook
     2192 * in.
    38682193 */
    3869 var Select = wp.media.view.MediaFrame.Select,
    3870     l10n = wp.media.view.l10n,
    3871     ImageDetails;
    3872 
    3873 ImageDetails = 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 ) {
    3938             return;
    3939         }
    3940 
    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         }) );
     2194media.events = _.extend( {}, Backbone.Events );
     2195
     2196/**
     2197 * Makes it easier to bind events using transitions.
     2198 *
     2199 * @param {string} selector
     2200 * @param {Number} sensitivity
     2201 * @returns {Promise}
     2202 */
     2203media.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();
    40242222    }
    40252223
    4026 });
    4027 
    4028 module.exports = ImageDetails;
    4029 
    4030 
    4031 /***/ }),
    4032 /* 51 */
    4033 /***/ (function(module, exports) {
    4034 
     2224    return deferred.promise();
     2225};
     2226
     2227media.controller.Region = require( './controllers/region.js' );
     2228media.controller.StateMachine = require( './controllers/state-machine.js' );
     2229media.controller.State = require( './controllers/state.js' );
     2230
     2231media.selectionSync = require( './utils/selection-sync.js' );
     2232media.controller.Library = require( './controllers/library.js' );
     2233media.controller.ImageDetails = require( './controllers/image-details.js' );
     2234media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
     2235media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
     2236media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
     2237media.controller.CollectionAdd = require( './controllers/collection-add.js' );
     2238media.controller.FeaturedImage = require( './controllers/featured-image.js' );
     2239media.controller.ReplaceImage = require( './controllers/replace-image.js' );
     2240media.controller.EditImage = require( './controllers/edit-image.js' );
     2241media.controller.MediaLibrary = require( './controllers/media-library.js' );
     2242media.controller.Embed = require( './controllers/embed.js' );
     2243media.controller.Cropper = require( './controllers/cropper.js' );
     2244media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' );
     2245media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' );
     2246
     2247media.View = require( './views/view.js' );
     2248media.view.Frame = require( './views/frame.js' );
     2249media.view.MediaFrame = require( './views/media-frame.js' );
     2250media.view.MediaFrame.Select = require( './views/frame/select.js' );
     2251media.view.MediaFrame.Post = require( './views/frame/post.js' );
     2252media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
     2253media.view.Modal = require( './views/modal.js' );
     2254media.view.FocusManager = require( './views/focus-manager.js' );
     2255media.view.UploaderWindow = require( './views/uploader/window.js' );
     2256media.view.EditorUploader = require( './views/uploader/editor.js' );
     2257media.view.UploaderInline = require( './views/uploader/inline.js' );
     2258media.view.UploaderStatus = require( './views/uploader/status.js' );
     2259media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
     2260media.view.Toolbar = require( './views/toolbar.js' );
     2261media.view.Toolbar.Select = require( './views/toolbar/select.js' );
     2262media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
     2263media.view.Button = require( './views/button.js' );
     2264media.view.ButtonGroup = require( './views/button-group.js' );
     2265media.view.PriorityList = require( './views/priority-list.js' );
     2266media.view.MenuItem = require( './views/menu-item.js' );
     2267media.view.Menu = require( './views/menu.js' );
     2268media.view.RouterItem = require( './views/router-item.js' );
     2269media.view.Router = require( './views/router.js' );
     2270media.view.Sidebar = require( './views/sidebar.js' );
     2271media.view.Attachment = require( './views/attachment.js' );
     2272media.view.Attachment.Library = require( './views/attachment/library.js' );
     2273media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
     2274media.view.Attachments = require( './views/attachments.js' );
     2275media.view.Search = require( './views/search.js' );
     2276media.view.AttachmentFilters = require( './views/attachment-filters.js' );
     2277media.view.DateFilter = require( './views/attachment-filters/date.js' );
     2278media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
     2279media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
     2280media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
     2281media.view.Selection = require( './views/selection.js' );
     2282media.view.Attachment.Selection = require( './views/attachment/selection.js' );
     2283media.view.Attachments.Selection = require( './views/attachments/selection.js' );
     2284media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
     2285media.view.Settings = require( './views/settings.js' );
     2286media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
     2287media.view.Settings.Gallery = require( './views/settings/gallery.js' );
     2288media.view.Settings.Playlist = require( './views/settings/playlist.js' );
     2289media.view.Attachment.Details = require( './views/attachment/details.js' );
     2290media.view.AttachmentCompat = require( './views/attachment-compat.js' );
     2291media.view.Iframe = require( './views/iframe.js' );
     2292media.view.Embed = require( './views/embed.js' );
     2293media.view.Label = require( './views/label.js' );
     2294media.view.EmbedUrl = require( './views/embed/url.js' );
     2295media.view.EmbedLink = require( './views/embed/link.js' );
     2296media.view.EmbedImage = require( './views/embed/image.js' );
     2297media.view.ImageDetails = require( './views/image-details.js' );
     2298media.view.Cropper = require( './views/cropper.js' );
     2299media.view.SiteIconCropper = require( './views/site-icon-cropper.js' );
     2300media.view.SiteIconPreview = require( './views/site-icon-preview.js' );
     2301media.view.EditImage = require( './views/edit-image.js' );
     2302media.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){
    40352305/**
    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  */
    4045 var $ = jQuery,
    4046     Modal;
    4047 
    4048 Modal = 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.
    4220      *
    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 
    4245 module.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  */
    4260 var 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 ) {
    4277             return;
    4278         }
    4279 
    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         }
    4291     }
    4292 
    4293 });
    4294 
    4295 module.exports = FocusManager;
    4296 
    4297 
    4298 /***/ }),
    4299 /* 53 */
    4300 /***/ (function(module, exports) {
    4301 
    4302 /**
    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]
    4317  */
    4318 var $ = jQuery,
    4319     UploaderWindow;
    4320 
    4321 UploaderWindow = 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 
    4410 module.exports = UploaderWindow;
    4411 
    4412 
    4413 /***/ }),
    4414 /* 54 */
    4415 /***/ (function(module, exports) {
    4416 
    4417 /**
    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
     2306 * wp.media.view.AttachmentCompat
     2307 *
     2308 * A view to display fields added via the `attachment_fields_to_edit` filter.
    44222309 *
    44232310 * @class
     
    44272314 */
    44282315var View = wp.media.View,
    4429     l10n = wp.media.view.l10n,
    4430     $ = jQuery,
    4431     EditorUploader;
    4432 
    4433 EditorUploader = 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      */
     2316    AttachmentCompat;
     2317
     2318AttachmentCompat = View.extend({
     2319    tagName:   'form',
     2320    className: 'compat-item',
     2321
     2322    events: {
     2323        'submit':          'preventDefault',
     2324        'change input':    'save',
     2325        'change select':   'save',
     2326        'change textarea': 'save'
     2327    },
     2328
    44462329    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 
    4639 module.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  */
    4656 var View = wp.media.View,
    4657     UploaderInline;
    4658 
    4659 UploaderInline = View.extend({
    4660     tagName:   'div',
    4661     className: 'uploader-inline',
    4662     template:  wp.template('uploader-inline'),
    4663 
    4664     events: {
    4665         'click .close': 'hide'
    4666     },
    4667 
    4668     initialize: function() {
    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
     2330        this.listenTo( this.model, 'change:compat', this.render );
     2331    },
     2332    /**
     2333     * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    47072334     */
    47082335    dispose: function() {
    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 
    4774 module.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  */
    4791 var View = wp.media.View,
    4792     UploaderStatus;
    4793 
    4794 UploaderStatus = 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 
    4916 module.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  */
    4931 var UploaderStatusError = wp.media.View.extend({
    4932     className: 'upload-error',
    4933     template:  wp.template('uploader-status-error')
    4934 });
    4935 
    4936 module.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  */
    4954 var View = wp.media.View,
    4955     Toolbar;
    4956 
    4957 Toolbar = 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 );
     2336        if ( this.$(':focus').length ) {
     2337            this.save();
    50022338        }
    50032339        /**
     
    50062342        return View.prototype.dispose.apply( this, arguments );
    50072343    },
    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 
     2344    /**
     2345     * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     2346     */
     2347    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();
    50472356        return this;
    50482357    },
    50492358    /**
    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 );
     2359     * @param {Object} event
     2360     */
     2361    preventDefault: function( event ) {
     2362        event.preventDefault();
     2363    },
     2364    /**
     2365     * @param {Object} event
     2366     */
     2367    save: function( event ) {
     2368        var data = {};
     2369
     2370        if ( event ) {
     2371            event.preventDefault();
     2372        }
     2373
     2374        _.each( this.$el.serializeArray(), function( pair ) {
     2375            data[ pair.name ] = pair.value;
    50962376        });
     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'] );
    50972384    }
    50982385});
    50992386
    5100 module.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  */
    5116 var Toolbar = wp.media.view.Toolbar,
    5117     l10n = wp.media.view.l10n,
    5118     Select;
    5119 
    5120 Select = 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 
    5174 module.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  */
    5191 var Select = wp.media.view.Toolbar.Select,
    5192     l10n = wp.media.view.l10n,
    5193     Embed;
    5194 
    5195 Embed = 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 
    5215 module.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  */
    5230 var 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
    5270      */
    5271     render: function() {
    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 
    5289         return this;
    5290     },
    5291     /**
    5292      * @param {Object} event
    5293      */
    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 
    5305 module.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  */
    5320 var $ = Backbone.$,
    5321     ButtonGroup;
    5322 
    5323 ButtonGroup = 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 
    5355 module.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  */
    5370 var 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 
    5456 module.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  */
    5471 var $ = jQuery,
    5472     MenuItem;
    5473 
    5474 MenuItem = wp.media.View.extend({
    5475     tagName:   'a',
    5476     className: 'media-menu-item',
    5477 
    5478     attributes: {
    5479         href: '#'
    5480     },
    5481 
    5482     events: {
    5483         'click': '_click'
    5484     },
    5485     /**
    5486      * @param {Object} event
    5487      */
    5488     _click: function( event ) {
    5489         var clickOverride = this.options.click;
    5490 
    5491         if ( event ) {
    5492             event.preventDefault();
    5493         }
    5494 
    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;
    5529     }
    5530 });
    5531 
    5532 module.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  */
    5548 var MenuItem = wp.media.view.MenuItem,
    5549     PriorityList = wp.media.view.PriorityList,
    5550     Menu;
    5551 
    5552 Menu = 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 
    5653 module.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  */
    5669 var 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 
    5681 module.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  */
    5698 var Menu = wp.media.view.Menu,
    5699     Router;
    5700 
    5701 Router = 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 
    5722 module.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  */
    5738 var Sidebar = wp.media.view.PriorityList.extend({
    5739     className: 'media-sidebar'
    5740 });
    5741 
    5742 module.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  */
    5757 var View = wp.media.View,
    5758     $ = jQuery,
    5759     Attachment;
    5760 
    5761 Attachment = 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 
    6292 module.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  */
    6308 var Library = wp.media.view.Attachment.extend({
    6309     buttons: {
    6310         check: true
    6311     }
    6312 });
    6313 
    6314 module.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  */
    6330 var EditLibrary = wp.media.view.Attachment.extend({
    6331     buttons: {
    6332         close: true
    6333     }
    6334 });
    6335 
    6336 module.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  */
    6351 var View = wp.media.View,
    6352     $ = jQuery,
    6353     Attachments;
    6354 
    6355 Attachments = 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 
    6639 module.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  */
    6654 var l10n = wp.media.view.l10n,
    6655     Search;
    6656 
    6657 Search = 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 
    6691 module.exports = Search;
    6692 
    6693 
    6694 /***/ }),
    6695 /* 74 */
    6696 /***/ (function(module, exports) {
    6697 
     2387module.exports = AttachmentCompat;
     2388
     2389},{}],21:[function(require,module,exports){
    66982390/**
    66992391 * wp.media.view.AttachmentFilters
     
    67722464module.exports = AttachmentFilters;
    67732465
    6774 
    6775 /***/ }),
    6776 /* 75 */
    6777 /***/ (function(module, exports) {
    6778 
     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 */
     2476var l10n = wp.media.view.l10n,
     2477    All;
     2478
     2479All = 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
     2554module.exports = All;
     2555
     2556},{}],23:[function(require,module,exports){
    67792557/**
    67802558 * A filter dropdown for month/dates.
     
    68172595module.exports = DateFilter;
    68182596
    6819 
    6820 /***/ }),
    6821 /* 76 */
    6822 /***/ (function(module, exports) {
    6823 
     2597},{}],24:[function(require,module,exports){
    68242598/**
    68252599 * wp.media.view.AttachmentFilters.Uploaded
     
    68802654module.exports = Uploaded;
    68812655
    6882 
    6883 /***/ }),
    6884 /* 77 */
    6885 /***/ (function(module, exports) {
    6886 
     2656},{}],25:[function(require,module,exports){
    68872657/**
    6888  * wp.media.view.AttachmentFilters.All
     2658 * wp.media.view.Attachment
    68892659 *
    68902660 * @class
    6891  * @augments wp.media.view.AttachmentFilters
    68922661 * @augments wp.media.View
    68932662 * @augments wp.Backbone.View
    68942663 * @augments Backbone.View
    68952664 */
    6896 var l10n = wp.media.view.l10n,
    6897     All;
    6898 
    6899 All = 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             };
     2665var View = wp.media.View,
     2666    $ = jQuery,
     2667    Attachment;
     2668
     2669Attachment = 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' )
     2681        };
     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
    69142858        });
    69152859
    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
     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')
    69263036        };
    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
    6952         };
    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;
     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        }
    69713166    }
    69723167});
    69733168
    6974 module.exports = All;
    6975 
    6976 
    6977 /***/ }),
    6978 /* 78 */
    6979 /***/ (function(module, exports) {
    6980 
     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
     3200module.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 */
     3212var Attachment = wp.media.view.Attachment,
     3213    l10n = wp.media.view.l10n,
     3214    Details;
     3215
     3216Details = 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
     3338module.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 */
     3350var EditLibrary = wp.media.view.Attachment.extend({
     3351    buttons: {
     3352        close: true
     3353    }
     3354});
     3355
     3356module.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 */
     3369var EditSelection = wp.media.view.Attachment.Selection.extend({
     3370    buttons: {
     3371        close: true
     3372    }
     3373});
     3374
     3375module.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 */
     3387var Library = wp.media.view.Attachment.extend({
     3388    buttons: {
     3389        check: true
     3390    }
     3391});
     3392
     3393module.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 */
     3405var 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
     3415module.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 */
     3426var View = wp.media.View,
     3427    $ = jQuery,
     3428    Attachments;
     3429
     3430Attachments = 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
     3714module.exports = Attachments;
     3715
     3716},{}],32:[function(require,module,exports){
    69813717/**
    69823718 * wp.media.view.AttachmentsBrowser
     
    74224158module.exports = AttachmentsBrowser;
    74234159
    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  */
    7437 var l10n = wp.media.view.l10n,
    7438     Selection;
    7439 
    7440 Selection = 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 
    7509 module.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  */
    7525 var 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 
    7535 module.exports = Selection;
    7536 
    7537 
    7538 /***/ }),
    7539 /* 81 */
    7540 /***/ (function(module, exports) {
    7541 
     4160},{}],33:[function(require,module,exports){
    75424161/**
    75434162 * wp.media.view.Attachments.Selection
     
    75694188module.exports = Selection;
    75704189
    7571 
    7572 /***/ }),
    7573 /* 82 */
    7574 /***/ (function(module, exports) {
    7575 
     4190},{}],34:[function(require,module,exports){
    75764191/**
    7577  * wp.media.view.Attachments.EditSelection
     4192 * wp.media.view.ButtonGroup
    75784193 *
    75794194 * @class
    7580  * @augments wp.media.view.Attachment.Selection
    7581  * @augments wp.media.view.Attachment
    75824195 * @augments wp.media.View
    75834196 * @augments wp.Backbone.View
    75844197 * @augments Backbone.View
    75854198 */
    7586 var EditSelection = wp.media.view.Attachment.Selection.extend({
    7587     buttons: {
    7588         close: true
     4199var $ = Backbone.$,
     4200    ButtonGroup;
     4201
     4202ButtonGroup = 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;
    75894231    }
    75904232});
    75914233
    7592 module.exports = EditSelection;
    7593 
    7594 
    7595 /***/ }),
    7596 /* 83 */
    7597 /***/ (function(module, exports) {
    7598 
     4234module.exports = ButtonGroup;
     4235
     4236},{}],35:[function(require,module,exports){
    75994237/**
    7600  * wp.media.view.Settings
     4238 * wp.media.view.Button
     4239 *
     4240 * @class
     4241 * @augments wp.media.View
     4242 * @augments wp.Backbone.View
     4243 * @augments Backbone.View
     4244 */
     4245var 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
     4320module.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.
    76014331 *
    76024332 * @class
     
    76064336 */
    76074337var View = wp.media.View,
    7608     $ = Backbone.$,
    7609     Settings;
    7610 
    7611 Settings = View.extend({
    7612     events: {
    7613         'click button':    'updateHandler',
    7614         'change input':    'updateHandler',
    7615         'change select':   'updateHandler',
    7616         'change textarea': 'updateHandler'
    7617     },
    7618 
     4338    UploaderStatus = wp.media.view.UploaderStatus,
     4339    l10n = wp.media.view.l10n,
     4340    $ = jQuery,
     4341    Cropper;
     4342
     4343Cropper = View.extend({
     4344    className: 'crop-content',
     4345    template: wp.template('crop-content'),
    76194346    initialize: function() {
    7620         this.model = this.model || new Backbone.Model();
    7621         this.listenTo( this.model, 'change', this.updateChanges );
    7622     },
    7623 
     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    },
    76244361    prepare: function() {
    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         }
     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 });
    77144384    }
    77154385});
    77164386
    7717 module.exports = Settings;
    7718 
    7719 
    7720 /***/ }),
    7721 /* 84 */
    7722 /***/ (function(module, exports) {
    7723 
     4387module.exports = Cropper;
     4388
     4389},{}],37:[function(require,module,exports){
    77244390/**
    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  */
    7733 var Settings = wp.media.view.Settings,
    7734     AttachmentDisplay;
    7735 
    7736 AttachmentDisplay = 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 
    7815 module.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  */
    7831 var Gallery = wp.media.view.Settings.extend({
    7832     className: 'collection-settings gallery-settings',
    7833     template:  wp.template('gallery-settings')
    7834 });
    7835 
    7836 module.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  */
    7852 var Playlist = wp.media.view.Settings.extend({
    7853     className: 'collection-settings playlist-settings',
    7854     template:  wp.template('playlist-settings')
    7855 });
    7856 
    7857 module.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  */
    7873 var Attachment = wp.media.view.Attachment,
    7874     l10n = wp.media.view.l10n,
    7875     Details;
    7876 
    7877 Details = 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 
    7999 module.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.
     4391 * wp.media.view.EditImage
    80104392 *
    80114393 * @class
     
    80154397 */
    80164398var View = wp.media.View,
    8017     AttachmentCompat;
    8018 
    8019 AttachmentCompat = 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'] );
     4399    EditImage;
     4400
     4401EditImage = 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 ) );
    80854439    }
     4440
    80864441});
    80874442
    8088 module.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  */
    8103 var 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 
    8116 module.exports = Iframe;
    8117 
    8118 
    8119 /***/ }),
    8120 /* 90 */
    8121 /***/ (function(module, exports) {
    8122 
     4443module.exports = EditImage;
     4444
     4445},{}],38:[function(require,module,exports){
    81234446/**
    81244447 * wp.media.view.Embed
     
    81844507module.exports = Embed;
    81854508
    8186 
    8187 /***/ }),
    8188 /* 91 */
    8189 /***/ (function(module, exports) {
    8190 
     4509},{}],39:[function(require,module,exports){
    81914510/**
    8192  * wp.media.view.Label
     4511 * wp.media.view.EmbedImage
    81934512 *
    81944513 * @class
     4514 * @augments wp.media.view.Settings.AttachmentDisplay
     4515 * @augments wp.media.view.Settings
    81954516 * @augments wp.media.View
    81964517 * @augments wp.Backbone.View
    81974518 * @augments Backbone.View
    81984519 */
    8199 var Label = wp.media.View.extend({
    8200     tagName: 'label',
    8201     className: 'screen-reader-text',
     4520var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     4521    EmbedImage;
     4522
     4523EmbedImage = AttachmentDisplay.extend({
     4524    className: 'embed-media-settings',
     4525    template:  wp.template('embed-image-settings'),
    82024526
    82034527    initialize: function() {
    8204         this.value = this.options.value;
    8205     },
    8206 
    8207     render: function() {
    8208         this.$el.html( this.value );
    8209 
    8210         return this;
     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') );
    82114537    }
    82124538});
    82134539
    8214 module.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  */
    8229 var View = wp.media.View,
    8230     $ = jQuery,
    8231     EmbedUrl;
    8232 
    8233 EmbedUrl = 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 
    8297 module.exports = EmbedUrl;
    8298 
    8299 
    8300 /***/ }),
    8301 /* 93 */
    8302 /***/ (function(module, exports) {
    8303 
     4540module.exports = EmbedImage;
     4541
     4542},{}],40:[function(require,module,exports){
    83044543/**
    83054544 * wp.media.view.EmbedLink
     
    83904629module.exports = EmbedLink;
    83914630
    8392 
    8393 /***/ }),
    8394 /* 94 */
    8395 /***/ (function(module, exports) {
    8396 
     4631},{}],41:[function(require,module,exports){
    83974632/**
    8398  * wp.media.view.EmbedImage
     4633 * wp.media.view.EmbedUrl
    83994634 *
    84004635 * @class
    8401  * @augments wp.media.view.Settings.AttachmentDisplay
    8402  * @augments wp.media.view.Settings
    84034636 * @augments wp.media.View
    84044637 * @augments wp.Backbone.View
    84054638 * @augments Backbone.View
    84064639 */
    8407 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    8408     EmbedImage;
    8409 
    8410 EmbedImage = AttachmentDisplay.extend({
    8411     className: 'embed-media-settings',
    8412     template:  wp.template('embed-image-settings'),
     4640var View = wp.media.View,
     4641    $ = jQuery,
     4642    EmbedUrl;
     4643
     4644EmbedUrl = View.extend({
     4645    tagName:   'label',
     4646    className: 'embed-url',
     4647
     4648    events: {
     4649        'input':  'url',
     4650        'keyup':  'url',
     4651        'change': 'url'
     4652    },
    84134653
    84144654    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://';
    84154680        /**
    8416          * Call `initialize` directly on parent class with passed arguments
     4681         * Call `render` directly on parent class with passed arguments
    84174682         */
    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') );
     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        }
    84244705    }
    84254706});
    84264707
    8427 module.exports = EmbedImage;
    8428 
    8429 
    8430 /***/ }),
    8431 /* 95 */
    8432 /***/ (function(module, exports) {
    8433 
     4708module.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 */
     4719var 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
     4754module.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 */
     4772var 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
     4920module.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 */
     4938var Select = wp.media.view.MediaFrame.Select,
     4939    l10n = wp.media.view.l10n,
     4940    ImageDetails;
     4941
     4942ImageDetails = 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
     5097module.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 */
     5114var Select = wp.media.view.MediaFrame.Select,
     5115    Library = wp.media.controller.Library,
     5116    l10n = wp.media.view.l10n,
     5117    Post;
     5118
     5119Post = 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
     5832module.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
     5849var MediaFrame = wp.media.view.MediaFrame,
     5850    l10n = wp.media.view.l10n,
     5851    Select;
     5852
     5853Select = 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
     6003module.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 */
     6014var 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
     6027module.exports = Iframe;
     6028
     6029},{}],48:[function(require,module,exports){
    84346030/**
    84356031 * wp.media.view.ImageDetails
     
    85996195module.exports = ImageDetails;
    86006196
    8601 
    8602 /***/ }),
    8603 /* 96 */
    8604 /***/ (function(module, exports) {
    8605 
     6197},{}],49:[function(require,module,exports){
    86066198/**
    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.
     6199 * wp.media.view.Label
     6200 *
     6201 * @class
     6202 * @augments wp.media.View
     6203 * @augments wp.Backbone.View
     6204 * @augments Backbone.View
     6205 */
     6206var 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
     6221module.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 */
     6236var Frame = wp.media.view.Frame,
     6237    $ = jQuery,
     6238    MediaFrame;
     6239
     6240MediaFrame = 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
     6468module.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 */
     6479var $ = jQuery,
     6480    MenuItem;
     6481
     6482MenuItem = 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
     6540module.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 */
     6552var MenuItem = wp.media.view.MenuItem,
     6553    PriorityList = wp.media.view.PriorityList,
     6554    Menu;
     6555
     6556Menu = 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
     6657module.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 */
     6670var $ = jQuery,
     6671    Modal;
     6672
     6673Modal = 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
     6870module.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 */
     6881var 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
     6967module.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 */
     6979var 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
     6991module.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 */
     7004var Menu = wp.media.view.Menu,
     7005    Router;
     7006
     7007Router = 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
     7028module.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 */
     7039var l10n = wp.media.view.l10n,
     7040    Search;
     7041
     7042Search = 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
     7076module.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 */
     7087var l10n = wp.media.view.l10n,
     7088    Selection;
     7089
     7090Selection = 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
     7159module.exports = Selection;
     7160
     7161},{}],59:[function(require,module,exports){
     7162/**
     7163 * wp.media.view.Settings
    86147164 *
    86157165 * @class
     
    86197169 */
    86207170var View = wp.media.View,
    8621     UploaderStatus = wp.media.view.UploaderStatus,
    8622     l10n = wp.media.view.l10n,
    8623     $ = jQuery,
    8624     Cropper;
    8625 
    8626 Cropper = View.extend({
    8627     className: 'crop-content',
    8628     template: wp.template('crop-content'),
     7171    $ = Backbone.$,
     7172    Settings;
     7173
     7174Settings = View.extend({
     7175    events: {
     7176        'click button':    'updateHandler',
     7177        'change input':    'updateHandler',
     7178        'change select':   'updateHandler',
     7179        'change textarea': 'updateHandler'
     7180    },
     7181
    86297182    initialize: function() {
    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     },
     7183        this.model = this.model || new Backbone.Model();
     7184        this.listenTo( this.model, 'change', this.updateChanges );
     7185    },
     7186
    86447187    prepare: function() {
    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 });
     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        }
    86677277    }
    86687278});
    86697279
    8670 module.exports = Cropper;
    8671 
    8672 
    8673 /***/ }),
    8674 /* 97 */
    8675 /***/ (function(module, exports) {
    8676 
     7280module.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 */
     7292var Settings = wp.media.view.Settings,
     7293    AttachmentDisplay;
     7294
     7295AttachmentDisplay = 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
     7374module.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 */
     7386var Gallery = wp.media.view.Settings.extend({
     7387    className: 'collection-settings gallery-settings',
     7388    template:  wp.template('gallery-settings')
     7389});
     7390
     7391module.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 */
     7403var Playlist = wp.media.view.Settings.extend({
     7404    className: 'collection-settings playlist-settings',
     7405    template:  wp.template('playlist-settings')
     7406});
     7407
     7408module.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 */
     7420var Sidebar = wp.media.view.PriorityList.extend({
     7421    className: 'media-sidebar'
     7422});
     7423
     7424module.exports = Sidebar;
     7425
     7426},{}],64:[function(require,module,exports){
    86777427/**
    86787428 * wp.media.view.SiteIconCropper
     
    87177467module.exports = SiteIconCropper;
    87187468
    8719 
    8720 /***/ }),
    8721 /* 98 */
    8722 /***/ (function(module, exports) {
    8723 
     7469},{}],65:[function(require,module,exports){
    87247470/**
    87257471 * wp.media.view.SiteIconPreview
     
    87777523module.exports = SiteIconPreview;
    87787524
    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  */
    8792 var View = wp.media.View,
    8793     EditImage;
    8794 
    8795 EditImage = 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 
    8837 module.exports = EditImage;
    8838 
    8839 
    8840 /***/ }),
    8841 /* 100 */
    8842 /***/ (function(module, exports) {
    8843 
     7525},{}],66:[function(require,module,exports){
    88447526/**
    88457527 * wp.media.view.Spinner
     
    88767558module.exports = Spinner;
    88777559
    8878 
    8879 /***/ })
    8880 /******/ ]));
     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 */
     7572var View = wp.media.View,
     7573    Toolbar;
     7574
     7575Toolbar = 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
     7718module.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 */
     7731var Select = wp.media.view.Toolbar.Select,
     7732    l10n = wp.media.view.l10n,
     7733    Embed;
     7734
     7735Embed = 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
     7755module.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 */
     7767var Toolbar = wp.media.view.Toolbar,
     7768    l10n = wp.media.view.l10n,
     7769    Select;
     7770
     7771Select = 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
     7825module.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 */
     7839var View = wp.media.View,
     7840    l10n = wp.media.view.l10n,
     7841    $ = jQuery,
     7842    EditorUploader;
     7843
     7844EditorUploader = 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
     8050module.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 */
     8063var View = wp.media.View,
     8064    UploaderInline;
     8065
     8066UploaderInline = 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
     8181module.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 */
     8192var UploaderStatusError = wp.media.View.extend({
     8193    className: 'upload-error',
     8194    template:  wp.template('uploader-status-error')
     8195});
     8196
     8197module.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 */
     8210var View = wp.media.View,
     8211    UploaderStatus;
     8212
     8213UploaderStatus = 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
     8335module.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 */
     8354var $ = jQuery,
     8355    UploaderWindow;
     8356
     8357UploaderWindow = 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
     8446module.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 */
     8465var 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
     8512module.exports = View;
     8513
     8514},{}]},{},[19]);
Note: See TracChangeset for help on using the changeset viewer.