Make WordPress Core

Changeset 46500


Ignore:
Timestamp:
10/14/2019 07:14:31 PM (7 years ago)
Author:
whyisjake
Message:

Backporting several bug fixes.

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

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

Location:
branches/4.2
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • branches/4.2

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

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

    r43998 r46500  
    14961496        if ( file_exists( $target ) )
    14971497                return @is_dir( $target );
     1498
     1499        // Do not allow path traversals.
     1500        if ( false !== strpos( $target, '../' ) || false !== strpos( $target, '..' . DIRECTORY_SEPARATOR ) ) {
     1501                return false;
     1502        }
    14981503
    14991504        // We need to find the permissions of the parent folder that exists and inherit that.
  • branches/4.2/src/wp-includes/http.php

    r37118 r46500  
    471471                } else {
    472472                        $ip = gethostbyname( $host );
    473                         if ( $ip === $host ) // Error condition for gethostbyname()
    474                                 $ip = false;
     473                        if ( $ip === $host ) { // Error condition for gethostbyname()
     474                                return false;
     475                        }
    475476                }
    476477                if ( $ip ) {
  • branches/4.2/src/wp-includes/js/media-audiovideo.js

    r33316 r46500  
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     1/******/ (function(modules) { // webpackBootstrap
     2/******/        // The module cache
     3/******/        var installedModules = {};
     4/******/
     5/******/        // The require function
     6/******/        function __webpack_require__(moduleId) {
     7/******/
     8/******/                // Check if module is in cache
     9/******/                if(installedModules[moduleId]) {
     10/******/                        return installedModules[moduleId].exports;
     11/******/                }
     12/******/                // Create a new module (and put it into the cache)
     13/******/                var module = installedModules[moduleId] = {
     14/******/                        i: moduleId,
     15/******/                        l: false,
     16/******/                        exports: {}
     17/******/                };
     18/******/
     19/******/                // Execute the module function
     20/******/                modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/                // Flag the module as loaded
     23/******/                module.l = true;
     24/******/
     25/******/                // Return the exports of the module
     26/******/                return module.exports;
     27/******/        }
     28/******/
     29/******/
     30/******/        // expose the modules object (__webpack_modules__)
     31/******/        __webpack_require__.m = modules;
     32/******/
     33/******/        // expose the module cache
     34/******/        __webpack_require__.c = installedModules;
     35/******/
     36/******/        // define getter function for harmony exports
     37/******/        __webpack_require__.d = function(exports, name, getter) {
     38/******/                if(!__webpack_require__.o(exports, name)) {
     39/******/                        Object.defineProperty(exports, name, {
     40/******/                                configurable: false,
     41/******/                                enumerable: true,
     42/******/                                get: getter
     43/******/                        });
     44/******/                }
     45/******/        };
     46/******/
     47/******/        // getDefaultExport function for compatibility with non-harmony modules
     48/******/        __webpack_require__.n = function(module) {
     49/******/                var getter = module && module.__esModule ?
     50/******/                        function getDefault() { return module['default']; } :
     51/******/                        function getModuleExports() { return module; };
     52/******/                __webpack_require__.d(getter, 'a', getter);
     53/******/                return getter;
     54/******/        };
     55/******/
     56/******/        // Object.prototype.hasOwnProperty.call
     57/******/        __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/        // __webpack_public_path__
     60/******/        __webpack_require__.p = "";
     61/******/
     62/******/        // Load entry module and return exports
     63/******/        return __webpack_require__(__webpack_require__.s = 0);
     64/******/ })
     65/************************************************************************/
     66/******/ ([
     67/* 0 */
     68/***/ (function(module, exports, __webpack_require__) {
     69
    270/*globals wp, _ */
    371
     
    208276};
    209277
    210 media.model.PostMedia = require( './models/post-media.js' );
    211 media.controller.AudioDetails = require( './controllers/audio-details.js' );
    212 media.controller.VideoDetails = require( './controllers/video-details.js' );
    213 media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' );
    214 media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' );
    215 media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' );
    216 media.view.MediaDetails = require( './views/media-details.js' );
    217 media.view.AudioDetails = require( './views/audio-details.js' );
    218 media.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){
     278media.model.PostMedia = __webpack_require__( 1 );
     279media.controller.AudioDetails = __webpack_require__( 2 );
     280media.controller.VideoDetails = __webpack_require__( 3 );
     281media.view.MediaFrame.MediaDetails = __webpack_require__( 4 );
     282media.view.MediaFrame.AudioDetails = __webpack_require__( 5 );
     283media.view.MediaFrame.VideoDetails = __webpack_require__( 6 );
     284media.view.MediaDetails = __webpack_require__( 7 );
     285media.view.AudioDetails = __webpack_require__( 8 );
     286media.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 */
     304var 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
     334module.exports = PostMedia;
     335
     336
     337/***/ }),
     338/* 2 */
     339/***/ (function(module, exports) {
     340
    221341/*globals wp */
    222342
     
    253373module.exports = AudioDetails;
    254374
    255 },{}],3:[function(require,module,exports){
     375
     376/***/ }),
     377/* 3 */
     378/***/ (function(module, exports) {
     379
    256380/*globals wp */
    257381
     
    288412module.exports = VideoDetails;
    289413
    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  */
    302 var 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 
    332 module.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  */
    348 var MediaDetails = wp.media.view.MediaDetails,
    349         AudioDetails;
    350 
    351 AudioDetails = 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 
    372 module.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  */
    390 var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
    391         MediaLibrary = wp.media.controller.MediaLibrary,
    392 
    393         l10n = wp.media.view.l10n,
    394         AudioDetails;
    395 
    396 AudioDetails = 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 
    450 module.exports = AudioDetails;
    451 
    452 },{}],7:[function(require,module,exports){
     414
     415/***/ }),
     416/* 4 */
     417/***/ (function(module, exports) {
     418
    453419/*globals wp */
    454420
     
    582548module.exports = MediaDetails;
    583549
    584 },{}],8:[function(require,module,exports){
     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 */
     570var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
     571        MediaLibrary = wp.media.controller.MediaLibrary,
     572
     573        l10n = wp.media.view.l10n,
     574        AudioDetails;
     575
     576AudioDetails = 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
     630module.exports = AudioDetails;
     631
     632
     633/***/ }),
     634/* 6 */
     635/***/ (function(module, exports) {
     636
    585637/*globals wp, _ */
    586638
     
    719771module.exports = VideoDetails;
    720772
    721 },{}],9:[function(require,module,exports){
     773
     774/***/ }),
     775/* 7 */
     776/***/ (function(module, exports) {
     777
    722778/*global wp, jQuery, _, MediaElementPlayer */
    723779
     
    887943module.exports = MediaDetails;
    888944
    889 },{}],10:[function(require,module,exports){
     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 */
     963var MediaDetails = wp.media.view.MediaDetails,
     964        AudioDetails;
     965
     966AudioDetails = 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
     987module.exports = AudioDetails;
     988
     989
     990/***/ }),
     991/* 9 */
     992/***/ (function(module, exports) {
     993
    890994/*globals wp */
    891995
     
    9321036module.exports = VideoDetails;
    9331037
    934 },{}]},{},[1]);
     1038
     1039/***/ })
     1040/******/ ]);
  • branches/4.2/src/wp-includes/js/media-grid.js

    r32125 r46500  
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     1/******/ (function(modules) { // webpackBootstrap
     2/******/        // The module cache
     3/******/        var installedModules = {};
     4/******/
     5/******/        // The require function
     6/******/        function __webpack_require__(moduleId) {
     7/******/
     8/******/                // Check if module is in cache
     9/******/                if(installedModules[moduleId]) {
     10/******/                        return installedModules[moduleId].exports;
     11/******/                }
     12/******/                // Create a new module (and put it into the cache)
     13/******/                var module = installedModules[moduleId] = {
     14/******/                        i: moduleId,
     15/******/                        l: false,
     16/******/                        exports: {}
     17/******/                };
     18/******/
     19/******/                // Execute the module function
     20/******/                modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/                // Flag the module as loaded
     23/******/                module.l = true;
     24/******/
     25/******/                // Return the exports of the module
     26/******/                return module.exports;
     27/******/        }
     28/******/
     29/******/
     30/******/        // expose the modules object (__webpack_modules__)
     31/******/        __webpack_require__.m = modules;
     32/******/
     33/******/        // expose the module cache
     34/******/        __webpack_require__.c = installedModules;
     35/******/
     36/******/        // define getter function for harmony exports
     37/******/        __webpack_require__.d = function(exports, name, getter) {
     38/******/                if(!__webpack_require__.o(exports, name)) {
     39/******/                        Object.defineProperty(exports, name, {
     40/******/                                configurable: false,
     41/******/                                enumerable: true,
     42/******/                                get: getter
     43/******/                        });
     44/******/                }
     45/******/        };
     46/******/
     47/******/        // getDefaultExport function for compatibility with non-harmony modules
     48/******/        __webpack_require__.n = function(module) {
     49/******/                var getter = module && module.__esModule ?
     50/******/                        function getDefault() { return module['default']; } :
     51/******/                        function getModuleExports() { return module; };
     52/******/                __webpack_require__.d(getter, 'a', getter);
     53/******/                return getter;
     54/******/        };
     55/******/
     56/******/        // Object.prototype.hasOwnProperty.call
     57/******/        __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/        // __webpack_public_path__
     60/******/        __webpack_require__.p = "";
     61/******/
     62/******/        // Load entry module and return exports
     63/******/        return __webpack_require__(__webpack_require__.s = 10);
     64/******/ })
     65/************************************************************************/
     66/******/ ([
     67/* 0 */,
     68/* 1 */,
     69/* 2 */,
     70/* 3 */,
     71/* 4 */,
     72/* 5 */,
     73/* 6 */,
     74/* 7 */,
     75/* 8 */,
     76/* 9 */,
     77/* 10 */
     78/***/ (function(module, exports, __webpack_require__) {
     79
     80/*globals wp */
     81
     82var media = wp.media;
     83
     84media.controller.EditAttachmentMetadata = __webpack_require__( 11 );
     85media.view.MediaFrame.Manage = __webpack_require__( 12 );
     86media.view.Attachment.Details.TwoColumn = __webpack_require__( 13 );
     87media.view.MediaFrame.Manage.Router = __webpack_require__( 14 );
     88media.view.EditImage.Details = __webpack_require__( 15 );
     89media.view.MediaFrame.EditAttachments = __webpack_require__( 16 );
     90media.view.SelectModeToggleButton = __webpack_require__( 17 );
     91media.view.DeleteSelectedButton = __webpack_require__( 18 );
     92media.view.DeleteSelectedPermanentlyButton = __webpack_require__( 19 );
     93
     94
     95/***/ }),
     96/* 11 */
     97/***/ (function(module, exports) {
     98
    299/*globals wp */
    3100
     
    29126module.exports = EditAttachmentMetadata;
    30127
    31 },{}],2:[function(require,module,exports){
     128
     129/***/ }),
     130/* 12 */
     131/***/ (function(module, exports) {
     132
     133/*globals wp, _, Backbone */
     134
     135/**
     136 * wp.media.view.MediaFrame.Manage
     137 *
     138 * A generic management frame workflow.
     139 *
     140 * Used in the media grid view.
     141 *
     142 * @class
     143 * @augments wp.media.view.MediaFrame
     144 * @augments wp.media.view.Frame
     145 * @augments wp.media.View
     146 * @augments wp.Backbone.View
     147 * @augments Backbone.View
     148 * @mixes wp.media.controller.StateMachine
     149 */
     150var MediaFrame = wp.media.view.MediaFrame,
     151        Library = wp.media.controller.Library,
     152
     153        $ = Backbone.$,
     154        Manage;
     155
     156Manage = MediaFrame.extend({
     157        /**
     158         * @global wp.Uploader
     159         */
     160        initialize: function() {
     161                _.defaults( this.options, {
     162                        title:     '',
     163                        modal:     false,
     164                        selection: [],
     165                        library:   {}, // Options hash for the query to the media library.
     166                        multiple:  'add',
     167                        state:     'library',
     168                        uploader:  true,
     169                        mode:      [ 'grid', 'edit' ]
     170                });
     171
     172                this.$body = $( document.body );
     173                this.$window = $( window );
     174                this.$adminBar = $( '#wpadminbar' );
     175                this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) );
     176                $( document ).on( 'click', '.add-new-h2', _.bind( this.addNewClickHandler, this ) );
     177
     178                // Ensure core and media grid view UI is enabled.
     179                this.$el.addClass('wp-core-ui');
     180
     181                // Force the uploader off if the upload limit has been exceeded or
     182                // if the browser isn't supported.
     183                if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     184                        this.options.uploader = false;
     185                }
     186
     187                // Initialize a window-wide uploader.
     188                if ( this.options.uploader ) {
     189                        this.uploader = new wp.media.view.UploaderWindow({
     190                                controller: this,
     191                                uploader: {
     192                                        dropzone:  document.body,
     193                                        container: document.body
     194                                }
     195                        }).render();
     196                        this.uploader.ready();
     197                        $('body').append( this.uploader.el );
     198
     199                        this.options.uploader = false;
     200                }
     201
     202                this.gridRouter = new wp.media.view.MediaFrame.Manage.Router();
     203
     204                // Call 'initialize' directly on the parent class.
     205                MediaFrame.prototype.initialize.apply( this, arguments );
     206
     207                // Append the frame view directly the supplied container.
     208                this.$el.appendTo( this.options.container );
     209
     210                this.createStates();
     211                this.bindRegionModeHandlers();
     212                this.render();
     213                this.bindSearchHandler();
     214        },
     215
     216        bindSearchHandler: function() {
     217                var search = this.$( '#media-search-input' ),
     218                        currentSearch = this.options.container.data( 'search' ),
     219                        searchView = this.browserView.toolbar.get( 'search' ).$el,
     220                        listMode = this.$( '.view-list' ),
     221
     222                        input  = _.debounce( function (e) {
     223                                var val = $( e.currentTarget ).val(),
     224                                        url = '';
     225
     226                                if ( val ) {
     227                                        url += '?search=' + val;
     228                                }
     229                                this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
     230                        }, 1000 );
     231
     232                // Update the URL when entering search string (at most once per second)
     233                search.on( 'input', _.bind( input, this ) );
     234                searchView.val( currentSearch ).trigger( 'input' );
     235
     236                this.gridRouter.on( 'route:search', function () {
     237                        var href = window.location.href;
     238                        if ( href.indexOf( 'mode=' ) > -1 ) {
     239                                href = href.replace( /mode=[^&]+/g, 'mode=list' );
     240                        } else {
     241                                href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
     242                        }
     243                        href = href.replace( 'search=', 's=' );
     244                        listMode.prop( 'href', href );
     245                } );
     246        },
     247
     248        /**
     249         * Create the default states for the frame.
     250         */
     251        createStates: function() {
     252                var options = this.options;
     253
     254                if ( this.options.states ) {
     255                        return;
     256                }
     257
     258                // Add the default states.
     259                this.states.add([
     260                        new Library({
     261                                library:            wp.media.query( options.library ),
     262                                multiple:           options.multiple,
     263                                title:              options.title,
     264                                content:            'browse',
     265                                toolbar:            'select',
     266                                contentUserSetting: false,
     267                                filterable:         'all',
     268                                autoSelect:         false
     269                        })
     270                ]);
     271        },
     272
     273        /**
     274         * Bind region mode activation events to proper handlers.
     275         */
     276        bindRegionModeHandlers: function() {
     277                this.on( 'content:create:browse', this.browseContent, this );
     278
     279                // Handle a frame-level event for editing an attachment.
     280                this.on( 'edit:attachment', this.openEditAttachmentModal, this );
     281
     282                this.on( 'select:activate', this.bindKeydown, this );
     283                this.on( 'select:deactivate', this.unbindKeydown, this );
     284        },
     285
     286        handleKeydown: function( e ) {
     287                if ( 27 === e.which ) {
     288                        e.preventDefault();
     289                        this.deactivateMode( 'select' ).activateMode( 'edit' );
     290                }
     291        },
     292
     293        bindKeydown: function() {
     294                this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) );
     295        },
     296
     297        unbindKeydown: function() {
     298                this.$body.off( 'keydown.select' );
     299        },
     300
     301        fixPosition: function() {
     302                var $browser, $toolbar;
     303                if ( ! this.isModeActive( 'select' ) ) {
     304                        return;
     305                }
     306
     307                $browser = this.$('.attachments-browser');
     308                $toolbar = $browser.find('.media-toolbar');
     309
     310                // Offset doesn't appear to take top margin into account, hence +16
     311                if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) {
     312                        $browser.addClass( 'fixed' );
     313                        $toolbar.css('width', $browser.width() + 'px');
     314                } else {
     315                        $browser.removeClass( 'fixed' );
     316                        $toolbar.css('width', '');
     317                }
     318        },
     319
     320        /**
     321         * Click handler for the `Add New` button.
     322         */
     323        addNewClickHandler: function( event ) {
     324                event.preventDefault();
     325                this.trigger( 'toggle:upload:attachment' );
     326        },
     327
     328        /**
     329         * Open the Edit Attachment modal.
     330         */
     331        openEditAttachmentModal: function( model ) {
     332                // Create a new EditAttachment frame, passing along the library and the attachment model.
     333                wp.media( {
     334                        frame:       'edit-attachments',
     335                        controller:  this,
     336                        library:     this.state().get('library'),
     337                        model:       model
     338                } );
     339        },
     340
     341        /**
     342         * Create an attachments browser view within the content region.
     343         *
     344         * @param {Object} contentRegion Basic object with a `view` property, which
     345         *                               should be set with the proper region view.
     346         * @this wp.media.controller.Region
     347         */
     348        browseContent: function( contentRegion ) {
     349                var state = this.state();
     350
     351                // Browse our library of attachments.
     352                this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({
     353                        controller: this,
     354                        collection: state.get('library'),
     355                        selection:  state.get('selection'),
     356                        model:      state,
     357                        sortable:   state.get('sortable'),
     358                        search:     state.get('searchable'),
     359                        filters:    state.get('filterable'),
     360                        date:       state.get('date'),
     361                        display:    state.get('displaySettings'),
     362                        dragInfo:   state.get('dragInfo'),
     363                        sidebar:    'errors',
     364
     365                        suggestedWidth:  state.get('suggestedWidth'),
     366                        suggestedHeight: state.get('suggestedHeight'),
     367
     368                        AttachmentView: state.get('AttachmentView'),
     369
     370                        scrollElement: document
     371                });
     372                this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) );
     373
     374                this.errors = wp.Uploader.errors;
     375                this.errors.on( 'add remove reset', this.sidebarVisibility, this );
     376        },
     377
     378        sidebarVisibility: function() {
     379                this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length );
     380        },
     381
     382        bindDeferred: function() {
     383                if ( ! this.browserView.dfd ) {
     384                        return;
     385                }
     386                this.browserView.dfd.done( _.bind( this.startHistory, this ) );
     387        },
     388
     389        startHistory: function() {
     390                // Verify pushState support and activate
     391                if ( window.history && window.history.pushState ) {
     392                        Backbone.history.start( {
     393                                root: window._wpMediaGridSettings.adminUrl,
     394                                pushState: true
     395                        } );
     396                }
     397        }
     398});
     399
     400module.exports = Manage;
     401
     402
     403/***/ }),
     404/* 13 */
     405/***/ (function(module, exports) {
     406
    32407/*globals wp */
    33408
    34 var media = wp.media;
    35 
    36 media.controller.EditAttachmentMetadata = require( './controllers/edit-attachment-metadata.js' );
    37 media.view.MediaFrame.Manage = require( './views/frame/manage.js' );
    38 media.view.Attachment.Details.TwoColumn = require( './views/attachment/details-two-column.js' );
    39 media.view.MediaFrame.Manage.Router = require( './routers/manage.js' );
    40 media.view.EditImage.Details = require( './views/edit-image-details.js' );
    41 media.view.MediaFrame.EditAttachments = require( './views/frame/edit-attachments.js' );
    42 media.view.SelectModeToggleButton = require( './views/button/select-mode-toggle.js' );
    43 media.view.DeleteSelectedButton = require( './views/button/delete-selected.js' );
    44 media.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){
     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 */
     422var Details = wp.media.view.Attachment.Details,
     423        TwoColumn;
     424
     425TwoColumn = 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
     449module.exports = TwoColumn;
     450
     451
     452/***/ }),
     453/* 14 */
     454/***/ (function(module, exports) {
     455
    47456/*globals wp, Backbone */
    48457
     
    94503module.exports = Router;
    95504
    96 },{}],4:[function(require,module,exports){
    97 /*globals wp */
     505
     506/***/ }),
     507/* 15 */
     508/***/ (function(module, exports) {
     509
     510/*globals wp, _ */
    98511
    99512/**
    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.
     513 * wp.media.view.EditImage.Details
    104514 *
    105515 * @class
    106  * @augments wp.media.view.Attachment.Details
    107  * @augments wp.media.view.Attachment
     516 * @augments wp.media.view.EditImage
    108517 * @augments wp.media.View
    109518 * @augments wp.Backbone.View
    110519 * @augments Backbone.View
    111520 */
    112 var Details = wp.media.view.Attachment.Details,
    113         TwoColumn;
    114 
    115 TwoColumn = 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                 } );
     521var View = wp.media.View,
     522        EditImage = wp.media.view.EditImage,
     523        Details;
     524
     525Details = 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 ) );
    136541        }
    137542});
    138543
    139 module.exports = TwoColumn;
    140 
    141 },{}],5:[function(require,module,exports){
    142 /*globals wp */
     544module.exports = Details;
     545
     546
     547/***/ }),
     548/* 16 */
     549/***/ (function(module, exports) {
     550
     551/*globals wp, _, jQuery */
    143552
    144553/**
    145  * wp.media.view.DeleteSelectedPermanentlyButton
    146  *
    147  * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
     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`.
    148561 *
    149562 * @class
    150  * @augments wp.media.view.DeleteSelectedButton
    151  * @augments wp.media.view.Button
     563 * @augments wp.media.view.Frame
    152564 * @augments wp.media.View
    153565 * @augments wp.Backbone.View
    154566 * @augments Backbone.View
     567 * @mixes wp.media.controller.StateMachine
    155568 */
    156 var Button = wp.media.view.Button,
    157         DeleteSelected = wp.media.view.DeleteSelectedButton,
    158         DeleteSelectedPermanently;
    159 
    160 DeleteSelectedPermanently = DeleteSelected.extend({
     569var Frame = wp.media.view.Frame,
     570        MediaFrame = wp.media.view.MediaFrame,
     571
     572        $ = jQuery,
     573        EditAttachments;
     574
     575EditAttachments = 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
    161586        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;
     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( '' ) );
    185790        }
    186791});
    187792
    188 module.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  */
    204 var Button = wp.media.view.Button,
    205         l10n = wp.media.view.l10n,
    206         DeleteSelected;
    207 
    208 DeleteSelected = 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 
    243 module.exports = DeleteSelected;
    244 
    245 },{}],7:[function(require,module,exports){
     793module.exports = EditAttachments;
     794
     795
     796/***/ }),
     797/* 17 */
     798/***/ (function(module, exports) {
     799
    246800/*globals wp */
    247801
     
    309863module.exports = SelectModeToggle;
    310864
    311 },{}],8:[function(require,module,exports){
    312 /*globals wp, _ */
     865
     866/***/ }),
     867/* 18 */
     868/***/ (function(module, exports) {
     869
     870/*globals wp */
    313871
    314872/**
    315  * wp.media.view.EditImage.Details
     873 * wp.media.view.DeleteSelectedButton
     874 *
     875 * A button that handles bulk Delete/Trash logic
    316876 *
    317877 * @class
    318  * @augments wp.media.view.EditImage
     878 * @augments wp.media.view.Button
    319879 * @augments wp.media.View
    320880 * @augments wp.Backbone.View
    321881 * @augments Backbone.View
    322882 */
    323 var View = wp.media.View,
    324         EditImage = wp.media.view.EditImage,
    325         Details;
    326 
    327 Details = 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 ) );
     883var Button = wp.media.view.Button,
     884        l10n = wp.media.view.l10n,
     885        DeleteSelected;
     886
     887DeleteSelected = 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;
    343919        }
    344920});
    345921
    346 module.exports = Details;
    347 
    348 },{}],9:[function(require,module,exports){
    349 /*globals wp, _, jQuery */
     922module.exports = DeleteSelected;
     923
     924
     925/***/ }),
     926/* 19 */
     927/***/ (function(module, exports) {
     928
     929/*globals wp */
    350930
    351931/**
    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`.
     932 * wp.media.view.DeleteSelectedPermanentlyButton
     933 *
     934 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
    359935 *
    360936 * @class
    361  * @augments wp.media.view.Frame
     937 * @augments wp.media.view.DeleteSelectedButton
     938 * @augments wp.media.view.Button
    362939 * @augments wp.media.View
    363940 * @augments wp.Backbone.View
    364941 * @augments Backbone.View
    365  * @mixes wp.media.controller.StateMachine
    366942 */
    367 var Frame = wp.media.view.Frame,
    368         MediaFrame = wp.media.view.MediaFrame,
    369 
    370         $ = jQuery,
    371         EditAttachments;
    372 
    373 EditAttachments = 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 
     943var Button = wp.media.view.Button,
     944        DeleteSelected = wp.media.view.DeleteSelectedButton,
     945        DeleteSelectedPermanently;
     946
     947DeleteSelectedPermanently = DeleteSelected.extend({
    384948        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( '' ) );
     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;
    588972        }
    589973});
    590974
    591 module.exports = EditAttachments;
    592 
    593 },{}],10:[function(require,module,exports){
    594 /*globals wp, _, Backbone */
    595 
    596 /**
    597  * wp.media.view.MediaFrame.Manage
    598  *
    599  * A generic management frame workflow.
    600  *
    601  * Used in the media grid view.
    602  *
    603  * @class
    604  * @augments wp.media.view.MediaFrame
    605  * @augments wp.media.view.Frame
    606  * @augments wp.media.View
    607  * @augments wp.Backbone.View
    608  * @augments Backbone.View
    609  * @mixes wp.media.controller.StateMachine
    610  */
    611 var MediaFrame = wp.media.view.MediaFrame,
    612         Library = wp.media.controller.Library,
    613 
    614         $ = Backbone.$,
    615         Manage;
    616 
    617 Manage = MediaFrame.extend({
    618         /**
    619          * @global wp.Uploader
    620          */
    621         initialize: function() {
    622                 _.defaults( this.options, {
    623                         title:     '',
    624                         modal:     false,
    625                         selection: [],
    626                         library:   {}, // Options hash for the query to the media library.
    627                         multiple:  'add',
    628                         state:     'library',
    629                         uploader:  true,
    630                         mode:      [ 'grid', 'edit' ]
    631                 });
    632 
    633                 this.$body = $( document.body );
    634                 this.$window = $( window );
    635                 this.$adminBar = $( '#wpadminbar' );
    636                 this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) );
    637                 $( document ).on( 'click', '.add-new-h2', _.bind( this.addNewClickHandler, this ) );
    638 
    639                 // Ensure core and media grid view UI is enabled.
    640                 this.$el.addClass('wp-core-ui');
    641 
    642                 // Force the uploader off if the upload limit has been exceeded or
    643                 // if the browser isn't supported.
    644                 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
    645                         this.options.uploader = false;
    646                 }
    647 
    648                 // Initialize a window-wide uploader.
    649                 if ( this.options.uploader ) {
    650                         this.uploader = new wp.media.view.UploaderWindow({
    651                                 controller: this,
    652                                 uploader: {
    653                                         dropzone:  document.body,
    654                                         container: document.body
    655                                 }
    656                         }).render();
    657                         this.uploader.ready();
    658                         $('body').append( this.uploader.el );
    659 
    660                         this.options.uploader = false;
    661                 }
    662 
    663                 this.gridRouter = new wp.media.view.MediaFrame.Manage.Router();
    664 
    665                 // Call 'initialize' directly on the parent class.
    666                 MediaFrame.prototype.initialize.apply( this, arguments );
    667 
    668                 // Append the frame view directly the supplied container.
    669                 this.$el.appendTo( this.options.container );
    670 
    671                 this.createStates();
    672                 this.bindRegionModeHandlers();
    673                 this.render();
    674                 this.bindSearchHandler();
    675         },
    676 
    677         bindSearchHandler: function() {
    678                 var search = this.$( '#media-search-input' ),
    679                         currentSearch = this.options.container.data( 'search' ),
    680                         searchView = this.browserView.toolbar.get( 'search' ).$el,
    681                         listMode = this.$( '.view-list' ),
    682 
    683                         input  = _.debounce( function (e) {
    684                                 var val = $( e.currentTarget ).val(),
    685                                         url = '';
    686 
    687                                 if ( val ) {
    688                                         url += '?search=' + val;
    689                                 }
    690                                 this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
    691                         }, 1000 );
    692 
    693                 // Update the URL when entering search string (at most once per second)
    694                 search.on( 'input', _.bind( input, this ) );
    695                 searchView.val( currentSearch ).trigger( 'input' );
    696 
    697                 this.gridRouter.on( 'route:search', function () {
    698                         var href = window.location.href;
    699                         if ( href.indexOf( 'mode=' ) > -1 ) {
    700                                 href = href.replace( /mode=[^&]+/g, 'mode=list' );
    701                         } else {
    702                                 href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
    703                         }
    704                         href = href.replace( 'search=', 's=' );
    705                         listMode.prop( 'href', href );
    706                 } );
    707         },
    708 
    709         /**
    710          * Create the default states for the frame.
    711          */
    712         createStates: function() {
    713                 var options = this.options;
    714 
    715                 if ( this.options.states ) {
    716                         return;
    717                 }
    718 
    719                 // Add the default states.
    720                 this.states.add([
    721                         new Library({
    722                                 library:            wp.media.query( options.library ),
    723                                 multiple:           options.multiple,
    724                                 title:              options.title,
    725                                 content:            'browse',
    726                                 toolbar:            'select',
    727                                 contentUserSetting: false,
    728                                 filterable:         'all',
    729                                 autoSelect:         false
    730                         })
    731                 ]);
    732         },
    733 
    734         /**
    735          * Bind region mode activation events to proper handlers.
    736          */
    737         bindRegionModeHandlers: function() {
    738                 this.on( 'content:create:browse', this.browseContent, this );
    739 
    740                 // Handle a frame-level event for editing an attachment.
    741                 this.on( 'edit:attachment', this.openEditAttachmentModal, this );
    742 
    743                 this.on( 'select:activate', this.bindKeydown, this );
    744                 this.on( 'select:deactivate', this.unbindKeydown, this );
    745         },
    746 
    747         handleKeydown: function( e ) {
    748                 if ( 27 === e.which ) {
    749                         e.preventDefault();
    750                         this.deactivateMode( 'select' ).activateMode( 'edit' );
    751                 }
    752         },
    753 
    754         bindKeydown: function() {
    755                 this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) );
    756         },
    757 
    758         unbindKeydown: function() {
    759                 this.$body.off( 'keydown.select' );
    760         },
    761 
    762         fixPosition: function() {
    763                 var $browser, $toolbar;
    764                 if ( ! this.isModeActive( 'select' ) ) {
    765                         return;
    766                 }
    767 
    768                 $browser = this.$('.attachments-browser');
    769                 $toolbar = $browser.find('.media-toolbar');
    770 
    771                 // Offset doesn't appear to take top margin into account, hence +16
    772                 if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) {
    773                         $browser.addClass( 'fixed' );
    774                         $toolbar.css('width', $browser.width() + 'px');
    775                 } else {
    776                         $browser.removeClass( 'fixed' );
    777                         $toolbar.css('width', '');
    778                 }
    779         },
    780 
    781         /**
    782          * Click handler for the `Add New` button.
    783          */
    784         addNewClickHandler: function( event ) {
    785                 event.preventDefault();
    786                 this.trigger( 'toggle:upload:attachment' );
    787         },
    788 
    789         /**
    790          * Open the Edit Attachment modal.
    791          */
    792         openEditAttachmentModal: function( model ) {
    793                 // Create a new EditAttachment frame, passing along the library and the attachment model.
    794                 wp.media( {
    795                         frame:       'edit-attachments',
    796                         controller:  this,
    797                         library:     this.state().get('library'),
    798                         model:       model
    799                 } );
    800         },
    801 
    802         /**
    803          * Create an attachments browser view within the content region.
    804          *
    805          * @param {Object} contentRegion Basic object with a `view` property, which
    806          *                               should be set with the proper region view.
    807          * @this wp.media.controller.Region
    808          */
    809         browseContent: function( contentRegion ) {
    810                 var state = this.state();
    811 
    812                 // Browse our library of attachments.
    813                 this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({
    814                         controller: this,
    815                         collection: state.get('library'),
    816                         selection:  state.get('selection'),
    817                         model:      state,
    818                         sortable:   state.get('sortable'),
    819                         search:     state.get('searchable'),
    820                         filters:    state.get('filterable'),
    821                         date:       state.get('date'),
    822                         display:    state.get('displaySettings'),
    823                         dragInfo:   state.get('dragInfo'),
    824                         sidebar:    'errors',
    825 
    826                         suggestedWidth:  state.get('suggestedWidth'),
    827                         suggestedHeight: state.get('suggestedHeight'),
    828 
    829                         AttachmentView: state.get('AttachmentView'),
    830 
    831                         scrollElement: document
    832                 });
    833                 this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) );
    834 
    835                 this.errors = wp.Uploader.errors;
    836                 this.errors.on( 'add remove reset', this.sidebarVisibility, this );
    837         },
    838 
    839         sidebarVisibility: function() {
    840                 this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length );
    841         },
    842 
    843         bindDeferred: function() {
    844                 if ( ! this.browserView.dfd ) {
    845                         return;
    846                 }
    847                 this.browserView.dfd.done( _.bind( this.startHistory, this ) );
    848         },
    849 
    850         startHistory: function() {
    851                 // Verify pushState support and activate
    852                 if ( window.history && window.history.pushState ) {
    853                         Backbone.history.start( {
    854                                 root: window._wpMediaGridSettings.adminUrl,
    855                                 pushState: true
    856                         } );
    857                 }
    858         }
    859 });
    860 
    861 module.exports = Manage;
    862 
    863 },{}]},{},[2]);
     975module.exports = DeleteSelectedPermanently;
     976
     977
     978/***/ })
     979/******/ ]);
  • branches/4.2/src/wp-includes/js/media-models.js

    r32125 r46500  
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     1/******/ (function(modules) { // webpackBootstrap
     2/******/        // The module cache
     3/******/        var installedModules = {};
     4/******/
     5/******/        // The require function
     6/******/        function __webpack_require__(moduleId) {
     7/******/
     8/******/                // Check if module is in cache
     9/******/                if(installedModules[moduleId]) {
     10/******/                        return installedModules[moduleId].exports;
     11/******/                }
     12/******/                // Create a new module (and put it into the cache)
     13/******/                var module = installedModules[moduleId] = {
     14/******/                        i: moduleId,
     15/******/                        l: false,
     16/******/                        exports: {}
     17/******/                };
     18/******/
     19/******/                // Execute the module function
     20/******/                modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/                // Flag the module as loaded
     23/******/                module.l = true;
     24/******/
     25/******/                // Return the exports of the module
     26/******/                return module.exports;
     27/******/        }
     28/******/
     29/******/
     30/******/        // expose the modules object (__webpack_modules__)
     31/******/        __webpack_require__.m = modules;
     32/******/
     33/******/        // expose the module cache
     34/******/        __webpack_require__.c = installedModules;
     35/******/
     36/******/        // define getter function for harmony exports
     37/******/        __webpack_require__.d = function(exports, name, getter) {
     38/******/                if(!__webpack_require__.o(exports, name)) {
     39/******/                        Object.defineProperty(exports, name, {
     40/******/                                configurable: false,
     41/******/                                enumerable: true,
     42/******/                                get: getter
     43/******/                        });
     44/******/                }
     45/******/        };
     46/******/
     47/******/        // getDefaultExport function for compatibility with non-harmony modules
     48/******/        __webpack_require__.n = function(module) {
     49/******/                var getter = module && module.__esModule ?
     50/******/                        function getDefault() { return module['default']; } :
     51/******/                        function getModuleExports() { return module; };
     52/******/                __webpack_require__.d(getter, 'a', getter);
     53/******/                return getter;
     54/******/        };
     55/******/
     56/******/        // Object.prototype.hasOwnProperty.call
     57/******/        __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/        // __webpack_public_path__
     60/******/        __webpack_require__.p = "";
     61/******/
     62/******/        // Load entry module and return exports
     63/******/        return __webpack_require__(__webpack_require__.s = 20);
     64/******/ })
     65/************************************************************************/
     66/******/ ({
     67
     68/***/ 20:
     69/***/ (function(module, exports, __webpack_require__) {
     70
    271/*globals wp, _, jQuery */
    372
     
    59128delete l10n.settings;
    60129
    61 Attachment = media.model.Attachment = require( './models/attachment.js' );
    62 Attachments = media.model.Attachments = require( './models/attachments.js' );
    63 
    64 media.model.Query = require( './models/query.js' );
    65 media.model.PostImage = require( './models/post-image.js' );
    66 media.model.Selection = require( './models/selection.js' );
     130Attachment = media.model.Attachment = __webpack_require__( 21 );
     131Attachments = media.model.Attachments = __webpack_require__( 22 );
     132
     133media.model.Query = __webpack_require__( 23 );
     134media.model.PostImage = __webpack_require__( 24 );
     135media.model.Selection = __webpack_require__( 25 );
    67136
    68137/**
     
    232301});
    233302
    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){
     303
     304/***/ }),
     305
     306/***/ 21:
     307/***/ (function(module, exports) {
     308
    235309/*globals wp, _, Backbone */
    236310
     
    402476module.exports = Attachment;
    403477
    404 },{}],3:[function(require,module,exports){
     478
     479/***/ }),
     480
     481/***/ 22:
     482/***/ (function(module, exports) {
     483
    405484/*globals wp, _, Backbone */
    406485
     
    9361015module.exports = Attachments;
    9371016
    938 },{}],4:[function(require,module,exports){
     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 */
     1042var Attachments = wp.media.model.Attachments,
     1043        Query;
     1044
     1045Query = 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
     1330module.exports = Query;
     1331
     1332
     1333/***/ }),
     1334
     1335/***/ 24:
     1336/***/ (function(module, exports) {
     1337
    9391338/*globals Backbone */
    9401339
     
    10921491module.exports = PostImage;
    10931492
    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  */
    1114 var Attachments = wp.media.model.Attachments,
    1115         Query;
    1116 
    1117 Query = 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 
    1402 module.exports = Query;
    1403 
    1404 },{}],6:[function(require,module,exports){
     1493
     1494/***/ }),
     1495
     1496/***/ 25:
     1497/***/ (function(module, exports) {
     1498
    14051499/*globals wp, _ */
    14061500
     
    15011595module.exports = Selection;
    15021596
    1503 },{}]},{},[1]);
     1597
     1598/***/ })
     1599
     1600/******/ });
  • branches/4.2/src/wp-includes/js/media-views.js

    r32258 r46500  
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     1/******/ (function(modules) { // webpackBootstrap
     2/******/        // The module cache
     3/******/        var installedModules = {};
     4/******/
     5/******/        // The require function
     6/******/        function __webpack_require__(moduleId) {
     7/******/
     8/******/                // Check if module is in cache
     9/******/                if(installedModules[moduleId]) {
     10/******/                        return installedModules[moduleId].exports;
     11/******/                }
     12/******/                // Create a new module (and put it into the cache)
     13/******/                var module = installedModules[moduleId] = {
     14/******/                        i: moduleId,
     15/******/                        l: false,
     16/******/                        exports: {}
     17/******/                };
     18/******/
     19/******/                // Execute the module function
     20/******/                modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/                // Flag the module as loaded
     23/******/                module.l = true;
     24/******/
     25/******/                // Return the exports of the module
     26/******/                return module.exports;
     27/******/        }
     28/******/
     29/******/
     30/******/        // expose the modules object (__webpack_modules__)
     31/******/        __webpack_require__.m = modules;
     32/******/
     33/******/        // expose the module cache
     34/******/        __webpack_require__.c = installedModules;
     35/******/
     36/******/        // define getter function for harmony exports
     37/******/        __webpack_require__.d = function(exports, name, getter) {
     38/******/                if(!__webpack_require__.o(exports, name)) {
     39/******/                        Object.defineProperty(exports, name, {
     40/******/                                configurable: false,
     41/******/                                enumerable: true,
     42/******/                                get: getter
     43/******/                        });
     44/******/                }
     45/******/        };
     46/******/
     47/******/        // getDefaultExport function for compatibility with non-harmony modules
     48/******/        __webpack_require__.n = function(module) {
     49/******/                var getter = module && module.__esModule ?
     50/******/                        function getDefault() { return module['default']; } :
     51/******/                        function getModuleExports() { return module; };
     52/******/                __webpack_require__.d(getter, 'a', getter);
     53/******/                return getter;
     54/******/        };
     55/******/
     56/******/        // Object.prototype.hasOwnProperty.call
     57/******/        __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/        // __webpack_public_path__
     60/******/        __webpack_require__.p = "";
     61/******/
     62/******/        // Load entry module and return exports
     63/******/        return __webpack_require__(__webpack_require__.s = 26);
     64/******/ })
     65/************************************************************************/
     66/******/ (Array(26).concat([
     67/* 26 */
     68/***/ (function(module, exports, __webpack_require__) {
     69
     70/*globals wp, jQuery, _, Backbone */
     71
     72var media = wp.media,
     73        $ = jQuery,
     74        l10n;
     75
     76media.isTouchDevice = ( 'ontouchend' in document );
     77
     78// Link any localized strings.
     79l10n = media.view.l10n = window._wpMediaViewsL10n || {};
     80
     81// Link any settings.
     82media.view.settings = l10n.settings || {};
     83delete l10n.settings;
     84
     85// Copy the `post` setting over to the model settings.
     86media.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}());
     106
     107/**
     108 * A shared event bus used to provide events into
     109 * the media workflows that 3rd-party devs can use to hook
     110 * in.
     111 */
     112media.events = _.extend( {}, Backbone.Events );
     113
     114/**
     115 * Makes it easier to bind events using transitions.
     116 *
     117 * @param {string} selector
     118 * @param {Number} sensitivity
     119 * @returns {Promise}
     120 */
     121media.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();
     140        }
     141
     142        return deferred.promise();
     143};
     144
     145media.controller.Region = __webpack_require__( 27 );
     146media.controller.StateMachine = __webpack_require__( 28 );
     147media.controller.State = __webpack_require__( 29 );
     148
     149media.selectionSync = __webpack_require__( 30 );
     150media.controller.Library = __webpack_require__( 31 );
     151media.controller.ImageDetails = __webpack_require__( 32 );
     152media.controller.GalleryEdit = __webpack_require__( 33 );
     153media.controller.GalleryAdd = __webpack_require__( 34 );
     154media.controller.CollectionEdit = __webpack_require__( 35 );
     155media.controller.CollectionAdd = __webpack_require__( 36 );
     156media.controller.FeaturedImage = __webpack_require__( 37 );
     157media.controller.ReplaceImage = __webpack_require__( 38 );
     158media.controller.EditImage = __webpack_require__( 39 );
     159media.controller.MediaLibrary = __webpack_require__( 40 );
     160media.controller.Embed = __webpack_require__( 41 );
     161media.controller.Cropper = __webpack_require__( 42 );
     162
     163media.View = __webpack_require__( 45 );
     164media.view.Frame = __webpack_require__( 46 );
     165media.view.MediaFrame = __webpack_require__( 47 );
     166media.view.MediaFrame.Select = __webpack_require__( 48 );
     167media.view.MediaFrame.Post = __webpack_require__( 49 );
     168media.view.MediaFrame.ImageDetails = __webpack_require__( 50 );
     169media.view.Modal = __webpack_require__( 51 );
     170media.view.FocusManager = __webpack_require__( 52 );
     171media.view.UploaderWindow = __webpack_require__( 53 );
     172media.view.EditorUploader = __webpack_require__( 54 );
     173media.view.UploaderInline = __webpack_require__( 55 );
     174media.view.UploaderStatus = __webpack_require__( 56 );
     175media.view.UploaderStatusError = __webpack_require__( 57 );
     176media.view.Toolbar = __webpack_require__( 58 );
     177media.view.Toolbar.Select = __webpack_require__( 59 );
     178media.view.Toolbar.Embed = __webpack_require__( 60 );
     179media.view.Button = __webpack_require__( 61 );
     180media.view.ButtonGroup = __webpack_require__( 62 );
     181media.view.PriorityList = __webpack_require__( 63 );
     182media.view.MenuItem = __webpack_require__( 64 );
     183media.view.Menu = __webpack_require__( 65 );
     184media.view.RouterItem = __webpack_require__( 66 );
     185media.view.Router = __webpack_require__( 67 );
     186media.view.Sidebar = __webpack_require__( 68 );
     187media.view.Attachment = __webpack_require__( 69 );
     188media.view.Attachment.Library = __webpack_require__( 70 );
     189media.view.Attachment.EditLibrary = __webpack_require__( 71 );
     190media.view.Attachments = __webpack_require__( 72 );
     191media.view.Search = __webpack_require__( 73 );
     192media.view.AttachmentFilters = __webpack_require__( 74 );
     193media.view.DateFilter = __webpack_require__( 75 );
     194media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 );
     195media.view.AttachmentFilters.All = __webpack_require__( 77 );
     196media.view.AttachmentsBrowser = __webpack_require__( 78 );
     197media.view.Selection = __webpack_require__( 79 );
     198media.view.Attachment.Selection = __webpack_require__( 80 );
     199media.view.Attachments.Selection = __webpack_require__( 81 );
     200media.view.Attachment.EditSelection = __webpack_require__( 82 );
     201media.view.Settings = __webpack_require__( 83 );
     202media.view.Settings.AttachmentDisplay = __webpack_require__( 84 );
     203media.view.Settings.Gallery = __webpack_require__( 85 );
     204media.view.Settings.Playlist = __webpack_require__( 86 );
     205media.view.Attachment.Details = __webpack_require__( 87 );
     206media.view.AttachmentCompat = __webpack_require__( 88 );
     207media.view.Iframe = __webpack_require__( 89 );
     208media.view.Embed = __webpack_require__( 90 );
     209media.view.Label = __webpack_require__( 91 );
     210media.view.EmbedUrl = __webpack_require__( 92 );
     211media.view.EmbedLink = __webpack_require__( 93 );
     212media.view.EmbedImage = __webpack_require__( 94 );
     213media.view.ImageDetails = __webpack_require__( 95 );
     214media.view.Cropper = __webpack_require__( 96 );
     215media.view.EditImage = __webpack_require__( 99 );
     216media.view.Spinner = __webpack_require__( 100 );
     217
     218
     219/***/ }),
     220/* 27 */
     221/***/ (function(module, exports) {
     222
     223/*globals Backbone, _ */
     224
     225/**
     226 * wp.media.controller.Region
     227 *
     228 * A region is a persistent application layout area.
     229 *
     230 * A region assumes one mode at any time, and can be switched to another.
     231 *
     232 * When mode changes, events are triggered on the region's parent view.
     233 * The parent view will listen to specific events and fill the region with an
     234 * appropriate view depending on mode. For example, a frame listens for the
     235 * 'browse' mode t be activated on the 'content' view and then fills the region
     236 * with an AttachmentsBrowser view.
     237 *
     238 * @class
     239 *
     240 * @param {object}        options          Options hash for the region.
     241 * @param {string}        options.id       Unique identifier for the region.
     242 * @param {Backbone.View} options.view     A parent view the region exists within.
     243 * @param {string}        options.selector jQuery selector for the region within the parent view.
     244 */
     245var Region = function( options ) {
     246        _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
     247};
     248
     249// Use Backbone's self-propagating `extend` inheritance method.
     250Region.extend = Backbone.Model.extend;
     251
     252_.extend( Region.prototype, {
     253        /**
     254         * Activate a mode.
     255         *
     256         * @since 3.5.0
     257         *
     258         * @param {string} mode
     259         *
     260         * @fires this.view#{this.id}:activate:{this._mode}
     261         * @fires this.view#{this.id}:activate
     262         * @fires this.view#{this.id}:deactivate:{this._mode}
     263         * @fires this.view#{this.id}:deactivate
     264         *
     265         * @returns {wp.media.controller.Region} Returns itself to allow chaining.
     266         */
     267        mode: function( mode ) {
     268                if ( ! mode ) {
     269                        return this._mode;
     270                }
     271                // Bail if we're trying to change to the current mode.
     272                if ( mode === this._mode ) {
     273                        return this;
     274                }
     275
     276                /**
     277                 * Region mode deactivation event.
     278                 *
     279                 * @event this.view#{this.id}:deactivate:{this._mode}
     280                 * @event this.view#{this.id}:deactivate
     281                 */
     282                this.trigger('deactivate');
     283
     284                this._mode = mode;
     285                this.render( mode );
     286
     287                /**
     288                 * Region mode activation event.
     289                 *
     290                 * @event this.view#{this.id}:activate:{this._mode}
     291                 * @event this.view#{this.id}:activate
     292                 */
     293                this.trigger('activate');
     294                return this;
     295        },
     296        /**
     297         * Render a mode.
     298         *
     299         * @since 3.5.0
     300         *
     301         * @param {string} mode
     302         *
     303         * @fires this.view#{this.id}:create:{this._mode}
     304         * @fires this.view#{this.id}:create
     305         * @fires this.view#{this.id}:render:{this._mode}
     306         * @fires this.view#{this.id}:render
     307         *
     308         * @returns {wp.media.controller.Region} Returns itself to allow chaining
     309         */
     310        render: function( mode ) {
     311                // If the mode isn't active, activate it.
     312                if ( mode && mode !== this._mode ) {
     313                        return this.mode( mode );
     314                }
     315
     316                var set = { view: null },
     317                        view;
     318
     319                /**
     320                 * Create region view event.
     321                 *
     322                 * Region view creation takes place in an event callback on the frame.
     323                 *
     324                 * @event this.view#{this.id}:create:{this._mode}
     325                 * @event this.view#{this.id}:create
     326                 */
     327                this.trigger( 'create', set );
     328                view = set.view;
     329
     330                /**
     331                 * Render region view event.
     332                 *
     333                 * Region view creation takes place in an event callback on the frame.
     334                 *
     335                 * @event this.view#{this.id}:create:{this._mode}
     336                 * @event this.view#{this.id}:create
     337                 */
     338                this.trigger( 'render', view );
     339                if ( view ) {
     340                        this.set( view );
     341                }
     342                return this;
     343        },
     344
     345        /**
     346         * Get the region's view.
     347         *
     348         * @since 3.5.0
     349         *
     350         * @returns {wp.media.View}
     351         */
     352        get: function() {
     353                return this.view.views.first( this.selector );
     354        },
     355
     356        /**
     357         * Set the region's view as a subview of the frame.
     358         *
     359         * @since 3.5.0
     360         *
     361         * @param {Array|Object} views
     362         * @param {Object} [options={}]
     363         * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
     364         */
     365        set: function( views, options ) {
     366                if ( options ) {
     367                        options.add = false;
     368                }
     369                return this.view.views.set( this.selector, views, options );
     370        },
     371
     372        /**
     373         * Trigger regional view events on the frame.
     374         *
     375         * @since 3.5.0
     376         *
     377         * @param {string} event
     378         * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
     379         */
     380        trigger: function( event ) {
     381                var base, args;
     382
     383                if ( ! this._mode ) {
     384                        return;
     385                }
     386
     387                args = _.toArray( arguments );
     388                base = this.id + ':' + event;
     389
     390                // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
     391                args[0] = base + ':' + this._mode;
     392                this.view.trigger.apply( this.view, args );
     393
     394                // Trigger `{this.id}:{event}` event on the frame.
     395                args[0] = base;
     396                this.view.trigger.apply( this.view, args );
     397                return this;
     398        }
     399});
     400
     401module.exports = Region;
     402
     403
     404/***/ }),
     405/* 28 */
     406/***/ (function(module, exports) {
     407
     408/*globals _, Backbone */
     409
     410/**
     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
     419 *
     420 * @class
     421 * @augments Backbone.Model
     422 * @mixin
     423 * @mixes Backbone.Events
     424 *
     425 * @param {Array} states
     426 */
     427var 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.
     433StateMachine.extend = Backbone.Model.extend;
     434
     435_.extend( StateMachine.prototype, Backbone.Events, {
     436        /**
     437         * Fetch a state.
     438         *
     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                }
     513        }
     514});
     515
     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
     531module.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 */
     557var 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
     778module.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 */
     797var 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
     850module.exports = selectionSync;
     851
     852
     853/***/ }),
     854/* 31 */
     855/***/ (function(module, exports) {
     856
     857/*globals wp, _, Backbone */
     858
     859/**
     860 * wp.media.controller.Library
     861 *
     862 * A state for choosing an attachment or group of attachments from the media library.
     863 *
     864 * @class
     865 * @augments wp.media.controller.State
     866 * @augments Backbone.Model
     867 * @mixes media.selectionSync
     868 *
     869 * @param {object}                          [attributes]                         The attributes hash passed to the state.
     870 * @param {string}                          [attributes.id=library]              Unique identifier.
     871 * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
     872 * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
     873 *                                                                               If one is not supplied, a collection of all attachments will be created.
     874 * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
     875 *                                                                               If the 'selection' attribute is a plain JS object,
     876 *                                                                               a Selection will be created using its values as the selection instance's `props` model.
     877 *                                                                               Otherwise, it will copy the library's `props` model.
     878 * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
     879 * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
     880 *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
     881 * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
     882 * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
     883 * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
     884 * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
     885 * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
     886 *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
     887 * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     888 * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     889 * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     890 * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     891 * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
     892 */
     893var l10n = wp.media.view.l10n,
     894        getUserSetting = window.getUserSetting,
     895        setUserSetting = window.setUserSetting,
     896        Library;
     897
     898Library = wp.media.controller.State.extend({
     899        defaults: {
     900                id:                 'library',
     901                title:              l10n.mediaLibraryTitle,
     902                multiple:           false,
     903                content:            'upload',
     904                menu:               'default',
     905                router:             'browse',
     906                toolbar:            'select',
     907                searchable:         true,
     908                filterable:         false,
     909                sortable:           true,
     910                autoSelect:         true,
     911                describe:           false,
     912                contentUserSetting: true,
     913                syncSelection:      true
     914        },
     915
     916        /**
     917         * If a library isn't provided, query all media items.
     918         * If a selection instance isn't provided, create one.
     919         *
     920         * @since 3.5.0
     921         */
     922        initialize: function() {
     923                var selection = this.get('selection'),
     924                        props;
     925
     926                if ( ! this.get('library') ) {
     927                        this.set( 'library', wp.media.query() );
     928                }
     929
     930                if ( ! ( selection instanceof wp.media.model.Selection ) ) {
     931                        props = selection;
     932
     933                        if ( ! props ) {
     934                                props = this.get('library').props.toJSON();
     935                                props = _.omit( props, 'orderby', 'query' );
     936                        }
     937
     938                        this.set( 'selection', new wp.media.model.Selection( null, {
     939                                multiple: this.get('multiple'),
     940                                props: props
     941                        }) );
     942                }
     943
     944                this.resetDisplays();
     945        },
     946
     947        /**
     948         * @since 3.5.0
     949         */
     950        activate: function() {
     951                this.syncSelection();
     952
     953                wp.Uploader.queue.on( 'add', this.uploading, this );
     954
     955                this.get('selection').on( 'add remove reset', this.refreshContent, this );
     956
     957                if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
     958                        this.frame.on( 'content:activate', this.saveContentMode, this );
     959                        this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
     960                }
     961        },
     962
     963        /**
     964         * @since 3.5.0
     965         */
     966        deactivate: function() {
     967                this.recordSelection();
     968
     969                this.frame.off( 'content:activate', this.saveContentMode, this );
     970
     971                // Unbind all event handlers that use this state as the context
     972                // from the selection.
     973                this.get('selection').off( null, null, this );
     974
     975                wp.Uploader.queue.off( null, null, this );
     976        },
     977
     978        /**
     979         * Reset the library to its initial state.
     980         *
     981         * @since 3.5.0
     982         */
     983        reset: function() {
     984                this.get('selection').reset();
     985                this.resetDisplays();
     986                this.refreshContent();
     987        },
     988
     989        /**
     990         * Reset the attachment display settings defaults to the site options.
     991         *
     992         * If site options don't define them, fall back to a persistent user setting.
     993         *
     994         * @since 3.5.0
     995         */
     996        resetDisplays: function() {
     997                var defaultProps = wp.media.view.settings.defaultProps;
     998                this._displays = [];
     999                this._defaultDisplaySettings = {
     1000                        align: defaultProps.align || getUserSetting( 'align', 'none' ),
     1001                        size:  defaultProps.size  || getUserSetting( 'imgsize', 'medium' ),
     1002                        link:  defaultProps.link  || getUserSetting( 'urlbutton', 'file' )
     1003                };
     1004        },
     1005
     1006        /**
     1007         * Create a model to represent display settings (alignment, etc.) for an attachment.
     1008         *
     1009         * @since 3.5.0
     1010         *
     1011         * @param {wp.media.model.Attachment} attachment
     1012         * @returns {Backbone.Model}
     1013         */
     1014        display: function( attachment ) {
     1015                var displays = this._displays;
     1016
     1017                if ( ! displays[ attachment.cid ] ) {
     1018                        displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
     1019                }
     1020                return displays[ attachment.cid ];
     1021        },
     1022
     1023        /**
     1024         * Given an attachment, create attachment display settings properties.
     1025         *
     1026         * @since 3.6.0
     1027         *
     1028         * @param {wp.media.model.Attachment} attachment
     1029         * @returns {Object}
     1030         */
     1031        defaultDisplaySettings: function( attachment ) {
     1032                var settings = this._defaultDisplaySettings;
     1033                if ( settings.canEmbed = this.canEmbed( attachment ) ) {
     1034                        settings.link = 'embed';
     1035                }
     1036                return settings;
     1037        },
     1038
     1039        /**
     1040         * Whether an attachment can be embedded (audio or video).
     1041         *
     1042         * @since 3.6.0
     1043         *
     1044         * @param {wp.media.model.Attachment} attachment
     1045         * @returns {Boolean}
     1046         */
     1047        canEmbed: function( attachment ) {
     1048                // If uploading, we know the filename but not the mime type.
     1049                if ( ! attachment.get('uploading') ) {
     1050                        var type = attachment.get('type');
     1051                        if ( type !== 'audio' && type !== 'video' ) {
     1052                                return false;
     1053                        }
     1054                }
     1055
     1056                return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
     1057        },
     1058
     1059
     1060        /**
     1061         * If the state is active, no items are selected, and the current
     1062         * content mode is not an option in the state's router (provided
     1063         * the state has a router), reset the content mode to the default.
     1064         *
     1065         * @since 3.5.0
     1066         */
     1067        refreshContent: function() {
     1068                var selection = this.get('selection'),
     1069                        frame = this.frame,
     1070                        router = frame.router.get(),
     1071                        mode = frame.content.mode();
     1072
     1073                if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
     1074                        this.frame.content.render( this.get('content') );
     1075                }
     1076        },
     1077
     1078        /**
     1079         * Callback handler when an attachment is uploaded.
     1080         *
     1081         * Switch to the Media Library if uploaded from the 'Upload Files' tab.
     1082         *
     1083         * Adds any uploading attachments to the selection.
     1084         *
     1085         * If the state only supports one attachment to be selected and multiple
     1086         * attachments are uploaded, the last attachment in the upload queue will
     1087         * be selected.
     1088         *
     1089         * @since 3.5.0
     1090         *
     1091         * @param {wp.media.model.Attachment} attachment
     1092         */
     1093        uploading: function( attachment ) {
     1094                var content = this.frame.content;
     1095
     1096                if ( 'upload' === content.mode() ) {
     1097                        this.frame.content.mode('browse');
     1098                }
     1099
     1100                if ( this.get( 'autoSelect' ) ) {
     1101                        this.get('selection').add( attachment );
     1102                        this.frame.trigger( 'library:selection:add' );
     1103                }
     1104        },
     1105
     1106        /**
     1107         * Persist the mode of the content region as a user setting.
     1108         *
     1109         * @since 3.5.0
     1110         */
     1111        saveContentMode: function() {
     1112                if ( 'browse' !== this.get('router') ) {
     1113                        return;
     1114                }
     1115
     1116                var mode = this.frame.content.mode(),
     1117                        view = this.frame.router.get();
     1118
     1119                if ( view && view.get( mode ) ) {
     1120                        setUserSetting( 'libraryContent', mode );
     1121                }
     1122        }
     1123});
     1124
     1125// Make selectionSync available on any Media Library state.
     1126_.extend( Library.prototype, wp.media.selectionSync );
     1127
     1128module.exports = Library;
     1129
     1130
     1131/***/ }),
     1132/* 32 */
     1133/***/ (function(module, exports) {
     1134
    21135/*globals wp, _ */
    31136
    41137/**
    5  * wp.media.controller.CollectionAdd
    6  *
    7  * A state for adding attachments to a collection (e.g. video playlist).
     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 */
     1161var State = wp.media.controller.State,
     1162        Library = wp.media.controller.Library,
     1163        l10n = wp.media.view.l10n,
     1164        ImageDetails;
     1165
     1166ImageDetails = 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
     1196module.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.
    81209 *
    91210 * @class
     
    121213 * @augments Backbone.Model
    131214 *
     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.
     1236 */
     1237var Library = wp.media.controller.Library,
     1238        l10n = wp.media.view.l10n,
     1239        GalleryEdit;
     1240
     1241GalleryEdit = 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                }
     1274
     1275                Library.prototype.initialize.apply( this, arguments );
     1276        },
     1277
     1278        /**
     1279         * @since 3.5.0
     1280         */
     1281        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
     1344module.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 *
    141363 * @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.
     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.
    171366 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    181367 * @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.
     1368 *                                                                          If one is not supplied, a collection of all images will be created.
    201369 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    211370 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     
    321381 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    331382 *                                                                          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').
    361383 */
    371384var Selection = wp.media.model.Selection,
    381385        Library = wp.media.controller.Library,
    39         CollectionAdd;
    40 
    41 CollectionAdd = Library.extend({
    42         defaults: _.defaults( {
    43                 // Selection defaults. @see media.model.Selection
     1386        l10n = wp.media.view.l10n,
     1387        GalleryAdd;
     1388
     1389GalleryAdd = Library.extend({
     1390        defaults: _.defaults({
     1391                id:            'gallery-library',
     1392                title:         l10n.addToGalleryTitle,
    441393                multiple:      'add',
    45                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
    461394                filterable:    'uploaded',
    47 
     1395                menu:          'gallery',
     1396                toolbar:       'gallery-add',
    481397                priority:      100,
    491398                syncSelection: false
     
    511400
    521401        /**
    53          * @since 3.9.0
     1402         * @since 3.5.0
    541403         */
    551404        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`.
     1405                // If a library wasn't supplied, create a library of images.
    671406                if ( ! this.get('library') ) {
    68                         this.set( 'library', wp.media.query({ type: this.get('type') }) );
    69                 }
     1407                        this.set( 'library', wp.media.query({ type: 'image' }) );
     1408                }
     1409
    701410                Library.prototype.initialize.apply( this, arguments );
    711411        },
    721412
    731413        /**
    74          * @since 3.9.0
     1414         * @since 3.5.0
    751415         */
    761416        activate: function() {
    771417                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 );
     1418                        edit    = this.frame.state('gallery-edit').get('library');
     1419
     1420                if ( this.editLibrary && this.editLibrary !== edit ) {
     1421                        library.unobserve( this.editLibrary );
    831422                }
    841423
     
    941433                library.reset( library.mirroring.models, { silent: true });
    951434                library.observe( edit );
    96                 this.set('editLibrary', edit);
     1435                this.editLibrary = edit;
    971436
    981437                Library.prototype.activate.apply( this, arguments );
     
    1001439});
    1011440
    102 module.exports = CollectionAdd;
    103 
    104 },{}],2:[function(require,module,exports){
     1441module.exports = GalleryAdd;
     1442
     1443
     1444/***/ }),
     1445/* 35 */
     1446/***/ (function(module, exports) {
     1447
    1051448/*globals wp, Backbone */
    1061449
     
    2661609module.exports = CollectionEdit;
    2671610
    268 },{}],3:[function(require,module,exports){
    269 /*globals wp, _, Backbone */
     1611
     1612/***/ }),
     1613/* 36 */
     1614/***/ (function(module, exports) {
     1615
     1616/*globals wp, _ */
    2701617
    2711618/**
    272  * wp.media.controller.Cropper
    273  *
    274  * A state for cropping an image.
     1619 * wp.media.controller.CollectionAdd
     1620 *
     1621 * A state for adding attachments to a collection (e.g. video playlist).
    2751622 *
    2761623 * @class
     1624 * @augments wp.media.controller.Library
    2771625 * @augments wp.media.controller.State
    2781626 * @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').
    2791650 */
    280 var l10n = wp.media.view.l10n,
    281         Cropper;
    282 
    283 Cropper = 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 
     1651var Selection = wp.media.model.Selection,
     1652        Library = wp.media.controller.Library,
     1653        CollectionAdd;
     1654
     1655CollectionAdd = 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         */
    2951690        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                         }
     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 );
    3531703                };
    3541704
    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                 } );
     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 );
    3811713        }
    3821714});
    3831715
    384 module.exports = Cropper;
    385 
    386 },{}],4:[function(require,module,exports){
    387 /*globals wp */
    388 
    389 /**
    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.
    406  */
    407 var l10n = wp.media.view.l10n,
    408         EditImage;
    409 
    410 EditImage = 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                 }) );
    459         }
    460 });
    461 
    462 module.exports = EditImage;
    463 
    464 },{}],5:[function(require,module,exports){
    465 /*globals wp, _, Backbone */
    466 
    467 /**
    468  * wp.media.controller.Embed
    469  *
    470  * A state for embedding media from a URL.
    471  *
    472  * @class
    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.
    487  */
    488 var l10n = wp.media.view.l10n,
    489         $ = Backbone.$,
    490         Embed;
    491 
    492 Embed = 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.
    519          *
    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 
    600 module.exports = Embed;
    601 
    602 },{}],6:[function(require,module,exports){
     1716module.exports = CollectionAdd;
     1717
     1718
     1719/***/ }),
     1720/* 37 */
     1721/***/ (function(module, exports) {
     1722
    6031723/*globals wp, _ */
    6041724
     
    7241844module.exports = FeaturedImage;
    7251845
    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  */
    760 var Selection = wp.media.model.Selection,
    761         Library = wp.media.controller.Library,
    762         l10n = wp.media.view.l10n,
    763         GalleryAdd;
    764 
    765 GalleryAdd = 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 
    817 module.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  */
    854 var Library = wp.media.controller.Library,
    855         l10n = wp.media.view.l10n,
    856         GalleryEdit;
    857 
    858 GalleryEdit = 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         /**
    925          * @since 3.5.0
    926          *
    927          * @param browser
    928          */
    929         gallerySettings: function( browser ) {
    930                 if ( ! this.get('displaySettings') ) {
    931                         return;
    932                 }
    933 
    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                 });
    958         }
    959 });
    960 
    961 module.exports = GalleryEdit;
    962 
    963 },{}],9:[function(require,module,exports){
    964 /*globals wp, _ */
    965 
    966 /**
    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.
    971  *
    972  * @class
    973  * @augments wp.media.controller.State
    974  * @augments Backbone.Model
    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.
    989  */
    990 var State = wp.media.controller.State,
    991         Library = wp.media.controller.Library,
    992         l10n = wp.media.view.l10n,
    993         ImageDetails;
    994 
    995 ImageDetails = 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
    1009          *
    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');
    1022         }
    1023 });
    1024 
    1025 module.exports = ImageDetails;
    1026 
    1027 },{}],10:[function(require,module,exports){
    1028 /*globals wp, _, Backbone */
    1029 
    1030 /**
    1031  * wp.media.controller.Library
    1032  *
    1033  * A state for choosing an attachment or group of attachments from the media library.
    1034  *
    1035  * @class
    1036  * @augments wp.media.controller.State
    1037  * @augments Backbone.Model
    1038  * @mixes media.selectionSync
    1039  *
    1040  * @param {object}                          [attributes]                         The attributes hash passed to the state.
    1041  * @param {string}                          [attributes.id=library]              Unique identifier.
    1042  * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
    1043  * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
    1044  *                                                                               If one is not supplied, a collection of all attachments will be created.
    1045  * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
    1046  *                                                                               If the 'selection' attribute is a plain JS object,
    1047  *                                                                               a Selection will be created using its values as the selection instance's `props` model.
    1048  *                                                                               Otherwise, it will copy the library's `props` model.
    1049  * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
    1050  * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
    1051  *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
    1052  * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
    1053  * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
    1054  * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
    1055  * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
    1056  * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
    1057  *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
    1058  * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1059  * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1060  * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1061  * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1062  * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    1063  */
    1064 var l10n = wp.media.view.l10n,
    1065         getUserSetting = window.getUserSetting,
    1066         setUserSetting = window.setUserSetting,
    1067         Library;
    1068 
    1069 Library = wp.media.controller.State.extend({
    1070         defaults: {
    1071                 id:                 'library',
    1072                 title:              l10n.mediaLibraryTitle,
    1073                 multiple:           false,
    1074                 content:            'upload',
    1075                 menu:               'default',
    1076                 router:             'browse',
    1077                 toolbar:            'select',
    1078                 searchable:         true,
    1079                 filterable:         false,
    1080                 sortable:           true,
    1081                 autoSelect:         true,
    1082                 describe:           false,
    1083                 contentUserSetting: true,
    1084                 syncSelection:      true
    1085         },
    1086 
    1087         /**
    1088          * If a library isn't provided, query all media items.
    1089          * If a selection instance isn't provided, create one.
    1090          *
    1091          * @since 3.5.0
    1092          */
    1093         initialize: function() {
    1094                 var selection = this.get('selection'),
    1095                         props;
    1096 
    1097                 if ( ! this.get('library') ) {
    1098                         this.set( 'library', wp.media.query() );
    1099                 }
    1100 
    1101                 if ( ! ( selection instanceof wp.media.model.Selection ) ) {
    1102                         props = selection;
    1103 
    1104                         if ( ! props ) {
    1105                                 props = this.get('library').props.toJSON();
    1106                                 props = _.omit( props, 'orderby', 'query' );
    1107                         }
    1108 
    1109                         this.set( 'selection', new wp.media.model.Selection( null, {
    1110                                 multiple: this.get('multiple'),
    1111                                 props: props
    1112                         }) );
    1113                 }
    1114 
    1115                 this.resetDisplays();
    1116         },
    1117 
    1118         /**
    1119          * @since 3.5.0
    1120          */
    1121         activate: function() {
    1122                 this.syncSelection();
    1123 
    1124                 wp.Uploader.queue.on( 'add', this.uploading, this );
    1125 
    1126                 this.get('selection').on( 'add remove reset', this.refreshContent, this );
    1127 
    1128                 if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
    1129                         this.frame.on( 'content:activate', this.saveContentMode, this );
    1130                         this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
    1131                 }
    1132         },
    1133 
    1134         /**
    1135          * @since 3.5.0
    1136          */
    1137         deactivate: function() {
    1138                 this.recordSelection();
    1139 
    1140                 this.frame.off( 'content:activate', this.saveContentMode, this );
    1141 
    1142                 // Unbind all event handlers that use this state as the context
    1143                 // from the selection.
    1144                 this.get('selection').off( null, null, this );
    1145 
    1146                 wp.Uploader.queue.off( null, null, this );
    1147         },
    1148 
    1149         /**
    1150          * Reset the library to its initial state.
    1151          *
    1152          * @since 3.5.0
    1153          */
    1154         reset: function() {
    1155                 this.get('selection').reset();
    1156                 this.resetDisplays();
    1157                 this.refreshContent();
    1158         },
    1159 
    1160         /**
    1161          * Reset the attachment display settings defaults to the site options.
    1162          *
    1163          * If site options don't define them, fall back to a persistent user setting.
    1164          *
    1165          * @since 3.5.0
    1166          */
    1167         resetDisplays: function() {
    1168                 var defaultProps = wp.media.view.settings.defaultProps;
    1169                 this._displays = [];
    1170                 this._defaultDisplaySettings = {
    1171                         align: defaultProps.align || getUserSetting( 'align', 'none' ),
    1172                         size:  defaultProps.size  || getUserSetting( 'imgsize', 'medium' ),
    1173                         link:  defaultProps.link  || getUserSetting( 'urlbutton', 'file' )
    1174                 };
    1175         },
    1176 
    1177         /**
    1178          * Create a model to represent display settings (alignment, etc.) for an attachment.
    1179          *
    1180          * @since 3.5.0
    1181          *
    1182          * @param {wp.media.model.Attachment} attachment
    1183          * @returns {Backbone.Model}
    1184          */
    1185         display: function( attachment ) {
    1186                 var displays = this._displays;
    1187 
    1188                 if ( ! displays[ attachment.cid ] ) {
    1189                         displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
    1190                 }
    1191                 return displays[ attachment.cid ];
    1192         },
    1193 
    1194         /**
    1195          * Given an attachment, create attachment display settings properties.
    1196          *
    1197          * @since 3.6.0
    1198          *
    1199          * @param {wp.media.model.Attachment} attachment
    1200          * @returns {Object}
    1201          */
    1202         defaultDisplaySettings: function( attachment ) {
    1203                 var settings = this._defaultDisplaySettings;
    1204                 if ( settings.canEmbed = this.canEmbed( attachment ) ) {
    1205                         settings.link = 'embed';
    1206                 }
    1207                 return settings;
    1208         },
    1209 
    1210         /**
    1211          * Whether an attachment can be embedded (audio or video).
    1212          *
    1213          * @since 3.6.0
    1214          *
    1215          * @param {wp.media.model.Attachment} attachment
    1216          * @returns {Boolean}
    1217          */
    1218         canEmbed: function( attachment ) {
    1219                 // If uploading, we know the filename but not the mime type.
    1220                 if ( ! attachment.get('uploading') ) {
    1221                         var type = attachment.get('type');
    1222                         if ( type !== 'audio' && type !== 'video' ) {
    1223                                 return false;
    1224                         }
    1225                 }
    1226 
    1227                 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
    1228         },
    1229 
    1230 
    1231         /**
    1232          * If the state is active, no items are selected, and the current
    1233          * content mode is not an option in the state's router (provided
    1234          * the state has a router), reset the content mode to the default.
    1235          *
    1236          * @since 3.5.0
    1237          */
    1238         refreshContent: function() {
    1239                 var selection = this.get('selection'),
    1240                         frame = this.frame,
    1241                         router = frame.router.get(),
    1242                         mode = frame.content.mode();
    1243 
    1244                 if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
    1245                         this.frame.content.render( this.get('content') );
    1246                 }
    1247         },
    1248 
    1249         /**
    1250          * Callback handler when an attachment is uploaded.
    1251          *
    1252          * Switch to the Media Library if uploaded from the 'Upload Files' tab.
    1253          *
    1254          * Adds any uploading attachments to the selection.
    1255          *
    1256          * If the state only supports one attachment to be selected and multiple
    1257          * attachments are uploaded, the last attachment in the upload queue will
    1258          * be selected.
    1259          *
    1260          * @since 3.5.0
    1261          *
    1262          * @param {wp.media.model.Attachment} attachment
    1263          */
    1264         uploading: function( attachment ) {
    1265                 var content = this.frame.content;
    1266 
    1267                 if ( 'upload' === content.mode() ) {
    1268                         this.frame.content.mode('browse');
    1269                 }
    1270 
    1271                 if ( this.get( 'autoSelect' ) ) {
    1272                         this.get('selection').add( attachment );
    1273                         this.frame.trigger( 'library:selection:add' );
    1274                 }
    1275         },
    1276 
    1277         /**
    1278          * Persist the mode of the content region as a user setting.
    1279          *
    1280          * @since 3.5.0
    1281          */
    1282         saveContentMode: function() {
    1283                 if ( 'browse' !== this.get('router') ) {
    1284                         return;
    1285                 }
    1286 
    1287                 var mode = this.frame.content.mode(),
    1288                         view = this.frame.router.get();
    1289 
    1290                 if ( view && view.get( mode ) ) {
    1291                         setUserSetting( 'libraryContent', mode );
    1292                 }
    1293         }
    1294 });
    1295 
    1296 // Make selectionSync available on any Media Library state.
    1297 _.extend( Library.prototype, wp.media.selectionSync );
    1298 
    1299 module.exports = Library;
    1300 
    1301 },{}],11:[function(require,module,exports){
    1302 /*globals wp, _ */
    1303 
    1304 /**
    1305  * wp.media.controller.MediaLibrary
    1306  *
    1307  * @class
    1308  * @augments wp.media.controller.Library
    1309  * @augments wp.media.controller.State
    1310  * @augments Backbone.Model
    1311  */
    1312 var Library = wp.media.controller.Library,
    1313         MediaLibrary;
    1314 
    1315 MediaLibrary = 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 }) );
    1334 
    1335                 Library.prototype.initialize.apply( this, arguments );
    1336         },
    1337 
    1338         /**
    1339          * @since 3.9.0
    1340          */
    1341         activate: function() {
    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                 }
    1347                 Library.prototype.activate.apply( this, arguments );
    1348         }
    1349 });
    1350 
    1351 module.exports = MediaLibrary;
    1352 
    1353 },{}],12:[function(require,module,exports){
    1354 /*globals Backbone, _ */
    1355 
    1356 /**
    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.
    1368  *
    1369  * @class
    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.
    1375  */
    1376 var Region = function( options ) {
    1377         _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
    1378 };
    1379 
    1380 // Use Backbone's self-propagating `extend` inheritance method.
    1381 Region.extend = Backbone.Model.extend;
    1382 
    1383 _.extend( Region.prototype, {
    1384         /**
    1385          * Activate a mode.
    1386          *
    1387          * @since 3.5.0
    1388          *
    1389          * @param {string} mode
    1390          *
    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 ) {
    1515                         return;
    1516                 }
    1517 
    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;
    1529         }
    1530 });
    1531 
    1532 module.exports = Region;
    1533 
    1534 },{}],13:[function(require,module,exports){
     1846
     1847/***/ }),
     1848/* 38 */
     1849/***/ (function(module, exports) {
     1850
    15351851/*globals wp, _ */
    15361852
     
    16421958module.exports = ReplaceImage;
    16431959
    1644 },{}],14:[function(require,module,exports){
    1645 /*globals _, Backbone */
     1960
     1961/***/ }),
     1962/* 39 */
     1963/***/ (function(module, exports) {
     1964
     1965/*globals wp */
    16461966
    16471967/**
    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
     1968 * wp.media.controller.EditImage
     1969 *
     1970 * A state for editing (cropping, etc.) an image.
    16561971 *
    16571972 * @class
     1973 * @augments wp.media.controller.State
    16581974 * @augments Backbone.Model
    1659  * @mixin
    1660  * @mixes Backbone.Events
    1661  *
    1662  * @param {Array} states
     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.
    16631984 */
    1664 var 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.
    1670 StateMachine.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                 }
     1985var l10n = wp.media.view.l10n,
     1986        EditImage;
     1987
     1988EditImage = 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                }) );
    17502037        }
    17512038});
    17522039
    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 );
     2040module.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 */
     2057var Library = wp.media.controller.Library,
     2058        MediaLibrary;
     2059
     2060MediaLibrary = 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
     2096module.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 */
     2126var l10n = wp.media.view.l10n,
     2127        $ = Backbone.$,
     2128        Embed;
     2129
     2130Embed = 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
     2238module.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 */
     2256var l10n = wp.media.view.l10n,
     2257        Cropper;
     2258
     2259Cropper = 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
     2360module.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 */
     2387var 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
     2434module.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 */
     2458var 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
     2606module.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 */
     2627var Frame = wp.media.view.Frame,
     2628        $ = jQuery,
     2629        MediaFrame;
     2630
     2631MediaFrame = 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                }
    17642855                return this;
    17652856        };
    17662857});
    17672858
    1768 module.exports = StateMachine;
    1769 
    1770 },{}],15:[function(require,module,exports){
    1771 /*globals _, Backbone */
     2859module.exports = MediaFrame;
     2860
     2861
     2862/***/ }),
     2863/* 48 */
     2864/***/ (function(module, exports) {
     2865
     2866/*globals wp, _ */
    17722867
    17732868/**
    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.
     2869 * wp.media.view.MediaFrame.Select
     2870 *
     2871 * A frame for selecting an item or items from the media library.
    17862872 *
    17872873 * @class
    1788  * @augments Backbone.Model
     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
    17892880 */
    1790 var State = Backbone.Model.extend({
    1791         /**
    1792          * Constructor.
     2881
     2882var MediaFrame = wp.media.view.MediaFrame,
     2883        l10n = wp.media.view.l10n,
     2884        Select;
     2885
     2886Select = 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.
    17932905         *
    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.
     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.
    18132909         *
    1814          * @abstract
    1815          * @since 3.5.0
    1816          */
    1817         ready: function() {},
    1818 
    1819         /**
    1820          * Activate event callback.
     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 ) {
     2934                        return;
     2935                }
     2936
     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.
    18212951         *
    1822          * @abstract
    1823          * @since 3.5.0
    1824          */
    1825         activate: function() {},
    1826 
    1827         /**
    1828          * Deactivate event callback.
     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.
    18292964         *
    1830          * @abstract
    1831          * @since 3.5.0
    1832          */
    1833         deactivate: function() {},
    1834 
    1835         /**
    1836          * Reset event callback.
     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
     2976                        }
     2977                });
     2978        },
     2979
     2980        /**
     2981         * Render callback for the content region in the `browse` mode.
    18372982         *
    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 ) {
    1920                         return;
    1921                 }
    1922 
    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.
     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
    19723023         *
    1973          * @access private
    1974          * @since 3.5.0
    1975          *
    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;
    1988                         }
    1989                 }
    1990 
    1991                 if ( ! menuItem ) {
    1992                         return;
    1993                 }
    1994 
    1995                 view.set( this.id, menuItem );
     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 );
    19963033        }
    19973034});
    19983035
    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         };
     3036module.exports = Select;
     3037
     3038
     3039/***/ }),
     3040/* 49 */
     3041/***/ (function(module, exports) {
     3042
     3043/*globals wp, _ */
     3044
     3045/**
     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
     3058 */
     3059var Select = wp.media.view.MediaFrame.Select,
     3060        Library = wp.media.controller.Library,
     3061        l10n = wp.media.view.l10n,
     3062        Post;
     3063
     3064Post = 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                }) );
     3774        }
    20093775});
    20103776
    2011 module.exports = State;
    2012 
    2013 },{}],16:[function(require,module,exports){
    2014 /*globals _ */
     3777module.exports = Post;
     3778
     3779
     3780/***/ }),
     3781/* 50 */
     3782/***/ (function(module, exports) {
     3783
     3784/*globals wp */
    20153785
    20163786/**
    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
     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
    20253800 */
    2026 var 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 ) {
     3801var Select = wp.media.view.MediaFrame.Select,
     3802        l10n = wp.media.view.l10n,
     3803        ImageDetails;
     3804
     3805ImageDetails = 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 ) {
    20353870                        return;
    20363871                }
    20373872
    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.
     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                }) );
     3956        }
     3957
     3958});
     3959
     3960module.exports = ImageDetails;
     3961
     3962
     3963/***/ }),
     3964/* 51 */
     3965/***/ (function(module, exports) {
     3966
     3967/*globals wp, _, jQuery */
     3968
     3969/**
     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 */
     3979var $ = jQuery,
     3980        Modal;
     3981
     3982Modal = 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.
    20574154         *
    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 ) {
     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
     4179module.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 */
     4194var 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 ) {
    20654211                        return;
    20664212                }
    20674213
    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;
     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                }
    20764225        }
    2077 };
    2078 
    2079 module.exports = selectionSync;
    2080 
    2081 },{}],17:[function(require,module,exports){
    2082 /*globals wp, jQuery, _, Backbone */
    2083 
    2084 var media = wp.media,
    2085         $ = jQuery,
    2086         l10n;
    2087 
    2088 media.isTouchDevice = ( 'ontouchend' in document );
    2089 
    2090 // Link any localized strings.
    2091 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
    2092 
    2093 // Link any settings.
    2094 media.view.settings = l10n.settings || {};
    2095 delete l10n.settings;
    2096 
    2097 // Copy the `post` setting over to the model settings.
    2098 media.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 }());
     4226
     4227});
     4228
     4229module.exports = FocusManager;
     4230
     4231
     4232/***/ }),
     4233/* 53 */
     4234/***/ (function(module, exports) {
     4235
     4236/*globals wp, _, jQuery */
    21184237
    21194238/**
    2120  * A shared event bus used to provide events into
    2121  * the media workflows that 3rd-party devs can use to hook
    2122  * in.
     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]
    21234253 */
    2124 media.events = _.extend( {}, Backbone.Events );
     4254var $ = jQuery,
     4255        UploaderWindow;
     4256
     4257UploaderWindow = 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
     4346module.exports = UploaderWindow;
     4347
     4348
     4349/***/ }),
     4350/* 54 */
     4351/***/ (function(module, exports) {
     4352
     4353/*globals wp, _, jQuery */
    21254354
    21264355/**
    2127  * Makes it easier to bind events using transitions.
    2128  *
    2129  * @param {string} selector
    2130  * @param {Number} sensitivity
    2131  * @returns {Promise}
    2132  */
    2133 media.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();
    2152         }
    2153 
    2154         return deferred.promise();
    2155 };
    2156 
    2157 media.controller.Region = require( './controllers/region.js' );
    2158 media.controller.StateMachine = require( './controllers/state-machine.js' );
    2159 media.controller.State = require( './controllers/state.js' );
    2160 
    2161 media.selectionSync = require( './utils/selection-sync.js' );
    2162 media.controller.Library = require( './controllers/library.js' );
    2163 media.controller.ImageDetails = require( './controllers/image-details.js' );
    2164 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
    2165 media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
    2166 media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
    2167 media.controller.CollectionAdd = require( './controllers/collection-add.js' );
    2168 media.controller.FeaturedImage = require( './controllers/featured-image.js' );
    2169 media.controller.ReplaceImage = require( './controllers/replace-image.js' );
    2170 media.controller.EditImage = require( './controllers/edit-image.js' );
    2171 media.controller.MediaLibrary = require( './controllers/media-library.js' );
    2172 media.controller.Embed = require( './controllers/embed.js' );
    2173 media.controller.Cropper = require( './controllers/cropper.js' );
    2174 
    2175 media.View = require( './views/view.js' );
    2176 media.view.Frame = require( './views/frame.js' );
    2177 media.view.MediaFrame = require( './views/media-frame.js' );
    2178 media.view.MediaFrame.Select = require( './views/frame/select.js' );
    2179 media.view.MediaFrame.Post = require( './views/frame/post.js' );
    2180 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
    2181 media.view.Modal = require( './views/modal.js' );
    2182 media.view.FocusManager = require( './views/focus-manager.js' );
    2183 media.view.UploaderWindow = require( './views/uploader/window.js' );
    2184 media.view.EditorUploader = require( './views/uploader/editor.js' );
    2185 media.view.UploaderInline = require( './views/uploader/inline.js' );
    2186 media.view.UploaderStatus = require( './views/uploader/status.js' );
    2187 media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
    2188 media.view.Toolbar = require( './views/toolbar.js' );
    2189 media.view.Toolbar.Select = require( './views/toolbar/select.js' );
    2190 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
    2191 media.view.Button = require( './views/button.js' );
    2192 media.view.ButtonGroup = require( './views/button-group.js' );
    2193 media.view.PriorityList = require( './views/priority-list.js' );
    2194 media.view.MenuItem = require( './views/menu-item.js' );
    2195 media.view.Menu = require( './views/menu.js' );
    2196 media.view.RouterItem = require( './views/router-item.js' );
    2197 media.view.Router = require( './views/router.js' );
    2198 media.view.Sidebar = require( './views/sidebar.js' );
    2199 media.view.Attachment = require( './views/attachment.js' );
    2200 media.view.Attachment.Library = require( './views/attachment/library.js' );
    2201 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
    2202 media.view.Attachments = require( './views/attachments.js' );
    2203 media.view.Search = require( './views/search.js' );
    2204 media.view.AttachmentFilters = require( './views/attachment-filters.js' );
    2205 media.view.DateFilter = require( './views/attachment-filters/date.js' );
    2206 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
    2207 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
    2208 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
    2209 media.view.Selection = require( './views/selection.js' );
    2210 media.view.Attachment.Selection = require( './views/attachment/selection.js' );
    2211 media.view.Attachments.Selection = require( './views/attachments/selection.js' );
    2212 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
    2213 media.view.Settings = require( './views/settings.js' );
    2214 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
    2215 media.view.Settings.Gallery = require( './views/settings/gallery.js' );
    2216 media.view.Settings.Playlist = require( './views/settings/playlist.js' );
    2217 media.view.Attachment.Details = require( './views/attachment/details.js' );
    2218 media.view.AttachmentCompat = require( './views/attachment-compat.js' );
    2219 media.view.Iframe = require( './views/iframe.js' );
    2220 media.view.Embed = require( './views/embed.js' );
    2221 media.view.Label = require( './views/label.js' );
    2222 media.view.EmbedUrl = require( './views/embed/url.js' );
    2223 media.view.EmbedLink = require( './views/embed/link.js' );
    2224 media.view.EmbedImage = require( './views/embed/image.js' );
    2225 media.view.ImageDetails = require( './views/image-details.js' );
    2226 media.view.Cropper = require( './views/cropper.js' );
    2227 media.view.EditImage = require( './views/edit-image.js' );
    2228 media.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 _ */
    2232 
    2233 /**
    2234  * wp.media.view.AttachmentCompat
    2235  *
    2236  * A view to display fields added via the `attachment_fields_to_edit` filter.
     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
    22374360 *
    22384361 * @class
     
    22424365 */
    22434366var View = wp.media.View,
    2244         AttachmentCompat;
    2245 
    2246 AttachmentCompat = View.extend({
    2247         tagName:   'form',
    2248         className: 'compat-item',
     4367        l10n = wp.media.view.l10n,
     4368        $ = jQuery,
     4369        EditorUploader;
     4370
     4371EditorUploader = 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         */
     4384        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
     4571module.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 */
     4590var View = wp.media.View,
     4591        UploaderInline;
     4592
     4593UploaderInline = View.extend({
     4594        tagName:   'div',
     4595        className: 'uploader-inline',
     4596        template:  wp.template('uploader-inline'),
    22494597
    22504598        events: {
    2251                 'submit':          'preventDefault',
    2252                 'change input':    'save',
    2253                 'change select':   'save',
    2254                 'change textarea': 'save'
     4599                'click .close': 'hide'
    22554600        },
    22564601
    22574602        initialize: function() {
    2258                 this.listenTo( this.model, 'change:compat', this.render );
    2259         },
    2260         /**
    2261          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     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
    22624641         */
    22634642        dispose: function() {
    2264                 if ( this.$(':focus').length ) {
    2265                         this.save();
     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
     4708module.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 */
     4727var View = wp.media.View,
     4728        UploaderStatus;
     4729
     4730UploaderStatus = 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
     4852module.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 */
     4869var UploaderStatusError = wp.media.View.extend({
     4870        className: 'upload-error',
     4871        template:  wp.template('uploader-status-error')
     4872});
     4873
     4874module.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 */
     4894var View = wp.media.View,
     4895        Toolbar;
     4896
     4897Toolbar = 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 );
    22664942                }
    22674943                /**
     
    22704946                return View.prototype.dispose.apply( this, arguments );
    22714947        },
    2272         /**
    2273          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     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
     4987                return this;
     4988        },
     4989        /**
     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 );
     5036                });
     5037        }
     5038});
     5039
     5040module.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 */
     5058var Toolbar = wp.media.view.Toolbar,
     5059        l10n = wp.media.view.l10n,
     5060        Select;
     5061
     5062Select = 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
     5116module.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 */
     5135var Select = wp.media.view.Toolbar.Select,
     5136        l10n = wp.media.view.l10n,
     5137        Embed;
     5138
     5139Embed = 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
     5159module.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 */
     5176var 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
    22745216         */
    22755217        render: function() {
    2276                 var compat = this.model.get('compat');
    2277                 if ( ! compat || ! compat.item ) {
     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
     5251module.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 */
     5268var $ = Backbone.$,
     5269        ButtonGroup;
     5270
     5271ButtonGroup = 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
     5303module.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 */
     5320var 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
     5406module.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 */
     5423var $ = jQuery,
     5424        MenuItem;
     5425
     5426MenuItem = 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
     5484module.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 */
     5500var MenuItem = wp.media.view.MenuItem,
     5501        PriorityList = wp.media.view.PriorityList,
     5502        Menu;
     5503
     5504Menu = 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 ) {
    22785573                        return;
    22795574                }
    22805575
     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
     5605module.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 */
     5621var 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
     5633module.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 */
     5652var Menu = wp.media.view.Menu,
     5653        Router;
     5654
     5655Router = 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
     5676module.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 */
     5692var Sidebar = wp.media.view.PriorityList.extend({
     5693        className: 'media-sidebar'
     5694});
     5695
     5696module.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 */
     5713var View = wp.media.View,
     5714        $ = jQuery,
     5715        Attachment;
     5716
     5717Attachment = 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
    22815835                this.views.detach();
    2282                 this.$el.html( compat.item );
     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
    22835852                this.views.render();
     5853
    22845854                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 );
    22856054        },
    22866055        /**
     
    22916060        },
    22926061        /**
     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        /**
    22936095         * @param {Object} event
    22946096         */
    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;
     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 );
    23046143                });
    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'] );
     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                }
    23126222        }
    23136223});
    23146224
    2315 module.exports = AttachmentCompat;
    2316 
    2317 },{}],19:[function(require,module,exports){
     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
     6256module.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 */
     6274var Library = wp.media.view.Attachment.extend({
     6275        buttons: {
     6276                check: true
     6277        }
     6278});
     6279
     6280module.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 */
     6298var EditLibrary = wp.media.view.Attachment.extend({
     6299        buttons: {
     6300                close: true
     6301        }
     6302});
     6303
     6304module.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 */
     6321var View = wp.media.View,
     6322        $ = jQuery,
     6323        Attachments;
     6324
     6325Attachments = 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
     6609module.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 */
     6626var l10n = wp.media.view.l10n,
     6627        Search;
     6628
     6629Search = 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
     6663module.exports = Search;
     6664
     6665
     6666/***/ }),
     6667/* 74 */
     6668/***/ (function(module, exports) {
     6669
    23186670/*globals _, jQuery */
    23196671
     
    23946746module.exports = AttachmentFilters;
    23956747
    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  */
    2408 var l10n = wp.media.view.l10n,
    2409         All;
    2410 
    2411 All = 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 
    2486 module.exports = All;
    2487 
    2488 },{}],21:[function(require,module,exports){
     6748
     6749/***/ }),
     6750/* 75 */
     6751/***/ (function(module, exports) {
     6752
    24896753/*globals wp, _ */
    24906754
     
    25296793module.exports = DateFilter;
    25306794
    2531 },{}],22:[function(require,module,exports){
     6795
     6796/***/ }),
     6797/* 76 */
     6798/***/ (function(module, exports) {
     6799
    25326800/*globals wp */
    25336801
     
    25906858module.exports = Uploaded;
    25916859
    2592 },{}],23:[function(require,module,exports){
    2593 /*globals wp, _, jQuery */
     6860
     6861/***/ }),
     6862/* 77 */
     6863/***/ (function(module, exports) {
     6864
     6865/*globals wp */
    25946866
    25956867/**
    2596  * wp.media.view.Attachment
     6868 * wp.media.view.AttachmentFilters.All
    25976869 *
    25986870 * @class
     6871 * @augments wp.media.view.AttachmentFilters
    25996872 * @augments wp.media.View
    26006873 * @augments wp.Backbone.View
    26016874 * @augments Backbone.View
    26026875 */
    2603 var View = wp.media.View,
    2604         $ = jQuery,
    2605         Attachment;
    2606 
    2607 Attachment = 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' )
     6876var l10n = wp.media.view.l10n,
     6877        All;
     6878
     6879All = 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                        };
     6894                });
     6895
     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
    26196906                };
    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
    2798                 });
    2799 
    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')
     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
    29826932                };
    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                 }
     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;
    31126951        }
    31136952});
    31146953
    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 
    3146 module.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  */
    3160 var Attachment = wp.media.view.Attachment,
    3161         l10n = wp.media.view.l10n,
    3162         Details;
    3163 
    3164 Details = 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 
    3288 module.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  */
    3302 var EditLibrary = wp.media.view.Attachment.extend({
    3303         buttons: {
    3304                 close: true
    3305         }
    3306 });
    3307 
    3308 module.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  */
    3323 var EditSelection = wp.media.view.Attachment.Selection.extend({
    3324         buttons: {
    3325                 close: true
    3326         }
    3327 });
    3328 
    3329 module.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  */
    3343 var Library = wp.media.view.Attachment.extend({
    3344         buttons: {
    3345                 check: true
    3346         }
    3347 });
    3348 
    3349 module.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  */
    3363 var 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 
    3373 module.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  */
    3386 var View = wp.media.View,
    3387         $ = jQuery,
    3388         Attachments;
    3389 
    3390 Attachments = 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 
    3674 module.exports = Attachments;
    3675 
    3676 },{}],30:[function(require,module,exports){
     6954module.exports = All;
     6955
     6956
     6957/***/ }),
     6958/* 78 */
     6959/***/ (function(module, exports) {
     6960
    36776961/*globals wp, _, jQuery */
    36786962
     
    41207404module.exports = AttachmentsBrowser;
    41217405
    4122 },{}],31:[function(require,module,exports){
     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 */
     7421var l10n = wp.media.view.l10n,
     7422        Selection;
     7423
     7424Selection = 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
     7493module.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 */
     7511var 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
     7521module.exports = Selection;
     7522
     7523
     7524/***/ }),
     7525/* 81 */
     7526/***/ (function(module, exports) {
     7527
    41237528/*globals wp, _ */
    41247529
     
    41527557module.exports = Selection;
    41537558
    4154 },{}],32:[function(require,module,exports){
    4155 /*globals _, Backbone */
     7559
     7560/***/ }),
     7561/* 82 */
     7562/***/ (function(module, exports) {
     7563
     7564/*globals wp */
    41567565
    41577566/**
    4158  * wp.media.view.ButtonGroup
     7567 * wp.media.view.Attachments.EditSelection
    41597568 *
    41607569 * @class
     7570 * @augments wp.media.view.Attachment.Selection
     7571 * @augments wp.media.view.Attachment
    41617572 * @augments wp.media.View
    41627573 * @augments wp.Backbone.View
    41637574 * @augments Backbone.View
    41647575 */
    4165 var $ = Backbone.$,
    4166         ButtonGroup;
    4167 
    4168 ButtonGroup = 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;
     7576var EditSelection = wp.media.view.Attachment.Selection.extend({
     7577        buttons: {
     7578                close: true
    41977579        }
    41987580});
    41997581
    4200 module.exports = ButtonGroup;
    4201 
    4202 },{}],33:[function(require,module,exports){
     7582module.exports = EditSelection;
     7583
     7584
     7585/***/ }),
     7586/* 83 */
     7587/***/ (function(module, exports) {
     7588
    42037589/*globals _, Backbone */
    42047590
    42057591/**
    4206  * wp.media.view.Button
    4207  *
    4208  * @class
    4209  * @augments wp.media.View
    4210  * @augments wp.Backbone.View
    4211  * @augments Backbone.View
    4212  */
    4213 var 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 
    4288 module.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.
     7592 * wp.media.view.Settings
    43017593 *
    43027594 * @class
     
    43067598 */
    43077599var View = wp.media.View,
    4308         UploaderStatus = wp.media.view.UploaderStatus,
    4309         l10n = wp.media.view.l10n,
    4310         $ = jQuery,
    4311         Cropper;
    4312 
    4313 Cropper = View.extend({
    4314         className: 'crop-content',
    4315         template: wp.template('crop-content'),
     7600        $ = Backbone.$,
     7601        Settings;
     7602
     7603Settings = View.extend({
     7604        events: {
     7605                'click button':    'updateHandler',
     7606                'change input':    'updateHandler',
     7607                'change select':   'updateHandler',
     7608                'change textarea': 'updateHandler'
     7609        },
     7610
    43167611        initialize: function() {
    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         },
     7612                this.model = this.model || new Backbone.Model();
     7613                this.listenTo( this.model, 'change', this.updateChanges );
     7614        },
     7615
    43317616        prepare: function() {
    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 });
     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                }
    43547706        }
    43557707});
    43567708
    4357 module.exports = Cropper;
    4358 
    4359 },{}],35:[function(require,module,exports){
     7709module.exports = Settings;
     7710
     7711
     7712/***/ }),
     7713/* 84 */
     7714/***/ (function(module, exports) {
     7715
    43607716/*globals wp, _ */
    43617717
    43627718/**
    4363  * wp.media.view.EditImage
     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 */
     7727var Settings = wp.media.view.Settings,
     7728        AttachmentDisplay;
     7729
     7730AttachmentDisplay = 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
     7809module.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 */
     7827var Gallery = wp.media.view.Settings.extend({
     7828        className: 'collection-settings gallery-settings',
     7829        template:  wp.template('gallery-settings')
     7830});
     7831
     7832module.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 */
     7850var Playlist = wp.media.view.Settings.extend({
     7851        className: 'collection-settings playlist-settings',
     7852        template:  wp.template('playlist-settings')
     7853});
     7854
     7855module.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 */
     7873var Attachment = wp.media.view.Attachment,
     7874        l10n = wp.media.view.l10n,
     7875        Details;
     7876
     7877Details = Attachment.extend({
     7878        tagName:   'div',
     7879        className: 'attachment-details',
     7880        template:  wp.template('attachment-details'),
     7881
     7882        attributes: function() {
     7883                return {
     7884                        'tabIndex':     0,
     7885                        'data-id':      this.model.get( 'id' )
     7886                };
     7887        },
     7888
     7889        events: {
     7890                'change [data-setting]':          'updateSetting',
     7891                'change [data-setting] input':    'updateSetting',
     7892                'change [data-setting] select':   'updateSetting',
     7893                'change [data-setting] textarea': 'updateSetting',
     7894                'click .delete-attachment':       'deleteAttachment',
     7895                'click .trash-attachment':        'trashAttachment',
     7896                'click .untrash-attachment':      'untrashAttachment',
     7897                'click .edit-attachment':         'editAttachment',
     7898                '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
     8001module.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.
    43648014 *
    43658015 * @class
     
    43698019 */
    43708020var View = wp.media.View,
    4371         EditImage;
    4372 
    4373 EditImage = 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 ) );
     8021        AttachmentCompat;
     8022
     8023AttachmentCompat = 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'] );
    44118089        }
    4412 
    44138090});
    44148091
    4415 module.exports = EditImage;
    4416 
    4417 },{}],36:[function(require,module,exports){
     8092module.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 */
     8107var 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
     8120module.exports = Iframe;
     8121
     8122
     8123/***/ }),
     8124/* 90 */
     8125/***/ (function(module, exports) {
     8126
    44188127/**
    44198128 * wp.media.view.Embed
     
    44798188module.exports = Embed;
    44808189
    4481 },{}],37:[function(require,module,exports){
     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 */
     8203var 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
     8218module.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 */
     8235var View = wp.media.View,
     8236        $ = jQuery,
     8237        EmbedUrl;
     8238
     8239EmbedUrl = 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
     8303module.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 */
     8321var $ = jQuery,
     8322        EmbedLink;
     8323
     8324EmbedLink = 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
     8380module.exports = EmbedLink;
     8381
     8382
     8383/***/ }),
     8384/* 94 */
     8385/***/ (function(module, exports) {
     8386
    44828387/*globals wp */
    44838388
     
    45148419module.exports = EmbedImage;
    45158420
    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  */
    4528 var $ = jQuery,
    4529         EmbedLink;
    4530 
    4531 EmbedLink = 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 
    4587 module.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  */
    4600 var View = wp.media.View,
    4601         $ = jQuery,
    4602         EmbedUrl;
    4603 
    4604 EmbedUrl = 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 
    4668 module.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  */
    4679 var 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 
    4714 module.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  */
    4734 var 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 
    4882 module.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  */
    4902 var Select = wp.media.view.MediaFrame.Select,
    4903         l10n = wp.media.view.l10n,
    4904         ImageDetails;
    4905 
    4906 ImageDetails = 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 
    5061 module.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  */
    5080 var Select = wp.media.view.MediaFrame.Select,
    5081         Library = wp.media.controller.Library,
    5082         l10n = wp.media.view.l10n,
    5083         Post;
    5084 
    5085 Post = 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 
    5798 module.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 
    5817 var MediaFrame = wp.media.view.MediaFrame,
    5818         l10n = wp.media.view.l10n,
    5819         Select;
    5820 
    5821 Select = 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 
    5971 module.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  */
    5982 var 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 
    5995 module.exports = Iframe;
    5996 
    5997 },{}],46:[function(require,module,exports){
     8421
     8422/***/ }),
     8423/* 95 */
     8424/***/ (function(module, exports) {
     8425
    59988426/*globals wp, _, jQuery */
    59998427
     
    61658593module.exports = ImageDetails;
    61668594
    6167 },{}],47:[function(require,module,exports){
     8595
     8596/***/ }),
     8597/* 96 */
     8598/***/ (function(module, exports) {
     8599
     8600/*globals wp, _, jQuery */
     8601
    61688602/**
    6169  * wp.media.view.Label
    6170  *
    6171  * @class
    6172  * @augments wp.media.View
    6173  * @augments wp.Backbone.View
    6174  * @augments Backbone.View
    6175  */
    6176 var 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 
    6191 module.exports = Label;
    6192 
    6193 },{}],48:[function(require,module,exports){
    6194 /*globals wp, _, jQuery */
    6195 
    6196 /**
    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  */
    6208 var Frame = wp.media.view.Frame,
    6209         $ = jQuery,
    6210         MediaFrame;
    6211 
    6212 MediaFrame = 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 
    6440 module.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  */
    6453 var $ = jQuery,
    6454         MenuItem;
    6455 
    6456 MenuItem = 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 
    6514 module.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  */
    6526 var MenuItem = wp.media.view.MenuItem,
    6527         PriorityList = wp.media.view.PriorityList,
    6528         Menu;
    6529 
    6530 Menu = 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 
    6631 module.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  */
    6646 var $ = jQuery,
    6647         Modal;
    6648 
    6649 Modal = 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 
    6846 module.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  */
    6859 var 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 
    6945 module.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  */
    6957 var 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 
    6969 module.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  */
    6984 var Menu = wp.media.view.Menu,
    6985         Router;
    6986 
    6987 Router = 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 
    7008 module.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  */
    7021 var l10n = wp.media.view.l10n,
    7022         Search;
    7023 
    7024 Search = 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 
    7058 module.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  */
    7071 var l10n = wp.media.view.l10n,
    7072         Selection;
    7073 
    7074 Selection = 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 
    7143 module.exports = Selection;
    7144 
    7145 },{}],57:[function(require,module,exports){
    7146 /*globals _, Backbone */
    7147 
    7148 /**
    7149  * wp.media.view.Settings
     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.
    71508610 *
    71518611 * @class
     
    71558615 */
    71568616var View = wp.media.View,
    7157         $ = Backbone.$,
    7158         Settings;
    7159 
    7160 Settings = View.extend({
    7161         events: {
    7162                 'click button':    'updateHandler',
    7163                 'change input':    'updateHandler',
    7164                 'change select':   'updateHandler',
    7165                 'change textarea': 'updateHandler'
    7166         },
    7167 
     8617        UploaderStatus = wp.media.view.UploaderStatus,
     8618        l10n = wp.media.view.l10n,
     8619        $ = jQuery,
     8620        Cropper;
     8621
     8622Cropper = View.extend({
     8623        className: 'crop-content',
     8624        template: wp.template('crop-content'),
    71688625        initialize: function() {
    7169                 this.model = this.model || new Backbone.Model();
    7170                 this.listenTo( this.model, 'change', this.updateChanges );
    7171         },
    7172 
     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        },
    71738640        prepare: function() {
    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                 }
     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 });
    72638663        }
    72648664});
    72658665
    7266 module.exports = Settings;
    7267 
    7268 },{}],58:[function(require,module,exports){
     8666module.exports = Cropper;
     8667
     8668
     8669/***/ }),
     8670/* 97 */,
     8671/* 98 */,
     8672/* 99 */
     8673/***/ (function(module, exports) {
     8674
    72698675/*globals wp, _ */
    72708676
    72718677/**
    7272  * wp.media.view.Settings.AttachmentDisplay
     8678 * wp.media.view.EditImage
    72738679 *
    72748680 * @class
    7275  * @augments wp.media.view.Settings
    72768681 * @augments wp.media.View
    72778682 * @augments wp.Backbone.View
    72788683 * @augments Backbone.View
    72798684 */
    7280 var Settings = wp.media.view.Settings,
    7281         AttachmentDisplay;
    7282 
    7283 AttachmentDisplay = 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                 }
     8685var View = wp.media.View,
     8686        EditImage;
     8687
     8688EditImage = 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 ) );
    73598726        }
     8727
    73608728});
    73618729
    7362 module.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  */
    7376 var Gallery = wp.media.view.Settings.extend({
    7377         className: 'collection-settings gallery-settings',
    7378         template:  wp.template('gallery-settings')
    7379 });
    7380 
    7381 module.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  */
    7395 var Playlist = wp.media.view.Settings.extend({
    7396         className: 'collection-settings playlist-settings',
    7397         template:  wp.template('playlist-settings')
    7398 });
    7399 
    7400 module.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  */
    7412 var Sidebar = wp.media.view.PriorityList.extend({
    7413         className: 'media-sidebar'
    7414 });
    7415 
    7416 module.exports = Sidebar;
    7417 
    7418 },{}],62:[function(require,module,exports){
     8730module.exports = EditImage;
     8731
     8732
     8733/***/ }),
     8734/* 100 */
     8735/***/ (function(module, exports) {
     8736
    74198737/*globals _ */
    74208738
     
    74538771module.exports = Spinner;
    74548772
    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  */
    7469 var View = wp.media.View,
    7470         Toolbar;
    7471 
    7472 Toolbar = 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 
    7615 module.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  */
    7630 var Select = wp.media.view.Toolbar.Select,
    7631         l10n = wp.media.view.l10n,
    7632         Embed;
    7633 
    7634 Embed = 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 
    7654 module.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  */
    7668 var Toolbar = wp.media.view.Toolbar,
    7669         l10n = wp.media.view.l10n,
    7670         Select;
    7671 
    7672 Select = 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 
    7726 module.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  */
    7742 var View = wp.media.View,
    7743         l10n = wp.media.view.l10n,
    7744         $ = jQuery,
    7745         EditorUploader;
    7746 
    7747 EditorUploader = 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 
    7947 module.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  */
    7962 var View = wp.media.View,
    7963         UploaderInline;
    7964 
    7965 UploaderInline = 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 
    8080 module.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  */
    8093 var UploaderStatusError = wp.media.View.extend({
    8094         className: 'upload-error',
    8095         template:  wp.template('uploader-status-error')
    8096 });
    8097 
    8098 module.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  */
    8113 var View = wp.media.View,
    8114         UploaderStatus;
    8115 
    8116 UploaderStatus = 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 
    8238 module.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  */
    8259 var $ = jQuery,
    8260         UploaderWindow;
    8261 
    8262 UploaderWindow = 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 
    8351 module.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  */
    8372 var 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 
    8419 module.exports = View;
    8420 
    8421 },{}]},{},[17]);
     8773
     8774/***/ })
     8775/******/ ]));
  • branches/4.2/src/wp-includes/pluggable.php

    r45983 r46500  
    10751075 */
    10761076function check_admin_referer( $action = -1, $query_arg = '_wpnonce' ) {
    1077         if ( -1 == $action )
    1078                 _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2' );
     1077        if ( -1 === $action )
     1078                _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2.0' );
    10791079
    10801080        $adminurl = strtolower(admin_url());
     
    10961096         */
    10971097        do_action( 'check_admin_referer', $action, $result );
     1098
     1099        if ( ! $result && ! ( -1 === $action && strpos( $referer, $adminurl ) === 0 ) ) {
     1100                wp_nonce_ays( $action );
     1101                die();
     1102        }
     1103
    10981104        return $result;
    10991105}
     
    11161122 */
    11171123function check_ajax_referer( $action = -1, $query_arg = false, $die = true ) {
     1124        if ( -1 === $action )
     1125                _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2.0' );
     1126
    11181127        $nonce = '';
    11191128
     
    23582367}
    23592368endif;
    2360 
  • branches/4.2/src/wp-includes/query.php

    r39961 r46500  
    14021402                        , 'attachment_id'
    14031403                        , 'name'
    1404                         , 'static'
    14051404                        , 'pagename'
    14061405                        , 'page_id'
     
    16061605                        // post is being queried.
    16071606                        $this->is_single = true;
    1608                 } elseif ( '' != $qv['static'] || '' != $qv['pagename'] || !empty($qv['page_id']) ) {
     1607                } elseif ( '' != $qv['pagename'] || !empty($qv['page_id']) ) {
    16091608                        $this->is_page = true;
    16101609                        $this->is_single = false;
  • branches/4.2/tests/phpunit/tests/auth.php

    r30576 r46500  
    109109        }
    110110
     111        /**
     112         * @ticket 36361
     113         */
     114        public function test_check_admin_referer_with_no_action_triggers_doing_it_wrong() {
     115                $this->setExpectedIncorrectUsage( 'check_admin_referer' );
     116
     117                // A valid nonce needs to be set so the check doesn't die()
     118                $_REQUEST['_wpnonce'] = wp_create_nonce( -1 );
     119                $result = check_admin_referer();
     120                $this->assertSame( 1, $result );
     121
     122                unset( $_REQUEST['_wpnonce'] );
     123        }
     124
     125        /**
     126         * @ticket 36361
     127         */
     128        public function test_check_ajax_referer_with_no_action_triggers_doing_it_wrong() {
     129                $this->setExpectedIncorrectUsage( 'check_ajax_referer' );
     130
     131                // A valid nonce needs to be set so the check doesn't die()
     132                $_REQUEST['_wpnonce'] = wp_create_nonce( -1 );
     133                $result = check_ajax_referer();
     134                $this->assertSame( 1, $result );
     135
     136                unset( $_REQUEST['_wpnonce'] );
     137        }
     138
    111139        function test_password_length_limit() {
    112140                $passwords = array(
Note: See TracChangeset for help on using the changeset viewer.