Make WordPress Core

Changeset 50020 for branches/4.2


Ignore:
Timestamp:
01/25/2021 08:16:08 PM (6 years ago)
Author:
desrosj
Message:

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

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

Partially reverts [46500].
Fixes #52367.

Location:
branches/4.2/src/wp-includes/js
Files:
4 edited

Legend:

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

    r46500 r50020  
    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){
    702/*globals wp, _ */
    713
    … …  
    276208};
    277209
    278 media.model.PostMedia = __webpack_require__( 1 );
    279 media.controller.AudioDetails = __webpack_require__( 2 );
    280 media.controller.VideoDetails = __webpack_require__( 3 );
    281 media.view.MediaFrame.MediaDetails = __webpack_require__( 4 );
    282 media.view.MediaFrame.AudioDetails = __webpack_require__( 5 );
    283 media.view.MediaFrame.VideoDetails = __webpack_require__( 6 );
    284 media.view.MediaDetails = __webpack_require__( 7 );
    285 media.view.AudioDetails = __webpack_require__( 8 );
    286 media.view.VideoDetails = __webpack_require__( 9 );
    287 
    288 
    289 /***/ }),
    290 /* 1 */
    291 /***/ (function(module, exports) {
    292 
    293 /*globals wp, Backbone, _ */
    294 
    295 /**
    296  * wp.media.model.PostMedia
    297  *
    298  * Shared model class for audio and video. Updates the model after
    299  *   "Add Audio|Video Source" and "Replace Audio|Video" states return
    300  *
    301  * @class
    302  * @augments Backbone.Model
    303  */
    304 var PostMedia = Backbone.Model.extend({
    305         initialize: function() {
    306                 this.attachment = false;
    307         },
    308 
    309         setSource: function( attachment ) {
    310                 this.attachment = attachment;
    311                 this.extension = attachment.get( 'filename' ).split('.').pop();
    312 
    313                 if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
    314                         this.unset( 'src' );
    315                 }
    316 
    317                 if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
    318                         this.set( this.extension, this.attachment.get( 'url' ) );
    319                 } else {
    320                         this.unset( this.extension );
    321                 }
    322         },
    323 
    324         changeAttachment: function( attachment ) {
    325                 this.setSource( attachment );
    326 
    327                 this.unset( 'src' );
    328                 _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
    329                         this.unset( ext );
    330                 }, this );
    331         }
    332 });
    333 
    334 module.exports = PostMedia;
    335 
    336 
    337 /***/ }),
    338 /* 2 */
    339 /***/ (function(module, exports) {
    340 
     210media.model.PostMedia = require( './models/post-media.js' );
     211media.controller.AudioDetails = require( './controllers/audio-details.js' );
     212media.controller.VideoDetails = require( './controllers/video-details.js' );
     213media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' );
     214media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' );
     215media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' );
     216media.view.MediaDetails = require( './views/media-details.js' );
     217media.view.AudioDetails = require( './views/audio-details.js' );
     218media.view.VideoDetails = require( './views/video-details.js' );
     219
     220},{"./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){
    341221/*globals wp */
    342222
    … …  
    373253module.exports = AudioDetails;
    374254
    375 
    376 /***/ }),
    377 /* 3 */
    378 /***/ (function(module, exports) {
    379 
     255},{}],3:[function(require,module,exports){
    380256/*globals wp */
    381257
    … …  
    412288module.exports = VideoDetails;
    413289
    414 
    415 /***/ }),
    416 /* 4 */
    417 /***/ (function(module, exports) {
    418 
     290},{}],4:[function(require,module,exports){
     291/*globals wp, Backbone, _ */
     292
     293/**
     294 * wp.media.model.PostMedia
     295 *
     296 * Shared model class for audio and video. Updates the model after
     297 *   "Add Audio|Video Source" and "Replace Audio|Video" states return
     298 *
     299 * @class
     300 * @augments Backbone.Model
     301 */
     302var PostMedia = Backbone.Model.extend({
     303        initialize: function() {
     304                this.attachment = false;
     305        },
     306
     307        setSource: function( attachment ) {
     308                this.attachment = attachment;
     309                this.extension = attachment.get( 'filename' ).split('.').pop();
     310
     311                if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
     312                        this.unset( 'src' );
     313                }
     314
     315                if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
     316                        this.set( this.extension, this.attachment.get( 'url' ) );
     317                } else {
     318                        this.unset( this.extension );
     319                }
     320        },
     321
     322        changeAttachment: function( attachment ) {
     323                this.setSource( attachment );
     324
     325                this.unset( 'src' );
     326                _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
     327                        this.unset( ext );
     328                }, this );
     329        }
     330});
     331
     332module.exports = PostMedia;
     333
     334},{}],5:[function(require,module,exports){
     335/*globals wp */
     336
     337/**
     338 * wp.media.view.AudioDetails
     339 *
     340 * @class
     341 * @augments wp.media.view.MediaDetails
     342 * @augments wp.media.view.Settings.AttachmentDisplay
     343 * @augments wp.media.view.Settings
     344 * @augments wp.media.View
     345 * @augments wp.Backbone.View
     346 * @augments Backbone.View
     347 */
     348var MediaDetails = wp.media.view.MediaDetails,
     349        AudioDetails;
     350
     351AudioDetails = MediaDetails.extend({
     352        className: 'audio-details',
     353        template:  wp.template('audio-details'),
     354
     355        setMedia: function() {
     356                var audio = this.$('.wp-audio-shortcode');
     357
     358                if ( audio.find( 'source' ).length ) {
     359                        if ( audio.is(':hidden') ) {
     360                                audio.show();
     361                        }
     362                        this.media = MediaDetails.prepareSrc( audio.get(0) );
     363                } else {
     364                        audio.hide();
     365                        this.media = false;
     366                }
     367
     368                return this;
     369        }
     370});
     371
     372module.exports = AudioDetails;
     373
     374},{}],6:[function(require,module,exports){
     375/*globals wp */
     376
     377/**
     378 * wp.media.view.MediaFrame.AudioDetails
     379 *
     380 * @class
     381 * @augments wp.media.view.MediaFrame.MediaDetails
     382 * @augments wp.media.view.MediaFrame.Select
     383 * @augments wp.media.view.MediaFrame
     384 * @augments wp.media.view.Frame
     385 * @augments wp.media.View
     386 * @augments wp.Backbone.View
     387 * @augments Backbone.View
     388 * @mixes wp.media.controller.StateMachine
     389 */
     390var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
     391        MediaLibrary = wp.media.controller.MediaLibrary,
     392
     393        l10n = wp.media.view.l10n,
     394        AudioDetails;
     395
     396AudioDetails = MediaDetails.extend({
     397        defaults: {
     398                id:      'audio',
     399                url:     '',
     400                menu:    'audio-details',
     401                content: 'audio-details',
     402                toolbar: 'audio-details',
     403                type:    'link',
     404                title:    l10n.audioDetailsTitle,
     405                priority: 120
     406        },
     407
     408        initialize: function( options ) {
     409                options.DetailsView = wp.media.view.AudioDetails;
     410                options.cancelText = l10n.audioDetailsCancel;
     411                options.addText = l10n.audioAddSourceTitle;
     412
     413                MediaDetails.prototype.initialize.call( this, options );
     414        },
     415
     416        bindHandlers: function() {
     417                MediaDetails.prototype.bindHandlers.apply( this, arguments );
     418
     419                this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
     420                this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
     421        },
     422
     423        createStates: function() {
     424                this.states.add([
     425                        new wp.media.controller.AudioDetails( {
     426                                media: this.media
     427                        } ),
     428
     429                        new MediaLibrary( {
     430                                type: 'audio',
     431                                id: 'replace-audio',
     432                                title: l10n.audioReplaceTitle,
     433                                toolbar: 'replace-audio',
     434                                media: this.media,
     435                                menu: 'audio-details'
     436                        } ),
     437
     438                        new MediaLibrary( {
     439                                type: 'audio',
     440                                id: 'add-audio-source',
     441                                title: l10n.audioAddSourceTitle,
     442                                toolbar: 'add-audio-source',
     443                                media: this.media,
     444                                menu: false
     445                        } )
     446                ]);
     447        }
     448});
     449
     450module.exports = AudioDetails;
     451
     452},{}],7:[function(require,module,exports){
    419453/*globals wp */
    420454
    … …  
    548582module.exports = MediaDetails;
    549583
    550 
    551 /***/ }),
    552 /* 5 */
    553 /***/ (function(module, exports) {
    554 
    555 /*globals wp */
    556 
    557 /**
    558  * wp.media.view.MediaFrame.AudioDetails
    559  *
    560  * @class
    561  * @augments wp.media.view.MediaFrame.MediaDetails
    562  * @augments wp.media.view.MediaFrame.Select
    563  * @augments wp.media.view.MediaFrame
    564  * @augments wp.media.view.Frame
    565  * @augments wp.media.View
    566  * @augments wp.Backbone.View
    567  * @augments Backbone.View
    568  * @mixes wp.media.controller.StateMachine
    569  */
    570 var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
    571         MediaLibrary = wp.media.controller.MediaLibrary,
    572 
    573         l10n = wp.media.view.l10n,
    574         AudioDetails;
    575 
    576 AudioDetails = MediaDetails.extend({
    577         defaults: {
    578                 id:      'audio',
    579                 url:     '',
    580                 menu:    'audio-details',
    581                 content: 'audio-details',
    582                 toolbar: 'audio-details',
    583                 type:    'link',
    584                 title:    l10n.audioDetailsTitle,
    585                 priority: 120
    586         },
    587 
    588         initialize: function( options ) {
    589                 options.DetailsView = wp.media.view.AudioDetails;
    590                 options.cancelText = l10n.audioDetailsCancel;
    591                 options.addText = l10n.audioAddSourceTitle;
    592 
    593                 MediaDetails.prototype.initialize.call( this, options );
    594         },
    595 
    596         bindHandlers: function() {
    597                 MediaDetails.prototype.bindHandlers.apply( this, arguments );
    598 
    599                 this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
    600                 this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
    601         },
    602 
    603         createStates: function() {
    604                 this.states.add([
    605                         new wp.media.controller.AudioDetails( {
    606                                 media: this.media
    607                         } ),
    608 
    609                         new MediaLibrary( {
    610                                 type: 'audio',
    611                                 id: 'replace-audio',
    612                                 title: l10n.audioReplaceTitle,
    613                                 toolbar: 'replace-audio',
    614                                 media: this.media,
    615                                 menu: 'audio-details'
    616                         } ),
    617 
    618                         new MediaLibrary( {
    619                                 type: 'audio',
    620                                 id: 'add-audio-source',
    621                                 title: l10n.audioAddSourceTitle,
    622                                 toolbar: 'add-audio-source',
    623                                 media: this.media,
    624                                 menu: false
    625                         } )
    626                 ]);
    627         }
    628 });
    629 
    630 module.exports = AudioDetails;
    631 
    632 
    633 /***/ }),
    634 /* 6 */
    635 /***/ (function(module, exports) {
    636 
     584},{}],8:[function(require,module,exports){
    637585/*globals wp, _ */
    638586
    … …  
    771719module.exports = VideoDetails;
    772720
    773 
    774 /***/ }),
    775 /* 7 */
    776 /***/ (function(module, exports) {
    777 
     721},{}],9:[function(require,module,exports){
    778722/*global wp, jQuery, _, MediaElementPlayer */
    779723
    … …  
    943887module.exports = MediaDetails;
    944888
    945 
    946 /***/ }),
    947 /* 8 */
    948 /***/ (function(module, exports) {
    949 
    950 /*globals wp */
    951 
    952 /**
    953  * wp.media.view.AudioDetails
    954  *
    955  * @class
    956  * @augments wp.media.view.MediaDetails
    957  * @augments wp.media.view.Settings.AttachmentDisplay
    958  * @augments wp.media.view.Settings
    959  * @augments wp.media.View
    960  * @augments wp.Backbone.View
    961  * @augments Backbone.View
    962  */
    963 var MediaDetails = wp.media.view.MediaDetails,
    964         AudioDetails;
    965 
    966 AudioDetails = MediaDetails.extend({
    967         className: 'audio-details',
    968         template:  wp.template('audio-details'),
    969 
    970         setMedia: function() {
    971                 var audio = this.$('.wp-audio-shortcode');
    972 
    973                 if ( audio.find( 'source' ).length ) {
    974                         if ( audio.is(':hidden') ) {
    975                                 audio.show();
    976                         }
    977                         this.media = MediaDetails.prepareSrc( audio.get(0) );
    978                 } else {
    979                         audio.hide();
    980                         this.media = false;
    981                 }
    982 
    983                 return this;
    984         }
    985 });
    986 
    987 module.exports = AudioDetails;
    988 
    989 
    990 /***/ }),
    991 /* 9 */
    992 /***/ (function(module, exports) {
    993 
     889},{}],10:[function(require,module,exports){
    994890/*globals wp */
    995891
    … …  
    1036932module.exports = VideoDetails;
    1037933
    1038 
    1039 /***/ })
    1040 /******/ ]);
     934},{}]},{},[1]);
  • branches/4.2/src/wp-includes/js/media-grid.js

    r46500 r50020  
    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 /*globals wp */
    81 
    82 var media = wp.media;
    83 
    84 media.controller.EditAttachmentMetadata = __webpack_require__( 11 );
    85 media.view.MediaFrame.Manage = __webpack_require__( 12 );
    86 media.view.Attachment.Details.TwoColumn = __webpack_require__( 13 );
    87 media.view.MediaFrame.Manage.Router = __webpack_require__( 14 );
    88 media.view.EditImage.Details = __webpack_require__( 15 );
    89 media.view.MediaFrame.EditAttachments = __webpack_require__( 16 );
    90 media.view.SelectModeToggleButton = __webpack_require__( 17 );
    91 media.view.DeleteSelectedButton = __webpack_require__( 18 );
    92 media.view.DeleteSelectedPermanentlyButton = __webpack_require__( 19 );
    93 
    94 
    95 /***/ }),
    96 /* 11 */
    97 /***/ (function(module, exports) {
    98 
     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){
    992/*globals wp */
    1003
    … …  
    12629module.exports = EditAttachmentMetadata;
    12730
    128 
    129 /***/ }),
    130 /* 12 */
    131 /***/ (function(module, exports) {
    132 
     31},{}],2:[function(require,module,exports){
     32/*globals wp */
     33
     34var media = wp.media;
     35
     36media.controller.EditAttachmentMetadata = require( './controllers/edit-attachment-metadata.js' );
     37media.view.MediaFrame.Manage = require( './views/frame/manage.js' );
     38media.view.Attachment.Details.TwoColumn = require( './views/attachment/details-two-column.js' );
     39media.view.MediaFrame.Manage.Router = require( './routers/manage.js' );
     40media.view.EditImage.Details = require( './views/edit-image-details.js' );
     41media.view.MediaFrame.EditAttachments = require( './views/frame/edit-attachments.js' );
     42media.view.SelectModeToggleButton = require( './views/button/select-mode-toggle.js' );
     43media.view.DeleteSelectedButton = require( './views/button/delete-selected.js' );
     44media.view.DeleteSelectedPermanentlyButton = require( './views/button/delete-selected-permanently.js' );
     45
     46},{"./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){
     47/*globals wp, Backbone */
     48
     49/**
     50 * wp.media.view.MediaFrame.Manage.Router
     51 *
     52 * A router for handling the browser history and application state.
     53 *
     54 * @class
     55 * @augments Backbone.Router
     56 */
     57var Router = Backbone.Router.extend({
     58        routes: {
     59                'upload.php?item=:slug':    'showItem',
     60                'upload.php?search=:query': 'search'
     61        },
     62
     63        // Map routes against the page URL
     64        baseUrl: function( url ) {
     65                return 'upload.php' + url;
     66        },
     67
     68        // Respond to the search route by filling the search field and trigggering the input event
     69        search: function( query ) {
     70                jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
     71        },
     72
     73        // Show the modal with a specific item
     74        showItem: function( query ) {
     75                var media = wp.media,
     76                        library = media.frame.state().get('library'),
     77                        item;
     78
     79                // Trigger the media frame to open the correct item
     80                item = library.findWhere( { id: parseInt( query, 10 ) } );
     81                if ( item ) {
     82                        media.frame.trigger( 'edit:attachment', item );
     83                } else {
     84                        item = media.attachment( query );
     85                        media.frame.listenTo( item, 'change', function( model ) {
     86                                media.frame.stopListening( item );
     87                                media.frame.trigger( 'edit:attachment', model );
     88                        } );
     89                        item.fetch();
     90                }
     91        }
     92});
     93
     94module.exports = Router;
     95
     96},{}],4:[function(require,module,exports){
     97/*globals wp */
     98
     99/**
     100 * wp.media.view.Attachment.Details.TwoColumn
     101 *
     102 * A similar view to media.view.Attachment.Details
     103 * for use in the Edit Attachment modal.
     104 *
     105 * @class
     106 * @augments wp.media.view.Attachment.Details
     107 * @augments wp.media.view.Attachment
     108 * @augments wp.media.View
     109 * @augments wp.Backbone.View
     110 * @augments Backbone.View
     111 */
     112var Details = wp.media.view.Attachment.Details,
     113        TwoColumn;
     114
     115TwoColumn = Details.extend({
     116        template: wp.template( 'attachment-details-two-column' ),
     117
     118        editAttachment: function( event ) {
     119                event.preventDefault();
     120                this.controller.content.mode( 'edit-image' );
     121        },
     122
     123        /**
     124         * Noop this from parent class, doesn't apply here.
     125         */
     126        toggleSelectionHandler: function() {},
     127
     128        render: function() {
     129                Details.prototype.render.apply( this, arguments );
     130
     131                wp.media.mixin.removeAllPlayers();
     132                this.$( 'audio, video' ).each( function (i, elem) {
     133                        var el = wp.media.view.MediaDetails.prepareSrc( elem );
     134                        new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
     135                } );
     136        }
     137});
     138
     139module.exports = TwoColumn;
     140
     141},{}],5:[function(require,module,exports){
     142/*globals wp */
     143
     144/**
     145 * wp.media.view.DeleteSelectedPermanentlyButton
     146 *
     147 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
     148 *
     149 * @class
     150 * @augments wp.media.view.DeleteSelectedButton
     151 * @augments wp.media.view.Button
     152 * @augments wp.media.View
     153 * @augments wp.Backbone.View
     154 * @augments Backbone.View
     155 */
     156var Button = wp.media.view.Button,
     157        DeleteSelected = wp.media.view.DeleteSelectedButton,
     158        DeleteSelectedPermanently;
     159
     160DeleteSelectedPermanently = DeleteSelected.extend({
     161        initialize: function() {
     162                DeleteSelected.prototype.initialize.apply( this, arguments );
     163                this.listenTo( this.controller, 'select:activate', this.selectActivate );
     164                this.listenTo( this.controller, 'select:deactivate', this.selectDeactivate );
     165        },
     166
     167        filterChange: function( model ) {
     168                this.canShow = ( 'trash' === model.get( 'status' ) );
     169        },
     170
     171        selectActivate: function() {
     172                this.toggleDisabled();
     173                this.$el.toggleClass( 'hidden', ! this.canShow );
     174        },
     175
     176        selectDeactivate: function() {
     177                this.toggleDisabled();
     178                this.$el.addClass( 'hidden' );
     179        },
     180
     181        render: function() {
     182                Button.prototype.render.apply( this, arguments );
     183                this.selectActivate();
     184                return this;
     185        }
     186});
     187
     188module.exports = DeleteSelectedPermanently;
     189
     190},{}],6:[function(require,module,exports){
     191/*globals wp */
     192
     193/**
     194 * wp.media.view.DeleteSelectedButton
     195 *
     196 * A button that handles bulk Delete/Trash logic
     197 *
     198 * @class
     199 * @augments wp.media.view.Button
     200 * @augments wp.media.View
     201 * @augments wp.Backbone.View
     202 * @augments Backbone.View
     203 */
     204var Button = wp.media.view.Button,
     205        l10n = wp.media.view.l10n,
     206        DeleteSelected;
     207
     208DeleteSelected = Button.extend({
     209        initialize: function() {
     210                Button.prototype.initialize.apply( this, arguments );
     211                if ( this.options.filters ) {
     212                        this.listenTo( this.options.filters.model, 'change', this.filterChange );
     213                }
     214                this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled );
     215        },
     216
     217        filterChange: function( model ) {
     218                if ( 'trash' === model.get( 'status' ) ) {
     219                        this.model.set( 'text', l10n.untrashSelected );
     220                } else if ( wp.media.view.settings.mediaTrash ) {
     221                        this.model.set( 'text', l10n.trashSelected );
     222                } else {
     223                        this.model.set( 'text', l10n.deleteSelected );
     224                }
     225        },
     226
     227        toggleDisabled: function() {
     228                this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
     229        },
     230
     231        render: function() {
     232                Button.prototype.render.apply( this, arguments );
     233                if ( this.controller.isModeActive( 'select' ) ) {
     234                        this.$el.addClass( 'delete-selected-button' );
     235                } else {
     236                        this.$el.addClass( 'delete-selected-button hidden' );
     237                }
     238                this.toggleDisabled();
     239                return this;
     240        }
     241});
     242
     243module.exports = DeleteSelected;
     244
     245},{}],7:[function(require,module,exports){
     246/*globals wp */
     247
     248/**
     249 * wp.media.view.SelectModeToggleButton
     250 *
     251 * @class
     252 * @augments wp.media.view.Button
     253 * @augments wp.media.View
     254 * @augments wp.Backbone.View
     255 * @augments Backbone.View
     256 */
     257var Button = wp.media.view.Button,
     258        l10n = wp.media.view.l10n,
     259        SelectModeToggle;
     260
     261SelectModeToggle = Button.extend({
     262        initialize: function() {
     263                Button.prototype.initialize.apply( this, arguments );
     264                this.listenTo( this.controller, 'select:activate select:deactivate', this.toggleBulkEditHandler );
     265                this.listenTo( this.controller, 'selection:action:done', this.back );
     266        },
     267
     268        back: function () {
     269                this.controller.deactivateMode( 'select' ).activateMode( 'edit' );
     270        },
     271
     272        click: function() {
     273                Button.prototype.click.apply( this, arguments );
     274                if ( this.controller.isModeActive( 'select' ) ) {
     275                        this.back();
     276                } else {
     277                        this.controller.deactivateMode( 'edit' ).activateMode( 'select' );
     278                }
     279        },
     280
     281        render: function() {
     282                Button.prototype.render.apply( this, arguments );
     283                this.$el.addClass( 'select-mode-toggle-button' );
     284                return this;
     285        },
     286
     287        toggleBulkEditHandler: function() {
     288                var toolbar = this.controller.content.get().toolbar, children;
     289
     290                children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' );
     291
     292                // TODO: the Frame should be doing all of this.
     293                if ( this.controller.isModeActive( 'select' ) ) {
     294                        this.model.set( 'text', l10n.cancelSelection );
     295                        children.not( '.media-button' ).hide();
     296                        this.$el.show();
     297                        toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' );
     298                } else {
     299                        this.model.set( 'text', l10n.bulkSelect );
     300                        this.controller.content.get().$el.removeClass( 'fixed' );
     301                        toolbar.$el.css( 'width', '' );
     302                        toolbar.$( '.delete-selected-button' ).addClass( 'hidden' );
     303                        children.not( '.spinner, .media-button' ).show();
     304                        this.controller.state().get( 'selection' ).reset();
     305                }
     306        }
     307});
     308
     309module.exports = SelectModeToggle;
     310
     311},{}],8:[function(require,module,exports){
     312/*globals wp, _ */
     313
     314/**
     315 * wp.media.view.EditImage.Details
     316 *
     317 * @class
     318 * @augments wp.media.view.EditImage
     319 * @augments wp.media.View
     320 * @augments wp.Backbone.View
     321 * @augments Backbone.View
     322 */
     323var View = wp.media.View,
     324        EditImage = wp.media.view.EditImage,
     325        Details;
     326
     327Details = EditImage.extend({
     328        initialize: function( options ) {
     329                this.editor = window.imageEdit;
     330                this.frame = options.frame;
     331                this.controller = options.controller;
     332                View.prototype.initialize.apply( this, arguments );
     333        },
     334
     335        back: function() {
     336                this.frame.content.mode( 'edit-metadata' );
     337        },
     338
     339        save: function() {
     340                this.model.fetch().done( _.bind( function() {
     341                        this.frame.content.mode( 'edit-metadata' );
     342                }, this ) );
     343        }
     344});
     345
     346module.exports = Details;
     347
     348},{}],9:[function(require,module,exports){
     349/*globals wp, _, jQuery */
     350
     351/**
     352 * wp.media.view.MediaFrame.EditAttachments
     353 *
     354 * A frame for editing the details of a specific media item.
     355 *
     356 * Opens in a modal by default.
     357 *
     358 * Requires an attachment model to be passed in the options hash under `model`.
     359 *
     360 * @class
     361 * @augments wp.media.view.Frame
     362 * @augments wp.media.View
     363 * @augments wp.Backbone.View
     364 * @augments Backbone.View
     365 * @mixes wp.media.controller.StateMachine
     366 */
     367var Frame = wp.media.view.Frame,
     368        MediaFrame = wp.media.view.MediaFrame,
     369
     370        $ = jQuery,
     371        EditAttachments;
     372
     373EditAttachments = MediaFrame.extend({
     374
     375        className: 'edit-attachment-frame',
     376        template:  wp.template( 'edit-attachment-frame' ),
     377        regions:   [ 'title', 'content' ],
     378
     379        events: {
     380                'click .left':  'previousMediaItem',
     381                'click .right': 'nextMediaItem'
     382        },
     383
     384        initialize: function() {
     385                Frame.prototype.initialize.apply( this, arguments );
     386
     387                _.defaults( this.options, {
     388                        modal: true,
     389                        state: 'edit-attachment'
     390                });
     391
     392                this.controller = this.options.controller;
     393                this.gridRouter = this.controller.gridRouter;
     394                this.library = this.options.library;
     395
     396                if ( this.options.model ) {
     397                        this.model = this.options.model;
     398                }
     399
     400                this.bindHandlers();
     401                this.createStates();
     402                this.createModal();
     403
     404                this.title.mode( 'default' );
     405                this.toggleNav();
     406        },
     407
     408        bindHandlers: function() {
     409                // Bind default title creation.
     410                this.on( 'title:create:default', this.createTitle, this );
     411
     412                // Close the modal if the attachment is deleted.
     413                this.listenTo( this.model, 'change:status destroy', this.close, this );
     414
     415                this.on( 'content:create:edit-metadata', this.editMetadataMode, this );
     416                this.on( 'content:create:edit-image', this.editImageMode, this );
     417                this.on( 'content:render:edit-image', this.editImageModeRender, this );
     418                this.on( 'close', this.detach );
     419        },
     420
     421        createModal: function() {
     422                // Initialize modal container view.
     423                if ( this.options.modal ) {
     424                        this.modal = new wp.media.view.Modal({
     425                                controller: this,
     426                                title:      this.options.title
     427                        });
     428
     429                        this.modal.on( 'open', _.bind( function () {
     430                                $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) );
     431                        }, this ) );
     432
     433                        // Completely destroy the modal DOM element when closing it.
     434                        this.modal.on( 'close', _.bind( function() {
     435                                this.modal.remove();
     436                                $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
     437                                // Restore the original focus item if possible
     438                                $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus();
     439                                this.resetRoute();
     440                        }, this ) );
     441
     442                        // Set this frame as the modal's content.
     443                        this.modal.content( this );
     444                        this.modal.open();
     445                }
     446        },
     447
     448        /**
     449         * Add the default states to the frame.
     450         */
     451        createStates: function() {
     452                this.states.add([
     453                        new wp.media.controller.EditAttachmentMetadata( { model: this.model } )
     454                ]);
     455        },
     456
     457        /**
     458         * Content region rendering callback for the `edit-metadata` mode.
     459         *
     460         * @param {Object} contentRegion Basic object with a `view` property, which
     461         *                               should be set with the proper region view.
     462         */
     463        editMetadataMode: function( contentRegion ) {
     464                contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({
     465                        controller: this,
     466                        model:      this.model
     467                });
     468
     469                /**
     470                 * Attach a subview to display fields added via the
     471                 * `attachment_fields_to_edit` filter.
     472                 */
     473                contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({
     474                        controller: this,
     475                        model:      this.model
     476                }) );
     477
     478                // Update browser url when navigating media details
     479                if ( this.model ) {
     480                        this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
     481                }
     482        },
     483
     484        /**
     485         * Render the EditImage view into the frame's content region.
     486         *
     487         * @param {Object} contentRegion Basic object with a `view` property, which
     488         *                               should be set with the proper region view.
     489         */
     490        editImageMode: function( contentRegion ) {
     491                var editImageController = new wp.media.controller.EditImage( {
     492                        model: this.model,
     493                        frame: this
     494                } );
     495                // Noop some methods.
     496                editImageController._toolbar = function() {};
     497                editImageController._router = function() {};
     498                editImageController._menu = function() {};
     499
     500                contentRegion.view = new wp.media.view.EditImage.Details( {
     501                        model: this.model,
     502                        frame: this,
     503                        controller: editImageController
     504                } );
     505        },
     506
     507        editImageModeRender: function( view ) {
     508                view.on( 'ready', view.loadEditor );
     509        },
     510
     511        toggleNav: function() {
     512                this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() );
     513                this.$('.right').toggleClass( 'disabled', ! this.hasNext() );
     514        },
     515
     516        /**
     517         * Rerender the view.
     518         */
     519        rerender: function() {
     520                // Only rerender the `content` region.
     521                if ( this.content.mode() !== 'edit-metadata' ) {
     522                        this.content.mode( 'edit-metadata' );
     523                } else {
     524                        this.content.render();
     525                }
     526
     527                this.toggleNav();
     528        },
     529
     530        /**
     531         * Click handler to switch to the previous media item.
     532         */
     533        previousMediaItem: function() {
     534                if ( ! this.hasPrevious() ) {
     535                        this.$( '.left' ).blur();
     536                        return;
     537                }
     538                this.model = this.library.at( this.getCurrentIndex() - 1 );
     539                this.rerender();
     540                this.$( '.left' ).focus();
     541        },
     542
     543        /**
     544         * Click handler to switch to the next media item.
     545         */
     546        nextMediaItem: function() {
     547                if ( ! this.hasNext() ) {
     548                        this.$( '.right' ).blur();
     549                        return;
     550                }
     551                this.model = this.library.at( this.getCurrentIndex() + 1 );
     552                this.rerender();
     553                this.$( '.right' ).focus();
     554        },
     555
     556        getCurrentIndex: function() {
     557                return this.library.indexOf( this.model );
     558        },
     559
     560        hasNext: function() {
     561                return ( this.getCurrentIndex() + 1 ) < this.library.length;
     562        },
     563
     564        hasPrevious: function() {
     565                return ( this.getCurrentIndex() - 1 ) > -1;
     566        },
     567        /**
     568         * Respond to the keyboard events: right arrow, left arrow, except when
     569         * focus is in a textarea or input field.
     570         */
     571        keyEvent: function( event ) {
     572                if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) {
     573                        return;
     574                }
     575
     576                // The right arrow key
     577                if ( 39 === event.keyCode ) {
     578                        this.nextMediaItem();
     579                }
     580                // The left arrow key
     581                if ( 37 === event.keyCode ) {
     582                        this.previousMediaItem();
     583                }
     584        },
     585
     586        resetRoute: function() {
     587                this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) );
     588        }
     589});
     590
     591module.exports = EditAttachments;
     592
     593},{}],10:[function(require,module,exports){
    133594/*globals wp, _, Backbone */
    134595
    … …  
    400861module.exports = Manage;
    401862
    402 
    403 /***/ }),
    404 /* 13 */
    405 /***/ (function(module, exports) {
    406 
    407 /*globals wp */
    408 
    409 /**
    410  * wp.media.view.Attachment.Details.TwoColumn
    411  *
    412  * A similar view to media.view.Attachment.Details
    413  * for use in the Edit Attachment modal.
    414  *
    415  * @class
    416  * @augments wp.media.view.Attachment.Details
    417  * @augments wp.media.view.Attachment
    418  * @augments wp.media.View
    419  * @augments wp.Backbone.View
    420  * @augments Backbone.View
    421  */
    422 var Details = wp.media.view.Attachment.Details,
    423         TwoColumn;
    424 
    425 TwoColumn = Details.extend({
    426         template: wp.template( 'attachment-details-two-column' ),
    427 
    428         editAttachment: function( event ) {
    429                 event.preventDefault();
    430                 this.controller.content.mode( 'edit-image' );
    431         },
    432 
    433         /**
    434          * Noop this from parent class, doesn't apply here.
    435          */
    436         toggleSelectionHandler: function() {},
    437 
    438         render: function() {
    439                 Details.prototype.render.apply( this, arguments );
    440 
    441                 wp.media.mixin.removeAllPlayers();
    442                 this.$( 'audio, video' ).each( function (i, elem) {
    443                         var el = wp.media.view.MediaDetails.prepareSrc( elem );
    444                         new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
    445                 } );
    446         }
    447 });
    448 
    449 module.exports = TwoColumn;
    450 
    451 
    452 /***/ }),
    453 /* 14 */
    454 /***/ (function(module, exports) {
    455 
    456 /*globals wp, Backbone */
    457 
    458 /**
    459  * wp.media.view.MediaFrame.Manage.Router
    460  *
    461  * A router for handling the browser history and application state.
    462  *
    463  * @class
    464  * @augments Backbone.Router
    465  */
    466 var Router = Backbone.Router.extend({
    467         routes: {
    468                 'upload.php?item=:slug':    'showItem',
    469                 'upload.php?search=:query': 'search'
    470         },
    471 
    472         // Map routes against the page URL
    473         baseUrl: function( url ) {
    474                 return 'upload.php' + url;
    475         },
    476 
    477         // Respond to the search route by filling the search field and trigggering the input event
    478         search: function( query ) {
    479                 jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
    480         },
    481 
    482         // Show the modal with a specific item
    483         showItem: function( query ) {
    484                 var media = wp.media,
    485                         library = media.frame.state().get('library'),
    486                         item;
    487 
    488                 // Trigger the media frame to open the correct item
    489                 item = library.findWhere( { id: parseInt( query, 10 ) } );
    490                 if ( item ) {
    491                         media.frame.trigger( 'edit:attachment', item );
    492                 } else {
    493                         item = media.attachment( query );
    494                         media.frame.listenTo( item, 'change', function( model ) {
    495                                 media.frame.stopListening( item );
    496                                 media.frame.trigger( 'edit:attachment', model );
    497                         } );
    498                         item.fetch();
    499                 }
    500         }
    501 });
    502 
    503 module.exports = Router;
    504 
    505 
    506 /***/ }),
    507 /* 15 */
    508 /***/ (function(module, exports) {
    509 
    510 /*globals wp, _ */
    511 
    512 /**
    513  * wp.media.view.EditImage.Details
    514  *
    515  * @class
    516  * @augments wp.media.view.EditImage
    517  * @augments wp.media.View
    518  * @augments wp.Backbone.View
    519  * @augments Backbone.View
    520  */
    521 var View = wp.media.View,
    522         EditImage = wp.media.view.EditImage,
    523         Details;
    524 
    525 Details = EditImage.extend({
    526         initialize: function( options ) {
    527                 this.editor = window.imageEdit;
    528                 this.frame = options.frame;
    529                 this.controller = options.controller;
    530                 View.prototype.initialize.apply( this, arguments );
    531         },
    532 
    533         back: function() {
    534                 this.frame.content.mode( 'edit-metadata' );
    535         },
    536 
    537         save: function() {
    538                 this.model.fetch().done( _.bind( function() {
    539                         this.frame.content.mode( 'edit-metadata' );
    540                 }, this ) );
    541         }
    542 });
    543 
    544 module.exports = Details;
    545 
    546 
    547 /***/ }),
    548 /* 16 */
    549 /***/ (function(module, exports) {
    550 
    551 /*globals wp, _, jQuery */
    552 
    553 /**
    554  * wp.media.view.MediaFrame.EditAttachments
    555  *
    556  * A frame for editing the details of a specific media item.
    557  *
    558  * Opens in a modal by default.
    559  *
    560  * Requires an attachment model to be passed in the options hash under `model`.
    561  *
    562  * @class
    563  * @augments wp.media.view.Frame
    564  * @augments wp.media.View
    565  * @augments wp.Backbone.View
    566  * @augments Backbone.View
    567  * @mixes wp.media.controller.StateMachine
    568  */
    569 var Frame = wp.media.view.Frame,
    570         MediaFrame = wp.media.view.MediaFrame,
    571 
    572         $ = jQuery,
    573         EditAttachments;
    574 
    575 EditAttachments = MediaFrame.extend({
    576 
    577         className: 'edit-attachment-frame',
    578         template:  wp.template( 'edit-attachment-frame' ),
    579         regions:   [ 'title', 'content' ],
    580 
    581         events: {
    582                 'click .left':  'previousMediaItem',
    583                 'click .right': 'nextMediaItem'
    584         },
    585 
    586         initialize: function() {
    587                 Frame.prototype.initialize.apply( this, arguments );
    588 
    589                 _.defaults( this.options, {
    590                         modal: true,
    591                         state: 'edit-attachment'
    592                 });
    593 
    594                 this.controller = this.options.controller;
    595                 this.gridRouter = this.controller.gridRouter;
    596                 this.library = this.options.library;
    597 
    598                 if ( this.options.model ) {
    599                         this.model = this.options.model;
    600                 }
    601 
    602                 this.bindHandlers();
    603                 this.createStates();
    604                 this.createModal();
    605 
    606                 this.title.mode( 'default' );
    607                 this.toggleNav();
    608         },
    609 
    610         bindHandlers: function() {
    611                 // Bind default title creation.
    612                 this.on( 'title:create:default', this.createTitle, this );
    613 
    614                 // Close the modal if the attachment is deleted.
    615                 this.listenTo( this.model, 'change:status destroy', this.close, this );
    616 
    617                 this.on( 'content:create:edit-metadata', this.editMetadataMode, this );
    618                 this.on( 'content:create:edit-image', this.editImageMode, this );
    619                 this.on( 'content:render:edit-image', this.editImageModeRender, this );
    620                 this.on( 'close', this.detach );
    621         },
    622 
    623         createModal: function() {
    624                 // Initialize modal container view.
    625                 if ( this.options.modal ) {
    626                         this.modal = new wp.media.view.Modal({
    627                                 controller: this,
    628                                 title:      this.options.title
    629                         });
    630 
    631                         this.modal.on( 'open', _.bind( function () {
    632                                 $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) );
    633                         }, this ) );
    634 
    635                         // Completely destroy the modal DOM element when closing it.
    636                         this.modal.on( 'close', _.bind( function() {
    637                                 this.modal.remove();
    638                                 $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
    639                                 // Restore the original focus item if possible
    640                                 $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus();
    641                                 this.resetRoute();
    642                         }, this ) );
    643 
    644                         // Set this frame as the modal's content.
    645                         this.modal.content( this );
    646                         this.modal.open();
    647                 }
    648         },
    649 
    650         /**
    651          * Add the default states to the frame.
    652          */
    653         createStates: function() {
    654                 this.states.add([
    655                         new wp.media.controller.EditAttachmentMetadata( { model: this.model } )
    656                 ]);
    657         },
    658 
    659         /**
    660          * Content region rendering callback for the `edit-metadata` mode.
    661          *
    662          * @param {Object} contentRegion Basic object with a `view` property, which
    663          *                               should be set with the proper region view.
    664          */
    665         editMetadataMode: function( contentRegion ) {
    666                 contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({
    667                         controller: this,
    668                         model:      this.model
    669                 });
    670 
    671                 /**
    672                  * Attach a subview to display fields added via the
    673                  * `attachment_fields_to_edit` filter.
    674                  */
    675                 contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({
    676                         controller: this,
    677                         model:      this.model
    678                 }) );
    679 
    680                 // Update browser url when navigating media details
    681                 if ( this.model ) {
    682                         this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
    683                 }
    684         },
    685 
    686         /**
    687          * Render the EditImage view into the frame's content region.
    688          *
    689          * @param {Object} contentRegion Basic object with a `view` property, which
    690          *                               should be set with the proper region view.
    691          */
    692         editImageMode: function( contentRegion ) {
    693                 var editImageController = new wp.media.controller.EditImage( {
    694                         model: this.model,
    695                         frame: this
    696                 } );
    697                 // Noop some methods.
    698                 editImageController._toolbar = function() {};
    699                 editImageController._router = function() {};
    700                 editImageController._menu = function() {};
    701 
    702                 contentRegion.view = new wp.media.view.EditImage.Details( {
    703                         model: this.model,
    704                         frame: this,
    705                         controller: editImageController
    706                 } );
    707         },
    708 
    709         editImageModeRender: function( view ) {
    710                 view.on( 'ready', view.loadEditor );
    711         },
    712 
    713         toggleNav: function() {
    714                 this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() );
    715                 this.$('.right').toggleClass( 'disabled', ! this.hasNext() );
    716         },
    717 
    718         /**
    719          * Rerender the view.
    720          */
    721         rerender: function() {
    722                 // Only rerender the `content` region.
    723                 if ( this.content.mode() !== 'edit-metadata' ) {
    724                         this.content.mode( 'edit-metadata' );
    725                 } else {
    726                         this.content.render();
    727                 }
    728 
    729                 this.toggleNav();
    730         },
    731 
    732         /**
    733          * Click handler to switch to the previous media item.
    734          */
    735         previousMediaItem: function() {
    736                 if ( ! this.hasPrevious() ) {
    737                         this.$( '.left' ).blur();
    738                         return;
    739                 }
    740                 this.model = this.library.at( this.getCurrentIndex() - 1 );
    741                 this.rerender();
    742                 this.$( '.left' ).focus();
    743         },
    744 
    745         /**
    746          * Click handler to switch to the next media item.
    747          */
    748         nextMediaItem: function() {
    749                 if ( ! this.hasNext() ) {
    750                         this.$( '.right' ).blur();
    751                         return;
    752                 }
    753                 this.model = this.library.at( this.getCurrentIndex() + 1 );
    754                 this.rerender();
    755                 this.$( '.right' ).focus();
    756         },
    757 
    758         getCurrentIndex: function() {
    759                 return this.library.indexOf( this.model );
    760         },
    761 
    762         hasNext: function() {
    763                 return ( this.getCurrentIndex() + 1 ) < this.library.length;
    764         },
    765 
    766         hasPrevious: function() {
    767                 return ( this.getCurrentIndex() - 1 ) > -1;
    768         },
    769         /**
    770          * Respond to the keyboard events: right arrow, left arrow, except when
    771          * focus is in a textarea or input field.
    772          */
    773         keyEvent: function( event ) {
    774                 if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) {
    775                         return;
    776                 }
    777 
    778                 // The right arrow key
    779                 if ( 39 === event.keyCode ) {
    780                         this.nextMediaItem();
    781                 }
    782                 // The left arrow key
    783                 if ( 37 === event.keyCode ) {
    784                         this.previousMediaItem();
    785                 }
    786         },
    787 
    788         resetRoute: function() {
    789                 this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) );
    790         }
    791 });
    792 
    793 module.exports = EditAttachments;
    794 
    795 
    796 /***/ }),
    797 /* 17 */
    798 /***/ (function(module, exports) {
    799 
    800 /*globals wp */
    801 
    802 /**
    803  * wp.media.view.SelectModeToggleButton
    804  *
    805  * @class
    806  * @augments wp.media.view.Button
    807  * @augments wp.media.View
    808  * @augments wp.Backbone.View
    809  * @augments Backbone.View
    810  */
    811 var Button = wp.media.view.Button,
    812         l10n = wp.media.view.l10n,
    813         SelectModeToggle;
    814 
    815 SelectModeToggle = Button.extend({
    816         initialize: function() {
    817                 Button.prototype.initialize.apply( this, arguments );
    818                 this.listenTo( this.controller, 'select:activate select:deactivate', this.toggleBulkEditHandler );
    819                 this.listenTo( this.controller, 'selection:action:done', this.back );
    820         },
    821 
    822         back: function () {
    823                 this.controller.deactivateMode( 'select' ).activateMode( 'edit' );
    824         },
    825 
    826         click: function() {
    827                 Button.prototype.click.apply( this, arguments );
    828                 if ( this.controller.isModeActive( 'select' ) ) {
    829                         this.back();
    830                 } else {
    831                         this.controller.deactivateMode( 'edit' ).activateMode( 'select' );
    832                 }
    833         },
    834 
    835         render: function() {
    836                 Button.prototype.render.apply( this, arguments );
    837                 this.$el.addClass( 'select-mode-toggle-button' );
    838                 return this;
    839         },
    840 
    841         toggleBulkEditHandler: function() {
    842                 var toolbar = this.controller.content.get().toolbar, children;
    843 
    844                 children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' );
    845 
    846                 // TODO: the Frame should be doing all of this.
    847                 if ( this.controller.isModeActive( 'select' ) ) {
    848                         this.model.set( 'text', l10n.cancelSelection );
    849                         children.not( '.media-button' ).hide();
    850                         this.$el.show();
    851                         toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' );
    852                 } else {
    853                         this.model.set( 'text', l10n.bulkSelect );
    854                         this.controller.content.get().$el.removeClass( 'fixed' );
    855                         toolbar.$el.css( 'width', '' );
    856                         toolbar.$( '.delete-selected-button' ).addClass( 'hidden' );
    857                         children.not( '.spinner, .media-button' ).show();
    858                         this.controller.state().get( 'selection' ).reset();
    859                 }
    860         }
    861 });
    862 
    863 module.exports = SelectModeToggle;
    864 
    865 
    866 /***/ }),
    867 /* 18 */
    868 /***/ (function(module, exports) {
    869 
    870 /*globals wp */
    871 
    872 /**
    873  * wp.media.view.DeleteSelectedButton
    874  *
    875  * A button that handles bulk Delete/Trash logic
    876  *
    877  * @class
    878  * @augments wp.media.view.Button
    879  * @augments wp.media.View
    880  * @augments wp.Backbone.View
    881  * @augments Backbone.View
    882  */
    883 var Button = wp.media.view.Button,
    884         l10n = wp.media.view.l10n,
    885         DeleteSelected;
    886 
    887 DeleteSelected = Button.extend({
    888         initialize: function() {
    889                 Button.prototype.initialize.apply( this, arguments );
    890                 if ( this.options.filters ) {
    891                         this.listenTo( this.options.filters.model, 'change', this.filterChange );
    892                 }
    893                 this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled );
    894         },
    895 
    896         filterChange: function( model ) {
    897                 if ( 'trash' === model.get( 'status' ) ) {
    898                         this.model.set( 'text', l10n.untrashSelected );
    899                 } else if ( wp.media.view.settings.mediaTrash ) {
    900                         this.model.set( 'text', l10n.trashSelected );
    901                 } else {
    902                         this.model.set( 'text', l10n.deleteSelected );
    903                 }
    904         },
    905 
    906         toggleDisabled: function() {
    907                 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
    908         },
    909 
    910         render: function() {
    911                 Button.prototype.render.apply( this, arguments );
    912                 if ( this.controller.isModeActive( 'select' ) ) {
    913                         this.$el.addClass( 'delete-selected-button' );
    914                 } else {
    915                         this.$el.addClass( 'delete-selected-button hidden' );
    916                 }
    917                 this.toggleDisabled();
    918                 return this;
    919         }
    920 });
    921 
    922 module.exports = DeleteSelected;
    923 
    924 
    925 /***/ }),
    926 /* 19 */
    927 /***/ (function(module, exports) {
    928 
    929 /*globals wp */
    930 
    931 /**
    932  * wp.media.view.DeleteSelectedPermanentlyButton
    933  *
    934  * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
    935  *
    936  * @class
    937  * @augments wp.media.view.DeleteSelectedButton
    938  * @augments wp.media.view.Button
    939  * @augments wp.media.View
    940  * @augments wp.Backbone.View
    941  * @augments Backbone.View
    942  */
    943 var Button = wp.media.view.Button,
    944         DeleteSelected = wp.media.view.DeleteSelectedButton,
    945         DeleteSelectedPermanently;
    946 
    947 DeleteSelectedPermanently = DeleteSelected.extend({
    948         initialize: function() {
    949                 DeleteSelected.prototype.initialize.apply( this, arguments );
    950                 this.listenTo( this.controller, 'select:activate', this.selectActivate );
    951                 this.listenTo( this.controller, 'select:deactivate', this.selectDeactivate );
    952         },
    953 
    954         filterChange: function( model ) {
    955                 this.canShow = ( 'trash' === model.get( 'status' ) );
    956         },
    957 
    958         selectActivate: function() {
    959                 this.toggleDisabled();
    960                 this.$el.toggleClass( 'hidden', ! this.canShow );
    961         },
    962 
    963         selectDeactivate: function() {
    964                 this.toggleDisabled();
    965                 this.$el.addClass( 'hidden' );
    966         },
    967 
    968         render: function() {
    969                 Button.prototype.render.apply( this, arguments );
    970                 this.selectActivate();
    971                 return this;
    972         }
    973 });
    974 
    975 module.exports = DeleteSelectedPermanently;
    976 
    977 
    978 /***/ })
    979 /******/ ]);
     863},{}]},{},[2]);
  • branches/4.2/src/wp-includes/js/media-models.js

    r46500 r50020  
    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 = 20);
    64 /******/ })
    65 /************************************************************************/
    66 /******/ ({
    67 
    68 /***/ 20:
    69 /***/ (function(module, exports, __webpack_require__) {
    70 
     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){
    712/*globals wp, _, jQuery */
    723
    … …  
    12859delete l10n.settings;
    12960
    130 Attachment = media.model.Attachment = __webpack_require__( 21 );
    131 Attachments = media.model.Attachments = __webpack_require__( 22 );
    132 
    133 media.model.Query = __webpack_require__( 23 );
    134 media.model.PostImage = __webpack_require__( 24 );
    135 media.model.Selection = __webpack_require__( 25 );
     61Attachment = media.model.Attachment = require( './models/attachment.js' );
     62Attachments = media.model.Attachments = require( './models/attachments.js' );
     63
     64media.model.Query = require( './models/query.js' );
     65media.model.PostImage = require( './models/post-image.js' );
     66media.model.Selection = require( './models/selection.js' );
    13667
    13768/**
    … …  
    301232});
    302233
    303 
    304 /***/ }),
    305 
    306 /***/ 21:
    307 /***/ (function(module, exports) {
    308 
     234},{"./models/attachment.js":2,"./models/attachments.js":3,"./models/post-image.js":4,"./models/query.js":5,"./models/selection.js":6}],2:[function(require,module,exports){
    309235/*globals wp, _, Backbone */
    310236
    … …  
    476402module.exports = Attachment;
    477403
    478 
    479 /***/ }),
    480 
    481 /***/ 22:
    482 /***/ (function(module, exports) {
    483 
     404},{}],3:[function(require,module,exports){
    484405/*globals wp, _, Backbone */
    485406
    … …  
    1015936module.exports = Attachments;
    1016937
    1017 
    1018 /***/ }),
    1019 
    1020 /***/ 23:
    1021 /***/ (function(module, exports) {
    1022 
    1023 /*globals wp, _ */
    1024 
    1025 /**
    1026  * wp.media.model.Query
    1027  *
    1028  * A collection of attachments that match the supplied query arguments.
    1029  *
    1030  * Note: Do NOT change this.args after the query has been initialized.
    1031  *       Things will break.
    1032  *
    1033  * @class
    1034  * @augments wp.media.model.Attachments
    1035  * @augments Backbone.Collection
    1036  *
    1037  * @param {array}  [models]                      Models to initialize with the collection.
    1038  * @param {object} [options]                     Options hash.
    1039  * @param {object} [options.args]                Attachments query arguments.
    1040  * @param {object} [options.args.posts_per_page]
    1041  */
    1042 var Attachments = wp.media.model.Attachments,
    1043         Query;
    1044 
    1045 Query = Attachments.extend({
    1046         /**
    1047          * @global wp.Uploader
    1048          *
    1049          * @param {array}  [models=[]]  Array of initial models to populate the collection.
    1050          * @param {object} [options={}]
    1051          */
    1052         initialize: function( models, options ) {
    1053                 var allowed;
    1054 
    1055                 options = options || {};
    1056                 Attachments.prototype.initialize.apply( this, arguments );
    1057 
    1058                 this.args     = options.args;
    1059                 this._hasMore = true;
    1060                 this.created  = new Date();
    1061 
    1062                 this.filters.order = function( attachment ) {
    1063                         var orderby = this.props.get('orderby'),
    1064                                 order = this.props.get('order');
    1065 
    1066                         if ( ! this.comparator ) {
    1067                                 return true;
    1068                         }
    1069 
    1070                         // We want any items that can be placed before the last
    1071                         // item in the set. If we add any items after the last
    1072                         // item, then we can't guarantee the set is complete.
    1073                         if ( this.length ) {
    1074                                 return 1 !== this.comparator( attachment, this.last(), { ties: true });
    1075 
    1076                         // Handle the case where there are no items yet and
    1077                         // we're sorting for recent items. In that case, we want
    1078                         // changes that occurred after we created the query.
    1079                         } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) {
    1080                                 return attachment.get( orderby ) >= this.created;
    1081 
    1082                         // If we're sorting by menu order and we have no items,
    1083                         // accept any items that have the default menu order (0).
    1084                         } else if ( 'ASC' === order && 'menuOrder' === orderby ) {
    1085                                 return attachment.get( orderby ) === 0;
    1086                         }
    1087 
    1088                         // Otherwise, we don't want any items yet.
    1089                         return false;
    1090                 };
    1091 
    1092                 // Observe the central `wp.Uploader.queue` collection to watch for
    1093                 // new matches for the query.
    1094                 //
    1095                 // Only observe when a limited number of query args are set. There
    1096                 // are no filters for other properties, so observing will result in
    1097                 // false positives in those queries.
    1098                 allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ];
    1099                 if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) {
    1100                         this.observe( wp.Uploader.queue );
    1101                 }
    1102         },
    1103         /**
    1104          * Whether there are more attachments that haven't been sync'd from the server
    1105          * that match the collection's query.
    1106          *
    1107          * @returns {boolean}
    1108          */
    1109         hasMore: function() {
    1110                 return this._hasMore;
    1111         },
    1112         /**
    1113          * Fetch more attachments from the server for the collection.
    1114          *
    1115          * @param   {object}  [options={}]
    1116          * @returns {Promise}
    1117          */
    1118         more: function( options ) {
    1119                 var query = this;
    1120 
    1121                 // If there is already a request pending, return early with the Deferred object.
    1122                 if ( this._more && 'pending' === this._more.state() ) {
    1123                         return this._more;
    1124                 }
    1125 
    1126                 if ( ! this.hasMore() ) {
    1127                         return jQuery.Deferred().resolveWith( this ).promise();
    1128                 }
    1129 
    1130                 options = options || {};
    1131                 options.remove = false;
    1132 
    1133                 return this._more = this.fetch( options ).done( function( resp ) {
    1134                         if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) {
    1135                                 query._hasMore = false;
    1136                         }
    1137                 });
    1138         },
    1139         /**
    1140          * Overrides Backbone.Collection.sync
    1141          * Overrides wp.media.model.Attachments.sync
    1142          *
    1143          * @param {String} method
    1144          * @param {Backbone.Model} model
    1145          * @param {Object} [options={}]
    1146          * @returns {Promise}
    1147          */
    1148         sync: function( method, model, options ) {
    1149                 var args, fallback;
    1150 
    1151                 // Overload the read method so Attachment.fetch() functions correctly.
    1152                 if ( 'read' === method ) {
    1153                         options = options || {};
    1154                         options.context = this;
    1155                         options.data = _.extend( options.data || {}, {
    1156                                 action:  'query-attachments',
    1157                                 post_id: wp.media.model.settings.post.id
    1158                         });
    1159 
    1160                         // Clone the args so manipulation is non-destructive.
    1161                         args = _.clone( this.args );
    1162 
    1163                         // Determine which page to query.
    1164                         if ( -1 !== args.posts_per_page ) {
    1165                                 args.paged = Math.round( this.length / args.posts_per_page ) + 1;
    1166                         }
    1167 
    1168                         options.data.query = args;
    1169                         return wp.media.ajax( options );
    1170 
    1171                 // Otherwise, fall back to Backbone.sync()
    1172                 } else {
    1173                         /**
    1174                          * Call wp.media.model.Attachments.sync or Backbone.sync
    1175                          */
    1176                         fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone;
    1177                         return fallback.sync.apply( this, arguments );
    1178                 }
    1179         }
    1180 }, {
    1181         /**
    1182          * @readonly
    1183          */
    1184         defaultProps: {
    1185                 orderby: 'date',
    1186                 order:   'DESC'
    1187         },
    1188         /**
    1189          * @readonly
    1190          */
    1191         defaultArgs: {
    1192                 posts_per_page: 40
    1193         },
    1194         /**
    1195          * @readonly
    1196          */
    1197         orderby: {
    1198                 allowed:  [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ],
    1199                 /**
    1200                  * A map of JavaScript orderby values to their WP_Query equivalents.
    1201                  * @type {Object}
    1202                  */
    1203                 valuemap: {
    1204                         'id':         'ID',
    1205                         'uploadedTo': 'parent',
    1206                         'menuOrder':  'menu_order ID'
    1207                 }
    1208         },
    1209         /**
    1210          * A map of JavaScript query properties to their WP_Query equivalents.
    1211          *
    1212          * @readonly
    1213          */
    1214         propmap: {
    1215                 'search':    's',
    1216                 'type':      'post_mime_type',
    1217                 'perPage':   'posts_per_page',
    1218                 'menuOrder': 'menu_order',
    1219                 'uploadedTo': 'post_parent',
    1220                 'status':     'post_status',
    1221                 'include':    'post__in',
    1222                 'exclude':    'post__not_in'
    1223         },
    1224         /**
    1225          * Creates and returns an Attachments Query collection given the properties.
    1226          *
    1227          * Caches query objects and reuses where possible.
    1228          *
    1229          * @static
    1230          * @method
    1231          *
    1232          * @param {object} [props]
    1233          * @param {Object} [props.cache=true]   Whether to use the query cache or not.
    1234          * @param {Object} [props.order]
    1235          * @param {Object} [props.orderby]
    1236          * @param {Object} [props.include]
    1237          * @param {Object} [props.exclude]
    1238          * @param {Object} [props.s]
    1239          * @param {Object} [props.post_mime_type]
    1240          * @param {Object} [props.posts_per_page]
    1241          * @param {Object} [props.menu_order]
    1242          * @param {Object} [props.post_parent]
    1243          * @param {Object} [props.post_status]
    1244          * @param {Object} [options]
    1245          *
    1246          * @returns {wp.media.model.Query} A new Attachments Query collection.
    1247          */
    1248         get: (function(){
    1249                 /**
    1250                  * @static
    1251                  * @type Array
    1252                  */
    1253                 var queries = [];
    1254 
    1255                 /**
    1256                  * @returns {Query}
    1257                  */
    1258                 return function( props, options ) {
    1259                         var args     = {},
    1260                                 orderby  = Query.orderby,
    1261                                 defaults = Query.defaultProps,
    1262                                 query,
    1263                                 cache    = !! props.cache || _.isUndefined( props.cache );
    1264 
    1265                         // Remove the `query` property. This isn't linked to a query,
    1266                         // this *is* the query.
    1267                         delete props.query;
    1268                         delete props.cache;
    1269 
    1270                         // Fill default args.
    1271                         _.defaults( props, defaults );
    1272 
    1273                         // Normalize the order.
    1274                         props.order = props.order.toUpperCase();
    1275                         if ( 'DESC' !== props.order && 'ASC' !== props.order ) {
    1276                                 props.order = defaults.order.toUpperCase();
    1277                         }
    1278 
    1279                         // Ensure we have a valid orderby value.
    1280                         if ( ! _.contains( orderby.allowed, props.orderby ) ) {
    1281                                 props.orderby = defaults.orderby;
    1282                         }
    1283 
    1284                         _.each( [ 'include', 'exclude' ], function( prop ) {
    1285                                 if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) {
    1286                                         props[ prop ] = [ props[ prop ] ];
    1287                                 }
    1288                         } );
    1289 
    1290                         // Generate the query `args` object.
    1291                         // Correct any differing property names.
    1292                         _.each( props, function( value, prop ) {
    1293                                 if ( _.isNull( value ) ) {
    1294                                         return;
    1295                                 }
    1296 
    1297                                 args[ Query.propmap[ prop ] || prop ] = value;
    1298                         });
    1299 
    1300                         // Fill any other default query args.
    1301                         _.defaults( args, Query.defaultArgs );
    1302 
    1303                         // `props.orderby` does not always map directly to `args.orderby`.
    1304                         // Substitute exceptions specified in orderby.keymap.
    1305                         args.orderby = orderby.valuemap[ props.orderby ] || props.orderby;
    1306 
    1307                         // Search the query cache for a matching query.
    1308                         if ( cache ) {
    1309                                 query = _.find( queries, function( query ) {
    1310                                         return _.isEqual( query.args, args );
    1311                                 });
    1312                         } else {
    1313                                 queries = [];
    1314                         }
    1315 
    1316                         // Otherwise, create a new query and add it to the cache.
    1317                         if ( ! query ) {
    1318                                 query = new Query( [], _.extend( options || {}, {
    1319                                         props: props,
    1320                                         args:  args
    1321                                 } ) );
    1322                                 queries.push( query );
    1323                         }
    1324 
    1325                         return query;
    1326                 };
    1327         }())
    1328 });
    1329 
    1330 module.exports = Query;
    1331 
    1332 
    1333 /***/ }),
    1334 
    1335 /***/ 24:
    1336 /***/ (function(module, exports) {
    1337 
     938},{}],4:[function(require,module,exports){
    1338939/*globals Backbone */
    1339940
    … …  
    14911092module.exports = PostImage;
    14921093
    1493 
    1494 /***/ }),
    1495 
    1496 /***/ 25:
    1497 /***/ (function(module, exports) {
    1498 
     1094},{}],5:[function(require,module,exports){
     1095/*globals wp, _ */
     1096
     1097/**
     1098 * wp.media.model.Query
     1099 *
     1100 * A collection of attachments that match the supplied query arguments.
     1101 *
     1102 * Note: Do NOT change this.args after the query has been initialized.
     1103 *       Things will break.
     1104 *
     1105 * @class
     1106 * @augments wp.media.model.Attachments
     1107 * @augments Backbone.Collection
     1108 *
     1109 * @param {array}  [models]                      Models to initialize with the collection.
     1110 * @param {object} [options]                     Options hash.
     1111 * @param {object} [options.args]                Attachments query arguments.
     1112 * @param {object} [options.args.posts_per_page]
     1113 */
     1114var Attachments = wp.media.model.Attachments,
     1115        Query;
     1116
     1117Query = Attachments.extend({
     1118        /**
     1119         * @global wp.Uploader
     1120         *
     1121         * @param {array}  [models=[]]  Array of initial models to populate the collection.
     1122         * @param {object} [options={}]
     1123         */
     1124        initialize: function( models, options ) {
     1125                var allowed;
     1126
     1127                options = options || {};
     1128                Attachments.prototype.initialize.apply( this, arguments );
     1129
     1130                this.args     = options.args;
     1131                this._hasMore = true;
     1132                this.created  = new Date();
     1133
     1134                this.filters.order = function( attachment ) {
     1135                        var orderby = this.props.get('orderby'),
     1136                                order = this.props.get('order');
     1137
     1138                        if ( ! this.comparator ) {
     1139                                return true;
     1140                        }
     1141
     1142                        // We want any items that can be placed before the last
     1143                        // item in the set. If we add any items after the last
     1144                        // item, then we can't guarantee the set is complete.
     1145                        if ( this.length ) {
     1146                                return 1 !== this.comparator( attachment, this.last(), { ties: true });
     1147
     1148                        // Handle the case where there are no items yet and
     1149                        // we're sorting for recent items. In that case, we want
     1150                        // changes that occurred after we created the query.
     1151                        } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) {
     1152                                return attachment.get( orderby ) >= this.created;
     1153
     1154                        // If we're sorting by menu order and we have no items,
     1155                        // accept any items that have the default menu order (0).
     1156                        } else if ( 'ASC' === order && 'menuOrder' === orderby ) {
     1157                                return attachment.get( orderby ) === 0;
     1158                        }
     1159
     1160                        // Otherwise, we don't want any items yet.
     1161                        return false;
     1162                };
     1163
     1164                // Observe the central `wp.Uploader.queue` collection to watch for
     1165                // new matches for the query.
     1166                //
     1167                // Only observe when a limited number of query args are set. There
     1168                // are no filters for other properties, so observing will result in
     1169                // false positives in those queries.
     1170                allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ];
     1171                if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) {
     1172                        this.observe( wp.Uploader.queue );
     1173                }
     1174        },
     1175        /**
     1176         * Whether there are more attachments that haven't been sync'd from the server
     1177         * that match the collection's query.
     1178         *
     1179         * @returns {boolean}
     1180         */
     1181        hasMore: function() {
     1182                return this._hasMore;
     1183        },
     1184        /**
     1185         * Fetch more attachments from the server for the collection.
     1186         *
     1187         * @param   {object}  [options={}]
     1188         * @returns {Promise}
     1189         */
     1190        more: function( options ) {
     1191                var query = this;
     1192
     1193                // If there is already a request pending, return early with the Deferred object.
     1194                if ( this._more && 'pending' === this._more.state() ) {
     1195                        return this._more;
     1196                }
     1197
     1198                if ( ! this.hasMore() ) {
     1199                        return jQuery.Deferred().resolveWith( this ).promise();
     1200                }
     1201
     1202                options = options || {};
     1203                options.remove = false;
     1204
     1205                return this._more = this.fetch( options ).done( function( resp ) {
     1206                        if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) {
     1207                                query._hasMore = false;
     1208                        }
     1209                });
     1210        },
     1211        /**
     1212         * Overrides Backbone.Collection.sync
     1213         * Overrides wp.media.model.Attachments.sync
     1214         *
     1215         * @param {String} method
     1216         * @param {Backbone.Model} model
     1217         * @param {Object} [options={}]
     1218         * @returns {Promise}
     1219         */
     1220        sync: function( method, model, options ) {
     1221                var args, fallback;
     1222
     1223                // Overload the read method so Attachment.fetch() functions correctly.
     1224                if ( 'read' === method ) {
     1225                        options = options || {};
     1226                        options.context = this;
     1227                        options.data = _.extend( options.data || {}, {
     1228                                action:  'query-attachments',
     1229                                post_id: wp.media.model.settings.post.id
     1230                        });
     1231
     1232                        // Clone the args so manipulation is non-destructive.
     1233                        args = _.clone( this.args );
     1234
     1235                        // Determine which page to query.
     1236                        if ( -1 !== args.posts_per_page ) {
     1237                                args.paged = Math.round( this.length / args.posts_per_page ) + 1;
     1238                        }
     1239
     1240                        options.data.query = args;
     1241                        return wp.media.ajax( options );
     1242
     1243                // Otherwise, fall back to Backbone.sync()
     1244                } else {
     1245                        /**
     1246                         * Call wp.media.model.Attachments.sync or Backbone.sync
     1247                         */
     1248                        fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone;
     1249                        return fallback.sync.apply( this, arguments );
     1250                }
     1251        }
     1252}, {
     1253        /**
     1254         * @readonly
     1255         */
     1256        defaultProps: {
     1257                orderby: 'date',
     1258                order:   'DESC'
     1259        },
     1260        /**
     1261         * @readonly
     1262         */
     1263        defaultArgs: {
     1264                posts_per_page: 40
     1265        },
     1266        /**
     1267         * @readonly
     1268         */
     1269        orderby: {
     1270                allowed:  [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ],
     1271                /**
     1272                 * A map of JavaScript orderby values to their WP_Query equivalents.
     1273                 * @type {Object}
     1274                 */
     1275                valuemap: {
     1276                        'id':         'ID',
     1277                        'uploadedTo': 'parent',
     1278                        'menuOrder':  'menu_order ID'
     1279                }
     1280        },
     1281        /**
     1282         * A map of JavaScript query properties to their WP_Query equivalents.
     1283         *
     1284         * @readonly
     1285         */
     1286        propmap: {
     1287                'search':    's',
     1288                'type':      'post_mime_type',
     1289                'perPage':   'posts_per_page',
     1290                'menuOrder': 'menu_order',
     1291                'uploadedTo': 'post_parent',
     1292                'status':     'post_status',
     1293                'include':    'post__in',
     1294                'exclude':    'post__not_in'
     1295        },
     1296        /**
     1297         * Creates and returns an Attachments Query collection given the properties.
     1298         *
     1299         * Caches query objects and reuses where possible.
     1300         *
     1301         * @static
     1302         * @method
     1303         *
     1304         * @param {object} [props]
     1305         * @param {Object} [props.cache=true]   Whether to use the query cache or not.
     1306         * @param {Object} [props.order]
     1307         * @param {Object} [props.orderby]
     1308         * @param {Object} [props.include]
     1309         * @param {Object} [props.exclude]
     1310         * @param {Object} [props.s]
     1311         * @param {Object} [props.post_mime_type]
     1312         * @param {Object} [props.posts_per_page]
     1313         * @param {Object} [props.menu_order]
     1314         * @param {Object} [props.post_parent]
     1315         * @param {Object} [props.post_status]
     1316         * @param {Object} [options]
     1317         *
     1318         * @returns {wp.media.model.Query} A new Attachments Query collection.
     1319         */
     1320        get: (function(){
     1321                /**
     1322                 * @static
     1323                 * @type Array
     1324                 */
     1325                var queries = [];
     1326
     1327                /**
     1328                 * @returns {Query}
     1329                 */
     1330                return function( props, options ) {
     1331                        var args     = {},
     1332                                orderby  = Query.orderby,
     1333                                defaults = Query.defaultProps,
     1334                                query,
     1335                                cache    = !! props.cache || _.isUndefined( props.cache );
     1336
     1337                        // Remove the `query` property. This isn't linked to a query,
     1338                        // this *is* the query.
     1339                        delete props.query;
     1340                        delete props.cache;
     1341
     1342                        // Fill default args.
     1343                        _.defaults( props, defaults );
     1344
     1345                        // Normalize the order.
     1346                        props.order = props.order.toUpperCase();
     1347                        if ( 'DESC' !== props.order && 'ASC' !== props.order ) {
     1348                                props.order = defaults.order.toUpperCase();
     1349                        }
     1350
     1351                        // Ensure we have a valid orderby value.
     1352                        if ( ! _.contains( orderby.allowed, props.orderby ) ) {
     1353                                props.orderby = defaults.orderby;
     1354                        }
     1355
     1356                        _.each( [ 'include', 'exclude' ], function( prop ) {
     1357                                if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) {
     1358                                        props[ prop ] = [ props[ prop ] ];
     1359                                }
     1360                        } );
     1361
     1362                        // Generate the query `args` object.
     1363                        // Correct any differing property names.
     1364                        _.each( props, function( value, prop ) {
     1365                                if ( _.isNull( value ) ) {
     1366                                        return;
     1367                                }
     1368
     1369                                args[ Query.propmap[ prop ] || prop ] = value;
     1370                        });
     1371
     1372                        // Fill any other default query args.
     1373                        _.defaults( args, Query.defaultArgs );
     1374
     1375                        // `props.orderby` does not always map directly to `args.orderby`.
     1376                        // Substitute exceptions specified in orderby.keymap.
     1377                        args.orderby = orderby.valuemap[ props.orderby ] || props.orderby;
     1378
     1379                        // Search the query cache for a matching query.
     1380                        if ( cache ) {
     1381                                query = _.find( queries, function( query ) {
     1382                                        return _.isEqual( query.args, args );
     1383                                });
     1384                        } else {
     1385                                queries = [];
     1386                        }
     1387
     1388                        // Otherwise, create a new query and add it to the cache.
     1389                        if ( ! query ) {
     1390                                query = new Query( [], _.extend( options || {}, {
     1391                                        props: props,
     1392                                        args:  args
     1393                                } ) );
     1394                                queries.push( query );
     1395                        }
     1396
     1397                        return query;
     1398                };
     1399        }())
     1400});
     1401
     1402module.exports = Query;
     1403
     1404},{}],6:[function(require,module,exports){
    14991405/*globals wp, _ */
    15001406
    … …  
    15951501module.exports = Selection;
    15961502
    1597 
    1598 /***/ })
    1599 
    1600 /******/ });
     1503},{}]},{},[1]);
  • branches/4.2/src/wp-includes/js/media-views.js

    r46500 r50020  
    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 /*globals wp, jQuery, _, Backbone */
    71 
    72 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/*globals wp, _ */
     3
     4/**
     5 * wp.media.controller.CollectionAdd
     6 *
     7 * A state for adding attachments to a collection (e.g. video playlist).
     8 *
     9 * @class
     10 * @augments wp.media.controller.Library
     11 * @augments wp.media.controller.State
     12 * @augments Backbone.Model
     13 *
     14 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     15 * @param {string}                     [attributes.id=library]      Unique identifier.
     16 * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
     17 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     18 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     19 *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
     20 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     21 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     22 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     23 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     24 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     25 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     26 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     27 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     28 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     29 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     30 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     31 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     32 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     33 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     34 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     35 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     36 */
     37var Selection = wp.media.model.Selection,
     38        Library = wp.media.controller.Library,
     39        CollectionAdd;
     40
     41CollectionAdd = Library.extend({
     42        defaults: _.defaults( {
     43                // Selection defaults. @see media.model.Selection
     44                multiple:      'add',
     45                // Attachments browser defaults. @see media.view.AttachmentsBrowser
     46                filterable:    'uploaded',
     47
     48                priority:      100,
     49                syncSelection: false
     50        }, Library.prototype.defaults ),
     51
     52        /**
     53         * @since 3.9.0
     54         */
     55        initialize: function() {
     56                var collectionType = this.get('collectionType');
     57
     58                if ( 'video' === this.get( 'type' ) ) {
     59                        collectionType = 'video-' + collectionType;
     60                }
     61
     62                this.set( 'id', collectionType + '-library' );
     63                this.set( 'toolbar', collectionType + '-add' );
     64                this.set( 'menu', collectionType );
     65
     66                // If we haven't been provided a `library`, create a `Selection`.
     67                if ( ! this.get('library') ) {
     68                        this.set( 'library', wp.media.query({ type: this.get('type') }) );
     69                }
     70                Library.prototype.initialize.apply( this, arguments );
     71        },
     72
     73        /**
     74         * @since 3.9.0
     75         */
     76        activate: function() {
     77                var library = this.get('library'),
     78                        editLibrary = this.get('editLibrary'),
     79                        edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
     80
     81                if ( editLibrary && editLibrary !== edit ) {
     82                        library.unobserve( editLibrary );
     83                }
     84
     85                // Accepts attachments that exist in the original library and
     86                // that do not exist in gallery's library.
     87                library.validator = function( attachment ) {
     88                        return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     89                };
     90
     91                // Reset the library to ensure that all attachments are re-added
     92                // to the collection. Do so silently, as calling `observe` will
     93                // trigger the `reset` event.
     94                library.reset( library.mirroring.models, { silent: true });
     95                library.observe( edit );
     96                this.set('editLibrary', edit);
     97
     98                Library.prototype.activate.apply( this, arguments );
     99        }
     100});
     101
     102module.exports = CollectionAdd;
     103
     104},{}],2:[function(require,module,exports){
     105/*globals wp, Backbone */
     106
     107/**
     108 * wp.media.controller.CollectionEdit
     109 *
     110 * A state for editing a collection, which is used by audio and video playlists,
     111 * and can be used for other collections.
     112 *
     113 * @class
     114 * @augments wp.media.controller.Library
     115 * @augments wp.media.controller.State
     116 * @augments Backbone.Model
     117 *
     118 * @param {object}                     [attributes]                      The attributes hash passed to the state.
     119 * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
     120 * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
     121 *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
     122 * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
     123 * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
     124 * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
     125 * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
     126 * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     127 * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
     128 * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
     129 * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
     130 * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
     131 * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
     132 * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
     133 * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
     134 * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
     135 *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
     136 * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
     137 * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
     138 *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     139 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     140 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     141 */
     142var Library = wp.media.controller.Library,
     143        l10n = wp.media.view.l10n,
    73144        $ = jQuery,
    74         l10n;
    75 
    76 media.isTouchDevice = ( 'ontouchend' in document );
    77 
    78 // Link any localized strings.
    79 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
    80 
    81 // Link any settings.
    82 media.view.settings = l10n.settings || {};
    83 delete l10n.settings;
    84 
    85 // Copy the `post` setting over to the model settings.
    86 media.model.settings.post = media.view.settings.post;
    87 
    88 // Check if the browser supports CSS 3.0 transitions
    89 $.support.transition = (function(){
    90         var style = document.documentElement.style,
    91                 transitions = {
    92                         WebkitTransition: 'webkitTransitionEnd',
    93                         MozTransition:    'transitionend',
    94                         OTransition:      'oTransitionEnd otransitionend',
    95                         transition:       'transitionend'
    96                 }, transition;
    97 
    98         transition = _.find( _.keys( transitions ), function( transition ) {
    99                 return ! _.isUndefined( style[ transition ] );
    100         });
    101 
    102         return transition && {
    103                 end: transitions[ transition ]
    104         };
    105 }());
     145        CollectionEdit;
     146
     147CollectionEdit = Library.extend({
     148        defaults: {
     149                multiple:         false,
     150                sortable:         true,
     151                date:             false,
     152                searchable:       false,
     153                content:          'browse',
     154                describe:         true,
     155                dragInfo:         true,
     156                idealColumnWidth: 170,
     157                editing:          false,
     158                priority:         60,
     159                SettingsView:     false,
     160                syncSelection:    false
     161        },
     162
     163        /**
     164         * @since 3.9.0
     165         */
     166        initialize: function() {
     167                var collectionType = this.get('collectionType');
     168
     169                if ( 'video' === this.get( 'type' ) ) {
     170                        collectionType = 'video-' + collectionType;
     171                }
     172
     173                this.set( 'id', collectionType + '-edit' );
     174                this.set( 'toolbar', collectionType + '-edit' );
     175
     176                // If we haven't been provided a `library`, create a `Selection`.
     177                if ( ! this.get('library') ) {
     178                        this.set( 'library', new wp.media.model.Selection() );
     179                }
     180                // The single `Attachment` view to be used in the `Attachments` view.
     181                if ( ! this.get('AttachmentView') ) {
     182                        this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     183                }
     184                Library.prototype.initialize.apply( this, arguments );
     185        },
     186
     187        /**
     188         * @since 3.9.0
     189         */
     190        activate: function() {
     191                var library = this.get('library');
     192
     193                // Limit the library to images only.
     194                library.props.set( 'type', this.get( 'type' ) );
     195
     196                // Watch for uploaded attachments.
     197                this.get('library').observe( wp.Uploader.queue );
     198
     199                this.frame.on( 'content:render:browse', this.renderSettings, this );
     200
     201                Library.prototype.activate.apply( this, arguments );
     202        },
     203
     204        /**
     205         * @since 3.9.0
     206         */
     207        deactivate: function() {
     208                // Stop watching for uploaded attachments.
     209                this.get('library').unobserve( wp.Uploader.queue );
     210
     211                this.frame.off( 'content:render:browse', this.renderSettings, this );
     212
     213                Library.prototype.deactivate.apply( this, arguments );
     214        },
     215
     216        /**
     217         * Render the collection embed settings view in the browser sidebar.
     218         *
     219         * @todo This is against the pattern elsewhere in media. Typically the frame
     220         *       is responsible for adding region mode callbacks. Explain.
     221         *
     222         * @since 3.9.0
     223         *
     224         * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
     225         */
     226        renderSettings: function( attachmentsBrowserView ) {
     227                var library = this.get('library'),
     228                        collectionType = this.get('collectionType'),
     229                        dragInfoText = this.get('dragInfoText'),
     230                        SettingsView = this.get('SettingsView'),
     231                        obj = {};
     232
     233                if ( ! library || ! attachmentsBrowserView ) {
     234                        return;
     235                }
     236
     237                library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
     238
     239                obj[ collectionType ] = new SettingsView({
     240                        controller: this,
     241                        model:      library[ collectionType ],
     242                        priority:   40
     243                });
     244
     245                attachmentsBrowserView.sidebar.set( obj );
     246
     247                if ( dragInfoText ) {
     248                        attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
     249                                el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
     250                                priority: -40
     251                        }) );
     252                }
     253
     254                // Add the 'Reverse order' button to the toolbar.
     255                attachmentsBrowserView.toolbar.set( 'reverse', {
     256                        text:     l10n.reverseOrder,
     257                        priority: 80,
     258
     259                        click: function() {
     260                                library.reset( library.toArray().reverse() );
     261                        }
     262                });
     263        }
     264});
     265
     266module.exports = CollectionEdit;
     267
     268},{}],3:[function(require,module,exports){
     269/*globals wp, _, Backbone */
    106270
    107271/**
    108  * A shared event bus used to provide events into
    109  * the media workflows that 3rd-party devs can use to hook
    110  * in.
     272 * wp.media.controller.Cropper
     273 *
     274 * A state for cropping an image.
     275 *
     276 * @class
     277 * @augments wp.media.controller.State
     278 * @augments Backbone.Model
    111279 */
    112 media.events = _.extend( {}, Backbone.Events );
     280var l10n = wp.media.view.l10n,
     281        Cropper;
     282
     283Cropper = wp.media.controller.State.extend({
     284        defaults: {
     285                id:          'cropper',
     286                title:       l10n.cropImage,
     287                // Region mode defaults.
     288                toolbar:     'crop',
     289                content:     'crop',
     290                router:      false,
     291
     292                canSkipCrop: false
     293        },
     294
     295        activate: function() {
     296                this.frame.on( 'content:create:crop', this.createCropContent, this );
     297                this.frame.on( 'close', this.removeCropper, this );
     298                this.set('selection', new Backbone.Collection(this.frame._selection.single));
     299        },
     300
     301        deactivate: function() {
     302                this.frame.toolbar.mode('browse');
     303        },
     304
     305        createCropContent: function() {
     306                this.cropperView = new wp.media.view.Cropper({
     307                        controller: this,
     308                        attachment: this.get('selection').first()
     309                });
     310                this.cropperView.on('image-loaded', this.createCropToolbar, this);
     311                this.frame.content.set(this.cropperView);
     312
     313        },
     314        removeCropper: function() {
     315                this.imgSelect.cancelSelection();
     316                this.imgSelect.setOptions({remove: true});
     317                this.imgSelect.update();
     318                this.cropperView.remove();
     319        },
     320        createCropToolbar: function() {
     321                var canSkipCrop, toolbarOptions;
     322
     323                canSkipCrop = this.get('canSkipCrop') || false;
     324
     325                toolbarOptions = {
     326                        controller: this.frame,
     327                        items: {
     328                                insert: {
     329                                        style:    'primary',
     330                                        text:     l10n.cropImage,
     331                                        priority: 80,
     332                                        requires: { library: false, selection: false },
     333
     334                                        click: function() {
     335                                                var controller = this.controller,
     336                                                        selection;
     337
     338                                                selection = controller.state().get('selection').first();
     339                                                selection.set({cropDetails: controller.state().imgSelect.getSelection()});
     340
     341                                                this.$el.text(l10n.cropping);
     342                                                this.$el.attr('disabled', true);
     343
     344                                                controller.state().doCrop( selection ).done( function( croppedImage ) {
     345                                                        controller.trigger('cropped', croppedImage );
     346                                                        controller.close();
     347                                                }).fail( function() {
     348                                                        controller.trigger('content:error:crop');
     349                                                });
     350                                        }
     351                                }
     352                        }
     353                };
     354
     355                if ( canSkipCrop ) {
     356                        _.extend( toolbarOptions.items, {
     357                                skip: {
     358                                        style:      'secondary',
     359                                        text:       l10n.skipCropping,
     360                                        priority:   70,
     361                                        requires:   { library: false, selection: false },
     362                                        click:      function() {
     363                                                var selection = this.controller.state().get('selection').first();
     364                                                this.controller.state().cropperView.remove();
     365                                                this.controller.trigger('skippedcrop', selection);
     366                                                this.controller.close();
     367                                        }
     368                                }
     369                        });
     370                }
     371
     372                this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
     373        },
     374
     375        doCrop: function( attachment ) {
     376                return wp.ajax.post( 'custom-header-crop', {
     377                        nonce: attachment.get('nonces').edit,
     378                        id: attachment.get('id'),
     379                        cropDetails: attachment.get('cropDetails')
     380                } );
     381        }
     382});
     383
     384module.exports = Cropper;
     385
     386},{}],4:[function(require,module,exports){
     387/*globals wp */
    113388
    114389/**
    115  * Makes it easier to bind events using transitions.
    116  *
    117  * @param {string} selector
    118  * @param {Number} sensitivity
    119  * @returns {Promise}
     390 * wp.media.controller.EditImage
     391 *
     392 * A state for editing (cropping, etc.) an image.
     393 *
     394 * @class
     395 * @augments wp.media.controller.State
     396 * @augments Backbone.Model
     397 *
     398 * @param {object}                    attributes                      The attributes hash passed to the state.
     399 * @param {wp.media.model.Attachment} attributes.model                The attachment.
     400 * @param {string}                    [attributes.id=edit-image]      Unique identifier.
     401 * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
     402 * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
     403 * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
     404 * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
     405 * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
    120406 */
    121 media.transition = function( selector, sensitivity ) {
    122         var deferred = $.Deferred();
    123 
    124         sensitivity = sensitivity || 2000;
    125 
    126         if ( $.support.transition ) {
    127                 if ( ! (selector instanceof $) ) {
    128                         selector = $( selector );
    129                 }
    130 
    131                 // Resolve the deferred when the first element finishes animating.
    132                 selector.first().one( $.support.transition.end, deferred.resolve );
    133 
    134                 // Just in case the event doesn't trigger, fire a callback.
    135                 _.delay( deferred.resolve, sensitivity );
    136 
    137         // Otherwise, execute on the spot.
    138         } else {
    139                 deferred.resolve();
     407var l10n = wp.media.view.l10n,
     408        EditImage;
     409
     410EditImage = wp.media.controller.State.extend({
     411        defaults: {
     412                id:      'edit-image',
     413                title:   l10n.editImage,
     414                menu:    false,
     415                toolbar: 'edit-image',
     416                content: 'edit-image',
     417                url:     ''
     418        },
     419
     420        /**
     421         * @since 3.9.0
     422         */
     423        activate: function() {
     424                this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
     425        },
     426
     427        /**
     428         * @since 3.9.0
     429         */
     430        deactivate: function() {
     431                this.stopListening( this.frame );
     432        },
     433
     434        /**
     435         * @since 3.9.0
     436         */
     437        toolbar: function() {
     438                var frame = this.frame,
     439                        lastState = frame.lastState(),
     440                        previous = lastState && lastState.id;
     441
     442                frame.toolbar.set( new wp.media.view.Toolbar({
     443                        controller: frame,
     444                        items: {
     445                                back: {
     446                                        style: 'primary',
     447                                        text:     l10n.back,
     448                                        priority: 20,
     449                                        click:    function() {
     450                                                if ( previous ) {
     451                                                        frame.setState( previous );
     452                                                } else {
     453                                                        frame.close();
     454                                                }
     455                                        }
     456                                }
     457                        }
     458                }) );
    140459        }
    141 
    142         return deferred.promise();
    143 };
    144 
    145 media.controller.Region = __webpack_require__( 27 );
    146 media.controller.StateMachine = __webpack_require__( 28 );
    147 media.controller.State = __webpack_require__( 29 );
    148 
    149 media.selectionSync = __webpack_require__( 30 );
    150 media.controller.Library = __webpack_require__( 31 );
    151 media.controller.ImageDetails = __webpack_require__( 32 );
    152 media.controller.GalleryEdit = __webpack_require__( 33 );
    153 media.controller.GalleryAdd = __webpack_require__( 34 );
    154 media.controller.CollectionEdit = __webpack_require__( 35 );
    155 media.controller.CollectionAdd = __webpack_require__( 36 );
    156 media.controller.FeaturedImage = __webpack_require__( 37 );
    157 media.controller.ReplaceImage = __webpack_require__( 38 );
    158 media.controller.EditImage = __webpack_require__( 39 );
    159 media.controller.MediaLibrary = __webpack_require__( 40 );
    160 media.controller.Embed = __webpack_require__( 41 );
    161 media.controller.Cropper = __webpack_require__( 42 );
    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.EditImage = __webpack_require__( 99 );
    216 media.view.Spinner = __webpack_require__( 100 );
    217 
    218 
    219 /***/ }),
    220 /* 27 */
    221 /***/ (function(module, exports) {
    222 
    223 /*globals Backbone, _ */
     460});
     461
     462module.exports = EditImage;
     463
     464},{}],5:[function(require,module,exports){
     465/*globals wp, _, Backbone */
    224466
    225467/**
    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.
     468 * wp.media.controller.Embed
     469 *
     470 * A state for embedding media from a URL.
    237471 *
    238472 * @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.
     473 * @augments wp.media.controller.State
     474 * @augments Backbone.Model
     475 *
     476 * @param {object} attributes                         The attributes hash passed to the state.
     477 * @param {string} [attributes.id=embed]              Unique identifier.
     478 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
     479 * @param {string} [attributes.content=embed]         Initial mode for the content region.
     480 * @param {string} [attributes.menu=default]          Initial mode for the menu region.
     481 * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
     482 * @param {string} [attributes.menu=false]            Initial mode for the menu region.
     483 * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
     484 * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
     485 * @param {string} [attributes.url]                   The embed URL.
     486 * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
    244487 */
    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.
     488var l10n = wp.media.view.l10n,
     489        $ = Backbone.$,
     490        Embed;
     491
     492Embed = wp.media.controller.State.extend({
     493        defaults: {
     494                id:       'embed',
     495                title:    l10n.insertFromUrlTitle,
     496                content:  'embed',
     497                menu:     'default',
     498                toolbar:  'main-embed',
     499                priority: 120,
     500                type:     'link',
     501                url:      '',
     502                metadata: {}
     503        },
     504
     505        // The amount of time used when debouncing the scan.
     506        sensitivity: 200,
     507
     508        initialize: function(options) {
     509                this.metadata = options.metadata;
     510                this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
     511                this.props = new Backbone.Model( this.metadata || { url: '' });
     512                this.props.on( 'change:url', this.debouncedScan, this );
     513                this.props.on( 'change:url', this.refresh, this );
     514                this.on( 'scan', this.scanImage, this );
     515        },
     516
     517        /**
     518         * Trigger a scan of the embedded URL's content for metadata required to embed.
    255519         *
     520         * @fires wp.media.controller.Embed#scan
     521         */
     522        scan: function() {
     523                var scanners,
     524                        embed = this,
     525                        attributes = {
     526                                type: 'link',
     527                                scanners: []
     528                        };
     529
     530                // Scan is triggered with the list of `attributes` to set on the
     531                // state, useful for the 'type' attribute and 'scanners' attribute,
     532                // an array of promise objects for asynchronous scan operations.
     533                if ( this.props.get('url') ) {
     534                        this.trigger( 'scan', attributes );
     535                }
     536
     537                if ( attributes.scanners.length ) {
     538                        scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
     539                        scanners.always( function() {
     540                                if ( embed.get('scanners') === scanners ) {
     541                                        embed.set( 'loading', false );
     542                                }
     543                        });
     544                } else {
     545                        attributes.scanners = null;
     546                }
     547
     548                attributes.loading = !! attributes.scanners;
     549                this.set( attributes );
     550        },
     551        /**
     552         * Try scanning the embed as an image to discover its dimensions.
     553         *
     554         * @param {Object} attributes
     555         */
     556        scanImage: function( attributes ) {
     557                var frame = this.frame,
     558                        state = this,
     559                        url = this.props.get('url'),
     560                        image = new Image(),
     561                        deferred = $.Deferred();
     562
     563                attributes.scanners.push( deferred.promise() );
     564
     565                // Try to load the image and find its width/height.
     566                image.onload = function() {
     567                        deferred.resolve();
     568
     569                        if ( state !== frame.state() || url !== state.props.get('url') ) {
     570                                return;
     571                        }
     572
     573                        state.set({
     574                                type: 'image'
     575                        });
     576
     577                        state.props.set({
     578                                width:  image.width,
     579                                height: image.height
     580                        });
     581                };
     582
     583                image.onerror = deferred.reject;
     584                image.src = url;
     585        },
     586
     587        refresh: function() {
     588                this.frame.toolbar.get().refresh();
     589        },
     590
     591        reset: function() {
     592                this.props.clear().set({ url: '' });
     593
     594                if ( this.active ) {
     595                        this.refresh();
     596                }
     597        }
     598});
     599
     600module.exports = Embed;
     601
     602},{}],6:[function(require,module,exports){
     603/*globals wp, _ */
     604
     605/**
     606 * wp.media.controller.FeaturedImage
     607 *
     608 * A state for selecting a featured image for a post.
     609 *
     610 * @class
     611 * @augments wp.media.controller.Library
     612 * @augments wp.media.controller.State
     613 * @augments Backbone.Model
     614 *
     615 * @param {object}                     [attributes]                          The attributes hash passed to the state.
     616 * @param {string}                     [attributes.id=featured-image]        Unique identifier.
     617 * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
     618 * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
     619 *                                                                           If one is not supplied, a collection of all images will be created.
     620 * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
     621 * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
     622 *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
     623 * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
     624 * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
     625 * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
     626 * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
     627 * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
     628 * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
     629 *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
     630 * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     631 * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
     632 * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     633 * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
     634 * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
     635 */
     636var Attachment = wp.media.model.Attachment,
     637        Library = wp.media.controller.Library,
     638        l10n = wp.media.view.l10n,
     639        FeaturedImage;
     640
     641FeaturedImage = Library.extend({
     642        defaults: _.defaults({
     643                id:            'featured-image',
     644                title:         l10n.setFeaturedImageTitle,
     645                multiple:      false,
     646                filterable:    'uploaded',
     647                toolbar:       'featured-image',
     648                priority:      60,
     649                syncSelection: true
     650        }, Library.prototype.defaults ),
     651
     652        /**
     653         * @since 3.5.0
     654         */
     655        initialize: function() {
     656                var library, comparator;
     657
     658                // If we haven't been provided a `library`, create a `Selection`.
     659                if ( ! this.get('library') ) {
     660                        this.set( 'library', wp.media.query({ type: 'image' }) );
     661                }
     662
     663                Library.prototype.initialize.apply( this, arguments );
     664
     665                library    = this.get('library');
     666                comparator = library.comparator;
     667
     668                // Overload the library's comparator to push items that are not in
     669                // the mirrored query to the front of the aggregate collection.
     670                library.comparator = function( a, b ) {
     671                        var aInQuery = !! this.mirroring.get( a.cid ),
     672                                bInQuery = !! this.mirroring.get( b.cid );
     673
     674                        if ( ! aInQuery && bInQuery ) {
     675                                return -1;
     676                        } else if ( aInQuery && ! bInQuery ) {
     677                                return 1;
     678                        } else {
     679                                return comparator.apply( this, arguments );
     680                        }
     681                };
     682
     683                // Add all items in the selection to the library, so any featured
     684                // images that are not initially loaded still appear.
     685                library.observe( this.get('selection') );
     686        },
     687
     688        /**
     689         * @since 3.5.0
     690         */
     691        activate: function() {
     692                this.updateSelection();
     693                this.frame.on( 'open', this.updateSelection, this );
     694
     695                Library.prototype.activate.apply( this, arguments );
     696        },
     697
     698        /**
     699         * @since 3.5.0
     700         */
     701        deactivate: function() {
     702                this.frame.off( 'open', this.updateSelection, this );
     703
     704                Library.prototype.deactivate.apply( this, arguments );
     705        },
     706
     707        /**
     708         * @since 3.5.0
     709         */
     710        updateSelection: function() {
     711                var selection = this.get('selection'),
     712                        id = wp.media.view.settings.post.featuredImageId,
     713                        attachment;
     714
     715                if ( '' !== id && -1 !== id ) {
     716                        attachment = Attachment.get( id );
     717                        attachment.fetch();
     718                }
     719
     720                selection.reset( attachment ? [ attachment ] : [] );
     721        }
     722});
     723
     724module.exports = FeaturedImage;
     725
     726},{}],7:[function(require,module,exports){
     727/*globals wp, _ */
     728
     729/**
     730 * wp.media.controller.GalleryAdd
     731 *
     732 * A state for selecting more images to add to a gallery.
     733 *
     734 * @class
     735 * @augments wp.media.controller.Library
     736 * @augments wp.media.controller.State
     737 * @augments Backbone.Model
     738 *
     739 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     740 * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
     741 * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
     742 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     743 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     744 *                                                                          If one is not supplied, a collection of all images will be created.
     745 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     746 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     747 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     748 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     749 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     750 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     751 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     752 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     753 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     754 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     755 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     756 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     757 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     758 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     759 */
     760var Selection = wp.media.model.Selection,
     761        Library = wp.media.controller.Library,
     762        l10n = wp.media.view.l10n,
     763        GalleryAdd;
     764
     765GalleryAdd = Library.extend({
     766        defaults: _.defaults({
     767                id:            'gallery-library',
     768                title:         l10n.addToGalleryTitle,
     769                multiple:      'add',
     770                filterable:    'uploaded',
     771                menu:          'gallery',
     772                toolbar:       'gallery-add',
     773                priority:      100,
     774                syncSelection: false
     775        }, Library.prototype.defaults ),
     776
     777        /**
     778         * @since 3.5.0
     779         */
     780        initialize: function() {
     781                // If a library wasn't supplied, create a library of images.
     782                if ( ! this.get('library') ) {
     783                        this.set( 'library', wp.media.query({ type: 'image' }) );
     784                }
     785
     786                Library.prototype.initialize.apply( this, arguments );
     787        },
     788
     789        /**
     790         * @since 3.5.0
     791         */
     792        activate: function() {
     793                var library = this.get('library'),
     794                        edit    = this.frame.state('gallery-edit').get('library');
     795
     796                if ( this.editLibrary && this.editLibrary !== edit ) {
     797                        library.unobserve( this.editLibrary );
     798                }
     799
     800                // Accepts attachments that exist in the original library and
     801                // that do not exist in gallery's library.
     802                library.validator = function( attachment ) {
     803                        return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     804                };
     805
     806                // Reset the library to ensure that all attachments are re-added
     807                // to the collection. Do so silently, as calling `observe` will
     808                // trigger the `reset` event.
     809                library.reset( library.mirroring.models, { silent: true });
     810                library.observe( edit );
     811                this.editLibrary = edit;
     812
     813                Library.prototype.activate.apply( this, arguments );
     814        }
     815});
     816
     817module.exports = GalleryAdd;
     818
     819},{}],8:[function(require,module,exports){
     820/*globals wp */
     821
     822/**
     823 * wp.media.controller.GalleryEdit
     824 *
     825 * A state for editing a gallery's images and settings.
     826 *
     827 * @class
     828 * @augments wp.media.controller.Library
     829 * @augments wp.media.controller.State
     830 * @augments Backbone.Model
     831 *
     832 * @param {object}                     [attributes]                       The attributes hash passed to the state.
     833 * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
     834 * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
     835 * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
     836 *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
     837 * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
     838 * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
     839 * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     840 * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
     841 * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
     842 * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
     843 * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     844 * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
     845 * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
     846 * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
     847 * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
     848 * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
     849 * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
     850 *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
     851 * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
     852 *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     853 */
     854var Library = wp.media.controller.Library,
     855        l10n = wp.media.view.l10n,
     856        GalleryEdit;
     857
     858GalleryEdit = Library.extend({
     859        defaults: {
     860                id:               'gallery-edit',
     861                title:            l10n.editGalleryTitle,
     862                multiple:         false,
     863                searchable:       false,
     864                sortable:         true,
     865                date:             false,
     866                display:          false,
     867                content:          'browse',
     868                toolbar:          'gallery-edit',
     869                describe:         true,
     870                displaySettings:  true,
     871                dragInfo:         true,
     872                idealColumnWidth: 170,
     873                editing:          false,
     874                priority:         60,
     875                syncSelection:    false
     876        },
     877
     878        /**
     879         * @since 3.5.0
     880         */
     881        initialize: function() {
     882                // If we haven't been provided a `library`, create a `Selection`.
     883                if ( ! this.get('library') ) {
     884                        this.set( 'library', new wp.media.model.Selection() );
     885                }
     886
     887                // The single `Attachment` view to be used in the `Attachments` view.
     888                if ( ! this.get('AttachmentView') ) {
     889                        this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     890                }
     891
     892                Library.prototype.initialize.apply( this, arguments );
     893        },
     894
     895        /**
     896         * @since 3.5.0
     897         */
     898        activate: function() {
     899                var library = this.get('library');
     900
     901                // Limit the library to images only.
     902                library.props.set( 'type', 'image' );
     903
     904                // Watch for uploaded attachments.
     905                this.get('library').observe( wp.Uploader.queue );
     906
     907                this.frame.on( 'content:render:browse', this.gallerySettings, this );
     908
     909                Library.prototype.activate.apply( this, arguments );
     910        },
     911
     912        /**
     913         * @since 3.5.0
     914         */
     915        deactivate: function() {
     916                // Stop watching for uploaded attachments.
     917                this.get('library').unobserve( wp.Uploader.queue );
     918
     919                this.frame.off( 'content:render:browse', this.gallerySettings, this );
     920
     921                Library.prototype.deactivate.apply( this, arguments );
     922        },
     923
     924        /**
    256925         * @since 3.5.0
    257926         *
    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 ) {
     927         * @param browser
     928         */
     929        gallerySettings: function( browser ) {
     930                if ( ! this.get('displaySettings') ) {
    384931                        return;
    385932                }
    386933
    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;
     934                var library = this.get('library');
     935
     936                if ( ! library || ! browser ) {
     937                        return;
     938                }
     939
     940                library.gallery = library.gallery || new Backbone.Model();
     941
     942                browser.sidebar.set({
     943                        gallery: new wp.media.view.Settings.Gallery({
     944                                controller: this,
     945                                model:      library.gallery,
     946                                priority:   40
     947                        })
     948                });
     949
     950                browser.toolbar.set( 'reverse', {
     951                        text:     l10n.reverseOrder,
     952                        priority: 80,
     953
     954                        click: function() {
     955                                library.reset( library.toArray().reverse() );
     956                        }
     957                });
    398958        }
    399959});
    400960
    401 module.exports = Region;
    402 
    403 
    404 /***/ }),
    405 /* 28 */
    406 /***/ (function(module, exports) {
    407 
    408 /*globals _, Backbone */
     961module.exports = GalleryEdit;
     962
     963},{}],9:[function(require,module,exports){
     964/*globals wp, _ */
    409965
    410966/**
    411  * wp.media.controller.StateMachine
    412  *
    413  * A state machine keeps track of state. It is in one state at a time,
    414  * and can change from one state to another.
    415  *
    416  * States are stored as models in a Backbone collection.
    417  *
    418  * @since 3.5.0
     967 * wp.media.controller.ImageDetails
     968 *
     969 * A state for editing the attachment display settings of an image that's been
     970 * inserted into the editor.
    419971 *
    420972 * @class
     973 * @augments wp.media.controller.State
    421974 * @augments Backbone.Model
    422  * @mixin
    423  * @mixes Backbone.Events
    424  *
    425  * @param {Array} states
     975 *
     976 * @param {object}                    [attributes]                       The attributes hash passed to the state.
     977 * @param {string}                    [attributes.id=image-details]      Unique identifier.
     978 * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
     979 * @param {wp.media.model.Attachment} attributes.image                   The image's model.
     980 * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
     981 * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
     982 * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
     983 * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
     984 * @param {boolean}                   [attributes.editing=false]         Unused.
     985 * @param {int}                       [attributes.priority=60]           Unused.
     986 *
     987 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
     988 *       however this may not do anything.
    426989 */
    427 var StateMachine = function( states ) {
    428         // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
    429         this.states = new Backbone.Collection( states );
    430 };
    431 
    432 // Use Backbone's self-propagating `extend` inheritance method.
    433 StateMachine.extend = Backbone.Model.extend;
    434 
    435 _.extend( StateMachine.prototype, Backbone.Events, {
    436         /**
    437          * Fetch a state.
     990var State = wp.media.controller.State,
     991        Library = wp.media.controller.Library,
     992        l10n = wp.media.view.l10n,
     993        ImageDetails;
     994
     995ImageDetails = State.extend({
     996        defaults: _.defaults({
     997                id:       'image-details',
     998                title:    l10n.imageDetailsTitle,
     999                content:  'image-details',
     1000                menu:     false,
     1001                router:   false,
     1002                toolbar:  'image-details',
     1003                editing:  false,
     1004                priority: 60
     1005        }, Library.prototype.defaults ),
     1006
     1007        /**
     1008         * @since 3.9.0
    4381009         *
    439          * If no `id` is provided, returns the active state.
    440          *
    441          * Implicitly creates states.
    442          *
    443          * Ensure that the `states` collection exists so the `StateMachine`
    444          *   can be used as a mixin.
    445          *
    446          * @since 3.5.0
    447          *
    448          * @param {string} id
    449          * @returns {wp.media.controller.State} Returns a State model
    450          *   from the StateMachine collection
    451          */
    452         state: function( id ) {
    453                 this.states = this.states || new Backbone.Collection();
    454 
    455                 // Default to the active state.
    456                 id = id || this._state;
    457 
    458                 if ( id && ! this.states.get( id ) ) {
    459                         this.states.add({ id: id });
    460                 }
    461                 return this.states.get( id );
    462         },
    463 
    464         /**
    465          * Sets the active state.
    466          *
    467          * Bail if we're trying to select the current state, if we haven't
    468          * created the `states` collection, or are trying to select a state
    469          * that does not exist.
    470          *
    471          * @since 3.5.0
    472          *
    473          * @param {string} id
    474          *
    475          * @fires wp.media.controller.State#deactivate
    476          * @fires wp.media.controller.State#activate
    477          *
    478          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
    479          */
    480         setState: function( id ) {
    481                 var previous = this.state();
    482 
    483                 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
    484                         return this;
    485                 }
    486 
    487                 if ( previous ) {
    488                         previous.trigger('deactivate');
    489                         this._lastState = previous.id;
    490                 }
    491 
    492                 this._state = id;
    493                 this.state().trigger('activate');
    494 
    495                 return this;
    496         },
    497 
    498         /**
    499          * Returns the previous active state.
    500          *
    501          * Call the `state()` method with no parameters to retrieve the current
    502          * active state.
    503          *
    504          * @since 3.5.0
    505          *
    506          * @returns {wp.media.controller.State} Returns a State model
    507          *    from the StateMachine collection
    508          */
    509         lastState: function() {
    510                 if ( this._lastState ) {
    511                         return this.state( this._lastState );
    512                 }
     1010         * @param options Attributes
     1011         */
     1012        initialize: function( options ) {
     1013                this.image = options.image;
     1014                State.prototype.initialize.apply( this, arguments );
     1015        },
     1016
     1017        /**
     1018         * @since 3.9.0
     1019         */
     1020        activate: function() {
     1021                this.frame.modal.$el.addClass('image-details');
    5131022        }
    5141023});
    5151024
    516 // Map all event binding and triggering on a StateMachine to its `states` collection.
    517 _.each([ 'on', 'off', 'trigger' ], function( method ) {
    518         /**
    519          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    520          */
    521         StateMachine.prototype[ method ] = function() {
    522                 // Ensure that the `states` collection exists so the `StateMachine`
    523                 // can be used as a mixin.
    524                 this.states = this.states || new Backbone.Collection();
    525                 // Forward the method to the `states` collection.
    526                 this.states[ method ].apply( this.states, arguments );
    527                 return this;
    528         };
    529 });
    530 
    531 module.exports = StateMachine;
    532 
    533 
    534 /***/ }),
    535 /* 29 */
    536 /***/ (function(module, exports) {
    537 
    538 /*globals _, Backbone */
    539 
    540 /**
    541  * wp.media.controller.State
    542  *
    543  * A state is a step in a workflow that when set will trigger the controllers
    544  * for the regions to be updated as specified in the frame.
    545  *
    546  * A state has an event-driven lifecycle:
    547  *
    548  *     'ready'      triggers when a state is added to a state machine's collection.
    549  *     'activate'   triggers when a state is activated by a state machine.
    550  *     'deactivate' triggers when a state is deactivated by a state machine.
    551  *     'reset'      is not triggered automatically. It should be invoked by the
    552  *                  proper controller to reset the state to its default.
    553  *
    554  * @class
    555  * @augments Backbone.Model
    556  */
    557 var State = Backbone.Model.extend({
    558         /**
    559          * Constructor.
    560          *
    561          * @since 3.5.0
    562          */
    563         constructor: function() {
    564                 this.on( 'activate', this._preActivate, this );
    565                 this.on( 'activate', this.activate, this );
    566                 this.on( 'activate', this._postActivate, this );
    567                 this.on( 'deactivate', this._deactivate, this );
    568                 this.on( 'deactivate', this.deactivate, this );
    569                 this.on( 'reset', this.reset, this );
    570                 this.on( 'ready', this._ready, this );
    571                 this.on( 'ready', this.ready, this );
    572                 /**
    573                  * Call parent constructor with passed arguments
    574                  */
    575                 Backbone.Model.apply( this, arguments );
    576                 this.on( 'change:menu', this._updateMenu, this );
    577         },
    578         /**
    579          * Ready event callback.
    580          *
    581          * @abstract
    582          * @since 3.5.0
    583          */
    584         ready: function() {},
    585 
    586         /**
    587          * Activate event callback.
    588          *
    589          * @abstract
    590          * @since 3.5.0
    591          */
    592         activate: function() {},
    593 
    594         /**
    595          * Deactivate event callback.
    596          *
    597          * @abstract
    598          * @since 3.5.0
    599          */
    600         deactivate: function() {},
    601 
    602         /**
    603          * Reset event callback.
    604          *
    605          * @abstract
    606          * @since 3.5.0
    607          */
    608         reset: function() {},
    609 
    610         /**
    611          * @access private
    612          * @since 3.5.0
    613          */
    614         _ready: function() {
    615                 this._updateMenu();
    616         },
    617 
    618         /**
    619          * @access private
    620          * @since 3.5.0
    621         */
    622         _preActivate: function() {
    623                 this.active = true;
    624         },
    625 
    626         /**
    627          * @access private
    628          * @since 3.5.0
    629          */
    630         _postActivate: function() {
    631                 this.on( 'change:menu', this._menu, this );
    632                 this.on( 'change:titleMode', this._title, this );
    633                 this.on( 'change:content', this._content, this );
    634                 this.on( 'change:toolbar', this._toolbar, this );
    635 
    636                 this.frame.on( 'title:render:default', this._renderTitle, this );
    637 
    638                 this._title();
    639                 this._menu();
    640                 this._toolbar();
    641                 this._content();
    642                 this._router();
    643         },
    644 
    645         /**
    646          * @access private
    647          * @since 3.5.0
    648          */
    649         _deactivate: function() {
    650                 this.active = false;
    651 
    652                 this.frame.off( 'title:render:default', this._renderTitle, this );
    653 
    654                 this.off( 'change:menu', this._menu, this );
    655                 this.off( 'change:titleMode', this._title, this );
    656                 this.off( 'change:content', this._content, this );
    657                 this.off( 'change:toolbar', this._toolbar, this );
    658         },
    659 
    660         /**
    661          * @access private
    662          * @since 3.5.0
    663          */
    664         _title: function() {
    665                 this.frame.title.render( this.get('titleMode') || 'default' );
    666         },
    667 
    668         /**
    669          * @access private
    670          * @since 3.5.0
    671          */
    672         _renderTitle: function( view ) {
    673                 view.$el.text( this.get('title') || '' );
    674         },
    675 
    676         /**
    677          * @access private
    678          * @since 3.5.0
    679          */
    680         _router: function() {
    681                 var router = this.frame.router,
    682                         mode = this.get('router'),
    683                         view;
    684 
    685                 this.frame.$el.toggleClass( 'hide-router', ! mode );
    686                 if ( ! mode ) {
    687                         return;
    688                 }
    689 
    690                 this.frame.router.render( mode );
    691 
    692                 view = router.get();
    693                 if ( view && view.select ) {
    694                         view.select( this.frame.content.mode() );
    695                 }
    696         },
    697 
    698         /**
    699          * @access private
    700          * @since 3.5.0
    701          */
    702         _menu: function() {
    703                 var menu = this.frame.menu,
    704                         mode = this.get('menu'),
    705                         view;
    706 
    707                 this.frame.$el.toggleClass( 'hide-menu', ! mode );
    708                 if ( ! mode ) {
    709                         return;
    710                 }
    711 
    712                 menu.mode( mode );
    713 
    714                 view = menu.get();
    715                 if ( view && view.select ) {
    716                         view.select( this.id );
    717                 }
    718         },
    719 
    720         /**
    721          * @access private
    722          * @since 3.5.0
    723          */
    724         _updateMenu: function() {
    725                 var previous = this.previous('menu'),
    726                         menu = this.get('menu');
    727 
    728                 if ( previous ) {
    729                         this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
    730                 }
    731 
    732                 if ( menu ) {
    733                         this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
    734                 }
    735         },
    736 
    737         /**
    738          * Create a view in the media menu for the state.
    739          *
    740          * @access private
    741          * @since 3.5.0
    742          *
    743          * @param {media.view.Menu} view The menu view.
    744          */
    745         _renderMenu: function( view ) {
    746                 var menuItem = this.get('menuItem'),
    747                         title = this.get('title'),
    748                         priority = this.get('priority');
    749 
    750                 if ( ! menuItem && title ) {
    751                         menuItem = { text: title };
    752 
    753                         if ( priority ) {
    754                                 menuItem.priority = priority;
    755                         }
    756                 }
    757 
    758                 if ( ! menuItem ) {
    759                         return;
    760                 }
    761 
    762                 view.set( this.id, menuItem );
    763         }
    764 });
    765 
    766 _.each(['toolbar','content'], function( region ) {
    767         /**
    768          * @access private
    769          */
    770         State.prototype[ '_' + region ] = function() {
    771                 var mode = this.get( region );
    772                 if ( mode ) {
    773                         this.frame[ region ].render( mode );
    774                 }
    775         };
    776 });
    777 
    778 module.exports = State;
    779 
    780 
    781 /***/ }),
    782 /* 30 */
    783 /***/ (function(module, exports) {
    784 
    785 /*globals _ */
    786 
    787 /**
    788  * wp.media.selectionSync
    789  *
    790  * Sync an attachments selection in a state with another state.
    791  *
    792  * Allows for selecting multiple images in the Insert Media workflow, and then
    793  * switching to the Insert Gallery workflow while preserving the attachments selection.
    794  *
    795  * @mixin
    796  */
    797 var selectionSync = {
    798         /**
    799          * @since 3.5.0
    800          */
    801         syncSelection: function() {
    802                 var selection = this.get('selection'),
    803                         manager = this.frame._selection;
    804 
    805                 if ( ! this.get('syncSelection') || ! manager || ! selection ) {
    806                         return;
    807                 }
    808 
    809                 // If the selection supports multiple items, validate the stored
    810                 // attachments based on the new selection's conditions. Record
    811                 // the attachments that are not included; we'll maintain a
    812                 // reference to those. Other attachments are considered in flux.
    813                 if ( selection.multiple ) {
    814                         selection.reset( [], { silent: true });
    815                         selection.validateAll( manager.attachments );
    816                         manager.difference = _.difference( manager.attachments.models, selection.models );
    817                 }
    818 
    819                 // Sync the selection's single item with the master.
    820                 selection.single( manager.single );
    821         },
    822 
    823         /**
    824          * Record the currently active attachments, which is a combination
    825          * of the selection's attachments and the set of selected
    826          * attachments that this specific selection considered invalid.
    827          * Reset the difference and record the single attachment.
    828          *
    829          * @since 3.5.0
    830          */
    831         recordSelection: function() {
    832                 var selection = this.get('selection'),
    833                         manager = this.frame._selection;
    834 
    835                 if ( ! this.get('syncSelection') || ! manager || ! selection ) {
    836                         return;
    837                 }
    838 
    839                 if ( selection.multiple ) {
    840                         manager.attachments.reset( selection.toArray().concat( manager.difference ) );
    841                         manager.difference = [];
    842                 } else {
    843                         manager.attachments.add( selection.toArray() );
    844                 }
    845 
    846                 manager.single = selection._single;
    847         }
    848 };
    849 
    850 module.exports = selectionSync;
    851 
    852 
    853 /***/ }),
    854 /* 31 */
    855 /***/ (function(module, exports) {
    856 
     1025module.exports = ImageDetails;
     1026
     1027},{}],10:[function(require,module,exports){
    8571028/*globals wp, _, Backbone */
    8581029
    … …  
    11281299module.exports = Library;
    11291300
    1130 
    1131 /***/ }),
    1132 /* 32 */
    1133 /***/ (function(module, exports) {
    1134 
     1301},{}],11:[function(require,module,exports){
    11351302/*globals wp, _ */
    11361303
    11371304/**
    1138  * wp.media.controller.ImageDetails
    1139  *
    1140  * A state for editing the attachment display settings of an image that's been
    1141  * inserted into the editor.
    1142  *
    1143  * @class
    1144  * @augments wp.media.controller.State
    1145  * @augments Backbone.Model
    1146  *
    1147  * @param {object}                    [attributes]                       The attributes hash passed to the state.
    1148  * @param {string}                    [attributes.id=image-details]      Unique identifier.
    1149  * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
    1150  * @param {wp.media.model.Attachment} attributes.image                   The image's model.
    1151  * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
    1152  * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
    1153  * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
    1154  * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
    1155  * @param {boolean}                   [attributes.editing=false]         Unused.
    1156  * @param {int}                       [attributes.priority=60]           Unused.
    1157  *
    1158  * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
    1159  *       however this may not do anything.
    1160  */
    1161 var State = wp.media.controller.State,
    1162         Library = wp.media.controller.Library,
    1163         l10n = wp.media.view.l10n,
    1164         ImageDetails;
    1165 
    1166 ImageDetails = State.extend({
    1167         defaults: _.defaults({
    1168                 id:       'image-details',
    1169                 title:    l10n.imageDetailsTitle,
    1170                 content:  'image-details',
    1171                 menu:     false,
    1172                 router:   false,
    1173                 toolbar:  'image-details',
    1174                 editing:  false,
    1175                 priority: 60
    1176         }, Library.prototype.defaults ),
    1177 
    1178         /**
    1179          * @since 3.9.0
    1180          *
    1181          * @param options Attributes
    1182          */
    1183         initialize: function( options ) {
    1184                 this.image = options.image;
    1185                 State.prototype.initialize.apply( this, arguments );
    1186         },
    1187 
    1188         /**
    1189          * @since 3.9.0
    1190          */
    1191         activate: function() {
    1192                 this.frame.modal.$el.addClass('image-details');
    1193         }
    1194 });
    1195 
    1196 module.exports = ImageDetails;
    1197 
    1198 
    1199 /***/ }),
    1200 /* 33 */
    1201 /***/ (function(module, exports) {
    1202 
    1203 /*globals wp */
    1204 
    1205 /**
    1206  * wp.media.controller.GalleryEdit
    1207  *
    1208  * A state for editing a gallery's images and settings.
     1305 * wp.media.controller.MediaLibrary
    12091306 *
    12101307 * @class
    … …  
    12121309 * @augments wp.media.controller.State
    12131310 * @augments Backbone.Model
    1214  *
    1215  * @param {object}                     [attributes]                       The attributes hash passed to the state.
    1216  * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
    1217  * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
    1218  * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
    1219  *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
    1220  * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
    1221  * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
    1222  * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1223  * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
    1224  * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
    1225  * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
    1226  * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1227  * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
    1228  * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
    1229  * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
    1230  * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
    1231  * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
    1232  * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
    1233  *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
    1234  * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
    1235  *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    12361311 */
    12371312var Library = wp.media.controller.Library,
    1238         l10n = wp.media.view.l10n,
    1239         GalleryEdit;
    1240 
    1241 GalleryEdit = Library.extend({
    1242         defaults: {
    1243                 id:               'gallery-edit',
    1244                 title:            l10n.editGalleryTitle,
    1245                 multiple:         false,
    1246                 searchable:       false,
    1247                 sortable:         true,
    1248                 date:             false,
    1249                 display:          false,
    1250                 content:          'browse',
    1251                 toolbar:          'gallery-edit',
    1252                 describe:         true,
    1253                 displaySettings:  true,
    1254                 dragInfo:         true,
    1255                 idealColumnWidth: 170,
    1256                 editing:          false,
    1257                 priority:         60,
    1258                 syncSelection:    false
    1259         },
    1260 
    1261         /**
    1262          * @since 3.5.0
    1263          */
    1264         initialize: function() {
    1265                 // If we haven't been provided a `library`, create a `Selection`.
    1266                 if ( ! this.get('library') ) {
    1267                         this.set( 'library', new wp.media.model.Selection() );
    1268                 }
    1269 
    1270                 // The single `Attachment` view to be used in the `Attachments` view.
    1271                 if ( ! this.get('AttachmentView') ) {
    1272                         this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    1273                 }
     1313        MediaLibrary;
     1314
     1315MediaLibrary = Library.extend({
     1316        defaults: _.defaults({
     1317                // Attachments browser defaults. @see media.view.AttachmentsBrowser
     1318                filterable:      'uploaded',
     1319
     1320                displaySettings: false,
     1321                priority:        80,
     1322                syncSelection:   false
     1323        }, Library.prototype.defaults ),
     1324
     1325        /**
     1326         * @since 3.9.0
     1327         *
     1328         * @param options
     1329         */
     1330        initialize: function( options ) {
     1331                this.media = options.media;
     1332                this.type = options.type;
     1333                this.set( 'library', wp.media.query({ type: this.type }) );
    12741334
    12751335                Library.prototype.initialize.apply( this, arguments );
    … …  
    12771337
    12781338        /**
    1279          * @since 3.5.0
     1339         * @since 3.9.0
    12801340         */
    12811341        activate: function() {
    1282                 var library = this.get('library');
    1283 
    1284                 // Limit the library to images only.
    1285                 library.props.set( 'type', 'image' );
    1286 
    1287                 // Watch for uploaded attachments.
    1288                 this.get('library').observe( wp.Uploader.queue );
    1289 
    1290                 this.frame.on( 'content:render:browse', this.gallerySettings, this );
    1291 
    1292                 Library.prototype.activate.apply( this, arguments );
    1293         },
    1294 
    1295         /**
    1296          * @since 3.5.0
    1297          */
    1298         deactivate: function() {
    1299                 // Stop watching for uploaded attachments.
    1300                 this.get('library').unobserve( wp.Uploader.queue );
    1301 
    1302                 this.frame.off( 'content:render:browse', this.gallerySettings, this );
    1303 
    1304                 Library.prototype.deactivate.apply( this, arguments );
    1305         },
    1306 
    1307         /**
    1308          * @since 3.5.0
    1309          *
    1310          * @param browser
    1311          */
    1312         gallerySettings: function( browser ) {
    1313                 if ( ! this.get('displaySettings') ) {
    1314                         return;
    1315                 }
    1316 
    1317                 var library = this.get('library');
    1318 
    1319                 if ( ! library || ! browser ) {
    1320                         return;
    1321                 }
    1322 
    1323                 library.gallery = library.gallery || new Backbone.Model();
    1324 
    1325                 browser.sidebar.set({
    1326                         gallery: new wp.media.view.Settings.Gallery({
    1327                                 controller: this,
    1328                                 model:      library.gallery,
    1329                                 priority:   40
    1330                         })
    1331                 });
    1332 
    1333                 browser.toolbar.set( 'reverse', {
    1334                         text:     l10n.reverseOrder,
    1335                         priority: 80,
    1336 
    1337                         click: function() {
    1338                                 library.reset( library.toArray().reverse() );
    1339                         }
    1340                 });
    1341         }
    1342 });
    1343 
    1344 module.exports = GalleryEdit;
    1345 
    1346 
    1347 /***/ }),
    1348 /* 34 */
    1349 /***/ (function(module, exports) {
    1350 
    1351 /*globals wp, _ */
    1352 
    1353 /**
    1354  * wp.media.controller.GalleryAdd
    1355  *
    1356  * A state for selecting more images to add to a gallery.
    1357  *
    1358  * @class
    1359  * @augments wp.media.controller.Library
    1360  * @augments wp.media.controller.State
    1361  * @augments Backbone.Model
    1362  *
    1363  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    1364  * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
    1365  * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
    1366  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    1367  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    1368  *                                                                          If one is not supplied, a collection of all images will be created.
    1369  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    1370  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    1371  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    1372  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    1373  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    1374  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    1375  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    1376  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    1377  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1378  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1379  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1380  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    1381  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    1382  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    1383  */
    1384 var Selection = wp.media.model.Selection,
    1385         Library = wp.media.controller.Library,
    1386         l10n = wp.media.view.l10n,
    1387         GalleryAdd;
    1388 
    1389 GalleryAdd = Library.extend({
    1390         defaults: _.defaults({
    1391                 id:            'gallery-library',
    1392                 title:         l10n.addToGalleryTitle,
    1393                 multiple:      'add',
    1394                 filterable:    'uploaded',
    1395                 menu:          'gallery',
    1396                 toolbar:       'gallery-add',
    1397                 priority:      100,
    1398                 syncSelection: false
    1399         }, Library.prototype.defaults ),
    1400 
    1401         /**
    1402          * @since 3.5.0
    1403          */
    1404         initialize: function() {
    1405                 // If a library wasn't supplied, create a library of images.
    1406                 if ( ! this.get('library') ) {
    1407                         this.set( 'library', wp.media.query({ type: 'image' }) );
    1408                 }
    1409 
    1410                 Library.prototype.initialize.apply( this, arguments );
    1411         },
    1412 
    1413         /**
    1414          * @since 3.5.0
    1415          */
    1416         activate: function() {
    1417                 var library = this.get('library'),
    1418                         edit    = this.frame.state('gallery-edit').get('library');
    1419 
    1420                 if ( this.editLibrary && this.editLibrary !== edit ) {
    1421                         library.unobserve( this.editLibrary );
    1422                 }
    1423 
    1424                 // Accepts attachments that exist in the original library and
    1425                 // that do not exist in gallery's library.
    1426                 library.validator = function( attachment ) {
    1427                         return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    1428                 };
    1429 
    1430                 // Reset the library to ensure that all attachments are re-added
    1431                 // to the collection. Do so silently, as calling `observe` will
    1432                 // trigger the `reset` event.
    1433                 library.reset( library.mirroring.models, { silent: true });
    1434                 library.observe( edit );
    1435                 this.editLibrary = edit;
    1436 
     1342                // @todo this should use this.frame.
     1343                if ( wp.media.frame.lastMime ) {
     1344                        this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
     1345                        delete wp.media.frame.lastMime;
     1346                }
    14371347                Library.prototype.activate.apply( this, arguments );
    14381348        }
    14391349});
    14401350
    1441 module.exports = GalleryAdd;
    1442 
    1443 
    1444 /***/ }),
    1445 /* 35 */
    1446 /***/ (function(module, exports) {
    1447 
    1448 /*globals wp, Backbone */
     1351module.exports = MediaLibrary;
     1352
     1353},{}],12:[function(require,module,exports){
     1354/*globals Backbone, _ */
    14491355
    14501356/**
    1451  * wp.media.controller.CollectionEdit
    1452  *
    1453  * A state for editing a collection, which is used by audio and video playlists,
    1454  * and can be used for other collections.
     1357 * wp.media.controller.Region
     1358 *
     1359 * A region is a persistent application layout area.
     1360 *
     1361 * A region assumes one mode at any time, and can be switched to another.
     1362 *
     1363 * When mode changes, events are triggered on the region's parent view.
     1364 * The parent view will listen to specific events and fill the region with an
     1365 * appropriate view depending on mode. For example, a frame listens for the
     1366 * 'browse' mode t be activated on the 'content' view and then fills the region
     1367 * with an AttachmentsBrowser view.
    14551368 *
    14561369 * @class
    1457  * @augments wp.media.controller.Library
    1458  * @augments wp.media.controller.State
    1459  * @augments Backbone.Model
    1460  *
    1461  * @param {object}                     [attributes]                      The attributes hash passed to the state.
    1462  * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
    1463  * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
    1464  *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
    1465  * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
    1466  * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
    1467  * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
    1468  * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
    1469  * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1470  * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
    1471  * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
    1472  * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
    1473  * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
    1474  * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
    1475  * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
    1476  * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
    1477  * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
    1478  *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
    1479  * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
    1480  * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
    1481  *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    1482  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    1483  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     1370 *
     1371 * @param {object}        options          Options hash for the region.
     1372 * @param {string}        options.id       Unique identifier for the region.
     1373 * @param {Backbone.View} options.view     A parent view the region exists within.
     1374 * @param {string}        options.selector jQuery selector for the region within the parent view.
    14841375 */
    1485 var Library = wp.media.controller.Library,
    1486         l10n = wp.media.view.l10n,
    1487         $ = jQuery,
    1488         CollectionEdit;
    1489 
    1490 CollectionEdit = Library.extend({
    1491         defaults: {
    1492                 multiple:         false,
    1493                 sortable:         true,
    1494                 date:             false,
    1495                 searchable:       false,
    1496                 content:          'browse',
    1497                 describe:         true,
    1498                 dragInfo:         true,
    1499                 idealColumnWidth: 170,
    1500                 editing:          false,
    1501                 priority:         60,
    1502                 SettingsView:     false,
    1503                 syncSelection:    false
    1504         },
    1505 
    1506         /**
    1507          * @since 3.9.0
    1508          */
    1509         initialize: function() {
    1510                 var collectionType = this.get('collectionType');
    1511 
    1512                 if ( 'video' === this.get( 'type' ) ) {
    1513                         collectionType = 'video-' + collectionType;
    1514                 }
    1515 
    1516                 this.set( 'id', collectionType + '-edit' );
    1517                 this.set( 'toolbar', collectionType + '-edit' );
    1518 
    1519                 // If we haven't been provided a `library`, create a `Selection`.
    1520                 if ( ! this.get('library') ) {
    1521                         this.set( 'library', new wp.media.model.Selection() );
    1522                 }
    1523                 // The single `Attachment` view to be used in the `Attachments` view.
    1524                 if ( ! this.get('AttachmentView') ) {
    1525                         this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    1526                 }
    1527                 Library.prototype.initialize.apply( this, arguments );
    1528         },
    1529 
    1530         /**
    1531          * @since 3.9.0
    1532          */
    1533         activate: function() {
    1534                 var library = this.get('library');
    1535 
    1536                 // Limit the library to images only.
    1537                 library.props.set( 'type', this.get( 'type' ) );
    1538 
    1539                 // Watch for uploaded attachments.
    1540                 this.get('library').observe( wp.Uploader.queue );
    1541 
    1542                 this.frame.on( 'content:render:browse', this.renderSettings, this );
    1543 
    1544                 Library.prototype.activate.apply( this, arguments );
    1545         },
    1546 
    1547         /**
    1548          * @since 3.9.0
    1549          */
    1550         deactivate: function() {
    1551                 // Stop watching for uploaded attachments.
    1552                 this.get('library').unobserve( wp.Uploader.queue );
    1553 
    1554                 this.frame.off( 'content:render:browse', this.renderSettings, this );
    1555 
    1556                 Library.prototype.deactivate.apply( this, arguments );
    1557         },
    1558 
    1559         /**
    1560          * Render the collection embed settings view in the browser sidebar.
     1376var Region = function( options ) {
     1377        _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
     1378};
     1379
     1380// Use Backbone's self-propagating `extend` inheritance method.
     1381Region.extend = Backbone.Model.extend;
     1382
     1383_.extend( Region.prototype, {
     1384        /**
     1385         * Activate a mode.
    15611386         *
    1562          * @todo This is against the pattern elsewhere in media. Typically the frame
    1563          *       is responsible for adding region mode callbacks. Explain.
     1387         * @since 3.5.0
    15641388         *
    1565          * @since 3.9.0
     1389         * @param {string} mode
    15661390         *
    1567          * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
    1568          */
    1569         renderSettings: function( attachmentsBrowserView ) {
    1570                 var library = this.get('library'),
    1571                         collectionType = this.get('collectionType'),
    1572                         dragInfoText = this.get('dragInfoText'),
    1573                         SettingsView = this.get('SettingsView'),
    1574                         obj = {};
    1575 
    1576                 if ( ! library || ! attachmentsBrowserView ) {
     1391         * @fires this.view#{this.id}:activate:{this._mode}
     1392         * @fires this.view#{this.id}:activate
     1393         * @fires this.view#{this.id}:deactivate:{this._mode}
     1394         * @fires this.view#{this.id}:deactivate
     1395         *
     1396         * @returns {wp.media.controller.Region} Returns itself to allow chaining.
     1397         */
     1398        mode: function( mode ) {
     1399                if ( ! mode ) {
     1400                        return this._mode;
     1401                }
     1402                // Bail if we're trying to change to the current mode.
     1403                if ( mode === this._mode ) {
     1404                        return this;
     1405                }
     1406
     1407                /**
     1408                 * Region mode deactivation event.
     1409                 *
     1410                 * @event this.view#{this.id}:deactivate:{this._mode}
     1411                 * @event this.view#{this.id}:deactivate
     1412                 */
     1413                this.trigger('deactivate');
     1414
     1415                this._mode = mode;
     1416                this.render( mode );
     1417
     1418                /**
     1419                 * Region mode activation event.
     1420                 *
     1421                 * @event this.view#{this.id}:activate:{this._mode}
     1422                 * @event this.view#{this.id}:activate
     1423                 */
     1424                this.trigger('activate');
     1425                return this;
     1426        },
     1427        /**
     1428         * Render a mode.
     1429         *
     1430         * @since 3.5.0
     1431         *
     1432         * @param {string} mode
     1433         *
     1434         * @fires this.view#{this.id}:create:{this._mode}
     1435         * @fires this.view#{this.id}:create
     1436         * @fires this.view#{this.id}:render:{this._mode}
     1437         * @fires this.view#{this.id}:render
     1438         *
     1439         * @returns {wp.media.controller.Region} Returns itself to allow chaining
     1440         */
     1441        render: function( mode ) {
     1442                // If the mode isn't active, activate it.
     1443                if ( mode && mode !== this._mode ) {
     1444                        return this.mode( mode );
     1445                }
     1446
     1447                var set = { view: null },
     1448                        view;
     1449
     1450                /**
     1451                 * Create region view event.
     1452                 *
     1453                 * Region view creation takes place in an event callback on the frame.
     1454                 *
     1455                 * @event this.view#{this.id}:create:{this._mode}
     1456                 * @event this.view#{this.id}:create
     1457                 */
     1458                this.trigger( 'create', set );
     1459                view = set.view;
     1460
     1461                /**
     1462                 * Render region view event.
     1463                 *
     1464                 * Region view creation takes place in an event callback on the frame.
     1465                 *
     1466                 * @event this.view#{this.id}:create:{this._mode}
     1467                 * @event this.view#{this.id}:create
     1468                 */
     1469                this.trigger( 'render', view );
     1470                if ( view ) {
     1471                        this.set( view );
     1472                }
     1473                return this;
     1474        },
     1475
     1476        /**
     1477         * Get the region's view.
     1478         *
     1479         * @since 3.5.0
     1480         *
     1481         * @returns {wp.media.View}
     1482         */
     1483        get: function() {
     1484                return this.view.views.first( this.selector );
     1485        },
     1486
     1487        /**
     1488         * Set the region's view as a subview of the frame.
     1489         *
     1490         * @since 3.5.0
     1491         *
     1492         * @param {Array|Object} views
     1493         * @param {Object} [options={}]
     1494         * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
     1495         */
     1496        set: function( views, options ) {
     1497                if ( options ) {
     1498                        options.add = false;
     1499                }
     1500                return this.view.views.set( this.selector, views, options );
     1501        },
     1502
     1503        /**
     1504         * Trigger regional view events on the frame.
     1505         *
     1506         * @since 3.5.0
     1507         *
     1508         * @param {string} event
     1509         * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
     1510         */
     1511        trigger: function( event ) {
     1512                var base, args;
     1513
     1514                if ( ! this._mode ) {
    15771515                        return;
    15781516                }
    15791517
    1580                 library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
    1581 
    1582                 obj[ collectionType ] = new SettingsView({
    1583                         controller: this,
    1584                         model:      library[ collectionType ],
    1585                         priority:   40
    1586                 });
    1587 
    1588                 attachmentsBrowserView.sidebar.set( obj );
    1589 
    1590                 if ( dragInfoText ) {
    1591                         attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
    1592                                 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
    1593                                 priority: -40
    1594                         }) );
    1595                 }
    1596 
    1597                 // Add the 'Reverse order' button to the toolbar.
    1598                 attachmentsBrowserView.toolbar.set( 'reverse', {
    1599                         text:     l10n.reverseOrder,
    1600                         priority: 80,
    1601 
    1602                         click: function() {
    1603                                 library.reset( library.toArray().reverse() );
    1604                         }
    1605                 });
     1518                args = _.toArray( arguments );
     1519                base = this.id + ':' + event;
     1520
     1521                // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
     1522                args[0] = base + ':' + this._mode;
     1523                this.view.trigger.apply( this.view, args );
     1524
     1525                // Trigger `{this.id}:{event}` event on the frame.
     1526                args[0] = base;
     1527                this.view.trigger.apply( this.view, args );
     1528                return this;
    16061529        }
    16071530});
    16081531
    1609 module.exports = CollectionEdit;
    1610 
    1611 
    1612 /***/ }),
    1613 /* 36 */
    1614 /***/ (function(module, exports) {
    1615 
    1616 /*globals wp, _ */
    1617 
    1618 /**
    1619  * wp.media.controller.CollectionAdd
    1620  *
    1621  * A state for adding attachments to a collection (e.g. video playlist).
    1622  *
    1623  * @class
    1624  * @augments wp.media.controller.Library
    1625  * @augments wp.media.controller.State
    1626  * @augments Backbone.Model
    1627  *
    1628  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    1629  * @param {string}                     [attributes.id=library]      Unique identifier.
    1630  * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
    1631  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    1632  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    1633  *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
    1634  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    1635  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    1636  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    1637  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    1638  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    1639  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    1640  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    1641  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    1642  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1643  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1644  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1645  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    1646  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    1647  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    1648  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    1649  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
    1650  */
    1651 var Selection = wp.media.model.Selection,
    1652         Library = wp.media.controller.Library,
    1653         CollectionAdd;
    1654 
    1655 CollectionAdd = Library.extend({
    1656         defaults: _.defaults( {
    1657                 // Selection defaults. @see media.model.Selection
    1658                 multiple:      'add',
    1659                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
    1660                 filterable:    'uploaded',
    1661 
    1662                 priority:      100,
    1663                 syncSelection: false
    1664         }, Library.prototype.defaults ),
    1665 
    1666         /**
    1667          * @since 3.9.0
    1668          */
    1669         initialize: function() {
    1670                 var collectionType = this.get('collectionType');
    1671 
    1672                 if ( 'video' === this.get( 'type' ) ) {
    1673                         collectionType = 'video-' + collectionType;
    1674                 }
    1675 
    1676                 this.set( 'id', collectionType + '-library' );
    1677                 this.set( 'toolbar', collectionType + '-add' );
    1678                 this.set( 'menu', collectionType );
    1679 
    1680                 // If we haven't been provided a `library`, create a `Selection`.
    1681                 if ( ! this.get('library') ) {
    1682                         this.set( 'library', wp.media.query({ type: this.get('type') }) );
    1683                 }
    1684                 Library.prototype.initialize.apply( this, arguments );
    1685         },
    1686 
    1687         /**
    1688          * @since 3.9.0
    1689          */
    1690         activate: function() {
    1691                 var library = this.get('library'),
    1692                         editLibrary = this.get('editLibrary'),
    1693                         edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
    1694 
    1695                 if ( editLibrary && editLibrary !== edit ) {
    1696                         library.unobserve( editLibrary );
    1697                 }
    1698 
    1699                 // Accepts attachments that exist in the original library and
    1700                 // that do not exist in gallery's library.
    1701                 library.validator = function( attachment ) {
    1702                         return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    1703                 };
    1704 
    1705                 // Reset the library to ensure that all attachments are re-added
    1706                 // to the collection. Do so silently, as calling `observe` will
    1707                 // trigger the `reset` event.
    1708                 library.reset( library.mirroring.models, { silent: true });
    1709                 library.observe( edit );
    1710                 this.set('editLibrary', edit);
    1711 
    1712                 Library.prototype.activate.apply( this, arguments );
    1713         }
    1714 });
    1715 
    1716 module.exports = CollectionAdd;
    1717 
    1718 
    1719 /***/ }),
    1720 /* 37 */
    1721 /***/ (function(module, exports) {
    1722 
    1723 /*globals wp, _ */
    1724 
    1725 /**
    1726  * wp.media.controller.FeaturedImage
    1727  *
    1728  * A state for selecting a featured image for a post.
    1729  *
    1730  * @class
    1731  * @augments wp.media.controller.Library
    1732  * @augments wp.media.controller.State
    1733  * @augments Backbone.Model
    1734  *
    1735  * @param {object}                     [attributes]                          The attributes hash passed to the state.
    1736  * @param {string}                     [attributes.id=featured-image]        Unique identifier.
    1737  * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
    1738  * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
    1739  *                                                                           If one is not supplied, a collection of all images will be created.
    1740  * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
    1741  * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
    1742  *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
    1743  * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
    1744  * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
    1745  * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
    1746  * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
    1747  * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
    1748  * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
    1749  *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
    1750  * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1751  * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
    1752  * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1753  * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
    1754  * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
    1755  */
    1756 var Attachment = wp.media.model.Attachment,
    1757         Library = wp.media.controller.Library,
    1758         l10n = wp.media.view.l10n,
    1759         FeaturedImage;
    1760 
    1761 FeaturedImage = Library.extend({
    1762         defaults: _.defaults({
    1763                 id:            'featured-image',
    1764                 title:         l10n.setFeaturedImageTitle,
    1765                 multiple:      false,
    1766                 filterable:    'uploaded',
    1767                 toolbar:       'featured-image',
    1768                 priority:      60,
    1769                 syncSelection: true
    1770         }, Library.prototype.defaults ),
    1771 
    1772         /**
    1773          * @since 3.5.0
    1774          */
    1775         initialize: function() {
    1776                 var library, comparator;
    1777 
    1778                 // If we haven't been provided a `library`, create a `Selection`.
    1779                 if ( ! this.get('library') ) {
    1780                         this.set( 'library', wp.media.query({ type: 'image' }) );
    1781                 }
    1782 
    1783                 Library.prototype.initialize.apply( this, arguments );
    1784 
    1785                 library    = this.get('library');
    1786                 comparator = library.comparator;
    1787 
    1788                 // Overload the library's comparator to push items that are not in
    1789                 // the mirrored query to the front of the aggregate collection.
    1790                 library.comparator = function( a, b ) {
    1791                         var aInQuery = !! this.mirroring.get( a.cid ),
    1792                                 bInQuery = !! this.mirroring.get( b.cid );
    1793 
    1794                         if ( ! aInQuery && bInQuery ) {
    1795                                 return -1;
    1796                         } else if ( aInQuery && ! bInQuery ) {
    1797                                 return 1;
    1798                         } else {
    1799                                 return comparator.apply( this, arguments );
    1800                         }
    1801                 };
    1802 
    1803                 // Add all items in the selection to the library, so any featured
    1804                 // images that are not initially loaded still appear.
    1805                 library.observe( this.get('selection') );
    1806         },
    1807 
    1808         /**
    1809          * @since 3.5.0
    1810          */
    1811         activate: function() {
    1812                 this.updateSelection();
    1813                 this.frame.on( 'open', this.updateSelection, this );
    1814 
    1815                 Library.prototype.activate.apply( this, arguments );
    1816         },
    1817 
    1818         /**
    1819          * @since 3.5.0
    1820          */
    1821         deactivate: function() {
    1822                 this.frame.off( 'open', this.updateSelection, this );
    1823 
    1824                 Library.prototype.deactivate.apply( this, arguments );
    1825         },
    1826 
    1827         /**
    1828          * @since 3.5.0
    1829          */
    1830         updateSelection: function() {
    1831                 var selection = this.get('selection'),
    1832                         id = wp.media.view.settings.post.featuredImageId,
    1833                         attachment;
    1834 
    1835                 if ( '' !== id && -1 !== id ) {
    1836                         attachment = Attachment.get( id );
    1837                         attachment.fetch();
    1838                 }
    1839 
    1840                 selection.reset( attachment ? [ attachment ] : [] );
    1841         }
    1842 });
    1843 
    1844 module.exports = FeaturedImage;
    1845 
    1846 
    1847 /***/ }),
    1848 /* 38 */
    1849 /***/ (function(module, exports) {
    1850 
     1532module.exports = Region;
     1533
     1534},{}],13:[function(require,module,exports){
    18511535/*globals wp, _ */
    18521536
    … …  
    19581642module.exports = ReplaceImage;
    19591643
    1960 
    1961 /***/ }),
    1962 /* 39 */
    1963 /***/ (function(module, exports) {
    1964 
    1965 /*globals wp */
     1644},{}],14:[function(require,module,exports){
     1645/*globals _, Backbone */
    19661646
    19671647/**
    1968  * wp.media.controller.EditImage
    1969  *
    1970  * A state for editing (cropping, etc.) an image.
     1648 * wp.media.controller.StateMachine
     1649 *
     1650 * A state machine keeps track of state. It is in one state at a time,
     1651 * and can change from one state to another.
     1652 *
     1653 * States are stored as models in a Backbone collection.
     1654 *
     1655 * @since 3.5.0
    19711656 *
    19721657 * @class
    1973  * @augments wp.media.controller.State
    19741658 * @augments Backbone.Model
    1975  *
    1976  * @param {object}                    attributes                      The attributes hash passed to the state.
    1977  * @param {wp.media.model.Attachment} attributes.model                The attachment.
    1978  * @param {string}                    [attributes.id=edit-image]      Unique identifier.
    1979  * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
    1980  * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
    1981  * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
    1982  * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
    1983  * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
     1659 * @mixin
     1660 * @mixes Backbone.Events
     1661 *
     1662 * @param {Array} states
    19841663 */
    1985 var l10n = wp.media.view.l10n,
    1986         EditImage;
    1987 
    1988 EditImage = wp.media.controller.State.extend({
    1989         defaults: {
    1990                 id:      'edit-image',
    1991                 title:   l10n.editImage,
    1992                 menu:    false,
    1993                 toolbar: 'edit-image',
    1994                 content: 'edit-image',
    1995                 url:     ''
    1996         },
    1997 
    1998         /**
    1999          * @since 3.9.0
    2000          */
    2001         activate: function() {
    2002                 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
    2003         },
    2004 
    2005         /**
    2006          * @since 3.9.0
    2007          */
    2008         deactivate: function() {
    2009                 this.stopListening( this.frame );
    2010         },
    2011 
    2012         /**
    2013          * @since 3.9.0
    2014          */
    2015         toolbar: function() {
    2016                 var frame = this.frame,
    2017                         lastState = frame.lastState(),
    2018                         previous = lastState && lastState.id;
    2019 
    2020                 frame.toolbar.set( new wp.media.view.Toolbar({
    2021                         controller: frame,
    2022                         items: {
    2023                                 back: {
    2024                                         style: 'primary',
    2025                                         text:     l10n.back,
    2026                                         priority: 20,
    2027                                         click:    function() {
    2028                                                 if ( previous ) {
    2029                                                         frame.setState( previous );
    2030                                                 } else {
    2031                                                         frame.close();
    2032                                                 }
    2033                                         }
    2034                                 }
    2035                         }
    2036                 }) );
     1664var StateMachine = function( states ) {
     1665        // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
     1666        this.states = new Backbone.Collection( states );
     1667};
     1668
     1669// Use Backbone's self-propagating `extend` inheritance method.
     1670StateMachine.extend = Backbone.Model.extend;
     1671
     1672_.extend( StateMachine.prototype, Backbone.Events, {
     1673        /**
     1674         * Fetch a state.
     1675         *
     1676         * If no `id` is provided, returns the active state.
     1677         *
     1678         * Implicitly creates states.
     1679         *
     1680         * Ensure that the `states` collection exists so the `StateMachine`
     1681         *   can be used as a mixin.
     1682         *
     1683         * @since 3.5.0
     1684         *
     1685         * @param {string} id
     1686         * @returns {wp.media.controller.State} Returns a State model
     1687         *   from the StateMachine collection
     1688         */
     1689        state: function( id ) {
     1690                this.states = this.states || new Backbone.Collection();
     1691
     1692                // Default to the active state.
     1693                id = id || this._state;
     1694
     1695                if ( id && ! this.states.get( id ) ) {
     1696                        this.states.add({ id: id });
     1697                }
     1698                return this.states.get( id );
     1699        },
     1700
     1701        /**
     1702         * Sets the active state.
     1703         *
     1704         * Bail if we're trying to select the current state, if we haven't
     1705         * created the `states` collection, or are trying to select a state
     1706         * that does not exist.
     1707         *
     1708         * @since 3.5.0
     1709         *
     1710         * @param {string} id
     1711         *
     1712         * @fires wp.media.controller.State#deactivate
     1713         * @fires wp.media.controller.State#activate
     1714         *
     1715         * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
     1716         */
     1717        setState: function( id ) {
     1718                var previous = this.state();
     1719
     1720                if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
     1721                        return this;
     1722                }
     1723
     1724                if ( previous ) {
     1725                        previous.trigger('deactivate');
     1726                        this._lastState = previous.id;
     1727                }
     1728
     1729                this._state = id;
     1730                this.state().trigger('activate');
     1731
     1732                return this;
     1733        },
     1734
     1735        /**
     1736         * Returns the previous active state.
     1737         *
     1738         * Call the `state()` method with no parameters to retrieve the current
     1739         * active state.
     1740         *
     1741         * @since 3.5.0
     1742         *
     1743         * @returns {wp.media.controller.State} Returns a State model
     1744         *    from the StateMachine collection
     1745         */
     1746        lastState: function() {
     1747                if ( this._lastState ) {
     1748                        return this.state( this._lastState );
     1749                }
    20371750        }
    20381751});
    20391752
    2040 module.exports = EditImage;
    2041 
    2042 
    2043 /***/ }),
    2044 /* 40 */
    2045 /***/ (function(module, exports) {
    2046 
    2047 /*globals wp, _ */
    2048 
    2049 /**
    2050  * wp.media.controller.MediaLibrary
    2051  *
    2052  * @class
    2053  * @augments wp.media.controller.Library
    2054  * @augments wp.media.controller.State
    2055  * @augments Backbone.Model
    2056  */
    2057 var Library = wp.media.controller.Library,
    2058         MediaLibrary;
    2059 
    2060 MediaLibrary = Library.extend({
    2061         defaults: _.defaults({
    2062                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
    2063                 filterable:      'uploaded',
    2064 
    2065                 displaySettings: false,
    2066                 priority:        80,
    2067                 syncSelection:   false
    2068         }, Library.prototype.defaults ),
    2069 
    2070         /**
    2071          * @since 3.9.0
    2072          *
    2073          * @param options
    2074          */
    2075         initialize: function( options ) {
    2076                 this.media = options.media;
    2077                 this.type = options.type;
    2078                 this.set( 'library', wp.media.query({ type: this.type }) );
    2079 
    2080                 Library.prototype.initialize.apply( this, arguments );
    2081         },
    2082 
    2083         /**
    2084          * @since 3.9.0
    2085          */
    2086         activate: function() {
    2087                 // @todo this should use this.frame.
    2088                 if ( wp.media.frame.lastMime ) {
    2089                         this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
    2090                         delete wp.media.frame.lastMime;
    2091                 }
    2092                 Library.prototype.activate.apply( this, arguments );
    2093         }
    2094 });
    2095 
    2096 module.exports = MediaLibrary;
    2097 
    2098 
    2099 /***/ }),
    2100 /* 41 */
    2101 /***/ (function(module, exports) {
    2102 
    2103 /*globals wp, _, Backbone */
    2104 
    2105 /**
    2106  * wp.media.controller.Embed
    2107  *
    2108  * A state for embedding media from a URL.
    2109  *
    2110  * @class
    2111  * @augments wp.media.controller.State
    2112  * @augments Backbone.Model
    2113  *
    2114  * @param {object} attributes                         The attributes hash passed to the state.
    2115  * @param {string} [attributes.id=embed]              Unique identifier.
    2116  * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
    2117  * @param {string} [attributes.content=embed]         Initial mode for the content region.
    2118  * @param {string} [attributes.menu=default]          Initial mode for the menu region.
    2119  * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
    2120  * @param {string} [attributes.menu=false]            Initial mode for the menu region.
    2121  * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
    2122  * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
    2123  * @param {string} [attributes.url]                   The embed URL.
    2124  * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
    2125  */
    2126 var l10n = wp.media.view.l10n,
    2127         $ = Backbone.$,
    2128         Embed;
    2129 
    2130 Embed = wp.media.controller.State.extend({
    2131         defaults: {
    2132                 id:       'embed',
    2133                 title:    l10n.insertFromUrlTitle,
    2134                 content:  'embed',
    2135                 menu:     'default',
    2136                 toolbar:  'main-embed',
    2137                 priority: 120,
    2138                 type:     'link',
    2139                 url:      '',
    2140                 metadata: {}
    2141         },
    2142 
    2143         // The amount of time used when debouncing the scan.
    2144         sensitivity: 200,
    2145 
    2146         initialize: function(options) {
    2147                 this.metadata = options.metadata;
    2148                 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
    2149                 this.props = new Backbone.Model( this.metadata || { url: '' });
    2150                 this.props.on( 'change:url', this.debouncedScan, this );
    2151                 this.props.on( 'change:url', this.refresh, this );
    2152                 this.on( 'scan', this.scanImage, this );
    2153         },
    2154 
    2155         /**
    2156          * Trigger a scan of the embedded URL's content for metadata required to embed.
    2157          *
    2158          * @fires wp.media.controller.Embed#scan
    2159          */
    2160         scan: function() {
    2161                 var scanners,
    2162                         embed = this,
    2163                         attributes = {
    2164                                 type: 'link',
    2165                                 scanners: []
    2166                         };
    2167 
    2168                 // Scan is triggered with the list of `attributes` to set on the
    2169                 // state, useful for the 'type' attribute and 'scanners' attribute,
    2170                 // an array of promise objects for asynchronous scan operations.
    2171                 if ( this.props.get('url') ) {
    2172                         this.trigger( 'scan', attributes );
    2173                 }
    2174 
    2175                 if ( attributes.scanners.length ) {
    2176                         scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
    2177                         scanners.always( function() {
    2178                                 if ( embed.get('scanners') === scanners ) {
    2179                                         embed.set( 'loading', false );
    2180                                 }
    2181                         });
    2182                 } else {
    2183                         attributes.scanners = null;
    2184                 }
    2185 
    2186                 attributes.loading = !! attributes.scanners;
    2187                 this.set( attributes );
    2188         },
    2189         /**
    2190          * Try scanning the embed as an image to discover its dimensions.
    2191          *
    2192          * @param {Object} attributes
    2193          */
    2194         scanImage: function( attributes ) {
    2195                 var frame = this.frame,
    2196                         state = this,
    2197                         url = this.props.get('url'),
    2198                         image = new Image(),
    2199                         deferred = $.Deferred();
    2200 
    2201                 attributes.scanners.push( deferred.promise() );
    2202 
    2203                 // Try to load the image and find its width/height.
    2204                 image.onload = function() {
    2205                         deferred.resolve();
    2206 
    2207                         if ( state !== frame.state() || url !== state.props.get('url') ) {
    2208                                 return;
    2209                         }
    2210 
    2211                         state.set({
    2212                                 type: 'image'
    2213                         });
    2214 
    2215                         state.props.set({
    2216                                 width:  image.width,
    2217                                 height: image.height
    2218                         });
    2219                 };
    2220 
    2221                 image.onerror = deferred.reject;
    2222                 image.src = url;
    2223         },
    2224 
    2225         refresh: function() {
    2226                 this.frame.toolbar.get().refresh();
    2227         },
    2228 
    2229         reset: function() {
    2230                 this.props.clear().set({ url: '' });
    2231 
    2232                 if ( this.active ) {
    2233                         this.refresh();
    2234                 }
    2235         }
    2236 });
    2237 
    2238 module.exports = Embed;
    2239 
    2240 
    2241 /***/ }),
    2242 /* 42 */
    2243 /***/ (function(module, exports) {
    2244 
    2245 /*globals wp, _, Backbone */
    2246 
    2247 /**
    2248  * wp.media.controller.Cropper
    2249  *
    2250  * A state for cropping an image.
    2251  *
    2252  * @class
    2253  * @augments wp.media.controller.State
    2254  * @augments Backbone.Model
    2255  */
    2256 var l10n = wp.media.view.l10n,
    2257         Cropper;
    2258 
    2259 Cropper = wp.media.controller.State.extend({
    2260         defaults: {
    2261                 id:          'cropper',
    2262                 title:       l10n.cropImage,
    2263                 // Region mode defaults.
    2264                 toolbar:     'crop',
    2265                 content:     'crop',
    2266                 router:      false,
    2267 
    2268                 canSkipCrop: false
    2269         },
    2270 
    2271         activate: function() {
    2272                 this.frame.on( 'content:create:crop', this.createCropContent, this );
    2273                 this.frame.on( 'close', this.removeCropper, this );
    2274                 this.set('selection', new Backbone.Collection(this.frame._selection.single));
    2275         },
    2276 
    2277         deactivate: function() {
    2278                 this.frame.toolbar.mode('browse');
    2279         },
    2280 
    2281         createCropContent: function() {
    2282                 this.cropperView = new wp.media.view.Cropper({
    2283                         controller: this,
    2284                         attachment: this.get('selection').first()
    2285                 });
    2286                 this.cropperView.on('image-loaded', this.createCropToolbar, this);
    2287                 this.frame.content.set(this.cropperView);
    2288 
    2289         },
    2290         removeCropper: function() {
    2291                 this.imgSelect.cancelSelection();
    2292                 this.imgSelect.setOptions({remove: true});
    2293                 this.imgSelect.update();
    2294                 this.cropperView.remove();
    2295         },
    2296         createCropToolbar: function() {
    2297                 var canSkipCrop, toolbarOptions;
    2298 
    2299                 canSkipCrop = this.get('canSkipCrop') || false;
    2300 
    2301                 toolbarOptions = {
    2302                         controller: this.frame,
    2303                         items: {
    2304                                 insert: {
    2305                                         style:    'primary',
    2306                                         text:     l10n.cropImage,
    2307                                         priority: 80,
    2308                                         requires: { library: false, selection: false },
    2309 
    2310                                         click: function() {
    2311                                                 var controller = this.controller,
    2312                                                         selection;
    2313 
    2314                                                 selection = controller.state().get('selection').first();
    2315                                                 selection.set({cropDetails: controller.state().imgSelect.getSelection()});
    2316 
    2317                                                 this.$el.text(l10n.cropping);
    2318                                                 this.$el.attr('disabled', true);
    2319 
    2320                                                 controller.state().doCrop( selection ).done( function( croppedImage ) {
    2321                                                         controller.trigger('cropped', croppedImage );
    2322                                                         controller.close();
    2323                                                 }).fail( function() {
    2324                                                         controller.trigger('content:error:crop');
    2325                                                 });
    2326                                         }
    2327                                 }
    2328                         }
    2329                 };
    2330 
    2331                 if ( canSkipCrop ) {
    2332                         _.extend( toolbarOptions.items, {
    2333                                 skip: {
    2334                                         style:      'secondary',
    2335                                         text:       l10n.skipCropping,
    2336                                         priority:   70,
    2337                                         requires:   { library: false, selection: false },
    2338                                         click:      function() {
    2339                                                 var selection = this.controller.state().get('selection').first();
    2340                                                 this.controller.state().cropperView.remove();
    2341                                                 this.controller.trigger('skippedcrop', selection);
    2342                                                 this.controller.close();
    2343                                         }
    2344                                 }
    2345                         });
    2346                 }
    2347 
    2348                 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
    2349         },
    2350 
    2351         doCrop: function( attachment ) {
    2352                 return wp.ajax.post( 'custom-header-crop', {
    2353                         nonce: attachment.get('nonces').edit,
    2354                         id: attachment.get('id'),
    2355                         cropDetails: attachment.get('cropDetails')
    2356                 } );
    2357         }
    2358 });
    2359 
    2360 module.exports = Cropper;
    2361 
    2362 
    2363 /***/ }),
    2364 /* 43 */,
    2365 /* 44 */,
    2366 /* 45 */
    2367 /***/ (function(module, exports) {
    2368 
    2369 /*globals wp */
    2370 
    2371 /**
    2372  * wp.media.View
    2373  *
    2374  * The base view class for media.
    2375  *
    2376  * Undelegating events, removing events from the model, and
    2377  * removing events from the controller mirror the code for
    2378  * `Backbone.View.dispose` in Backbone 0.9.8 development.
    2379  *
    2380  * This behavior has since been removed, and should not be used
    2381  * outside of the media manager.
    2382  *
    2383  * @class
    2384  * @augments wp.Backbone.View
    2385  * @augments Backbone.View
    2386  */
    2387 var View = wp.Backbone.View.extend({
    2388         constructor: function( options ) {
    2389                 if ( options && options.controller ) {
    2390                         this.controller = options.controller;
    2391                 }
    2392                 wp.Backbone.View.apply( this, arguments );
    2393         },
    2394         /**
    2395          * @todo The internal comment mentions this might have been a stop-gap
    2396          *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
    2397          *       care of this in Backbone.View now.
    2398          *
    2399          * @returns {wp.media.View} Returns itself to allow chaining
    2400          */
    2401         dispose: function() {
    2402                 // Undelegating events, removing events from the model, and
    2403                 // removing events from the controller mirror the code for
    2404                 // `Backbone.View.dispose` in Backbone 0.9.8 development.
    2405                 this.undelegateEvents();
    2406 
    2407                 if ( this.model && this.model.off ) {
    2408                         this.model.off( null, null, this );
    2409                 }
    2410 
    2411                 if ( this.collection && this.collection.off ) {
    2412                         this.collection.off( null, null, this );
    2413                 }
    2414 
    2415                 // Unbind controller events.
    2416                 if ( this.controller && this.controller.off ) {
    2417                         this.controller.off( null, null, this );
    2418                 }
    2419 
    2420                 return this;
    2421         },
    2422         /**
    2423          * @returns {wp.media.View} Returns itself to allow chaining
    2424          */
    2425         remove: function() {
    2426                 this.dispose();
    2427                 /**
    2428                  * call 'remove' directly on the parent class
    2429                  */
    2430                 return wp.Backbone.View.prototype.remove.apply( this, arguments );
    2431         }
    2432 });
    2433 
    2434 module.exports = View;
    2435 
    2436 
    2437 /***/ }),
    2438 /* 46 */
    2439 /***/ (function(module, exports) {
    2440 
    2441 /*globals _, Backbone */
    2442 
    2443 /**
    2444  * wp.media.view.Frame
    2445  *
    2446  * A frame is a composite view consisting of one or more regions and one or more
    2447  * states.
    2448  *
    2449  * @see wp.media.controller.State
    2450  * @see wp.media.controller.Region
    2451  *
    2452  * @class
    2453  * @augments wp.media.View
    2454  * @augments wp.Backbone.View
    2455  * @augments Backbone.View
    2456  * @mixes wp.media.controller.StateMachine
    2457  */
    2458 var Frame = wp.media.View.extend({
    2459         initialize: function() {
    2460                 _.defaults( this.options, {
    2461                         mode: [ 'select' ]
    2462                 });
    2463                 this._createRegions();
    2464                 this._createStates();
    2465                 this._createModes();
    2466         },
    2467 
    2468         _createRegions: function() {
    2469                 // Clone the regions array.
    2470                 this.regions = this.regions ? this.regions.slice() : [];
    2471 
    2472                 // Initialize regions.
    2473                 _.each( this.regions, function( region ) {
    2474                         this[ region ] = new wp.media.controller.Region({
    2475                                 view:     this,
    2476                                 id:       region,
    2477                                 selector: '.media-frame-' + region
    2478                         });
    2479                 }, this );
    2480         },
    2481         /**
    2482          * Create the frame's states.
    2483          *
    2484          * @see wp.media.controller.State
    2485          * @see wp.media.controller.StateMachine
    2486          *
    2487          * @fires wp.media.controller.State#ready
    2488          */
    2489         _createStates: function() {
    2490                 // Create the default `states` collection.
    2491                 this.states = new Backbone.Collection( null, {
    2492                         model: wp.media.controller.State
    2493                 });
    2494 
    2495                 // Ensure states have a reference to the frame.
    2496                 this.states.on( 'add', function( model ) {
    2497                         model.frame = this;
    2498                         model.trigger('ready');
    2499                 }, this );
    2500 
    2501                 if ( this.options.states ) {
    2502                         this.states.add( this.options.states );
    2503                 }
    2504         },
    2505 
    2506         /**
    2507          * A frame can be in a mode or multiple modes at one time.
    2508          *
    2509          * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
    2510          */
    2511         _createModes: function() {
    2512                 // Store active "modes" that the frame is in. Unrelated to region modes.
    2513                 this.activeModes = new Backbone.Collection();
    2514                 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
    2515 
    2516                 _.each( this.options.mode, function( mode ) {
    2517                         this.activateMode( mode );
    2518                 }, this );
    2519         },
    2520         /**
    2521          * Reset all states on the frame to their defaults.
    2522          *
    2523          * @returns {wp.media.view.Frame} Returns itself to allow chaining
    2524          */
    2525         reset: function() {
    2526                 this.states.invoke( 'trigger', 'reset' );
    2527                 return this;
    2528         },
    2529         /**
    2530          * Map activeMode collection events to the frame.
    2531          */
    2532         triggerModeEvents: function( model, collection, options ) {
    2533                 var collectionEvent,
    2534                         modeEventMap = {
    2535                                 add: 'activate',
    2536                                 remove: 'deactivate'
    2537                         },
    2538                         eventToTrigger;
    2539                 // Probably a better way to do this.
    2540                 _.each( options, function( value, key ) {
    2541                         if ( value ) {
    2542                                 collectionEvent = key;
    2543                         }
    2544                 } );
    2545 
    2546                 if ( ! _.has( modeEventMap, collectionEvent ) ) {
    2547                         return;
    2548                 }
    2549 
    2550                 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
    2551                 this.trigger( eventToTrigger );
    2552         },
    2553         /**
    2554          * Activate a mode on the frame.
    2555          *
    2556          * @param string mode Mode ID.
    2557          * @returns {this} Returns itself to allow chaining.
    2558          */
    2559         activateMode: function( mode ) {
    2560                 // Bail if the mode is already active.
    2561                 if ( this.isModeActive( mode ) ) {
    2562                         return;
    2563                 }
    2564                 this.activeModes.add( [ { id: mode } ] );
    2565                 // Add a CSS class to the frame so elements can be styled for the mode.
    2566                 this.$el.addClass( 'mode-' + mode );
    2567 
    2568                 return this;
    2569         },
    2570         /**
    2571          * Deactivate a mode on the frame.
    2572          *
    2573          * @param string mode Mode ID.
    2574          * @returns {this} Returns itself to allow chaining.
    2575          */
    2576         deactivateMode: function( mode ) {
    2577                 // Bail if the mode isn't active.
    2578                 if ( ! this.isModeActive( mode ) ) {
    2579                         return this;
    2580                 }
    2581                 this.activeModes.remove( this.activeModes.where( { id: mode } ) );
    2582                 this.$el.removeClass( 'mode-' + mode );
    2583                 /**
    2584                  * Frame mode deactivation event.
    2585                  *
    2586                  * @event this#{mode}:deactivate
    2587                  */
    2588                 this.trigger( mode + ':deactivate' );
    2589 
    2590                 return this;
    2591         },
    2592         /**
    2593          * Check if a mode is enabled on the frame.
    2594          *
    2595          * @param  string mode Mode ID.
    2596          * @return bool
    2597          */
    2598         isModeActive: function( mode ) {
    2599                 return Boolean( this.activeModes.where( { id: mode } ).length );
    2600         }
    2601 });
    2602 
    2603 // Make the `Frame` a `StateMachine`.
    2604 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
    2605 
    2606 module.exports = Frame;
    2607 
    2608 
    2609 /***/ }),
    2610 /* 47 */
    2611 /***/ (function(module, exports) {
    2612 
    2613 /*globals wp, _, jQuery */
    2614 
    2615 /**
    2616  * wp.media.view.MediaFrame
    2617  *
    2618  * The frame used to create the media modal.
    2619  *
    2620  * @class
    2621  * @augments wp.media.view.Frame
    2622  * @augments wp.media.View
    2623  * @augments wp.Backbone.View
    2624  * @augments Backbone.View
    2625  * @mixes wp.media.controller.StateMachine
    2626  */
    2627 var Frame = wp.media.view.Frame,
    2628         $ = jQuery,
    2629         MediaFrame;
    2630 
    2631 MediaFrame = Frame.extend({
    2632         className: 'media-frame',
    2633         template:  wp.template('media-frame'),
    2634         regions:   ['menu','title','content','toolbar','router'],
    2635 
    2636         events: {
    2637                 'click div.media-frame-title h1': 'toggleMenu'
    2638         },
    2639 
    2640         /**
    2641          * @global wp.Uploader
    2642          */
    2643         initialize: function() {
    2644                 Frame.prototype.initialize.apply( this, arguments );
    2645 
    2646                 _.defaults( this.options, {
    2647                         title:    '',
    2648                         modal:    true,
    2649                         uploader: true
    2650                 });
    2651 
    2652                 // Ensure core UI is enabled.
    2653                 this.$el.addClass('wp-core-ui');
    2654 
    2655                 // Initialize modal container view.
    2656                 if ( this.options.modal ) {
    2657                         this.modal = new wp.media.view.Modal({
    2658                                 controller: this,
    2659                                 title:      this.options.title
    2660                         });
    2661 
    2662                         this.modal.content( this );
    2663                 }
    2664 
    2665                 // Force the uploader off if the upload limit has been exceeded or
    2666                 // if the browser isn't supported.
    2667                 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
    2668                         this.options.uploader = false;
    2669                 }
    2670 
    2671                 // Initialize window-wide uploader.
    2672                 if ( this.options.uploader ) {
    2673                         this.uploader = new wp.media.view.UploaderWindow({
    2674                                 controller: this,
    2675                                 uploader: {
    2676                                         dropzone:  this.modal ? this.modal.$el : this.$el,
    2677                                         container: this.$el
    2678                                 }
    2679                         });
    2680                         this.views.set( '.media-frame-uploader', this.uploader );
    2681                 }
    2682 
    2683                 this.on( 'attach', _.bind( this.views.ready, this.views ), this );
    2684 
    2685                 // Bind default title creation.
    2686                 this.on( 'title:create:default', this.createTitle, this );
    2687                 this.title.mode('default');
    2688 
    2689                 this.on( 'title:render', function( view ) {
    2690                         view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
    2691                 });
    2692 
    2693                 // Bind default menu.
    2694                 this.on( 'menu:create:default', this.createMenu, this );
    2695         },
    2696         /**
    2697          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    2698          */
    2699         render: function() {
    2700                 // Activate the default state if no active state exists.
    2701                 if ( ! this.state() && this.options.state ) {
    2702                         this.setState( this.options.state );
    2703                 }
    2704                 /**
    2705                  * call 'render' directly on the parent class
    2706                  */
    2707                 return Frame.prototype.render.apply( this, arguments );
    2708         },
    2709         /**
    2710          * @param {Object} title
    2711          * @this wp.media.controller.Region
    2712          */
    2713         createTitle: function( title ) {
    2714                 title.view = new wp.media.View({
    2715                         controller: this,
    2716                         tagName: 'h1'
    2717                 });
    2718         },
    2719         /**
    2720          * @param {Object} menu
    2721          * @this wp.media.controller.Region
    2722          */
    2723         createMenu: function( menu ) {
    2724                 menu.view = new wp.media.view.Menu({
    2725                         controller: this
    2726                 });
    2727         },
    2728 
    2729         toggleMenu: function() {
    2730                 this.$el.find( '.media-menu' ).toggleClass( 'visible' );
    2731         },
    2732 
    2733         /**
    2734          * @param {Object} toolbar
    2735          * @this wp.media.controller.Region
    2736          */
    2737         createToolbar: function( toolbar ) {
    2738                 toolbar.view = new wp.media.view.Toolbar({
    2739                         controller: this
    2740                 });
    2741         },
    2742         /**
    2743          * @param {Object} router
    2744          * @this wp.media.controller.Region
    2745          */
    2746         createRouter: function( router ) {
    2747                 router.view = new wp.media.view.Router({
    2748                         controller: this
    2749                 });
    2750         },
    2751         /**
    2752          * @param {Object} options
    2753          */
    2754         createIframeStates: function( options ) {
    2755                 var settings = wp.media.view.settings,
    2756                         tabs = settings.tabs,
    2757                         tabUrl = settings.tabUrl,
    2758                         $postId;
    2759 
    2760                 if ( ! tabs || ! tabUrl ) {
    2761                         return;
    2762                 }
    2763 
    2764                 // Add the post ID to the tab URL if it exists.
    2765                 $postId = $('#post_ID');
    2766                 if ( $postId.length ) {
    2767                         tabUrl += '&post_id=' + $postId.val();
    2768                 }
    2769 
    2770                 // Generate the tab states.
    2771                 _.each( tabs, function( title, id ) {
    2772                         this.state( 'iframe:' + id ).set( _.defaults({
    2773                                 tab:     id,
    2774                                 src:     tabUrl + '&tab=' + id,
    2775                                 title:   title,
    2776                                 content: 'iframe',
    2777                                 menu:    'default'
    2778                         }, options ) );
    2779                 }, this );
    2780 
    2781                 this.on( 'content:create:iframe', this.iframeContent, this );
    2782                 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
    2783                 this.on( 'menu:render:default', this.iframeMenu, this );
    2784                 this.on( 'open', this.hijackThickbox, this );
    2785                 this.on( 'close', this.restoreThickbox, this );
    2786         },
    2787 
    2788         /**
    2789          * @param {Object} content
    2790          * @this wp.media.controller.Region
    2791          */
    2792         iframeContent: function( content ) {
    2793                 this.$el.addClass('hide-toolbar');
    2794                 content.view = new wp.media.view.Iframe({
    2795                         controller: this
    2796                 });
    2797         },
    2798 
    2799         iframeContentCleanup: function() {
    2800                 this.$el.removeClass('hide-toolbar');
    2801         },
    2802 
    2803         iframeMenu: function( view ) {
    2804                 var views = {};
    2805 
    2806                 if ( ! view ) {
    2807                         return;
    2808                 }
    2809 
    2810                 _.each( wp.media.view.settings.tabs, function( title, id ) {
    2811                         views[ 'iframe:' + id ] = {
    2812                                 text: this.state( 'iframe:' + id ).get('title'),
    2813                                 priority: 200
    2814                         };
    2815                 }, this );
    2816 
    2817                 view.set( views );
    2818         },
    2819 
    2820         hijackThickbox: function() {
    2821                 var frame = this;
    2822 
    2823                 if ( ! window.tb_remove || this._tb_remove ) {
    2824                         return;
    2825                 }
    2826 
    2827                 this._tb_remove = window.tb_remove;
    2828                 window.tb_remove = function() {
    2829                         frame.close();
    2830                         frame.reset();
    2831                         frame.setState( frame.options.state );
    2832                         frame._tb_remove.call( window );
    2833                 };
    2834         },
    2835 
    2836         restoreThickbox: function() {
    2837                 if ( ! this._tb_remove ) {
    2838                         return;
    2839                 }
    2840 
    2841                 window.tb_remove = this._tb_remove;
    2842                 delete this._tb_remove;
    2843         }
    2844 });
    2845 
    2846 // Map some of the modal's methods to the frame.
    2847 _.each(['open','close','attach','detach','escape'], function( method ) {
    2848         /**
    2849          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    2850          */
    2851         MediaFrame.prototype[ method ] = function() {
    2852                 if ( this.modal ) {
    2853                         this.modal[ method ].apply( this.modal, arguments );
    2854                 }
     1753// Map all event binding and triggering on a StateMachine to its `states` collection.
     1754_.each([ 'on', 'off', 'trigger' ], function( method ) {
     1755        /**
     1756         * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     1757         */
     1758        StateMachine.prototype[ method ] = function() {
     1759                // Ensure that the `states` collection exists so the `StateMachine`
     1760                // can be used as a mixin.
     1761                this.states = this.states || new Backbone.Collection();
     1762                // Forward the method to the `states` collection.
     1763                this.states[ method ].apply( this.states, arguments );
    28551764                return this;
    28561765        };
    28571766});
    28581767
    2859 module.exports = MediaFrame;
    2860 
    2861 
    2862 /***/ }),
    2863 /* 48 */
    2864 /***/ (function(module, exports) {
    2865 
    2866 /*globals wp, _ */
     1768module.exports = StateMachine;
     1769
     1770},{}],15:[function(require,module,exports){
     1771/*globals _, Backbone */
    28671772
    28681773/**
    2869  * wp.media.view.MediaFrame.Select
    2870  *
    2871  * A frame for selecting an item or items from the media library.
     1774 * wp.media.controller.State
     1775 *
     1776 * A state is a step in a workflow that when set will trigger the controllers
     1777 * for the regions to be updated as specified in the frame.
     1778 *
     1779 * A state has an event-driven lifecycle:
     1780 *
     1781 *     'ready'      triggers when a state is added to a state machine's collection.
     1782 *     'activate'   triggers when a state is activated by a state machine.
     1783 *     'deactivate' triggers when a state is deactivated by a state machine.
     1784 *     'reset'      is not triggered automatically. It should be invoked by the
     1785 *                  proper controller to reset the state to its default.
    28721786 *
    28731787 * @class
    2874  * @augments wp.media.view.MediaFrame
    2875  * @augments wp.media.view.Frame
    2876  * @augments wp.media.View
    2877  * @augments wp.Backbone.View
    2878  * @augments Backbone.View
    2879  * @mixes wp.media.controller.StateMachine
     1788 * @augments Backbone.Model
    28801789 */
    2881 
    2882 var MediaFrame = wp.media.view.MediaFrame,
    2883         l10n = wp.media.view.l10n,
    2884         Select;
    2885 
    2886 Select = MediaFrame.extend({
    2887         initialize: function() {
    2888                 // Call 'initialize' directly on the parent class.
    2889                 MediaFrame.prototype.initialize.apply( this, arguments );
    2890 
    2891                 _.defaults( this.options, {
    2892                         selection: [],
    2893                         library:   {},
    2894                         multiple:  false,
    2895                         state:    'library'
    2896                 });
    2897 
    2898                 this.createSelection();
    2899                 this.createStates();
    2900                 this.bindHandlers();
    2901         },
    2902 
    2903         /**
    2904          * Attach a selection collection to the frame.
     1790var State = Backbone.Model.extend({
     1791        /**
     1792         * Constructor.
    29051793         *
    2906          * A selection is a collection of attachments used for a specific purpose
    2907          * by a media frame. e.g. Selecting an attachment (or many) to insert into
    2908          * post content.
     1794         * @since 3.5.0
     1795         */
     1796        constructor: function() {
     1797                this.on( 'activate', this._preActivate, this );
     1798                this.on( 'activate', this.activate, this );
     1799                this.on( 'activate', this._postActivate, this );
     1800                this.on( 'deactivate', this._deactivate, this );
     1801                this.on( 'deactivate', this.deactivate, this );
     1802                this.on( 'reset', this.reset, this );
     1803                this.on( 'ready', this._ready, this );
     1804                this.on( 'ready', this.ready, this );
     1805                /**
     1806                 * Call parent constructor with passed arguments
     1807                 */
     1808                Backbone.Model.apply( this, arguments );
     1809                this.on( 'change:menu', this._updateMenu, this );
     1810        },
     1811        /**
     1812         * Ready event callback.
    29091813         *
    2910          * @see media.model.Selection
    2911          */
    2912         createSelection: function() {
    2913                 var selection = this.options.selection;
    2914 
    2915                 if ( ! (selection instanceof wp.media.model.Selection) ) {
    2916                         this.options.selection = new wp.media.model.Selection( selection, {
    2917                                 multiple: this.options.multiple
    2918                         });
    2919                 }
    2920 
    2921                 this._selection = {
    2922                         attachments: new wp.media.model.Attachments(),
    2923                         difference: []
    2924                 };
    2925         },
    2926 
    2927         /**
    2928          * Create the default states on the frame.
    2929          */
    2930         createStates: function() {
    2931                 var options = this.options;
    2932 
    2933                 if ( this.options.states ) {
     1814         * @abstract
     1815         * @since 3.5.0
     1816         */
     1817        ready: function() {},
     1818
     1819        /**
     1820         * Activate event callback.
     1821         *
     1822         * @abstract
     1823         * @since 3.5.0
     1824         */
     1825        activate: function() {},
     1826
     1827        /**
     1828         * Deactivate event callback.
     1829         *
     1830         * @abstract
     1831         * @since 3.5.0
     1832         */
     1833        deactivate: function() {},
     1834
     1835        /**
     1836         * Reset event callback.
     1837         *
     1838         * @abstract
     1839         * @since 3.5.0
     1840         */
     1841        reset: function() {},
     1842
     1843        /**
     1844         * @access private
     1845         * @since 3.5.0
     1846         */
     1847        _ready: function() {
     1848                this._updateMenu();
     1849        },
     1850
     1851        /**
     1852         * @access private
     1853         * @since 3.5.0
     1854        */
     1855        _preActivate: function() {
     1856                this.active = true;
     1857        },
     1858
     1859        /**
     1860         * @access private
     1861         * @since 3.5.0
     1862         */
     1863        _postActivate: function() {
     1864                this.on( 'change:menu', this._menu, this );
     1865                this.on( 'change:titleMode', this._title, this );
     1866                this.on( 'change:content', this._content, this );
     1867                this.on( 'change:toolbar', this._toolbar, this );
     1868
     1869                this.frame.on( 'title:render:default', this._renderTitle, this );
     1870
     1871                this._title();
     1872                this._menu();
     1873                this._toolbar();
     1874                this._content();
     1875                this._router();
     1876        },
     1877
     1878        /**
     1879         * @access private
     1880         * @since 3.5.0
     1881         */
     1882        _deactivate: function() {
     1883                this.active = false;
     1884
     1885                this.frame.off( 'title:render:default', this._renderTitle, this );
     1886
     1887                this.off( 'change:menu', this._menu, this );
     1888                this.off( 'change:titleMode', this._title, this );
     1889                this.off( 'change:content', this._content, this );
     1890                this.off( 'change:toolbar', this._toolbar, this );
     1891        },
     1892
     1893        /**
     1894         * @access private
     1895         * @since 3.5.0
     1896         */
     1897        _title: function() {
     1898                this.frame.title.render( this.get('titleMode') || 'default' );
     1899        },
     1900
     1901        /**
     1902         * @access private
     1903         * @since 3.5.0
     1904         */
     1905        _renderTitle: function( view ) {
     1906                view.$el.text( this.get('title') || '' );
     1907        },
     1908
     1909        /**
     1910         * @access private
     1911         * @since 3.5.0
     1912         */
     1913        _router: function() {
     1914                var router = this.frame.router,
     1915                        mode = this.get('router'),
     1916                        view;
     1917
     1918                this.frame.$el.toggleClass( 'hide-router', ! mode );
     1919                if ( ! mode ) {
    29341920                        return;
    29351921                }
    29361922
    2937                 // Add the default states.
    2938                 this.states.add([
    2939                         // Main states.
    2940                         new wp.media.controller.Library({
    2941                                 library:   wp.media.query( options.library ),
    2942                                 multiple:  options.multiple,
    2943                                 title:     options.title,
    2944                                 priority:  20
    2945                         })
    2946                 ]);
    2947         },
    2948 
    2949         /**
    2950          * Bind region mode event callbacks.
     1923                this.frame.router.render( mode );
     1924
     1925                view = router.get();
     1926                if ( view && view.select ) {
     1927                        view.select( this.frame.content.mode() );
     1928                }
     1929        },
     1930
     1931        /**
     1932         * @access private
     1933         * @since 3.5.0
     1934         */
     1935        _menu: function() {
     1936                var menu = this.frame.menu,
     1937                        mode = this.get('menu'),
     1938                        view;
     1939
     1940                this.frame.$el.toggleClass( 'hide-menu', ! mode );
     1941                if ( ! mode ) {
     1942                        return;
     1943                }
     1944
     1945                menu.mode( mode );
     1946
     1947                view = menu.get();
     1948                if ( view && view.select ) {
     1949                        view.select( this.id );
     1950                }
     1951        },
     1952
     1953        /**
     1954         * @access private
     1955         * @since 3.5.0
     1956         */
     1957        _updateMenu: function() {
     1958                var previous = this.previous('menu'),
     1959                        menu = this.get('menu');
     1960
     1961                if ( previous ) {
     1962                        this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
     1963                }
     1964
     1965                if ( menu ) {
     1966                        this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
     1967                }
     1968        },
     1969
     1970        /**
     1971         * Create a view in the media menu for the state.
    29511972         *
    2952          * @see media.controller.Region.render
    2953          */
    2954         bindHandlers: function() {
    2955                 this.on( 'router:create:browse', this.createRouter, this );
    2956                 this.on( 'router:render:browse', this.browseRouter, this );
    2957                 this.on( 'content:create:browse', this.browseContent, this );
    2958                 this.on( 'content:render:upload', this.uploadContent, this );
    2959                 this.on( 'toolbar:create:select', this.createSelectToolbar, this );
    2960         },
    2961 
    2962         /**
    2963          * Render callback for the router region in the `browse` mode.
     1973         * @access private
     1974         * @since 3.5.0
    29641975         *
    2965          * @param {wp.media.view.Router} routerView
    2966          */
    2967         browseRouter: function( routerView ) {
    2968                 routerView.set({
    2969                         upload: {
    2970                                 text:     l10n.uploadFilesTitle,
    2971                                 priority: 20
    2972                         },
    2973                         browse: {
    2974                                 text:     l10n.mediaLibraryTitle,
    2975                                 priority: 40
     1976         * @param {media.view.Menu} view The menu view.
     1977         */
     1978        _renderMenu: function( view ) {
     1979                var menuItem = this.get('menuItem'),
     1980                        title = this.get('title'),
     1981                        priority = this.get('priority');
     1982
     1983                if ( ! menuItem && title ) {
     1984                        menuItem = { text: title };
     1985
     1986                        if ( priority ) {
     1987                                menuItem.priority = priority;
    29761988                        }
    2977                 });
    2978         },
    2979 
    2980         /**
    2981          * Render callback for the content region in the `browse` mode.
    2982          *
    2983          * @param {wp.media.controller.Region} contentRegion
    2984          */
    2985         browseContent: function( contentRegion ) {
    2986                 var state = this.state();
    2987 
    2988                 this.$el.removeClass('hide-toolbar');
    2989 
    2990                 // Browse our library of attachments.
    2991                 contentRegion.view = new wp.media.view.AttachmentsBrowser({
    2992                         controller: this,
    2993                         collection: state.get('library'),
    2994                         selection:  state.get('selection'),
    2995                         model:      state,
    2996                         sortable:   state.get('sortable'),
    2997                         search:     state.get('searchable'),
    2998                         filters:    state.get('filterable'),
    2999                         date:       state.get('date'),
    3000                         display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
    3001                         dragInfo:   state.get('dragInfo'),
    3002 
    3003                         idealColumnWidth: state.get('idealColumnWidth'),
    3004                         suggestedWidth:   state.get('suggestedWidth'),
    3005                         suggestedHeight:  state.get('suggestedHeight'),
    3006 
    3007                         AttachmentView: state.get('AttachmentView')
    3008                 });
    3009         },
    3010 
    3011         /**
    3012          * Render callback for the content region in the `upload` mode.
    3013          */
    3014         uploadContent: function() {
    3015                 this.$el.removeClass( 'hide-toolbar' );
    3016                 this.content.set( new wp.media.view.UploaderInline({
    3017                         controller: this
    3018                 }) );
    3019         },
    3020 
    3021         /**
    3022          * Toolbars
    3023          *
    3024          * @param {Object} toolbar
    3025          * @param {Object} [options={}]
    3026          * @this wp.media.controller.Region
    3027          */
    3028         createSelectToolbar: function( toolbar, options ) {
    3029                 options = options || this.options.button || {};
    3030                 options.controller = this;
    3031 
    3032                 toolbar.view = new wp.media.view.Toolbar.Select( options );
     1989                }
     1990
     1991                if ( ! menuItem ) {
     1992                        return;
     1993                }
     1994
     1995                view.set( this.id, menuItem );
    30331996        }
    30341997});
    30351998
    3036 module.exports = Select;
    3037 
    3038 
    3039 /***/ }),
    3040 /* 49 */
    3041 /***/ (function(module, exports) {
    3042 
    3043 /*globals wp, _ */
     1999_.each(['toolbar','content'], function( region ) {
     2000        /**
     2001         * @access private
     2002         */
     2003        State.prototype[ '_' + region ] = function() {
     2004                var mode = this.get( region );
     2005                if ( mode ) {
     2006                        this.frame[ region ].render( mode );
     2007                }
     2008        };
     2009});
     2010
     2011module.exports = State;
     2012
     2013},{}],16:[function(require,module,exports){
     2014/*globals _ */
    30442015
    30452016/**
    3046  * wp.media.view.MediaFrame.Post
    3047  *
    3048  * The frame for manipulating media on the Edit Post page.
    3049  *
    3050  * @class
    3051  * @augments wp.media.view.MediaFrame.Select
    3052  * @augments wp.media.view.MediaFrame
    3053  * @augments wp.media.view.Frame
    3054  * @augments wp.media.View
    3055  * @augments wp.Backbone.View
    3056  * @augments Backbone.View
    3057  * @mixes wp.media.controller.StateMachine
     2017 * wp.media.selectionSync
     2018 *
     2019 * Sync an attachments selection in a state with another state.
     2020 *
     2021 * Allows for selecting multiple images in the Insert Media workflow, and then
     2022 * switching to the Insert Gallery workflow while preserving the attachments selection.
     2023 *
     2024 * @mixin
    30582025 */
    3059 var Select = wp.media.view.MediaFrame.Select,
    3060         Library = wp.media.controller.Library,
    3061         l10n = wp.media.view.l10n,
    3062         Post;
    3063 
    3064 Post = Select.extend({
    3065         initialize: function() {
    3066                 this.counts = {
    3067                         audio: {
    3068                                 count: wp.media.view.settings.attachmentCounts.audio,
    3069                                 state: 'playlist'
    3070                         },
    3071                         video: {
    3072                                 count: wp.media.view.settings.attachmentCounts.video,
    3073                                 state: 'video-playlist'
    3074                         }
    3075                 };
    3076 
    3077                 _.defaults( this.options, {
    3078                         multiple:  true,
    3079                         editing:   false,
    3080                         state:    'insert',
    3081                         metadata:  {}
    3082                 });
    3083 
    3084                 // Call 'initialize' directly on the parent class.
    3085                 Select.prototype.initialize.apply( this, arguments );
    3086                 this.createIframeStates();
    3087 
    3088         },
    3089 
    3090         /**
    3091          * Create the default states.
    3092          */
    3093         createStates: function() {
    3094                 var options = this.options;
    3095 
    3096                 this.states.add([
    3097                         // Main states.
    3098                         new Library({
    3099                                 id:         'insert',
    3100                                 title:      l10n.insertMediaTitle,
    3101                                 priority:   20,
    3102                                 toolbar:    'main-insert',
    3103                                 filterable: 'all',
    3104                                 library:    wp.media.query( options.library ),
    3105                                 multiple:   options.multiple ? 'reset' : false,
    3106                                 editable:   true,
    3107 
    3108                                 // If the user isn't allowed to edit fields,
    3109                                 // can they still edit it locally?
    3110                                 allowLocalEdits: true,
    3111 
    3112                                 // Show the attachment display settings.
    3113                                 displaySettings: true,
    3114                                 // Update user settings when users adjust the
    3115                                 // attachment display settings.
    3116                                 displayUserSettings: true
    3117                         }),
    3118 
    3119                         new Library({
    3120                                 id:         'gallery',
    3121                                 title:      l10n.createGalleryTitle,
    3122                                 priority:   40,
    3123                                 toolbar:    'main-gallery',
    3124                                 filterable: 'uploaded',
    3125                                 multiple:   'add',
    3126                                 editable:   false,
    3127 
    3128                                 library:  wp.media.query( _.defaults({
    3129                                         type: 'image'
    3130                                 }, options.library ) )
    3131                         }),
    3132 
    3133                         // Embed states.
    3134                         new wp.media.controller.Embed( { metadata: options.metadata } ),
    3135 
    3136                         new wp.media.controller.EditImage( { model: options.editImage } ),
    3137 
    3138                         // Gallery states.
    3139                         new wp.media.controller.GalleryEdit({
    3140                                 library: options.selection,
    3141                                 editing: options.editing,
    3142                                 menu:    'gallery'
    3143                         }),
    3144 
    3145                         new wp.media.controller.GalleryAdd(),
    3146 
    3147                         new Library({
    3148                                 id:         'playlist',
    3149                                 title:      l10n.createPlaylistTitle,
    3150                                 priority:   60,
    3151                                 toolbar:    'main-playlist',
    3152                                 filterable: 'uploaded',
    3153                                 multiple:   'add',
    3154                                 editable:   false,
    3155 
    3156                                 library:  wp.media.query( _.defaults({
    3157                                         type: 'audio'
    3158                                 }, options.library ) )
    3159                         }),
    3160 
    3161                         // Playlist states.
    3162                         new wp.media.controller.CollectionEdit({
    3163                                 type: 'audio',
    3164                                 collectionType: 'playlist',
    3165                                 title:          l10n.editPlaylistTitle,
    3166                                 SettingsView:   wp.media.view.Settings.Playlist,
    3167                                 library:        options.selection,
    3168                                 editing:        options.editing,
    3169                                 menu:           'playlist',
    3170                                 dragInfoText:   l10n.playlistDragInfo,
    3171                                 dragInfo:       false
    3172                         }),
    3173 
    3174                         new wp.media.controller.CollectionAdd({
    3175                                 type: 'audio',
    3176                                 collectionType: 'playlist',
    3177                                 title: l10n.addToPlaylistTitle
    3178                         }),
    3179 
    3180                         new Library({
    3181                                 id:         'video-playlist',
    3182                                 title:      l10n.createVideoPlaylistTitle,
    3183                                 priority:   60,
    3184                                 toolbar:    'main-video-playlist',
    3185                                 filterable: 'uploaded',
    3186                                 multiple:   'add',
    3187                                 editable:   false,
    3188 
    3189                                 library:  wp.media.query( _.defaults({
    3190                                         type: 'video'
    3191                                 }, options.library ) )
    3192                         }),
    3193 
    3194                         new wp.media.controller.CollectionEdit({
    3195                                 type: 'video',
    3196                                 collectionType: 'playlist',
    3197                                 title:          l10n.editVideoPlaylistTitle,
    3198                                 SettingsView:   wp.media.view.Settings.Playlist,
    3199                                 library:        options.selection,
    3200                                 editing:        options.editing,
    3201                                 menu:           'video-playlist',
    3202                                 dragInfoText:   l10n.videoPlaylistDragInfo,
    3203                                 dragInfo:       false
    3204                         }),
    3205 
    3206                         new wp.media.controller.CollectionAdd({
    3207                                 type: 'video',
    3208                                 collectionType: 'playlist',
    3209                                 title: l10n.addToVideoPlaylistTitle
    3210                         })
    3211                 ]);
    3212 
    3213                 if ( wp.media.view.settings.post.featuredImageId ) {
    3214                         this.states.add( new wp.media.controller.FeaturedImage() );
    3215                 }
    3216         },
    3217 
    3218         bindHandlers: function() {
    3219                 var handlers, checkCounts;
    3220 
    3221                 Select.prototype.bindHandlers.apply( this, arguments );
    3222 
    3223                 this.on( 'activate', this.activate, this );
    3224 
    3225                 // Only bother checking media type counts if one of the counts is zero
    3226                 checkCounts = _.find( this.counts, function( type ) {
    3227                         return type.count === 0;
    3228                 } );
    3229 
    3230                 if ( typeof checkCounts !== 'undefined' ) {
    3231                         this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
    3232                 }
    3233 
    3234                 this.on( 'menu:create:gallery', this.createMenu, this );
    3235                 this.on( 'menu:create:playlist', this.createMenu, this );
    3236                 this.on( 'menu:create:video-playlist', this.createMenu, this );
    3237                 this.on( 'toolbar:create:main-insert', this.createToolbar, this );
    3238                 this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
    3239                 this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
    3240                 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
    3241                 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
    3242                 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
    3243 
    3244                 handlers = {
    3245                         menu: {
    3246                                 'default': 'mainMenu',
    3247                                 'gallery': 'galleryMenu',
    3248                                 'playlist': 'playlistMenu',
    3249                                 'video-playlist': 'videoPlaylistMenu'
    3250                         },
    3251 
    3252                         content: {
    3253                                 'embed':          'embedContent',
    3254                                 'edit-image':     'editImageContent',
    3255                                 'edit-selection': 'editSelectionContent'
    3256                         },
    3257 
    3258                         toolbar: {
    3259                                 'main-insert':      'mainInsertToolbar',
    3260                                 'main-gallery':     'mainGalleryToolbar',
    3261                                 'gallery-edit':     'galleryEditToolbar',
    3262                                 'gallery-add':      'galleryAddToolbar',
    3263                                 'main-playlist':        'mainPlaylistToolbar',
    3264                                 'playlist-edit':        'playlistEditToolbar',
    3265                                 'playlist-add':         'playlistAddToolbar',
    3266                                 'main-video-playlist': 'mainVideoPlaylistToolbar',
    3267                                 'video-playlist-edit': 'videoPlaylistEditToolbar',
    3268                                 'video-playlist-add': 'videoPlaylistAddToolbar'
    3269                         }
    3270                 };
    3271 
    3272                 _.each( handlers, function( regionHandlers, region ) {
    3273                         _.each( regionHandlers, function( callback, handler ) {
    3274                                 this.on( region + ':render:' + handler, this[ callback ], this );
    3275                         }, this );
    3276                 }, this );
    3277         },
    3278 
    3279         activate: function() {
    3280                 // Hide menu items for states tied to particular media types if there are no items
    3281                 _.each( this.counts, function( type ) {
    3282                         if ( type.count < 1 ) {
    3283                                 this.menuItemVisibility( type.state, 'hide' );
    3284                         }
    3285                 }, this );
    3286         },
    3287 
    3288         mediaTypeCounts: function( model, attr ) {
    3289                 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
    3290                         this.counts[ attr ].count++;
    3291                         this.menuItemVisibility( this.counts[ attr ].state, 'show' );
    3292                 }
    3293         },
    3294 
    3295         // Menus
    3296         /**
    3297          * @param {wp.Backbone.View} view
    3298          */
    3299         mainMenu: function( view ) {
    3300                 view.set({
    3301                         'library-separator': new wp.media.View({
    3302                                 className: 'separator',
    3303                                 priority: 100
    3304                         })
    3305                 });
    3306         },
    3307 
    3308         menuItemVisibility: function( state, visibility ) {
    3309                 var menu = this.menu.get();
    3310                 if ( visibility === 'hide' ) {
    3311                         menu.hide( state );
    3312                 } else if ( visibility === 'show' ) {
    3313                         menu.show( state );
    3314                 }
    3315         },
    3316         /**
    3317          * @param {wp.Backbone.View} view
    3318          */
    3319         galleryMenu: function( view ) {
    3320                 var lastState = this.lastState(),
    3321                         previous = lastState && lastState.id,
    3322                         frame = this;
    3323 
    3324                 view.set({
    3325                         cancel: {
    3326                                 text:     l10n.cancelGalleryTitle,
    3327                                 priority: 20,
    3328                                 click:    function() {
    3329                                         if ( previous ) {
    3330                                                 frame.setState( previous );
    3331                                         } else {
    3332                                                 frame.close();
    3333                                         }
    3334 
    3335                                         // Keep focus inside media modal
    3336                                         // after canceling a gallery
    3337                                         this.controller.modal.focusManager.focus();
    3338                                 }
    3339                         },
    3340                         separateCancel: new wp.media.View({
    3341                                 className: 'separator',
    3342                                 priority: 40
    3343                         })
    3344                 });
    3345         },
    3346 
    3347         playlistMenu: function( view ) {
    3348                 var lastState = this.lastState(),
    3349                         previous = lastState && lastState.id,
    3350                         frame = this;
    3351 
    3352                 view.set({
    3353                         cancel: {
    3354                                 text:     l10n.cancelPlaylistTitle,
    3355                                 priority: 20,
    3356                                 click:    function() {
    3357                                         if ( previous ) {
    3358                                                 frame.setState( previous );
    3359                                         } else {
    3360                                                 frame.close();
    3361                                         }
    3362                                 }
    3363                         },
    3364                         separateCancel: new wp.media.View({
    3365                                 className: 'separator',
    3366                                 priority: 40
    3367                         })
    3368                 });
    3369         },
    3370 
    3371         videoPlaylistMenu: function( view ) {
    3372                 var lastState = this.lastState(),
    3373                         previous = lastState && lastState.id,
    3374                         frame = this;
    3375 
    3376                 view.set({
    3377                         cancel: {
    3378                                 text:     l10n.cancelVideoPlaylistTitle,
    3379                                 priority: 20,
    3380                                 click:    function() {
    3381                                         if ( previous ) {
    3382                                                 frame.setState( previous );
    3383                                         } else {
    3384                                                 frame.close();
    3385                                         }
    3386                                 }
    3387                         },
    3388                         separateCancel: new wp.media.View({
    3389                                 className: 'separator',
    3390                                 priority: 40
    3391                         })
    3392                 });
    3393         },
    3394 
    3395         // Content
    3396         embedContent: function() {
    3397                 var view = new wp.media.view.Embed({
    3398                         controller: this,
    3399                         model:      this.state()
    3400                 }).render();
    3401 
    3402                 this.content.set( view );
    3403 
    3404                 if ( ! wp.media.isTouchDevice ) {
    3405                         view.url.focus();
    3406                 }
    3407         },
    3408 
    3409         editSelectionContent: function() {
    3410                 var state = this.state(),
    3411                         selection = state.get('selection'),
    3412                         view;
    3413 
    3414                 view = new wp.media.view.AttachmentsBrowser({
    3415                         controller: this,
    3416                         collection: selection,
    3417                         selection:  selection,
    3418                         model:      state,
    3419                         sortable:   true,
    3420                         search:     false,
    3421                         date:       false,
    3422                         dragInfo:   true,
    3423 
    3424                         AttachmentView: wp.media.view.Attachments.EditSelection
    3425                 }).render();
    3426 
    3427                 view.toolbar.set( 'backToLibrary', {
    3428                         text:     l10n.returnToLibrary,
    3429                         priority: -100,
    3430 
    3431                         click: function() {
    3432                                 this.controller.content.mode('browse');
    3433                         }
    3434                 });
    3435 
    3436                 // Browse our library of attachments.
    3437                 this.content.set( view );
    3438 
    3439                 // Trigger the controller to set focus
    3440                 this.trigger( 'edit:selection', this );
    3441         },
    3442 
    3443         editImageContent: function() {
    3444                 var image = this.state().get('image'),
    3445                         view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
    3446 
    3447                 this.content.set( view );
    3448 
    3449                 // after creating the wrapper view, load the actual editor via an ajax call
    3450                 view.loadEditor();
    3451 
    3452         },
    3453 
    3454         // Toolbars
    3455 
    3456         /**
    3457          * @param {wp.Backbone.View} view
    3458          */
    3459         selectionStatusToolbar: function( view ) {
    3460                 var editable = this.state().get('editable');
    3461 
    3462                 view.set( 'selection', new wp.media.view.Selection({
    3463                         controller: this,
    3464                         collection: this.state().get('selection'),
    3465                         priority:   -40,
    3466 
    3467                         // If the selection is editable, pass the callback to
    3468                         // switch the content mode.
    3469                         editable: editable && function() {
    3470                                 this.controller.content.mode('edit-selection');
    3471                         }
    3472                 }).render() );
    3473         },
    3474 
    3475         /**
    3476          * @param {wp.Backbone.View} view
    3477          */
    3478         mainInsertToolbar: function( view ) {
    3479                 var controller = this;
    3480 
    3481                 this.selectionStatusToolbar( view );
    3482 
    3483                 view.set( 'insert', {
    3484                         style:    'primary',
    3485                         priority: 80,
    3486                         text:     l10n.insertIntoPost,
    3487                         requires: { selection: true },
    3488 
    3489                         /**
    3490                          * @fires wp.media.controller.State#insert
    3491                          */
    3492                         click: function() {
    3493                                 var state = controller.state(),
    3494                                         selection = state.get('selection');
    3495 
    3496                                 controller.close();
    3497                                 state.trigger( 'insert', selection ).reset();
    3498                         }
    3499                 });
    3500         },
    3501 
    3502         /**
    3503          * @param {wp.Backbone.View} view
    3504          */
    3505         mainGalleryToolbar: function( view ) {
    3506                 var controller = this;
    3507 
    3508                 this.selectionStatusToolbar( view );
    3509 
    3510                 view.set( 'gallery', {
    3511                         style:    'primary',
    3512                         text:     l10n.createNewGallery,
    3513                         priority: 60,
    3514                         requires: { selection: true },
    3515 
    3516                         click: function() {
    3517                                 var selection = controller.state().get('selection'),
    3518                                         edit = controller.state('gallery-edit'),
    3519                                         models = selection.where({ type: 'image' });
    3520 
    3521                                 edit.set( 'library', new wp.media.model.Selection( models, {
    3522                                         props:    selection.props.toJSON(),
    3523                                         multiple: true
    3524                                 }) );
    3525 
    3526                                 this.controller.setState('gallery-edit');
    3527 
    3528                                 // Keep focus inside media modal
    3529                                 // after jumping to gallery view
    3530                                 this.controller.modal.focusManager.focus();
    3531                         }
    3532                 });
    3533         },
    3534 
    3535         mainPlaylistToolbar: function( view ) {
    3536                 var controller = this;
    3537 
    3538                 this.selectionStatusToolbar( view );
    3539 
    3540                 view.set( 'playlist', {
    3541                         style:    'primary',
    3542                         text:     l10n.createNewPlaylist,
    3543                         priority: 100,
    3544                         requires: { selection: true },
    3545 
    3546                         click: function() {
    3547                                 var selection = controller.state().get('selection'),
    3548                                         edit = controller.state('playlist-edit'),
    3549                                         models = selection.where({ type: 'audio' });
    3550 
    3551                                 edit.set( 'library', new wp.media.model.Selection( models, {
    3552                                         props:    selection.props.toJSON(),
    3553                                         multiple: true
    3554                                 }) );
    3555 
    3556                                 this.controller.setState('playlist-edit');
    3557 
    3558                                 // Keep focus inside media modal
    3559                                 // after jumping to playlist view
    3560                                 this.controller.modal.focusManager.focus();
    3561                         }
    3562                 });
    3563         },
    3564 
    3565         mainVideoPlaylistToolbar: function( view ) {
    3566                 var controller = this;
    3567 
    3568                 this.selectionStatusToolbar( view );
    3569 
    3570                 view.set( 'video-playlist', {
    3571                         style:    'primary',
    3572                         text:     l10n.createNewVideoPlaylist,
    3573                         priority: 100,
    3574                         requires: { selection: true },
    3575 
    3576                         click: function() {
    3577                                 var selection = controller.state().get('selection'),
    3578                                         edit = controller.state('video-playlist-edit'),
    3579                                         models = selection.where({ type: 'video' });
    3580 
    3581                                 edit.set( 'library', new wp.media.model.Selection( models, {
    3582                                         props:    selection.props.toJSON(),
    3583                                         multiple: true
    3584                                 }) );
    3585 
    3586                                 this.controller.setState('video-playlist-edit');
    3587 
    3588                                 // Keep focus inside media modal
    3589                                 // after jumping to video playlist view
    3590                                 this.controller.modal.focusManager.focus();
    3591                         }
    3592                 });
    3593         },
    3594 
    3595         featuredImageToolbar: function( toolbar ) {
    3596                 this.createSelectToolbar( toolbar, {
    3597                         text:  l10n.setFeaturedImage,
    3598                         state: this.options.state
    3599                 });
    3600         },
    3601 
    3602         mainEmbedToolbar: function( toolbar ) {
    3603                 toolbar.view = new wp.media.view.Toolbar.Embed({
    3604                         controller: this
    3605                 });
    3606         },
    3607 
    3608         galleryEditToolbar: function() {
    3609                 var editing = this.state().get('editing');
    3610                 this.toolbar.set( new wp.media.view.Toolbar({
    3611                         controller: this,
    3612                         items: {
    3613                                 insert: {
    3614                                         style:    'primary',
    3615                                         text:     editing ? l10n.updateGallery : l10n.insertGallery,
    3616                                         priority: 80,
    3617                                         requires: { library: true },
    3618 
    3619                                         /**
    3620                                          * @fires wp.media.controller.State#update
    3621                                          */
    3622                                         click: function() {
    3623                                                 var controller = this.controller,
    3624                                                         state = controller.state();
    3625 
    3626                                                 controller.close();
    3627                                                 state.trigger( 'update', state.get('library') );
    3628 
    3629                                                 // Restore and reset the default state.
    3630                                                 controller.setState( controller.options.state );
    3631                                                 controller.reset();
    3632                                         }
    3633                                 }
    3634                         }
    3635                 }) );
    3636         },
    3637 
    3638         galleryAddToolbar: function() {
    3639                 this.toolbar.set( new wp.media.view.Toolbar({
    3640                         controller: this,
    3641                         items: {
    3642                                 insert: {
    3643                                         style:    'primary',
    3644                                         text:     l10n.addToGallery,
    3645                                         priority: 80,
    3646                                         requires: { selection: true },
    3647 
    3648                                         /**
    3649                                          * @fires wp.media.controller.State#reset
    3650                                          */
    3651                                         click: function() {
    3652                                                 var controller = this.controller,
    3653                                                         state = controller.state(),
    3654                                                         edit = controller.state('gallery-edit');
    3655 
    3656                                                 edit.get('library').add( state.get('selection').models );
    3657                                                 state.trigger('reset');
    3658                                                 controller.setState('gallery-edit');
    3659                                         }
    3660                                 }
    3661                         }
    3662                 }) );
    3663         },
    3664 
    3665         playlistEditToolbar: function() {
    3666                 var editing = this.state().get('editing');
    3667                 this.toolbar.set( new wp.media.view.Toolbar({
    3668                         controller: this,
    3669                         items: {
    3670                                 insert: {
    3671                                         style:    'primary',
    3672                                         text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
    3673                                         priority: 80,
    3674                                         requires: { library: true },
    3675 
    3676                                         /**
    3677                                          * @fires wp.media.controller.State#update
    3678                                          */
    3679                                         click: function() {
    3680                                                 var controller = this.controller,
    3681                                                         state = controller.state();
    3682 
    3683                                                 controller.close();
    3684                                                 state.trigger( 'update', state.get('library') );
    3685 
    3686                                                 // Restore and reset the default state.
    3687                                                 controller.setState( controller.options.state );
    3688                                                 controller.reset();
    3689                                         }
    3690                                 }
    3691                         }
    3692                 }) );
    3693         },
    3694 
    3695         playlistAddToolbar: function() {
    3696                 this.toolbar.set( new wp.media.view.Toolbar({
    3697                         controller: this,
    3698                         items: {
    3699                                 insert: {
    3700                                         style:    'primary',
    3701                                         text:     l10n.addToPlaylist,
    3702                                         priority: 80,
    3703                                         requires: { selection: true },
    3704 
    3705                                         /**
    3706                                          * @fires wp.media.controller.State#reset
    3707                                          */
    3708                                         click: function() {
    3709                                                 var controller = this.controller,
    3710                                                         state = controller.state(),
    3711                                                         edit = controller.state('playlist-edit');
    3712 
    3713                                                 edit.get('library').add( state.get('selection').models );
    3714                                                 state.trigger('reset');
    3715                                                 controller.setState('playlist-edit');
    3716                                         }
    3717                                 }
    3718                         }
    3719                 }) );
    3720         },
    3721 
    3722         videoPlaylistEditToolbar: function() {
    3723                 var editing = this.state().get('editing');
    3724                 this.toolbar.set( new wp.media.view.Toolbar({
    3725                         controller: this,
    3726                         items: {
    3727                                 insert: {
    3728                                         style:    'primary',
    3729                                         text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
    3730                                         priority: 140,
    3731                                         requires: { library: true },
    3732 
    3733                                         click: function() {
    3734                                                 var controller = this.controller,
    3735                                                         state = controller.state(),
    3736                                                         library = state.get('library');
    3737 
    3738                                                 library.type = 'video';
    3739 
    3740                                                 controller.close();
    3741                                                 state.trigger( 'update', library );
    3742 
    3743                                                 // Restore and reset the default state.
    3744                                                 controller.setState( controller.options.state );
    3745                                                 controller.reset();
    3746                                         }
    3747                                 }
    3748                         }
    3749                 }) );
    3750         },
    3751 
    3752         videoPlaylistAddToolbar: function() {
    3753                 this.toolbar.set( new wp.media.view.Toolbar({
    3754                         controller: this,
    3755                         items: {
    3756                                 insert: {
    3757                                         style:    'primary',
    3758                                         text:     l10n.addToVideoPlaylist,
    3759                                         priority: 140,
    3760                                         requires: { selection: true },
    3761 
    3762                                         click: function() {
    3763                                                 var controller = this.controller,
    3764                                                         state = controller.state(),
    3765                                                         edit = controller.state('video-playlist-edit');
    3766 
    3767                                                 edit.get('library').add( state.get('selection').models );
    3768                                                 state.trigger('reset');
    3769                                                 controller.setState('video-playlist-edit');
    3770                                         }
    3771                                 }
    3772                         }
    3773                 }) );
     2026var selectionSync = {
     2027        /**
     2028         * @since 3.5.0
     2029         */
     2030        syncSelection: function() {
     2031                var selection = this.get('selection'),
     2032                        manager = this.frame._selection;
     2033
     2034                if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     2035                        return;
     2036                }
     2037
     2038                // If the selection supports multiple items, validate the stored
     2039                // attachments based on the new selection's conditions. Record
     2040                // the attachments that are not included; we'll maintain a
     2041                // reference to those. Other attachments are considered in flux.
     2042                if ( selection.multiple ) {
     2043                        selection.reset( [], { silent: true });
     2044                        selection.validateAll( manager.attachments );
     2045                        manager.difference = _.difference( manager.attachments.models, selection.models );
     2046                }
     2047
     2048                // Sync the selection's single item with the master.
     2049                selection.single( manager.single );
     2050        },
     2051
     2052        /**
     2053         * Record the currently active attachments, which is a combination
     2054         * of the selection's attachments and the set of selected
     2055         * attachments that this specific selection considered invalid.
     2056         * Reset the difference and record the single attachment.
     2057         *
     2058         * @since 3.5.0
     2059         */
     2060        recordSelection: function() {
     2061                var selection = this.get('selection'),
     2062                        manager = this.frame._selection;
     2063
     2064                if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     2065                        return;
     2066                }
     2067
     2068                if ( selection.multiple ) {
     2069                        manager.attachments.reset( selection.toArray().concat( manager.difference ) );
     2070                        manager.difference = [];
     2071                } else {
     2072                        manager.attachments.add( selection.toArray() );
     2073                }
     2074
     2075                manager.single = selection._single;
    37742076        }
    3775 });
    3776 
    3777 module.exports = Post;
    3778 
    3779 
    3780 /***/ }),
    3781 /* 50 */
    3782 /***/ (function(module, exports) {
    3783 
    3784 /*globals wp */
     2077};
     2078
     2079module.exports = selectionSync;
     2080
     2081},{}],17:[function(require,module,exports){
     2082/*globals wp, jQuery, _, Backbone */
     2083
     2084var media = wp.media,
     2085        $ = jQuery,
     2086        l10n;
     2087
     2088media.isTouchDevice = ( 'ontouchend' in document );
     2089
     2090// Link any localized strings.
     2091l10n = media.view.l10n = window._wpMediaViewsL10n || {};
     2092
     2093// Link any settings.
     2094media.view.settings = l10n.settings || {};
     2095delete l10n.settings;
     2096
     2097// Copy the `post` setting over to the model settings.
     2098media.model.settings.post = media.view.settings.post;
     2099
     2100// Check if the browser supports CSS 3.0 transitions
     2101$.support.transition = (function(){
     2102        var style = document.documentElement.style,
     2103                transitions = {
     2104                        WebkitTransition: 'webkitTransitionEnd',
     2105                        MozTransition:    'transitionend',
     2106                        OTransition:      'oTransitionEnd otransitionend',
     2107                        transition:       'transitionend'
     2108                }, transition;
     2109
     2110        transition = _.find( _.keys( transitions ), function( transition ) {
     2111                return ! _.isUndefined( style[ transition ] );
     2112        });
     2113
     2114        return transition && {
     2115                end: transitions[ transition ]
     2116        };
     2117}());
    37852118
    37862119/**
    3787  * wp.media.view.MediaFrame.ImageDetails
    3788  *
    3789  * A media frame for manipulating an image that's already been inserted
    3790  * into a post.
    3791  *
    3792  * @class
    3793  * @augments wp.media.view.MediaFrame.Select
    3794  * @augments wp.media.view.MediaFrame
    3795  * @augments wp.media.view.Frame
    3796  * @augments wp.media.View
    3797  * @augments wp.Backbone.View
    3798  * @augments Backbone.View
    3799  * @mixes wp.media.controller.StateMachine
     2120 * A shared event bus used to provide events into
     2121 * the media workflows that 3rd-party devs can use to hook
     2122 * in.
    38002123 */
    3801 var Select = wp.media.view.MediaFrame.Select,
    3802         l10n = wp.media.view.l10n,
    3803         ImageDetails;
    3804 
    3805 ImageDetails = Select.extend({
    3806         defaults: {
    3807                 id:      'image',
    3808                 url:     '',
    3809                 menu:    'image-details',
    3810                 content: 'image-details',
    3811                 toolbar: 'image-details',
    3812                 type:    'link',
    3813                 title:    l10n.imageDetailsTitle,
    3814                 priority: 120
    3815         },
    3816 
    3817         initialize: function( options ) {
    3818                 this.image = new wp.media.model.PostImage( options.metadata );
    3819                 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
    3820                 Select.prototype.initialize.apply( this, arguments );
    3821         },
    3822 
    3823         bindHandlers: function() {
    3824                 Select.prototype.bindHandlers.apply( this, arguments );
    3825                 this.on( 'menu:create:image-details', this.createMenu, this );
    3826                 this.on( 'content:create:image-details', this.imageDetailsContent, this );
    3827                 this.on( 'content:render:edit-image', this.editImageContent, this );
    3828                 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
    3829                 // override the select toolbar
    3830                 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
    3831         },
    3832 
    3833         createStates: function() {
    3834                 this.states.add([
    3835                         new wp.media.controller.ImageDetails({
    3836                                 image: this.image,
    3837                                 editable: false
    3838                         }),
    3839                         new wp.media.controller.ReplaceImage({
    3840                                 id: 'replace-image',
    3841                                 library: wp.media.query( { type: 'image' } ),
    3842                                 image: this.image,
    3843                                 multiple:  false,
    3844                                 title:     l10n.imageReplaceTitle,
    3845                                 toolbar: 'replace',
    3846                                 priority:  80,
    3847                                 displaySettings: true
    3848                         }),
    3849                         new wp.media.controller.EditImage( {
    3850                                 image: this.image,
    3851                                 selection: this.options.selection
    3852                         } )
    3853                 ]);
    3854         },
    3855 
    3856         imageDetailsContent: function( options ) {
    3857                 options.view = new wp.media.view.ImageDetails({
    3858                         controller: this,
    3859                         model: this.state().image,
    3860                         attachment: this.state().image.attachment
    3861                 });
    3862         },
    3863 
    3864         editImageContent: function() {
    3865                 var state = this.state(),
    3866                         model = state.get('image'),
    3867                         view;
    3868 
    3869                 if ( ! model ) {
    3870                         return;
    3871                 }
    3872 
    3873                 view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
    3874 
    3875                 this.content.set( view );
    3876 
    3877                 // after bringing in the frame, load the actual editor via an ajax call
    3878                 view.loadEditor();
    3879 
    3880         },
    3881 
    3882         renderImageDetailsToolbar: function() {
    3883                 this.toolbar.set( new wp.media.view.Toolbar({
    3884                         controller: this,
    3885                         items: {
    3886                                 select: {
    3887                                         style:    'primary',
    3888                                         text:     l10n.update,
    3889                                         priority: 80,
    3890 
    3891                                         click: function() {
    3892                                                 var controller = this.controller,
    3893                                                         state = controller.state();
    3894 
    3895                                                 controller.close();
    3896 
    3897                                                 // not sure if we want to use wp.media.string.image which will create a shortcode or
    3898                                                 // perhaps wp.html.string to at least to build the <img />
    3899                                                 state.trigger( 'update', controller.image.toJSON() );
    3900 
    3901                                                 // Restore and reset the default state.
    3902                                                 controller.setState( controller.options.state );
    3903                                                 controller.reset();
    3904                                         }
    3905                                 }
    3906                         }
    3907                 }) );
    3908         },
    3909 
    3910         renderReplaceImageToolbar: function() {
    3911                 var frame = this,
    3912                         lastState = frame.lastState(),
    3913                         previous = lastState && lastState.id;
    3914 
    3915                 this.toolbar.set( new wp.media.view.Toolbar({
    3916                         controller: this,
    3917                         items: {
    3918                                 back: {
    3919                                         text:     l10n.back,
    3920                                         priority: 20,
    3921                                         click:    function() {
    3922                                                 if ( previous ) {
    3923                                                         frame.setState( previous );
    3924                                                 } else {
    3925                                                         frame.close();
    3926                                                 }
    3927                                         }
    3928                                 },
    3929 
    3930                                 replace: {
    3931                                         style:    'primary',
    3932                                         text:     l10n.replace,
    3933                                         priority: 80,
    3934 
    3935                                         click: function() {
    3936                                                 var controller = this.controller,
    3937                                                         state = controller.state(),
    3938                                                         selection = state.get( 'selection' ),
    3939                                                         attachment = selection.single();
    3940 
    3941                                                 controller.close();
    3942 
    3943                                                 controller.image.changeAttachment( attachment, state.display( attachment ) );
    3944 
    3945                                                 // not sure if we want to use wp.media.string.image which will create a shortcode or
    3946                                                 // perhaps wp.html.string to at least to build the <img />
    3947                                                 state.trigger( 'replace', controller.image.toJSON() );
    3948 
    3949                                                 // Restore and reset the default state.
    3950                                                 controller.setState( controller.options.state );
    3951                                                 controller.reset();
    3952                                         }
    3953                                 }
    3954                         }
    3955                 }) );
     2124media.events = _.extend( {}, Backbone.Events );
     2125
     2126/**
     2127 * Makes it easier to bind events using transitions.
     2128 *
     2129 * @param {string} selector
     2130 * @param {Number} sensitivity
     2131 * @returns {Promise}
     2132 */
     2133media.transition = function( selector, sensitivity ) {
     2134        var deferred = $.Deferred();
     2135
     2136        sensitivity = sensitivity || 2000;
     2137
     2138        if ( $.support.transition ) {
     2139                if ( ! (selector instanceof $) ) {
     2140                        selector = $( selector );
     2141                }
     2142
     2143                // Resolve the deferred when the first element finishes animating.
     2144                selector.first().one( $.support.transition.end, deferred.resolve );
     2145
     2146                // Just in case the event doesn't trigger, fire a callback.
     2147                _.delay( deferred.resolve, sensitivity );
     2148
     2149        // Otherwise, execute on the spot.
     2150        } else {
     2151                deferred.resolve();
    39562152        }
    39572153
    3958 });
    3959 
    3960 module.exports = ImageDetails;
    3961 
    3962 
    3963 /***/ }),
    3964 /* 51 */
    3965 /***/ (function(module, exports) {
    3966 
    3967 /*globals wp, _, jQuery */
     2154        return deferred.promise();
     2155};
     2156
     2157media.controller.Region = require( './controllers/region.js' );
     2158media.controller.StateMachine = require( './controllers/state-machine.js' );
     2159media.controller.State = require( './controllers/state.js' );
     2160
     2161media.selectionSync = require( './utils/selection-sync.js' );
     2162media.controller.Library = require( './controllers/library.js' );
     2163media.controller.ImageDetails = require( './controllers/image-details.js' );
     2164media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
     2165media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
     2166media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
     2167media.controller.CollectionAdd = require( './controllers/collection-add.js' );
     2168media.controller.FeaturedImage = require( './controllers/featured-image.js' );
     2169media.controller.ReplaceImage = require( './controllers/replace-image.js' );
     2170media.controller.EditImage = require( './controllers/edit-image.js' );
     2171media.controller.MediaLibrary = require( './controllers/media-library.js' );
     2172media.controller.Embed = require( './controllers/embed.js' );
     2173media.controller.Cropper = require( './controllers/cropper.js' );
     2174
     2175media.View = require( './views/view.js' );
     2176media.view.Frame = require( './views/frame.js' );
     2177media.view.MediaFrame = require( './views/media-frame.js' );
     2178media.view.MediaFrame.Select = require( './views/frame/select.js' );
     2179media.view.MediaFrame.Post = require( './views/frame/post.js' );
     2180media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
     2181media.view.Modal = require( './views/modal.js' );
     2182media.view.FocusManager = require( './views/focus-manager.js' );
     2183media.view.UploaderWindow = require( './views/uploader/window.js' );
     2184media.view.EditorUploader = require( './views/uploader/editor.js' );
     2185media.view.UploaderInline = require( './views/uploader/inline.js' );
     2186media.view.UploaderStatus = require( './views/uploader/status.js' );
     2187media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
     2188media.view.Toolbar = require( './views/toolbar.js' );
     2189media.view.Toolbar.Select = require( './views/toolbar/select.js' );
     2190media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
     2191media.view.Button = require( './views/button.js' );
     2192media.view.ButtonGroup = require( './views/button-group.js' );
     2193media.view.PriorityList = require( './views/priority-list.js' );
     2194media.view.MenuItem = require( './views/menu-item.js' );
     2195media.view.Menu = require( './views/menu.js' );
     2196media.view.RouterItem = require( './views/router-item.js' );
     2197media.view.Router = require( './views/router.js' );
     2198media.view.Sidebar = require( './views/sidebar.js' );
     2199media.view.Attachment = require( './views/attachment.js' );
     2200media.view.Attachment.Library = require( './views/attachment/library.js' );
     2201media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
     2202media.view.Attachments = require( './views/attachments.js' );
     2203media.view.Search = require( './views/search.js' );
     2204media.view.AttachmentFilters = require( './views/attachment-filters.js' );
     2205media.view.DateFilter = require( './views/attachment-filters/date.js' );
     2206media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
     2207media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
     2208media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
     2209media.view.Selection = require( './views/selection.js' );
     2210media.view.Attachment.Selection = require( './views/attachment/selection.js' );
     2211media.view.Attachments.Selection = require( './views/attachments/selection.js' );
     2212media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
     2213media.view.Settings = require( './views/settings.js' );
     2214media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
     2215media.view.Settings.Gallery = require( './views/settings/gallery.js' );
     2216media.view.Settings.Playlist = require( './views/settings/playlist.js' );
     2217media.view.Attachment.Details = require( './views/attachment/details.js' );
     2218media.view.AttachmentCompat = require( './views/attachment-compat.js' );
     2219media.view.Iframe = require( './views/iframe.js' );
     2220media.view.Embed = require( './views/embed.js' );
     2221media.view.Label = require( './views/label.js' );
     2222media.view.EmbedUrl = require( './views/embed/url.js' );
     2223media.view.EmbedLink = require( './views/embed/link.js' );
     2224media.view.EmbedImage = require( './views/embed/image.js' );
     2225media.view.ImageDetails = require( './views/image-details.js' );
     2226media.view.Cropper = require( './views/cropper.js' );
     2227media.view.EditImage = require( './views/edit-image.js' );
     2228media.view.Spinner = require( './views/spinner.js' );
     2229
     2230},{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/edit-image.js":4,"./controllers/embed.js":5,"./controllers/featured-image.js":6,"./controllers/gallery-add.js":7,"./controllers/gallery-edit.js":8,"./controllers/image-details.js":9,"./controllers/library.js":10,"./controllers/media-library.js":11,"./controllers/region.js":12,"./controllers/replace-image.js":13,"./controllers/state-machine.js":14,"./controllers/state.js":15,"./utils/selection-sync.js":16,"./views/attachment-compat.js":18,"./views/attachment-filters.js":19,"./views/attachment-filters/all.js":20,"./views/attachment-filters/date.js":21,"./views/attachment-filters/uploaded.js":22,"./views/attachment.js":23,"./views/attachment/details.js":24,"./views/attachment/edit-library.js":25,"./views/attachment/edit-selection.js":26,"./views/attachment/library.js":27,"./views/attachment/selection.js":28,"./views/attachments.js":29,"./views/attachments/browser.js":30,"./views/attachments/selection.js":31,"./views/button-group.js":32,"./views/button.js":33,"./views/cropper.js":34,"./views/edit-image.js":35,"./views/embed.js":36,"./views/embed/image.js":37,"./views/embed/link.js":38,"./views/embed/url.js":39,"./views/focus-manager.js":40,"./views/frame.js":41,"./views/frame/image-details.js":42,"./views/frame/post.js":43,"./views/frame/select.js":44,"./views/iframe.js":45,"./views/image-details.js":46,"./views/label.js":47,"./views/media-frame.js":48,"./views/menu-item.js":49,"./views/menu.js":50,"./views/modal.js":51,"./views/priority-list.js":52,"./views/router-item.js":53,"./views/router.js":54,"./views/search.js":55,"./views/selection.js":56,"./views/settings.js":57,"./views/settings/attachment-display.js":58,"./views/settings/gallery.js":59,"./views/settings/playlist.js":60,"./views/sidebar.js":61,"./views/spinner.js":62,"./views/toolbar.js":63,"./views/toolbar/embed.js":64,"./views/toolbar/select.js":65,"./views/uploader/editor.js":66,"./views/uploader/inline.js":67,"./views/uploader/status-error.js":68,"./views/uploader/status.js":69,"./views/uploader/window.js":70,"./views/view.js":71}],18:[function(require,module,exports){
     2231/*globals _ */
    39682232
    39692233/**
    3970  * wp.media.view.Modal
    3971  *
    3972  * A modal view, which the media modal uses as its default container.
    3973  *
    3974  * @class
    3975  * @augments wp.media.View
    3976  * @augments wp.Backbone.View
    3977  * @augments Backbone.View
    3978  */
    3979 var $ = jQuery,
    3980         Modal;
    3981 
    3982 Modal = wp.media.View.extend({
    3983         tagName:  'div',
    3984         template: wp.template('media-modal'),
    3985 
    3986         attributes: {
    3987                 tabindex: 0
    3988         },
    3989 
    3990         events: {
    3991                 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
    3992                 'keydown': 'keydown'
    3993         },
    3994 
    3995         initialize: function() {
    3996                 _.defaults( this.options, {
    3997                         container: document.body,
    3998                         title:     '',
    3999                         propagate: true,
    4000                         freeze:    true
    4001                 });
    4002 
    4003                 this.focusManager = new wp.media.view.FocusManager({
    4004                         el: this.el
    4005                 });
    4006         },
    4007         /**
    4008          * @returns {Object}
    4009          */
    4010         prepare: function() {
    4011                 return {
    4012                         title: this.options.title
    4013                 };
    4014         },
    4015 
    4016         /**
    4017          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    4018          */
    4019         attach: function() {
    4020                 if ( this.views.attached ) {
    4021                         return this;
    4022                 }
    4023 
    4024                 if ( ! this.views.rendered ) {
    4025                         this.render();
    4026                 }
    4027 
    4028                 this.$el.appendTo( this.options.container );
    4029 
    4030                 // Manually mark the view as attached and trigger ready.
    4031                 this.views.attached = true;
    4032                 this.views.ready();
    4033 
    4034                 return this.propagate('attach');
    4035         },
    4036 
    4037         /**
    4038          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    4039          */
    4040         detach: function() {
    4041                 if ( this.$el.is(':visible') ) {
    4042                         this.close();
    4043                 }
    4044 
    4045                 this.$el.detach();
    4046                 this.views.attached = false;
    4047                 return this.propagate('detach');
    4048         },
    4049 
    4050         /**
    4051          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    4052          */
    4053         open: function() {
    4054                 var $el = this.$el,
    4055                         options = this.options,
    4056                         mceEditor;
    4057 
    4058                 if ( $el.is(':visible') ) {
    4059                         return this;
    4060                 }
    4061 
    4062                 if ( ! this.views.attached ) {
    4063                         this.attach();
    4064                 }
    4065 
    4066                 // If the `freeze` option is set, record the window's scroll position.
    4067                 if ( options.freeze ) {
    4068                         this._freeze = {
    4069                                 scrollTop: $( window ).scrollTop()
    4070                         };
    4071                 }
    4072 
    4073                 // Disable page scrolling.
    4074                 $( 'body' ).addClass( 'modal-open' );
    4075 
    4076                 $el.show();
    4077 
    4078                 // Try to close the onscreen keyboard
    4079                 if ( 'ontouchend' in document ) {
    4080                         if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
    4081                                 mceEditor.iframeElement.focus();
    4082                                 mceEditor.iframeElement.blur();
    4083 
    4084                                 setTimeout( function() {
    4085                                         mceEditor.iframeElement.blur();
    4086                                 }, 100 );
    4087                         }
    4088                 }
    4089 
    4090                 this.$el.focus();
    4091 
    4092                 return this.propagate('open');
    4093         },
    4094 
    4095         /**
    4096          * @param {Object} options
    4097          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    4098          */
    4099         close: function( options ) {
    4100                 var freeze = this._freeze;
    4101 
    4102                 if ( ! this.views.attached || ! this.$el.is(':visible') ) {
    4103                         return this;
    4104                 }
    4105 
    4106                 // Enable page scrolling.
    4107                 $( 'body' ).removeClass( 'modal-open' );
    4108 
    4109                 // Hide modal and remove restricted media modal tab focus once it's closed
    4110                 this.$el.hide().undelegate( 'keydown' );
    4111 
    4112                 // Put focus back in useful location once modal is closed
    4113                 $('#wpbody-content').focus();
    4114 
    4115                 this.propagate('close');
    4116 
    4117                 // If the `freeze` option is set, restore the container's scroll position.
    4118                 if ( freeze ) {
    4119                         $( window ).scrollTop( freeze.scrollTop );
    4120                 }
    4121 
    4122                 if ( options && options.escape ) {
    4123                         this.propagate('escape');
    4124                 }
    4125 
    4126                 return this;
    4127         },
    4128         /**
    4129          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    4130          */
    4131         escape: function() {
    4132                 return this.close({ escape: true });
    4133         },
    4134         /**
    4135          * @param {Object} event
    4136          */
    4137         escapeHandler: function( event ) {
    4138                 event.preventDefault();
    4139                 this.escape();
    4140         },
    4141 
    4142         /**
    4143          * @param {Array|Object} content Views to register to '.media-modal-content'
    4144          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    4145          */
    4146         content: function( content ) {
    4147                 this.views.set( '.media-modal-content', content );
    4148                 return this;
    4149         },
    4150 
    4151         /**
    4152          * Triggers a modal event and if the `propagate` option is set,
    4153          * forwards events to the modal's controller.
    4154          *
    4155          * @param {string} id
    4156          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    4157          */
    4158         propagate: function( id ) {
    4159                 this.trigger( id );
    4160 
    4161                 if ( this.options.propagate ) {
    4162                         this.controller.trigger( id );
    4163                 }
    4164 
    4165                 return this;
    4166         },
    4167         /**
    4168          * @param {Object} event
    4169          */
    4170         keydown: function( event ) {
    4171                 // Close the modal when escape is pressed.
    4172                 if ( 27 === event.which && this.$el.is(':visible') ) {
    4173                         this.escape();
    4174                         event.stopImmediatePropagation();
    4175                 }
    4176         }
    4177 });
    4178 
    4179 module.exports = Modal;
    4180 
    4181 
    4182 /***/ }),
    4183 /* 52 */
    4184 /***/ (function(module, exports) {
    4185 
    4186 /**
    4187  * wp.media.view.FocusManager
    4188  *
    4189  * @class
    4190  * @augments wp.media.View
    4191  * @augments wp.Backbone.View
    4192  * @augments Backbone.View
    4193  */
    4194 var FocusManager = wp.media.View.extend({
    4195 
    4196         events: {
    4197                 'keydown': 'constrainTabbing'
    4198         },
    4199 
    4200         focus: function() { // Reset focus on first left menu item
    4201                 this.$('.media-menu-item').first().focus();
    4202         },
    4203         /**
    4204          * @param {Object} event
    4205          */
    4206         constrainTabbing: function( event ) {
    4207                 var tabbables;
    4208 
    4209                 // Look for the tab key.
    4210                 if ( 9 !== event.keyCode ) {
    4211                         return;
    4212                 }
    4213 
    4214                 // Skip the file input added by Plupload.
    4215                 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
    4216 
    4217                 // Keep tab focus within media modal while it's open
    4218                 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
    4219                         tabbables.first().focus();
    4220                         return false;
    4221                 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
    4222                         tabbables.last().focus();
    4223                         return false;
    4224                 }
    4225         }
    4226 
    4227 });
    4228 
    4229 module.exports = FocusManager;
    4230 
    4231 
    4232 /***/ }),
    4233 /* 53 */
    4234 /***/ (function(module, exports) {
    4235 
    4236 /*globals wp, _, jQuery */
    4237 
    4238 /**
    4239  * wp.media.view.UploaderWindow
    4240  *
    4241  * An uploader window that allows for dragging and dropping media.
    4242  *
    4243  * @class
    4244  * @augments wp.media.View
    4245  * @augments wp.Backbone.View
    4246  * @augments Backbone.View
    4247  *
    4248  * @param {object} [options]                   Options hash passed to the view.
    4249  * @param {object} [options.uploader]          Uploader properties.
    4250  * @param {jQuery} [options.uploader.browser]
    4251  * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
    4252  * @param {object} [options.uploader.params]
    4253  */
    4254 var $ = jQuery,
    4255         UploaderWindow;
    4256 
    4257 UploaderWindow = wp.media.View.extend({
    4258         tagName:   'div',
    4259         className: 'uploader-window',
    4260         template:  wp.template('uploader-window'),
    4261 
    4262         initialize: function() {
    4263                 var uploader;
    4264 
    4265                 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');
    4266 
    4267                 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
    4268                         dropzone:  this.$el,
    4269                         browser:   this.$browser,
    4270                         params:    {}
    4271                 });
    4272 
    4273                 // Ensure the dropzone is a jQuery collection.
    4274                 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
    4275                         uploader.dropzone = $( uploader.dropzone );
    4276                 }
    4277 
    4278                 this.controller.on( 'activate', this.refresh, this );
    4279 
    4280                 this.controller.on( 'detach', function() {
    4281                         this.$browser.remove();
    4282                 }, this );
    4283         },
    4284 
    4285         refresh: function() {
    4286                 if ( this.uploader ) {
    4287                         this.uploader.refresh();
    4288                 }
    4289         },
    4290 
    4291         ready: function() {
    4292                 var postId = wp.media.view.settings.post.id,
    4293                         dropzone;
    4294 
    4295                 // If the uploader already exists, bail.
    4296                 if ( this.uploader ) {
    4297                         return;
    4298                 }
    4299 
    4300                 if ( postId ) {
    4301                         this.options.uploader.params.post_id = postId;
    4302                 }
    4303                 this.uploader = new wp.Uploader( this.options.uploader );
    4304 
    4305                 dropzone = this.uploader.dropzone;
    4306                 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
    4307                 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
    4308 
    4309                 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
    4310         },
    4311 
    4312         _ready: function() {
    4313                 this.controller.trigger( 'uploader:ready' );
    4314         },
    4315 
    4316         show: function() {
    4317                 var $el = this.$el.show();
    4318 
    4319                 // Ensure that the animation is triggered by waiting until
    4320                 // the transparent element is painted into the DOM.
    4321                 _.defer( function() {
    4322                         $el.css({ opacity: 1 });
    4323                 });
    4324         },
    4325 
    4326         hide: function() {
    4327                 var $el = this.$el.css({ opacity: 0 });
    4328 
    4329                 wp.media.transition( $el ).done( function() {
    4330                         // Transition end events are subject to race conditions.
    4331                         // Make sure that the value is set as intended.
    4332                         if ( '0' === $el.css('opacity') ) {
    4333                                 $el.hide();
    4334                         }
    4335                 });
    4336 
    4337                 // https://core.trac.wordpress.org/ticket/27341
    4338                 _.delay( function() {
    4339                         if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
    4340                                 $el.hide();
    4341                         }
    4342                 }, 500 );
    4343         }
    4344 });
    4345 
    4346 module.exports = UploaderWindow;
    4347 
    4348 
    4349 /***/ }),
    4350 /* 54 */
    4351 /***/ (function(module, exports) {
    4352 
    4353 /*globals wp, _, jQuery */
    4354 
    4355 /**
    4356  * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap
    4357  * or #wp-fullscreen-body) and relays drag'n'dropped files to a media workflow.
    4358  *
    4359  * wp.media.view.EditorUploader
     2234 * wp.media.view.AttachmentCompat
     2235 *
     2236 * A view to display fields added via the `attachment_fields_to_edit` filter.
    43602237 *
    43612238 * @class
    … …  
    43652242 */
    43662243var View = wp.media.View,
    4367         l10n = wp.media.view.l10n,
    4368         $ = jQuery,
    4369         EditorUploader;
    4370 
    4371 EditorUploader = View.extend({
    4372         tagName:   'div',
    4373         className: 'uploader-editor',
    4374         template:  wp.template( 'uploader-editor' ),
    4375 
    4376         localDrag: false,
    4377         overContainer: false,
    4378         overDropzone: false,
    4379         draggingFile: null,
    4380 
    4381         /**
    4382          * Bind drag'n'drop events to callbacks.
    4383          */
     2244        AttachmentCompat;
     2245
     2246AttachmentCompat = View.extend({
     2247        tagName:   'form',
     2248        className: 'compat-item',
     2249
     2250        events: {
     2251                'submit':          'preventDefault',
     2252                'change input':    'save',
     2253                'change select':   'save',
     2254                'change textarea': 'save'
     2255        },
     2256
    43842257        initialize: function() {
    4385                 this.initialized = false;
    4386 
    4387                 // Bail if not enabled or UA does not support drag'n'drop or File API.
    4388                 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
    4389                         return this;
    4390                 }
    4391 
    4392                 this.$document = $(document);
    4393                 this.dropzones = [];
    4394                 this.files = [];
    4395 
    4396                 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
    4397                 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
    4398                 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
    4399                 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
    4400 
    4401                 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
    4402                 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
    4403 
    4404                 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
    4405                         this.localDrag = event.type === 'dragstart';
    4406                 }, this ) );
    4407 
    4408                 this.initialized = true;
    4409                 return this;
    4410         },
    4411 
    4412         /**
    4413          * Check browser support for drag'n'drop.
    4414          *
    4415          * @return Boolean
    4416          */
    4417         browserSupport: function() {
    4418                 var supports = false, div = document.createElement('div');
    4419 
    4420                 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
    4421                 supports = supports && !! ( window.File && window.FileList && window.FileReader );
    4422                 return supports;
    4423         },
    4424 
    4425         isDraggingFile: function( event ) {
    4426                 if ( this.draggingFile !== null ) {
    4427                         return this.draggingFile;
    4428                 }
    4429 
    4430                 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
    4431                         return false;
    4432                 }
    4433 
    4434                 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
    4435                         _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
    4436 
    4437                 return this.draggingFile;
    4438         },
    4439 
    4440         refresh: function( e ) {
    4441                 var dropzone_id;
    4442                 for ( dropzone_id in this.dropzones ) {
    4443                         // Hide the dropzones only if dragging has left the screen.
    4444                         this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
    4445                 }
    4446 
    4447                 if ( ! _.isUndefined( e ) ) {
    4448                         $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
    4449                 }
    4450 
    4451                 if ( ! this.overContainer && ! this.overDropzone ) {
    4452                         this.draggingFile = null;
    4453                 }
    4454 
    4455                 return this;
    4456         },
    4457 
    4458         render: function() {
    4459                 if ( ! this.initialized ) {
    4460                         return this;
    4461                 }
    4462 
    4463                 View.prototype.render.apply( this, arguments );
    4464                 $( '.wp-editor-wrap, #wp-fullscreen-body' ).each( _.bind( this.attach, this ) );
    4465                 return this;
    4466         },
    4467 
    4468         attach: function( index, editor ) {
    4469                 // Attach a dropzone to an editor.
    4470                 var dropzone = this.$el.clone();
    4471                 this.dropzones.push( dropzone );
    4472                 $( editor ).append( dropzone );
    4473                 return this;
    4474         },
    4475 
    4476         /**
    4477          * When a file is dropped on the editor uploader, open up an editor media workflow
    4478          * and upload the file immediately.
    4479          *
    4480          * @param  {jQuery.Event} event The 'drop' event.
    4481          */
    4482         drop: function( event ) {
    4483                 var $wrap = null, uploadView;
    4484 
    4485                 this.containerDragleave( event );
    4486                 this.dropzoneDragleave( event );
    4487 
    4488                 this.files = event.originalEvent.dataTransfer.files;
    4489                 if ( this.files.length < 1 ) {
    4490                         return;
    4491                 }
    4492 
    4493                 // Set the active editor to the drop target.
    4494                 $wrap = $( event.target ).parents( '.wp-editor-wrap' );
    4495                 if ( $wrap.length > 0 && $wrap[0].id ) {
    4496                         window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
    4497                 }
    4498 
    4499                 if ( ! this.workflow ) {
    4500                         this.workflow = wp.media.editor.open( 'content', {
    4501                                 frame:    'post',
    4502                                 state:    'insert',
    4503                                 title:    l10n.addMedia,
    4504                                 multiple: true
    4505                         });
    4506                         uploadView = this.workflow.uploader;
    4507                         if ( uploadView.uploader && uploadView.uploader.ready ) {
    4508                                 this.addFiles.apply( this );
    4509                         } else {
    4510                                 this.workflow.on( 'uploader:ready', this.addFiles, this );
    4511                         }
    4512                 } else {
    4513                         this.workflow.state().reset();
    4514                         this.addFiles.apply( this );
    4515                         this.workflow.open();
    4516                 }
    4517 
    4518                 return false;
    4519         },
    4520 
    4521         /**
    4522          * Add the files to the uploader.
    4523          */
    4524         addFiles: function() {
    4525                 if ( this.files.length ) {
    4526                         this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
    4527                         this.files = [];
    4528                 }
    4529                 return this;
    4530         },
    4531 
    4532         containerDragover: function( event ) {
    4533                 if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    4534                         return;
    4535                 }
    4536 
    4537                 this.overContainer = true;
    4538                 this.refresh();
    4539         },
    4540 
    4541         containerDragleave: function() {
    4542                 this.overContainer = false;
    4543 
    4544                 // Throttle dragleave because it's called when bouncing from some elements to others.
    4545                 _.delay( _.bind( this.refresh, this ), 50 );
    4546         },
    4547 
    4548         dropzoneDragover: function( event ) {
    4549                 if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    4550                         return;
    4551                 }
    4552 
    4553                 this.overDropzone = true;
    4554                 this.refresh( event );
    4555                 return false;
    4556         },
    4557 
    4558         dropzoneDragleave: function( e ) {
    4559                 this.overDropzone = false;
    4560                 _.delay( _.bind( this.refresh, this, e ), 50 );
    4561         },
    4562 
    4563         click: function( e ) {
    4564                 // In the rare case where the dropzone gets stuck, hide it on click.
    4565                 this.containerDragleave( e );
    4566                 this.dropzoneDragleave( e );
    4567                 this.localDrag = false;
    4568         }
    4569 });
    4570 
    4571 module.exports = EditorUploader;
    4572 
    4573 
    4574 /***/ }),
    4575 /* 55 */
    4576 /***/ (function(module, exports) {
    4577 
    4578 /*globals wp, _ */
    4579 
    4580 /**
    4581  * wp.media.view.UploaderInline
    4582  *
    4583  * The inline uploader that shows up in the 'Upload Files' tab.
    4584  *
    4585  * @class
    4586  * @augments wp.media.View
    4587  * @augments wp.Backbone.View
    4588  * @augments Backbone.View
    4589  */
    4590 var View = wp.media.View,
    4591         UploaderInline;
    4592 
    4593 UploaderInline = View.extend({
    4594         tagName:   'div',
    4595         className: 'uploader-inline',
    4596         template:  wp.template('uploader-inline'),
    4597 
    4598         events: {
    4599                 'click .close': 'hide'
    4600         },
    4601 
    4602         initialize: function() {
    4603                 _.defaults( this.options, {
    4604                         message: '',
    4605                         status:  true,
    4606                         canClose: false
    4607                 });
    4608 
    4609                 if ( ! this.options.$browser && this.controller.uploader ) {
    4610                         this.options.$browser = this.controller.uploader.$browser;
    4611                 }
    4612 
    4613                 if ( _.isUndefined( this.options.postId ) ) {
    4614                         this.options.postId = wp.media.view.settings.post.id;
    4615                 }
    4616 
    4617                 if ( this.options.status ) {
    4618                         this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
    4619                                 controller: this.controller
    4620                         }) );
    4621                 }
    4622         },
    4623 
    4624         prepare: function() {
    4625                 var suggestedWidth = this.controller.state().get('suggestedWidth'),
    4626                         suggestedHeight = this.controller.state().get('suggestedHeight'),
    4627                         data = {};
    4628 
    4629                 data.message = this.options.message;
    4630                 data.canClose = this.options.canClose;
    4631 
    4632                 if ( suggestedWidth && suggestedHeight ) {
    4633                         data.suggestedWidth = suggestedWidth;
    4634                         data.suggestedHeight = suggestedHeight;
    4635                 }
    4636 
    4637                 return data;
    4638         },
    4639         /**
    4640          * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
     2258                this.listenTo( this.model, 'change:compat', this.render );
     2259        },
     2260        /**
     2261         * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    46412262         */
    46422263        dispose: function() {
    4643                 if ( this.disposing ) {
    4644                         /**
    4645                          * call 'dispose' directly on the parent class
    4646                          */
    4647                         return View.prototype.dispose.apply( this, arguments );
    4648                 }
    4649 
    4650                 // Run remove on `dispose`, so we can be sure to refresh the
    4651                 // uploader with a view-less DOM. Track whether we're disposing
    4652                 // so we don't trigger an infinite loop.
    4653                 this.disposing = true;
    4654                 return this.remove();
    4655         },
    4656         /**
    4657          * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    4658          */
    4659         remove: function() {
    4660                 /**
    4661                  * call 'remove' directly on the parent class
    4662                  */
    4663                 var result = View.prototype.remove.apply( this, arguments );
    4664 
    4665                 _.defer( _.bind( this.refresh, this ) );
    4666                 return result;
    4667         },
    4668 
    4669         refresh: function() {
    4670                 var uploader = this.controller.uploader;
    4671 
    4672                 if ( uploader ) {
    4673                         uploader.refresh();
    4674                 }
    4675         },
    4676         /**
    4677          * @returns {wp.media.view.UploaderInline}
    4678          */
    4679         ready: function() {
    4680                 var $browser = this.options.$browser,
    4681                         $placeholder;
    4682 
    4683                 if ( this.controller.uploader ) {
    4684                         $placeholder = this.$('.browser');
    4685 
    4686                         // Check if we've already replaced the placeholder.
    4687                         if ( $placeholder[0] === $browser[0] ) {
    4688                                 return;
    4689                         }
    4690 
    4691                         $browser.detach().text( $placeholder.text() );
    4692                         $browser[0].className = $placeholder[0].className;
    4693                         $placeholder.replaceWith( $browser.show() );
    4694                 }
    4695 
    4696                 this.refresh();
    4697                 return this;
    4698         },
    4699         show: function() {
    4700                 this.$el.removeClass( 'hidden' );
    4701         },
    4702         hide: function() {
    4703                 this.$el.addClass( 'hidden' );
    4704         }
    4705 
    4706 });
    4707 
    4708 module.exports = UploaderInline;
    4709 
    4710 
    4711 /***/ }),
    4712 /* 56 */
    4713 /***/ (function(module, exports) {
    4714 
    4715 /*globals wp, _ */
    4716 
    4717 /**
    4718  * wp.media.view.UploaderStatus
    4719  *
    4720  * An uploader status for on-going uploads.
    4721  *
    4722  * @class
    4723  * @augments wp.media.View
    4724  * @augments wp.Backbone.View
    4725  * @augments Backbone.View
    4726  */
    4727 var View = wp.media.View,
    4728         UploaderStatus;
    4729 
    4730 UploaderStatus = View.extend({
    4731         className: 'media-uploader-status',
    4732         template:  wp.template('uploader-status'),
    4733 
    4734         events: {
    4735                 'click .upload-dismiss-errors': 'dismiss'
    4736         },
    4737 
    4738         initialize: function() {
    4739                 this.queue = wp.Uploader.queue;
    4740                 this.queue.on( 'add remove reset', this.visibility, this );
    4741                 this.queue.on( 'add remove reset change:percent', this.progress, this );
    4742                 this.queue.on( 'add remove reset change:uploading', this.info, this );
    4743 
    4744                 this.errors = wp.Uploader.errors;
    4745                 this.errors.reset();
    4746                 this.errors.on( 'add remove reset', this.visibility, this );
    4747                 this.errors.on( 'add', this.error, this );
    4748         },
    4749         /**
    4750          * @global wp.Uploader
    4751          * @returns {wp.media.view.UploaderStatus}
    4752          */
    4753         dispose: function() {
    4754                 wp.Uploader.queue.off( null, null, this );
    4755                 /**
    4756                  * call 'dispose' directly on the parent class
    4757                  */
    4758                 View.prototype.dispose.apply( this, arguments );
    4759                 return this;
    4760         },
    4761 
    4762         visibility: function() {
    4763                 this.$el.toggleClass( 'uploading', !! this.queue.length );
    4764                 this.$el.toggleClass( 'errors', !! this.errors.length );
    4765                 this.$el.toggle( !! this.queue.length || !! this.errors.length );
    4766         },
    4767 
    4768         ready: function() {
    4769                 _.each({
    4770                         '$bar':      '.media-progress-bar div',
    4771                         '$index':    '.upload-index',
    4772                         '$total':    '.upload-total',
    4773                         '$filename': '.upload-filename'
    4774                 }, function( selector, key ) {
    4775                         this[ key ] = this.$( selector );
    4776                 }, this );
    4777 
    4778                 this.visibility();
    4779                 this.progress();
    4780                 this.info();
    4781         },
    4782 
    4783         progress: function() {
    4784                 var queue = this.queue,
    4785                         $bar = this.$bar;
    4786 
    4787                 if ( ! $bar || ! queue.length ) {
    4788                         return;
    4789                 }
    4790 
    4791                 $bar.width( ( queue.reduce( function( memo, attachment ) {
    4792                         if ( ! attachment.get('uploading') ) {
    4793                                 return memo + 100;
    4794                         }
    4795 
    4796                         var percent = attachment.get('percent');
    4797                         return memo + ( _.isNumber( percent ) ? percent : 100 );
    4798                 }, 0 ) / queue.length ) + '%' );
    4799         },
    4800 
    4801         info: function() {
    4802                 var queue = this.queue,
    4803                         index = 0, active;
    4804 
    4805                 if ( ! queue.length ) {
    4806                         return;
    4807                 }
    4808 
    4809                 active = this.queue.find( function( attachment, i ) {
    4810                         index = i;
    4811                         return attachment.get('uploading');
    4812                 });
    4813 
    4814                 this.$index.text( index + 1 );
    4815                 this.$total.text( queue.length );
    4816                 this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
    4817         },
    4818         /**
    4819          * @param {string} filename
    4820          * @returns {string}
    4821          */
    4822         filename: function( filename ) {
    4823                 return wp.media.truncate( _.escape( filename ), 24 );
    4824         },
    4825         /**
    4826          * @param {Backbone.Model} error
    4827          */
    4828         error: function( error ) {
    4829                 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    4830                         filename: this.filename( error.get('file').name ),
    4831                         message:  error.get('message')
    4832                 }), { at: 0 });
    4833         },
    4834 
    4835         /**
    4836          * @global wp.Uploader
    4837          *
    4838          * @param {Object} event
    4839          */
    4840         dismiss: function( event ) {
    4841                 var errors = this.views.get('.upload-errors');
    4842 
    4843                 event.preventDefault();
    4844 
    4845                 if ( errors ) {
    4846                         _.invoke( errors, 'remove' );
    4847                 }
    4848                 wp.Uploader.errors.reset();
    4849         }
    4850 });
    4851 
    4852 module.exports = UploaderStatus;
    4853 
    4854 
    4855 /***/ }),
    4856 /* 57 */
    4857 /***/ (function(module, exports) {
    4858 
    4859 /*globals wp */
    4860 
    4861 /**
    4862  * wp.media.view.UploaderStatusError
    4863  *
    4864  * @class
    4865  * @augments wp.media.View
    4866  * @augments wp.Backbone.View
    4867  * @augments Backbone.View
    4868  */
    4869 var UploaderStatusError = wp.media.View.extend({
    4870         className: 'upload-error',
    4871         template:  wp.template('uploader-status-error')
    4872 });
    4873 
    4874 module.exports = UploaderStatusError;
    4875 
    4876 
    4877 /***/ }),
    4878 /* 58 */
    4879 /***/ (function(module, exports) {
    4880 
    4881 /*globals _, Backbone */
    4882 
    4883 /**
    4884  * wp.media.view.Toolbar
    4885  *
    4886  * A toolbar which consists of a primary and a secondary section. Each sections
    4887  * can be filled with views.
    4888  *
    4889  * @class
    4890  * @augments wp.media.View
    4891  * @augments wp.Backbone.View
    4892  * @augments Backbone.View
    4893  */
    4894 var View = wp.media.View,
    4895         Toolbar;
    4896 
    4897 Toolbar = View.extend({
    4898         tagName:   'div',
    4899         className: 'media-toolbar',
    4900 
    4901         initialize: function() {
    4902                 var state = this.controller.state(),
    4903                         selection = this.selection = state.get('selection'),
    4904                         library = this.library = state.get('library');
    4905 
    4906                 this._views = {};
    4907 
    4908                 // The toolbar is composed of two `PriorityList` views.
    4909                 this.primary   = new wp.media.view.PriorityList();
    4910                 this.secondary = new wp.media.view.PriorityList();
    4911                 this.primary.$el.addClass('media-toolbar-primary search-form');
    4912                 this.secondary.$el.addClass('media-toolbar-secondary');
    4913 
    4914                 this.views.set([ this.secondary, this.primary ]);
    4915 
    4916                 if ( this.options.items ) {
    4917                         this.set( this.options.items, { silent: true });
    4918                 }
    4919 
    4920                 if ( ! this.options.silent ) {
    4921                         this.render();
    4922                 }
    4923 
    4924                 if ( selection ) {
    4925                         selection.on( 'add remove reset', this.refresh, this );
    4926                 }
    4927 
    4928                 if ( library ) {
    4929                         library.on( 'add remove reset', this.refresh, this );
    4930                 }
    4931         },
    4932         /**
    4933          * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
    4934          */
    4935         dispose: function() {
    4936                 if ( this.selection ) {
    4937                         this.selection.off( null, null, this );
    4938                 }
    4939 
    4940                 if ( this.library ) {
    4941                         this.library.off( null, null, this );
     2264                if ( this.$(':focus').length ) {
     2265                        this.save();
    49422266                }
    49432267                /**
    … …  
    49462270                return View.prototype.dispose.apply( this, arguments );
    49472271        },
    4948 
    4949         ready: function() {
    4950                 this.refresh();
    4951         },
    4952 
    4953         /**
    4954          * @param {string} id
    4955          * @param {Backbone.View|Object} view
    4956          * @param {Object} [options={}]
    4957          * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
    4958          */
    4959         set: function( id, view, options ) {
    4960                 var list;
    4961                 options = options || {};
    4962 
    4963                 // Accept an object with an `id` : `view` mapping.
    4964                 if ( _.isObject( id ) ) {
    4965                         _.each( id, function( view, id ) {
    4966                                 this.set( id, view, { silent: true });
    4967                         }, this );
    4968 
    4969                 } else {
    4970                         if ( ! ( view instanceof Backbone.View ) ) {
    4971                                 view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
    4972                                 view = new wp.media.view.Button( view ).render();
    4973                         }
    4974 
    4975                         view.controller = view.controller || this.controller;
    4976 
    4977                         this._views[ id ] = view;
    4978 
    4979                         list = view.options.priority < 0 ? 'secondary' : 'primary';
    4980                         this[ list ].set( id, view, options );
    4981                 }
    4982 
    4983                 if ( ! options.silent ) {
    4984                         this.refresh();
    4985                 }
    4986 
     2272        /**
     2273         * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     2274         */
     2275        render: function() {
     2276                var compat = this.model.get('compat');
     2277                if ( ! compat || ! compat.item ) {
     2278                        return;
     2279                }
     2280
     2281                this.views.detach();
     2282                this.$el.html( compat.item );
     2283                this.views.render();
    49872284                return this;
    49882285        },
    49892286        /**
    4990          * @param {string} id
    4991          * @returns {wp.media.view.Button}
    4992          */
    4993         get: function( id ) {
    4994                 return this._views[ id ];
    4995         },
    4996         /**
    4997          * @param {string} id
    4998          * @param {Object} options
    4999          * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
    5000          */
    5001         unset: function( id, options ) {
    5002                 delete this._views[ id ];
    5003                 this.primary.unset( id, options );
    5004                 this.secondary.unset( id, options );
    5005 
    5006                 if ( ! options || ! options.silent ) {
    5007                         this.refresh();
    5008                 }
    5009                 return this;
    5010         },
    5011 
    5012         refresh: function() {
    5013                 var state = this.controller.state(),
    5014                         library = state.get('library'),
    5015                         selection = state.get('selection');
    5016 
    5017                 _.each( this._views, function( button ) {
    5018                         if ( ! button.model || ! button.options || ! button.options.requires ) {
    5019                                 return;
    5020                         }
    5021 
    5022                         var requires = button.options.requires,
    5023                                 disabled = false;
    5024 
    5025                         // Prevent insertion of attachments if any of them are still uploading
    5026                         disabled = _.some( selection.models, function( attachment ) {
    5027                                 return attachment.get('uploading') === true;
    5028                         });
    5029 
    5030                         if ( requires.selection && selection && ! selection.length ) {
    5031                                 disabled = true;
    5032                         } else if ( requires.library && library && ! library.length ) {
    5033                                 disabled = true;
    5034                         }
    5035                         button.model.set( 'disabled', disabled );
     2287         * @param {Object} event
     2288         */
     2289        preventDefault: function( event ) {
     2290                event.preventDefault();
     2291        },
     2292        /**
     2293         * @param {Object} event
     2294         */
     2295        save: function( event ) {
     2296                var data = {};
     2297
     2298                if ( event ) {
     2299                        event.preventDefault();
     2300                }
     2301
     2302                _.each( this.$el.serializeArray(), function( pair ) {
     2303                        data[ pair.name ] = pair.value;
    50362304                });
     2305
     2306                this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
     2307                this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
     2308        },
     2309
     2310        postSave: function() {
     2311                this.controller.trigger( 'attachment:compat:ready', ['ready'] );
    50372312        }
    50382313});
    50392314
    5040 module.exports = Toolbar;
    5041 
    5042 
    5043 /***/ }),
    5044 /* 59 */
    5045 /***/ (function(module, exports) {
    5046 
    5047 /*globals wp, _ */
    5048 
    5049 /**
    5050  * wp.media.view.Toolbar.Select
    5051  *
    5052  * @class
    5053  * @augments wp.media.view.Toolbar
    5054  * @augments wp.media.View
    5055  * @augments wp.Backbone.View
    5056  * @augments Backbone.View
    5057  */
    5058 var Toolbar = wp.media.view.Toolbar,
    5059         l10n = wp.media.view.l10n,
    5060         Select;
    5061 
    5062 Select = Toolbar.extend({
    5063         initialize: function() {
    5064                 var options = this.options;
    5065 
    5066                 _.bindAll( this, 'clickSelect' );
    5067 
    5068                 _.defaults( options, {
    5069                         event: 'select',
    5070                         state: false,
    5071                         reset: true,
    5072                         close: true,
    5073                         text:  l10n.select,
    5074 
    5075                         // Does the button rely on the selection?
    5076                         requires: {
    5077                                 selection: true
    5078                         }
    5079                 });
    5080 
    5081                 options.items = _.defaults( options.items || {}, {
    5082                         select: {
    5083                                 style:    'primary',
    5084                                 text:     options.text,
    5085                                 priority: 80,
    5086                                 click:    this.clickSelect,
    5087                                 requires: options.requires
    5088                         }
    5089                 });
    5090                 // Call 'initialize' directly on the parent class.
    5091                 Toolbar.prototype.initialize.apply( this, arguments );
    5092         },
    5093 
    5094         clickSelect: function() {
    5095                 var options = this.options,
    5096                         controller = this.controller;
    5097 
    5098                 if ( options.close ) {
    5099                         controller.close();
    5100                 }
    5101 
    5102                 if ( options.event ) {
    5103                         controller.state().trigger( options.event );
    5104                 }
    5105 
    5106                 if ( options.state ) {
    5107                         controller.setState( options.state );
    5108                 }
    5109 
    5110                 if ( options.reset ) {
    5111                         controller.reset();
    5112                 }
    5113         }
    5114 });
    5115 
    5116 module.exports = Select;
    5117 
    5118 
    5119 /***/ }),
    5120 /* 60 */
    5121 /***/ (function(module, exports) {
    5122 
    5123 /*globals wp, _ */
    5124 
    5125 /**
    5126  * wp.media.view.Toolbar.Embed
    5127  *
    5128  * @class
    5129  * @augments wp.media.view.Toolbar.Select
    5130  * @augments wp.media.view.Toolbar
    5131  * @augments wp.media.View
    5132  * @augments wp.Backbone.View
    5133  * @augments Backbone.View
    5134  */
    5135 var Select = wp.media.view.Toolbar.Select,
    5136         l10n = wp.media.view.l10n,
    5137         Embed;
    5138 
    5139 Embed = Select.extend({
    5140         initialize: function() {
    5141                 _.defaults( this.options, {
    5142                         text: l10n.insertIntoPost,
    5143                         requires: false
    5144                 });
    5145                 // Call 'initialize' directly on the parent class.
    5146                 Select.prototype.initialize.apply( this, arguments );
    5147         },
    5148 
    5149         refresh: function() {
    5150                 var url = this.controller.state().props.get('url');
    5151                 this.get('select').model.set( 'disabled', ! url || url === 'http://' );
    5152                 /**
    5153                  * call 'refresh' directly on the parent class
    5154                  */
    5155                 Select.prototype.refresh.apply( this, arguments );
    5156         }
    5157 });
    5158 
    5159 module.exports = Embed;
    5160 
    5161 
    5162 /***/ }),
    5163 /* 61 */
    5164 /***/ (function(module, exports) {
    5165 
    5166 /*globals _, Backbone */
    5167 
    5168 /**
    5169  * wp.media.view.Button
    5170  *
    5171  * @class
    5172  * @augments wp.media.View
    5173  * @augments wp.Backbone.View
    5174  * @augments Backbone.View
    5175  */
    5176 var Button = wp.media.View.extend({
    5177         tagName:    'a',
    5178         className:  'media-button',
    5179         attributes: { href: '#' },
    5180 
    5181         events: {
    5182                 'click': 'click'
    5183         },
    5184 
    5185         defaults: {
    5186                 text:     '',
    5187                 style:    '',
    5188                 size:     'large',
    5189                 disabled: false
    5190         },
    5191 
    5192         initialize: function() {
    5193                 /**
    5194                  * Create a model with the provided `defaults`.
    5195                  *
    5196                  * @member {Backbone.Model}
    5197                  */
    5198                 this.model = new Backbone.Model( this.defaults );
    5199 
    5200                 // If any of the `options` have a key from `defaults`, apply its
    5201                 // value to the `model` and remove it from the `options object.
    5202                 _.each( this.defaults, function( def, key ) {
    5203                         var value = this.options[ key ];
    5204                         if ( _.isUndefined( value ) ) {
    5205                                 return;
    5206                         }
    5207 
    5208                         this.model.set( key, value );
    5209                         delete this.options[ key ];
    5210                 }, this );
    5211 
    5212                 this.listenTo( this.model, 'change', this.render );
    5213         },
    5214         /**
    5215          * @returns {wp.media.view.Button} Returns itself to allow chaining
    5216          */
    5217         render: function() {
    5218                 var classes = [ 'button', this.className ],
    5219                         model = this.model.toJSON();
    5220 
    5221                 if ( model.style ) {
    5222                         classes.push( 'button-' + model.style );
    5223                 }
    5224 
    5225                 if ( model.size ) {
    5226                         classes.push( 'button-' + model.size );
    5227                 }
    5228 
    5229                 classes = _.uniq( classes.concat( this.options.classes ) );
    5230                 this.el.className = classes.join(' ');
    5231 
    5232                 this.$el.attr( 'disabled', model.disabled );
    5233                 this.$el.text( this.model.get('text') );
    5234 
    5235                 return this;
    5236         },
    5237         /**
    5238          * @param {Object} event
    5239          */
    5240         click: function( event ) {
    5241                 if ( '#' === this.attributes.href ) {
    5242                         event.preventDefault();
    5243                 }
    5244 
    5245                 if ( this.options.click && ! this.model.get('disabled') ) {
    5246                         this.options.click.apply( this, arguments );
    5247                 }
    5248         }
    5249 });
    5250 
    5251 module.exports = Button;
    5252 
    5253 
    5254 /***/ }),
    5255 /* 62 */
    5256 /***/ (function(module, exports) {
    5257 
    5258 /*globals _, Backbone */
    5259 
    5260 /**
    5261  * wp.media.view.ButtonGroup
    5262  *
    5263  * @class
    5264  * @augments wp.media.View
    5265  * @augments wp.Backbone.View
    5266  * @augments Backbone.View
    5267  */
    5268 var $ = Backbone.$,
    5269         ButtonGroup;
    5270 
    5271 ButtonGroup = wp.media.View.extend({
    5272         tagName:   'div',
    5273         className: 'button-group button-large media-button-group',
    5274 
    5275         initialize: function() {
    5276                 /**
    5277                  * @member {wp.media.view.Button[]}
    5278                  */
    5279                 this.buttons = _.map( this.options.buttons || [], function( button ) {
    5280                         if ( button instanceof Backbone.View ) {
    5281                                 return button;
    5282                         } else {
    5283                                 return new wp.media.view.Button( button ).render();
    5284                         }
    5285                 });
    5286 
    5287                 delete this.options.buttons;
    5288 
    5289                 if ( this.options.classes ) {
    5290                         this.$el.addClass( this.options.classes );
    5291                 }
    5292         },
    5293 
    5294         /**
    5295          * @returns {wp.media.view.ButtonGroup}
    5296          */
    5297         render: function() {
    5298                 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
    5299                 return this;
    5300         }
    5301 });
    5302 
    5303 module.exports = ButtonGroup;
    5304 
    5305 
    5306 /***/ }),
    5307 /* 63 */
    5308 /***/ (function(module, exports) {
    5309 
    5310 /*globals _, Backbone */
    5311 
    5312 /**
    5313  * wp.media.view.PriorityList
    5314  *
    5315  * @class
    5316  * @augments wp.media.View
    5317  * @augments wp.Backbone.View
    5318  * @augments Backbone.View
    5319  */
    5320 var PriorityList = wp.media.View.extend({
    5321         tagName:   'div',
    5322 
    5323         initialize: function() {
    5324                 this._views = {};
    5325 
    5326                 this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
    5327                 delete this.options.views;
    5328 
    5329                 if ( ! this.options.silent ) {
    5330                         this.render();
    5331                 }
    5332         },
    5333         /**
    5334          * @param {string} id
    5335          * @param {wp.media.View|Object} view
    5336          * @param {Object} options
    5337          * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
    5338          */
    5339         set: function( id, view, options ) {
    5340                 var priority, views, index;
    5341 
    5342                 options = options || {};
    5343 
    5344                 // Accept an object with an `id` : `view` mapping.
    5345                 if ( _.isObject( id ) ) {
    5346                         _.each( id, function( view, id ) {
    5347                                 this.set( id, view );
    5348                         }, this );
    5349                         return this;
    5350                 }
    5351 
    5352                 if ( ! (view instanceof Backbone.View) ) {
    5353                         view = this.toView( view, id, options );
    5354                 }
    5355                 view.controller = view.controller || this.controller;
    5356 
    5357                 this.unset( id );
    5358 
    5359                 priority = view.options.priority || 10;
    5360                 views = this.views.get() || [];
    5361 
    5362                 _.find( views, function( existing, i ) {
    5363                         if ( existing.options.priority > priority ) {
    5364                                 index = i;
    5365                                 return true;
    5366                         }
    5367                 });
    5368 
    5369                 this._views[ id ] = view;
    5370                 this.views.add( view, {
    5371                         at: _.isNumber( index ) ? index : views.length || 0
    5372                 });
    5373 
    5374                 return this;
    5375         },
    5376         /**
    5377          * @param {string} id
    5378          * @returns {wp.media.View}
    5379          */
    5380         get: function( id ) {
    5381                 return this._views[ id ];
    5382         },
    5383         /**
    5384          * @param {string} id
    5385          * @returns {wp.media.view.PriorityList}
    5386          */
    5387         unset: function( id ) {
    5388                 var view = this.get( id );
    5389 
    5390                 if ( view ) {
    5391                         view.remove();
    5392                 }
    5393 
    5394                 delete this._views[ id ];
    5395                 return this;
    5396         },
    5397         /**
    5398          * @param {Object} options
    5399          * @returns {wp.media.View}
    5400          */
    5401         toView: function( options ) {
    5402                 return new wp.media.View( options );
    5403         }
    5404 });
    5405 
    5406 module.exports = PriorityList;
    5407 
    5408 
    5409 /***/ }),
    5410 /* 64 */
    5411 /***/ (function(module, exports) {
    5412 
    5413 /*globals jQuery */
    5414 
    5415 /**
    5416  * wp.media.view.MenuItem
    5417  *
    5418  * @class
    5419  * @augments wp.media.View
    5420  * @augments wp.Backbone.View
    5421  * @augments Backbone.View
    5422  */
    5423 var $ = jQuery,
    5424         MenuItem;
    5425 
    5426 MenuItem = wp.media.View.extend({
    5427         tagName:   'a',
    5428         className: 'media-menu-item',
    5429 
    5430         attributes: {
    5431                 href: '#'
    5432         },
    5433 
    5434         events: {
    5435                 'click': '_click'
    5436         },
    5437         /**
    5438          * @param {Object} event
    5439          */
    5440         _click: function( event ) {
    5441                 var clickOverride = this.options.click;
    5442 
    5443                 if ( event ) {
    5444                         event.preventDefault();
    5445                 }
    5446 
    5447                 if ( clickOverride ) {
    5448                         clickOverride.call( this );
    5449                 } else {
    5450                         this.click();
    5451                 }
    5452 
    5453                 // When selecting a tab along the left side,
    5454                 // focus should be transferred into the main panel
    5455                 if ( ! wp.media.isTouchDevice ) {
    5456                         $('.media-frame-content input').first().focus();
    5457                 }
    5458         },
    5459 
    5460         click: function() {
    5461                 var state = this.options.state;
    5462 
    5463                 if ( state ) {
    5464                         this.controller.setState( state );
    5465                         this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
    5466                 }
    5467         },
    5468         /**
    5469          * @returns {wp.media.view.MenuItem} returns itself to allow chaining
    5470          */
    5471         render: function() {
    5472                 var options = this.options;
    5473 
    5474                 if ( options.text ) {
    5475                         this.$el.text( options.text );
    5476                 } else if ( options.html ) {
    5477                         this.$el.html( options.html );
    5478                 }
    5479 
    5480                 return this;
    5481         }
    5482 });
    5483 
    5484 module.exports = MenuItem;
    5485 
    5486 
    5487 /***/ }),
    5488 /* 65 */
    5489 /***/ (function(module, exports) {
    5490 
    5491 /**
    5492  * wp.media.view.Menu
    5493  *
    5494  * @class
    5495  * @augments wp.media.view.PriorityList
    5496  * @augments wp.media.View
    5497  * @augments wp.Backbone.View
    5498  * @augments Backbone.View
    5499  */
    5500 var MenuItem = wp.media.view.MenuItem,
    5501         PriorityList = wp.media.view.PriorityList,
    5502         Menu;
    5503 
    5504 Menu = PriorityList.extend({
    5505         tagName:   'div',
    5506         className: 'media-menu',
    5507         property:  'state',
    5508         ItemView:  MenuItem,
    5509         region:    'menu',
    5510 
    5511         /* TODO: alternatively hide on any click anywhere
    5512         events: {
    5513                 'click': 'click'
    5514         },
    5515 
    5516         click: function() {
    5517                 this.$el.removeClass( 'visible' );
    5518         },
    5519         */
    5520 
    5521         /**
    5522          * @param {Object} options
    5523          * @param {string} id
    5524          * @returns {wp.media.View}
    5525          */
    5526         toView: function( options, id ) {
    5527                 options = options || {};
    5528                 options[ this.property ] = options[ this.property ] || id;
    5529                 return new this.ItemView( options ).render();
    5530         },
    5531 
    5532         ready: function() {
    5533                 /**
    5534                  * call 'ready' directly on the parent class
    5535                  */
    5536                 PriorityList.prototype.ready.apply( this, arguments );
    5537                 this.visibility();
    5538         },
    5539 
    5540         set: function() {
    5541                 /**
    5542                  * call 'set' directly on the parent class
    5543                  */
    5544                 PriorityList.prototype.set.apply( this, arguments );
    5545                 this.visibility();
    5546         },
    5547 
    5548         unset: function() {
    5549                 /**
    5550                  * call 'unset' directly on the parent class
    5551                  */
    5552                 PriorityList.prototype.unset.apply( this, arguments );
    5553                 this.visibility();
    5554         },
    5555 
    5556         visibility: function() {
    5557                 var region = this.region,
    5558                         view = this.controller[ region ].get(),
    5559                         views = this.views.get(),
    5560                         hide = ! views || views.length < 2;
    5561 
    5562                 if ( this === view ) {
    5563                         this.controller.$el.toggleClass( 'hide-' + region, hide );
    5564                 }
    5565         },
    5566         /**
    5567          * @param {string} id
    5568          */
    5569         select: function( id ) {
    5570                 var view = this.get( id );
    5571 
    5572                 if ( ! view ) {
    5573                         return;
    5574                 }
    5575 
    5576                 this.deselect();
    5577                 view.$el.addClass('active');
    5578         },
    5579 
    5580         deselect: function() {
    5581                 this.$el.children().removeClass('active');
    5582         },
    5583 
    5584         hide: function( id ) {
    5585                 var view = this.get( id );
    5586 
    5587                 if ( ! view ) {
    5588                         return;
    5589                 }
    5590 
    5591                 view.$el.addClass('hidden');
    5592         },
    5593 
    5594         show: function( id ) {
    5595                 var view = this.get( id );
    5596 
    5597                 if ( ! view ) {
    5598                         return;
    5599                 }
    5600 
    5601                 view.$el.removeClass('hidden');
    5602         }
    5603 });
    5604 
    5605 module.exports = Menu;
    5606 
    5607 
    5608 /***/ }),
    5609 /* 66 */
    5610 /***/ (function(module, exports) {
    5611 
    5612 /**
    5613  * wp.media.view.RouterItem
    5614  *
    5615  * @class
    5616  * @augments wp.media.view.MenuItem
    5617  * @augments wp.media.View
    5618  * @augments wp.Backbone.View
    5619  * @augments Backbone.View
    5620  */
    5621 var RouterItem = wp.media.view.MenuItem.extend({
    5622         /**
    5623          * On click handler to activate the content region's corresponding mode.
    5624          */
    5625         click: function() {
    5626                 var contentMode = this.options.contentMode;
    5627                 if ( contentMode ) {
    5628                         this.controller.content.mode( contentMode );
    5629                 }
    5630         }
    5631 });
    5632 
    5633 module.exports = RouterItem;
    5634 
    5635 
    5636 /***/ }),
    5637 /* 67 */
    5638 /***/ (function(module, exports) {
    5639 
    5640 /*globals wp */
    5641 
    5642 /**
    5643  * wp.media.view.Router
    5644  *
    5645  * @class
    5646  * @augments wp.media.view.Menu
    5647  * @augments wp.media.view.PriorityList
    5648  * @augments wp.media.View
    5649  * @augments wp.Backbone.View
    5650  * @augments Backbone.View
    5651  */
    5652 var Menu = wp.media.view.Menu,
    5653         Router;
    5654 
    5655 Router = Menu.extend({
    5656         tagName:   'div',
    5657         className: 'media-router',
    5658         property:  'contentMode',
    5659         ItemView:  wp.media.view.RouterItem,
    5660         region:    'router',
    5661 
    5662         initialize: function() {
    5663                 this.controller.on( 'content:render', this.update, this );
    5664                 // Call 'initialize' directly on the parent class.
    5665                 Menu.prototype.initialize.apply( this, arguments );
    5666         },
    5667 
    5668         update: function() {
    5669                 var mode = this.controller.content.mode();
    5670                 if ( mode ) {
    5671                         this.select( mode );
    5672                 }
    5673         }
    5674 });
    5675 
    5676 module.exports = Router;
    5677 
    5678 
    5679 /***/ }),
    5680 /* 68 */
    5681 /***/ (function(module, exports) {
    5682 
    5683 /**
    5684  * wp.media.view.Sidebar
    5685  *
    5686  * @class
    5687  * @augments wp.media.view.PriorityList
    5688  * @augments wp.media.View
    5689  * @augments wp.Backbone.View
    5690  * @augments Backbone.View
    5691  */
    5692 var Sidebar = wp.media.view.PriorityList.extend({
    5693         className: 'media-sidebar'
    5694 });
    5695 
    5696 module.exports = Sidebar;
    5697 
    5698 
    5699 /***/ }),
    5700 /* 69 */
    5701 /***/ (function(module, exports) {
    5702 
    5703 /*globals wp, _, jQuery */
    5704 
    5705 /**
    5706  * wp.media.view.Attachment
    5707  *
    5708  * @class
    5709  * @augments wp.media.View
    5710  * @augments wp.Backbone.View
    5711  * @augments Backbone.View
    5712  */
    5713 var View = wp.media.View,
    5714         $ = jQuery,
    5715         Attachment;
    5716 
    5717 Attachment = View.extend({
    5718         tagName:   'li',
    5719         className: 'attachment',
    5720         template:  wp.template('attachment'),
    5721 
    5722         attributes: function() {
    5723                 return {
    5724                         'tabIndex':     0,
    5725                         'role':         'checkbox',
    5726                         'aria-label':   this.model.get( 'title' ),
    5727                         'aria-checked': false,
    5728                         'data-id':      this.model.get( 'id' )
    5729                 };
    5730         },
    5731 
    5732         events: {
    5733                 'click .js--select-attachment':   'toggleSelectionHandler',
    5734                 'change [data-setting]':          'updateSetting',
    5735                 'change [data-setting] input':    'updateSetting',
    5736                 'change [data-setting] select':   'updateSetting',
    5737                 'change [data-setting] textarea': 'updateSetting',
    5738                 'click .close':                   'removeFromLibrary',
    5739                 'click .check':                   'checkClickHandler',
    5740                 'click a':                        'preventDefault',
    5741                 'keydown .close':                 'removeFromLibrary',
    5742                 'keydown':                        'toggleSelectionHandler'
    5743         },
    5744 
    5745         buttons: {},
    5746 
    5747         initialize: function() {
    5748                 var selection = this.options.selection,
    5749                         options = _.defaults( this.options, {
    5750                                 rerenderOnModelChange: true
    5751                         } );
    5752 
    5753                 if ( options.rerenderOnModelChange ) {
    5754                         this.listenTo( this.model, 'change', this.render );
    5755                 } else {
    5756                         this.listenTo( this.model, 'change:percent', this.progress );
    5757                 }
    5758                 this.listenTo( this.model, 'change:title', this._syncTitle );
    5759                 this.listenTo( this.model, 'change:caption', this._syncCaption );
    5760                 this.listenTo( this.model, 'change:artist', this._syncArtist );
    5761                 this.listenTo( this.model, 'change:album', this._syncAlbum );
    5762 
    5763                 // Update the selection.
    5764                 this.listenTo( this.model, 'add', this.select );
    5765                 this.listenTo( this.model, 'remove', this.deselect );
    5766                 if ( selection ) {
    5767                         selection.on( 'reset', this.updateSelect, this );
    5768                         // Update the model's details view.
    5769                         this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
    5770                         this.details( this.model, this.controller.state().get('selection') );
    5771                 }
    5772 
    5773                 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
    5774         },
    5775         /**
    5776          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    5777          */
    5778         dispose: function() {
    5779                 var selection = this.options.selection;
    5780 
    5781                 // Make sure all settings are saved before removing the view.
    5782                 this.updateAll();
    5783 
    5784                 if ( selection ) {
    5785                         selection.off( null, null, this );
    5786                 }
    5787                 /**
    5788                  * call 'dispose' directly on the parent class
    5789                  */
    5790                 View.prototype.dispose.apply( this, arguments );
    5791                 return this;
    5792         },
    5793         /**
    5794          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    5795          */
    5796         render: function() {
    5797                 var options = _.defaults( this.model.toJSON(), {
    5798                                 orientation:   'landscape',
    5799                                 uploading:     false,
    5800                                 type:          '',
    5801                                 subtype:       '',
    5802                                 icon:          '',
    5803                                 filename:      '',
    5804                                 caption:       '',
    5805                                 title:         '',
    5806                                 dateFormatted: '',
    5807                                 width:         '',
    5808                                 height:        '',
    5809                                 compat:        false,
    5810                                 alt:           '',
    5811                                 description:   ''
    5812                         }, this.options );
    5813 
    5814                 options.buttons  = this.buttons;
    5815                 options.describe = this.controller.state().get('describe');
    5816 
    5817                 if ( 'image' === options.type ) {
    5818                         options.size = this.imageSize();
    5819                 }
    5820 
    5821                 options.can = {};
    5822                 if ( options.nonces ) {
    5823                         options.can.remove = !! options.nonces['delete'];
    5824                         options.can.save = !! options.nonces.update;
    5825                 }
    5826 
    5827                 if ( this.controller.state().get('allowLocalEdits') ) {
    5828                         options.allowLocalEdits = true;
    5829                 }
    5830 
    5831                 if ( options.uploading && ! options.percent ) {
    5832                         options.percent = 0;
    5833                 }
    5834 
    5835                 this.views.detach();
    5836                 this.$el.html( this.template( options ) );
    5837 
    5838                 this.$el.toggleClass( 'uploading', options.uploading );
    5839 
    5840                 if ( options.uploading ) {
    5841                         this.$bar = this.$('.media-progress-bar div');
    5842                 } else {
    5843                         delete this.$bar;
    5844                 }
    5845 
    5846                 // Check if the model is selected.
    5847                 this.updateSelect();
    5848 
    5849                 // Update the save status.
    5850                 this.updateSave();
    5851 
    5852                 this.views.render();
    5853 
    5854                 return this;
    5855         },
    5856 
    5857         progress: function() {
    5858                 if ( this.$bar && this.$bar.length ) {
    5859                         this.$bar.width( this.model.get('percent') + '%' );
    5860                 }
    5861         },
    5862 
    5863         /**
    5864          * @param {Object} event
    5865          */
    5866         toggleSelectionHandler: function( event ) {
    5867                 var method;
    5868 
    5869                 // Don't do anything inside inputs.
    5870                 if ( 'INPUT' === event.target.nodeName ) {
    5871                         return;
    5872                 }
    5873 
    5874                 // Catch arrow events
    5875                 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    5876                         this.controller.trigger( 'attachment:keydown:arrow', event );
    5877                         return;
    5878                 }
    5879 
    5880                 // Catch enter and space events
    5881                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    5882                         return;
    5883                 }
    5884 
    5885                 event.preventDefault();
    5886 
    5887                 // In the grid view, bubble up an edit:attachment event to the controller.
    5888                 if ( this.controller.isModeActive( 'grid' ) ) {
    5889                         if ( this.controller.isModeActive( 'edit' ) ) {
    5890                                 // Pass the current target to restore focus when closing
    5891                                 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
    5892                                 return;
    5893                         }
    5894 
    5895                         if ( this.controller.isModeActive( 'select' ) ) {
    5896                                 method = 'toggle';
    5897                         }
    5898                 }
    5899 
    5900                 if ( event.shiftKey ) {
    5901                         method = 'between';
    5902                 } else if ( event.ctrlKey || event.metaKey ) {
    5903                         method = 'toggle';
    5904                 }
    5905 
    5906                 this.toggleSelection({
    5907                         method: method
    5908                 });
    5909 
    5910                 this.controller.trigger( 'selection:toggle' );
    5911         },
    5912         /**
    5913          * @param {Object} options
    5914          */
    5915         toggleSelection: function( options ) {
    5916                 var collection = this.collection,
    5917                         selection = this.options.selection,
    5918                         model = this.model,
    5919                         method = options && options.method,
    5920                         single, models, singleIndex, modelIndex;
    5921 
    5922                 if ( ! selection ) {
    5923                         return;
    5924                 }
    5925 
    5926                 single = selection.single();
    5927                 method = _.isUndefined( method ) ? selection.multiple : method;
    5928 
    5929                 // If the `method` is set to `between`, select all models that
    5930                 // exist between the current and the selected model.
    5931                 if ( 'between' === method && single && selection.multiple ) {
    5932                         // If the models are the same, short-circuit.
    5933                         if ( single === model ) {
    5934                                 return;
    5935                         }
    5936 
    5937                         singleIndex = collection.indexOf( single );
    5938                         modelIndex  = collection.indexOf( this.model );
    5939 
    5940                         if ( singleIndex < modelIndex ) {
    5941                                 models = collection.models.slice( singleIndex, modelIndex + 1 );
    5942                         } else {
    5943                                 models = collection.models.slice( modelIndex, singleIndex + 1 );
    5944                         }
    5945 
    5946                         selection.add( models );
    5947                         selection.single( model );
    5948                         return;
    5949 
    5950                 // If the `method` is set to `toggle`, just flip the selection
    5951                 // status, regardless of whether the model is the single model.
    5952                 } else if ( 'toggle' === method ) {
    5953                         selection[ this.selected() ? 'remove' : 'add' ]( model );
    5954                         selection.single( model );
    5955                         return;
    5956                 } else if ( 'add' === method ) {
    5957                         selection.add( model );
    5958                         selection.single( model );
    5959                         return;
    5960                 }
    5961 
    5962                 // Fixes bug that loses focus when selecting a featured image
    5963                 if ( ! method ) {
    5964                         method = 'add';
    5965                 }
    5966 
    5967                 if ( method !== 'add' ) {
    5968                         method = 'reset';
    5969                 }
    5970 
    5971                 if ( this.selected() ) {
    5972                         // If the model is the single model, remove it.
    5973                         // If it is not the same as the single model,
    5974                         // it now becomes the single model.
    5975                         selection[ single === model ? 'remove' : 'single' ]( model );
    5976                 } else {
    5977                         // If the model is not selected, run the `method` on the
    5978                         // selection. By default, we `reset` the selection, but the
    5979                         // `method` can be set to `add` the model to the selection.
    5980                         selection[ method ]( model );
    5981                         selection.single( model );
    5982                 }
    5983         },
    5984 
    5985         updateSelect: function() {
    5986                 this[ this.selected() ? 'select' : 'deselect' ]();
    5987         },
    5988         /**
    5989          * @returns {unresolved|Boolean}
    5990          */
    5991         selected: function() {
    5992                 var selection = this.options.selection;
    5993                 if ( selection ) {
    5994                         return !! selection.get( this.model.cid );
    5995                 }
    5996         },
    5997         /**
    5998          * @param {Backbone.Model} model
    5999          * @param {Backbone.Collection} collection
    6000          */
    6001         select: function( model, collection ) {
    6002                 var selection = this.options.selection,
    6003                         controller = this.controller;
    6004 
    6005                 // Check if a selection exists and if it's the collection provided.
    6006                 // If they're not the same collection, bail; we're in another
    6007                 // selection's event loop.
    6008                 if ( ! selection || ( collection && collection !== selection ) ) {
    6009                         return;
    6010                 }
    6011 
    6012                 // Bail if the model is already selected.
    6013                 if ( this.$el.hasClass( 'selected' ) ) {
    6014                         return;
    6015                 }
    6016 
    6017                 // Add 'selected' class to model, set aria-checked to true.
    6018                 this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
    6019                 //  Make the checkbox tabable, except in media grid (bulk select mode).
    6020                 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
    6021                         this.$( '.check' ).attr( 'tabindex', '0' );
    6022                 }
    6023         },
    6024         /**
    6025          * @param {Backbone.Model} model
    6026          * @param {Backbone.Collection} collection
    6027          */
    6028         deselect: function( model, collection ) {
    6029                 var selection = this.options.selection;
    6030 
    6031                 // Check if a selection exists and if it's the collection provided.
    6032                 // If they're not the same collection, bail; we're in another
    6033                 // selection's event loop.
    6034                 if ( ! selection || ( collection && collection !== selection ) ) {
    6035                         return;
    6036                 }
    6037                 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
    6038                         .find( '.check' ).attr( 'tabindex', '-1' );
    6039         },
    6040         /**
    6041          * @param {Backbone.Model} model
    6042          * @param {Backbone.Collection} collection
    6043          */
    6044         details: function( model, collection ) {
    6045                 var selection = this.options.selection,
    6046                         details;
    6047 
    6048                 if ( selection !== collection ) {
    6049                         return;
    6050                 }
    6051 
    6052                 details = selection.single();
    6053                 this.$el.toggleClass( 'details', details === this.model );
    6054         },
    6055         /**
    6056          * @param {Object} event
    6057          */
    6058         preventDefault: function( event ) {
    6059                 event.preventDefault();
    6060         },
    6061         /**
    6062          * @param {string} size
    6063          * @returns {Object}
    6064          */
    6065         imageSize: function( size ) {
    6066                 var sizes = this.model.get('sizes'), matched = false;
    6067 
    6068                 size = size || 'medium';
    6069 
    6070                 // Use the provided image size if possible.
    6071                 if ( sizes ) {
    6072                         if ( sizes[ size ] ) {
    6073                                 matched = sizes[ size ];
    6074                         } else if ( sizes.large ) {
    6075                                 matched = sizes.large;
    6076                         } else if ( sizes.thumbnail ) {
    6077                                 matched = sizes.thumbnail;
    6078                         } else if ( sizes.full ) {
    6079                                 matched = sizes.full;
    6080                         }
    6081 
    6082                         if ( matched ) {
    6083                                 return _.clone( matched );
    6084                         }
    6085                 }
    6086 
    6087                 return {
    6088                         url:         this.model.get('url'),
    6089                         width:       this.model.get('width'),
    6090                         height:      this.model.get('height'),
    6091                         orientation: this.model.get('orientation')
    6092                 };
    6093         },
    6094         /**
    6095          * @param {Object} event
    6096          */
    6097         updateSetting: function( event ) {
    6098                 var $setting = $( event.target ).closest('[data-setting]'),
    6099                         setting, value;
    6100 
    6101                 if ( ! $setting.length ) {
    6102                         return;
    6103                 }
    6104 
    6105                 setting = $setting.data('setting');
    6106                 value   = event.target.value;
    6107 
    6108                 if ( this.model.get( setting ) !== value ) {
    6109                         this.save( setting, value );
    6110                 }
    6111         },
    6112 
    6113         /**
    6114          * Pass all the arguments to the model's save method.
    6115          *
    6116          * Records the aggregate status of all save requests and updates the
    6117          * view's classes accordingly.
    6118          */
    6119         save: function() {
    6120                 var view = this,
    6121                         save = this._save = this._save || { status: 'ready' },
    6122                         request = this.model.save.apply( this.model, arguments ),
    6123                         requests = save.requests ? $.when( request, save.requests ) : request;
    6124 
    6125                 // If we're waiting to remove 'Saved.', stop.
    6126                 if ( save.savedTimer ) {
    6127                         clearTimeout( save.savedTimer );
    6128                 }
    6129 
    6130                 this.updateSave('waiting');
    6131                 save.requests = requests;
    6132                 requests.always( function() {
    6133                         // If we've performed another request since this one, bail.
    6134                         if ( save.requests !== requests ) {
    6135                                 return;
    6136                         }
    6137 
    6138                         view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
    6139                         save.savedTimer = setTimeout( function() {
    6140                                 view.updateSave('ready');
    6141                                 delete save.savedTimer;
    6142                         }, 2000 );
    6143                 });
    6144         },
    6145         /**
    6146          * @param {string} status
    6147          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    6148          */
    6149         updateSave: function( status ) {
    6150                 var save = this._save = this._save || { status: 'ready' };
    6151 
    6152                 if ( status && status !== save.status ) {
    6153                         this.$el.removeClass( 'save-' + save.status );
    6154                         save.status = status;
    6155                 }
    6156 
    6157                 this.$el.addClass( 'save-' + save.status );
    6158                 return this;
    6159         },
    6160 
    6161         updateAll: function() {
    6162                 var $settings = this.$('[data-setting]'),
    6163                         model = this.model,
    6164                         changed;
    6165 
    6166                 changed = _.chain( $settings ).map( function( el ) {
    6167                         var $input = $('input, textarea, select, [value]', el ),
    6168                                 setting, value;
    6169 
    6170                         if ( ! $input.length ) {
    6171                                 return;
    6172                         }
    6173 
    6174                         setting = $(el).data('setting');
    6175                         value = $input.val();
    6176 
    6177                         // Record the value if it changed.
    6178                         if ( model.get( setting ) !== value ) {
    6179                                 return [ setting, value ];
    6180                         }
    6181                 }).compact().object().value();
    6182 
    6183                 if ( ! _.isEmpty( changed ) ) {
    6184                         model.save( changed );
    6185                 }
    6186         },
    6187         /**
    6188          * @param {Object} event
    6189          */
    6190         removeFromLibrary: function( event ) {
    6191                 // Catch enter and space events
    6192                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    6193                         return;
    6194                 }
    6195 
    6196                 // Stop propagation so the model isn't selected.
    6197                 event.stopPropagation();
    6198 
    6199                 this.collection.remove( this.model );
    6200         },
    6201 
    6202         /**
    6203          * Add the model if it isn't in the selection, if it is in the selection,
    6204          * remove it.
    6205          *
    6206          * @param  {[type]} event [description]
    6207          * @return {[type]}       [description]
    6208          */
    6209         checkClickHandler: function ( event ) {
    6210                 var selection = this.options.selection;
    6211                 if ( ! selection ) {
    6212                         return;
    6213                 }
    6214                 event.stopPropagation();
    6215                 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
    6216                         selection.remove( this.model );
    6217                         // Move focus back to the attachment tile (from the check).
    6218                         this.$el.focus();
    6219                 } else {
    6220                         selection.add( this.model );
    6221                 }
    6222         }
    6223 });
    6224 
    6225 // Ensure settings remain in sync between attachment views.
    6226 _.each({
    6227         caption: '_syncCaption',
    6228         title:   '_syncTitle',
    6229         artist:  '_syncArtist',
    6230         album:   '_syncAlbum'
    6231 }, function( method, setting ) {
    6232         /**
    6233          * @param {Backbone.Model} model
    6234          * @param {string} value
    6235          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    6236          */
    6237         Attachment.prototype[ method ] = function( model, value ) {
    6238                 var $setting = this.$('[data-setting="' + setting + '"]');
    6239 
    6240                 if ( ! $setting.length ) {
    6241                         return this;
    6242                 }
    6243 
    6244                 // If the updated value is in sync with the value in the DOM, there
    6245                 // is no need to re-render. If we're currently editing the value,
    6246                 // it will automatically be in sync, suppressing the re-render for
    6247                 // the view we're editing, while updating any others.
    6248                 if ( value === $setting.find('input, textarea, select, [value]').val() ) {
    6249                         return this;
    6250                 }
    6251 
    6252                 return this.render();
    6253         };
    6254 });
    6255 
    6256 module.exports = Attachment;
    6257 
    6258 
    6259 /***/ }),
    6260 /* 70 */
    6261 /***/ (function(module, exports) {
    6262 
    6263 /*globals wp */
    6264 
    6265 /**
    6266  * wp.media.view.Attachment.Library
    6267  *
    6268  * @class
    6269  * @augments wp.media.view.Attachment
    6270  * @augments wp.media.View
    6271  * @augments wp.Backbone.View
    6272  * @augments Backbone.View
    6273  */
    6274 var Library = wp.media.view.Attachment.extend({
    6275         buttons: {
    6276                 check: true
    6277         }
    6278 });
    6279 
    6280 module.exports = Library;
    6281 
    6282 
    6283 /***/ }),
    6284 /* 71 */
    6285 /***/ (function(module, exports) {
    6286 
    6287 /*globals wp */
    6288 
    6289 /**
    6290  * wp.media.view.Attachment.EditLibrary
    6291  *
    6292  * @class
    6293  * @augments wp.media.view.Attachment
    6294  * @augments wp.media.View
    6295  * @augments wp.Backbone.View
    6296  * @augments Backbone.View
    6297  */
    6298 var EditLibrary = wp.media.view.Attachment.extend({
    6299         buttons: {
    6300                 close: true
    6301         }
    6302 });
    6303 
    6304 module.exports = EditLibrary;
    6305 
    6306 
    6307 /***/ }),
    6308 /* 72 */
    6309 /***/ (function(module, exports) {
    6310 
    6311 /*globals wp, _, jQuery */
    6312 
    6313 /**
    6314  * wp.media.view.Attachments
    6315  *
    6316  * @class
    6317  * @augments wp.media.View
    6318  * @augments wp.Backbone.View
    6319  * @augments Backbone.View
    6320  */
    6321 var View = wp.media.View,
    6322         $ = jQuery,
    6323         Attachments;
    6324 
    6325 Attachments = View.extend({
    6326         tagName:   'ul',
    6327         className: 'attachments',
    6328 
    6329         attributes: {
    6330                 tabIndex: -1
    6331         },
    6332 
    6333         initialize: function() {
    6334                 this.el.id = _.uniqueId('__attachments-view-');
    6335 
    6336                 _.defaults( this.options, {
    6337                         refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
    6338                         refreshThreshold:   3,
    6339                         AttachmentView:     wp.media.view.Attachment,
    6340                         sortable:           false,
    6341                         resize:             true,
    6342                         idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
    6343                 });
    6344 
    6345                 this._viewsByCid = {};
    6346                 this.$window = $( window );
    6347                 this.resizeEvent = 'resize.media-modal-columns';
    6348 
    6349                 this.collection.on( 'add', function( attachment ) {
    6350                         this.views.add( this.createAttachmentView( attachment ), {
    6351                                 at: this.collection.indexOf( attachment )
    6352                         });
    6353                 }, this );
    6354 
    6355                 this.collection.on( 'remove', function( attachment ) {
    6356                         var view = this._viewsByCid[ attachment.cid ];
    6357                         delete this._viewsByCid[ attachment.cid ];
    6358 
    6359                         if ( view ) {
    6360                                 view.remove();
    6361                         }
    6362                 }, this );
    6363 
    6364                 this.collection.on( 'reset', this.render, this );
    6365 
    6366                 this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
    6367 
    6368                 // Throttle the scroll handler and bind this.
    6369                 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
    6370 
    6371                 this.options.scrollElement = this.options.scrollElement || this.el;
    6372                 $( this.options.scrollElement ).on( 'scroll', this.scroll );
    6373 
    6374                 this.initSortable();
    6375 
    6376                 _.bindAll( this, 'setColumns' );
    6377 
    6378                 if ( this.options.resize ) {
    6379                         this.on( 'ready', this.bindEvents );
    6380                         this.controller.on( 'open', this.setColumns );
    6381 
    6382                         // Call this.setColumns() after this view has been rendered in the DOM so
    6383                         // attachments get proper width applied.
    6384                         _.defer( this.setColumns, this );
    6385                 }
    6386         },
    6387 
    6388         bindEvents: function() {
    6389                 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
    6390         },
    6391 
    6392         attachmentFocus: function() {
    6393                 this.$( 'li:first' ).focus();
    6394         },
    6395 
    6396         restoreFocus: function() {
    6397                 this.$( 'li.selected:first' ).focus();
    6398         },
    6399 
    6400         arrowEvent: function( event ) {
    6401                 var attachments = this.$el.children( 'li' ),
    6402                         perRow = this.columns,
    6403                         index = attachments.filter( ':focus' ).index(),
    6404                         row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
    6405 
    6406                 if ( index === -1 ) {
    6407                         return;
    6408                 }
    6409 
    6410                 // Left arrow
    6411                 if ( 37 === event.keyCode ) {
    6412                         if ( 0 === index ) {
    6413                                 return;
    6414                         }
    6415                         attachments.eq( index - 1 ).focus();
    6416                 }
    6417 
    6418                 // Up arrow
    6419                 if ( 38 === event.keyCode ) {
    6420                         if ( 1 === row ) {
    6421                                 return;
    6422                         }
    6423                         attachments.eq( index - perRow ).focus();
    6424                 }
    6425 
    6426                 // Right arrow
    6427                 if ( 39 === event.keyCode ) {
    6428                         if ( attachments.length === index ) {
    6429                                 return;
    6430                         }
    6431                         attachments.eq( index + 1 ).focus();
    6432                 }
    6433 
    6434                 // Down arrow
    6435                 if ( 40 === event.keyCode ) {
    6436                         if ( Math.ceil( attachments.length / perRow ) === row ) {
    6437                                 return;
    6438                         }
    6439                         attachments.eq( index + perRow ).focus();
    6440                 }
    6441         },
    6442 
    6443         dispose: function() {
    6444                 this.collection.props.off( null, null, this );
    6445                 if ( this.options.resize ) {
    6446                         this.$window.off( this.resizeEvent );
    6447                 }
    6448 
    6449                 /**
    6450                  * call 'dispose' directly on the parent class
    6451                  */
    6452                 View.prototype.dispose.apply( this, arguments );
    6453         },
    6454 
    6455         setColumns: function() {
    6456                 var prev = this.columns,
    6457                         width = this.$el.width();
    6458 
    6459                 if ( width ) {
    6460                         this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
    6461 
    6462                         if ( ! prev || prev !== this.columns ) {
    6463                                 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
    6464                         }
    6465                 }
    6466         },
    6467 
    6468         initSortable: function() {
    6469                 var collection = this.collection;
    6470 
    6471                 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
    6472                         return;
    6473                 }
    6474 
    6475                 this.$el.sortable( _.extend({
    6476                         // If the `collection` has a `comparator`, disable sorting.
    6477                         disabled: !! collection.comparator,
    6478 
    6479                         // Change the position of the attachment as soon as the
    6480                         // mouse pointer overlaps a thumbnail.
    6481                         tolerance: 'pointer',
    6482 
    6483                         // Record the initial `index` of the dragged model.
    6484                         start: function( event, ui ) {
    6485                                 ui.item.data('sortableIndexStart', ui.item.index());
    6486                         },
    6487 
    6488                         // Update the model's index in the collection.
    6489                         // Do so silently, as the view is already accurate.
    6490                         update: function( event, ui ) {
    6491                                 var model = collection.at( ui.item.data('sortableIndexStart') ),
    6492                                         comparator = collection.comparator;
    6493 
    6494                                 // Temporarily disable the comparator to prevent `add`
    6495                                 // from re-sorting.
    6496                                 delete collection.comparator;
    6497 
    6498                                 // Silently shift the model to its new index.
    6499                                 collection.remove( model, {
    6500                                         silent: true
    6501                                 });
    6502                                 collection.add( model, {
    6503                                         silent: true,
    6504                                         at:     ui.item.index()
    6505                                 });
    6506 
    6507                                 // Restore the comparator.
    6508                                 collection.comparator = comparator;
    6509 
    6510                                 // Fire the `reset` event to ensure other collections sync.
    6511                                 collection.trigger( 'reset', collection );
    6512 
    6513                                 // If the collection is sorted by menu order,
    6514                                 // update the menu order.
    6515                                 collection.saveMenuOrder();
    6516                         }
    6517                 }, this.options.sortable ) );
    6518 
    6519                 // If the `orderby` property is changed on the `collection`,
    6520                 // check to see if we have a `comparator`. If so, disable sorting.
    6521                 collection.props.on( 'change:orderby', function() {
    6522                         this.$el.sortable( 'option', 'disabled', !! collection.comparator );
    6523                 }, this );
    6524 
    6525                 this.collection.props.on( 'change:orderby', this.refreshSortable, this );
    6526                 this.refreshSortable();
    6527         },
    6528 
    6529         refreshSortable: function() {
    6530                 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
    6531                         return;
    6532                 }
    6533 
    6534                 // If the `collection` has a `comparator`, disable sorting.
    6535                 var collection = this.collection,
    6536                         orderby = collection.props.get('orderby'),
    6537                         enabled = 'menuOrder' === orderby || ! collection.comparator;
    6538 
    6539                 this.$el.sortable( 'option', 'disabled', ! enabled );
    6540         },
    6541 
    6542         /**
    6543          * @param {wp.media.model.Attachment} attachment
    6544          * @returns {wp.media.View}
    6545          */
    6546         createAttachmentView: function( attachment ) {
    6547                 var view = new this.options.AttachmentView({
    6548                         controller:           this.controller,
    6549                         model:                attachment,
    6550                         collection:           this.collection,
    6551                         selection:            this.options.selection
    6552                 });
    6553 
    6554                 return this._viewsByCid[ attachment.cid ] = view;
    6555         },
    6556 
    6557         prepare: function() {
    6558                 // Create all of the Attachment views, and replace
    6559                 // the list in a single DOM operation.
    6560                 if ( this.collection.length ) {
    6561                         this.views.set( this.collection.map( this.createAttachmentView, this ) );
    6562 
    6563                 // If there are no elements, clear the views and load some.
    6564                 } else {
    6565                         this.views.unset();
    6566                         this.collection.more().done( this.scroll );
    6567                 }
    6568         },
    6569 
    6570         ready: function() {
    6571                 // Trigger the scroll event to check if we're within the
    6572                 // threshold to query for additional attachments.
    6573                 this.scroll();
    6574         },
    6575 
    6576         scroll: function() {
    6577                 var view = this,
    6578                         el = this.options.scrollElement,
    6579                         scrollTop = el.scrollTop,
    6580                         toolbar;
    6581 
    6582                 // The scroll event occurs on the document, but the element
    6583                 // that should be checked is the document body.
    6584                 if ( el === document ) {
    6585                         el = document.body;
    6586                         scrollTop = $(document).scrollTop();
    6587                 }
    6588 
    6589                 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
    6590                         return;
    6591                 }
    6592 
    6593                 toolbar = this.views.parent.toolbar;
    6594 
    6595                 // Show the spinner only if we are close to the bottom.
    6596                 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
    6597                         toolbar.get('spinner').show();
    6598                 }
    6599 
    6600                 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
    6601                         this.collection.more().done(function() {
    6602                                 view.scroll();
    6603                                 toolbar.get('spinner').hide();
    6604                         });
    6605                 }
    6606         }
    6607 });
    6608 
    6609 module.exports = Attachments;
    6610 
    6611 
    6612 /***/ }),
    6613 /* 73 */
    6614 /***/ (function(module, exports) {
    6615 
    6616 /*globals wp */
    6617 
    6618 /**
    6619  * wp.media.view.Search
    6620  *
    6621  * @class
    6622  * @augments wp.media.View
    6623  * @augments wp.Backbone.View
    6624  * @augments Backbone.View
    6625  */
    6626 var l10n = wp.media.view.l10n,
    6627         Search;
    6628 
    6629 Search = wp.media.View.extend({
    6630         tagName:   'input',
    6631         className: 'search',
    6632         id:        'media-search-input',
    6633 
    6634         attributes: {
    6635                 type:        'search',
    6636                 placeholder: l10n.search
    6637         },
    6638 
    6639         events: {
    6640                 'input':  'search',
    6641                 'keyup':  'search',
    6642                 'change': 'search',
    6643                 'search': 'search'
    6644         },
    6645 
    6646         /**
    6647          * @returns {wp.media.view.Search} Returns itself to allow chaining
    6648          */
    6649         render: function() {
    6650                 this.el.value = this.model.escape('search');
    6651                 return this;
    6652         },
    6653 
    6654         search: function( event ) {
    6655                 if ( event.target.value ) {
    6656                         this.model.set( 'search', event.target.value );
    6657                 } else {
    6658                         this.model.unset('search');
    6659                 }
    6660         }
    6661 });
    6662 
    6663 module.exports = Search;
    6664 
    6665 
    6666 /***/ }),
    6667 /* 74 */
    6668 /***/ (function(module, exports) {
    6669 
     2315module.exports = AttachmentCompat;
     2316
     2317},{}],19:[function(require,module,exports){
    66702318/*globals _, jQuery */
    66712319
    … …  
    67462394module.exports = AttachmentFilters;
    67472395
    6748 
    6749 /***/ }),
    6750 /* 75 */
    6751 /***/ (function(module, exports) {
    6752 
     2396},{}],20:[function(require,module,exports){
     2397/*globals wp */
     2398
     2399/**
     2400 * wp.media.view.AttachmentFilters.All
     2401 *
     2402 * @class
     2403 * @augments wp.media.view.AttachmentFilters
     2404 * @augments wp.media.View
     2405 * @augments wp.Backbone.View
     2406 * @augments Backbone.View
     2407 */
     2408var l10n = wp.media.view.l10n,
     2409        All;
     2410
     2411All = wp.media.view.AttachmentFilters.extend({
     2412        createFilters: function() {
     2413                var filters = {};
     2414
     2415                _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
     2416                        filters[ key ] = {
     2417                                text: text,
     2418                                props: {
     2419                                        status:  null,
     2420                                        type:    key,
     2421                                        uploadedTo: null,
     2422                                        orderby: 'date',
     2423                                        order:   'DESC'
     2424                                }
     2425                        };
     2426                });
     2427
     2428                filters.all = {
     2429                        text:  l10n.allMediaItems,
     2430                        props: {
     2431                                status:  null,
     2432                                type:    null,
     2433                                uploadedTo: null,
     2434                                orderby: 'date',
     2435                                order:   'DESC'
     2436                        },
     2437                        priority: 10
     2438                };
     2439
     2440                if ( wp.media.view.settings.post.id ) {
     2441                        filters.uploaded = {
     2442                                text:  l10n.uploadedToThisPost,
     2443                                props: {
     2444                                        status:  null,
     2445                                        type:    null,
     2446                                        uploadedTo: wp.media.view.settings.post.id,
     2447                                        orderby: 'menuOrder',
     2448                                        order:   'ASC'
     2449                                },
     2450                                priority: 20
     2451                        };
     2452                }
     2453
     2454                filters.unattached = {
     2455                        text:  l10n.unattached,
     2456                        props: {
     2457                                status:     null,
     2458                                uploadedTo: 0,
     2459                                type:       null,
     2460                                orderby:    'menuOrder',
     2461                                order:      'ASC'
     2462                        },
     2463                        priority: 50
     2464                };
     2465
     2466                if ( wp.media.view.settings.mediaTrash &&
     2467                        this.controller.isModeActive( 'grid' ) ) {
     2468
     2469                        filters.trash = {
     2470                                text:  l10n.trash,
     2471                                props: {
     2472                                        uploadedTo: null,
     2473                                        status:     'trash',
     2474                                        type:       null,
     2475                                        orderby:    'date',
     2476                                        order:      'DESC'
     2477                                },
     2478                                priority: 50
     2479                        };
     2480                }
     2481
     2482                this.filters = filters;
     2483        }
     2484});
     2485
     2486module.exports = All;
     2487
     2488},{}],21:[function(require,module,exports){
    67532489/*globals wp, _ */
    67542490
    … …  
    67932529module.exports = DateFilter;
    67942530
    6795 
    6796 /***/ }),
    6797 /* 76 */
    6798 /***/ (function(module, exports) {
    6799 
     2531},{}],22:[function(require,module,exports){
    68002532/*globals wp */
    68012533
    … …  
    68582590module.exports = Uploaded;
    68592591
    6860 
    6861 /***/ }),
    6862 /* 77 */
    6863 /***/ (function(module, exports) {
    6864 
    6865 /*globals wp */
     2592},{}],23:[function(require,module,exports){
     2593/*globals wp, _, jQuery */
    68662594
    68672595/**
    6868  * wp.media.view.AttachmentFilters.All
     2596 * wp.media.view.Attachment
    68692597 *
    68702598 * @class
    6871  * @augments wp.media.view.AttachmentFilters
    68722599 * @augments wp.media.View
    68732600 * @augments wp.Backbone.View
    68742601 * @augments Backbone.View
    68752602 */
    6876 var l10n = wp.media.view.l10n,
    6877         All;
    6878 
    6879 All = wp.media.view.AttachmentFilters.extend({
    6880         createFilters: function() {
    6881                 var filters = {};
    6882 
    6883                 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
    6884                         filters[ key ] = {
    6885                                 text: text,
    6886                                 props: {
    6887                                         status:  null,
    6888                                         type:    key,
    6889                                         uploadedTo: null,
    6890                                         orderby: 'date',
    6891                                         order:   'DESC'
    6892                                 }
    6893                         };
     2603var View = wp.media.View,
     2604        $ = jQuery,
     2605        Attachment;
     2606
     2607Attachment = View.extend({
     2608        tagName:   'li',
     2609        className: 'attachment',
     2610        template:  wp.template('attachment'),
     2611
     2612        attributes: function() {
     2613                return {
     2614                        'tabIndex':     0,
     2615                        'role':         'checkbox',
     2616                        'aria-label':   this.model.get( 'title' ),
     2617                        'aria-checked': false,
     2618                        'data-id':      this.model.get( 'id' )
     2619                };
     2620        },
     2621
     2622        events: {
     2623                'click .js--select-attachment':   'toggleSelectionHandler',
     2624                'change [data-setting]':          'updateSetting',
     2625                'change [data-setting] input':    'updateSetting',
     2626                'change [data-setting] select':   'updateSetting',
     2627                'change [data-setting] textarea': 'updateSetting',
     2628                'click .close':                   'removeFromLibrary',
     2629                'click .check':                   'checkClickHandler',
     2630                'click a':                        'preventDefault',
     2631                'keydown .close':                 'removeFromLibrary',
     2632                'keydown':                        'toggleSelectionHandler'
     2633        },
     2634
     2635        buttons: {},
     2636
     2637        initialize: function() {
     2638                var selection = this.options.selection,
     2639                        options = _.defaults( this.options, {
     2640                                rerenderOnModelChange: true
     2641                        } );
     2642
     2643                if ( options.rerenderOnModelChange ) {
     2644                        this.listenTo( this.model, 'change', this.render );
     2645                } else {
     2646                        this.listenTo( this.model, 'change:percent', this.progress );
     2647                }
     2648                this.listenTo( this.model, 'change:title', this._syncTitle );
     2649                this.listenTo( this.model, 'change:caption', this._syncCaption );
     2650                this.listenTo( this.model, 'change:artist', this._syncArtist );
     2651                this.listenTo( this.model, 'change:album', this._syncAlbum );
     2652
     2653                // Update the selection.
     2654                this.listenTo( this.model, 'add', this.select );
     2655                this.listenTo( this.model, 'remove', this.deselect );
     2656                if ( selection ) {
     2657                        selection.on( 'reset', this.updateSelect, this );
     2658                        // Update the model's details view.
     2659                        this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
     2660                        this.details( this.model, this.controller.state().get('selection') );
     2661                }
     2662
     2663                this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
     2664        },
     2665        /**
     2666         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     2667         */
     2668        dispose: function() {
     2669                var selection = this.options.selection;
     2670
     2671                // Make sure all settings are saved before removing the view.
     2672                this.updateAll();
     2673
     2674                if ( selection ) {
     2675                        selection.off( null, null, this );
     2676                }
     2677                /**
     2678                 * call 'dispose' directly on the parent class
     2679                 */
     2680                View.prototype.dispose.apply( this, arguments );
     2681                return this;
     2682        },
     2683        /**
     2684         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     2685         */
     2686        render: function() {
     2687                var options = _.defaults( this.model.toJSON(), {
     2688                                orientation:   'landscape',
     2689                                uploading:     false,
     2690                                type:          '',
     2691                                subtype:       '',
     2692                                icon:          '',
     2693                                filename:      '',
     2694                                caption:       '',
     2695                                title:         '',
     2696                                dateFormatted: '',
     2697                                width:         '',
     2698                                height:        '',
     2699                                compat:        false,
     2700                                alt:           '',
     2701                                description:   ''
     2702                        }, this.options );
     2703
     2704                options.buttons  = this.buttons;
     2705                options.describe = this.controller.state().get('describe');
     2706
     2707                if ( 'image' === options.type ) {
     2708                        options.size = this.imageSize();
     2709                }
     2710
     2711                options.can = {};
     2712                if ( options.nonces ) {
     2713                        options.can.remove = !! options.nonces['delete'];
     2714                        options.can.save = !! options.nonces.update;
     2715                }
     2716
     2717                if ( this.controller.state().get('allowLocalEdits') ) {
     2718                        options.allowLocalEdits = true;
     2719                }
     2720
     2721                if ( options.uploading && ! options.percent ) {
     2722                        options.percent = 0;
     2723                }
     2724
     2725                this.views.detach();
     2726                this.$el.html( this.template( options ) );
     2727
     2728                this.$el.toggleClass( 'uploading', options.uploading );
     2729
     2730                if ( options.uploading ) {
     2731                        this.$bar = this.$('.media-progress-bar div');
     2732                } else {
     2733                        delete this.$bar;
     2734                }
     2735
     2736                // Check if the model is selected.
     2737                this.updateSelect();
     2738
     2739                // Update the save status.
     2740                this.updateSave();
     2741
     2742                this.views.render();
     2743
     2744                return this;
     2745        },
     2746
     2747        progress: function() {
     2748                if ( this.$bar && this.$bar.length ) {
     2749                        this.$bar.width( this.model.get('percent') + '%' );
     2750                }
     2751        },
     2752
     2753        /**
     2754         * @param {Object} event
     2755         */
     2756        toggleSelectionHandler: function( event ) {
     2757                var method;
     2758
     2759                // Don't do anything inside inputs.
     2760                if ( 'INPUT' === event.target.nodeName ) {
     2761                        return;
     2762                }
     2763
     2764                // Catch arrow events
     2765                if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     2766                        this.controller.trigger( 'attachment:keydown:arrow', event );
     2767                        return;
     2768                }
     2769
     2770                // Catch enter and space events
     2771                if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     2772                        return;
     2773                }
     2774
     2775                event.preventDefault();
     2776
     2777                // In the grid view, bubble up an edit:attachment event to the controller.
     2778                if ( this.controller.isModeActive( 'grid' ) ) {
     2779                        if ( this.controller.isModeActive( 'edit' ) ) {
     2780                                // Pass the current target to restore focus when closing
     2781                                this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
     2782                                return;
     2783                        }
     2784
     2785                        if ( this.controller.isModeActive( 'select' ) ) {
     2786                                method = 'toggle';
     2787                        }
     2788                }
     2789
     2790                if ( event.shiftKey ) {
     2791                        method = 'between';
     2792                } else if ( event.ctrlKey || event.metaKey ) {
     2793                        method = 'toggle';
     2794                }
     2795
     2796                this.toggleSelection({
     2797                        method: method
    68942798                });
    68952799
    6896                 filters.all = {
    6897                         text:  l10n.allMediaItems,
    6898                         props: {
    6899                                 status:  null,
    6900                                 type:    null,
    6901                                 uploadedTo: null,
    6902                                 orderby: 'date',
    6903                                 order:   'DESC'
    6904                         },
    6905                         priority: 10
     2800                this.controller.trigger( 'selection:toggle' );
     2801        },
     2802        /**
     2803         * @param {Object} options
     2804         */
     2805        toggleSelection: function( options ) {
     2806                var collection = this.collection,
     2807                        selection = this.options.selection,
     2808                        model = this.model,
     2809                        method = options && options.method,
     2810                        single, models, singleIndex, modelIndex;
     2811
     2812                if ( ! selection ) {
     2813                        return;
     2814                }
     2815
     2816                single = selection.single();
     2817                method = _.isUndefined( method ) ? selection.multiple : method;
     2818
     2819                // If the `method` is set to `between`, select all models that
     2820                // exist between the current and the selected model.
     2821                if ( 'between' === method && single && selection.multiple ) {
     2822                        // If the models are the same, short-circuit.
     2823                        if ( single === model ) {
     2824                                return;
     2825                        }
     2826
     2827                        singleIndex = collection.indexOf( single );
     2828                        modelIndex  = collection.indexOf( this.model );
     2829
     2830                        if ( singleIndex < modelIndex ) {
     2831                                models = collection.models.slice( singleIndex, modelIndex + 1 );
     2832                        } else {
     2833                                models = collection.models.slice( modelIndex, singleIndex + 1 );
     2834                        }
     2835
     2836                        selection.add( models );
     2837                        selection.single( model );
     2838                        return;
     2839
     2840                // If the `method` is set to `toggle`, just flip the selection
     2841                // status, regardless of whether the model is the single model.
     2842                } else if ( 'toggle' === method ) {
     2843                        selection[ this.selected() ? 'remove' : 'add' ]( model );
     2844                        selection.single( model );
     2845                        return;
     2846                } else if ( 'add' === method ) {
     2847                        selection.add( model );
     2848                        selection.single( model );
     2849                        return;
     2850                }
     2851
     2852                // Fixes bug that loses focus when selecting a featured image
     2853                if ( ! method ) {
     2854                        method = 'add';
     2855                }
     2856
     2857                if ( method !== 'add' ) {
     2858                        method = 'reset';
     2859                }
     2860
     2861                if ( this.selected() ) {
     2862                        // If the model is the single model, remove it.
     2863                        // If it is not the same as the single model,
     2864                        // it now becomes the single model.
     2865                        selection[ single === model ? 'remove' : 'single' ]( model );
     2866                } else {
     2867                        // If the model is not selected, run the `method` on the
     2868                        // selection. By default, we `reset` the selection, but the
     2869                        // `method` can be set to `add` the model to the selection.
     2870                        selection[ method ]( model );
     2871                        selection.single( model );
     2872                }
     2873        },
     2874
     2875        updateSelect: function() {
     2876                this[ this.selected() ? 'select' : 'deselect' ]();
     2877        },
     2878        /**
     2879         * @returns {unresolved|Boolean}
     2880         */
     2881        selected: function() {
     2882                var selection = this.options.selection;
     2883                if ( selection ) {
     2884                        return !! selection.get( this.model.cid );
     2885                }
     2886        },
     2887        /**
     2888         * @param {Backbone.Model} model
     2889         * @param {Backbone.Collection} collection
     2890         */
     2891        select: function( model, collection ) {
     2892                var selection = this.options.selection,
     2893                        controller = this.controller;
     2894
     2895                // Check if a selection exists and if it's the collection provided.
     2896                // If they're not the same collection, bail; we're in another
     2897                // selection's event loop.
     2898                if ( ! selection || ( collection && collection !== selection ) ) {
     2899                        return;
     2900                }
     2901
     2902                // Bail if the model is already selected.
     2903                if ( this.$el.hasClass( 'selected' ) ) {
     2904                        return;
     2905                }
     2906
     2907                // Add 'selected' class to model, set aria-checked to true.
     2908                this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
     2909                //  Make the checkbox tabable, except in media grid (bulk select mode).
     2910                if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
     2911                        this.$( '.check' ).attr( 'tabindex', '0' );
     2912                }
     2913        },
     2914        /**
     2915         * @param {Backbone.Model} model
     2916         * @param {Backbone.Collection} collection
     2917         */
     2918        deselect: function( model, collection ) {
     2919                var selection = this.options.selection;
     2920
     2921                // Check if a selection exists and if it's the collection provided.
     2922                // If they're not the same collection, bail; we're in another
     2923                // selection's event loop.
     2924                if ( ! selection || ( collection && collection !== selection ) ) {
     2925                        return;
     2926                }
     2927                this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
     2928                        .find( '.check' ).attr( 'tabindex', '-1' );
     2929        },
     2930        /**
     2931         * @param {Backbone.Model} model
     2932         * @param {Backbone.Collection} collection
     2933         */
     2934        details: function( model, collection ) {
     2935                var selection = this.options.selection,
     2936                        details;
     2937
     2938                if ( selection !== collection ) {
     2939                        return;
     2940                }
     2941
     2942                details = selection.single();
     2943                this.$el.toggleClass( 'details', details === this.model );
     2944        },
     2945        /**
     2946         * @param {Object} event
     2947         */
     2948        preventDefault: function( event ) {
     2949                event.preventDefault();
     2950        },
     2951        /**
     2952         * @param {string} size
     2953         * @returns {Object}
     2954         */
     2955        imageSize: function( size ) {
     2956                var sizes = this.model.get('sizes'), matched = false;
     2957
     2958                size = size || 'medium';
     2959
     2960                // Use the provided image size if possible.
     2961                if ( sizes ) {
     2962                        if ( sizes[ size ] ) {
     2963                                matched = sizes[ size ];
     2964                        } else if ( sizes.large ) {
     2965                                matched = sizes.large;
     2966                        } else if ( sizes.thumbnail ) {
     2967                                matched = sizes.thumbnail;
     2968                        } else if ( sizes.full ) {
     2969                                matched = sizes.full;
     2970                        }
     2971
     2972                        if ( matched ) {
     2973                                return _.clone( matched );
     2974                        }
     2975                }
     2976
     2977                return {
     2978                        url:         this.model.get('url'),
     2979                        width:       this.model.get('width'),
     2980                        height:      this.model.get('height'),
     2981                        orientation: this.model.get('orientation')
    69062982                };
    6907 
    6908                 if ( wp.media.view.settings.post.id ) {
    6909                         filters.uploaded = {
    6910                                 text:  l10n.uploadedToThisPost,
    6911                                 props: {
    6912                                         status:  null,
    6913                                         type:    null,
    6914                                         uploadedTo: wp.media.view.settings.post.id,
    6915                                         orderby: 'menuOrder',
    6916                                         order:   'ASC'
    6917                                 },
    6918                                 priority: 20
    6919                         };
    6920                 }
    6921 
    6922                 filters.unattached = {
    6923                         text:  l10n.unattached,
    6924                         props: {
    6925                                 status:     null,
    6926                                 uploadedTo: 0,
    6927                                 type:       null,
    6928                                 orderby:    'menuOrder',
    6929                                 order:      'ASC'
    6930                         },
    6931                         priority: 50
    6932                 };
    6933 
    6934                 if ( wp.media.view.settings.mediaTrash &&
    6935                         this.controller.isModeActive( 'grid' ) ) {
    6936 
    6937                         filters.trash = {
    6938                                 text:  l10n.trash,
    6939                                 props: {
    6940                                         uploadedTo: null,
    6941                                         status:     'trash',
    6942                                         type:       null,
    6943                                         orderby:    'date',
    6944                                         order:      'DESC'
    6945                                 },
    6946                                 priority: 50
    6947                         };
    6948                 }
    6949 
    6950                 this.filters = filters;
     2983        },
     2984        /**
     2985         * @param {Object} event
     2986         */
     2987        updateSetting: function( event ) {
     2988                var $setting = $( event.target ).closest('[data-setting]'),
     2989                        setting, value;
     2990
     2991                if ( ! $setting.length ) {
     2992                        return;
     2993                }
     2994
     2995                setting = $setting.data('setting');
     2996                value   = event.target.value;
     2997
     2998                if ( this.model.get( setting ) !== value ) {
     2999                        this.save( setting, value );
     3000                }
     3001        },
     3002
     3003        /**
     3004         * Pass all the arguments to the model's save method.
     3005         *
     3006         * Records the aggregate status of all save requests and updates the
     3007         * view's classes accordingly.
     3008         */
     3009        save: function() {
     3010                var view = this,
     3011                        save = this._save = this._save || { status: 'ready' },
     3012                        request = this.model.save.apply( this.model, arguments ),
     3013                        requests = save.requests ? $.when( request, save.requests ) : request;
     3014
     3015                // If we're waiting to remove 'Saved.', stop.
     3016                if ( save.savedTimer ) {
     3017                        clearTimeout( save.savedTimer );
     3018                }
     3019
     3020                this.updateSave('waiting');
     3021                save.requests = requests;
     3022                requests.always( function() {
     3023                        // If we've performed another request since this one, bail.
     3024                        if ( save.requests !== requests ) {
     3025                                return;
     3026                        }
     3027
     3028                        view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
     3029                        save.savedTimer = setTimeout( function() {
     3030                                view.updateSave('ready');
     3031                                delete save.savedTimer;
     3032                        }, 2000 );
     3033                });
     3034        },
     3035        /**
     3036         * @param {string} status
     3037         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     3038         */
     3039        updateSave: function( status ) {
     3040                var save = this._save = this._save || { status: 'ready' };
     3041
     3042                if ( status && status !== save.status ) {
     3043                        this.$el.removeClass( 'save-' + save.status );
     3044                        save.status = status;
     3045                }
     3046
     3047                this.$el.addClass( 'save-' + save.status );
     3048                return this;
     3049        },
     3050
     3051        updateAll: function() {
     3052                var $settings = this.$('[data-setting]'),
     3053                        model = this.model,
     3054                        changed;
     3055
     3056                changed = _.chain( $settings ).map( function( el ) {
     3057                        var $input = $('input, textarea, select, [value]', el ),
     3058                                setting, value;
     3059
     3060                        if ( ! $input.length ) {
     3061                                return;
     3062                        }
     3063
     3064                        setting = $(el).data('setting');
     3065                        value = $input.val();
     3066
     3067                        // Record the value if it changed.
     3068                        if ( model.get( setting ) !== value ) {
     3069                                return [ setting, value ];
     3070                        }
     3071                }).compact().object().value();
     3072
     3073                if ( ! _.isEmpty( changed ) ) {
     3074                        model.save( changed );
     3075                }
     3076        },
     3077        /**
     3078         * @param {Object} event
     3079         */
     3080        removeFromLibrary: function( event ) {
     3081                // Catch enter and space events
     3082                if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     3083                        return;
     3084                }
     3085
     3086                // Stop propagation so the model isn't selected.
     3087                event.stopPropagation();
     3088
     3089                this.collection.remove( this.model );
     3090        },
     3091
     3092        /**
     3093         * Add the model if it isn't in the selection, if it is in the selection,
     3094         * remove it.
     3095         *
     3096         * @param  {[type]} event [description]
     3097         * @return {[type]}       [description]
     3098         */
     3099        checkClickHandler: function ( event ) {
     3100                var selection = this.options.selection;
     3101                if ( ! selection ) {
     3102                        return;
     3103                }
     3104                event.stopPropagation();
     3105                if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
     3106                        selection.remove( this.model );
     3107                        // Move focus back to the attachment tile (from the check).
     3108                        this.$el.focus();
     3109                } else {
     3110                        selection.add( this.model );
     3111                }
    69513112        }
    69523113});
    69533114
    6954 module.exports = All;
    6955 
    6956 
    6957 /***/ }),
    6958 /* 78 */
    6959 /***/ (function(module, exports) {
    6960 
     3115// Ensure settings remain in sync between attachment views.
     3116_.each({
     3117        caption: '_syncCaption',
     3118        title:   '_syncTitle',
     3119        artist:  '_syncArtist',
     3120        album:   '_syncAlbum'
     3121}, function( method, setting ) {
     3122        /**
     3123         * @param {Backbone.Model} model
     3124         * @param {string} value
     3125         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     3126         */
     3127        Attachment.prototype[ method ] = function( model, value ) {
     3128                var $setting = this.$('[data-setting="' + setting + '"]');
     3129
     3130                if ( ! $setting.length ) {
     3131                        return this;
     3132                }
     3133
     3134                // If the updated value is in sync with the value in the DOM, there
     3135                // is no need to re-render. If we're currently editing the value,
     3136                // it will automatically be in sync, suppressing the re-render for
     3137                // the view we're editing, while updating any others.
     3138                if ( value === $setting.find('input, textarea, select, [value]').val() ) {
     3139                        return this;
     3140                }
     3141
     3142                return this.render();
     3143        };
     3144});
     3145
     3146module.exports = Attachment;
     3147
     3148},{}],24:[function(require,module,exports){
     3149/*globals wp, _ */
     3150
     3151/**
     3152 * wp.media.view.Attachment.Details
     3153 *
     3154 * @class
     3155 * @augments wp.media.view.Attachment
     3156 * @augments wp.media.View
     3157 * @augments wp.Backbone.View
     3158 * @augments Backbone.View
     3159 */
     3160var Attachment = wp.media.view.Attachment,
     3161        l10n = wp.media.view.l10n,
     3162        Details;
     3163
     3164Details = Attachment.extend({
     3165        tagName:   'div',
     3166        className: 'attachment-details',
     3167        template:  wp.template('attachment-details'),
     3168
     3169        attributes: function() {
     3170                return {
     3171                        'tabIndex':     0,
     3172                        'data-id':      this.model.get( 'id' )
     3173                };
     3174        },
     3175
     3176        events: {
     3177                'change [data-setting]':          'updateSetting',
     3178                'change [data-setting] input':    'updateSetting',
     3179                'change [data-setting] select':   'updateSetting',
     3180                'change [data-setting] textarea': 'updateSetting',
     3181                'click .delete-attachment':       'deleteAttachment',
     3182                'click .trash-attachment':        'trashAttachment',
     3183                'click .untrash-attachment':      'untrashAttachment',
     3184                'click .edit-attachment':         'editAttachment',
     3185                'click .refresh-attachment':      'refreshAttachment',
     3186                'keydown':                        'toggleSelectionHandler'
     3187        },
     3188
     3189        initialize: function() {
     3190                this.options = _.defaults( this.options, {
     3191                        rerenderOnModelChange: false
     3192                });
     3193
     3194                this.on( 'ready', this.initialFocus );
     3195                // Call 'initialize' directly on the parent class.
     3196                Attachment.prototype.initialize.apply( this, arguments );
     3197        },
     3198
     3199        initialFocus: function() {
     3200                if ( ! wp.media.isTouchDevice ) {
     3201                        this.$( ':input' ).eq( 0 ).focus();
     3202                }
     3203        },
     3204        /**
     3205         * @param {Object} event
     3206         */
     3207        deleteAttachment: function( event ) {
     3208                event.preventDefault();
     3209
     3210                if ( window.confirm( l10n.warnDelete ) ) {
     3211                        this.model.destroy();
     3212                        // Keep focus inside media modal
     3213                        // after image is deleted
     3214                        this.controller.modal.focusManager.focus();
     3215                }
     3216        },
     3217        /**
     3218         * @param {Object} event
     3219         */
     3220        trashAttachment: function( event ) {
     3221                var library = this.controller.library;
     3222                event.preventDefault();
     3223
     3224                if ( wp.media.view.settings.mediaTrash &&
     3225                        'edit-metadata' === this.controller.content.mode() ) {
     3226
     3227                        this.model.set( 'status', 'trash' );
     3228                        this.model.save().done( function() {
     3229                                library._requery( true );
     3230                        } );
     3231                }  else {
     3232                        this.model.destroy();
     3233                }
     3234        },
     3235        /**
     3236         * @param {Object} event
     3237         */
     3238        untrashAttachment: function( event ) {
     3239                var library = this.controller.library;
     3240                event.preventDefault();
     3241
     3242                this.model.set( 'status', 'inherit' );
     3243                this.model.save().done( function() {
     3244                        library._requery( true );
     3245                } );
     3246        },
     3247        /**
     3248         * @param {Object} event
     3249         */
     3250        editAttachment: function( event ) {
     3251                var editState = this.controller.states.get( 'edit-image' );
     3252                if ( window.imageEdit && editState ) {
     3253                        event.preventDefault();
     3254
     3255                        editState.set( 'image', this.model );
     3256                        this.controller.setState( 'edit-image' );
     3257                } else {
     3258                        this.$el.addClass('needs-refresh');
     3259                }
     3260        },
     3261        /**
     3262         * @param {Object} event
     3263         */
     3264        refreshAttachment: function( event ) {
     3265                this.$el.removeClass('needs-refresh');
     3266                event.preventDefault();
     3267                this.model.fetch();
     3268        },
     3269        /**
     3270         * When reverse tabbing(shift+tab) out of the right details panel, deliver
     3271         * the focus to the item in the list that was being edited.
     3272         *
     3273         * @param {Object} event
     3274         */
     3275        toggleSelectionHandler: function( event ) {
     3276                if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
     3277                        this.controller.trigger( 'attachment:details:shift-tab', event );
     3278                        return false;
     3279                }
     3280
     3281                if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     3282                        this.controller.trigger( 'attachment:keydown:arrow', event );
     3283                        return;
     3284                }
     3285        }
     3286});
     3287
     3288module.exports = Details;
     3289
     3290},{}],25:[function(require,module,exports){
     3291/*globals wp */
     3292
     3293/**
     3294 * wp.media.view.Attachment.EditLibrary
     3295 *
     3296 * @class
     3297 * @augments wp.media.view.Attachment
     3298 * @augments wp.media.View
     3299 * @augments wp.Backbone.View
     3300 * @augments Backbone.View
     3301 */
     3302var EditLibrary = wp.media.view.Attachment.extend({
     3303        buttons: {
     3304                close: true
     3305        }
     3306});
     3307
     3308module.exports = EditLibrary;
     3309
     3310},{}],26:[function(require,module,exports){
     3311/*globals wp */
     3312
     3313/**
     3314 * wp.media.view.Attachments.EditSelection
     3315 *
     3316 * @class
     3317 * @augments wp.media.view.Attachment.Selection
     3318 * @augments wp.media.view.Attachment
     3319 * @augments wp.media.View
     3320 * @augments wp.Backbone.View
     3321 * @augments Backbone.View
     3322 */
     3323var EditSelection = wp.media.view.Attachment.Selection.extend({
     3324        buttons: {
     3325                close: true
     3326        }
     3327});
     3328
     3329module.exports = EditSelection;
     3330
     3331},{}],27:[function(require,module,exports){
     3332/*globals wp */
     3333
     3334/**
     3335 * wp.media.view.Attachment.Library
     3336 *
     3337 * @class
     3338 * @augments wp.media.view.Attachment
     3339 * @augments wp.media.View
     3340 * @augments wp.Backbone.View
     3341 * @augments Backbone.View
     3342 */
     3343var Library = wp.media.view.Attachment.extend({
     3344        buttons: {
     3345                check: true
     3346        }
     3347});
     3348
     3349module.exports = Library;
     3350
     3351},{}],28:[function(require,module,exports){
     3352/*globals wp */
     3353
     3354/**
     3355 * wp.media.view.Attachment.Selection
     3356 *
     3357 * @class
     3358 * @augments wp.media.view.Attachment
     3359 * @augments wp.media.View
     3360 * @augments wp.Backbone.View
     3361 * @augments Backbone.View
     3362 */
     3363var Selection = wp.media.view.Attachment.extend({
     3364        className: 'attachment selection',
     3365
     3366        // On click, just select the model, instead of removing the model from
     3367        // the selection.
     3368        toggleSelection: function() {
     3369                this.options.selection.single( this.model );
     3370        }
     3371});
     3372
     3373module.exports = Selection;
     3374
     3375},{}],29:[function(require,module,exports){
     3376/*globals wp, _, jQuery */
     3377
     3378/**
     3379 * wp.media.view.Attachments
     3380 *
     3381 * @class
     3382 * @augments wp.media.View
     3383 * @augments wp.Backbone.View
     3384 * @augments Backbone.View
     3385 */
     3386var View = wp.media.View,
     3387        $ = jQuery,
     3388        Attachments;
     3389
     3390Attachments = View.extend({
     3391        tagName:   'ul',
     3392        className: 'attachments',
     3393
     3394        attributes: {
     3395                tabIndex: -1
     3396        },
     3397
     3398        initialize: function() {
     3399                this.el.id = _.uniqueId('__attachments-view-');
     3400
     3401                _.defaults( this.options, {
     3402                        refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
     3403                        refreshThreshold:   3,
     3404                        AttachmentView:     wp.media.view.Attachment,
     3405                        sortable:           false,
     3406                        resize:             true,
     3407                        idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
     3408                });
     3409
     3410                this._viewsByCid = {};
     3411                this.$window = $( window );
     3412                this.resizeEvent = 'resize.media-modal-columns';
     3413
     3414                this.collection.on( 'add', function( attachment ) {
     3415                        this.views.add( this.createAttachmentView( attachment ), {
     3416                                at: this.collection.indexOf( attachment )
     3417                        });
     3418                }, this );
     3419
     3420                this.collection.on( 'remove', function( attachment ) {
     3421                        var view = this._viewsByCid[ attachment.cid ];
     3422                        delete this._viewsByCid[ attachment.cid ];
     3423
     3424                        if ( view ) {
     3425                                view.remove();
     3426                        }
     3427                }, this );
     3428
     3429                this.collection.on( 'reset', this.render, this );
     3430
     3431                this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
     3432
     3433                // Throttle the scroll handler and bind this.
     3434                this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
     3435
     3436                this.options.scrollElement = this.options.scrollElement || this.el;
     3437                $( this.options.scrollElement ).on( 'scroll', this.scroll );
     3438
     3439                this.initSortable();
     3440
     3441                _.bindAll( this, 'setColumns' );
     3442
     3443                if ( this.options.resize ) {
     3444                        this.on( 'ready', this.bindEvents );
     3445                        this.controller.on( 'open', this.setColumns );
     3446
     3447                        // Call this.setColumns() after this view has been rendered in the DOM so
     3448                        // attachments get proper width applied.
     3449                        _.defer( this.setColumns, this );
     3450                }
     3451        },
     3452
     3453        bindEvents: function() {
     3454                this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
     3455        },
     3456
     3457        attachmentFocus: function() {
     3458                this.$( 'li:first' ).focus();
     3459        },
     3460
     3461        restoreFocus: function() {
     3462                this.$( 'li.selected:first' ).focus();
     3463        },
     3464
     3465        arrowEvent: function( event ) {
     3466                var attachments = this.$el.children( 'li' ),
     3467                        perRow = this.columns,
     3468                        index = attachments.filter( ':focus' ).index(),
     3469                        row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
     3470
     3471                if ( index === -1 ) {
     3472                        return;
     3473                }
     3474
     3475                // Left arrow
     3476                if ( 37 === event.keyCode ) {
     3477                        if ( 0 === index ) {
     3478                                return;
     3479                        }
     3480                        attachments.eq( index - 1 ).focus();
     3481                }
     3482
     3483                // Up arrow
     3484                if ( 38 === event.keyCode ) {
     3485                        if ( 1 === row ) {
     3486                                return;
     3487                        }
     3488                        attachments.eq( index - perRow ).focus();
     3489                }
     3490
     3491                // Right arrow
     3492                if ( 39 === event.keyCode ) {
     3493                        if ( attachments.length === index ) {
     3494                                return;
     3495                        }
     3496                        attachments.eq( index + 1 ).focus();
     3497                }
     3498
     3499                // Down arrow
     3500                if ( 40 === event.keyCode ) {
     3501                        if ( Math.ceil( attachments.length / perRow ) === row ) {
     3502                                return;
     3503                        }
     3504                        attachments.eq( index + perRow ).focus();
     3505                }
     3506        },
     3507
     3508        dispose: function() {
     3509                this.collection.props.off( null, null, this );
     3510                if ( this.options.resize ) {
     3511                        this.$window.off( this.resizeEvent );
     3512                }
     3513
     3514                /**
     3515                 * call 'dispose' directly on the parent class
     3516                 */
     3517                View.prototype.dispose.apply( this, arguments );
     3518        },
     3519
     3520        setColumns: function() {
     3521                var prev = this.columns,
     3522                        width = this.$el.width();
     3523
     3524                if ( width ) {
     3525                        this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
     3526
     3527                        if ( ! prev || prev !== this.columns ) {
     3528                                this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
     3529                        }
     3530                }
     3531        },
     3532
     3533        initSortable: function() {
     3534                var collection = this.collection;
     3535
     3536                if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
     3537                        return;
     3538                }
     3539
     3540                this.$el.sortable( _.extend({
     3541                        // If the `collection` has a `comparator`, disable sorting.
     3542                        disabled: !! collection.comparator,
     3543
     3544                        // Change the position of the attachment as soon as the
     3545                        // mouse pointer overlaps a thumbnail.
     3546                        tolerance: 'pointer',
     3547
     3548                        // Record the initial `index` of the dragged model.
     3549                        start: function( event, ui ) {
     3550                                ui.item.data('sortableIndexStart', ui.item.index());
     3551                        },
     3552
     3553                        // Update the model's index in the collection.
     3554                        // Do so silently, as the view is already accurate.
     3555                        update: function( event, ui ) {
     3556                                var model = collection.at( ui.item.data('sortableIndexStart') ),
     3557                                        comparator = collection.comparator;
     3558
     3559                                // Temporarily disable the comparator to prevent `add`
     3560                                // from re-sorting.
     3561                                delete collection.comparator;
     3562
     3563                                // Silently shift the model to its new index.
     3564                                collection.remove( model, {
     3565                                        silent: true
     3566                                });
     3567                                collection.add( model, {
     3568                                        silent: true,
     3569                                        at:     ui.item.index()
     3570                                });
     3571
     3572                                // Restore the comparator.
     3573                                collection.comparator = comparator;
     3574
     3575                                // Fire the `reset` event to ensure other collections sync.
     3576                                collection.trigger( 'reset', collection );
     3577
     3578                                // If the collection is sorted by menu order,
     3579                                // update the menu order.
     3580                                collection.saveMenuOrder();
     3581                        }
     3582                }, this.options.sortable ) );
     3583
     3584                // If the `orderby` property is changed on the `collection`,
     3585                // check to see if we have a `comparator`. If so, disable sorting.
     3586                collection.props.on( 'change:orderby', function() {
     3587                        this.$el.sortable( 'option', 'disabled', !! collection.comparator );
     3588                }, this );
     3589
     3590                this.collection.props.on( 'change:orderby', this.refreshSortable, this );
     3591                this.refreshSortable();
     3592        },
     3593
     3594        refreshSortable: function() {
     3595                if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
     3596                        return;
     3597                }
     3598
     3599                // If the `collection` has a `comparator`, disable sorting.
     3600                var collection = this.collection,
     3601                        orderby = collection.props.get('orderby'),
     3602                        enabled = 'menuOrder' === orderby || ! collection.comparator;
     3603
     3604                this.$el.sortable( 'option', 'disabled', ! enabled );
     3605        },
     3606
     3607        /**
     3608         * @param {wp.media.model.Attachment} attachment
     3609         * @returns {wp.media.View}
     3610         */
     3611        createAttachmentView: function( attachment ) {
     3612                var view = new this.options.AttachmentView({
     3613                        controller:           this.controller,
     3614                        model:                attachment,
     3615                        collection:           this.collection,
     3616                        selection:            this.options.selection
     3617                });
     3618
     3619                return this._viewsByCid[ attachment.cid ] = view;
     3620        },
     3621
     3622        prepare: function() {
     3623                // Create all of the Attachment views, and replace
     3624                // the list in a single DOM operation.
     3625                if ( this.collection.length ) {
     3626                        this.views.set( this.collection.map( this.createAttachmentView, this ) );
     3627
     3628                // If there are no elements, clear the views and load some.
     3629                } else {
     3630                        this.views.unset();
     3631                        this.collection.more().done( this.scroll );
     3632                }
     3633        },
     3634
     3635        ready: function() {
     3636                // Trigger the scroll event to check if we're within the
     3637                // threshold to query for additional attachments.
     3638                this.scroll();
     3639        },
     3640
     3641        scroll: function() {
     3642                var view = this,
     3643                        el = this.options.scrollElement,
     3644                        scrollTop = el.scrollTop,
     3645                        toolbar;
     3646
     3647                // The scroll event occurs on the document, but the element
     3648                // that should be checked is the document body.
     3649                if ( el === document ) {
     3650                        el = document.body;
     3651                        scrollTop = $(document).scrollTop();
     3652                }
     3653
     3654                if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
     3655                        return;
     3656                }
     3657
     3658                toolbar = this.views.parent.toolbar;
     3659
     3660                // Show the spinner only if we are close to the bottom.
     3661                if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
     3662                        toolbar.get('spinner').show();
     3663                }
     3664
     3665                if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
     3666                        this.collection.more().done(function() {
     3667                                view.scroll();
     3668                                toolbar.get('spinner').hide();
     3669                        });
     3670                }
     3671        }
     3672});
     3673
     3674module.exports = Attachments;
     3675
     3676},{}],30:[function(require,module,exports){
    69613677/*globals wp, _, jQuery */
    69623678
    … …  
    74044120module.exports = AttachmentsBrowser;
    74054121
    7406 
    7407 /***/ }),
    7408 /* 79 */
    7409 /***/ (function(module, exports) {
    7410 
    7411 /*globals wp, _, Backbone */
    7412 
    7413 /**
    7414  * wp.media.view.Selection
    7415  *
    7416  * @class
    7417  * @augments wp.media.View
    7418  * @augments wp.Backbone.View
    7419  * @augments Backbone.View
    7420  */
    7421 var l10n = wp.media.view.l10n,
    7422         Selection;
    7423 
    7424 Selection = wp.media.View.extend({
    7425         tagName:   'div',
    7426         className: 'media-selection',
    7427         template:  wp.template('media-selection'),
    7428 
    7429         events: {
    7430                 'click .edit-selection':  'edit',
    7431                 'click .clear-selection': 'clear'
    7432         },
    7433 
    7434         initialize: function() {
    7435                 _.defaults( this.options, {
    7436                         editable:  false,
    7437                         clearable: true
    7438                 });
    7439 
    7440                 /**
    7441                  * @member {wp.media.view.Attachments.Selection}
    7442                  */
    7443                 this.attachments = new wp.media.view.Attachments.Selection({
    7444                         controller: this.controller,
    7445                         collection: this.collection,
    7446                         selection:  this.collection,
    7447                         model:      new Backbone.Model()
    7448                 });
    7449 
    7450                 this.views.set( '.selection-view', this.attachments );
    7451                 this.collection.on( 'add remove reset', this.refresh, this );
    7452                 this.controller.on( 'content:activate', this.refresh, this );
    7453         },
    7454 
    7455         ready: function() {
    7456                 this.refresh();
    7457         },
    7458 
    7459         refresh: function() {
    7460                 // If the selection hasn't been rendered, bail.
    7461                 if ( ! this.$el.children().length ) {
    7462                         return;
    7463                 }
    7464 
    7465                 var collection = this.collection,
    7466                         editing = 'edit-selection' === this.controller.content.mode();
    7467 
    7468                 // If nothing is selected, display nothing.
    7469                 this.$el.toggleClass( 'empty', ! collection.length );
    7470                 this.$el.toggleClass( 'one', 1 === collection.length );
    7471                 this.$el.toggleClass( 'editing', editing );
    7472 
    7473                 this.$('.count').text( l10n.selected.replace('%d', collection.length) );
    7474         },
    7475 
    7476         edit: function( event ) {
    7477                 event.preventDefault();
    7478                 if ( this.options.editable ) {
    7479                         this.options.editable.call( this, this.collection );
    7480                 }
    7481         },
    7482 
    7483         clear: function( event ) {
    7484                 event.preventDefault();
    7485                 this.collection.reset();
    7486 
    7487                 // Keep focus inside media modal
    7488                 // after clear link is selected
    7489                 this.controller.modal.focusManager.focus();
    7490         }
    7491 });
    7492 
    7493 module.exports = Selection;
    7494 
    7495 
    7496 /***/ }),
    7497 /* 80 */
    7498 /***/ (function(module, exports) {
    7499 
    7500 /*globals wp */
    7501 
    7502 /**
    7503  * wp.media.view.Attachment.Selection
    7504  *
    7505  * @class
    7506  * @augments wp.media.view.Attachment
    7507  * @augments wp.media.View
    7508  * @augments wp.Backbone.View
    7509  * @augments Backbone.View
    7510  */
    7511 var Selection = wp.media.view.Attachment.extend({
    7512         className: 'attachment selection',
    7513 
    7514         // On click, just select the model, instead of removing the model from
    7515         // the selection.
    7516         toggleSelection: function() {
    7517                 this.options.selection.single( this.model );
    7518         }
    7519 });
    7520 
    7521 module.exports = Selection;
    7522 
    7523 
    7524 /***/ }),
    7525 /* 81 */
    7526 /***/ (function(module, exports) {
    7527 
     4122},{}],31:[function(require,module,exports){
    75284123/*globals wp, _ */
    75294124
    … …  
    75574152module.exports = Selection;
    75584153
    7559 
    7560 /***/ }),
    7561 /* 82 */
    7562 /***/ (function(module, exports) {
    7563 
    7564 /*globals wp */
     4154},{}],32:[function(require,module,exports){
     4155/*globals _, Backbone */
    75654156
    75664157/**
    7567  * wp.media.view.Attachments.EditSelection
     4158 * wp.media.view.ButtonGroup
    75684159 *
    75694160 * @class
    7570  * @augments wp.media.view.Attachment.Selection
    7571  * @augments wp.media.view.Attachment
    75724161 * @augments wp.media.View
    75734162 * @augments wp.Backbone.View
    75744163 * @augments Backbone.View
    75754164 */
    7576 var EditSelection = wp.media.view.Attachment.Selection.extend({
    7577         buttons: {
    7578                 close: true
     4165var $ = Backbone.$,
     4166        ButtonGroup;
     4167
     4168ButtonGroup = wp.media.View.extend({
     4169        tagName:   'div',
     4170        className: 'button-group button-large media-button-group',
     4171
     4172        initialize: function() {
     4173                /**
     4174                 * @member {wp.media.view.Button[]}
     4175                 */
     4176                this.buttons = _.map( this.options.buttons || [], function( button ) {
     4177                        if ( button instanceof Backbone.View ) {
     4178                                return button;
     4179                        } else {
     4180                                return new wp.media.view.Button( button ).render();
     4181                        }
     4182                });
     4183
     4184                delete this.options.buttons;
     4185
     4186                if ( this.options.classes ) {
     4187                        this.$el.addClass( this.options.classes );
     4188                }
     4189        },
     4190
     4191        /**
     4192         * @returns {wp.media.view.ButtonGroup}
     4193         */
     4194        render: function() {
     4195                this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
     4196                return this;
    75794197        }
    75804198});
    75814199
    7582 module.exports = EditSelection;
    7583 
    7584 
    7585 /***/ }),
    7586 /* 83 */
    7587 /***/ (function(module, exports) {
    7588 
     4200module.exports = ButtonGroup;
     4201
     4202},{}],33:[function(require,module,exports){
    75894203/*globals _, Backbone */
    75904204
    75914205/**
    7592  * wp.media.view.Settings
     4206 * wp.media.view.Button
     4207 *
     4208 * @class
     4209 * @augments wp.media.View
     4210 * @augments wp.Backbone.View
     4211 * @augments Backbone.View
     4212 */
     4213var Button = wp.media.View.extend({
     4214        tagName:    'a',
     4215        className:  'media-button',
     4216        attributes: { href: '#' },
     4217
     4218        events: {
     4219                'click': 'click'
     4220        },
     4221
     4222        defaults: {
     4223                text:     '',
     4224                style:    '',
     4225                size:     'large',
     4226                disabled: false
     4227        },
     4228
     4229        initialize: function() {
     4230                /**
     4231                 * Create a model with the provided `defaults`.
     4232                 *
     4233                 * @member {Backbone.Model}
     4234                 */
     4235                this.model = new Backbone.Model( this.defaults );
     4236
     4237                // If any of the `options` have a key from `defaults`, apply its
     4238                // value to the `model` and remove it from the `options object.
     4239                _.each( this.defaults, function( def, key ) {
     4240                        var value = this.options[ key ];
     4241                        if ( _.isUndefined( value ) ) {
     4242                                return;
     4243                        }
     4244
     4245                        this.model.set( key, value );
     4246                        delete this.options[ key ];
     4247                }, this );
     4248
     4249                this.listenTo( this.model, 'change', this.render );
     4250        },
     4251        /**
     4252         * @returns {wp.media.view.Button} Returns itself to allow chaining
     4253         */
     4254        render: function() {
     4255                var classes = [ 'button', this.className ],
     4256                        model = this.model.toJSON();
     4257
     4258                if ( model.style ) {
     4259                        classes.push( 'button-' + model.style );
     4260                }
     4261
     4262                if ( model.size ) {
     4263                        classes.push( 'button-' + model.size );
     4264                }
     4265
     4266                classes = _.uniq( classes.concat( this.options.classes ) );
     4267                this.el.className = classes.join(' ');
     4268
     4269                this.$el.attr( 'disabled', model.disabled );
     4270                this.$el.text( this.model.get('text') );
     4271
     4272                return this;
     4273        },
     4274        /**
     4275         * @param {Object} event
     4276         */
     4277        click: function( event ) {
     4278                if ( '#' === this.attributes.href ) {
     4279                        event.preventDefault();
     4280                }
     4281
     4282                if ( this.options.click && ! this.model.get('disabled') ) {
     4283                        this.options.click.apply( this, arguments );
     4284                }
     4285        }
     4286});
     4287
     4288module.exports = Button;
     4289
     4290},{}],34:[function(require,module,exports){
     4291/*globals wp, _, jQuery */
     4292
     4293/**
     4294 * wp.media.view.Cropper
     4295 *
     4296 * Uses the imgAreaSelect plugin to allow a user to crop an image.
     4297 *
     4298 * Takes imgAreaSelect options from
     4299 * wp.customize.HeaderControl.calculateImageSelectOptions via
     4300 * wp.customize.HeaderControl.openMM.
    75934301 *
    75944302 * @class
    … …  
    75984306 */
    75994307var View = wp.media.View,
    7600         $ = Backbone.$,
    7601         Settings;
    7602 
    7603 Settings = View.extend({
    7604         events: {
    7605                 'click button':    'updateHandler',
    7606                 'change input':    'updateHandler',
    7607                 'change select':   'updateHandler',
    7608                 'change textarea': 'updateHandler'
    7609         },
    7610 
     4308        UploaderStatus = wp.media.view.UploaderStatus,
     4309        l10n = wp.media.view.l10n,
     4310        $ = jQuery,
     4311        Cropper;
     4312
     4313Cropper = View.extend({
     4314        className: 'crop-content',
     4315        template: wp.template('crop-content'),
    76114316        initialize: function() {
    7612                 this.model = this.model || new Backbone.Model();
    7613                 this.listenTo( this.model, 'change', this.updateChanges );
    7614         },
    7615 
     4317                _.bindAll(this, 'onImageLoad');
     4318        },
     4319        ready: function() {
     4320                this.controller.frame.on('content:error:crop', this.onError, this);
     4321                this.$image = this.$el.find('.crop-image');
     4322                this.$image.on('load', this.onImageLoad);
     4323                $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
     4324        },
     4325        remove: function() {
     4326                $(window).off('resize.cropper');
     4327                this.$el.remove();
     4328                this.$el.off();
     4329                View.prototype.remove.apply(this, arguments);
     4330        },
    76164331        prepare: function() {
    7617                 return _.defaults({
    7618                         model: this.model.toJSON()
    7619                 }, this.options );
    7620         },
    7621         /**
    7622          * @returns {wp.media.view.Settings} Returns itself to allow chaining
    7623          */
    7624         render: function() {
    7625                 View.prototype.render.apply( this, arguments );
    7626                 // Select the correct values.
    7627                 _( this.model.attributes ).chain().keys().each( this.update, this );
    7628                 return this;
    7629         },
    7630         /**
    7631          * @param {string} key
    7632          */
    7633         update: function( key ) {
    7634                 var value = this.model.get( key ),
    7635                         $setting = this.$('[data-setting="' + key + '"]'),
    7636                         $buttons, $value;
    7637 
    7638                 // Bail if we didn't find a matching setting.
    7639                 if ( ! $setting.length ) {
    7640                         return;
    7641                 }
    7642 
    7643                 // Attempt to determine how the setting is rendered and update
    7644                 // the selected value.
    7645 
    7646                 // Handle dropdowns.
    7647                 if ( $setting.is('select') ) {
    7648                         $value = $setting.find('[value="' + value + '"]');
    7649 
    7650                         if ( $value.length ) {
    7651                                 $setting.find('option').prop( 'selected', false );
    7652                                 $value.prop( 'selected', true );
    7653                         } else {
    7654                                 // If we can't find the desired value, record what *is* selected.
    7655                                 this.model.set( key, $setting.find(':selected').val() );
    7656                         }
    7657 
    7658                 // Handle button groups.
    7659                 } else if ( $setting.hasClass('button-group') ) {
    7660                         $buttons = $setting.find('button').removeClass('active');
    7661                         $buttons.filter( '[value="' + value + '"]' ).addClass('active');
    7662 
    7663                 // Handle text inputs and textareas.
    7664                 } else if ( $setting.is('input[type="text"], textarea') ) {
    7665                         if ( ! $setting.is(':focus') ) {
    7666                                 $setting.val( value );
    7667                         }
    7668                 // Handle checkboxes.
    7669                 } else if ( $setting.is('input[type="checkbox"]') ) {
    7670                         $setting.prop( 'checked', !! value && 'false' !== value );
    7671                 }
    7672         },
    7673         /**
    7674          * @param {Object} event
    7675          */
    7676         updateHandler: function( event ) {
    7677                 var $setting = $( event.target ).closest('[data-setting]'),
    7678                         value = event.target.value,
    7679                         userSetting;
    7680 
    7681                 event.preventDefault();
    7682 
    7683                 if ( ! $setting.length ) {
    7684                         return;
    7685                 }
    7686 
    7687                 // Use the correct value for checkboxes.
    7688                 if ( $setting.is('input[type="checkbox"]') ) {
    7689                         value = $setting[0].checked;
    7690                 }
    7691 
    7692                 // Update the corresponding setting.
    7693                 this.model.set( $setting.data('setting'), value );
    7694 
    7695                 // If the setting has a corresponding user setting,
    7696                 // update that as well.
    7697                 if ( userSetting = $setting.data('userSetting') ) {
    7698                         window.setUserSetting( userSetting, value );
    7699                 }
    7700         },
    7701 
    7702         updateChanges: function( model ) {
    7703                 if ( model.hasChanged() ) {
    7704                         _( model.changed ).chain().keys().each( this.update, this );
    7705                 }
     4332                return {
     4333                        title: l10n.cropYourImage,
     4334                        url: this.options.attachment.get('url')
     4335                };
     4336        },
     4337        onImageLoad: function() {
     4338                var imgOptions = this.controller.get('imgSelectOptions');
     4339                if (typeof imgOptions === 'function') {
     4340                        imgOptions = imgOptions(this.options.attachment, this.controller);
     4341                }
     4342
     4343                imgOptions = _.extend(imgOptions, {parent: this.$el});
     4344                this.trigger('image-loaded');
     4345                this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
     4346        },
     4347        onError: function() {
     4348                var filename = this.options.attachment.get('filename');
     4349
     4350                this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     4351                        filename: UploaderStatus.prototype.filename(filename),
     4352                        message: window._wpMediaViewsL10n.cropError
     4353                }), { at: 0 });
    77064354        }
    77074355});
    77084356
    7709 module.exports = Settings;
    7710 
    7711 
    7712 /***/ }),
    7713 /* 84 */
    7714 /***/ (function(module, exports) {
    7715 
     4357module.exports = Cropper;
     4358
     4359},{}],35:[function(require,module,exports){
    77164360/*globals wp, _ */
    77174361
    77184362/**
    7719  * wp.media.view.Settings.AttachmentDisplay
    7720  *
    7721  * @class
    7722  * @augments wp.media.view.Settings
    7723  * @augments wp.media.View
    7724  * @augments wp.Backbone.View
    7725  * @augments Backbone.View
    7726  */
    7727 var Settings = wp.media.view.Settings,
    7728         AttachmentDisplay;
    7729 
    7730 AttachmentDisplay = Settings.extend({
    7731         className: 'attachment-display-settings',
    7732         template:  wp.template('attachment-display-settings'),
    7733 
    7734         initialize: function() {
    7735                 var attachment = this.options.attachment;
    7736 
    7737                 _.defaults( this.options, {
    7738                         userSettings: false
    7739                 });
    7740                 // Call 'initialize' directly on the parent class.
    7741                 Settings.prototype.initialize.apply( this, arguments );
    7742                 this.listenTo( this.model, 'change:link', this.updateLinkTo );
    7743 
    7744                 if ( attachment ) {
    7745                         attachment.on( 'change:uploading', this.render, this );
    7746                 }
    7747         },
    7748 
    7749         dispose: function() {
    7750                 var attachment = this.options.attachment;
    7751                 if ( attachment ) {
    7752                         attachment.off( null, null, this );
    7753                 }
    7754                 /**
    7755                  * call 'dispose' directly on the parent class
    7756                  */
    7757                 Settings.prototype.dispose.apply( this, arguments );
    7758         },
    7759         /**
    7760          * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
    7761          */
    7762         render: function() {
    7763                 var attachment = this.options.attachment;
    7764                 if ( attachment ) {
    7765                         _.extend( this.options, {
    7766                                 sizes: attachment.get('sizes'),
    7767                                 type:  attachment.get('type')
    7768                         });
    7769                 }
    7770                 /**
    7771                  * call 'render' directly on the parent class
    7772                  */
    7773                 Settings.prototype.render.call( this );
    7774                 this.updateLinkTo();
    7775                 return this;
    7776         },
    7777 
    7778         updateLinkTo: function() {
    7779                 var linkTo = this.model.get('link'),
    7780                         $input = this.$('.link-to-custom'),
    7781                         attachment = this.options.attachment;
    7782 
    7783                 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
    7784                         $input.addClass( 'hidden' );
    7785                         return;
    7786                 }
    7787 
    7788                 if ( attachment ) {
    7789                         if ( 'post' === linkTo ) {
    7790                                 $input.val( attachment.get('link') );
    7791                         } else if ( 'file' === linkTo ) {
    7792                                 $input.val( attachment.get('url') );
    7793                         } else if ( ! this.model.get('linkUrl') ) {
    7794                                 $input.val('http://');
    7795                         }
    7796 
    7797                         $input.prop( 'readonly', 'custom' !== linkTo );
    7798                 }
    7799 
    7800                 $input.removeClass( 'hidden' );
    7801 
    7802                 // If the input is visible, focus and select its contents.
    7803                 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
    7804                         $input.focus()[0].select();
    7805                 }
    7806         }
    7807 });
    7808 
    7809 module.exports = AttachmentDisplay;
    7810 
    7811 
    7812 /***/ }),
    7813 /* 85 */
    7814 /***/ (function(module, exports) {
    7815 
    7816 /*globals wp */
    7817 
    7818 /**
    7819  * wp.media.view.Settings.Gallery
    7820  *
    7821  * @class
    7822  * @augments wp.media.view.Settings
    7823  * @augments wp.media.View
    7824  * @augments wp.Backbone.View
    7825  * @augments Backbone.View
    7826  */
    7827 var Gallery = wp.media.view.Settings.extend({
    7828         className: 'collection-settings gallery-settings',
    7829         template:  wp.template('gallery-settings')
    7830 });
    7831 
    7832 module.exports = Gallery;
    7833 
    7834 
    7835 /***/ }),
    7836 /* 86 */
    7837 /***/ (function(module, exports) {
    7838 
    7839 /*globals wp */
    7840 
    7841 /**
    7842  * wp.media.view.Settings.Playlist
    7843  *
    7844  * @class
    7845  * @augments wp.media.view.Settings
    7846  * @augments wp.media.View
    7847  * @augments wp.Backbone.View
    7848  * @augments Backbone.View
    7849  */
    7850 var Playlist = wp.media.view.Settings.extend({
    7851         className: 'collection-settings playlist-settings',
    7852         template:  wp.template('playlist-settings')
    7853 });
    7854 
    7855 module.exports = Playlist;
    7856 
    7857 
    7858 /***/ }),
    7859 /* 87 */
    7860 /***/ (function(module, exports) {
    7861 
    7862 /*globals wp, _ */
    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                 'click .refresh-attachment':      'refreshAttachment',
    7899                 'keydown':                        'toggleSelectionHandler'
    7900         },
    7901 
    7902         initialize: function() {
    7903                 this.options = _.defaults( this.options, {
    7904                         rerenderOnModelChange: false
    7905                 });
    7906 
    7907                 this.on( 'ready', this.initialFocus );
    7908                 // Call 'initialize' directly on the parent class.
    7909                 Attachment.prototype.initialize.apply( this, arguments );
    7910         },
    7911 
    7912         initialFocus: function() {
    7913                 if ( ! wp.media.isTouchDevice ) {
    7914                         this.$( ':input' ).eq( 0 ).focus();
    7915                 }
    7916         },
    7917         /**
    7918          * @param {Object} event
    7919          */
    7920         deleteAttachment: function( event ) {
    7921                 event.preventDefault();
    7922 
    7923                 if ( window.confirm( l10n.warnDelete ) ) {
    7924                         this.model.destroy();
    7925                         // Keep focus inside media modal
    7926                         // after image is deleted
    7927                         this.controller.modal.focusManager.focus();
    7928                 }
    7929         },
    7930         /**
    7931          * @param {Object} event
    7932          */
    7933         trashAttachment: function( event ) {
    7934                 var library = this.controller.library;
    7935                 event.preventDefault();
    7936 
    7937                 if ( wp.media.view.settings.mediaTrash &&
    7938                         'edit-metadata' === this.controller.content.mode() ) {
    7939 
    7940                         this.model.set( 'status', 'trash' );
    7941                         this.model.save().done( function() {
    7942                                 library._requery( true );
    7943                         } );
    7944                 }  else {
    7945                         this.model.destroy();
    7946                 }
    7947         },
    7948         /**
    7949          * @param {Object} event
    7950          */
    7951         untrashAttachment: function( event ) {
    7952                 var library = this.controller.library;
    7953                 event.preventDefault();
    7954 
    7955                 this.model.set( 'status', 'inherit' );
    7956                 this.model.save().done( function() {
    7957                         library._requery( true );
    7958                 } );
    7959         },
    7960         /**
    7961          * @param {Object} event
    7962          */
    7963         editAttachment: function( event ) {
    7964                 var editState = this.controller.states.get( 'edit-image' );
    7965                 if ( window.imageEdit && editState ) {
    7966                         event.preventDefault();
    7967 
    7968                         editState.set( 'image', this.model );
    7969                         this.controller.setState( 'edit-image' );
    7970                 } else {
    7971                         this.$el.addClass('needs-refresh');
    7972                 }
    7973         },
    7974         /**
    7975          * @param {Object} event
    7976          */
    7977         refreshAttachment: function( event ) {
    7978                 this.$el.removeClass('needs-refresh');
    7979                 event.preventDefault();
    7980                 this.model.fetch();
    7981         },
    7982         /**
    7983          * When reverse tabbing(shift+tab) out of the right details panel, deliver
    7984          * the focus to the item in the list that was being edited.
    7985          *
    7986          * @param {Object} event
    7987          */
    7988         toggleSelectionHandler: function( event ) {
    7989                 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
    7990                         this.controller.trigger( 'attachment:details:shift-tab', event );
    7991                         return false;
    7992                 }
    7993 
    7994                 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    7995                         this.controller.trigger( 'attachment:keydown:arrow', event );
    7996                         return;
    7997                 }
    7998         }
    7999 });
    8000 
    8001 module.exports = Details;
    8002 
    8003 
    8004 /***/ }),
    8005 /* 88 */
    8006 /***/ (function(module, exports) {
    8007 
    8008 /*globals _ */
    8009 
    8010 /**
    8011  * wp.media.view.AttachmentCompat
    8012  *
    8013  * A view to display fields added via the `attachment_fields_to_edit` filter.
     4363 * wp.media.view.EditImage
    80144364 *
    80154365 * @class
    … …  
    80194369 */
    80204370var View = wp.media.View,
    8021         AttachmentCompat;
    8022 
    8023 AttachmentCompat = View.extend({
    8024         tagName:   'form',
    8025         className: 'compat-item',
    8026 
    8027         events: {
    8028                 'submit':          'preventDefault',
    8029                 'change input':    'save',
    8030                 'change select':   'save',
    8031                 'change textarea': 'save'
    8032         },
    8033 
    8034         initialize: function() {
    8035                 this.listenTo( this.model, 'change:compat', this.render );
    8036         },
    8037         /**
    8038          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    8039          */
    8040         dispose: function() {
    8041                 if ( this.$(':focus').length ) {
    8042                         this.save();
    8043                 }
    8044                 /**
    8045                  * call 'dispose' directly on the parent class
    8046                  */
    8047                 return View.prototype.dispose.apply( this, arguments );
    8048         },
    8049         /**
    8050          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    8051          */
    8052         render: function() {
    8053                 var compat = this.model.get('compat');
    8054                 if ( ! compat || ! compat.item ) {
    8055                         return;
    8056                 }
    8057 
    8058                 this.views.detach();
    8059                 this.$el.html( compat.item );
    8060                 this.views.render();
    8061                 return this;
    8062         },
    8063         /**
    8064          * @param {Object} event
    8065          */
    8066         preventDefault: function( event ) {
    8067                 event.preventDefault();
    8068         },
    8069         /**
    8070          * @param {Object} event
    8071          */
    8072         save: function( event ) {
    8073                 var data = {};
    8074 
    8075                 if ( event ) {
    8076                         event.preventDefault();
    8077                 }
    8078 
    8079                 _.each( this.$el.serializeArray(), function( pair ) {
    8080                         data[ pair.name ] = pair.value;
    8081                 });
    8082 
    8083                 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
    8084                 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
    8085         },
    8086 
    8087         postSave: function() {
    8088                 this.controller.trigger( 'attachment:compat:ready', ['ready'] );
     4371        EditImage;
     4372
     4373EditImage = View.extend({
     4374        className: 'image-editor',
     4375        template: wp.template('image-editor'),
     4376
     4377        initialize: function( options ) {
     4378                this.editor = window.imageEdit;
     4379                this.controller = options.controller;
     4380                View.prototype.initialize.apply( this, arguments );
     4381        },
     4382
     4383        prepare: function() {
     4384                return this.model.toJSON();
     4385        },
     4386
     4387        loadEditor: function() {
     4388                var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
     4389                dfd.done( _.bind( this.focus, this ) );
     4390        },
     4391
     4392        focus: function() {
     4393                this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
     4394        },
     4395
     4396        back: function() {
     4397                var lastState = this.controller.lastState();
     4398                this.controller.setState( lastState );
     4399        },
     4400
     4401        refresh: function() {
     4402                this.model.fetch();
     4403        },
     4404
     4405        save: function() {
     4406                var lastState = this.controller.lastState();
     4407
     4408                this.model.fetch().done( _.bind( function() {
     4409                        this.controller.setState( lastState );
     4410                }, this ) );
    80894411        }
     4412
    80904413});
    80914414
    8092 module.exports = AttachmentCompat;
    8093 
    8094 
    8095 /***/ }),
    8096 /* 89 */
    8097 /***/ (function(module, exports) {
    8098 
    8099 /**
    8100  * wp.media.view.Iframe
    8101  *
    8102  * @class
    8103  * @augments wp.media.View
    8104  * @augments wp.Backbone.View
    8105  * @augments Backbone.View
    8106  */
    8107 var Iframe = wp.media.View.extend({
    8108         className: 'media-iframe',
    8109         /**
    8110          * @returns {wp.media.view.Iframe} Returns itself to allow chaining
    8111          */
    8112         render: function() {
    8113                 this.views.detach();
    8114                 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
    8115                 this.views.render();
    8116                 return this;
    8117         }
    8118 });
    8119 
    8120 module.exports = Iframe;
    8121 
    8122 
    8123 /***/ }),
    8124 /* 90 */
    8125 /***/ (function(module, exports) {
    8126 
     4415module.exports = EditImage;
     4416
     4417},{}],36:[function(require,module,exports){
    81274418/**
    81284419 * wp.media.view.Embed
    … …  
    81884479module.exports = Embed;
    81894480
    8190 
    8191 /***/ }),
    8192 /* 91 */
    8193 /***/ (function(module, exports) {
    8194 
    8195 /**
    8196  * wp.media.view.Label
    8197  *
    8198  * @class
    8199  * @augments wp.media.View
    8200  * @augments wp.Backbone.View
    8201  * @augments Backbone.View
    8202  */
    8203 var Label = wp.media.View.extend({
    8204         tagName: 'label',
    8205         className: 'screen-reader-text',
    8206 
    8207         initialize: function() {
    8208                 this.value = this.options.value;
    8209         },
    8210 
    8211         render: function() {
    8212                 this.$el.html( this.value );
    8213 
    8214                 return this;
    8215         }
    8216 });
    8217 
    8218 module.exports = Label;
    8219 
    8220 
    8221 /***/ }),
    8222 /* 92 */
    8223 /***/ (function(module, exports) {
    8224 
    8225 /*globals wp, _, jQuery */
    8226 
    8227 /**
    8228  * wp.media.view.EmbedUrl
    8229  *
    8230  * @class
    8231  * @augments wp.media.View
    8232  * @augments wp.Backbone.View
    8233  * @augments Backbone.View
    8234  */
    8235 var View = wp.media.View,
    8236         $ = jQuery,
    8237         EmbedUrl;
    8238 
    8239 EmbedUrl = View.extend({
    8240         tagName:   'label',
    8241         className: 'embed-url',
    8242 
    8243         events: {
    8244                 'input':  'url',
    8245                 'keyup':  'url',
    8246                 'change': 'url'
    8247         },
    8248 
    8249         initialize: function() {
    8250                 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
    8251                 this.input = this.$input[0];
    8252 
    8253                 this.spinner = $('<span class="spinner" />')[0];
    8254                 this.$el.append([ this.input, this.spinner ]);
    8255 
    8256                 this.listenTo( this.model, 'change:url', this.render );
    8257 
    8258                 if ( this.model.get( 'url' ) ) {
    8259                         _.delay( _.bind( function () {
    8260                                 this.model.trigger( 'change:url' );
    8261                         }, this ), 500 );
    8262                 }
    8263         },
    8264         /**
    8265          * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
    8266          */
    8267         render: function() {
    8268                 var $input = this.$input;
    8269 
    8270                 if ( $input.is(':focus') ) {
    8271                         return;
    8272                 }
    8273 
    8274                 this.input.value = this.model.get('url') || 'http://';
    8275                 /**
    8276                  * Call `render` directly on parent class with passed arguments
    8277                  */
    8278                 View.prototype.render.apply( this, arguments );
    8279                 return this;
    8280         },
    8281 
    8282         ready: function() {
    8283                 if ( ! wp.media.isTouchDevice ) {
    8284                         this.focus();
    8285                 }
    8286         },
    8287 
    8288         url: function( event ) {
    8289                 this.model.set( 'url', event.target.value );
    8290         },
    8291 
    8292         /**
    8293          * If the input is visible, focus and select its contents.
    8294          */
    8295         focus: function() {
    8296                 var $input = this.$input;
    8297                 if ( $input.is(':visible') ) {
    8298                         $input.focus()[0].select();
    8299                 }
    8300         }
    8301 });
    8302 
    8303 module.exports = EmbedUrl;
    8304 
    8305 
    8306 /***/ }),
    8307 /* 93 */
    8308 /***/ (function(module, exports) {
    8309 
    8310 /*globals wp, _, jQuery */
    8311 
    8312 /**
    8313  * wp.media.view.EmbedLink
    8314  *
    8315  * @class
    8316  * @augments wp.media.view.Settings
    8317  * @augments wp.media.View
    8318  * @augments wp.Backbone.View
    8319  * @augments Backbone.View
    8320  */
    8321 var $ = jQuery,
    8322         EmbedLink;
    8323 
    8324 EmbedLink = wp.media.view.Settings.extend({
    8325         className: 'embed-link-settings',
    8326         template:  wp.template('embed-link-settings'),
    8327 
    8328         initialize: function() {
    8329                 this.spinner = $('<span class="spinner" />');
    8330                 this.$el.append( this.spinner[0] );
    8331                 this.listenTo( this.model, 'change:url', this.updateoEmbed );
    8332         },
    8333 
    8334         updateoEmbed: _.debounce( function() {
    8335                 var url = this.model.get( 'url' );
    8336 
    8337                 // clear out previous results
    8338                 this.$('.embed-container').hide().find('.embed-preview').empty();
    8339                 this.$( '.setting' ).hide();
    8340 
    8341                 // only proceed with embed if the field contains more than 6 characters
    8342                 if ( url && url.length < 6 ) {
    8343                         return;
    8344                 }
    8345 
    8346                 this.fetch();
    8347         }, 600 ),
    8348 
    8349         fetch: function() {
    8350                 // check if they haven't typed in 500 ms
    8351                 if ( $('#embed-url-field').val() !== this.model.get('url') ) {
    8352                         return;
    8353                 }
    8354 
    8355                 wp.ajax.send( 'parse-embed', {
    8356                         data : {
    8357                                 post_ID: wp.media.view.settings.post.id,
    8358                                 shortcode: '[embed]' + this.model.get('url') + '[/embed]'
    8359                         }
    8360                 } )
    8361                         .done( _.bind( this.renderoEmbed, this ) )
    8362                         .fail( _.bind( this.renderFail, this ) );
    8363         },
    8364 
    8365         renderFail: function () {
    8366                 this.$( '.link-text' ).show();
    8367         },
    8368 
    8369         renderoEmbed: function( response ) {
    8370                 var html = ( response && response.body ) || '';
    8371 
    8372                 if ( html ) {
    8373                         this.$('.embed-container').show().find('.embed-preview').html( html );
    8374                 } else {
    8375                         this.renderFail();
    8376                 }
    8377         }
    8378 });
    8379 
    8380 module.exports = EmbedLink;
    8381 
    8382 
    8383 /***/ }),
    8384 /* 94 */
    8385 /***/ (function(module, exports) {
    8386 
     4481},{}],37:[function(require,module,exports){
    83874482/*globals wp */
    83884483
    … …  
    84194514module.exports = EmbedImage;
    84204515
    8421 
    8422 /***/ }),
    8423 /* 95 */
    8424 /***/ (function(module, exports) {
    8425 
     4516},{}],38:[function(require,module,exports){
     4517/*globals wp, _, jQuery */
     4518
     4519/**
     4520 * wp.media.view.EmbedLink
     4521 *
     4522 * @class
     4523 * @augments wp.media.view.Settings
     4524 * @augments wp.media.View
     4525 * @augments wp.Backbone.View
     4526 * @augments Backbone.View
     4527 */
     4528var $ = jQuery,
     4529        EmbedLink;
     4530
     4531EmbedLink = wp.media.view.Settings.extend({
     4532        className: 'embed-link-settings',
     4533        template:  wp.template('embed-link-settings'),
     4534
     4535        initialize: function() {
     4536                this.spinner = $('<span class="spinner" />');
     4537                this.$el.append( this.spinner[0] );
     4538                this.listenTo( this.model, 'change:url', this.updateoEmbed );
     4539        },
     4540
     4541        updateoEmbed: _.debounce( function() {
     4542                var url = this.model.get( 'url' );
     4543
     4544                // clear out previous results
     4545                this.$('.embed-container').hide().find('.embed-preview').empty();
     4546                this.$( '.setting' ).hide();
     4547
     4548                // only proceed with embed if the field contains more than 6 characters
     4549                if ( url && url.length < 6 ) {
     4550                        return;
     4551                }
     4552
     4553                this.fetch();
     4554        }, 600 ),
     4555
     4556        fetch: function() {
     4557                // check if they haven't typed in 500 ms
     4558                if ( $('#embed-url-field').val() !== this.model.get('url') ) {
     4559                        return;
     4560                }
     4561
     4562                wp.ajax.send( 'parse-embed', {
     4563                        data : {
     4564                                post_ID: wp.media.view.settings.post.id,
     4565                                shortcode: '[embed]' + this.model.get('url') + '[/embed]'
     4566                        }
     4567                } )
     4568                        .done( _.bind( this.renderoEmbed, this ) )
     4569                        .fail( _.bind( this.renderFail, this ) );
     4570        },
     4571
     4572        renderFail: function () {
     4573                this.$( '.link-text' ).show();
     4574        },
     4575
     4576        renderoEmbed: function( response ) {
     4577                var html = ( response && response.body ) || '';
     4578
     4579                if ( html ) {
     4580                        this.$('.embed-container').show().find('.embed-preview').html( html );
     4581                } else {
     4582                        this.renderFail();
     4583                }
     4584        }
     4585});
     4586
     4587module.exports = EmbedLink;
     4588
     4589},{}],39:[function(require,module,exports){
     4590/*globals wp, _, jQuery */
     4591
     4592/**
     4593 * wp.media.view.EmbedUrl
     4594 *
     4595 * @class
     4596 * @augments wp.media.View
     4597 * @augments wp.Backbone.View
     4598 * @augments Backbone.View
     4599 */
     4600var View = wp.media.View,
     4601        $ = jQuery,
     4602        EmbedUrl;
     4603
     4604EmbedUrl = View.extend({
     4605        tagName:   'label',
     4606        className: 'embed-url',
     4607
     4608        events: {
     4609                'input':  'url',
     4610                'keyup':  'url',
     4611                'change': 'url'
     4612        },
     4613
     4614        initialize: function() {
     4615                this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
     4616                this.input = this.$input[0];
     4617
     4618                this.spinner = $('<span class="spinner" />')[0];
     4619                this.$el.append([ this.input, this.spinner ]);
     4620
     4621                this.listenTo( this.model, 'change:url', this.render );
     4622
     4623                if ( this.model.get( 'url' ) ) {
     4624                        _.delay( _.bind( function () {
     4625                                this.model.trigger( 'change:url' );
     4626                        }, this ), 500 );
     4627                }
     4628        },
     4629        /**
     4630         * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
     4631         */
     4632        render: function() {
     4633                var $input = this.$input;
     4634
     4635                if ( $input.is(':focus') ) {
     4636                        return;
     4637                }
     4638
     4639                this.input.value = this.model.get('url') || 'http://';
     4640                /**
     4641                 * Call `render` directly on parent class with passed arguments
     4642                 */
     4643                View.prototype.render.apply( this, arguments );
     4644                return this;
     4645        },
     4646
     4647        ready: function() {
     4648                if ( ! wp.media.isTouchDevice ) {
     4649                        this.focus();
     4650                }
     4651        },
     4652
     4653        url: function( event ) {
     4654                this.model.set( 'url', event.target.value );
     4655        },
     4656
     4657        /**
     4658         * If the input is visible, focus and select its contents.
     4659         */
     4660        focus: function() {
     4661                var $input = this.$input;
     4662                if ( $input.is(':visible') ) {
     4663                        $input.focus()[0].select();
     4664                }
     4665        }
     4666});
     4667
     4668module.exports = EmbedUrl;
     4669
     4670},{}],40:[function(require,module,exports){
     4671/**
     4672 * wp.media.view.FocusManager
     4673 *
     4674 * @class
     4675 * @augments wp.media.View
     4676 * @augments wp.Backbone.View
     4677 * @augments Backbone.View
     4678 */
     4679var FocusManager = wp.media.View.extend({
     4680
     4681        events: {
     4682                'keydown': 'constrainTabbing'
     4683        },
     4684
     4685        focus: function() { // Reset focus on first left menu item
     4686                this.$('.media-menu-item').first().focus();
     4687        },
     4688        /**
     4689         * @param {Object} event
     4690         */
     4691        constrainTabbing: function( event ) {
     4692                var tabbables;
     4693
     4694                // Look for the tab key.
     4695                if ( 9 !== event.keyCode ) {
     4696                        return;
     4697                }
     4698
     4699                // Skip the file input added by Plupload.
     4700                tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
     4701
     4702                // Keep tab focus within media modal while it's open
     4703                if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
     4704                        tabbables.first().focus();
     4705                        return false;
     4706                } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
     4707                        tabbables.last().focus();
     4708                        return false;
     4709                }
     4710        }
     4711
     4712});
     4713
     4714module.exports = FocusManager;
     4715
     4716},{}],41:[function(require,module,exports){
     4717/*globals _, Backbone */
     4718
     4719/**
     4720 * wp.media.view.Frame
     4721 *
     4722 * A frame is a composite view consisting of one or more regions and one or more
     4723 * states.
     4724 *
     4725 * @see wp.media.controller.State
     4726 * @see wp.media.controller.Region
     4727 *
     4728 * @class
     4729 * @augments wp.media.View
     4730 * @augments wp.Backbone.View
     4731 * @augments Backbone.View
     4732 * @mixes wp.media.controller.StateMachine
     4733 */
     4734var Frame = wp.media.View.extend({
     4735        initialize: function() {
     4736                _.defaults( this.options, {
     4737                        mode: [ 'select' ]
     4738                });
     4739                this._createRegions();
     4740                this._createStates();
     4741                this._createModes();
     4742        },
     4743
     4744        _createRegions: function() {
     4745                // Clone the regions array.
     4746                this.regions = this.regions ? this.regions.slice() : [];
     4747
     4748                // Initialize regions.
     4749                _.each( this.regions, function( region ) {
     4750                        this[ region ] = new wp.media.controller.Region({
     4751                                view:     this,
     4752                                id:       region,
     4753                                selector: '.media-frame-' + region
     4754                        });
     4755                }, this );
     4756        },
     4757        /**
     4758         * Create the frame's states.
     4759         *
     4760         * @see wp.media.controller.State
     4761         * @see wp.media.controller.StateMachine
     4762         *
     4763         * @fires wp.media.controller.State#ready
     4764         */
     4765        _createStates: function() {
     4766                // Create the default `states` collection.
     4767                this.states = new Backbone.Collection( null, {
     4768                        model: wp.media.controller.State
     4769                });
     4770
     4771                // Ensure states have a reference to the frame.
     4772                this.states.on( 'add', function( model ) {
     4773                        model.frame = this;
     4774                        model.trigger('ready');
     4775                }, this );
     4776
     4777                if ( this.options.states ) {
     4778                        this.states.add( this.options.states );
     4779                }
     4780        },
     4781
     4782        /**
     4783         * A frame can be in a mode or multiple modes at one time.
     4784         *
     4785         * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
     4786         */
     4787        _createModes: function() {
     4788                // Store active "modes" that the frame is in. Unrelated to region modes.
     4789                this.activeModes = new Backbone.Collection();
     4790                this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
     4791
     4792                _.each( this.options.mode, function( mode ) {
     4793                        this.activateMode( mode );
     4794                }, this );
     4795        },
     4796        /**
     4797         * Reset all states on the frame to their defaults.
     4798         *
     4799         * @returns {wp.media.view.Frame} Returns itself to allow chaining
     4800         */
     4801        reset: function() {
     4802                this.states.invoke( 'trigger', 'reset' );
     4803                return this;
     4804        },
     4805        /**
     4806         * Map activeMode collection events to the frame.
     4807         */
     4808        triggerModeEvents: function( model, collection, options ) {
     4809                var collectionEvent,
     4810                        modeEventMap = {
     4811                                add: 'activate',
     4812                                remove: 'deactivate'
     4813                        },
     4814                        eventToTrigger;
     4815                // Probably a better way to do this.
     4816                _.each( options, function( value, key ) {
     4817                        if ( value ) {
     4818                                collectionEvent = key;
     4819                        }
     4820                } );
     4821
     4822                if ( ! _.has( modeEventMap, collectionEvent ) ) {
     4823                        return;
     4824                }
     4825
     4826                eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
     4827                this.trigger( eventToTrigger );
     4828        },
     4829        /**
     4830         * Activate a mode on the frame.
     4831         *
     4832         * @param string mode Mode ID.
     4833         * @returns {this} Returns itself to allow chaining.
     4834         */
     4835        activateMode: function( mode ) {
     4836                // Bail if the mode is already active.
     4837                if ( this.isModeActive( mode ) ) {
     4838                        return;
     4839                }
     4840                this.activeModes.add( [ { id: mode } ] );
     4841                // Add a CSS class to the frame so elements can be styled for the mode.
     4842                this.$el.addClass( 'mode-' + mode );
     4843
     4844                return this;
     4845        },
     4846        /**
     4847         * Deactivate a mode on the frame.
     4848         *
     4849         * @param string mode Mode ID.
     4850         * @returns {this} Returns itself to allow chaining.
     4851         */
     4852        deactivateMode: function( mode ) {
     4853                // Bail if the mode isn't active.
     4854                if ( ! this.isModeActive( mode ) ) {
     4855                        return this;
     4856                }
     4857                this.activeModes.remove( this.activeModes.where( { id: mode } ) );
     4858                this.$el.removeClass( 'mode-' + mode );
     4859                /**
     4860                 * Frame mode deactivation event.
     4861                 *
     4862                 * @event this#{mode}:deactivate
     4863                 */
     4864                this.trigger( mode + ':deactivate' );
     4865
     4866                return this;
     4867        },
     4868        /**
     4869         * Check if a mode is enabled on the frame.
     4870         *
     4871         * @param  string mode Mode ID.
     4872         * @return bool
     4873         */
     4874        isModeActive: function( mode ) {
     4875                return Boolean( this.activeModes.where( { id: mode } ).length );
     4876        }
     4877});
     4878
     4879// Make the `Frame` a `StateMachine`.
     4880_.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
     4881
     4882module.exports = Frame;
     4883
     4884},{}],42:[function(require,module,exports){
     4885/*globals wp */
     4886
     4887/**
     4888 * wp.media.view.MediaFrame.ImageDetails
     4889 *
     4890 * A media frame for manipulating an image that's already been inserted
     4891 * into a post.
     4892 *
     4893 * @class
     4894 * @augments wp.media.view.MediaFrame.Select
     4895 * @augments wp.media.view.MediaFrame
     4896 * @augments wp.media.view.Frame
     4897 * @augments wp.media.View
     4898 * @augments wp.Backbone.View
     4899 * @augments Backbone.View
     4900 * @mixes wp.media.controller.StateMachine
     4901 */
     4902var Select = wp.media.view.MediaFrame.Select,
     4903        l10n = wp.media.view.l10n,
     4904        ImageDetails;
     4905
     4906ImageDetails = Select.extend({
     4907        defaults: {
     4908                id:      'image',
     4909                url:     '',
     4910                menu:    'image-details',
     4911                content: 'image-details',
     4912                toolbar: 'image-details',
     4913                type:    'link',
     4914                title:    l10n.imageDetailsTitle,
     4915                priority: 120
     4916        },
     4917
     4918        initialize: function( options ) {
     4919                this.image = new wp.media.model.PostImage( options.metadata );
     4920                this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
     4921                Select.prototype.initialize.apply( this, arguments );
     4922        },
     4923
     4924        bindHandlers: function() {
     4925                Select.prototype.bindHandlers.apply( this, arguments );
     4926                this.on( 'menu:create:image-details', this.createMenu, this );
     4927                this.on( 'content:create:image-details', this.imageDetailsContent, this );
     4928                this.on( 'content:render:edit-image', this.editImageContent, this );
     4929                this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
     4930                // override the select toolbar
     4931                this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
     4932        },
     4933
     4934        createStates: function() {
     4935                this.states.add([
     4936                        new wp.media.controller.ImageDetails({
     4937                                image: this.image,
     4938                                editable: false
     4939                        }),
     4940                        new wp.media.controller.ReplaceImage({
     4941                                id: 'replace-image',
     4942                                library: wp.media.query( { type: 'image' } ),
     4943                                image: this.image,
     4944                                multiple:  false,
     4945                                title:     l10n.imageReplaceTitle,
     4946                                toolbar: 'replace',
     4947                                priority:  80,
     4948                                displaySettings: true
     4949                        }),
     4950                        new wp.media.controller.EditImage( {
     4951                                image: this.image,
     4952                                selection: this.options.selection
     4953                        } )
     4954                ]);
     4955        },
     4956
     4957        imageDetailsContent: function( options ) {
     4958                options.view = new wp.media.view.ImageDetails({
     4959                        controller: this,
     4960                        model: this.state().image,
     4961                        attachment: this.state().image.attachment
     4962                });
     4963        },
     4964
     4965        editImageContent: function() {
     4966                var state = this.state(),
     4967                        model = state.get('image'),
     4968                        view;
     4969
     4970                if ( ! model ) {
     4971                        return;
     4972                }
     4973
     4974                view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
     4975
     4976                this.content.set( view );
     4977
     4978                // after bringing in the frame, load the actual editor via an ajax call
     4979                view.loadEditor();
     4980
     4981        },
     4982
     4983        renderImageDetailsToolbar: function() {
     4984                this.toolbar.set( new wp.media.view.Toolbar({
     4985                        controller: this,
     4986                        items: {
     4987                                select: {
     4988                                        style:    'primary',
     4989                                        text:     l10n.update,
     4990                                        priority: 80,
     4991
     4992                                        click: function() {
     4993                                                var controller = this.controller,
     4994                                                        state = controller.state();
     4995
     4996                                                controller.close();
     4997
     4998                                                // not sure if we want to use wp.media.string.image which will create a shortcode or
     4999                                                // perhaps wp.html.string to at least to build the <img />
     5000                                                state.trigger( 'update', controller.image.toJSON() );
     5001
     5002                                                // Restore and reset the default state.
     5003                                                controller.setState( controller.options.state );
     5004                                                controller.reset();
     5005                                        }
     5006                                }
     5007                        }
     5008                }) );
     5009        },
     5010
     5011        renderReplaceImageToolbar: function() {
     5012                var frame = this,
     5013                        lastState = frame.lastState(),
     5014                        previous = lastState && lastState.id;
     5015
     5016                this.toolbar.set( new wp.media.view.Toolbar({
     5017                        controller: this,
     5018                        items: {
     5019                                back: {
     5020                                        text:     l10n.back,
     5021                                        priority: 20,
     5022                                        click:    function() {
     5023                                                if ( previous ) {
     5024                                                        frame.setState( previous );
     5025                                                } else {
     5026                                                        frame.close();
     5027                                                }
     5028                                        }
     5029                                },
     5030
     5031                                replace: {
     5032                                        style:    'primary',
     5033                                        text:     l10n.replace,
     5034                                        priority: 80,
     5035
     5036                                        click: function() {
     5037                                                var controller = this.controller,
     5038                                                        state = controller.state(),
     5039                                                        selection = state.get( 'selection' ),
     5040                                                        attachment = selection.single();
     5041
     5042                                                controller.close();
     5043
     5044                                                controller.image.changeAttachment( attachment, state.display( attachment ) );
     5045
     5046                                                // not sure if we want to use wp.media.string.image which will create a shortcode or
     5047                                                // perhaps wp.html.string to at least to build the <img />
     5048                                                state.trigger( 'replace', controller.image.toJSON() );
     5049
     5050                                                // Restore and reset the default state.
     5051                                                controller.setState( controller.options.state );
     5052                                                controller.reset();
     5053                                        }
     5054                                }
     5055                        }
     5056                }) );
     5057        }
     5058
     5059});
     5060
     5061module.exports = ImageDetails;
     5062
     5063},{}],43:[function(require,module,exports){
     5064/*globals wp, _ */
     5065
     5066/**
     5067 * wp.media.view.MediaFrame.Post
     5068 *
     5069 * The frame for manipulating media on the Edit Post page.
     5070 *
     5071 * @class
     5072 * @augments wp.media.view.MediaFrame.Select
     5073 * @augments wp.media.view.MediaFrame
     5074 * @augments wp.media.view.Frame
     5075 * @augments wp.media.View
     5076 * @augments wp.Backbone.View
     5077 * @augments Backbone.View
     5078 * @mixes wp.media.controller.StateMachine
     5079 */
     5080var Select = wp.media.view.MediaFrame.Select,
     5081        Library = wp.media.controller.Library,
     5082        l10n = wp.media.view.l10n,
     5083        Post;
     5084
     5085Post = Select.extend({
     5086        initialize: function() {
     5087                this.counts = {
     5088                        audio: {
     5089                                count: wp.media.view.settings.attachmentCounts.audio,
     5090                                state: 'playlist'
     5091                        },
     5092                        video: {
     5093                                count: wp.media.view.settings.attachmentCounts.video,
     5094                                state: 'video-playlist'
     5095                        }
     5096                };
     5097
     5098                _.defaults( this.options, {
     5099                        multiple:  true,
     5100                        editing:   false,
     5101                        state:    'insert',
     5102                        metadata:  {}
     5103                });
     5104
     5105                // Call 'initialize' directly on the parent class.
     5106                Select.prototype.initialize.apply( this, arguments );
     5107                this.createIframeStates();
     5108
     5109        },
     5110
     5111        /**
     5112         * Create the default states.
     5113         */
     5114        createStates: function() {
     5115                var options = this.options;
     5116
     5117                this.states.add([
     5118                        // Main states.
     5119                        new Library({
     5120                                id:         'insert',
     5121                                title:      l10n.insertMediaTitle,
     5122                                priority:   20,
     5123                                toolbar:    'main-insert',
     5124                                filterable: 'all',
     5125                                library:    wp.media.query( options.library ),
     5126                                multiple:   options.multiple ? 'reset' : false,
     5127                                editable:   true,
     5128
     5129                                // If the user isn't allowed to edit fields,
     5130                                // can they still edit it locally?
     5131                                allowLocalEdits: true,
     5132
     5133                                // Show the attachment display settings.
     5134                                displaySettings: true,
     5135                                // Update user settings when users adjust the
     5136                                // attachment display settings.
     5137                                displayUserSettings: true
     5138                        }),
     5139
     5140                        new Library({
     5141                                id:         'gallery',
     5142                                title:      l10n.createGalleryTitle,
     5143                                priority:   40,
     5144                                toolbar:    'main-gallery',
     5145                                filterable: 'uploaded',
     5146                                multiple:   'add',
     5147                                editable:   false,
     5148
     5149                                library:  wp.media.query( _.defaults({
     5150                                        type: 'image'
     5151                                }, options.library ) )
     5152                        }),
     5153
     5154                        // Embed states.
     5155                        new wp.media.controller.Embed( { metadata: options.metadata } ),
     5156
     5157                        new wp.media.controller.EditImage( { model: options.editImage } ),
     5158
     5159                        // Gallery states.
     5160                        new wp.media.controller.GalleryEdit({
     5161                                library: options.selection,
     5162                                editing: options.editing,
     5163                                menu:    'gallery'
     5164                        }),
     5165
     5166                        new wp.media.controller.GalleryAdd(),
     5167
     5168                        new Library({
     5169                                id:         'playlist',
     5170                                title:      l10n.createPlaylistTitle,
     5171                                priority:   60,
     5172                                toolbar:    'main-playlist',
     5173                                filterable: 'uploaded',
     5174                                multiple:   'add',
     5175                                editable:   false,
     5176
     5177                                library:  wp.media.query( _.defaults({
     5178                                        type: 'audio'
     5179                                }, options.library ) )
     5180                        }),
     5181
     5182                        // Playlist states.
     5183                        new wp.media.controller.CollectionEdit({
     5184                                type: 'audio',
     5185                                collectionType: 'playlist',
     5186                                title:          l10n.editPlaylistTitle,
     5187                                SettingsView:   wp.media.view.Settings.Playlist,
     5188                                library:        options.selection,
     5189                                editing:        options.editing,
     5190                                menu:           'playlist',
     5191                                dragInfoText:   l10n.playlistDragInfo,
     5192                                dragInfo:       false
     5193                        }),
     5194
     5195                        new wp.media.controller.CollectionAdd({
     5196                                type: 'audio',
     5197                                collectionType: 'playlist',
     5198                                title: l10n.addToPlaylistTitle
     5199                        }),
     5200
     5201                        new Library({
     5202                                id:         'video-playlist',
     5203                                title:      l10n.createVideoPlaylistTitle,
     5204                                priority:   60,
     5205                                toolbar:    'main-video-playlist',
     5206                                filterable: 'uploaded',
     5207                                multiple:   'add',
     5208                                editable:   false,
     5209
     5210                                library:  wp.media.query( _.defaults({
     5211                                        type: 'video'
     5212                                }, options.library ) )
     5213                        }),
     5214
     5215                        new wp.media.controller.CollectionEdit({
     5216                                type: 'video',
     5217                                collectionType: 'playlist',
     5218                                title:          l10n.editVideoPlaylistTitle,
     5219                                SettingsView:   wp.media.view.Settings.Playlist,
     5220                                library:        options.selection,
     5221                                editing:        options.editing,
     5222                                menu:           'video-playlist',
     5223                                dragInfoText:   l10n.videoPlaylistDragInfo,
     5224                                dragInfo:       false
     5225                        }),
     5226
     5227                        new wp.media.controller.CollectionAdd({
     5228                                type: 'video',
     5229                                collectionType: 'playlist',
     5230                                title: l10n.addToVideoPlaylistTitle
     5231                        })
     5232                ]);
     5233
     5234                if ( wp.media.view.settings.post.featuredImageId ) {
     5235                        this.states.add( new wp.media.controller.FeaturedImage() );
     5236                }
     5237        },
     5238
     5239        bindHandlers: function() {
     5240                var handlers, checkCounts;
     5241
     5242                Select.prototype.bindHandlers.apply( this, arguments );
     5243
     5244                this.on( 'activate', this.activate, this );
     5245
     5246                // Only bother checking media type counts if one of the counts is zero
     5247                checkCounts = _.find( this.counts, function( type ) {
     5248                        return type.count === 0;
     5249                } );
     5250
     5251                if ( typeof checkCounts !== 'undefined' ) {
     5252                        this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
     5253                }
     5254
     5255                this.on( 'menu:create:gallery', this.createMenu, this );
     5256                this.on( 'menu:create:playlist', this.createMenu, this );
     5257                this.on( 'menu:create:video-playlist', this.createMenu, this );
     5258                this.on( 'toolbar:create:main-insert', this.createToolbar, this );
     5259                this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
     5260                this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
     5261                this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
     5262                this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
     5263                this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
     5264
     5265                handlers = {
     5266                        menu: {
     5267                                'default': 'mainMenu',
     5268                                'gallery': 'galleryMenu',
     5269                                'playlist': 'playlistMenu',
     5270                                'video-playlist': 'videoPlaylistMenu'
     5271                        },
     5272
     5273                        content: {
     5274                                'embed':          'embedContent',
     5275                                'edit-image':     'editImageContent',
     5276                                'edit-selection': 'editSelectionContent'
     5277                        },
     5278
     5279                        toolbar: {
     5280                                'main-insert':      'mainInsertToolbar',
     5281                                'main-gallery':     'mainGalleryToolbar',
     5282                                'gallery-edit':     'galleryEditToolbar',
     5283                                'gallery-add':      'galleryAddToolbar',
     5284                                'main-playlist':        'mainPlaylistToolbar',
     5285                                'playlist-edit':        'playlistEditToolbar',
     5286                                'playlist-add':         'playlistAddToolbar',
     5287                                'main-video-playlist': 'mainVideoPlaylistToolbar',
     5288                                'video-playlist-edit': 'videoPlaylistEditToolbar',
     5289                                'video-playlist-add': 'videoPlaylistAddToolbar'
     5290                        }
     5291                };
     5292
     5293                _.each( handlers, function( regionHandlers, region ) {
     5294                        _.each( regionHandlers, function( callback, handler ) {
     5295                                this.on( region + ':render:' + handler, this[ callback ], this );
     5296                        }, this );
     5297                }, this );
     5298        },
     5299
     5300        activate: function() {
     5301                // Hide menu items for states tied to particular media types if there are no items
     5302                _.each( this.counts, function( type ) {
     5303                        if ( type.count < 1 ) {
     5304                                this.menuItemVisibility( type.state, 'hide' );
     5305                        }
     5306                }, this );
     5307        },
     5308
     5309        mediaTypeCounts: function( model, attr ) {
     5310                if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
     5311                        this.counts[ attr ].count++;
     5312                        this.menuItemVisibility( this.counts[ attr ].state, 'show' );
     5313                }
     5314        },
     5315
     5316        // Menus
     5317        /**
     5318         * @param {wp.Backbone.View} view
     5319         */
     5320        mainMenu: function( view ) {
     5321                view.set({
     5322                        'library-separator': new wp.media.View({
     5323                                className: 'separator',
     5324                                priority: 100
     5325                        })
     5326                });
     5327        },
     5328
     5329        menuItemVisibility: function( state, visibility ) {
     5330                var menu = this.menu.get();
     5331                if ( visibility === 'hide' ) {
     5332                        menu.hide( state );
     5333                } else if ( visibility === 'show' ) {
     5334                        menu.show( state );
     5335                }
     5336        },
     5337        /**
     5338         * @param {wp.Backbone.View} view
     5339         */
     5340        galleryMenu: function( view ) {
     5341                var lastState = this.lastState(),
     5342                        previous = lastState && lastState.id,
     5343                        frame = this;
     5344
     5345                view.set({
     5346                        cancel: {
     5347                                text:     l10n.cancelGalleryTitle,
     5348                                priority: 20,
     5349                                click:    function() {
     5350                                        if ( previous ) {
     5351                                                frame.setState( previous );
     5352                                        } else {
     5353                                                frame.close();
     5354                                        }
     5355
     5356                                        // Keep focus inside media modal
     5357                                        // after canceling a gallery
     5358                                        this.controller.modal.focusManager.focus();
     5359                                }
     5360                        },
     5361                        separateCancel: new wp.media.View({
     5362                                className: 'separator',
     5363                                priority: 40
     5364                        })
     5365                });
     5366        },
     5367
     5368        playlistMenu: function( view ) {
     5369                var lastState = this.lastState(),
     5370                        previous = lastState && lastState.id,
     5371                        frame = this;
     5372
     5373                view.set({
     5374                        cancel: {
     5375                                text:     l10n.cancelPlaylistTitle,
     5376                                priority: 20,
     5377                                click:    function() {
     5378                                        if ( previous ) {
     5379                                                frame.setState( previous );
     5380                                        } else {
     5381                                                frame.close();
     5382                                        }
     5383                                }
     5384                        },
     5385                        separateCancel: new wp.media.View({
     5386                                className: 'separator',
     5387                                priority: 40
     5388                        })
     5389                });
     5390        },
     5391
     5392        videoPlaylistMenu: function( view ) {
     5393                var lastState = this.lastState(),
     5394                        previous = lastState && lastState.id,
     5395                        frame = this;
     5396
     5397                view.set({
     5398                        cancel: {
     5399                                text:     l10n.cancelVideoPlaylistTitle,
     5400                                priority: 20,
     5401                                click:    function() {
     5402                                        if ( previous ) {
     5403                                                frame.setState( previous );
     5404                                        } else {
     5405                                                frame.close();
     5406                                        }
     5407                                }
     5408                        },
     5409                        separateCancel: new wp.media.View({
     5410                                className: 'separator',
     5411                                priority: 40
     5412                        })
     5413                });
     5414        },
     5415
     5416        // Content
     5417        embedContent: function() {
     5418                var view = new wp.media.view.Embed({
     5419                        controller: this,
     5420                        model:      this.state()
     5421                }).render();
     5422
     5423                this.content.set( view );
     5424
     5425                if ( ! wp.media.isTouchDevice ) {
     5426                        view.url.focus();
     5427                }
     5428        },
     5429
     5430        editSelectionContent: function() {
     5431                var state = this.state(),
     5432                        selection = state.get('selection'),
     5433                        view;
     5434
     5435                view = new wp.media.view.AttachmentsBrowser({
     5436                        controller: this,
     5437                        collection: selection,
     5438                        selection:  selection,
     5439                        model:      state,
     5440                        sortable:   true,
     5441                        search:     false,
     5442                        date:       false,
     5443                        dragInfo:   true,
     5444
     5445                        AttachmentView: wp.media.view.Attachments.EditSelection
     5446                }).render();
     5447
     5448                view.toolbar.set( 'backToLibrary', {
     5449                        text:     l10n.returnToLibrary,
     5450                        priority: -100,
     5451
     5452                        click: function() {
     5453                                this.controller.content.mode('browse');
     5454                        }
     5455                });
     5456
     5457                // Browse our library of attachments.
     5458                this.content.set( view );
     5459
     5460                // Trigger the controller to set focus
     5461                this.trigger( 'edit:selection', this );
     5462        },
     5463
     5464        editImageContent: function() {
     5465                var image = this.state().get('image'),
     5466                        view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
     5467
     5468                this.content.set( view );
     5469
     5470                // after creating the wrapper view, load the actual editor via an ajax call
     5471                view.loadEditor();
     5472
     5473        },
     5474
     5475        // Toolbars
     5476
     5477        /**
     5478         * @param {wp.Backbone.View} view
     5479         */
     5480        selectionStatusToolbar: function( view ) {
     5481                var editable = this.state().get('editable');
     5482
     5483                view.set( 'selection', new wp.media.view.Selection({
     5484                        controller: this,
     5485                        collection: this.state().get('selection'),
     5486                        priority:   -40,
     5487
     5488                        // If the selection is editable, pass the callback to
     5489                        // switch the content mode.
     5490                        editable: editable && function() {
     5491                                this.controller.content.mode('edit-selection');
     5492                        }
     5493                }).render() );
     5494        },
     5495
     5496        /**
     5497         * @param {wp.Backbone.View} view
     5498         */
     5499        mainInsertToolbar: function( view ) {
     5500                var controller = this;
     5501
     5502                this.selectionStatusToolbar( view );
     5503
     5504                view.set( 'insert', {
     5505                        style:    'primary',
     5506                        priority: 80,
     5507                        text:     l10n.insertIntoPost,
     5508                        requires: { selection: true },
     5509
     5510                        /**
     5511                         * @fires wp.media.controller.State#insert
     5512                         */
     5513                        click: function() {
     5514                                var state = controller.state(),
     5515                                        selection = state.get('selection');
     5516
     5517                                controller.close();
     5518                                state.trigger( 'insert', selection ).reset();
     5519                        }
     5520                });
     5521        },
     5522
     5523        /**
     5524         * @param {wp.Backbone.View} view
     5525         */
     5526        mainGalleryToolbar: function( view ) {
     5527                var controller = this;
     5528
     5529                this.selectionStatusToolbar( view );
     5530
     5531                view.set( 'gallery', {
     5532                        style:    'primary',
     5533                        text:     l10n.createNewGallery,
     5534                        priority: 60,
     5535                        requires: { selection: true },
     5536
     5537                        click: function() {
     5538                                var selection = controller.state().get('selection'),
     5539                                        edit = controller.state('gallery-edit'),
     5540                                        models = selection.where({ type: 'image' });
     5541
     5542                                edit.set( 'library', new wp.media.model.Selection( models, {
     5543                                        props:    selection.props.toJSON(),
     5544                                        multiple: true
     5545                                }) );
     5546
     5547                                this.controller.setState('gallery-edit');
     5548
     5549                                // Keep focus inside media modal
     5550                                // after jumping to gallery view
     5551                                this.controller.modal.focusManager.focus();
     5552                        }
     5553                });
     5554        },
     5555
     5556        mainPlaylistToolbar: function( view ) {
     5557                var controller = this;
     5558
     5559                this.selectionStatusToolbar( view );
     5560
     5561                view.set( 'playlist', {
     5562                        style:    'primary',
     5563                        text:     l10n.createNewPlaylist,
     5564                        priority: 100,
     5565                        requires: { selection: true },
     5566
     5567                        click: function() {
     5568                                var selection = controller.state().get('selection'),
     5569                                        edit = controller.state('playlist-edit'),
     5570                                        models = selection.where({ type: 'audio' });
     5571
     5572                                edit.set( 'library', new wp.media.model.Selection( models, {
     5573                                        props:    selection.props.toJSON(),
     5574                                        multiple: true
     5575                                }) );
     5576
     5577                                this.controller.setState('playlist-edit');
     5578
     5579                                // Keep focus inside media modal
     5580                                // after jumping to playlist view
     5581                                this.controller.modal.focusManager.focus();
     5582                        }
     5583                });
     5584        },
     5585
     5586        mainVideoPlaylistToolbar: function( view ) {
     5587                var controller = this;
     5588
     5589                this.selectionStatusToolbar( view );
     5590
     5591                view.set( 'video-playlist', {
     5592                        style:    'primary',
     5593                        text:     l10n.createNewVideoPlaylist,
     5594                        priority: 100,
     5595                        requires: { selection: true },
     5596
     5597                        click: function() {
     5598                                var selection = controller.state().get('selection'),
     5599                                        edit = controller.state('video-playlist-edit'),
     5600                                        models = selection.where({ type: 'video' });
     5601
     5602                                edit.set( 'library', new wp.media.model.Selection( models, {
     5603                                        props:    selection.props.toJSON(),
     5604                                        multiple: true
     5605                                }) );
     5606
     5607                                this.controller.setState('video-playlist-edit');
     5608
     5609                                // Keep focus inside media modal
     5610                                // after jumping to video playlist view
     5611                                this.controller.modal.focusManager.focus();
     5612                        }
     5613                });
     5614        },
     5615
     5616        featuredImageToolbar: function( toolbar ) {
     5617                this.createSelectToolbar( toolbar, {
     5618                        text:  l10n.setFeaturedImage,
     5619                        state: this.options.state
     5620                });
     5621        },
     5622
     5623        mainEmbedToolbar: function( toolbar ) {
     5624                toolbar.view = new wp.media.view.Toolbar.Embed({
     5625                        controller: this
     5626                });
     5627        },
     5628
     5629        galleryEditToolbar: function() {
     5630                var editing = this.state().get('editing');
     5631                this.toolbar.set( new wp.media.view.Toolbar({
     5632                        controller: this,
     5633                        items: {
     5634                                insert: {
     5635                                        style:    'primary',
     5636                                        text:     editing ? l10n.updateGallery : l10n.insertGallery,
     5637                                        priority: 80,
     5638                                        requires: { library: true },
     5639
     5640                                        /**
     5641                                         * @fires wp.media.controller.State#update
     5642                                         */
     5643                                        click: function() {
     5644                                                var controller = this.controller,
     5645                                                        state = controller.state();
     5646
     5647                                                controller.close();
     5648                                                state.trigger( 'update', state.get('library') );
     5649
     5650                                                // Restore and reset the default state.
     5651                                                controller.setState( controller.options.state );
     5652                                                controller.reset();
     5653                                        }
     5654                                }
     5655                        }
     5656                }) );
     5657        },
     5658
     5659        galleryAddToolbar: function() {
     5660                this.toolbar.set( new wp.media.view.Toolbar({
     5661                        controller: this,
     5662                        items: {
     5663                                insert: {
     5664                                        style:    'primary',
     5665                                        text:     l10n.addToGallery,
     5666                                        priority: 80,
     5667                                        requires: { selection: true },
     5668
     5669                                        /**
     5670                                         * @fires wp.media.controller.State#reset
     5671                                         */
     5672                                        click: function() {
     5673                                                var controller = this.controller,
     5674                                                        state = controller.state(),
     5675                                                        edit = controller.state('gallery-edit');
     5676
     5677                                                edit.get('library').add( state.get('selection').models );
     5678                                                state.trigger('reset');
     5679                                                controller.setState('gallery-edit');
     5680                                        }
     5681                                }
     5682                        }
     5683                }) );
     5684        },
     5685
     5686        playlistEditToolbar: function() {
     5687                var editing = this.state().get('editing');
     5688                this.toolbar.set( new wp.media.view.Toolbar({
     5689                        controller: this,
     5690                        items: {
     5691                                insert: {
     5692                                        style:    'primary',
     5693                                        text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
     5694                                        priority: 80,
     5695                                        requires: { library: true },
     5696
     5697                                        /**
     5698                                         * @fires wp.media.controller.State#update
     5699                                         */
     5700                                        click: function() {
     5701                                                var controller = this.controller,
     5702                                                        state = controller.state();
     5703
     5704                                                controller.close();
     5705                                                state.trigger( 'update', state.get('library') );
     5706
     5707                                                // Restore and reset the default state.
     5708                                                controller.setState( controller.options.state );
     5709                                                controller.reset();
     5710                                        }
     5711                                }
     5712                        }
     5713                }) );
     5714        },
     5715
     5716        playlistAddToolbar: function() {
     5717                this.toolbar.set( new wp.media.view.Toolbar({
     5718                        controller: this,
     5719                        items: {
     5720                                insert: {
     5721                                        style:    'primary',
     5722                                        text:     l10n.addToPlaylist,
     5723                                        priority: 80,
     5724                                        requires: { selection: true },
     5725
     5726                                        /**
     5727                                         * @fires wp.media.controller.State#reset
     5728                                         */
     5729                                        click: function() {
     5730                                                var controller = this.controller,
     5731                                                        state = controller.state(),
     5732                                                        edit = controller.state('playlist-edit');
     5733
     5734                                                edit.get('library').add( state.get('selection').models );
     5735                                                state.trigger('reset');
     5736                                                controller.setState('playlist-edit');
     5737                                        }
     5738                                }
     5739                        }
     5740                }) );
     5741        },
     5742
     5743        videoPlaylistEditToolbar: function() {
     5744                var editing = this.state().get('editing');
     5745                this.toolbar.set( new wp.media.view.Toolbar({
     5746                        controller: this,
     5747                        items: {
     5748                                insert: {
     5749                                        style:    'primary',
     5750                                        text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
     5751                                        priority: 140,
     5752                                        requires: { library: true },
     5753
     5754                                        click: function() {
     5755                                                var controller = this.controller,
     5756                                                        state = controller.state(),
     5757                                                        library = state.get('library');
     5758
     5759                                                library.type = 'video';
     5760
     5761                                                controller.close();
     5762                                                state.trigger( 'update', library );
     5763
     5764                                                // Restore and reset the default state.
     5765                                                controller.setState( controller.options.state );
     5766                                                controller.reset();
     5767                                        }
     5768                                }
     5769                        }
     5770                }) );
     5771        },
     5772
     5773        videoPlaylistAddToolbar: function() {
     5774                this.toolbar.set( new wp.media.view.Toolbar({
     5775                        controller: this,
     5776                        items: {
     5777                                insert: {
     5778                                        style:    'primary',
     5779                                        text:     l10n.addToVideoPlaylist,
     5780                                        priority: 140,
     5781                                        requires: { selection: true },
     5782
     5783                                        click: function() {
     5784                                                var controller = this.controller,
     5785                                                        state = controller.state(),
     5786                                                        edit = controller.state('video-playlist-edit');
     5787
     5788                                                edit.get('library').add( state.get('selection').models );
     5789                                                state.trigger('reset');
     5790                                                controller.setState('video-playlist-edit');
     5791                                        }
     5792                                }
     5793                        }
     5794                }) );
     5795        }
     5796});
     5797
     5798module.exports = Post;
     5799
     5800},{}],44:[function(require,module,exports){
     5801/*globals wp, _ */
     5802
     5803/**
     5804 * wp.media.view.MediaFrame.Select
     5805 *
     5806 * A frame for selecting an item or items from the media library.
     5807 *
     5808 * @class
     5809 * @augments wp.media.view.MediaFrame
     5810 * @augments wp.media.view.Frame
     5811 * @augments wp.media.View
     5812 * @augments wp.Backbone.View
     5813 * @augments Backbone.View
     5814 * @mixes wp.media.controller.StateMachine
     5815 */
     5816
     5817var MediaFrame = wp.media.view.MediaFrame,
     5818        l10n = wp.media.view.l10n,
     5819        Select;
     5820
     5821Select = MediaFrame.extend({
     5822        initialize: function() {
     5823                // Call 'initialize' directly on the parent class.
     5824                MediaFrame.prototype.initialize.apply( this, arguments );
     5825
     5826                _.defaults( this.options, {
     5827                        selection: [],
     5828                        library:   {},
     5829                        multiple:  false,
     5830                        state:    'library'
     5831                });
     5832
     5833                this.createSelection();
     5834                this.createStates();
     5835                this.bindHandlers();
     5836        },
     5837
     5838        /**
     5839         * Attach a selection collection to the frame.
     5840         *
     5841         * A selection is a collection of attachments used for a specific purpose
     5842         * by a media frame. e.g. Selecting an attachment (or many) to insert into
     5843         * post content.
     5844         *
     5845         * @see media.model.Selection
     5846         */
     5847        createSelection: function() {
     5848                var selection = this.options.selection;
     5849
     5850                if ( ! (selection instanceof wp.media.model.Selection) ) {
     5851                        this.options.selection = new wp.media.model.Selection( selection, {
     5852                                multiple: this.options.multiple
     5853                        });
     5854                }
     5855
     5856                this._selection = {
     5857                        attachments: new wp.media.model.Attachments(),
     5858                        difference: []
     5859                };
     5860        },
     5861
     5862        /**
     5863         * Create the default states on the frame.
     5864         */
     5865        createStates: function() {
     5866                var options = this.options;
     5867
     5868                if ( this.options.states ) {
     5869                        return;
     5870                }
     5871
     5872                // Add the default states.
     5873                this.states.add([
     5874                        // Main states.
     5875                        new wp.media.controller.Library({
     5876                                library:   wp.media.query( options.library ),
     5877                                multiple:  options.multiple,
     5878                                title:     options.title,
     5879                                priority:  20
     5880                        })
     5881                ]);
     5882        },
     5883
     5884        /**
     5885         * Bind region mode event callbacks.
     5886         *
     5887         * @see media.controller.Region.render
     5888         */
     5889        bindHandlers: function() {
     5890                this.on( 'router:create:browse', this.createRouter, this );
     5891                this.on( 'router:render:browse', this.browseRouter, this );
     5892                this.on( 'content:create:browse', this.browseContent, this );
     5893                this.on( 'content:render:upload', this.uploadContent, this );
     5894                this.on( 'toolbar:create:select', this.createSelectToolbar, this );
     5895        },
     5896
     5897        /**
     5898         * Render callback for the router region in the `browse` mode.
     5899         *
     5900         * @param {wp.media.view.Router} routerView
     5901         */
     5902        browseRouter: function( routerView ) {
     5903                routerView.set({
     5904                        upload: {
     5905                                text:     l10n.uploadFilesTitle,
     5906                                priority: 20
     5907                        },
     5908                        browse: {
     5909                                text:     l10n.mediaLibraryTitle,
     5910                                priority: 40
     5911                        }
     5912                });
     5913        },
     5914
     5915        /**
     5916         * Render callback for the content region in the `browse` mode.
     5917         *
     5918         * @param {wp.media.controller.Region} contentRegion
     5919         */
     5920        browseContent: function( contentRegion ) {
     5921                var state = this.state();
     5922
     5923                this.$el.removeClass('hide-toolbar');
     5924
     5925                // Browse our library of attachments.
     5926                contentRegion.view = new wp.media.view.AttachmentsBrowser({
     5927                        controller: this,
     5928                        collection: state.get('library'),
     5929                        selection:  state.get('selection'),
     5930                        model:      state,
     5931                        sortable:   state.get('sortable'),
     5932                        search:     state.get('searchable'),
     5933                        filters:    state.get('filterable'),
     5934                        date:       state.get('date'),
     5935                        display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
     5936                        dragInfo:   state.get('dragInfo'),
     5937
     5938                        idealColumnWidth: state.get('idealColumnWidth'),
     5939                        suggestedWidth:   state.get('suggestedWidth'),
     5940                        suggestedHeight:  state.get('suggestedHeight'),
     5941
     5942                        AttachmentView: state.get('AttachmentView')
     5943                });
     5944        },
     5945
     5946        /**
     5947         * Render callback for the content region in the `upload` mode.
     5948         */
     5949        uploadContent: function() {
     5950                this.$el.removeClass( 'hide-toolbar' );
     5951                this.content.set( new wp.media.view.UploaderInline({
     5952                        controller: this
     5953                }) );
     5954        },
     5955
     5956        /**
     5957         * Toolbars
     5958         *
     5959         * @param {Object} toolbar
     5960         * @param {Object} [options={}]
     5961         * @this wp.media.controller.Region
     5962         */
     5963        createSelectToolbar: function( toolbar, options ) {
     5964                options = options || this.options.button || {};
     5965                options.controller = this;
     5966
     5967                toolbar.view = new wp.media.view.Toolbar.Select( options );
     5968        }
     5969});
     5970
     5971module.exports = Select;
     5972
     5973},{}],45:[function(require,module,exports){
     5974/**
     5975 * wp.media.view.Iframe
     5976 *
     5977 * @class
     5978 * @augments wp.media.View
     5979 * @augments wp.Backbone.View
     5980 * @augments Backbone.View
     5981 */
     5982var Iframe = wp.media.View.extend({
     5983        className: 'media-iframe',
     5984        /**
     5985         * @returns {wp.media.view.Iframe} Returns itself to allow chaining
     5986         */
     5987        render: function() {
     5988                this.views.detach();
     5989                this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
     5990                this.views.render();
     5991                return this;
     5992        }
     5993});
     5994
     5995module.exports = Iframe;
     5996
     5997},{}],46:[function(require,module,exports){
    84265998/*globals wp, _, jQuery */
    84275999
    … …  
    85936165module.exports = ImageDetails;
    85946166
    8595 
    8596 /***/ }),
    8597 /* 96 */
    8598 /***/ (function(module, exports) {
    8599 
     6167},{}],47:[function(require,module,exports){
     6168/**
     6169 * wp.media.view.Label
     6170 *
     6171 * @class
     6172 * @augments wp.media.View
     6173 * @augments wp.Backbone.View
     6174 * @augments Backbone.View
     6175 */
     6176var Label = wp.media.View.extend({
     6177        tagName: 'label',
     6178        className: 'screen-reader-text',
     6179
     6180        initialize: function() {
     6181                this.value = this.options.value;
     6182        },
     6183
     6184        render: function() {
     6185                this.$el.html( this.value );
     6186
     6187                return this;
     6188        }
     6189});
     6190
     6191module.exports = Label;
     6192
     6193},{}],48:[function(require,module,exports){
    86006194/*globals wp, _, jQuery */
    86016195
    86026196/**
    8603  * wp.media.view.Cropper
    8604  *
    8605  * Uses the imgAreaSelect plugin to allow a user to crop an image.
    8606  *
    8607  * Takes imgAreaSelect options from
    8608  * wp.customize.HeaderControl.calculateImageSelectOptions via
    8609  * wp.customize.HeaderControl.openMM.
     6197 * wp.media.view.MediaFrame
     6198 *
     6199 * The frame used to create the media modal.
     6200 *
     6201 * @class
     6202 * @augments wp.media.view.Frame
     6203 * @augments wp.media.View
     6204 * @augments wp.Backbone.View
     6205 * @augments Backbone.View
     6206 * @mixes wp.media.controller.StateMachine
     6207 */
     6208var Frame = wp.media.view.Frame,
     6209        $ = jQuery,
     6210        MediaFrame;
     6211
     6212MediaFrame = Frame.extend({
     6213        className: 'media-frame',
     6214        template:  wp.template('media-frame'),
     6215        regions:   ['menu','title','content','toolbar','router'],
     6216
     6217        events: {
     6218                'click div.media-frame-title h1': 'toggleMenu'
     6219        },
     6220
     6221        /**
     6222         * @global wp.Uploader
     6223         */
     6224        initialize: function() {
     6225                Frame.prototype.initialize.apply( this, arguments );
     6226
     6227                _.defaults( this.options, {
     6228                        title:    '',
     6229                        modal:    true,
     6230                        uploader: true
     6231                });
     6232
     6233                // Ensure core UI is enabled.
     6234                this.$el.addClass('wp-core-ui');
     6235
     6236                // Initialize modal container view.
     6237                if ( this.options.modal ) {
     6238                        this.modal = new wp.media.view.Modal({
     6239                                controller: this,
     6240                                title:      this.options.title
     6241                        });
     6242
     6243                        this.modal.content( this );
     6244                }
     6245
     6246                // Force the uploader off if the upload limit has been exceeded or
     6247                // if the browser isn't supported.
     6248                if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     6249                        this.options.uploader = false;
     6250                }
     6251
     6252                // Initialize window-wide uploader.
     6253                if ( this.options.uploader ) {
     6254                        this.uploader = new wp.media.view.UploaderWindow({
     6255                                controller: this,
     6256                                uploader: {
     6257                                        dropzone:  this.modal ? this.modal.$el : this.$el,
     6258                                        container: this.$el
     6259                                }
     6260                        });
     6261                        this.views.set( '.media-frame-uploader', this.uploader );
     6262                }
     6263
     6264                this.on( 'attach', _.bind( this.views.ready, this.views ), this );
     6265
     6266                // Bind default title creation.
     6267                this.on( 'title:create:default', this.createTitle, this );
     6268                this.title.mode('default');
     6269
     6270                this.on( 'title:render', function( view ) {
     6271                        view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
     6272                });
     6273
     6274                // Bind default menu.
     6275                this.on( 'menu:create:default', this.createMenu, this );
     6276        },
     6277        /**
     6278         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     6279         */
     6280        render: function() {
     6281                // Activate the default state if no active state exists.
     6282                if ( ! this.state() && this.options.state ) {
     6283                        this.setState( this.options.state );
     6284                }
     6285                /**
     6286                 * call 'render' directly on the parent class
     6287                 */
     6288                return Frame.prototype.render.apply( this, arguments );
     6289        },
     6290        /**
     6291         * @param {Object} title
     6292         * @this wp.media.controller.Region
     6293         */
     6294        createTitle: function( title ) {
     6295                title.view = new wp.media.View({
     6296                        controller: this,
     6297                        tagName: 'h1'
     6298                });
     6299        },
     6300        /**
     6301         * @param {Object} menu
     6302         * @this wp.media.controller.Region
     6303         */
     6304        createMenu: function( menu ) {
     6305                menu.view = new wp.media.view.Menu({
     6306                        controller: this
     6307                });
     6308        },
     6309
     6310        toggleMenu: function() {
     6311                this.$el.find( '.media-menu' ).toggleClass( 'visible' );
     6312        },
     6313
     6314        /**
     6315         * @param {Object} toolbar
     6316         * @this wp.media.controller.Region
     6317         */
     6318        createToolbar: function( toolbar ) {
     6319                toolbar.view = new wp.media.view.Toolbar({
     6320                        controller: this
     6321                });
     6322        },
     6323        /**
     6324         * @param {Object} router
     6325         * @this wp.media.controller.Region
     6326         */
     6327        createRouter: function( router ) {
     6328                router.view = new wp.media.view.Router({
     6329                        controller: this
     6330                });
     6331        },
     6332        /**
     6333         * @param {Object} options
     6334         */
     6335        createIframeStates: function( options ) {
     6336                var settings = wp.media.view.settings,
     6337                        tabs = settings.tabs,
     6338                        tabUrl = settings.tabUrl,
     6339                        $postId;
     6340
     6341                if ( ! tabs || ! tabUrl ) {
     6342                        return;
     6343                }
     6344
     6345                // Add the post ID to the tab URL if it exists.
     6346                $postId = $('#post_ID');
     6347                if ( $postId.length ) {
     6348                        tabUrl += '&post_id=' + $postId.val();
     6349                }
     6350
     6351                // Generate the tab states.
     6352                _.each( tabs, function( title, id ) {
     6353                        this.state( 'iframe:' + id ).set( _.defaults({
     6354                                tab:     id,
     6355                                src:     tabUrl + '&tab=' + id,
     6356                                title:   title,
     6357                                content: 'iframe',
     6358                                menu:    'default'
     6359                        }, options ) );
     6360                }, this );
     6361
     6362                this.on( 'content:create:iframe', this.iframeContent, this );
     6363                this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
     6364                this.on( 'menu:render:default', this.iframeMenu, this );
     6365                this.on( 'open', this.hijackThickbox, this );
     6366                this.on( 'close', this.restoreThickbox, this );
     6367        },
     6368
     6369        /**
     6370         * @param {Object} content
     6371         * @this wp.media.controller.Region
     6372         */
     6373        iframeContent: function( content ) {
     6374                this.$el.addClass('hide-toolbar');
     6375                content.view = new wp.media.view.Iframe({
     6376                        controller: this
     6377                });
     6378        },
     6379
     6380        iframeContentCleanup: function() {
     6381                this.$el.removeClass('hide-toolbar');
     6382        },
     6383
     6384        iframeMenu: function( view ) {
     6385                var views = {};
     6386
     6387                if ( ! view ) {
     6388                        return;
     6389                }
     6390
     6391                _.each( wp.media.view.settings.tabs, function( title, id ) {
     6392                        views[ 'iframe:' + id ] = {
     6393                                text: this.state( 'iframe:' + id ).get('title'),
     6394                                priority: 200
     6395                        };
     6396                }, this );
     6397
     6398                view.set( views );
     6399        },
     6400
     6401        hijackThickbox: function() {
     6402                var frame = this;
     6403
     6404                if ( ! window.tb_remove || this._tb_remove ) {
     6405                        return;
     6406                }
     6407
     6408                this._tb_remove = window.tb_remove;
     6409                window.tb_remove = function() {
     6410                        frame.close();
     6411                        frame.reset();
     6412                        frame.setState( frame.options.state );
     6413                        frame._tb_remove.call( window );
     6414                };
     6415        },
     6416
     6417        restoreThickbox: function() {
     6418                if ( ! this._tb_remove ) {
     6419                        return;
     6420                }
     6421
     6422                window.tb_remove = this._tb_remove;
     6423                delete this._tb_remove;
     6424        }
     6425});
     6426
     6427// Map some of the modal's methods to the frame.
     6428_.each(['open','close','attach','detach','escape'], function( method ) {
     6429        /**
     6430         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     6431         */
     6432        MediaFrame.prototype[ method ] = function() {
     6433                if ( this.modal ) {
     6434                        this.modal[ method ].apply( this.modal, arguments );
     6435                }
     6436                return this;
     6437        };
     6438});
     6439
     6440module.exports = MediaFrame;
     6441
     6442},{}],49:[function(require,module,exports){
     6443/*globals jQuery */
     6444
     6445/**
     6446 * wp.media.view.MenuItem
     6447 *
     6448 * @class
     6449 * @augments wp.media.View
     6450 * @augments wp.Backbone.View
     6451 * @augments Backbone.View
     6452 */
     6453var $ = jQuery,
     6454        MenuItem;
     6455
     6456MenuItem = wp.media.View.extend({
     6457        tagName:   'a',
     6458        className: 'media-menu-item',
     6459
     6460        attributes: {
     6461                href: '#'
     6462        },
     6463
     6464        events: {
     6465                'click': '_click'
     6466        },
     6467        /**
     6468         * @param {Object} event
     6469         */
     6470        _click: function( event ) {
     6471                var clickOverride = this.options.click;
     6472
     6473                if ( event ) {
     6474                        event.preventDefault();
     6475                }
     6476
     6477                if ( clickOverride ) {
     6478                        clickOverride.call( this );
     6479                } else {
     6480                        this.click();
     6481                }
     6482
     6483                // When selecting a tab along the left side,
     6484                // focus should be transferred into the main panel
     6485                if ( ! wp.media.isTouchDevice ) {
     6486                        $('.media-frame-content input').first().focus();
     6487                }
     6488        },
     6489
     6490        click: function() {
     6491                var state = this.options.state;
     6492
     6493                if ( state ) {
     6494                        this.controller.setState( state );
     6495                        this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
     6496                }
     6497        },
     6498        /**
     6499         * @returns {wp.media.view.MenuItem} returns itself to allow chaining
     6500         */
     6501        render: function() {
     6502                var options = this.options;
     6503
     6504                if ( options.text ) {
     6505                        this.$el.text( options.text );
     6506                } else if ( options.html ) {
     6507                        this.$el.html( options.html );
     6508                }
     6509
     6510                return this;
     6511        }
     6512});
     6513
     6514module.exports = MenuItem;
     6515
     6516},{}],50:[function(require,module,exports){
     6517/**
     6518 * wp.media.view.Menu
     6519 *
     6520 * @class
     6521 * @augments wp.media.view.PriorityList
     6522 * @augments wp.media.View
     6523 * @augments wp.Backbone.View
     6524 * @augments Backbone.View
     6525 */
     6526var MenuItem = wp.media.view.MenuItem,
     6527        PriorityList = wp.media.view.PriorityList,
     6528        Menu;
     6529
     6530Menu = PriorityList.extend({
     6531        tagName:   'div',
     6532        className: 'media-menu',
     6533        property:  'state',
     6534        ItemView:  MenuItem,
     6535        region:    'menu',
     6536
     6537        /* TODO: alternatively hide on any click anywhere
     6538        events: {
     6539                'click': 'click'
     6540        },
     6541
     6542        click: function() {
     6543                this.$el.removeClass( 'visible' );
     6544        },
     6545        */
     6546
     6547        /**
     6548         * @param {Object} options
     6549         * @param {string} id
     6550         * @returns {wp.media.View}
     6551         */
     6552        toView: function( options, id ) {
     6553                options = options || {};
     6554                options[ this.property ] = options[ this.property ] || id;
     6555                return new this.ItemView( options ).render();
     6556        },
     6557
     6558        ready: function() {
     6559                /**
     6560                 * call 'ready' directly on the parent class
     6561                 */
     6562                PriorityList.prototype.ready.apply( this, arguments );
     6563                this.visibility();
     6564        },
     6565
     6566        set: function() {
     6567                /**
     6568                 * call 'set' directly on the parent class
     6569                 */
     6570                PriorityList.prototype.set.apply( this, arguments );
     6571                this.visibility();
     6572        },
     6573
     6574        unset: function() {
     6575                /**
     6576                 * call 'unset' directly on the parent class
     6577                 */
     6578                PriorityList.prototype.unset.apply( this, arguments );
     6579                this.visibility();
     6580        },
     6581
     6582        visibility: function() {
     6583                var region = this.region,
     6584                        view = this.controller[ region ].get(),
     6585                        views = this.views.get(),
     6586                        hide = ! views || views.length < 2;
     6587
     6588                if ( this === view ) {
     6589                        this.controller.$el.toggleClass( 'hide-' + region, hide );
     6590                }
     6591        },
     6592        /**
     6593         * @param {string} id
     6594         */
     6595        select: function( id ) {
     6596                var view = this.get( id );
     6597
     6598                if ( ! view ) {
     6599                        return;
     6600                }
     6601
     6602                this.deselect();
     6603                view.$el.addClass('active');
     6604        },
     6605
     6606        deselect: function() {
     6607                this.$el.children().removeClass('active');
     6608        },
     6609
     6610        hide: function( id ) {
     6611                var view = this.get( id );
     6612
     6613                if ( ! view ) {
     6614                        return;
     6615                }
     6616
     6617                view.$el.addClass('hidden');
     6618        },
     6619
     6620        show: function( id ) {
     6621                var view = this.get( id );
     6622
     6623                if ( ! view ) {
     6624                        return;
     6625                }
     6626
     6627                view.$el.removeClass('hidden');
     6628        }
     6629});
     6630
     6631module.exports = Menu;
     6632
     6633},{}],51:[function(require,module,exports){
     6634/*globals wp, _, jQuery */
     6635
     6636/**
     6637 * wp.media.view.Modal
     6638 *
     6639 * A modal view, which the media modal uses as its default container.
     6640 *
     6641 * @class
     6642 * @augments wp.media.View
     6643 * @augments wp.Backbone.View
     6644 * @augments Backbone.View
     6645 */
     6646var $ = jQuery,
     6647        Modal;
     6648
     6649Modal = wp.media.View.extend({
     6650        tagName:  'div',
     6651        template: wp.template('media-modal'),
     6652
     6653        attributes: {
     6654                tabindex: 0
     6655        },
     6656
     6657        events: {
     6658                'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
     6659                'keydown': 'keydown'
     6660        },
     6661
     6662        initialize: function() {
     6663                _.defaults( this.options, {
     6664                        container: document.body,
     6665                        title:     '',
     6666                        propagate: true,
     6667                        freeze:    true
     6668                });
     6669
     6670                this.focusManager = new wp.media.view.FocusManager({
     6671                        el: this.el
     6672                });
     6673        },
     6674        /**
     6675         * @returns {Object}
     6676         */
     6677        prepare: function() {
     6678                return {
     6679                        title: this.options.title
     6680                };
     6681        },
     6682
     6683        /**
     6684         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6685         */
     6686        attach: function() {
     6687                if ( this.views.attached ) {
     6688                        return this;
     6689                }
     6690
     6691                if ( ! this.views.rendered ) {
     6692                        this.render();
     6693                }
     6694
     6695                this.$el.appendTo( this.options.container );
     6696
     6697                // Manually mark the view as attached and trigger ready.
     6698                this.views.attached = true;
     6699                this.views.ready();
     6700
     6701                return this.propagate('attach');
     6702        },
     6703
     6704        /**
     6705         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6706         */
     6707        detach: function() {
     6708                if ( this.$el.is(':visible') ) {
     6709                        this.close();
     6710                }
     6711
     6712                this.$el.detach();
     6713                this.views.attached = false;
     6714                return this.propagate('detach');
     6715        },
     6716
     6717        /**
     6718         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6719         */
     6720        open: function() {
     6721                var $el = this.$el,
     6722                        options = this.options,
     6723                        mceEditor;
     6724
     6725                if ( $el.is(':visible') ) {
     6726                        return this;
     6727                }
     6728
     6729                if ( ! this.views.attached ) {
     6730                        this.attach();
     6731                }
     6732
     6733                // If the `freeze` option is set, record the window's scroll position.
     6734                if ( options.freeze ) {
     6735                        this._freeze = {
     6736                                scrollTop: $( window ).scrollTop()
     6737                        };
     6738                }
     6739
     6740                // Disable page scrolling.
     6741                $( 'body' ).addClass( 'modal-open' );
     6742
     6743                $el.show();
     6744
     6745                // Try to close the onscreen keyboard
     6746                if ( 'ontouchend' in document ) {
     6747                        if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
     6748                                mceEditor.iframeElement.focus();
     6749                                mceEditor.iframeElement.blur();
     6750
     6751                                setTimeout( function() {
     6752                                        mceEditor.iframeElement.blur();
     6753                                }, 100 );
     6754                        }
     6755                }
     6756
     6757                this.$el.focus();
     6758
     6759                return this.propagate('open');
     6760        },
     6761
     6762        /**
     6763         * @param {Object} options
     6764         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6765         */
     6766        close: function( options ) {
     6767                var freeze = this._freeze;
     6768
     6769                if ( ! this.views.attached || ! this.$el.is(':visible') ) {
     6770                        return this;
     6771                }
     6772
     6773                // Enable page scrolling.
     6774                $( 'body' ).removeClass( 'modal-open' );
     6775
     6776                // Hide modal and remove restricted media modal tab focus once it's closed
     6777                this.$el.hide().undelegate( 'keydown' );
     6778
     6779                // Put focus back in useful location once modal is closed
     6780                $('#wpbody-content').focus();
     6781
     6782                this.propagate('close');
     6783
     6784                // If the `freeze` option is set, restore the container's scroll position.
     6785                if ( freeze ) {
     6786                        $( window ).scrollTop( freeze.scrollTop );
     6787                }
     6788
     6789                if ( options && options.escape ) {
     6790                        this.propagate('escape');
     6791                }
     6792
     6793                return this;
     6794        },
     6795        /**
     6796         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6797         */
     6798        escape: function() {
     6799                return this.close({ escape: true });
     6800        },
     6801        /**
     6802         * @param {Object} event
     6803         */
     6804        escapeHandler: function( event ) {
     6805                event.preventDefault();
     6806                this.escape();
     6807        },
     6808
     6809        /**
     6810         * @param {Array|Object} content Views to register to '.media-modal-content'
     6811         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6812         */
     6813        content: function( content ) {
     6814                this.views.set( '.media-modal-content', content );
     6815                return this;
     6816        },
     6817
     6818        /**
     6819         * Triggers a modal event and if the `propagate` option is set,
     6820         * forwards events to the modal's controller.
     6821         *
     6822         * @param {string} id
     6823         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6824         */
     6825        propagate: function( id ) {
     6826                this.trigger( id );
     6827
     6828                if ( this.options.propagate ) {
     6829                        this.controller.trigger( id );
     6830                }
     6831
     6832                return this;
     6833        },
     6834        /**
     6835         * @param {Object} event
     6836         */
     6837        keydown: function( event ) {
     6838                // Close the modal when escape is pressed.
     6839                if ( 27 === event.which && this.$el.is(':visible') ) {
     6840                        this.escape();
     6841                        event.stopImmediatePropagation();
     6842                }
     6843        }
     6844});
     6845
     6846module.exports = Modal;
     6847
     6848},{}],52:[function(require,module,exports){
     6849/*globals _, Backbone */
     6850
     6851/**
     6852 * wp.media.view.PriorityList
     6853 *
     6854 * @class
     6855 * @augments wp.media.View
     6856 * @augments wp.Backbone.View
     6857 * @augments Backbone.View
     6858 */
     6859var PriorityList = wp.media.View.extend({
     6860        tagName:   'div',
     6861
     6862        initialize: function() {
     6863                this._views = {};
     6864
     6865                this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
     6866                delete this.options.views;
     6867
     6868                if ( ! this.options.silent ) {
     6869                        this.render();
     6870                }
     6871        },
     6872        /**
     6873         * @param {string} id
     6874         * @param {wp.media.View|Object} view
     6875         * @param {Object} options
     6876         * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
     6877         */
     6878        set: function( id, view, options ) {
     6879                var priority, views, index;
     6880
     6881                options = options || {};
     6882
     6883                // Accept an object with an `id` : `view` mapping.
     6884                if ( _.isObject( id ) ) {
     6885                        _.each( id, function( view, id ) {
     6886                                this.set( id, view );
     6887                        }, this );
     6888                        return this;
     6889                }
     6890
     6891                if ( ! (view instanceof Backbone.View) ) {
     6892                        view = this.toView( view, id, options );
     6893                }
     6894                view.controller = view.controller || this.controller;
     6895
     6896                this.unset( id );
     6897
     6898                priority = view.options.priority || 10;
     6899                views = this.views.get() || [];
     6900
     6901                _.find( views, function( existing, i ) {
     6902                        if ( existing.options.priority > priority ) {
     6903                                index = i;
     6904                                return true;
     6905                        }
     6906                });
     6907
     6908                this._views[ id ] = view;
     6909                this.views.add( view, {
     6910                        at: _.isNumber( index ) ? index : views.length || 0
     6911                });
     6912
     6913                return this;
     6914        },
     6915        /**
     6916         * @param {string} id
     6917         * @returns {wp.media.View}
     6918         */
     6919        get: function( id ) {
     6920                return this._views[ id ];
     6921        },
     6922        /**
     6923         * @param {string} id
     6924         * @returns {wp.media.view.PriorityList}
     6925         */
     6926        unset: function( id ) {
     6927                var view = this.get( id );
     6928
     6929                if ( view ) {
     6930                        view.remove();
     6931                }
     6932
     6933                delete this._views[ id ];
     6934                return this;
     6935        },
     6936        /**
     6937         * @param {Object} options
     6938         * @returns {wp.media.View}
     6939         */
     6940        toView: function( options ) {
     6941                return new wp.media.View( options );
     6942        }
     6943});
     6944
     6945module.exports = PriorityList;
     6946
     6947},{}],53:[function(require,module,exports){
     6948/**
     6949 * wp.media.view.RouterItem
     6950 *
     6951 * @class
     6952 * @augments wp.media.view.MenuItem
     6953 * @augments wp.media.View
     6954 * @augments wp.Backbone.View
     6955 * @augments Backbone.View
     6956 */
     6957var RouterItem = wp.media.view.MenuItem.extend({
     6958        /**
     6959         * On click handler to activate the content region's corresponding mode.
     6960         */
     6961        click: function() {
     6962                var contentMode = this.options.contentMode;
     6963                if ( contentMode ) {
     6964                        this.controller.content.mode( contentMode );
     6965                }
     6966        }
     6967});
     6968
     6969module.exports = RouterItem;
     6970
     6971},{}],54:[function(require,module,exports){
     6972/*globals wp */
     6973
     6974/**
     6975 * wp.media.view.Router
     6976 *
     6977 * @class
     6978 * @augments wp.media.view.Menu
     6979 * @augments wp.media.view.PriorityList
     6980 * @augments wp.media.View
     6981 * @augments wp.Backbone.View
     6982 * @augments Backbone.View
     6983 */
     6984var Menu = wp.media.view.Menu,
     6985        Router;
     6986
     6987Router = Menu.extend({
     6988        tagName:   'div',
     6989        className: 'media-router',
     6990        property:  'contentMode',
     6991        ItemView:  wp.media.view.RouterItem,
     6992        region:    'router',
     6993
     6994        initialize: function() {
     6995                this.controller.on( 'content:render', this.update, this );
     6996                // Call 'initialize' directly on the parent class.
     6997                Menu.prototype.initialize.apply( this, arguments );
     6998        },
     6999
     7000        update: function() {
     7001                var mode = this.controller.content.mode();
     7002                if ( mode ) {
     7003                        this.select( mode );
     7004                }
     7005        }
     7006});
     7007
     7008module.exports = Router;
     7009
     7010},{}],55:[function(require,module,exports){
     7011/*globals wp */
     7012
     7013/**
     7014 * wp.media.view.Search
     7015 *
     7016 * @class
     7017 * @augments wp.media.View
     7018 * @augments wp.Backbone.View
     7019 * @augments Backbone.View
     7020 */
     7021var l10n = wp.media.view.l10n,
     7022        Search;
     7023
     7024Search = wp.media.View.extend({
     7025        tagName:   'input',
     7026        className: 'search',
     7027        id:        'media-search-input',
     7028
     7029        attributes: {
     7030                type:        'search',
     7031                placeholder: l10n.search
     7032        },
     7033
     7034        events: {
     7035                'input':  'search',
     7036                'keyup':  'search',
     7037                'change': 'search',
     7038                'search': 'search'
     7039        },
     7040
     7041        /**
     7042         * @returns {wp.media.view.Search} Returns itself to allow chaining
     7043         */
     7044        render: function() {
     7045                this.el.value = this.model.escape('search');
     7046                return this;
     7047        },
     7048
     7049        search: function( event ) {
     7050                if ( event.target.value ) {
     7051                        this.model.set( 'search', event.target.value );
     7052                } else {
     7053                        this.model.unset('search');
     7054                }
     7055        }
     7056});
     7057
     7058module.exports = Search;
     7059
     7060},{}],56:[function(require,module,exports){
     7061/*globals wp, _, Backbone */
     7062
     7063/**
     7064 * wp.media.view.Selection
     7065 *
     7066 * @class
     7067 * @augments wp.media.View
     7068 * @augments wp.Backbone.View
     7069 * @augments Backbone.View
     7070 */
     7071var l10n = wp.media.view.l10n,
     7072        Selection;
     7073
     7074Selection = wp.media.View.extend({
     7075        tagName:   'div',
     7076        className: 'media-selection',
     7077        template:  wp.template('media-selection'),
     7078
     7079        events: {
     7080                'click .edit-selection':  'edit',
     7081                'click .clear-selection': 'clear'
     7082        },
     7083
     7084        initialize: function() {
     7085                _.defaults( this.options, {
     7086                        editable:  false,
     7087                        clearable: true
     7088                });
     7089
     7090                /**
     7091                 * @member {wp.media.view.Attachments.Selection}
     7092                 */
     7093                this.attachments = new wp.media.view.Attachments.Selection({
     7094                        controller: this.controller,
     7095                        collection: this.collection,
     7096                        selection:  this.collection,
     7097                        model:      new Backbone.Model()
     7098                });
     7099
     7100                this.views.set( '.selection-view', this.attachments );
     7101                this.collection.on( 'add remove reset', this.refresh, this );
     7102                this.controller.on( 'content:activate', this.refresh, this );
     7103        },
     7104
     7105        ready: function() {
     7106                this.refresh();
     7107        },
     7108
     7109        refresh: function() {
     7110                // If the selection hasn't been rendered, bail.
     7111                if ( ! this.$el.children().length ) {
     7112                        return;
     7113                }
     7114
     7115                var collection = this.collection,
     7116                        editing = 'edit-selection' === this.controller.content.mode();
     7117
     7118                // If nothing is selected, display nothing.
     7119                this.$el.toggleClass( 'empty', ! collection.length );
     7120                this.$el.toggleClass( 'one', 1 === collection.length );
     7121                this.$el.toggleClass( 'editing', editing );
     7122
     7123                this.$('.count').text( l10n.selected.replace('%d', collection.length) );
     7124        },
     7125
     7126        edit: function( event ) {
     7127                event.preventDefault();
     7128                if ( this.options.editable ) {
     7129                        this.options.editable.call( this, this.collection );
     7130                }
     7131        },
     7132
     7133        clear: function( event ) {
     7134                event.preventDefault();
     7135                this.collection.reset();
     7136
     7137                // Keep focus inside media modal
     7138                // after clear link is selected
     7139                this.controller.modal.focusManager.focus();
     7140        }
     7141});
     7142
     7143module.exports = Selection;
     7144
     7145},{}],57:[function(require,module,exports){
     7146/*globals _, Backbone */
     7147
     7148/**
     7149 * wp.media.view.Settings
    86107150 *
    86117151 * @class
    … …  
    86157155 */
    86167156var View = wp.media.View,
    8617         UploaderStatus = wp.media.view.UploaderStatus,
    8618         l10n = wp.media.view.l10n,
    8619         $ = jQuery,
    8620         Cropper;
    8621 
    8622 Cropper = View.extend({
    8623         className: 'crop-content',
    8624         template: wp.template('crop-content'),
     7157        $ = Backbone.$,
     7158        Settings;
     7159
     7160Settings = View.extend({
     7161        events: {
     7162                'click button':    'updateHandler',
     7163                'change input':    'updateHandler',
     7164                'change select':   'updateHandler',
     7165                'change textarea': 'updateHandler'
     7166        },
     7167
    86257168        initialize: function() {
    8626                 _.bindAll(this, 'onImageLoad');
    8627         },
    8628         ready: function() {
    8629                 this.controller.frame.on('content:error:crop', this.onError, this);
    8630                 this.$image = this.$el.find('.crop-image');
    8631                 this.$image.on('load', this.onImageLoad);
    8632                 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
    8633         },
    8634         remove: function() {
    8635                 $(window).off('resize.cropper');
    8636                 this.$el.remove();
    8637                 this.$el.off();
    8638                 View.prototype.remove.apply(this, arguments);
    8639         },
     7169                this.model = this.model || new Backbone.Model();
     7170                this.listenTo( this.model, 'change', this.updateChanges );
     7171        },
     7172
    86407173        prepare: function() {
    8641                 return {
    8642                         title: l10n.cropYourImage,
    8643                         url: this.options.attachment.get('url')
    8644                 };
    8645         },
    8646         onImageLoad: function() {
    8647                 var imgOptions = this.controller.get('imgSelectOptions');
    8648                 if (typeof imgOptions === 'function') {
    8649                         imgOptions = imgOptions(this.options.attachment, this.controller);
    8650                 }
    8651 
    8652                 imgOptions = _.extend(imgOptions, {parent: this.$el});
    8653                 this.trigger('image-loaded');
    8654                 this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
    8655         },
    8656         onError: function() {
    8657                 var filename = this.options.attachment.get('filename');
    8658 
    8659                 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    8660                         filename: UploaderStatus.prototype.filename(filename),
    8661                         message: window._wpMediaViewsL10n.cropError
    8662                 }), { at: 0 });
     7174                return _.defaults({
     7175                        model: this.model.toJSON()
     7176                }, this.options );
     7177        },
     7178        /**
     7179         * @returns {wp.media.view.Settings} Returns itself to allow chaining
     7180         */
     7181        render: function() {
     7182                View.prototype.render.apply( this, arguments );
     7183                // Select the correct values.
     7184                _( this.model.attributes ).chain().keys().each( this.update, this );
     7185                return this;
     7186        },
     7187        /**
     7188         * @param {string} key
     7189         */
     7190        update: function( key ) {
     7191                var value = this.model.get( key ),
     7192                        $setting = this.$('[data-setting="' + key + '"]'),
     7193                        $buttons, $value;
     7194
     7195                // Bail if we didn't find a matching setting.
     7196                if ( ! $setting.length ) {
     7197                        return;
     7198                }
     7199
     7200                // Attempt to determine how the setting is rendered and update
     7201                // the selected value.
     7202
     7203                // Handle dropdowns.
     7204                if ( $setting.is('select') ) {
     7205                        $value = $setting.find('[value="' + value + '"]');
     7206
     7207                        if ( $value.length ) {
     7208                                $setting.find('option').prop( 'selected', false );
     7209                                $value.prop( 'selected', true );
     7210                        } else {
     7211                                // If we can't find the desired value, record what *is* selected.
     7212                                this.model.set( key, $setting.find(':selected').val() );
     7213                        }
     7214
     7215                // Handle button groups.
     7216                } else if ( $setting.hasClass('button-group') ) {
     7217                        $buttons = $setting.find('button').removeClass('active');
     7218                        $buttons.filter( '[value="' + value + '"]' ).addClass('active');
     7219
     7220                // Handle text inputs and textareas.
     7221                } else if ( $setting.is('input[type="text"], textarea') ) {
     7222                        if ( ! $setting.is(':focus') ) {
     7223                                $setting.val( value );
     7224                        }
     7225                // Handle checkboxes.
     7226                } else if ( $setting.is('input[type="checkbox"]') ) {
     7227                        $setting.prop( 'checked', !! value && 'false' !== value );
     7228                }
     7229        },
     7230        /**
     7231         * @param {Object} event
     7232         */
     7233        updateHandler: function( event ) {
     7234                var $setting = $( event.target ).closest('[data-setting]'),
     7235                        value = event.target.value,
     7236                        userSetting;
     7237
     7238                event.preventDefault();
     7239
     7240                if ( ! $setting.length ) {
     7241                        return;
     7242                }
     7243
     7244                // Use the correct value for checkboxes.
     7245                if ( $setting.is('input[type="checkbox"]') ) {
     7246                        value = $setting[0].checked;
     7247                }
     7248
     7249                // Update the corresponding setting.
     7250                this.model.set( $setting.data('setting'), value );
     7251
     7252                // If the setting has a corresponding user setting,
     7253                // update that as well.
     7254                if ( userSetting = $setting.data('userSetting') ) {
     7255                        window.setUserSetting( userSetting, value );
     7256                }
     7257        },
     7258
     7259        updateChanges: function( model ) {
     7260                if ( model.hasChanged() ) {
     7261                        _( model.changed ).chain().keys().each( this.update, this );
     7262                }
    86637263        }
    86647264});
    86657265
    8666 module.exports = Cropper;
    8667 
    8668 
    8669 /***/ }),
    8670 /* 97 */,
    8671 /* 98 */,
    8672 /* 99 */
    8673 /***/ (function(module, exports) {
    8674 
     7266module.exports = Settings;
     7267
     7268},{}],58:[function(require,module,exports){
    86757269/*globals wp, _ */
    86767270
    86777271/**
    8678  * wp.media.view.EditImage
     7272 * wp.media.view.Settings.AttachmentDisplay
    86797273 *
    86807274 * @class
     7275 * @augments wp.media.view.Settings
    86817276 * @augments wp.media.View
    86827277 * @augments wp.Backbone.View
    86837278 * @augments Backbone.View
    86847279 */
    8685 var View = wp.media.View,
    8686         EditImage;
    8687 
    8688 EditImage = View.extend({
    8689         className: 'image-editor',
    8690         template: wp.template('image-editor'),
    8691 
    8692         initialize: function( options ) {
    8693                 this.editor = window.imageEdit;
    8694                 this.controller = options.controller;
    8695                 View.prototype.initialize.apply( this, arguments );
    8696         },
    8697 
    8698         prepare: function() {
    8699                 return this.model.toJSON();
    8700         },
    8701 
    8702         loadEditor: function() {
    8703                 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
    8704                 dfd.done( _.bind( this.focus, this ) );
    8705         },
    8706 
    8707         focus: function() {
    8708                 this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
    8709         },
    8710 
    8711         back: function() {
    8712                 var lastState = this.controller.lastState();
    8713                 this.controller.setState( lastState );
    8714         },
    8715 
    8716         refresh: function() {
    8717                 this.model.fetch();
    8718         },
    8719 
    8720         save: function() {
    8721                 var lastState = this.controller.lastState();
    8722 
    8723                 this.model.fetch().done( _.bind( function() {
    8724                         this.controller.setState( lastState );
    8725                 }, this ) );
     7280var Settings = wp.media.view.Settings,
     7281        AttachmentDisplay;
     7282
     7283AttachmentDisplay = Settings.extend({
     7284        className: 'attachment-display-settings',
     7285        template:  wp.template('attachment-display-settings'),
     7286
     7287        initialize: function() {
     7288                var attachment = this.options.attachment;
     7289
     7290                _.defaults( this.options, {
     7291                        userSettings: false
     7292                });
     7293                // Call 'initialize' directly on the parent class.
     7294                Settings.prototype.initialize.apply( this, arguments );
     7295                this.listenTo( this.model, 'change:link', this.updateLinkTo );
     7296
     7297                if ( attachment ) {
     7298                        attachment.on( 'change:uploading', this.render, this );
     7299                }
     7300        },
     7301
     7302        dispose: function() {
     7303                var attachment = this.options.attachment;
     7304                if ( attachment ) {
     7305                        attachment.off( null, null, this );
     7306                }
     7307                /**
     7308                 * call 'dispose' directly on the parent class
     7309                 */
     7310                Settings.prototype.dispose.apply( this, arguments );
     7311        },
     7312        /**
     7313         * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
     7314         */
     7315        render: function() {
     7316                var attachment = this.options.attachment;
     7317                if ( attachment ) {
     7318                        _.extend( this.options, {
     7319                                sizes: attachment.get('sizes'),
     7320                                type:  attachment.get('type')
     7321                        });
     7322                }
     7323                /**
     7324                 * call 'render' directly on the parent class
     7325                 */
     7326                Settings.prototype.render.call( this );
     7327                this.updateLinkTo();
     7328                return this;
     7329        },
     7330
     7331        updateLinkTo: function() {
     7332                var linkTo = this.model.get('link'),
     7333                        $input = this.$('.link-to-custom'),
     7334                        attachment = this.options.attachment;
     7335
     7336                if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
     7337                        $input.addClass( 'hidden' );
     7338                        return;
     7339                }
     7340
     7341                if ( attachment ) {
     7342                        if ( 'post' === linkTo ) {
     7343                                $input.val( attachment.get('link') );
     7344                        } else if ( 'file' === linkTo ) {
     7345                                $input.val( attachment.get('url') );
     7346                        } else if ( ! this.model.get('linkUrl') ) {
     7347                                $input.val('http://');
     7348                        }
     7349
     7350                        $input.prop( 'readonly', 'custom' !== linkTo );
     7351                }
     7352
     7353                $input.removeClass( 'hidden' );
     7354
     7355                // If the input is visible, focus and select its contents.
     7356                if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
     7357                        $input.focus()[0].select();
     7358                }
    87267359        }
    8727 
    87287360});
    87297361
    8730 module.exports = EditImage;
    8731 
    8732 
    8733 /***/ }),
    8734 /* 100 */
    8735 /***/ (function(module, exports) {
    8736 
     7362module.exports = AttachmentDisplay;
     7363
     7364},{}],59:[function(require,module,exports){
     7365/*globals wp */
     7366
     7367/**
     7368 * wp.media.view.Settings.Gallery
     7369 *
     7370 * @class
     7371 * @augments wp.media.view.Settings
     7372 * @augments wp.media.View
     7373 * @augments wp.Backbone.View
     7374 * @augments Backbone.View
     7375 */
     7376var Gallery = wp.media.view.Settings.extend({
     7377        className: 'collection-settings gallery-settings',
     7378        template:  wp.template('gallery-settings')
     7379});
     7380
     7381module.exports = Gallery;
     7382
     7383},{}],60:[function(require,module,exports){
     7384/*globals wp */
     7385
     7386/**
     7387 * wp.media.view.Settings.Playlist
     7388 *
     7389 * @class
     7390 * @augments wp.media.view.Settings
     7391 * @augments wp.media.View
     7392 * @augments wp.Backbone.View
     7393 * @augments Backbone.View
     7394 */
     7395var Playlist = wp.media.view.Settings.extend({
     7396        className: 'collection-settings playlist-settings',
     7397        template:  wp.template('playlist-settings')
     7398});
     7399
     7400module.exports = Playlist;
     7401
     7402},{}],61:[function(require,module,exports){
     7403/**
     7404 * wp.media.view.Sidebar
     7405 *
     7406 * @class
     7407 * @augments wp.media.view.PriorityList
     7408 * @augments wp.media.View
     7409 * @augments wp.Backbone.View
     7410 * @augments Backbone.View
     7411 */
     7412var Sidebar = wp.media.view.PriorityList.extend({
     7413        className: 'media-sidebar'
     7414});
     7415
     7416module.exports = Sidebar;
     7417
     7418},{}],62:[function(require,module,exports){
    87377419/*globals _ */
    87387420
    … …  
    87717453module.exports = Spinner;
    87727454
    8773 
    8774 /***/ })
    8775 /******/ ]));
     7455},{}],63:[function(require,module,exports){
     7456/*globals _, Backbone */
     7457
     7458/**
     7459 * wp.media.view.Toolbar
     7460 *
     7461 * A toolbar which consists of a primary and a secondary section. Each sections
     7462 * can be filled with views.
     7463 *
     7464 * @class
     7465 * @augments wp.media.View
     7466 * @augments wp.Backbone.View
     7467 * @augments Backbone.View
     7468 */
     7469var View = wp.media.View,
     7470        Toolbar;
     7471
     7472Toolbar = View.extend({
     7473        tagName:   'div',
     7474        className: 'media-toolbar',
     7475
     7476        initialize: function() {
     7477                var state = this.controller.state(),
     7478                        selection = this.selection = state.get('selection'),
     7479                        library = this.library = state.get('library');
     7480
     7481                this._views = {};
     7482
     7483                // The toolbar is composed of two `PriorityList` views.
     7484                this.primary   = new wp.media.view.PriorityList();
     7485                this.secondary = new wp.media.view.PriorityList();
     7486                this.primary.$el.addClass('media-toolbar-primary search-form');
     7487                this.secondary.$el.addClass('media-toolbar-secondary');
     7488
     7489                this.views.set([ this.secondary, this.primary ]);
     7490
     7491                if ( this.options.items ) {
     7492                        this.set( this.options.items, { silent: true });
     7493                }
     7494
     7495                if ( ! this.options.silent ) {
     7496                        this.render();
     7497                }
     7498
     7499                if ( selection ) {
     7500                        selection.on( 'add remove reset', this.refresh, this );
     7501                }
     7502
     7503                if ( library ) {
     7504                        library.on( 'add remove reset', this.refresh, this );
     7505                }
     7506        },
     7507        /**
     7508         * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
     7509         */
     7510        dispose: function() {
     7511                if ( this.selection ) {
     7512                        this.selection.off( null, null, this );
     7513                }
     7514
     7515                if ( this.library ) {
     7516                        this.library.off( null, null, this );
     7517                }
     7518                /**
     7519                 * call 'dispose' directly on the parent class
     7520                 */
     7521                return View.prototype.dispose.apply( this, arguments );
     7522        },
     7523
     7524        ready: function() {
     7525                this.refresh();
     7526        },
     7527
     7528        /**
     7529         * @param {string} id
     7530         * @param {Backbone.View|Object} view
     7531         * @param {Object} [options={}]
     7532         * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     7533         */
     7534        set: function( id, view, options ) {
     7535                var list;
     7536                options = options || {};
     7537
     7538                // Accept an object with an `id` : `view` mapping.
     7539                if ( _.isObject( id ) ) {
     7540                        _.each( id, function( view, id ) {
     7541                                this.set( id, view, { silent: true });
     7542                        }, this );
     7543
     7544                } else {
     7545                        if ( ! ( view instanceof Backbone.View ) ) {
     7546                                view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
     7547                                view = new wp.media.view.Button( view ).render();
     7548                        }
     7549
     7550                        view.controller = view.controller || this.controller;
     7551
     7552                        this._views[ id ] = view;
     7553
     7554                        list = view.options.priority < 0 ? 'secondary' : 'primary';
     7555                        this[ list ].set( id, view, options );
     7556                }
     7557
     7558                if ( ! options.silent ) {
     7559                        this.refresh();
     7560                }
     7561
     7562                return this;
     7563        },
     7564        /**
     7565         * @param {string} id
     7566         * @returns {wp.media.view.Button}
     7567         */
     7568        get: function( id ) {
     7569                return this._views[ id ];
     7570        },
     7571        /**
     7572         * @param {string} id
     7573         * @param {Object} options
     7574         * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     7575         */
     7576        unset: function( id, options ) {
     7577                delete this._views[ id ];
     7578                this.primary.unset( id, options );
     7579                this.secondary.unset( id, options );
     7580
     7581                if ( ! options || ! options.silent ) {
     7582                        this.refresh();
     7583                }
     7584                return this;
     7585        },
     7586
     7587        refresh: function() {
     7588                var state = this.controller.state(),
     7589                        library = state.get('library'),
     7590                        selection = state.get('selection');
     7591
     7592                _.each( this._views, function( button ) {
     7593                        if ( ! button.model || ! button.options || ! button.options.requires ) {
     7594                                return;
     7595                        }
     7596
     7597                        var requires = button.options.requires,
     7598                                disabled = false;
     7599
     7600                        // Prevent insertion of attachments if any of them are still uploading
     7601                        disabled = _.some( selection.models, function( attachment ) {
     7602                                return attachment.get('uploading') === true;
     7603                        });
     7604
     7605                        if ( requires.selection && selection && ! selection.length ) {
     7606                                disabled = true;
     7607                        } else if ( requires.library && library && ! library.length ) {
     7608                                disabled = true;
     7609                        }
     7610                        button.model.set( 'disabled', disabled );
     7611                });
     7612        }
     7613});
     7614
     7615module.exports = Toolbar;
     7616
     7617},{}],64:[function(require,module,exports){
     7618/*globals wp, _ */
     7619
     7620/**
     7621 * wp.media.view.Toolbar.Embed
     7622 *
     7623 * @class
     7624 * @augments wp.media.view.Toolbar.Select
     7625 * @augments wp.media.view.Toolbar
     7626 * @augments wp.media.View
     7627 * @augments wp.Backbone.View
     7628 * @augments Backbone.View
     7629 */
     7630var Select = wp.media.view.Toolbar.Select,
     7631        l10n = wp.media.view.l10n,
     7632        Embed;
     7633
     7634Embed = Select.extend({
     7635        initialize: function() {
     7636                _.defaults( this.options, {
     7637                        text: l10n.insertIntoPost,
     7638                        requires: false
     7639                });
     7640                // Call 'initialize' directly on the parent class.
     7641                Select.prototype.initialize.apply( this, arguments );
     7642        },
     7643
     7644        refresh: function() {
     7645                var url = this.controller.state().props.get('url');
     7646                this.get('select').model.set( 'disabled', ! url || url === 'http://' );
     7647                /**
     7648                 * call 'refresh' directly on the parent class
     7649                 */
     7650                Select.prototype.refresh.apply( this, arguments );
     7651        }
     7652});
     7653
     7654module.exports = Embed;
     7655
     7656},{}],65:[function(require,module,exports){
     7657/*globals wp, _ */
     7658
     7659/**
     7660 * wp.media.view.Toolbar.Select
     7661 *
     7662 * @class
     7663 * @augments wp.media.view.Toolbar
     7664 * @augments wp.media.View
     7665 * @augments wp.Backbone.View
     7666 * @augments Backbone.View
     7667 */
     7668var Toolbar = wp.media.view.Toolbar,
     7669        l10n = wp.media.view.l10n,
     7670        Select;
     7671
     7672Select = Toolbar.extend({
     7673        initialize: function() {
     7674                var options = this.options;
     7675
     7676                _.bindAll( this, 'clickSelect' );
     7677
     7678                _.defaults( options, {
     7679                        event: 'select',
     7680                        state: false,
     7681                        reset: true,
     7682                        close: true,
     7683                        text:  l10n.select,
     7684
     7685                        // Does the button rely on the selection?
     7686                        requires: {
     7687                                selection: true
     7688                        }
     7689                });
     7690
     7691                options.items = _.defaults( options.items || {}, {
     7692                        select: {
     7693                                style:    'primary',
     7694                                text:     options.text,
     7695                                priority: 80,
     7696                                click:    this.clickSelect,
     7697                                requires: options.requires
     7698                        }
     7699                });
     7700                // Call 'initialize' directly on the parent class.
     7701                Toolbar.prototype.initialize.apply( this, arguments );
     7702        },
     7703
     7704        clickSelect: function() {
     7705                var options = this.options,
     7706                        controller = this.controller;
     7707
     7708                if ( options.close ) {
     7709                        controller.close();
     7710                }
     7711
     7712                if ( options.event ) {
     7713                        controller.state().trigger( options.event );
     7714                }
     7715
     7716                if ( options.state ) {
     7717                        controller.setState( options.state );
     7718                }
     7719
     7720                if ( options.reset ) {
     7721                        controller.reset();
     7722                }
     7723        }
     7724});
     7725
     7726module.exports = Select;
     7727
     7728},{}],66:[function(require,module,exports){
     7729/*globals wp, _, jQuery */
     7730
     7731/**
     7732 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap
     7733 * or #wp-fullscreen-body) and relays drag'n'dropped files to a media workflow.
     7734 *
     7735 * wp.media.view.EditorUploader
     7736 *
     7737 * @class
     7738 * @augments wp.media.View
     7739 * @augments wp.Backbone.View
     7740 * @augments Backbone.View
     7741 */
     7742var View = wp.media.View,
     7743        l10n = wp.media.view.l10n,
     7744        $ = jQuery,
     7745        EditorUploader;
     7746
     7747EditorUploader = View.extend({
     7748        tagName:   'div',
     7749        className: 'uploader-editor',
     7750        template:  wp.template( 'uploader-editor' ),
     7751
     7752        localDrag: false,
     7753        overContainer: false,
     7754        overDropzone: false,
     7755        draggingFile: null,
     7756
     7757        /**
     7758         * Bind drag'n'drop events to callbacks.
     7759         */
     7760        initialize: function() {
     7761                this.initialized = false;
     7762
     7763                // Bail if not enabled or UA does not support drag'n'drop or File API.
     7764                if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
     7765                        return this;
     7766                }
     7767
     7768                this.$document = $(document);
     7769                this.dropzones = [];
     7770                this.files = [];
     7771
     7772                this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
     7773                this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
     7774                this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
     7775                this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
     7776
     7777                this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
     7778                this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
     7779
     7780                this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
     7781                        this.localDrag = event.type === 'dragstart';
     7782                }, this ) );
     7783
     7784                this.initialized = true;
     7785                return this;
     7786        },
     7787
     7788        /**
     7789         * Check browser support for drag'n'drop.
     7790         *
     7791         * @return Boolean
     7792         */
     7793        browserSupport: function() {
     7794                var supports = false, div = document.createElement('div');
     7795
     7796                supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
     7797                supports = supports && !! ( window.File && window.FileList && window.FileReader );
     7798                return supports;
     7799        },
     7800
     7801        isDraggingFile: function( event ) {
     7802                if ( this.draggingFile !== null ) {
     7803                        return this.draggingFile;
     7804                }
     7805
     7806                if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
     7807                        return false;
     7808                }
     7809
     7810                this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
     7811                        _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
     7812
     7813                return this.draggingFile;
     7814        },
     7815
     7816        refresh: function( e ) {
     7817                var dropzone_id;
     7818                for ( dropzone_id in this.dropzones ) {
     7819                        // Hide the dropzones only if dragging has left the screen.
     7820                        this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
     7821                }
     7822
     7823                if ( ! _.isUndefined( e ) ) {
     7824                        $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
     7825                }
     7826
     7827                if ( ! this.overContainer && ! this.overDropzone ) {
     7828                        this.draggingFile = null;
     7829                }
     7830
     7831                return this;
     7832        },
     7833
     7834        render: function() {
     7835                if ( ! this.initialized ) {
     7836                        return this;
     7837                }
     7838
     7839                View.prototype.render.apply( this, arguments );
     7840                $( '.wp-editor-wrap, #wp-fullscreen-body' ).each( _.bind( this.attach, this ) );
     7841                return this;
     7842        },
     7843
     7844        attach: function( index, editor ) {
     7845                // Attach a dropzone to an editor.
     7846                var dropzone = this.$el.clone();
     7847                this.dropzones.push( dropzone );
     7848                $( editor ).append( dropzone );
     7849                return this;
     7850        },
     7851
     7852        /**
     7853         * When a file is dropped on the editor uploader, open up an editor media workflow
     7854         * and upload the file immediately.
     7855         *
     7856         * @param  {jQuery.Event} event The 'drop' event.
     7857         */
     7858        drop: function( event ) {
     7859                var $wrap = null, uploadView;
     7860
     7861                this.containerDragleave( event );
     7862                this.dropzoneDragleave( event );
     7863
     7864                this.files = event.originalEvent.dataTransfer.files;
     7865                if ( this.files.length < 1 ) {
     7866                        return;
     7867                }
     7868
     7869                // Set the active editor to the drop target.
     7870                $wrap = $( event.target ).parents( '.wp-editor-wrap' );
     7871                if ( $wrap.length > 0 && $wrap[0].id ) {
     7872                        window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
     7873                }
     7874
     7875                if ( ! this.workflow ) {
     7876                        this.workflow = wp.media.editor.open( 'content', {
     7877                                frame:    'post',
     7878                                state:    'insert',
     7879                                title:    l10n.addMedia,
     7880                                multiple: true
     7881                        });
     7882                        uploadView = this.workflow.uploader;
     7883                        if ( uploadView.uploader && uploadView.uploader.ready ) {
     7884                                this.addFiles.apply( this );
     7885                        } else {
     7886                                this.workflow.on( 'uploader:ready', this.addFiles, this );
     7887                        }
     7888                } else {
     7889                        this.workflow.state().reset();
     7890                        this.addFiles.apply( this );
     7891                        this.workflow.open();
     7892                }
     7893
     7894                return false;
     7895        },
     7896
     7897        /**
     7898         * Add the files to the uploader.
     7899         */
     7900        addFiles: function() {
     7901                if ( this.files.length ) {
     7902                        this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
     7903                        this.files = [];
     7904                }
     7905                return this;
     7906        },
     7907
     7908        containerDragover: function( event ) {
     7909                if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     7910                        return;
     7911                }
     7912
     7913                this.overContainer = true;
     7914                this.refresh();
     7915        },
     7916
     7917        containerDragleave: function() {
     7918                this.overContainer = false;
     7919
     7920                // Throttle dragleave because it's called when bouncing from some elements to others.
     7921                _.delay( _.bind( this.refresh, this ), 50 );
     7922        },
     7923
     7924        dropzoneDragover: function( event ) {
     7925                if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     7926                        return;
     7927                }
     7928
     7929                this.overDropzone = true;
     7930                this.refresh( event );
     7931                return false;
     7932        },
     7933
     7934        dropzoneDragleave: function( e ) {
     7935                this.overDropzone = false;
     7936                _.delay( _.bind( this.refresh, this, e ), 50 );
     7937        },
     7938
     7939        click: function( e ) {
     7940                // In the rare case where the dropzone gets stuck, hide it on click.
     7941                this.containerDragleave( e );
     7942                this.dropzoneDragleave( e );
     7943                this.localDrag = false;
     7944        }
     7945});
     7946
     7947module.exports = EditorUploader;
     7948
     7949},{}],67:[function(require,module,exports){
     7950/*globals wp, _ */
     7951
     7952/**
     7953 * wp.media.view.UploaderInline
     7954 *
     7955 * The inline uploader that shows up in the 'Upload Files' tab.
     7956 *
     7957 * @class
     7958 * @augments wp.media.View
     7959 * @augments wp.Backbone.View
     7960 * @augments Backbone.View
     7961 */
     7962var View = wp.media.View,
     7963        UploaderInline;
     7964
     7965UploaderInline = View.extend({
     7966        tagName:   'div',
     7967        className: 'uploader-inline',
     7968        template:  wp.template('uploader-inline'),
     7969
     7970        events: {
     7971                'click .close': 'hide'
     7972        },
     7973
     7974        initialize: function() {
     7975                _.defaults( this.options, {
     7976                        message: '',
     7977                        status:  true,
     7978                        canClose: false
     7979                });
     7980
     7981                if ( ! this.options.$browser && this.controller.uploader ) {
     7982                        this.options.$browser = this.controller.uploader.$browser;
     7983                }
     7984
     7985                if ( _.isUndefined( this.options.postId ) ) {
     7986                        this.options.postId = wp.media.view.settings.post.id;
     7987                }
     7988
     7989                if ( this.options.status ) {
     7990                        this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
     7991                                controller: this.controller
     7992                        }) );
     7993                }
     7994        },
     7995
     7996        prepare: function() {
     7997                var suggestedWidth = this.controller.state().get('suggestedWidth'),
     7998                        suggestedHeight = this.controller.state().get('suggestedHeight'),
     7999                        data = {};
     8000
     8001                data.message = this.options.message;
     8002                data.canClose = this.options.canClose;
     8003
     8004                if ( suggestedWidth && suggestedHeight ) {
     8005                        data.suggestedWidth = suggestedWidth;
     8006                        data.suggestedHeight = suggestedHeight;
     8007                }
     8008
     8009                return data;
     8010        },
     8011        /**
     8012         * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
     8013         */
     8014        dispose: function() {
     8015                if ( this.disposing ) {
     8016                        /**
     8017                         * call 'dispose' directly on the parent class
     8018                         */
     8019                        return View.prototype.dispose.apply( this, arguments );
     8020                }
     8021
     8022                // Run remove on `dispose`, so we can be sure to refresh the
     8023                // uploader with a view-less DOM. Track whether we're disposing
     8024                // so we don't trigger an infinite loop.
     8025                this.disposing = true;
     8026                return this.remove();
     8027        },
     8028        /**
     8029         * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
     8030         */
     8031        remove: function() {
     8032                /**
     8033                 * call 'remove' directly on the parent class
     8034                 */
     8035                var result = View.prototype.remove.apply( this, arguments );
     8036
     8037                _.defer( _.bind( this.refresh, this ) );
     8038                return result;
     8039        },
     8040
     8041        refresh: function() {
     8042                var uploader = this.controller.uploader;
     8043
     8044                if ( uploader ) {
     8045                        uploader.refresh();
     8046                }
     8047        },
     8048        /**
     8049         * @returns {wp.media.view.UploaderInline}
     8050         */
     8051        ready: function() {
     8052                var $browser = this.options.$browser,
     8053                        $placeholder;
     8054
     8055                if ( this.controller.uploader ) {
     8056                        $placeholder = this.$('.browser');
     8057
     8058                        // Check if we've already replaced the placeholder.
     8059                        if ( $placeholder[0] === $browser[0] ) {
     8060                                return;
     8061                        }
     8062
     8063                        $browser.detach().text( $placeholder.text() );
     8064                        $browser[0].className = $placeholder[0].className;
     8065                        $placeholder.replaceWith( $browser.show() );
     8066                }
     8067
     8068                this.refresh();
     8069                return this;
     8070        },
     8071        show: function() {
     8072                this.$el.removeClass( 'hidden' );
     8073        },
     8074        hide: function() {
     8075                this.$el.addClass( 'hidden' );
     8076        }
     8077
     8078});
     8079
     8080module.exports = UploaderInline;
     8081
     8082},{}],68:[function(require,module,exports){
     8083/*globals wp */
     8084
     8085/**
     8086 * wp.media.view.UploaderStatusError
     8087 *
     8088 * @class
     8089 * @augments wp.media.View
     8090 * @augments wp.Backbone.View
     8091 * @augments Backbone.View
     8092 */
     8093var UploaderStatusError = wp.media.View.extend({
     8094        className: 'upload-error',
     8095        template:  wp.template('uploader-status-error')
     8096});
     8097
     8098module.exports = UploaderStatusError;
     8099
     8100},{}],69:[function(require,module,exports){
     8101/*globals wp, _ */
     8102
     8103/**
     8104 * wp.media.view.UploaderStatus
     8105 *
     8106 * An uploader status for on-going uploads.
     8107 *
     8108 * @class
     8109 * @augments wp.media.View
     8110 * @augments wp.Backbone.View
     8111 * @augments Backbone.View
     8112 */
     8113var View = wp.media.View,
     8114        UploaderStatus;
     8115
     8116UploaderStatus = View.extend({
     8117        className: 'media-uploader-status',
     8118        template:  wp.template('uploader-status'),
     8119
     8120        events: {
     8121                'click .upload-dismiss-errors': 'dismiss'
     8122        },
     8123
     8124        initialize: function() {
     8125                this.queue = wp.Uploader.queue;
     8126                this.queue.on( 'add remove reset', this.visibility, this );
     8127                this.queue.on( 'add remove reset change:percent', this.progress, this );
     8128                this.queue.on( 'add remove reset change:uploading', this.info, this );
     8129
     8130                this.errors = wp.Uploader.errors;
     8131                this.errors.reset();
     8132                this.errors.on( 'add remove reset', this.visibility, this );
     8133                this.errors.on( 'add', this.error, this );
     8134        },
     8135        /**
     8136         * @global wp.Uploader
     8137         * @returns {wp.media.view.UploaderStatus}
     8138         */
     8139        dispose: function() {
     8140                wp.Uploader.queue.off( null, null, this );
     8141                /**
     8142                 * call 'dispose' directly on the parent class
     8143                 */
     8144                View.prototype.dispose.apply( this, arguments );
     8145                return this;
     8146        },
     8147
     8148        visibility: function() {
     8149                this.$el.toggleClass( 'uploading', !! this.queue.length );
     8150                this.$el.toggleClass( 'errors', !! this.errors.length );
     8151                this.$el.toggle( !! this.queue.length || !! this.errors.length );
     8152        },
     8153
     8154        ready: function() {
     8155                _.each({
     8156                        '$bar':      '.media-progress-bar div',
     8157                        '$index':    '.upload-index',
     8158                        '$total':    '.upload-total',
     8159                        '$filename': '.upload-filename'
     8160                }, function( selector, key ) {
     8161                        this[ key ] = this.$( selector );
     8162                }, this );
     8163
     8164                this.visibility();
     8165                this.progress();
     8166                this.info();
     8167        },
     8168
     8169        progress: function() {
     8170                var queue = this.queue,
     8171                        $bar = this.$bar;
     8172
     8173                if ( ! $bar || ! queue.length ) {
     8174                        return;
     8175                }
     8176
     8177                $bar.width( ( queue.reduce( function( memo, attachment ) {
     8178                        if ( ! attachment.get('uploading') ) {
     8179                                return memo + 100;
     8180                        }
     8181
     8182                        var percent = attachment.get('percent');
     8183                        return memo + ( _.isNumber( percent ) ? percent : 100 );
     8184                }, 0 ) / queue.length ) + '%' );
     8185        },
     8186
     8187        info: function() {
     8188                var queue = this.queue,
     8189                        index = 0, active;
     8190
     8191                if ( ! queue.length ) {
     8192                        return;
     8193                }
     8194
     8195                active = this.queue.find( function( attachment, i ) {
     8196                        index = i;
     8197                        return attachment.get('uploading');
     8198                });
     8199
     8200                this.$index.text( index + 1 );
     8201                this.$total.text( queue.length );
     8202                this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
     8203        },
     8204        /**
     8205         * @param {string} filename
     8206         * @returns {string}
     8207         */
     8208        filename: function( filename ) {
     8209                return wp.media.truncate( _.escape( filename ), 24 );
     8210        },
     8211        /**
     8212         * @param {Backbone.Model} error
     8213         */
     8214        error: function( error ) {
     8215                this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     8216                        filename: this.filename( error.get('file').name ),
     8217                        message:  error.get('message')
     8218                }), { at: 0 });
     8219        },
     8220
     8221        /**
     8222         * @global wp.Uploader
     8223         *
     8224         * @param {Object} event
     8225         */
     8226        dismiss: function( event ) {
     8227                var errors = this.views.get('.upload-errors');
     8228
     8229                event.preventDefault();
     8230
     8231                if ( errors ) {
     8232                        _.invoke( errors, 'remove' );
     8233                }
     8234                wp.Uploader.errors.reset();
     8235        }
     8236});
     8237
     8238module.exports = UploaderStatus;
     8239
     8240},{}],70:[function(require,module,exports){
     8241/*globals wp, _, jQuery */
     8242
     8243/**
     8244 * wp.media.view.UploaderWindow
     8245 *
     8246 * An uploader window that allows for dragging and dropping media.
     8247 *
     8248 * @class
     8249 * @augments wp.media.View
     8250 * @augments wp.Backbone.View
     8251 * @augments Backbone.View
     8252 *
     8253 * @param {object} [options]                   Options hash passed to the view.
     8254 * @param {object} [options.uploader]          Uploader properties.
     8255 * @param {jQuery} [options.uploader.browser]
     8256 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
     8257 * @param {object} [options.uploader.params]
     8258 */
     8259var $ = jQuery,
     8260        UploaderWindow;
     8261
     8262UploaderWindow = wp.media.View.extend({
     8263        tagName:   'div',
     8264        className: 'uploader-window',
     8265        template:  wp.template('uploader-window'),
     8266
     8267        initialize: function() {
     8268                var uploader;
     8269
     8270                this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');
     8271
     8272                uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
     8273                        dropzone:  this.$el,
     8274                        browser:   this.$browser,
     8275                        params:    {}
     8276                });
     8277
     8278                // Ensure the dropzone is a jQuery collection.
     8279                if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
     8280                        uploader.dropzone = $( uploader.dropzone );
     8281                }
     8282
     8283                this.controller.on( 'activate', this.refresh, this );
     8284
     8285                this.controller.on( 'detach', function() {
     8286                        this.$browser.remove();
     8287                }, this );
     8288        },
     8289
     8290        refresh: function() {
     8291                if ( this.uploader ) {
     8292                        this.uploader.refresh();
     8293                }
     8294        },
     8295
     8296        ready: function() {
     8297                var postId = wp.media.view.settings.post.id,
     8298                        dropzone;
     8299
     8300                // If the uploader already exists, bail.
     8301                if ( this.uploader ) {
     8302                        return;
     8303                }
     8304
     8305                if ( postId ) {
     8306                        this.options.uploader.params.post_id = postId;
     8307                }
     8308                this.uploader = new wp.Uploader( this.options.uploader );
     8309
     8310                dropzone = this.uploader.dropzone;
     8311                dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
     8312                dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
     8313
     8314                $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
     8315        },
     8316
     8317        _ready: function() {
     8318                this.controller.trigger( 'uploader:ready' );
     8319        },
     8320
     8321        show: function() {
     8322                var $el = this.$el.show();
     8323
     8324                // Ensure that the animation is triggered by waiting until
     8325                // the transparent element is painted into the DOM.
     8326                _.defer( function() {
     8327                        $el.css({ opacity: 1 });
     8328                });
     8329        },
     8330
     8331        hide: function() {
     8332                var $el = this.$el.css({ opacity: 0 });
     8333
     8334                wp.media.transition( $el ).done( function() {
     8335                        // Transition end events are subject to race conditions.
     8336                        // Make sure that the value is set as intended.
     8337                        if ( '0' === $el.css('opacity') ) {
     8338                                $el.hide();
     8339                        }
     8340                });
     8341
     8342                // https://core.trac.wordpress.org/ticket/27341
     8343                _.delay( function() {
     8344                        if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
     8345                                $el.hide();
     8346                        }
     8347                }, 500 );
     8348        }
     8349});
     8350
     8351module.exports = UploaderWindow;
     8352
     8353},{}],71:[function(require,module,exports){
     8354/*globals wp */
     8355
     8356/**
     8357 * wp.media.View
     8358 *
     8359 * The base view class for media.
     8360 *
     8361 * Undelegating events, removing events from the model, and
     8362 * removing events from the controller mirror the code for
     8363 * `Backbone.View.dispose` in Backbone 0.9.8 development.
     8364 *
     8365 * This behavior has since been removed, and should not be used
     8366 * outside of the media manager.
     8367 *
     8368 * @class
     8369 * @augments wp.Backbone.View
     8370 * @augments Backbone.View
     8371 */
     8372var View = wp.Backbone.View.extend({
     8373        constructor: function( options ) {
     8374                if ( options && options.controller ) {
     8375                        this.controller = options.controller;
     8376                }
     8377                wp.Backbone.View.apply( this, arguments );
     8378        },
     8379        /**
     8380         * @todo The internal comment mentions this might have been a stop-gap
     8381         *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
     8382         *       care of this in Backbone.View now.
     8383         *
     8384         * @returns {wp.media.View} Returns itself to allow chaining
     8385         */
     8386        dispose: function() {
     8387                // Undelegating events, removing events from the model, and
     8388                // removing events from the controller mirror the code for
     8389                // `Backbone.View.dispose` in Backbone 0.9.8 development.
     8390                this.undelegateEvents();
     8391
     8392                if ( this.model && this.model.off ) {
     8393                        this.model.off( null, null, this );
     8394                }
     8395
     8396                if ( this.collection && this.collection.off ) {
     8397                        this.collection.off( null, null, this );
     8398                }
     8399
     8400                // Unbind controller events.
     8401                if ( this.controller && this.controller.off ) {
     8402                        this.controller.off( null, null, this );
     8403                }
     8404
     8405                return this;
     8406        },
     8407        /**
     8408         * @returns {wp.media.View} Returns itself to allow chaining
     8409         */
     8410        remove: function() {
     8411                this.dispose();
     8412                /**
     8413                 * call 'remove' directly on the parent class
     8414                 */
     8415                return wp.Backbone.View.prototype.remove.apply( this, arguments );
     8416        }
     8417});
     8418
     8419module.exports = View;
     8420
     8421},{}]},{},[17]);
Note: See TracChangeset for help on using the changeset viewer.