Make WordPress Core

Changeset 41752


Ignore:
Timestamp:
10/04/2017 09:00:15 PM (7 years ago)
Author:
adamsilverstein
Message:

Build: Webpack instead of Browserify as JavaScript bundler.

Use Webpack as our bundler of choice, enabling improved JavaScript build chain capabilities.

Props aduth, netweb, kadamwhite, schlessera.
Fixes #40894.

Location:
trunk
Files:
2 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/.jshintrc

    r39898 r41752  
    2222    "jQuery": false,
    2323    "JSON": false,
    24     "wp": false
     24    "wp": false,
     25    "export": false,
     26    "module": false,
     27    "require": false
    2528  }
    2629}
  • trunk/Gruntfile.js

    r41751 r41752  
    11/* jshint node:true */
    22/* globals Set */
     3var webpackConfig = require( './webpack.config.prod' );
     4var webpackDevConfig = require( './webpack.config.dev' );
     5
    36module.exports = function(grunt) {
    47    var path = require('path'),
     
    811        BUILD_DIR = 'build/',
    912        BANNER_TEXT = '/*! This file is auto-generated */',
    10         autoprefixer = require('autoprefixer'),
    11         mediaConfig = {},
    12         mediaBuilds = ['audiovideo', 'grid', 'models', 'views'];
     13        autoprefixer = require( 'autoprefixer' );
    1314
    1415    // Load tasks.
     
    1617    // Load legacy utils
    1718    grunt.util = require('grunt-legacy-util');
    18 
    19     mediaBuilds.forEach( function ( build ) {
    20         var path = SOURCE_DIR + 'wp-includes/js/media';
    21         mediaConfig[ build ] = { files : {} };
    22         mediaConfig[ build ].files[ path + '-' + build + '.js' ] = [ path + '/' + build + '.manifest.js' ];
    23     } );
    2419
    2520    // Project configuration.
     
    178173            }
    179174        },
    180         browserify: mediaConfig,
    181175        sass: {
    182176            colors: {
     
    339333            },
    340334            media: {
    341                 options: {
    342                     browserify: true
    343                 },
    344335                src: [
    345336                    SOURCE_DIR + 'wp-includes/js/media/**/*.js'
     
    554545            }
    555546        },
    556 
     547        webpack: {
     548            prod: webpackConfig,
     549            dev: webpackDevConfig
     550        },
    557551        concat: {
    558552            tinymce: {
     
    720714            },
    721715            config: {
    722                 files: 'Gruntfile.js'
     716                files: [
     717                    'Gruntfile.js',
     718                    'webpack-dev.config.js',
     719                    'webpack.config.js'
     720                ]
    723721            },
    724722            colors: {
     
    757755
    758756    // Register tasks.
     757
     758    // Webpack task.
     759    grunt.loadNpmTasks( 'grunt-webpack' );
    759760
    760761    // RTL task.
     
    781782
    782783    grunt.registerTask( 'watch', function() {
    783         if ( ! this.args.length || this.args.indexOf( 'browserify' ) > -1 ) {
    784             grunt.config( 'browserify.options', {
    785                 browserifyOptions: {
    786                     debug: true
    787                 },
    788                 watch: true
    789             } );
    790 
    791             grunt.task.run( 'browserify' );
     784        if ( ! this.args.length || this.args.indexOf( 'webpack' ) > -1 ) {
     785
     786            grunt.task.run( 'webpack:dev' );
    792787        }
    793788
     
    800795
    801796    grunt.registerTask( 'precommit:js', [
    802         'browserify',
     797        'webpack:prod',
    803798        'jshint:corejs',
    804799        'uglify:masonry',
     
    979974        var src;
    980975
    981         if ( [ 'all', 'rtl', 'browserify' ].indexOf( target ) === -1 ) {
     976        if ( [ 'all', 'rtl', 'webpack' ].indexOf( target ) === -1 ) {
    982977            return;
    983978        }
  • trunk/package.json

    r41351 r41752  
    1616    "grunt": "~0.4.5",
    1717    "grunt-banner": "^0.6.0",
    18     "grunt-browserify": "~5.0.0",
    1918    "grunt-contrib-clean": "~1.0.0",
    2019    "grunt-contrib-compress": "~1.3.0",
     
    3736    "grunt-sass": "~1.2.1",
    3837    "ink-docstrap": "^1.3.0",
    39     "matchdep": "~1.0.0"
     38    "grunt-webpack": "^3.0.2",
     39    "matchdep": "~1.0.0",
     40    "webpack": "^3.6.0",
     41    "webpack-dev-server": "^2.9.1"
    4042  }
    4143}
  • trunk/src/wp-includes/js/media-audiovideo.js

    r41351 r41752  
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     1/******/ (function(modules) { // webpackBootstrap
     2/******/    // The module cache
     3/******/    var installedModules = {};
     4/******/
     5/******/    // The require function
     6/******/    function __webpack_require__(moduleId) {
     7/******/
     8/******/        // Check if module is in cache
     9/******/        if(installedModules[moduleId]) {
     10/******/            return installedModules[moduleId].exports;
     11/******/        }
     12/******/        // Create a new module (and put it into the cache)
     13/******/        var module = installedModules[moduleId] = {
     14/******/            i: moduleId,
     15/******/            l: false,
     16/******/            exports: {}
     17/******/        };
     18/******/
     19/******/        // Execute the module function
     20/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/        // Flag the module as loaded
     23/******/        module.l = true;
     24/******/
     25/******/        // Return the exports of the module
     26/******/        return module.exports;
     27/******/    }
     28/******/
     29/******/
     30/******/    // expose the modules object (__webpack_modules__)
     31/******/    __webpack_require__.m = modules;
     32/******/
     33/******/    // expose the module cache
     34/******/    __webpack_require__.c = installedModules;
     35/******/
     36/******/    // define getter function for harmony exports
     37/******/    __webpack_require__.d = function(exports, name, getter) {
     38/******/        if(!__webpack_require__.o(exports, name)) {
     39/******/            Object.defineProperty(exports, name, {
     40/******/                configurable: false,
     41/******/                enumerable: true,
     42/******/                get: getter
     43/******/            });
     44/******/        }
     45/******/    };
     46/******/
     47/******/    // getDefaultExport function for compatibility with non-harmony modules
     48/******/    __webpack_require__.n = function(module) {
     49/******/        var getter = module && module.__esModule ?
     50/******/            function getDefault() { return module['default']; } :
     51/******/            function getModuleExports() { return module; };
     52/******/        __webpack_require__.d(getter, 'a', getter);
     53/******/        return getter;
     54/******/    };
     55/******/
     56/******/    // Object.prototype.hasOwnProperty.call
     57/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/    // __webpack_public_path__
     60/******/    __webpack_require__.p = "";
     61/******/
     62/******/    // Load entry module and return exports
     63/******/    return __webpack_require__(__webpack_require__.s = 0);
     64/******/ })
     65/************************************************************************/
     66/******/ ([
     67/* 0 */
     68/***/ (function(module, exports, __webpack_require__) {
     69
    270var media = wp.media,
    371    baseSettings = window._wpmejsSettings || {},
     
    269337};
    270338
    271 media.model.PostMedia = require( './models/post-media.js' );
    272 media.controller.AudioDetails = require( './controllers/audio-details.js' );
    273 media.controller.VideoDetails = require( './controllers/video-details.js' );
    274 media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' );
    275 media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' );
    276 media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' );
    277 media.view.MediaDetails = require( './views/media-details.js' );
    278 media.view.AudioDetails = require( './views/audio-details.js' );
    279 media.view.VideoDetails = require( './views/video-details.js' );
    280 
    281 },{"./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){
     339media.model.PostMedia = __webpack_require__( 1 );
     340media.controller.AudioDetails = __webpack_require__( 2 );
     341media.controller.VideoDetails = __webpack_require__( 3 );
     342media.view.MediaFrame.MediaDetails = __webpack_require__( 4 );
     343media.view.MediaFrame.AudioDetails = __webpack_require__( 5 );
     344media.view.MediaFrame.VideoDetails = __webpack_require__( 6 );
     345media.view.MediaDetails = __webpack_require__( 7 );
     346media.view.AudioDetails = __webpack_require__( 8 );
     347media.view.VideoDetails = __webpack_require__( 9 );
     348
     349
     350/***/ }),
     351/* 1 */
     352/***/ (function(module, exports) {
     353
     354/**
     355 * wp.media.model.PostMedia
     356 *
     357 * Shared model class for audio and video. Updates the model after
     358 *   "Add Audio|Video Source" and "Replace Audio|Video" states return
     359 *
     360 * @memberOf wp.media.model
     361 *
     362 * @class
     363 * @augments Backbone.Model
     364 */
     365var PostMedia = Backbone.Model.extend(/** @lends wp.media.model.PostMedia.prototype */{
     366    initialize: function() {
     367        this.attachment = false;
     368    },
     369
     370    setSource: function( attachment ) {
     371        this.attachment = attachment;
     372        this.extension = attachment.get( 'filename' ).split('.').pop();
     373
     374        if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
     375            this.unset( 'src' );
     376        }
     377
     378        if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
     379            this.set( this.extension, this.attachment.get( 'url' ) );
     380        } else {
     381            this.unset( this.extension );
     382        }
     383    },
     384
     385    changeAttachment: function( attachment ) {
     386        this.setSource( attachment );
     387
     388        this.unset( 'src' );
     389        _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
     390            this.unset( ext );
     391        }, this );
     392    }
     393});
     394
     395module.exports = PostMedia;
     396
     397
     398/***/ }),
     399/* 2 */
     400/***/ (function(module, exports) {
     401
    282402var State = wp.media.controller.State,
    283403    l10n = wp.media.view.l10n,
     
    314434module.exports = AudioDetails;
    315435
    316 },{}],3:[function(require,module,exports){
     436
     437/***/ }),
     438/* 3 */
     439/***/ (function(module, exports) {
     440
    317441/**
    318442 * wp.media.controller.VideoDetails
     
    349473module.exports = VideoDetails;
    350474
    351 },{}],4:[function(require,module,exports){
    352 /**
    353  * wp.media.model.PostMedia
    354  *
    355  * Shared model class for audio and video. Updates the model after
    356  *   "Add Audio|Video Source" and "Replace Audio|Video" states return
    357  *
    358  * @memberOf wp.media.model
    359  *
    360  * @class
    361  * @augments Backbone.Model
    362  */
    363 var PostMedia = Backbone.Model.extend(/** @lends wp.media.model.PostMedia.prototype */{
    364     initialize: function() {
    365         this.attachment = false;
    366     },
    367 
    368     setSource: function( attachment ) {
    369         this.attachment = attachment;
    370         this.extension = attachment.get( 'filename' ).split('.').pop();
    371 
    372         if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
    373             this.unset( 'src' );
    374         }
    375 
    376         if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
    377             this.set( this.extension, this.attachment.get( 'url' ) );
    378         } else {
    379             this.unset( this.extension );
    380         }
    381     },
    382 
    383     changeAttachment: function( attachment ) {
    384         this.setSource( attachment );
    385 
    386         this.unset( 'src' );
    387         _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
    388             this.unset( ext );
    389         }, this );
    390     }
    391 });
    392 
    393 module.exports = PostMedia;
    394 
    395 },{}],5:[function(require,module,exports){
    396 var MediaDetails = wp.media.view.MediaDetails,
    397     AudioDetails;
    398 
    399 /**
    400  * wp.media.view.AudioDetails
    401  *
    402  * @memberOf wp.media.view
    403  *
    404  * @class
    405  * @augments wp.media.view.MediaDetails
    406  * @augments wp.media.view.Settings.AttachmentDisplay
    407  * @augments wp.media.view.Settings
    408  * @augments wp.media.View
    409  * @augments wp.Backbone.View
    410  * @augments Backbone.View
    411  */
    412 AudioDetails = MediaDetails.extend(/** @lends wp.media.view.AudioDetails.prototype */{
    413     className: 'audio-details',
    414     template:  wp.template('audio-details'),
    415 
    416     setMedia: function() {
    417         var audio = this.$('.wp-audio-shortcode');
    418 
    419         if ( audio.find( 'source' ).length ) {
    420             if ( audio.is(':hidden') ) {
    421                 audio.show();
    422             }
    423             this.media = MediaDetails.prepareSrc( audio.get(0) );
    424         } else {
    425             audio.hide();
    426             this.media = false;
    427         }
    428 
    429         return this;
    430     }
    431 });
    432 
    433 module.exports = AudioDetails;
    434 
    435 },{}],6:[function(require,module,exports){
    436 var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
    437     MediaLibrary = wp.media.controller.MediaLibrary,
    438 
    439     l10n = wp.media.view.l10n,
    440     AudioDetails;
    441 
    442 /**
    443  * wp.media.view.MediaFrame.AudioDetails
    444  *
    445  * @memberOf wp.media.view.MediaFrame
    446  *
    447  * @class
    448  * @augments wp.media.view.MediaFrame.MediaDetails
    449  * @augments wp.media.view.MediaFrame.Select
    450  * @augments wp.media.view.MediaFrame
    451  * @augments wp.media.view.Frame
    452  * @augments wp.media.View
    453  * @augments wp.Backbone.View
    454  * @augments Backbone.View
    455  * @mixes wp.media.controller.StateMachine
    456  */
    457 AudioDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.AudioDetails.prototype */{
    458     defaults: {
    459         id:      'audio',
    460         url:     '',
    461         menu:    'audio-details',
    462         content: 'audio-details',
    463         toolbar: 'audio-details',
    464         type:    'link',
    465         title:    l10n.audioDetailsTitle,
    466         priority: 120
    467     },
    468 
    469     initialize: function( options ) {
    470         options.DetailsView = wp.media.view.AudioDetails;
    471         options.cancelText = l10n.audioDetailsCancel;
    472         options.addText = l10n.audioAddSourceTitle;
    473 
    474         MediaDetails.prototype.initialize.call( this, options );
    475     },
    476 
    477     bindHandlers: function() {
    478         MediaDetails.prototype.bindHandlers.apply( this, arguments );
    479 
    480         this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
    481         this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
    482     },
    483 
    484     createStates: function() {
    485         this.states.add([
    486             new wp.media.controller.AudioDetails( {
    487                 media: this.media
    488             } ),
    489 
    490             new MediaLibrary( {
    491                 type: 'audio',
    492                 id: 'replace-audio',
    493                 title: l10n.audioReplaceTitle,
    494                 toolbar: 'replace-audio',
    495                 media: this.media,
    496                 menu: 'audio-details'
    497             } ),
    498 
    499             new MediaLibrary( {
    500                 type: 'audio',
    501                 id: 'add-audio-source',
    502                 title: l10n.audioAddSourceTitle,
    503                 toolbar: 'add-audio-source',
    504                 media: this.media,
    505                 menu: false
    506             } )
    507         ]);
    508     }
    509 });
    510 
    511 module.exports = AudioDetails;
    512 
    513 },{}],7:[function(require,module,exports){
     475
     476/***/ }),
     477/* 4 */
     478/***/ (function(module, exports) {
     479
    514480var Select = wp.media.view.MediaFrame.Select,
    515481    l10n = wp.media.view.l10n,
     
    643609module.exports = MediaDetails;
    644610
    645 },{}],8:[function(require,module,exports){
     611
     612/***/ }),
     613/* 5 */
     614/***/ (function(module, exports) {
     615
     616var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
     617    MediaLibrary = wp.media.controller.MediaLibrary,
     618
     619    l10n = wp.media.view.l10n,
     620    AudioDetails;
     621
     622/**
     623 * wp.media.view.MediaFrame.AudioDetails
     624 *
     625 * @memberOf wp.media.view.MediaFrame
     626 *
     627 * @class
     628 * @augments wp.media.view.MediaFrame.MediaDetails
     629 * @augments wp.media.view.MediaFrame.Select
     630 * @augments wp.media.view.MediaFrame
     631 * @augments wp.media.view.Frame
     632 * @augments wp.media.View
     633 * @augments wp.Backbone.View
     634 * @augments Backbone.View
     635 * @mixes wp.media.controller.StateMachine
     636 */
     637AudioDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.AudioDetails.prototype */{
     638    defaults: {
     639        id:      'audio',
     640        url:     '',
     641        menu:    'audio-details',
     642        content: 'audio-details',
     643        toolbar: 'audio-details',
     644        type:    'link',
     645        title:    l10n.audioDetailsTitle,
     646        priority: 120
     647    },
     648
     649    initialize: function( options ) {
     650        options.DetailsView = wp.media.view.AudioDetails;
     651        options.cancelText = l10n.audioDetailsCancel;
     652        options.addText = l10n.audioAddSourceTitle;
     653
     654        MediaDetails.prototype.initialize.call( this, options );
     655    },
     656
     657    bindHandlers: function() {
     658        MediaDetails.prototype.bindHandlers.apply( this, arguments );
     659
     660        this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
     661        this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
     662    },
     663
     664    createStates: function() {
     665        this.states.add([
     666            new wp.media.controller.AudioDetails( {
     667                media: this.media
     668            } ),
     669
     670            new MediaLibrary( {
     671                type: 'audio',
     672                id: 'replace-audio',
     673                title: l10n.audioReplaceTitle,
     674                toolbar: 'replace-audio',
     675                media: this.media,
     676                menu: 'audio-details'
     677            } ),
     678
     679            new MediaLibrary( {
     680                type: 'audio',
     681                id: 'add-audio-source',
     682                title: l10n.audioAddSourceTitle,
     683                toolbar: 'add-audio-source',
     684                media: this.media,
     685                menu: false
     686            } )
     687        ]);
     688    }
     689});
     690
     691module.exports = AudioDetails;
     692
     693
     694/***/ }),
     695/* 6 */
     696/***/ (function(module, exports) {
     697
    646698var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
    647699    MediaLibrary = wp.media.controller.MediaLibrary,
     
    780832module.exports = VideoDetails;
    781833
    782 },{}],9:[function(require,module,exports){
     834
     835/***/ }),
     836/* 7 */
     837/***/ (function(module, exports) {
     838
    783839/* global MediaElementPlayer */
    784840var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     
    9501006module.exports = MediaDetails;
    9511007
    952 },{}],10:[function(require,module,exports){
     1008
     1009/***/ }),
     1010/* 8 */
     1011/***/ (function(module, exports) {
     1012
     1013var MediaDetails = wp.media.view.MediaDetails,
     1014    AudioDetails;
     1015
     1016/**
     1017 * wp.media.view.AudioDetails
     1018 *
     1019 * @memberOf wp.media.view
     1020 *
     1021 * @class
     1022 * @augments wp.media.view.MediaDetails
     1023 * @augments wp.media.view.Settings.AttachmentDisplay
     1024 * @augments wp.media.view.Settings
     1025 * @augments wp.media.View
     1026 * @augments wp.Backbone.View
     1027 * @augments Backbone.View
     1028 */
     1029AudioDetails = MediaDetails.extend(/** @lends wp.media.view.AudioDetails.prototype */{
     1030    className: 'audio-details',
     1031    template:  wp.template('audio-details'),
     1032
     1033    setMedia: function() {
     1034        var audio = this.$('.wp-audio-shortcode');
     1035
     1036        if ( audio.find( 'source' ).length ) {
     1037            if ( audio.is(':hidden') ) {
     1038                audio.show();
     1039            }
     1040            this.media = MediaDetails.prepareSrc( audio.get(0) );
     1041        } else {
     1042            audio.hide();
     1043            this.media = false;
     1044        }
     1045
     1046        return this;
     1047    }
     1048});
     1049
     1050module.exports = AudioDetails;
     1051
     1052
     1053/***/ }),
     1054/* 9 */
     1055/***/ (function(module, exports) {
     1056
    9531057var MediaDetails = wp.media.view.MediaDetails,
    9541058    VideoDetails;
     
    9951099module.exports = VideoDetails;
    9961100
    997 },{}]},{},[1]);
     1101
     1102/***/ })
     1103/******/ ]);
  • trunk/src/wp-includes/js/media-grid.js

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

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

    r41557 r41752  
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
    2 var Selection = wp.media.model.Selection,
    3     Library = wp.media.controller.Library,
    4     CollectionAdd;
     1/******/ (function(modules) { // webpackBootstrap
     2/******/    // The module cache
     3/******/    var installedModules = {};
     4/******/
     5/******/    // The require function
     6/******/    function __webpack_require__(moduleId) {
     7/******/
     8/******/        // Check if module is in cache
     9/******/        if(installedModules[moduleId]) {
     10/******/            return installedModules[moduleId].exports;
     11/******/        }
     12/******/        // Create a new module (and put it into the cache)
     13/******/        var module = installedModules[moduleId] = {
     14/******/            i: moduleId,
     15/******/            l: false,
     16/******/            exports: {}
     17/******/        };
     18/******/
     19/******/        // Execute the module function
     20/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/        // Flag the module as loaded
     23/******/        module.l = true;
     24/******/
     25/******/        // Return the exports of the module
     26/******/        return module.exports;
     27/******/    }
     28/******/
     29/******/
     30/******/    // expose the modules object (__webpack_modules__)
     31/******/    __webpack_require__.m = modules;
     32/******/
     33/******/    // expose the module cache
     34/******/    __webpack_require__.c = installedModules;
     35/******/
     36/******/    // define getter function for harmony exports
     37/******/    __webpack_require__.d = function(exports, name, getter) {
     38/******/        if(!__webpack_require__.o(exports, name)) {
     39/******/            Object.defineProperty(exports, name, {
     40/******/                configurable: false,
     41/******/                enumerable: true,
     42/******/                get: getter
     43/******/            });
     44/******/        }
     45/******/    };
     46/******/
     47/******/    // getDefaultExport function for compatibility with non-harmony modules
     48/******/    __webpack_require__.n = function(module) {
     49/******/        var getter = module && module.__esModule ?
     50/******/            function getDefault() { return module['default']; } :
     51/******/            function getModuleExports() { return module; };
     52/******/        __webpack_require__.d(getter, 'a', getter);
     53/******/        return getter;
     54/******/    };
     55/******/
     56/******/    // Object.prototype.hasOwnProperty.call
     57/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/    // __webpack_public_path__
     60/******/    __webpack_require__.p = "";
     61/******/
     62/******/    // Load entry module and return exports
     63/******/    return __webpack_require__(__webpack_require__.s = 26);
     64/******/ })
     65/************************************************************************/
     66/******/ (Array(26).concat([
     67/* 26 */
     68/***/ (function(module, exports, __webpack_require__) {
     69
     70var media = wp.media,
     71    $ = jQuery,
     72    l10n;
     73
     74media.isTouchDevice = ( 'ontouchend' in document );
     75
     76// Link any localized strings.
     77l10n = media.view.l10n = window._wpMediaViewsL10n || {};
     78
     79// Link any settings.
     80media.view.settings = l10n.settings || {};
     81delete l10n.settings;
     82
     83// Copy the `post` setting over to the model settings.
     84media.model.settings.post = media.view.settings.post;
     85
     86// Check if the browser supports CSS 3.0 transitions
     87$.support.transition = (function(){
     88    var style = document.documentElement.style,
     89        transitions = {
     90            WebkitTransition: 'webkitTransitionEnd',
     91            MozTransition:    'transitionend',
     92            OTransition:      'oTransitionEnd otransitionend',
     93            transition:       'transitionend'
     94        }, transition;
     95
     96    transition = _.find( _.keys( transitions ), function( transition ) {
     97        return ! _.isUndefined( style[ transition ] );
     98    });
     99
     100    return transition && {
     101        end: transitions[ transition ]
     102    };
     103}());
    5104
    6105/**
    7  * wp.media.controller.CollectionAdd
    8  *
    9  * A state for adding attachments to a collection (e.g. video playlist).
     106 * A shared event bus used to provide events into
     107 * the media workflows that 3rd-party devs can use to hook
     108 * in.
     109 */
     110media.events = _.extend( {}, Backbone.Events );
     111
     112/**
     113 * Makes it easier to bind events using transitions.
     114 *
     115 * @param {string} selector
     116 * @param {Number} sensitivity
     117 * @returns {Promise}
     118 */
     119media.transition = function( selector, sensitivity ) {
     120    var deferred = $.Deferred();
     121
     122    sensitivity = sensitivity || 2000;
     123
     124    if ( $.support.transition ) {
     125        if ( ! (selector instanceof $) ) {
     126            selector = $( selector );
     127        }
     128
     129        // Resolve the deferred when the first element finishes animating.
     130        selector.first().one( $.support.transition.end, deferred.resolve );
     131
     132        // Just in case the event doesn't trigger, fire a callback.
     133        _.delay( deferred.resolve, sensitivity );
     134
     135    // Otherwise, execute on the spot.
     136    } else {
     137        deferred.resolve();
     138    }
     139
     140    return deferred.promise();
     141};
     142
     143media.controller.Region = __webpack_require__( 27 );
     144media.controller.StateMachine = __webpack_require__( 28 );
     145media.controller.State = __webpack_require__( 29 );
     146
     147media.selectionSync = __webpack_require__( 30 );
     148media.controller.Library = __webpack_require__( 31 );
     149media.controller.ImageDetails = __webpack_require__( 32 );
     150media.controller.GalleryEdit = __webpack_require__( 33 );
     151media.controller.GalleryAdd = __webpack_require__( 34 );
     152media.controller.CollectionEdit = __webpack_require__( 35 );
     153media.controller.CollectionAdd = __webpack_require__( 36 );
     154media.controller.FeaturedImage = __webpack_require__( 37 );
     155media.controller.ReplaceImage = __webpack_require__( 38 );
     156media.controller.EditImage = __webpack_require__( 39 );
     157media.controller.MediaLibrary = __webpack_require__( 40 );
     158media.controller.Embed = __webpack_require__( 41 );
     159media.controller.Cropper = __webpack_require__( 42 );
     160media.controller.CustomizeImageCropper = __webpack_require__( 43 );
     161media.controller.SiteIconCropper = __webpack_require__( 44 );
     162
     163media.View = __webpack_require__( 45 );
     164media.view.Frame = __webpack_require__( 46 );
     165media.view.MediaFrame = __webpack_require__( 47 );
     166media.view.MediaFrame.Select = __webpack_require__( 48 );
     167media.view.MediaFrame.Post = __webpack_require__( 49 );
     168media.view.MediaFrame.ImageDetails = __webpack_require__( 50 );
     169media.view.Modal = __webpack_require__( 51 );
     170media.view.FocusManager = __webpack_require__( 52 );
     171media.view.UploaderWindow = __webpack_require__( 53 );
     172media.view.EditorUploader = __webpack_require__( 54 );
     173media.view.UploaderInline = __webpack_require__( 55 );
     174media.view.UploaderStatus = __webpack_require__( 56 );
     175media.view.UploaderStatusError = __webpack_require__( 57 );
     176media.view.Toolbar = __webpack_require__( 58 );
     177media.view.Toolbar.Select = __webpack_require__( 59 );
     178media.view.Toolbar.Embed = __webpack_require__( 60 );
     179media.view.Button = __webpack_require__( 61 );
     180media.view.ButtonGroup = __webpack_require__( 62 );
     181media.view.PriorityList = __webpack_require__( 63 );
     182media.view.MenuItem = __webpack_require__( 64 );
     183media.view.Menu = __webpack_require__( 65 );
     184media.view.RouterItem = __webpack_require__( 66 );
     185media.view.Router = __webpack_require__( 67 );
     186media.view.Sidebar = __webpack_require__( 68 );
     187media.view.Attachment = __webpack_require__( 69 );
     188media.view.Attachment.Library = __webpack_require__( 70 );
     189media.view.Attachment.EditLibrary = __webpack_require__( 71 );
     190media.view.Attachments = __webpack_require__( 72 );
     191media.view.Search = __webpack_require__( 73 );
     192media.view.AttachmentFilters = __webpack_require__( 74 );
     193media.view.DateFilter = __webpack_require__( 75 );
     194media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 );
     195media.view.AttachmentFilters.All = __webpack_require__( 77 );
     196media.view.AttachmentsBrowser = __webpack_require__( 78 );
     197media.view.Selection = __webpack_require__( 79 );
     198media.view.Attachment.Selection = __webpack_require__( 80 );
     199media.view.Attachments.Selection = __webpack_require__( 81 );
     200media.view.Attachment.EditSelection = __webpack_require__( 82 );
     201media.view.Settings = __webpack_require__( 83 );
     202media.view.Settings.AttachmentDisplay = __webpack_require__( 84 );
     203media.view.Settings.Gallery = __webpack_require__( 85 );
     204media.view.Settings.Playlist = __webpack_require__( 86 );
     205media.view.Attachment.Details = __webpack_require__( 87 );
     206media.view.AttachmentCompat = __webpack_require__( 88 );
     207media.view.Iframe = __webpack_require__( 89 );
     208media.view.Embed = __webpack_require__( 90 );
     209media.view.Label = __webpack_require__( 91 );
     210media.view.EmbedUrl = __webpack_require__( 92 );
     211media.view.EmbedLink = __webpack_require__( 93 );
     212media.view.EmbedImage = __webpack_require__( 94 );
     213media.view.ImageDetails = __webpack_require__( 95 );
     214media.view.Cropper = __webpack_require__( 96 );
     215media.view.SiteIconCropper = __webpack_require__( 97 );
     216media.view.SiteIconPreview = __webpack_require__( 98 );
     217media.view.EditImage = __webpack_require__( 99 );
     218media.view.Spinner = __webpack_require__( 100 );
     219
     220
     221/***/ }),
     222/* 27 */
     223/***/ (function(module, exports) {
     224
     225/**
     226 * wp.media.controller.Region
     227 *
     228 * A region is a persistent application layout area.
     229 *
     230 * A region assumes one mode at any time, and can be switched to another.
     231 *
     232 * When mode changes, events are triggered on the region's parent view.
     233 * The parent view will listen to specific events and fill the region with an
     234 * appropriate view depending on mode. For example, a frame listens for the
     235 * 'browse' mode t be activated on the 'content' view and then fills the region
     236 * with an AttachmentsBrowser view.
    10237 *
    11238 * @memberOf wp.media.controller
    12239 *
    13240 * @class
    14  * @augments wp.media.controller.Library
    15  * @augments wp.media.controller.State
    16  * @augments Backbone.Model
    17  *
    18  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    19  * @param {string}                     [attributes.id=library]      Unique identifier.
    20  * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
    21  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    22  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    23  *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
    24  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    25  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    26  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    27  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    28  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    29  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    30  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    31  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    32  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    33  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    34  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    35  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    36  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    37  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    38  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    39  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     241 *
     242 * @param {object}        options          Options hash for the region.
     243 * @param {string}        options.id       Unique identifier for the region.
     244 * @param {Backbone.View} options.view     A parent view the region exists within.
     245 * @param {string}        options.selector jQuery selector for the region within the parent view.
    40246 */
    41 CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{
    42     defaults: _.defaults( {
    43         // Selection defaults. @see media.model.Selection
    44         multiple:      'add',
    45         // Attachments browser defaults. @see media.view.AttachmentsBrowser
    46         filterable:    'uploaded',
    47 
    48         priority:      100,
    49         syncSelection: false
    50     }, Library.prototype.defaults ),
    51 
    52     /**
    53      * @since 3.9.0
    54      */
    55     initialize: function() {
    56         var collectionType = this.get('collectionType');
    57 
    58         if ( 'video' === this.get( 'type' ) ) {
    59             collectionType = 'video-' + collectionType;
    60         }
    61 
    62         this.set( 'id', collectionType + '-library' );
    63         this.set( 'toolbar', collectionType + '-add' );
    64         this.set( 'menu', collectionType );
    65 
    66         // If we haven't been provided a `library`, create a `Selection`.
    67         if ( ! this.get('library') ) {
    68             this.set( 'library', wp.media.query({ type: this.get('type') }) );
    69         }
    70         Library.prototype.initialize.apply( this, arguments );
    71     },
    72 
    73     /**
    74      * @since 3.9.0
    75      */
    76     activate: function() {
    77         var library = this.get('library'),
    78             editLibrary = this.get('editLibrary'),
    79             edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
    80 
    81         if ( editLibrary && editLibrary !== edit ) {
    82             library.unobserve( editLibrary );
    83         }
    84 
    85         // Accepts attachments that exist in the original library and
    86         // that do not exist in gallery's library.
    87         library.validator = function( attachment ) {
    88             return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    89         };
    90 
    91         // Reset the library to ensure that all attachments are re-added
    92         // to the collection. Do so silently, as calling `observe` will
    93         // trigger the `reset` event.
    94         library.reset( library.mirroring.models, { silent: true });
    95         library.observe( edit );
    96         this.set('editLibrary', edit);
    97 
    98         Library.prototype.activate.apply( this, arguments );
     247var Region = function( options ) {
     248    _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
     249};
     250
     251// Use Backbone's self-propagating `extend` inheritance method.
     252Region.extend = Backbone.Model.extend;
     253
     254_.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{
     255    /**
     256     * Activate a mode.
     257     *
     258     * @since 3.5.0
     259     *
     260     * @param {string} mode
     261     *
     262     * @fires Region#activate
     263     * @fires Region#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 wp.media.controller.Region#deactivate
     280         */
     281        this.trigger('deactivate');
     282
     283        this._mode = mode;
     284        this.render( mode );
     285
     286        /**
     287         * Region mode activation event.
     288         *
     289         * @event wp.media.controller.Region#activate
     290         */
     291        this.trigger('activate');
     292        return this;
     293    },
     294    /**
     295     * Render a mode.
     296     *
     297     * @since 3.5.0
     298     *
     299     * @param {string} mode
     300     *
     301     * @fires Region#create
     302     * @fires Region#render
     303     *
     304     * @returns {wp.media.controller.Region} Returns itself to allow chaining
     305     */
     306    render: function( mode ) {
     307        // If the mode isn't active, activate it.
     308        if ( mode && mode !== this._mode ) {
     309            return this.mode( mode );
     310        }
     311
     312        var set = { view: null },
     313            view;
     314
     315        /**
     316         * Create region view event.
     317         *
     318         * Region view creation takes place in an event callback on the frame.
     319         *
     320         * @event wp.media.controller.Region#create
     321         * @type {object}
     322         * @property {object} view
     323         */
     324        this.trigger( 'create', set );
     325        view = set.view;
     326
     327        /**
     328         * Render region view event.
     329         *
     330         * Region view creation takes place in an event callback on the frame.
     331         *
     332         * @event wp.media.controller.Region#render
     333         * @type {object}
     334         */
     335        this.trigger( 'render', view );
     336        if ( view ) {
     337            this.set( view );
     338        }
     339        return this;
     340    },
     341
     342    /**
     343     * Get the region's view.
     344     *
     345     * @since 3.5.0
     346     *
     347     * @returns {wp.media.View}
     348     */
     349    get: function() {
     350        return this.view.views.first( this.selector );
     351    },
     352
     353    /**
     354     * Set the region's view as a subview of the frame.
     355     *
     356     * @since 3.5.0
     357     *
     358     * @param {Array|Object} views
     359     * @param {Object} [options={}]
     360     * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
     361     */
     362    set: function( views, options ) {
     363        if ( options ) {
     364            options.add = false;
     365        }
     366        return this.view.views.set( this.selector, views, options );
     367    },
     368
     369    /**
     370     * Trigger regional view events on the frame.
     371     *
     372     * @since 3.5.0
     373     *
     374     * @param {string} event
     375     * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
     376     */
     377    trigger: function( event ) {
     378        var base, args;
     379
     380        if ( ! this._mode ) {
     381            return;
     382        }
     383
     384        args = _.toArray( arguments );
     385        base = this.id + ':' + event;
     386
     387        // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
     388        args[0] = base + ':' + this._mode;
     389        this.view.trigger.apply( this.view, args );
     390
     391        // Trigger `{this.id}:{event}` event on the frame.
     392        args[0] = base;
     393        this.view.trigger.apply( this.view, args );
     394        return this;
    99395    }
    100396});
    101397
    102 module.exports = CollectionAdd;
    103 
    104 },{}],2:[function(require,module,exports){
    105 var Library = wp.media.controller.Library,
    106     l10n = wp.media.view.l10n,
    107     $ = jQuery,
    108     CollectionEdit;
     398module.exports = Region;
     399
     400
     401/***/ }),
     402/* 28 */
     403/***/ (function(module, exports) {
    109404
    110405/**
    111  * wp.media.controller.CollectionEdit
    112  *
    113  * A state for editing a collection, which is used by audio and video playlists,
    114  * and can be used for other collections.
     406 * wp.media.controller.StateMachine
     407 *
     408 * A state machine keeps track of state. It is in one state at a time,
     409 * and can change from one state to another.
     410 *
     411 * States are stored as models in a Backbone collection.
    115412 *
    116413 * @memberOf wp.media.controller
    117414 *
     415 * @since 3.5.0
     416 *
    118417 * @class
    119  * @augments wp.media.controller.Library
    120  * @augments wp.media.controller.State
    121418 * @augments Backbone.Model
    122  *
    123  * @param {object}                     [attributes]                      The attributes hash passed to the state.
    124  * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
    125  * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
    126  *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
    127  * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
    128  * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
    129  * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
    130  * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
    131  * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    132  * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
    133  * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
    134  * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
    135  * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
    136  * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
    137  * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
    138  * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
    139  * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
    140  *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
    141  * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
    142  * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
    143  *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    144  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    145  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     419 * @mixin
     420 * @mixes Backbone.Events
     421 *
     422 * @param {Array} states
    146423 */
    147 CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{
    148     defaults: {
    149         multiple:         false,
    150         sortable:         true,
    151         date:             false,
    152         searchable:       false,
    153         content:          'browse',
    154         describe:         true,
    155         dragInfo:         true,
    156         idealColumnWidth: 170,
    157         editing:          false,
    158         priority:         60,
    159         SettingsView:     false,
    160         syncSelection:    false
    161     },
    162 
    163     /**
    164      * @since 3.9.0
    165      */
    166     initialize: function() {
    167         var collectionType = this.get('collectionType');
    168 
    169         if ( 'video' === this.get( 'type' ) ) {
    170             collectionType = 'video-' + collectionType;
    171         }
    172 
    173         this.set( 'id', collectionType + '-edit' );
    174         this.set( 'toolbar', collectionType + '-edit' );
    175 
    176         // If we haven't been provided a `library`, create a `Selection`.
    177         if ( ! this.get('library') ) {
    178             this.set( 'library', new wp.media.model.Selection() );
    179         }
    180         // The single `Attachment` view to be used in the `Attachments` view.
    181         if ( ! this.get('AttachmentView') ) {
    182             this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    183         }
    184         Library.prototype.initialize.apply( this, arguments );
    185     },
    186 
    187     /**
    188      * @since 3.9.0
    189      */
    190     activate: function() {
    191         var library = this.get('library');
    192 
    193         // Limit the library to images only.
    194         library.props.set( 'type', this.get( 'type' ) );
    195 
    196         // Watch for uploaded attachments.
    197         this.get('library').observe( wp.Uploader.queue );
    198 
    199         this.frame.on( 'content:render:browse', this.renderSettings, this );
    200 
    201         Library.prototype.activate.apply( this, arguments );
    202     },
    203 
    204     /**
    205      * @since 3.9.0
    206      */
    207     deactivate: function() {
    208         // Stop watching for uploaded attachments.
    209         this.get('library').unobserve( wp.Uploader.queue );
    210 
    211         this.frame.off( 'content:render:browse', this.renderSettings, this );
    212 
    213         Library.prototype.deactivate.apply( this, arguments );
    214     },
    215 
    216     /**
    217      * Render the collection embed settings view in the browser sidebar.
     424var StateMachine = function( states ) {
     425    // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
     426    this.states = new Backbone.Collection( states );
     427};
     428
     429// Use Backbone's self-propagating `extend` inheritance method.
     430StateMachine.extend = Backbone.Model.extend;
     431
     432_.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{
     433    /**
     434     * Fetch a state.
    218435     *
    219      * @todo This is against the pattern elsewhere in media. Typically the frame
    220      *       is responsible for adding region mode callbacks. Explain.
     436     * If no `id` is provided, returns the active state.
    221437     *
    222      * @since 3.9.0
     438     * Implicitly creates states.
    223439     *
    224      * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
    225      */
    226     renderSettings: function( attachmentsBrowserView ) {
    227         var library = this.get('library'),
    228             collectionType = this.get('collectionType'),
    229             dragInfoText = this.get('dragInfoText'),
    230             SettingsView = this.get('SettingsView'),
    231             obj = {};
    232 
    233         if ( ! library || ! attachmentsBrowserView ) {
    234             return;
    235         }
    236 
    237         library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
    238 
    239         obj[ collectionType ] = new SettingsView({
    240             controller: this,
    241             model:      library[ collectionType ],
    242             priority:   40
    243         });
    244 
    245         attachmentsBrowserView.sidebar.set( obj );
    246 
    247         if ( dragInfoText ) {
    248             attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
    249                 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
    250                 priority: -40
    251             }) );
    252         }
    253 
    254         // Add the 'Reverse order' button to the toolbar.
    255         attachmentsBrowserView.toolbar.set( 'reverse', {
    256             text:     l10n.reverseOrder,
    257             priority: 80,
    258 
    259             click: function() {
    260                 library.reset( library.toArray().reverse() );
    261             }
    262         });
     440     * Ensure that the `states` collection exists so the `StateMachine`
     441     *   can be used as a mixin.
     442     *
     443     * @since 3.5.0
     444     *
     445     * @param {string} id
     446     * @returns {wp.media.controller.State} Returns a State model
     447     *   from the StateMachine collection
     448     */
     449    state: function( id ) {
     450        this.states = this.states || new Backbone.Collection();
     451
     452        // Default to the active state.
     453        id = id || this._state;
     454
     455        if ( id && ! this.states.get( id ) ) {
     456            this.states.add({ id: id });
     457        }
     458        return this.states.get( id );
     459    },
     460
     461    /**
     462     * Sets the active state.
     463     *
     464     * Bail if we're trying to select the current state, if we haven't
     465     * created the `states` collection, or are trying to select a state
     466     * that does not exist.
     467     *
     468     * @since 3.5.0
     469     *
     470     * @param {string} id
     471     *
     472     * @fires wp.media.controller.State#deactivate
     473     * @fires wp.media.controller.State#activate
     474     *
     475     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
     476     */
     477    setState: function( id ) {
     478        var previous = this.state();
     479
     480        if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
     481            return this;
     482        }
     483
     484        if ( previous ) {
     485            previous.trigger('deactivate');
     486            this._lastState = previous.id;
     487        }
     488
     489        this._state = id;
     490        this.state().trigger('activate');
     491
     492        return this;
     493    },
     494
     495    /**
     496     * Returns the previous active state.
     497     *
     498     * Call the `state()` method with no parameters to retrieve the current
     499     * active state.
     500     *
     501     * @since 3.5.0
     502     *
     503     * @returns {wp.media.controller.State} Returns a State model
     504     *    from the StateMachine collection
     505     */
     506    lastState: function() {
     507        if ( this._lastState ) {
     508            return this.state( this._lastState );
     509        }
    263510    }
    264511});
    265512
    266 module.exports = CollectionEdit;
    267 
    268 },{}],3:[function(require,module,exports){
     513// Map all event binding and triggering on a StateMachine to its `states` collection.
     514_.each([ 'on', 'off', 'trigger' ], function( method ) {
     515    /**
     516     * @function on
     517     * @memberOf wp.media.controller.StateMachine
     518     * @instance
     519     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     520     */
     521    /**
     522     * @function off
     523     * @memberOf wp.media.controller.StateMachine
     524     * @instance
     525     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     526     */
     527    /**
     528     * @function trigger
     529     * @memberOf wp.media.controller.StateMachine
     530     * @instance
     531     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     532     */
     533    StateMachine.prototype[ method ] = function() {
     534        // Ensure that the `states` collection exists so the `StateMachine`
     535        // can be used as a mixin.
     536        this.states = this.states || new Backbone.Collection();
     537        // Forward the method to the `states` collection.
     538        this.states[ method ].apply( this.states, arguments );
     539        return this;
     540    };
     541});
     542
     543module.exports = StateMachine;
     544
     545
     546/***/ }),
     547/* 29 */
     548/***/ (function(module, exports) {
     549
     550/**
     551 * wp.media.controller.State
     552 *
     553 * A state is a step in a workflow that when set will trigger the controllers
     554 * for the regions to be updated as specified in the frame.
     555 *
     556 * A state has an event-driven lifecycle:
     557 *
     558 *     'ready'      triggers when a state is added to a state machine's collection.
     559 *     'activate'   triggers when a state is activated by a state machine.
     560 *     'deactivate' triggers when a state is deactivated by a state machine.
     561 *     'reset'      is not triggered automatically. It should be invoked by the
     562 *                  proper controller to reset the state to its default.
     563 *
     564 * @memberOf wp.media.controller
     565 *
     566 * @class
     567 * @augments Backbone.Model
     568 */
     569var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{
     570    /**
     571     * Constructor.
     572     *
     573     * @since 3.5.0
     574     */
     575    constructor: function() {
     576        this.on( 'activate', this._preActivate, this );
     577        this.on( 'activate', this.activate, this );
     578        this.on( 'activate', this._postActivate, this );
     579        this.on( 'deactivate', this._deactivate, this );
     580        this.on( 'deactivate', this.deactivate, this );
     581        this.on( 'reset', this.reset, this );
     582        this.on( 'ready', this._ready, this );
     583        this.on( 'ready', this.ready, this );
     584        /**
     585         * Call parent constructor with passed arguments
     586         */
     587        Backbone.Model.apply( this, arguments );
     588        this.on( 'change:menu', this._updateMenu, this );
     589    },
     590    /**
     591     * Ready event callback.
     592     *
     593     * @abstract
     594     * @since 3.5.0
     595     */
     596    ready: function() {},
     597
     598    /**
     599     * Activate event callback.
     600     *
     601     * @abstract
     602     * @since 3.5.0
     603     */
     604    activate: function() {},
     605
     606    /**
     607     * Deactivate event callback.
     608     *
     609     * @abstract
     610     * @since 3.5.0
     611     */
     612    deactivate: function() {},
     613
     614    /**
     615     * Reset event callback.
     616     *
     617     * @abstract
     618     * @since 3.5.0
     619     */
     620    reset: function() {},
     621
     622    /**
     623     * @access private
     624     * @since 3.5.0
     625     */
     626    _ready: function() {
     627        this._updateMenu();
     628    },
     629
     630    /**
     631     * @access private
     632     * @since 3.5.0
     633    */
     634    _preActivate: function() {
     635        this.active = true;
     636    },
     637
     638    /**
     639     * @access private
     640     * @since 3.5.0
     641     */
     642    _postActivate: function() {
     643        this.on( 'change:menu', this._menu, this );
     644        this.on( 'change:titleMode', this._title, this );
     645        this.on( 'change:content', this._content, this );
     646        this.on( 'change:toolbar', this._toolbar, this );
     647
     648        this.frame.on( 'title:render:default', this._renderTitle, this );
     649
     650        this._title();
     651        this._menu();
     652        this._toolbar();
     653        this._content();
     654        this._router();
     655    },
     656
     657    /**
     658     * @access private
     659     * @since 3.5.0
     660     */
     661    _deactivate: function() {
     662        this.active = false;
     663
     664        this.frame.off( 'title:render:default', this._renderTitle, this );
     665
     666        this.off( 'change:menu', this._menu, this );
     667        this.off( 'change:titleMode', this._title, this );
     668        this.off( 'change:content', this._content, this );
     669        this.off( 'change:toolbar', this._toolbar, this );
     670    },
     671
     672    /**
     673     * @access private
     674     * @since 3.5.0
     675     */
     676    _title: function() {
     677        this.frame.title.render( this.get('titleMode') || 'default' );
     678    },
     679
     680    /**
     681     * @access private
     682     * @since 3.5.0
     683     */
     684    _renderTitle: function( view ) {
     685        view.$el.text( this.get('title') || '' );
     686    },
     687
     688    /**
     689     * @access private
     690     * @since 3.5.0
     691     */
     692    _router: function() {
     693        var router = this.frame.router,
     694            mode = this.get('router'),
     695            view;
     696
     697        this.frame.$el.toggleClass( 'hide-router', ! mode );
     698        if ( ! mode ) {
     699            return;
     700        }
     701
     702        this.frame.router.render( mode );
     703
     704        view = router.get();
     705        if ( view && view.select ) {
     706            view.select( this.frame.content.mode() );
     707        }
     708    },
     709
     710    /**
     711     * @access private
     712     * @since 3.5.0
     713     */
     714    _menu: function() {
     715        var menu = this.frame.menu,
     716            mode = this.get('menu'),
     717            view;
     718
     719        this.frame.$el.toggleClass( 'hide-menu', ! mode );
     720        if ( ! mode ) {
     721            return;
     722        }
     723
     724        menu.mode( mode );
     725
     726        view = menu.get();
     727        if ( view && view.select ) {
     728            view.select( this.id );
     729        }
     730    },
     731
     732    /**
     733     * @access private
     734     * @since 3.5.0
     735     */
     736    _updateMenu: function() {
     737        var previous = this.previous('menu'),
     738            menu = this.get('menu');
     739
     740        if ( previous ) {
     741            this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
     742        }
     743
     744        if ( menu ) {
     745            this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
     746        }
     747    },
     748
     749    /**
     750     * Create a view in the media menu for the state.
     751     *
     752     * @access private
     753     * @since 3.5.0
     754     *
     755     * @param {media.view.Menu} view The menu view.
     756     */
     757    _renderMenu: function( view ) {
     758        var menuItem = this.get('menuItem'),
     759            title = this.get('title'),
     760            priority = this.get('priority');
     761
     762        if ( ! menuItem && title ) {
     763            menuItem = { text: title };
     764
     765            if ( priority ) {
     766                menuItem.priority = priority;
     767            }
     768        }
     769
     770        if ( ! menuItem ) {
     771            return;
     772        }
     773
     774        view.set( this.id, menuItem );
     775    }
     776});
     777
     778_.each(['toolbar','content'], function( region ) {
     779    /**
     780     * @access private
     781     */
     782    State.prototype[ '_' + region ] = function() {
     783        var mode = this.get( region );
     784        if ( mode ) {
     785            this.frame[ region ].render( mode );
     786        }
     787    };
     788});
     789
     790module.exports = State;
     791
     792
     793/***/ }),
     794/* 30 */
     795/***/ (function(module, exports) {
     796
     797/**
     798 * wp.media.selectionSync
     799 *
     800 * Sync an attachments selection in a state with another state.
     801 *
     802 * Allows for selecting multiple images in the Add Media workflow, and then
     803 * switching to the Insert Gallery workflow while preserving the attachments selection.
     804 *
     805 * @memberOf wp.media
     806 *
     807 * @mixin
     808 */
     809var selectionSync = {
     810    /**
     811     * @since 3.5.0
     812     */
     813    syncSelection: function() {
     814        var selection = this.get('selection'),
     815            manager = this.frame._selection;
     816
     817        if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     818            return;
     819        }
     820
     821        // If the selection supports multiple items, validate the stored
     822        // attachments based on the new selection's conditions. Record
     823        // the attachments that are not included; we'll maintain a
     824        // reference to those. Other attachments are considered in flux.
     825        if ( selection.multiple ) {
     826            selection.reset( [], { silent: true });
     827            selection.validateAll( manager.attachments );
     828            manager.difference = _.difference( manager.attachments.models, selection.models );
     829        }
     830
     831        // Sync the selection's single item with the master.
     832        selection.single( manager.single );
     833    },
     834
     835    /**
     836     * Record the currently active attachments, which is a combination
     837     * of the selection's attachments and the set of selected
     838     * attachments that this specific selection considered invalid.
     839     * Reset the difference and record the single attachment.
     840     *
     841     * @since 3.5.0
     842     */
     843    recordSelection: function() {
     844        var selection = this.get('selection'),
     845            manager = this.frame._selection;
     846
     847        if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     848            return;
     849        }
     850
     851        if ( selection.multiple ) {
     852            manager.attachments.reset( selection.toArray().concat( manager.difference ) );
     853            manager.difference = [];
     854        } else {
     855            manager.attachments.add( selection.toArray() );
     856        }
     857
     858        manager.single = selection._single;
     859    }
     860};
     861
     862module.exports = selectionSync;
     863
     864
     865/***/ }),
     866/* 31 */
     867/***/ (function(module, exports) {
     868
    269869var l10n = wp.media.view.l10n,
    270     Cropper;
     870    getUserSetting = window.getUserSetting,
     871    setUserSetting = window.setUserSetting,
     872    Library;
    271873
    272874/**
    273  * wp.media.controller.Cropper
    274  *
    275  * A state for cropping an image.
     875 * wp.media.controller.Library
     876 *
     877 * A state for choosing an attachment or group of attachments from the media library.
    276878 *
    277879 * @memberOf wp.media.controller
     
    280882 * @augments wp.media.controller.State
    281883 * @augments Backbone.Model
     884 * @mixes media.selectionSync
     885 *
     886 * @param {object}                          [attributes]                         The attributes hash passed to the state.
     887 * @param {string}                          [attributes.id=library]              Unique identifier.
     888 * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
     889 * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
     890 *                                                                               If one is not supplied, a collection of all attachments will be created.
     891 * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
     892 *                                                                               If the 'selection' attribute is a plain JS object,
     893 *                                                                               a Selection will be created using its values as the selection instance's `props` model.
     894 *                                                                               Otherwise, it will copy the library's `props` model.
     895 * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
     896 * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
     897 *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
     898 * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
     899 * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
     900 * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
     901 * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
     902 * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
     903 *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
     904 * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     905 * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     906 * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     907 * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     908 * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    282909 */
    283 Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{
     910Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{
    284911    defaults: {
    285         id:          'cropper',
    286         title:       l10n.cropImage,
    287         // Region mode defaults.
    288         toolbar:     'crop',
    289         content:     'crop',
    290         router:      false,
    291         canSkipCrop: false,
    292 
    293         // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state.
    294         doCropArgs: {}
    295     },
    296 
     912        id:                 'library',
     913        title:              l10n.mediaLibraryTitle,
     914        multiple:           false,
     915        content:            'upload',
     916        menu:               'default',
     917        router:             'browse',
     918        toolbar:            'select',
     919        searchable:         true,
     920        filterable:         false,
     921        sortable:           true,
     922        autoSelect:         true,
     923        describe:           false,
     924        contentUserSetting: true,
     925        syncSelection:      true
     926    },
     927
     928    /**
     929     * If a library isn't provided, query all media items.
     930     * If a selection instance isn't provided, create one.
     931     *
     932     * @since 3.5.0
     933     */
     934    initialize: function() {
     935        var selection = this.get('selection'),
     936            props;
     937
     938        if ( ! this.get('library') ) {
     939            this.set( 'library', wp.media.query() );
     940        }
     941
     942        if ( ! ( selection instanceof wp.media.model.Selection ) ) {
     943            props = selection;
     944
     945            if ( ! props ) {
     946                props = this.get('library').props.toJSON();
     947                props = _.omit( props, 'orderby', 'query' );
     948            }
     949
     950            this.set( 'selection', new wp.media.model.Selection( null, {
     951                multiple: this.get('multiple'),
     952                props: props
     953            }) );
     954        }
     955
     956        this.resetDisplays();
     957    },
     958
     959    /**
     960     * @since 3.5.0
     961     */
    297962    activate: function() {
    298         this.frame.on( 'content:create:crop', this.createCropContent, this );
    299         this.frame.on( 'close', this.removeCropper, this );
    300         this.set('selection', new Backbone.Collection(this.frame._selection.single));
    301     },
    302 
     963        this.syncSelection();
     964
     965        wp.Uploader.queue.on( 'add', this.uploading, this );
     966
     967        this.get('selection').on( 'add remove reset', this.refreshContent, this );
     968
     969        if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
     970            this.frame.on( 'content:activate', this.saveContentMode, this );
     971            this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
     972        }
     973    },
     974
     975    /**
     976     * @since 3.5.0
     977     */
    303978    deactivate: function() {
    304         this.frame.toolbar.mode('browse');
    305     },
    306 
    307     createCropContent: function() {
    308         this.cropperView = new wp.media.view.Cropper({
    309             controller: this,
    310             attachment: this.get('selection').first()
    311         });
    312         this.cropperView.on('image-loaded', this.createCropToolbar, this);
    313         this.frame.content.set(this.cropperView);
    314 
    315     },
    316     removeCropper: function() {
    317         this.imgSelect.cancelSelection();
    318         this.imgSelect.setOptions({remove: true});
    319         this.imgSelect.update();
    320         this.cropperView.remove();
    321     },
    322     createCropToolbar: function() {
    323         var canSkipCrop, toolbarOptions;
    324 
    325         canSkipCrop = this.get('canSkipCrop') || false;
    326 
    327         toolbarOptions = {
    328             controller: this.frame,
    329             items: {
    330                 insert: {
    331                     style:    'primary',
    332                     text:     l10n.cropImage,
    333                     priority: 80,
    334                     requires: { library: false, selection: false },
    335 
    336                     click: function() {
    337                         var controller = this.controller,
    338                             selection;
    339 
    340                         selection = controller.state().get('selection').first();
    341                         selection.set({cropDetails: controller.state().imgSelect.getSelection()});
    342 
    343                         this.$el.text(l10n.cropping);
    344                         this.$el.attr('disabled', true);
    345 
    346                         controller.state().doCrop( selection ).done( function( croppedImage ) {
    347                             controller.trigger('cropped', croppedImage );
    348                             controller.close();
    349                         }).fail( function() {
    350                             controller.trigger('content:error:crop');
    351                         });
    352                     }
    353                 }
     979        this.recordSelection();
     980
     981        this.frame.off( 'content:activate', this.saveContentMode, this );
     982
     983        // Unbind all event handlers that use this state as the context
     984        // from the selection.
     985        this.get('selection').off( null, null, this );
     986
     987        wp.Uploader.queue.off( null, null, this );
     988    },
     989
     990    /**
     991     * Reset the library to its initial state.
     992     *
     993     * @since 3.5.0
     994     */
     995    reset: function() {
     996        this.get('selection').reset();
     997        this.resetDisplays();
     998        this.refreshContent();
     999    },
     1000
     1001    /**
     1002     * Reset the attachment display settings defaults to the site options.
     1003     *
     1004     * If site options don't define them, fall back to a persistent user setting.
     1005     *
     1006     * @since 3.5.0
     1007     */
     1008    resetDisplays: function() {
     1009        var defaultProps = wp.media.view.settings.defaultProps;
     1010        this._displays = [];
     1011        this._defaultDisplaySettings = {
     1012            align: getUserSetting( 'align', defaultProps.align ) || 'none',
     1013            size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
     1014            link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
     1015        };
     1016    },
     1017
     1018    /**
     1019     * Create a model to represent display settings (alignment, etc.) for an attachment.
     1020     *
     1021     * @since 3.5.0
     1022     *
     1023     * @param {wp.media.model.Attachment} attachment
     1024     * @returns {Backbone.Model}
     1025     */
     1026    display: function( attachment ) {
     1027        var displays = this._displays;
     1028
     1029        if ( ! displays[ attachment.cid ] ) {
     1030            displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
     1031        }
     1032        return displays[ attachment.cid ];
     1033    },
     1034
     1035    /**
     1036     * Given an attachment, create attachment display settings properties.
     1037     *
     1038     * @since 3.6.0
     1039     *
     1040     * @param {wp.media.model.Attachment} attachment
     1041     * @returns {Object}
     1042     */
     1043    defaultDisplaySettings: function( attachment ) {
     1044        var settings = _.clone( this._defaultDisplaySettings );
     1045
     1046        if ( settings.canEmbed = this.canEmbed( attachment ) ) {
     1047            settings.link = 'embed';
     1048        } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
     1049            settings.link = 'file';
     1050        }
     1051
     1052        return settings;
     1053    },
     1054
     1055    /**
     1056     * Whether an attachment is image.
     1057     *
     1058     * @since 4.4.1
     1059     *
     1060     * @param {wp.media.model.Attachment} attachment
     1061     * @returns {Boolean}
     1062     */
     1063    isImageAttachment: function( attachment ) {
     1064        // If uploading, we know the filename but not the mime type.
     1065        if ( attachment.get('uploading') ) {
     1066            return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
     1067        }
     1068
     1069        return attachment.get('type') === 'image';
     1070    },
     1071
     1072    /**
     1073     * Whether an attachment can be embedded (audio or video).
     1074     *
     1075     * @since 3.6.0
     1076     *
     1077     * @param {wp.media.model.Attachment} attachment
     1078     * @returns {Boolean}
     1079     */
     1080    canEmbed: function( attachment ) {
     1081        // If uploading, we know the filename but not the mime type.
     1082        if ( ! attachment.get('uploading') ) {
     1083            var type = attachment.get('type');
     1084            if ( type !== 'audio' && type !== 'video' ) {
     1085                return false;
    3541086            }
    355         };
    356 
    357         if ( canSkipCrop ) {
    358             _.extend( toolbarOptions.items, {
    359                 skip: {
    360                     style:      'secondary',
    361                     text:       l10n.skipCropping,
    362                     priority:   70,
    363                     requires:   { library: false, selection: false },
    364                     click:      function() {
    365                         var selection = this.controller.state().get('selection').first();
    366                         this.controller.state().cropperView.remove();
    367                         this.controller.trigger('skippedcrop', selection);
    368                         this.controller.close();
    369                     }
    370                 }
    371             });
    372         }
    373 
    374         this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
    375     },
    376 
    377     doCrop: function( attachment ) {
    378         return wp.ajax.post( 'custom-header-crop', _.extend(
    379             {},
    380             this.defaults.doCropArgs,
    381             {
    382                 nonce: attachment.get( 'nonces' ).edit,
    383                 id: attachment.get( 'id' ),
    384                 cropDetails: attachment.get( 'cropDetails' )
    385             }
    386         ) );
     1087        }
     1088
     1089        return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
     1090    },
     1091
     1092
     1093    /**
     1094     * If the state is active, no items are selected, and the current
     1095     * content mode is not an option in the state's router (provided
     1096     * the state has a router), reset the content mode to the default.
     1097     *
     1098     * @since 3.5.0
     1099     */
     1100    refreshContent: function() {
     1101        var selection = this.get('selection'),
     1102            frame = this.frame,
     1103            router = frame.router.get(),
     1104            mode = frame.content.mode();
     1105
     1106        if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
     1107            this.frame.content.render( this.get('content') );
     1108        }
     1109    },
     1110
     1111    /**
     1112     * Callback handler when an attachment is uploaded.
     1113     *
     1114     * Switch to the Media Library if uploaded from the 'Upload Files' tab.
     1115     *
     1116     * Adds any uploading attachments to the selection.
     1117     *
     1118     * If the state only supports one attachment to be selected and multiple
     1119     * attachments are uploaded, the last attachment in the upload queue will
     1120     * be selected.
     1121     *
     1122     * @since 3.5.0
     1123     *
     1124     * @param {wp.media.model.Attachment} attachment
     1125     */
     1126    uploading: function( attachment ) {
     1127        var content = this.frame.content;
     1128
     1129        if ( 'upload' === content.mode() ) {
     1130            this.frame.content.mode('browse');
     1131        }
     1132
     1133        if ( this.get( 'autoSelect' ) ) {
     1134            this.get('selection').add( attachment );
     1135            this.frame.trigger( 'library:selection:add' );
     1136        }
     1137    },
     1138
     1139    /**
     1140     * Persist the mode of the content region as a user setting.
     1141     *
     1142     * @since 3.5.0
     1143     */
     1144    saveContentMode: function() {
     1145        if ( 'browse' !== this.get('router') ) {
     1146            return;
     1147        }
     1148
     1149        var mode = this.frame.content.mode(),
     1150            view = this.frame.router.get();
     1151
     1152        if ( view && view.get( mode ) ) {
     1153            setUserSetting( 'libraryContent', mode );
     1154        }
    3871155    }
    3881156});
    3891157
    390 module.exports = Cropper;
    391 
    392 },{}],4:[function(require,module,exports){
    393 var Controller = wp.media.controller,
    394     CustomizeImageCropper;
     1158// Make selectionSync available on any Media Library state.
     1159_.extend( Library.prototype, wp.media.selectionSync );
     1160
     1161module.exports = Library;
     1162
     1163
     1164/***/ }),
     1165/* 32 */
     1166/***/ (function(module, exports) {
     1167
     1168var State = wp.media.controller.State,
     1169    Library = wp.media.controller.Library,
     1170    l10n = wp.media.view.l10n,
     1171    ImageDetails;
    3951172
    3961173/**
    397  * wp.media.controller.CustomizeImageCropper
    398  *
    399  * @memberOf wp.media.controller
    400  *
    401  * A state for cropping an image.
    402  *
    403  * @class
    404  * @augments wp.media.controller.Cropper
    405  * @augments wp.media.controller.State
    406  * @augments Backbone.Model
    407  */
    408 CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{
    409     doCrop: function( attachment ) {
    410         var cropDetails = attachment.get( 'cropDetails' ),
    411             control = this.get( 'control' ),
    412             ratio = cropDetails.width / cropDetails.height;
    413 
    414         // Use crop measurements when flexible in both directions.
    415         if ( control.params.flex_width && control.params.flex_height ) {
    416             cropDetails.dst_width  = cropDetails.width;
    417             cropDetails.dst_height = cropDetails.height;
    418 
    419         // Constrain flexible side based on image ratio and size of the fixed side.
    420         } else {
    421             cropDetails.dst_width  = control.params.flex_width  ? control.params.height * ratio : control.params.width;
    422             cropDetails.dst_height = control.params.flex_height ? control.params.width  / ratio : control.params.height;
    423         }
    424 
    425         return wp.ajax.post( 'crop-image', {
    426             wp_customize: 'on',
    427             nonce: attachment.get( 'nonces' ).edit,
    428             id: attachment.get( 'id' ),
    429             context: control.id,
    430             cropDetails: cropDetails
    431         } );
    432     }
    433 });
    434 
    435 module.exports = CustomizeImageCropper;
    436 
    437 },{}],5:[function(require,module,exports){
    438 var l10n = wp.media.view.l10n,
    439     EditImage;
    440 
    441 /**
    442  * wp.media.controller.EditImage
    443  *
    444  * A state for editing (cropping, etc.) an image.
     1174 * wp.media.controller.ImageDetails
     1175 *
     1176 * A state for editing the attachment display settings of an image that's been
     1177 * inserted into the editor.
    4451178 *
    4461179 * @memberOf wp.media.controller
     
    4501183 * @augments Backbone.Model
    4511184 *
    452  * @param {object}                    attributes                      The attributes hash passed to the state.
    453  * @param {wp.media.model.Attachment} attributes.model                The attachment.
    454  * @param {string}                    [attributes.id=edit-image]      Unique identifier.
    455  * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
    456  * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
    457  * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
    458  * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
    459  * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
     1185 * @param {object}                    [attributes]                       The attributes hash passed to the state.
     1186 * @param {string}                    [attributes.id=image-details]      Unique identifier.
     1187 * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
     1188 * @param {wp.media.model.Attachment} attributes.image                   The image's model.
     1189 * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
     1190 * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
     1191 * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
     1192 * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
     1193 * @param {boolean}                   [attributes.editing=false]         Unused.
     1194 * @param {int}                       [attributes.priority=60]           Unused.
     1195 *
     1196 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
     1197 *       however this may not do anything.
    4601198 */
    461 EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{
    462     defaults: {
    463         id:      'edit-image',
    464         title:   l10n.editImage,
    465         menu:    false,
    466         toolbar: 'edit-image',
    467         content: 'edit-image',
    468         url:     ''
    469     },
     1199ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototype */{
     1200    defaults: _.defaults({
     1201        id:       'image-details',
     1202        title:    l10n.imageDetailsTitle,
     1203        content:  'image-details',
     1204        menu:     false,
     1205        router:   false,
     1206        toolbar:  'image-details',
     1207        editing:  false,
     1208        priority: 60
     1209    }, Library.prototype.defaults ),
    4701210
    4711211    /**
    4721212     * @since 3.9.0
     1213     *
     1214     * @param options Attributes
     1215     */
     1216    initialize: function( options ) {
     1217        this.image = options.image;
     1218        State.prototype.initialize.apply( this, arguments );
     1219    },
     1220
     1221    /**
     1222     * @since 3.9.0
    4731223     */
    4741224    activate: function() {
    475         this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
    476     },
    477 
    478     /**
    479      * @since 3.9.0
    480      */
    481     deactivate: function() {
    482         this.frame.off( 'toolbar:render:edit-image' );
    483     },
    484 
    485     /**
    486      * @since 3.9.0
    487      */
    488     toolbar: function() {
    489         var frame = this.frame,
    490             lastState = frame.lastState(),
    491             previous = lastState && lastState.id;
    492 
    493         frame.toolbar.set( new wp.media.view.Toolbar({
    494             controller: frame,
    495             items: {
    496                 back: {
    497                     style: 'primary',
    498                     text:     l10n.back,
    499                     priority: 20,
    500                     click:    function() {
    501                         if ( previous ) {
    502                             frame.setState( previous );
    503                         } else {
    504                             frame.close();
    505                         }
    506                     }
    507                 }
    508             }
    509         }) );
     1225        this.frame.modal.$el.addClass('image-details');
    5101226    }
    5111227});
    5121228
    513 module.exports = EditImage;
    514 
    515 },{}],6:[function(require,module,exports){
    516 var l10n = wp.media.view.l10n,
    517     $ = Backbone.$,
    518     Embed;
    519 
    520 /**
    521  * wp.media.controller.Embed
    522  *
    523  * A state for embedding media from a URL.
    524  *
    525  * @memberOf wp.media.controller
    526  *
    527  * @class
    528  * @augments wp.media.controller.State
    529  * @augments Backbone.Model
    530  *
    531  * @param {object} attributes                         The attributes hash passed to the state.
    532  * @param {string} [attributes.id=embed]              Unique identifier.
    533  * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
    534  * @param {string} [attributes.content=embed]         Initial mode for the content region.
    535  * @param {string} [attributes.menu=default]          Initial mode for the menu region.
    536  * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
    537  * @param {string} [attributes.menu=false]            Initial mode for the menu region.
    538  * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
    539  * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
    540  * @param {string} [attributes.url]                   The embed URL.
    541  * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
    542  */
    543 Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{
    544     defaults: {
    545         id:       'embed',
    546         title:    l10n.insertFromUrlTitle,
    547         content:  'embed',
    548         menu:     'default',
    549         toolbar:  'main-embed',
    550         priority: 120,
    551         type:     'link',
    552         url:      '',
    553         metadata: {}
    554     },
    555 
    556     // The amount of time used when debouncing the scan.
    557     sensitivity: 400,
    558 
    559     initialize: function(options) {
    560         this.metadata = options.metadata;
    561         this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
    562         this.props = new Backbone.Model( this.metadata || { url: '' });
    563         this.props.on( 'change:url', this.debouncedScan, this );
    564         this.props.on( 'change:url', this.refresh, this );
    565         this.on( 'scan', this.scanImage, this );
    566     },
    567 
    568     /**
    569      * Trigger a scan of the embedded URL's content for metadata required to embed.
    570      *
    571      * @fires wp.media.controller.Embed#scan
    572      */
    573     scan: function() {
    574         var scanners,
    575             embed = this,
    576             attributes = {
    577                 type: 'link',
    578                 scanners: []
    579             };
    580 
    581         // Scan is triggered with the list of `attributes` to set on the
    582         // state, useful for the 'type' attribute and 'scanners' attribute,
    583         // an array of promise objects for asynchronous scan operations.
    584         if ( this.props.get('url') ) {
    585             this.trigger( 'scan', attributes );
    586         }
    587 
    588         if ( attributes.scanners.length ) {
    589             scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
    590             scanners.always( function() {
    591                 if ( embed.get('scanners') === scanners ) {
    592                     embed.set( 'loading', false );
    593                 }
    594             });
    595         } else {
    596             attributes.scanners = null;
    597         }
    598 
    599         attributes.loading = !! attributes.scanners;
    600         this.set( attributes );
    601     },
    602     /**
    603      * Try scanning the embed as an image to discover its dimensions.
    604      *
    605      * @param {Object} attributes
    606      */
    607     scanImage: function( attributes ) {
    608         var frame = this.frame,
    609             state = this,
    610             url = this.props.get('url'),
    611             image = new Image(),
    612             deferred = $.Deferred();
    613 
    614         attributes.scanners.push( deferred.promise() );
    615 
    616         // Try to load the image and find its width/height.
    617         image.onload = function() {
    618             deferred.resolve();
    619 
    620             if ( state !== frame.state() || url !== state.props.get('url') ) {
    621                 return;
    622             }
    623 
    624             state.set({
    625                 type: 'image'
    626             });
    627 
    628             state.props.set({
    629                 width:  image.width,
    630                 height: image.height
    631             });
    632         };
    633 
    634         image.onerror = deferred.reject;
    635         image.src = url;
    636     },
    637 
    638     refresh: function() {
    639         this.frame.toolbar.get().refresh();
    640     },
    641 
    642     reset: function() {
    643         this.props.clear().set({ url: '' });
    644 
    645         if ( this.active ) {
    646             this.refresh();
    647         }
    648     }
    649 });
    650 
    651 module.exports = Embed;
    652 
    653 },{}],7:[function(require,module,exports){
    654 var Attachment = wp.media.model.Attachment,
    655     Library = wp.media.controller.Library,
    656     l10n = wp.media.view.l10n,
    657     FeaturedImage;
    658 
    659 /**
    660  * wp.media.controller.FeaturedImage
    661  *
    662  * A state for selecting a featured image for a post.
    663  *
    664  * @memberOf wp.media.controller
    665  *
    666  * @class
    667  * @augments wp.media.controller.Library
    668  * @augments wp.media.controller.State
    669  * @augments Backbone.Model
    670  *
    671  * @param {object}                     [attributes]                          The attributes hash passed to the state.
    672  * @param {string}                     [attributes.id=featured-image]        Unique identifier.
    673  * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
    674  * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
    675  *                                                                           If one is not supplied, a collection of all images will be created.
    676  * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
    677  * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
    678  *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
    679  * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
    680  * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
    681  * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
    682  * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
    683  * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
    684  * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
    685  *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
    686  * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    687  * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
    688  * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    689  * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
    690  * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
    691  */
    692 FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{
    693     defaults: _.defaults({
    694         id:            'featured-image',
    695         title:         l10n.setFeaturedImageTitle,
    696         multiple:      false,
    697         filterable:    'uploaded',
    698         toolbar:       'featured-image',
    699         priority:      60,
    700         syncSelection: true
    701     }, Library.prototype.defaults ),
    702 
    703     /**
    704      * @since 3.5.0
    705      */
    706     initialize: function() {
    707         var library, comparator;
    708 
    709         // If we haven't been provided a `library`, create a `Selection`.
    710         if ( ! this.get('library') ) {
    711             this.set( 'library', wp.media.query({ type: 'image' }) );
    712         }
    713 
    714         Library.prototype.initialize.apply( this, arguments );
    715 
    716         library    = this.get('library');
    717         comparator = library.comparator;
    718 
    719         // Overload the library's comparator to push items that are not in
    720         // the mirrored query to the front of the aggregate collection.
    721         library.comparator = function( a, b ) {
    722             var aInQuery = !! this.mirroring.get( a.cid ),
    723                 bInQuery = !! this.mirroring.get( b.cid );
    724 
    725             if ( ! aInQuery && bInQuery ) {
    726                 return -1;
    727             } else if ( aInQuery && ! bInQuery ) {
    728                 return 1;
    729             } else {
    730                 return comparator.apply( this, arguments );
    731             }
    732         };
    733 
    734         // Add all items in the selection to the library, so any featured
    735         // images that are not initially loaded still appear.
    736         library.observe( this.get('selection') );
    737     },
    738 
    739     /**
    740      * @since 3.5.0
    741      */
    742     activate: function() {
    743         this.updateSelection();
    744         this.frame.on( 'open', this.updateSelection, this );
    745 
    746         Library.prototype.activate.apply( this, arguments );
    747     },
    748 
    749     /**
    750      * @since 3.5.0
    751      */
    752     deactivate: function() {
    753         this.frame.off( 'open', this.updateSelection, this );
    754 
    755         Library.prototype.deactivate.apply( this, arguments );
    756     },
    757 
    758     /**
    759      * @since 3.5.0
    760      */
    761     updateSelection: function() {
    762         var selection = this.get('selection'),
    763             id = wp.media.view.settings.post.featuredImageId,
    764             attachment;
    765 
    766         if ( '' !== id && -1 !== id ) {
    767             attachment = Attachment.get( id );
    768             attachment.fetch();
    769         }
    770 
    771         selection.reset( attachment ? [ attachment ] : [] );
    772     }
    773 });
    774 
    775 module.exports = FeaturedImage;
    776 
    777 },{}],8:[function(require,module,exports){
    778 var Selection = wp.media.model.Selection,
    779     Library = wp.media.controller.Library,
    780     l10n = wp.media.view.l10n,
    781     GalleryAdd;
    782 
    783 /**
    784  * wp.media.controller.GalleryAdd
    785  *
    786  * A state for selecting more images to add to a gallery.
    787  *
    788  * @memberOf wp.media.controller
    789  *
    790  * @class
    791  * @augments wp.media.controller.Library
    792  * @augments wp.media.controller.State
    793  * @augments Backbone.Model
    794  *
    795  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    796  * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
    797  * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
    798  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    799  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    800  *                                                                          If one is not supplied, a collection of all images will be created.
    801  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    802  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    803  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    804  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    805  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    806  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    807  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    808  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    809  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    810  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    811  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    812  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    813  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    814  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    815  */
    816 GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{
    817     defaults: _.defaults({
    818         id:            'gallery-library',
    819         title:         l10n.addToGalleryTitle,
    820         multiple:      'add',
    821         filterable:    'uploaded',
    822         menu:          'gallery',
    823         toolbar:       'gallery-add',
    824         priority:      100,
    825         syncSelection: false
    826     }, Library.prototype.defaults ),
    827 
    828     /**
    829      * @since 3.5.0
    830      */
    831     initialize: function() {
    832         // If a library wasn't supplied, create a library of images.
    833         if ( ! this.get('library') ) {
    834             this.set( 'library', wp.media.query({ type: 'image' }) );
    835         }
    836 
    837         Library.prototype.initialize.apply( this, arguments );
    838     },
    839 
    840     /**
    841      * @since 3.5.0
    842      */
    843     activate: function() {
    844         var library = this.get('library'),
    845             edit    = this.frame.state('gallery-edit').get('library');
    846 
    847         if ( this.editLibrary && this.editLibrary !== edit ) {
    848             library.unobserve( this.editLibrary );
    849         }
    850 
    851         // Accepts attachments that exist in the original library and
    852         // that do not exist in gallery's library.
    853         library.validator = function( attachment ) {
    854             return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    855         };
    856 
    857         // Reset the library to ensure that all attachments are re-added
    858         // to the collection. Do so silently, as calling `observe` will
    859         // trigger the `reset` event.
    860         library.reset( library.mirroring.models, { silent: true });
    861         library.observe( edit );
    862         this.editLibrary = edit;
    863 
    864         Library.prototype.activate.apply( this, arguments );
    865     }
    866 });
    867 
    868 module.exports = GalleryAdd;
    869 
    870 },{}],9:[function(require,module,exports){
     1229module.exports = ImageDetails;
     1230
     1231
     1232/***/ }),
     1233/* 33 */
     1234/***/ (function(module, exports) {
     1235
    8711236var Library = wp.media.controller.Library,
    8721237    l10n = wp.media.view.l10n,
     
    10121377module.exports = GalleryEdit;
    10131378
    1014 },{}],10:[function(require,module,exports){
    1015 var State = wp.media.controller.State,
     1379
     1380/***/ }),
     1381/* 34 */
     1382/***/ (function(module, exports) {
     1383
     1384var Selection = wp.media.model.Selection,
    10161385    Library = wp.media.controller.Library,
    10171386    l10n = wp.media.view.l10n,
    1018     ImageDetails;
     1387    GalleryAdd;
    10191388
    10201389/**
    1021  * wp.media.controller.ImageDetails
    1022  *
    1023  * A state for editing the attachment display settings of an image that's been
    1024  * inserted into the editor.
    1025  *
    1026  * @memberOf wp.media.controller
    1027  *
    1028  * @class
    1029  * @augments wp.media.controller.State
    1030  * @augments Backbone.Model
    1031  *
    1032  * @param {object}                    [attributes]                       The attributes hash passed to the state.
    1033  * @param {string}                    [attributes.id=image-details]      Unique identifier.
    1034  * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
    1035  * @param {wp.media.model.Attachment} attributes.image                   The image's model.
    1036  * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
    1037  * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
    1038  * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
    1039  * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
    1040  * @param {boolean}                   [attributes.editing=false]         Unused.
    1041  * @param {int}                       [attributes.priority=60]           Unused.
    1042  *
    1043  * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
    1044  *       however this may not do anything.
    1045  */
    1046 ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototype */{
    1047     defaults: _.defaults({
    1048         id:       'image-details',
    1049         title:    l10n.imageDetailsTitle,
    1050         content:  'image-details',
    1051         menu:     false,
    1052         router:   false,
    1053         toolbar:  'image-details',
    1054         editing:  false,
    1055         priority: 60
    1056     }, Library.prototype.defaults ),
    1057 
    1058     /**
    1059      * @since 3.9.0
    1060      *
    1061      * @param options Attributes
    1062      */
    1063     initialize: function( options ) {
    1064         this.image = options.image;
    1065         State.prototype.initialize.apply( this, arguments );
    1066     },
    1067 
    1068     /**
    1069      * @since 3.9.0
    1070      */
    1071     activate: function() {
    1072         this.frame.modal.$el.addClass('image-details');
    1073     }
    1074 });
    1075 
    1076 module.exports = ImageDetails;
    1077 
    1078 },{}],11:[function(require,module,exports){
    1079 var l10n = wp.media.view.l10n,
    1080     getUserSetting = window.getUserSetting,
    1081     setUserSetting = window.setUserSetting,
    1082     Library;
    1083 
    1084 /**
    1085  * wp.media.controller.Library
    1086  *
    1087  * A state for choosing an attachment or group of attachments from the media library.
    1088  *
    1089  * @memberOf wp.media.controller
    1090  *
    1091  * @class
    1092  * @augments wp.media.controller.State
    1093  * @augments Backbone.Model
    1094  * @mixes media.selectionSync
    1095  *
    1096  * @param {object}                          [attributes]                         The attributes hash passed to the state.
    1097  * @param {string}                          [attributes.id=library]              Unique identifier.
    1098  * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
    1099  * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
    1100  *                                                                               If one is not supplied, a collection of all attachments will be created.
    1101  * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
    1102  *                                                                               If the 'selection' attribute is a plain JS object,
    1103  *                                                                               a Selection will be created using its values as the selection instance's `props` model.
    1104  *                                                                               Otherwise, it will copy the library's `props` model.
    1105  * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
    1106  * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
    1107  *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
    1108  * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
    1109  * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
    1110  * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
    1111  * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
    1112  * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
    1113  *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
    1114  * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1115  * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1116  * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1117  * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1118  * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    1119  */
    1120 Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{
    1121     defaults: {
    1122         id:                 'library',
    1123         title:              l10n.mediaLibraryTitle,
    1124         multiple:           false,
    1125         content:            'upload',
    1126         menu:               'default',
    1127         router:             'browse',
    1128         toolbar:            'select',
    1129         searchable:         true,
    1130         filterable:         false,
    1131         sortable:           true,
    1132         autoSelect:         true,
    1133         describe:           false,
    1134         contentUserSetting: true,
    1135         syncSelection:      true
    1136     },
    1137 
    1138     /**
    1139      * If a library isn't provided, query all media items.
    1140      * If a selection instance isn't provided, create one.
    1141      *
    1142      * @since 3.5.0
    1143      */
    1144     initialize: function() {
    1145         var selection = this.get('selection'),
    1146             props;
    1147 
    1148         if ( ! this.get('library') ) {
    1149             this.set( 'library', wp.media.query() );
    1150         }
    1151 
    1152         if ( ! ( selection instanceof wp.media.model.Selection ) ) {
    1153             props = selection;
    1154 
    1155             if ( ! props ) {
    1156                 props = this.get('library').props.toJSON();
    1157                 props = _.omit( props, 'orderby', 'query' );
    1158             }
    1159 
    1160             this.set( 'selection', new wp.media.model.Selection( null, {
    1161                 multiple: this.get('multiple'),
    1162                 props: props
    1163             }) );
    1164         }
    1165 
    1166         this.resetDisplays();
    1167     },
    1168 
    1169     /**
    1170      * @since 3.5.0
    1171      */
    1172     activate: function() {
    1173         this.syncSelection();
    1174 
    1175         wp.Uploader.queue.on( 'add', this.uploading, this );
    1176 
    1177         this.get('selection').on( 'add remove reset', this.refreshContent, this );
    1178 
    1179         if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
    1180             this.frame.on( 'content:activate', this.saveContentMode, this );
    1181             this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
    1182         }
    1183     },
    1184 
    1185     /**
    1186      * @since 3.5.0
    1187      */
    1188     deactivate: function() {
    1189         this.recordSelection();
    1190 
    1191         this.frame.off( 'content:activate', this.saveContentMode, this );
    1192 
    1193         // Unbind all event handlers that use this state as the context
    1194         // from the selection.
    1195         this.get('selection').off( null, null, this );
    1196 
    1197         wp.Uploader.queue.off( null, null, this );
    1198     },
    1199 
    1200     /**
    1201      * Reset the library to its initial state.
    1202      *
    1203      * @since 3.5.0
    1204      */
    1205     reset: function() {
    1206         this.get('selection').reset();
    1207         this.resetDisplays();
    1208         this.refreshContent();
    1209     },
    1210 
    1211     /**
    1212      * Reset the attachment display settings defaults to the site options.
    1213      *
    1214      * If site options don't define them, fall back to a persistent user setting.
    1215      *
    1216      * @since 3.5.0
    1217      */
    1218     resetDisplays: function() {
    1219         var defaultProps = wp.media.view.settings.defaultProps;
    1220         this._displays = [];
    1221         this._defaultDisplaySettings = {
    1222             align: getUserSetting( 'align', defaultProps.align ) || 'none',
    1223             size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
    1224             link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
    1225         };
    1226     },
    1227 
    1228     /**
    1229      * Create a model to represent display settings (alignment, etc.) for an attachment.
    1230      *
    1231      * @since 3.5.0
    1232      *
    1233      * @param {wp.media.model.Attachment} attachment
    1234      * @returns {Backbone.Model}
    1235      */
    1236     display: function( attachment ) {
    1237         var displays = this._displays;
    1238 
    1239         if ( ! displays[ attachment.cid ] ) {
    1240             displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
    1241         }
    1242         return displays[ attachment.cid ];
    1243     },
    1244 
    1245     /**
    1246      * Given an attachment, create attachment display settings properties.
    1247      *
    1248      * @since 3.6.0
    1249      *
    1250      * @param {wp.media.model.Attachment} attachment
    1251      * @returns {Object}
    1252      */
    1253     defaultDisplaySettings: function( attachment ) {
    1254         var settings = _.clone( this._defaultDisplaySettings );
    1255 
    1256         if ( settings.canEmbed = this.canEmbed( attachment ) ) {
    1257             settings.link = 'embed';
    1258         } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
    1259             settings.link = 'file';
    1260         }
    1261 
    1262         return settings;
    1263     },
    1264 
    1265     /**
    1266      * Whether an attachment is image.
    1267      *
    1268      * @since 4.4.1
    1269      *
    1270      * @param {wp.media.model.Attachment} attachment
    1271      * @returns {Boolean}
    1272      */
    1273     isImageAttachment: function( attachment ) {
    1274         // If uploading, we know the filename but not the mime type.
    1275         if ( attachment.get('uploading') ) {
    1276             return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
    1277         }
    1278 
    1279         return attachment.get('type') === 'image';
    1280     },
    1281 
    1282     /**
    1283      * Whether an attachment can be embedded (audio or video).
    1284      *
    1285      * @since 3.6.0
    1286      *
    1287      * @param {wp.media.model.Attachment} attachment
    1288      * @returns {Boolean}
    1289      */
    1290     canEmbed: function( attachment ) {
    1291         // If uploading, we know the filename but not the mime type.
    1292         if ( ! attachment.get('uploading') ) {
    1293             var type = attachment.get('type');
    1294             if ( type !== 'audio' && type !== 'video' ) {
    1295                 return false;
    1296             }
    1297         }
    1298 
    1299         return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
    1300     },
    1301 
    1302 
    1303     /**
    1304      * If the state is active, no items are selected, and the current
    1305      * content mode is not an option in the state's router (provided
    1306      * the state has a router), reset the content mode to the default.
    1307      *
    1308      * @since 3.5.0
    1309      */
    1310     refreshContent: function() {
    1311         var selection = this.get('selection'),
    1312             frame = this.frame,
    1313             router = frame.router.get(),
    1314             mode = frame.content.mode();
    1315 
    1316         if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
    1317             this.frame.content.render( this.get('content') );
    1318         }
    1319     },
    1320 
    1321     /**
    1322      * Callback handler when an attachment is uploaded.
    1323      *
    1324      * Switch to the Media Library if uploaded from the 'Upload Files' tab.
    1325      *
    1326      * Adds any uploading attachments to the selection.
    1327      *
    1328      * If the state only supports one attachment to be selected and multiple
    1329      * attachments are uploaded, the last attachment in the upload queue will
    1330      * be selected.
    1331      *
    1332      * @since 3.5.0
    1333      *
    1334      * @param {wp.media.model.Attachment} attachment
    1335      */
    1336     uploading: function( attachment ) {
    1337         var content = this.frame.content;
    1338 
    1339         if ( 'upload' === content.mode() ) {
    1340             this.frame.content.mode('browse');
    1341         }
    1342 
    1343         if ( this.get( 'autoSelect' ) ) {
    1344             this.get('selection').add( attachment );
    1345             this.frame.trigger( 'library:selection:add' );
    1346         }
    1347     },
    1348 
    1349     /**
    1350      * Persist the mode of the content region as a user setting.
    1351      *
    1352      * @since 3.5.0
    1353      */
    1354     saveContentMode: function() {
    1355         if ( 'browse' !== this.get('router') ) {
    1356             return;
    1357         }
    1358 
    1359         var mode = this.frame.content.mode(),
    1360             view = this.frame.router.get();
    1361 
    1362         if ( view && view.get( mode ) ) {
    1363             setUserSetting( 'libraryContent', mode );
    1364         }
    1365     }
    1366 });
    1367 
    1368 // Make selectionSync available on any Media Library state.
    1369 _.extend( Library.prototype, wp.media.selectionSync );
    1370 
    1371 module.exports = Library;
    1372 
    1373 },{}],12:[function(require,module,exports){
    1374 /**
    1375  * wp.media.controller.MediaLibrary
     1390 * wp.media.controller.GalleryAdd
     1391 *
     1392 * A state for selecting more images to add to a gallery.
    13761393 *
    13771394 * @memberOf wp.media.controller
     
    13811398 * @augments wp.media.controller.State
    13821399 * @augments Backbone.Model
     1400 *
     1401 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     1402 * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
     1403 * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
     1404 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     1405 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     1406 *                                                                          If one is not supplied, a collection of all images will be created.
     1407 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     1408 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     1409 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     1410 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     1411 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     1412 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     1413 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     1414 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     1415 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1416 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     1417 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     1418 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     1419 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     1420 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    13831421 */
    1384 var Library = wp.media.controller.Library,
    1385     MediaLibrary;
    1386 
    1387 MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{
     1422GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{
    13881423    defaults: _.defaults({
    1389         // Attachments browser defaults. @see media.view.AttachmentsBrowser
    1390         filterable:      'uploaded',
    1391 
    1392         displaySettings: false,
    1393         priority:        80,
    1394         syncSelection:   false
     1424        id:            'gallery-library',
     1425        title:         l10n.addToGalleryTitle,
     1426        multiple:      'add',
     1427        filterable:    'uploaded',
     1428        menu:          'gallery',
     1429        toolbar:       'gallery-add',
     1430        priority:      100,
     1431        syncSelection: false
    13951432    }, Library.prototype.defaults ),
    13961433
    13971434    /**
    1398      * @since 3.9.0
    1399      *
    1400      * @param options
    1401      */
    1402     initialize: function( options ) {
    1403         this.media = options.media;
    1404         this.type = options.type;
    1405         this.set( 'library', wp.media.query({ type: this.type }) );
     1435     * @since 3.5.0
     1436     */
     1437    initialize: function() {
     1438        // If a library wasn't supplied, create a library of images.
     1439        if ( ! this.get('library') ) {
     1440            this.set( 'library', wp.media.query({ type: 'image' }) );
     1441        }
    14061442
    14071443        Library.prototype.initialize.apply( this, arguments );
     
    14091445
    14101446    /**
    1411      * @since 3.9.0
     1447     * @since 3.5.0
    14121448     */
    14131449    activate: function() {
    1414         // @todo this should use this.frame.
    1415         if ( wp.media.frame.lastMime ) {
    1416             this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
    1417             delete wp.media.frame.lastMime;
    1418         }
     1450        var library = this.get('library'),
     1451            edit    = this.frame.state('gallery-edit').get('library');
     1452
     1453        if ( this.editLibrary && this.editLibrary !== edit ) {
     1454            library.unobserve( this.editLibrary );
     1455        }
     1456
     1457        // Accepts attachments that exist in the original library and
     1458        // that do not exist in gallery's library.
     1459        library.validator = function( attachment ) {
     1460            return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     1461        };
     1462
     1463        // Reset the library to ensure that all attachments are re-added
     1464        // to the collection. Do so silently, as calling `observe` will
     1465        // trigger the `reset` event.
     1466        library.reset( library.mirroring.models, { silent: true });
     1467        library.observe( edit );
     1468        this.editLibrary = edit;
     1469
    14191470        Library.prototype.activate.apply( this, arguments );
    14201471    }
    14211472});
    14221473
    1423 module.exports = MediaLibrary;
    1424 
    1425 },{}],13:[function(require,module,exports){
     1474module.exports = GalleryAdd;
     1475
     1476
     1477/***/ }),
     1478/* 35 */
     1479/***/ (function(module, exports) {
     1480
     1481var Library = wp.media.controller.Library,
     1482    l10n = wp.media.view.l10n,
     1483    $ = jQuery,
     1484    CollectionEdit;
     1485
    14261486/**
    1427  * wp.media.controller.Region
    1428  *
    1429  * A region is a persistent application layout area.
    1430  *
    1431  * A region assumes one mode at any time, and can be switched to another.
    1432  *
    1433  * When mode changes, events are triggered on the region's parent view.
    1434  * The parent view will listen to specific events and fill the region with an
    1435  * appropriate view depending on mode. For example, a frame listens for the
    1436  * 'browse' mode t be activated on the 'content' view and then fills the region
    1437  * with an AttachmentsBrowser view.
     1487 * wp.media.controller.CollectionEdit
     1488 *
     1489 * A state for editing a collection, which is used by audio and video playlists,
     1490 * and can be used for other collections.
    14381491 *
    14391492 * @memberOf wp.media.controller
    14401493 *
    14411494 * @class
    1442  *
    1443  * @param {object}        options          Options hash for the region.
    1444  * @param {string}        options.id       Unique identifier for the region.
    1445  * @param {Backbone.View} options.view     A parent view the region exists within.
    1446  * @param {string}        options.selector jQuery selector for the region within the parent view.
     1495 * @augments wp.media.controller.Library
     1496 * @augments wp.media.controller.State
     1497 * @augments Backbone.Model
     1498 *
     1499 * @param {object}                     [attributes]                      The attributes hash passed to the state.
     1500 * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
     1501 * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
     1502 *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
     1503 * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
     1504 * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
     1505 * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
     1506 * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
     1507 * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1508 * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
     1509 * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
     1510 * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
     1511 * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
     1512 * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
     1513 * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
     1514 * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
     1515 * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
     1516 *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
     1517 * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
     1518 * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
     1519 *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     1520 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     1521 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
    14471522 */
    1448 var Region = function( options ) {
    1449     _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
    1450 };
    1451 
    1452 // Use Backbone's self-propagating `extend` inheritance method.
    1453 Region.extend = Backbone.Model.extend;
    1454 
    1455 _.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{
    1456     /**
    1457      * Activate a mode.
     1523CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{
     1524    defaults: {
     1525        multiple:         false,
     1526        sortable:         true,
     1527        date:             false,
     1528        searchable:       false,
     1529        content:          'browse',
     1530        describe:         true,
     1531        dragInfo:         true,
     1532        idealColumnWidth: 170,
     1533        editing:          false,
     1534        priority:         60,
     1535        SettingsView:     false,
     1536        syncSelection:    false
     1537    },
     1538
     1539    /**
     1540     * @since 3.9.0
     1541     */
     1542    initialize: function() {
     1543        var collectionType = this.get('collectionType');
     1544
     1545        if ( 'video' === this.get( 'type' ) ) {
     1546            collectionType = 'video-' + collectionType;
     1547        }
     1548
     1549        this.set( 'id', collectionType + '-edit' );
     1550        this.set( 'toolbar', collectionType + '-edit' );
     1551
     1552        // If we haven't been provided a `library`, create a `Selection`.
     1553        if ( ! this.get('library') ) {
     1554            this.set( 'library', new wp.media.model.Selection() );
     1555        }
     1556        // The single `Attachment` view to be used in the `Attachments` view.
     1557        if ( ! this.get('AttachmentView') ) {
     1558            this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     1559        }
     1560        Library.prototype.initialize.apply( this, arguments );
     1561    },
     1562
     1563    /**
     1564     * @since 3.9.0
     1565     */
     1566    activate: function() {
     1567        var library = this.get('library');
     1568
     1569        // Limit the library to images only.
     1570        library.props.set( 'type', this.get( 'type' ) );
     1571
     1572        // Watch for uploaded attachments.
     1573        this.get('library').observe( wp.Uploader.queue );
     1574
     1575        this.frame.on( 'content:render:browse', this.renderSettings, this );
     1576
     1577        Library.prototype.activate.apply( this, arguments );
     1578    },
     1579
     1580    /**
     1581     * @since 3.9.0
     1582     */
     1583    deactivate: function() {
     1584        // Stop watching for uploaded attachments.
     1585        this.get('library').unobserve( wp.Uploader.queue );
     1586
     1587        this.frame.off( 'content:render:browse', this.renderSettings, this );
     1588
     1589        Library.prototype.deactivate.apply( this, arguments );
     1590    },
     1591
     1592    /**
     1593     * Render the collection embed settings view in the browser sidebar.
    14581594     *
    1459      * @since 3.5.0
     1595     * @todo This is against the pattern elsewhere in media. Typically the frame
     1596     *       is responsible for adding region mode callbacks. Explain.
    14601597     *
    1461      * @param {string} mode
     1598     * @since 3.9.0
    14621599     *
    1463      * @fires Region#activate
    1464      * @fires Region#deactivate
    1465      *
    1466      * @returns {wp.media.controller.Region} Returns itself to allow chaining.
    1467      */
    1468     mode: function( mode ) {
    1469         if ( ! mode ) {
    1470             return this._mode;
    1471         }
    1472         // Bail if we're trying to change to the current mode.
    1473         if ( mode === this._mode ) {
    1474             return this;
    1475         }
    1476 
    1477         /**
    1478          * Region mode deactivation event.
    1479          *
    1480          * @event wp.media.controller.Region#deactivate
    1481          */
    1482         this.trigger('deactivate');
    1483 
    1484         this._mode = mode;
    1485         this.render( mode );
    1486 
    1487         /**
    1488          * Region mode activation event.
    1489          *
    1490          * @event wp.media.controller.Region#activate
    1491          */
    1492         this.trigger('activate');
    1493         return this;
    1494     },
    1495     /**
    1496      * Render a mode.
    1497      *
    1498      * @since 3.5.0
    1499      *
    1500      * @param {string} mode
    1501      *
    1502      * @fires Region#create
    1503      * @fires Region#render
    1504      *
    1505      * @returns {wp.media.controller.Region} Returns itself to allow chaining
    1506      */
    1507     render: function( mode ) {
    1508         // If the mode isn't active, activate it.
    1509         if ( mode && mode !== this._mode ) {
    1510             return this.mode( mode );
    1511         }
    1512 
    1513         var set = { view: null },
    1514             view;
    1515 
    1516         /**
    1517          * Create region view event.
    1518          *
    1519          * Region view creation takes place in an event callback on the frame.
    1520          *
    1521          * @event wp.media.controller.Region#create
    1522          * @type {object}
    1523          * @property {object} view
    1524          */
    1525         this.trigger( 'create', set );
    1526         view = set.view;
    1527 
    1528         /**
    1529          * Render region view event.
    1530          *
    1531          * Region view creation takes place in an event callback on the frame.
    1532          *
    1533          * @event wp.media.controller.Region#render
    1534          * @type {object}
    1535          */
    1536         this.trigger( 'render', view );
    1537         if ( view ) {
    1538             this.set( view );
    1539         }
    1540         return this;
    1541     },
    1542 
    1543     /**
    1544      * Get the region's view.
    1545      *
    1546      * @since 3.5.0
    1547      *
    1548      * @returns {wp.media.View}
    1549      */
    1550     get: function() {
    1551         return this.view.views.first( this.selector );
    1552     },
    1553 
    1554     /**
    1555      * Set the region's view as a subview of the frame.
    1556      *
    1557      * @since 3.5.0
    1558      *
    1559      * @param {Array|Object} views
    1560      * @param {Object} [options={}]
    1561      * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
    1562      */
    1563     set: function( views, options ) {
    1564         if ( options ) {
    1565             options.add = false;
    1566         }
    1567         return this.view.views.set( this.selector, views, options );
    1568     },
    1569 
    1570     /**
    1571      * Trigger regional view events on the frame.
    1572      *
    1573      * @since 3.5.0
    1574      *
    1575      * @param {string} event
    1576      * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
    1577      */
    1578     trigger: function( event ) {
    1579         var base, args;
    1580 
    1581         if ( ! this._mode ) {
     1600     * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
     1601     */
     1602    renderSettings: function( attachmentsBrowserView ) {
     1603        var library = this.get('library'),
     1604            collectionType = this.get('collectionType'),
     1605            dragInfoText = this.get('dragInfoText'),
     1606            SettingsView = this.get('SettingsView'),
     1607            obj = {};
     1608
     1609        if ( ! library || ! attachmentsBrowserView ) {
    15821610            return;
    15831611        }
    15841612
    1585         args = _.toArray( arguments );
    1586         base = this.id + ':' + event;
    1587 
    1588         // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
    1589         args[0] = base + ':' + this._mode;
    1590         this.view.trigger.apply( this.view, args );
    1591 
    1592         // Trigger `{this.id}:{event}` event on the frame.
    1593         args[0] = base;
    1594         this.view.trigger.apply( this.view, args );
    1595         return this;
     1613        library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
     1614
     1615        obj[ collectionType ] = new SettingsView({
     1616            controller: this,
     1617            model:      library[ collectionType ],
     1618            priority:   40
     1619        });
     1620
     1621        attachmentsBrowserView.sidebar.set( obj );
     1622
     1623        if ( dragInfoText ) {
     1624            attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
     1625                el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
     1626                priority: -40
     1627            }) );
     1628        }
     1629
     1630        // Add the 'Reverse order' button to the toolbar.
     1631        attachmentsBrowserView.toolbar.set( 'reverse', {
     1632            text:     l10n.reverseOrder,
     1633            priority: 80,
     1634
     1635            click: function() {
     1636                library.reset( library.toArray().reverse() );
     1637            }
     1638        });
    15961639    }
    15971640});
    15981641
    1599 module.exports = Region;
    1600 
    1601 },{}],14:[function(require,module,exports){
     1642module.exports = CollectionEdit;
     1643
     1644
     1645/***/ }),
     1646/* 36 */
     1647/***/ (function(module, exports) {
     1648
     1649var Selection = wp.media.model.Selection,
     1650    Library = wp.media.controller.Library,
     1651    CollectionAdd;
     1652
     1653/**
     1654 * wp.media.controller.CollectionAdd
     1655 *
     1656 * A state for adding attachments to a collection (e.g. video playlist).
     1657 *
     1658 * @memberOf wp.media.controller
     1659 *
     1660 * @class
     1661 * @augments wp.media.controller.Library
     1662 * @augments wp.media.controller.State
     1663 * @augments Backbone.Model
     1664 *
     1665 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     1666 * @param {string}                     [attributes.id=library]      Unique identifier.
     1667 * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
     1668 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     1669 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     1670 *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
     1671 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     1672 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     1673 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     1674 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     1675 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     1676 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     1677 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     1678 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     1679 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1680 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     1681 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     1682 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     1683 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     1684 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     1685 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     1686 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     1687 */
     1688CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{
     1689    defaults: _.defaults( {
     1690        // Selection defaults. @see media.model.Selection
     1691        multiple:      'add',
     1692        // Attachments browser defaults. @see media.view.AttachmentsBrowser
     1693        filterable:    'uploaded',
     1694
     1695        priority:      100,
     1696        syncSelection: false
     1697    }, Library.prototype.defaults ),
     1698
     1699    /**
     1700     * @since 3.9.0
     1701     */
     1702    initialize: function() {
     1703        var collectionType = this.get('collectionType');
     1704
     1705        if ( 'video' === this.get( 'type' ) ) {
     1706            collectionType = 'video-' + collectionType;
     1707        }
     1708
     1709        this.set( 'id', collectionType + '-library' );
     1710        this.set( 'toolbar', collectionType + '-add' );
     1711        this.set( 'menu', collectionType );
     1712
     1713        // If we haven't been provided a `library`, create a `Selection`.
     1714        if ( ! this.get('library') ) {
     1715            this.set( 'library', wp.media.query({ type: this.get('type') }) );
     1716        }
     1717        Library.prototype.initialize.apply( this, arguments );
     1718    },
     1719
     1720    /**
     1721     * @since 3.9.0
     1722     */
     1723    activate: function() {
     1724        var library = this.get('library'),
     1725            editLibrary = this.get('editLibrary'),
     1726            edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
     1727
     1728        if ( editLibrary && editLibrary !== edit ) {
     1729            library.unobserve( editLibrary );
     1730        }
     1731
     1732        // Accepts attachments that exist in the original library and
     1733        // that do not exist in gallery's library.
     1734        library.validator = function( attachment ) {
     1735            return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     1736        };
     1737
     1738        // Reset the library to ensure that all attachments are re-added
     1739        // to the collection. Do so silently, as calling `observe` will
     1740        // trigger the `reset` event.
     1741        library.reset( library.mirroring.models, { silent: true });
     1742        library.observe( edit );
     1743        this.set('editLibrary', edit);
     1744
     1745        Library.prototype.activate.apply( this, arguments );
     1746    }
     1747});
     1748
     1749module.exports = CollectionAdd;
     1750
     1751
     1752/***/ }),
     1753/* 37 */
     1754/***/ (function(module, exports) {
     1755
     1756var Attachment = wp.media.model.Attachment,
     1757    Library = wp.media.controller.Library,
     1758    l10n = wp.media.view.l10n,
     1759    FeaturedImage;
     1760
     1761/**
     1762 * wp.media.controller.FeaturedImage
     1763 *
     1764 * A state for selecting a featured image for a post.
     1765 *
     1766 * @memberOf wp.media.controller
     1767 *
     1768 * @class
     1769 * @augments wp.media.controller.Library
     1770 * @augments wp.media.controller.State
     1771 * @augments Backbone.Model
     1772 *
     1773 * @param {object}                     [attributes]                          The attributes hash passed to the state.
     1774 * @param {string}                     [attributes.id=featured-image]        Unique identifier.
     1775 * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
     1776 * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
     1777 *                                                                           If one is not supplied, a collection of all images will be created.
     1778 * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
     1779 * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
     1780 *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
     1781 * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
     1782 * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
     1783 * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
     1784 * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
     1785 * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
     1786 * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
     1787 *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
     1788 * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1789 * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
     1790 * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     1791 * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
     1792 * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
     1793 */
     1794FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{
     1795    defaults: _.defaults({
     1796        id:            'featured-image',
     1797        title:         l10n.setFeaturedImageTitle,
     1798        multiple:      false,
     1799        filterable:    'uploaded',
     1800        toolbar:       'featured-image',
     1801        priority:      60,
     1802        syncSelection: true
     1803    }, Library.prototype.defaults ),
     1804
     1805    /**
     1806     * @since 3.5.0
     1807     */
     1808    initialize: function() {
     1809        var library, comparator;
     1810
     1811        // If we haven't been provided a `library`, create a `Selection`.
     1812        if ( ! this.get('library') ) {
     1813            this.set( 'library', wp.media.query({ type: 'image' }) );
     1814        }
     1815
     1816        Library.prototype.initialize.apply( this, arguments );
     1817
     1818        library    = this.get('library');
     1819        comparator = library.comparator;
     1820
     1821        // Overload the library's comparator to push items that are not in
     1822        // the mirrored query to the front of the aggregate collection.
     1823        library.comparator = function( a, b ) {
     1824            var aInQuery = !! this.mirroring.get( a.cid ),
     1825                bInQuery = !! this.mirroring.get( b.cid );
     1826
     1827            if ( ! aInQuery && bInQuery ) {
     1828                return -1;
     1829            } else if ( aInQuery && ! bInQuery ) {
     1830                return 1;
     1831            } else {
     1832                return comparator.apply( this, arguments );
     1833            }
     1834        };
     1835
     1836        // Add all items in the selection to the library, so any featured
     1837        // images that are not initially loaded still appear.
     1838        library.observe( this.get('selection') );
     1839    },
     1840
     1841    /**
     1842     * @since 3.5.0
     1843     */
     1844    activate: function() {
     1845        this.updateSelection();
     1846        this.frame.on( 'open', this.updateSelection, this );
     1847
     1848        Library.prototype.activate.apply( this, arguments );
     1849    },
     1850
     1851    /**
     1852     * @since 3.5.0
     1853     */
     1854    deactivate: function() {
     1855        this.frame.off( 'open', this.updateSelection, this );
     1856
     1857        Library.prototype.deactivate.apply( this, arguments );
     1858    },
     1859
     1860    /**
     1861     * @since 3.5.0
     1862     */
     1863    updateSelection: function() {
     1864        var selection = this.get('selection'),
     1865            id = wp.media.view.settings.post.featuredImageId,
     1866            attachment;
     1867
     1868        if ( '' !== id && -1 !== id ) {
     1869            attachment = Attachment.get( id );
     1870            attachment.fetch();
     1871        }
     1872
     1873        selection.reset( attachment ? [ attachment ] : [] );
     1874    }
     1875});
     1876
     1877module.exports = FeaturedImage;
     1878
     1879
     1880/***/ }),
     1881/* 38 */
     1882/***/ (function(module, exports) {
     1883
    16021884var Library = wp.media.controller.Library,
    16031885    l10n = wp.media.view.l10n,
     
    17091991module.exports = ReplaceImage;
    17101992
    1711 },{}],15:[function(require,module,exports){
     1993
     1994/***/ }),
     1995/* 39 */
     1996/***/ (function(module, exports) {
     1997
     1998var l10n = wp.media.view.l10n,
     1999    EditImage;
     2000
     2001/**
     2002 * wp.media.controller.EditImage
     2003 *
     2004 * A state for editing (cropping, etc.) an image.
     2005 *
     2006 * @memberOf wp.media.controller
     2007 *
     2008 * @class
     2009 * @augments wp.media.controller.State
     2010 * @augments Backbone.Model
     2011 *
     2012 * @param {object}                    attributes                      The attributes hash passed to the state.
     2013 * @param {wp.media.model.Attachment} attributes.model                The attachment.
     2014 * @param {string}                    [attributes.id=edit-image]      Unique identifier.
     2015 * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
     2016 * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
     2017 * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
     2018 * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
     2019 * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
     2020 */
     2021EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{
     2022    defaults: {
     2023        id:      'edit-image',
     2024        title:   l10n.editImage,
     2025        menu:    false,
     2026        toolbar: 'edit-image',
     2027        content: 'edit-image',
     2028        url:     ''
     2029    },
     2030
     2031    /**
     2032     * @since 3.9.0
     2033     */
     2034    activate: function() {
     2035        this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
     2036    },
     2037
     2038    /**
     2039     * @since 3.9.0
     2040     */
     2041    deactivate: function() {
     2042        this.frame.off( 'toolbar:render:edit-image' );
     2043    },
     2044
     2045    /**
     2046     * @since 3.9.0
     2047     */
     2048    toolbar: function() {
     2049        var frame = this.frame,
     2050            lastState = frame.lastState(),
     2051            previous = lastState && lastState.id;
     2052
     2053        frame.toolbar.set( new wp.media.view.Toolbar({
     2054            controller: frame,
     2055            items: {
     2056                back: {
     2057                    style: 'primary',
     2058                    text:     l10n.back,
     2059                    priority: 20,
     2060                    click:    function() {
     2061                        if ( previous ) {
     2062                            frame.setState( previous );
     2063                        } else {
     2064                            frame.close();
     2065                        }
     2066                    }
     2067                }
     2068            }
     2069        }) );
     2070    }
     2071});
     2072
     2073module.exports = EditImage;
     2074
     2075
     2076/***/ }),
     2077/* 40 */
     2078/***/ (function(module, exports) {
     2079
     2080/**
     2081 * wp.media.controller.MediaLibrary
     2082 *
     2083 * @memberOf wp.media.controller
     2084 *
     2085 * @class
     2086 * @augments wp.media.controller.Library
     2087 * @augments wp.media.controller.State
     2088 * @augments Backbone.Model
     2089 */
     2090var Library = wp.media.controller.Library,
     2091    MediaLibrary;
     2092
     2093MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{
     2094    defaults: _.defaults({
     2095        // Attachments browser defaults. @see media.view.AttachmentsBrowser
     2096        filterable:      'uploaded',
     2097
     2098        displaySettings: false,
     2099        priority:        80,
     2100        syncSelection:   false
     2101    }, Library.prototype.defaults ),
     2102
     2103    /**
     2104     * @since 3.9.0
     2105     *
     2106     * @param options
     2107     */
     2108    initialize: function( options ) {
     2109        this.media = options.media;
     2110        this.type = options.type;
     2111        this.set( 'library', wp.media.query({ type: this.type }) );
     2112
     2113        Library.prototype.initialize.apply( this, arguments );
     2114    },
     2115
     2116    /**
     2117     * @since 3.9.0
     2118     */
     2119    activate: function() {
     2120        // @todo this should use this.frame.
     2121        if ( wp.media.frame.lastMime ) {
     2122            this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
     2123            delete wp.media.frame.lastMime;
     2124        }
     2125        Library.prototype.activate.apply( this, arguments );
     2126    }
     2127});
     2128
     2129module.exports = MediaLibrary;
     2130
     2131
     2132/***/ }),
     2133/* 41 */
     2134/***/ (function(module, exports) {
     2135
     2136var l10n = wp.media.view.l10n,
     2137    $ = Backbone.$,
     2138    Embed;
     2139
     2140/**
     2141 * wp.media.controller.Embed
     2142 *
     2143 * A state for embedding media from a URL.
     2144 *
     2145 * @memberOf wp.media.controller
     2146 *
     2147 * @class
     2148 * @augments wp.media.controller.State
     2149 * @augments Backbone.Model
     2150 *
     2151 * @param {object} attributes                         The attributes hash passed to the state.
     2152 * @param {string} [attributes.id=embed]              Unique identifier.
     2153 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
     2154 * @param {string} [attributes.content=embed]         Initial mode for the content region.
     2155 * @param {string} [attributes.menu=default]          Initial mode for the menu region.
     2156 * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
     2157 * @param {string} [attributes.menu=false]            Initial mode for the menu region.
     2158 * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
     2159 * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
     2160 * @param {string} [attributes.url]                   The embed URL.
     2161 * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
     2162 */
     2163Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{
     2164    defaults: {
     2165        id:       'embed',
     2166        title:    l10n.insertFromUrlTitle,
     2167        content:  'embed',
     2168        menu:     'default',
     2169        toolbar:  'main-embed',
     2170        priority: 120,
     2171        type:     'link',
     2172        url:      '',
     2173        metadata: {}
     2174    },
     2175
     2176    // The amount of time used when debouncing the scan.
     2177    sensitivity: 400,
     2178
     2179    initialize: function(options) {
     2180        this.metadata = options.metadata;
     2181        this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
     2182        this.props = new Backbone.Model( this.metadata || { url: '' });
     2183        this.props.on( 'change:url', this.debouncedScan, this );
     2184        this.props.on( 'change:url', this.refresh, this );
     2185        this.on( 'scan', this.scanImage, this );
     2186    },
     2187
     2188    /**
     2189     * Trigger a scan of the embedded URL's content for metadata required to embed.
     2190     *
     2191     * @fires wp.media.controller.Embed#scan
     2192     */
     2193    scan: function() {
     2194        var scanners,
     2195            embed = this,
     2196            attributes = {
     2197                type: 'link',
     2198                scanners: []
     2199            };
     2200
     2201        // Scan is triggered with the list of `attributes` to set on the
     2202        // state, useful for the 'type' attribute and 'scanners' attribute,
     2203        // an array of promise objects for asynchronous scan operations.
     2204        if ( this.props.get('url') ) {
     2205            this.trigger( 'scan', attributes );
     2206        }
     2207
     2208        if ( attributes.scanners.length ) {
     2209            scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
     2210            scanners.always( function() {
     2211                if ( embed.get('scanners') === scanners ) {
     2212                    embed.set( 'loading', false );
     2213                }
     2214            });
     2215        } else {
     2216            attributes.scanners = null;
     2217        }
     2218
     2219        attributes.loading = !! attributes.scanners;
     2220        this.set( attributes );
     2221    },
     2222    /**
     2223     * Try scanning the embed as an image to discover its dimensions.
     2224     *
     2225     * @param {Object} attributes
     2226     */
     2227    scanImage: function( attributes ) {
     2228        var frame = this.frame,
     2229            state = this,
     2230            url = this.props.get('url'),
     2231            image = new Image(),
     2232            deferred = $.Deferred();
     2233
     2234        attributes.scanners.push( deferred.promise() );
     2235
     2236        // Try to load the image and find its width/height.
     2237        image.onload = function() {
     2238            deferred.resolve();
     2239
     2240            if ( state !== frame.state() || url !== state.props.get('url') ) {
     2241                return;
     2242            }
     2243
     2244            state.set({
     2245                type: 'image'
     2246            });
     2247
     2248            state.props.set({
     2249                width:  image.width,
     2250                height: image.height
     2251            });
     2252        };
     2253
     2254        image.onerror = deferred.reject;
     2255        image.src = url;
     2256    },
     2257
     2258    refresh: function() {
     2259        this.frame.toolbar.get().refresh();
     2260    },
     2261
     2262    reset: function() {
     2263        this.props.clear().set({ url: '' });
     2264
     2265        if ( this.active ) {
     2266            this.refresh();
     2267        }
     2268    }
     2269});
     2270
     2271module.exports = Embed;
     2272
     2273
     2274/***/ }),
     2275/* 42 */
     2276/***/ (function(module, exports) {
     2277
     2278var l10n = wp.media.view.l10n,
     2279    Cropper;
     2280
     2281/**
     2282 * wp.media.controller.Cropper
     2283 *
     2284 * A state for cropping an image.
     2285 *
     2286 * @memberOf wp.media.controller
     2287 *
     2288 * @class
     2289 * @augments wp.media.controller.State
     2290 * @augments Backbone.Model
     2291 */
     2292Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{
     2293    defaults: {
     2294        id:          'cropper',
     2295        title:       l10n.cropImage,
     2296        // Region mode defaults.
     2297        toolbar:     'crop',
     2298        content:     'crop',
     2299        router:      false,
     2300        canSkipCrop: false,
     2301
     2302        // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state.
     2303        doCropArgs: {}
     2304    },
     2305
     2306    activate: function() {
     2307        this.frame.on( 'content:create:crop', this.createCropContent, this );
     2308        this.frame.on( 'close', this.removeCropper, this );
     2309        this.set('selection', new Backbone.Collection(this.frame._selection.single));
     2310    },
     2311
     2312    deactivate: function() {
     2313        this.frame.toolbar.mode('browse');
     2314    },
     2315
     2316    createCropContent: function() {
     2317        this.cropperView = new wp.media.view.Cropper({
     2318            controller: this,
     2319            attachment: this.get('selection').first()
     2320        });
     2321        this.cropperView.on('image-loaded', this.createCropToolbar, this);
     2322        this.frame.content.set(this.cropperView);
     2323
     2324    },
     2325    removeCropper: function() {
     2326        this.imgSelect.cancelSelection();
     2327        this.imgSelect.setOptions({remove: true});
     2328        this.imgSelect.update();
     2329        this.cropperView.remove();
     2330    },
     2331    createCropToolbar: function() {
     2332        var canSkipCrop, toolbarOptions;
     2333
     2334        canSkipCrop = this.get('canSkipCrop') || false;
     2335
     2336        toolbarOptions = {
     2337            controller: this.frame,
     2338            items: {
     2339                insert: {
     2340                    style:    'primary',
     2341                    text:     l10n.cropImage,
     2342                    priority: 80,
     2343                    requires: { library: false, selection: false },
     2344
     2345                    click: function() {
     2346                        var controller = this.controller,
     2347                            selection;
     2348
     2349                        selection = controller.state().get('selection').first();
     2350                        selection.set({cropDetails: controller.state().imgSelect.getSelection()});
     2351
     2352                        this.$el.text(l10n.cropping);
     2353                        this.$el.attr('disabled', true);
     2354
     2355                        controller.state().doCrop( selection ).done( function( croppedImage ) {
     2356                            controller.trigger('cropped', croppedImage );
     2357                            controller.close();
     2358                        }).fail( function() {
     2359                            controller.trigger('content:error:crop');
     2360                        });
     2361                    }
     2362                }
     2363            }
     2364        };
     2365
     2366        if ( canSkipCrop ) {
     2367            _.extend( toolbarOptions.items, {
     2368                skip: {
     2369                    style:      'secondary',
     2370                    text:       l10n.skipCropping,
     2371                    priority:   70,
     2372                    requires:   { library: false, selection: false },
     2373                    click:      function() {
     2374                        var selection = this.controller.state().get('selection').first();
     2375                        this.controller.state().cropperView.remove();
     2376                        this.controller.trigger('skippedcrop', selection);
     2377                        this.controller.close();
     2378                    }
     2379                }
     2380            });
     2381        }
     2382
     2383        this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
     2384    },
     2385
     2386    doCrop: function( attachment ) {
     2387        return wp.ajax.post( 'custom-header-crop', _.extend(
     2388            {},
     2389            this.defaults.doCropArgs,
     2390            {
     2391                nonce: attachment.get( 'nonces' ).edit,
     2392                id: attachment.get( 'id' ),
     2393                cropDetails: attachment.get( 'cropDetails' )
     2394            }
     2395        ) );
     2396    }
     2397});
     2398
     2399module.exports = Cropper;
     2400
     2401
     2402/***/ }),
     2403/* 43 */
     2404/***/ (function(module, exports) {
     2405
     2406var Controller = wp.media.controller,
     2407    CustomizeImageCropper;
     2408
     2409/**
     2410 * wp.media.controller.CustomizeImageCropper
     2411 *
     2412 * @memberOf wp.media.controller
     2413 *
     2414 * A state for cropping an image.
     2415 *
     2416 * @class
     2417 * @augments wp.media.controller.Cropper
     2418 * @augments wp.media.controller.State
     2419 * @augments Backbone.Model
     2420 */
     2421CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{
     2422    doCrop: function( attachment ) {
     2423        var cropDetails = attachment.get( 'cropDetails' ),
     2424            control = this.get( 'control' ),
     2425            ratio = cropDetails.width / cropDetails.height;
     2426
     2427        // Use crop measurements when flexible in both directions.
     2428        if ( control.params.flex_width && control.params.flex_height ) {
     2429            cropDetails.dst_width  = cropDetails.width;
     2430            cropDetails.dst_height = cropDetails.height;
     2431
     2432        // Constrain flexible side based on image ratio and size of the fixed side.
     2433        } else {
     2434            cropDetails.dst_width  = control.params.flex_width  ? control.params.height * ratio : control.params.width;
     2435            cropDetails.dst_height = control.params.flex_height ? control.params.width  / ratio : control.params.height;
     2436        }
     2437
     2438        return wp.ajax.post( 'crop-image', {
     2439            wp_customize: 'on',
     2440            nonce: attachment.get( 'nonces' ).edit,
     2441            id: attachment.get( 'id' ),
     2442            context: control.id,
     2443            cropDetails: cropDetails
     2444        } );
     2445    }
     2446});
     2447
     2448module.exports = CustomizeImageCropper;
     2449
     2450
     2451/***/ }),
     2452/* 44 */
     2453/***/ (function(module, exports) {
     2454
    17122455var Controller = wp.media.controller,
    17132456    SiteIconCropper;
     
    17602503module.exports = SiteIconCropper;
    17612504
    1762 },{}],16:[function(require,module,exports){
     2505
     2506/***/ }),
     2507/* 45 */
     2508/***/ (function(module, exports) {
     2509
    17632510/**
    1764  * wp.media.controller.StateMachine
    1765  *
    1766  * A state machine keeps track of state. It is in one state at a time,
    1767  * and can change from one state to another.
    1768  *
    1769  * States are stored as models in a Backbone collection.
    1770  *
    1771  * @memberOf wp.media.controller
    1772  *
    1773  * @since 3.5.0
     2511 * wp.media.View
     2512 *
     2513 * The base view class for media.
     2514 *
     2515 * Undelegating events, removing events from the model, and
     2516 * removing events from the controller mirror the code for
     2517 * `Backbone.View.dispose` in Backbone 0.9.8 development.
     2518 *
     2519 * This behavior has since been removed, and should not be used
     2520 * outside of the media manager.
     2521 *
     2522 * @memberOf wp.media
    17742523 *
    17752524 * @class
    1776  * @augments Backbone.Model
    1777  * @mixin
    1778  * @mixes Backbone.Events
    1779  *
    1780  * @param {Array} states
     2525 * @augments wp.Backbone.View
     2526 * @augments Backbone.View
    17812527 */
    1782 var StateMachine = function( states ) {
    1783     // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
    1784     this.states = new Backbone.Collection( states );
    1785 };
    1786 
    1787 // Use Backbone's self-propagating `extend` inheritance method.
    1788 StateMachine.extend = Backbone.Model.extend;
    1789 
    1790 _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{
    1791     /**
    1792      * Fetch a state.
     2528var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{
     2529    constructor: function( options ) {
     2530        if ( options && options.controller ) {
     2531            this.controller = options.controller;
     2532        }
     2533        wp.Backbone.View.apply( this, arguments );
     2534    },
     2535    /**
     2536     * @todo The internal comment mentions this might have been a stop-gap
     2537     *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
     2538     *       care of this in Backbone.View now.
    17932539     *
    1794      * If no `id` is provided, returns the active state.
    1795      *
    1796      * Implicitly creates states.
    1797      *
    1798      * Ensure that the `states` collection exists so the `StateMachine`
    1799      *   can be used as a mixin.
    1800      *
    1801      * @since 3.5.0
    1802      *
    1803      * @param {string} id
    1804      * @returns {wp.media.controller.State} Returns a State model
    1805      *   from the StateMachine collection
    1806      */
    1807     state: function( id ) {
    1808         this.states = this.states || new Backbone.Collection();
    1809 
    1810         // Default to the active state.
    1811         id = id || this._state;
    1812 
    1813         if ( id && ! this.states.get( id ) ) {
    1814             this.states.add({ id: id });
    1815         }
    1816         return this.states.get( id );
    1817     },
    1818 
    1819     /**
    1820      * Sets the active state.
    1821      *
    1822      * Bail if we're trying to select the current state, if we haven't
    1823      * created the `states` collection, or are trying to select a state
    1824      * that does not exist.
    1825      *
    1826      * @since 3.5.0
    1827      *
    1828      * @param {string} id
    1829      *
    1830      * @fires wp.media.controller.State#deactivate
    1831      * @fires wp.media.controller.State#activate
    1832      *
    1833      * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
    1834      */
    1835     setState: function( id ) {
    1836         var previous = this.state();
    1837 
    1838         if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
    1839             return this;
    1840         }
    1841 
    1842         if ( previous ) {
    1843             previous.trigger('deactivate');
    1844             this._lastState = previous.id;
    1845         }
    1846 
    1847         this._state = id;
    1848         this.state().trigger('activate');
     2540     * @returns {wp.media.View} Returns itself to allow chaining
     2541     */
     2542    dispose: function() {
     2543        // Undelegating events, removing events from the model, and
     2544        // removing events from the controller mirror the code for
     2545        // `Backbone.View.dispose` in Backbone 0.9.8 development.
     2546        this.undelegateEvents();
     2547
     2548        if ( this.model && this.model.off ) {
     2549            this.model.off( null, null, this );
     2550        }
     2551
     2552        if ( this.collection && this.collection.off ) {
     2553            this.collection.off( null, null, this );
     2554        }
     2555
     2556        // Unbind controller events.
     2557        if ( this.controller && this.controller.off ) {
     2558            this.controller.off( null, null, this );
     2559        }
    18492560
    18502561        return this;
    18512562    },
    1852 
    1853     /**
    1854      * Returns the previous active state.
    1855      *
    1856      * Call the `state()` method with no parameters to retrieve the current
    1857      * active state.
    1858      *
    1859      * @since 3.5.0
    1860      *
    1861      * @returns {wp.media.controller.State} Returns a State model
    1862      *    from the StateMachine collection
    1863      */
    1864     lastState: function() {
    1865         if ( this._lastState ) {
    1866             return this.state( this._lastState );
    1867         }
     2563    /**
     2564     * @returns {wp.media.View} Returns itself to allow chaining
     2565     */
     2566    remove: function() {
     2567        this.dispose();
     2568        /**
     2569         * call 'remove' directly on the parent class
     2570         */
     2571        return wp.Backbone.View.prototype.remove.apply( this, arguments );
    18682572    }
    18692573});
    18702574
    1871 // Map all event binding and triggering on a StateMachine to its `states` collection.
    1872 _.each([ 'on', 'off', 'trigger' ], function( method ) {
    1873     /**
    1874      * @function on
    1875      * @memberOf wp.media.controller.StateMachine
     2575module.exports = View;
     2576
     2577
     2578/***/ }),
     2579/* 46 */
     2580/***/ (function(module, exports) {
     2581
     2582/**
     2583 * wp.media.view.Frame
     2584 *
     2585 * A frame is a composite view consisting of one or more regions and one or more
     2586 * states.
     2587 *
     2588 * @memberOf wp.media.view
     2589 *
     2590 * @see wp.media.controller.State
     2591 * @see wp.media.controller.Region
     2592 *
     2593 * @class
     2594 * @augments wp.media.View
     2595 * @augments wp.Backbone.View
     2596 * @augments Backbone.View
     2597 * @mixes wp.media.controller.StateMachine
     2598 */
     2599var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{
     2600    initialize: function() {
     2601        _.defaults( this.options, {
     2602            mode: [ 'select' ]
     2603        });
     2604        this._createRegions();
     2605        this._createStates();
     2606        this._createModes();
     2607    },
     2608
     2609    _createRegions: function() {
     2610        // Clone the regions array.
     2611        this.regions = this.regions ? this.regions.slice() : [];
     2612
     2613        // Initialize regions.
     2614        _.each( this.regions, function( region ) {
     2615            this[ region ] = new wp.media.controller.Region({
     2616                view:     this,
     2617                id:       region,
     2618                selector: '.media-frame-' + region
     2619            });
     2620        }, this );
     2621    },
     2622    /**
     2623     * Create the frame's states.
     2624     *
     2625     * @see wp.media.controller.State
     2626     * @see wp.media.controller.StateMachine
     2627     *
     2628     * @fires wp.media.controller.State#ready
     2629     */
     2630    _createStates: function() {
     2631        // Create the default `states` collection.
     2632        this.states = new Backbone.Collection( null, {
     2633            model: wp.media.controller.State
     2634        });
     2635
     2636        // Ensure states have a reference to the frame.
     2637        this.states.on( 'add', function( model ) {
     2638            model.frame = this;
     2639            model.trigger('ready');
     2640        }, this );
     2641
     2642        if ( this.options.states ) {
     2643            this.states.add( this.options.states );
     2644        }
     2645    },
     2646
     2647    /**
     2648     * A frame can be in a mode or multiple modes at one time.
     2649     *
     2650     * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
     2651     */
     2652    _createModes: function() {
     2653        // Store active "modes" that the frame is in. Unrelated to region modes.
     2654        this.activeModes = new Backbone.Collection();
     2655        this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
     2656
     2657        _.each( this.options.mode, function( mode ) {
     2658            this.activateMode( mode );
     2659        }, this );
     2660    },
     2661    /**
     2662     * Reset all states on the frame to their defaults.
     2663     *
     2664     * @returns {wp.media.view.Frame} Returns itself to allow chaining
     2665     */
     2666    reset: function() {
     2667        this.states.invoke( 'trigger', 'reset' );
     2668        return this;
     2669    },
     2670    /**
     2671     * Map activeMode collection events to the frame.
     2672     */
     2673    triggerModeEvents: function( model, collection, options ) {
     2674        var collectionEvent,
     2675            modeEventMap = {
     2676                add: 'activate',
     2677                remove: 'deactivate'
     2678            },
     2679            eventToTrigger;
     2680        // Probably a better way to do this.
     2681        _.each( options, function( value, key ) {
     2682            if ( value ) {
     2683                collectionEvent = key;
     2684            }
     2685        } );
     2686
     2687        if ( ! _.has( modeEventMap, collectionEvent ) ) {
     2688            return;
     2689        }
     2690
     2691        eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
     2692        this.trigger( eventToTrigger );
     2693    },
     2694    /**
     2695     * Activate a mode on the frame.
     2696     *
     2697     * @param string mode Mode ID.
     2698     * @returns {this} Returns itself to allow chaining.
     2699     */
     2700    activateMode: function( mode ) {
     2701        // Bail if the mode is already active.
     2702        if ( this.isModeActive( mode ) ) {
     2703            return;
     2704        }
     2705        this.activeModes.add( [ { id: mode } ] );
     2706        // Add a CSS class to the frame so elements can be styled for the mode.
     2707        this.$el.addClass( 'mode-' + mode );
     2708
     2709        return this;
     2710    },
     2711    /**
     2712     * Deactivate a mode on the frame.
     2713     *
     2714     * @param string mode Mode ID.
     2715     * @returns {this} Returns itself to allow chaining.
     2716     */
     2717    deactivateMode: function( mode ) {
     2718        // Bail if the mode isn't active.
     2719        if ( ! this.isModeActive( mode ) ) {
     2720            return this;
     2721        }
     2722        this.activeModes.remove( this.activeModes.where( { id: mode } ) );
     2723        this.$el.removeClass( 'mode-' + mode );
     2724        /**
     2725         * Frame mode deactivation event.
     2726         *
     2727         * @event wp.media.view.Frame#{mode}:deactivate
     2728         */
     2729        this.trigger( mode + ':deactivate' );
     2730
     2731        return this;
     2732    },
     2733    /**
     2734     * Check if a mode is enabled on the frame.
     2735     *
     2736     * @param  string mode Mode ID.
     2737     * @return bool
     2738     */
     2739    isModeActive: function( mode ) {
     2740        return Boolean( this.activeModes.where( { id: mode } ).length );
     2741    }
     2742});
     2743
     2744// Make the `Frame` a `StateMachine`.
     2745_.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
     2746
     2747module.exports = Frame;
     2748
     2749
     2750/***/ }),
     2751/* 47 */
     2752/***/ (function(module, exports) {
     2753
     2754var Frame = wp.media.view.Frame,
     2755    $ = jQuery,
     2756    MediaFrame;
     2757
     2758/**
     2759 * wp.media.view.MediaFrame
     2760 *
     2761 * The frame used to create the media modal.
     2762 *
     2763 * @memberOf wp.media.view
     2764 *
     2765 * @class
     2766 * @augments wp.media.view.Frame
     2767 * @augments wp.media.View
     2768 * @augments wp.Backbone.View
     2769 * @augments Backbone.View
     2770 * @mixes wp.media.controller.StateMachine
     2771 */
     2772MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{
     2773    className: 'media-frame',
     2774    template:  wp.template('media-frame'),
     2775    regions:   ['menu','title','content','toolbar','router'],
     2776
     2777    events: {
     2778        'click div.media-frame-title h1': 'toggleMenu'
     2779    },
     2780
     2781    /**
     2782     * @constructs
     2783     */
     2784    initialize: function() {
     2785        Frame.prototype.initialize.apply( this, arguments );
     2786
     2787        _.defaults( this.options, {
     2788            title:    '',
     2789            modal:    true,
     2790            uploader: true
     2791        });
     2792
     2793        // Ensure core UI is enabled.
     2794        this.$el.addClass('wp-core-ui');
     2795
     2796        // Initialize modal container view.
     2797        if ( this.options.modal ) {
     2798            this.modal = new wp.media.view.Modal({
     2799                controller: this,
     2800                title:      this.options.title
     2801            });
     2802
     2803            this.modal.content( this );
     2804        }
     2805
     2806        // Force the uploader off if the upload limit has been exceeded or
     2807        // if the browser isn't supported.
     2808        if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     2809            this.options.uploader = false;
     2810        }
     2811
     2812        // Initialize window-wide uploader.
     2813        if ( this.options.uploader ) {
     2814            this.uploader = new wp.media.view.UploaderWindow({
     2815                controller: this,
     2816                uploader: {
     2817                    dropzone:  this.modal ? this.modal.$el : this.$el,
     2818                    container: this.$el
     2819                }
     2820            });
     2821            this.views.set( '.media-frame-uploader', this.uploader );
     2822        }
     2823
     2824        this.on( 'attach', _.bind( this.views.ready, this.views ), this );
     2825
     2826        // Bind default title creation.
     2827        this.on( 'title:create:default', this.createTitle, this );
     2828        this.title.mode('default');
     2829
     2830        this.on( 'title:render', function( view ) {
     2831            view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
     2832        });
     2833
     2834        // Bind default menu.
     2835        this.on( 'menu:create:default', this.createMenu, this );
     2836    },
     2837    /**
     2838     * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     2839     */
     2840    render: function() {
     2841        // Activate the default state if no active state exists.
     2842        if ( ! this.state() && this.options.state ) {
     2843            this.setState( this.options.state );
     2844        }
     2845        /**
     2846         * call 'render' directly on the parent class
     2847         */
     2848        return Frame.prototype.render.apply( this, arguments );
     2849    },
     2850    /**
     2851     * @param {Object} title
     2852     * @this wp.media.controller.Region
     2853     */
     2854    createTitle: function( title ) {
     2855        title.view = new wp.media.View({
     2856            controller: this,
     2857            tagName: 'h1'
     2858        });
     2859    },
     2860    /**
     2861     * @param {Object} menu
     2862     * @this wp.media.controller.Region
     2863     */
     2864    createMenu: function( menu ) {
     2865        menu.view = new wp.media.view.Menu({
     2866            controller: this
     2867        });
     2868    },
     2869
     2870    toggleMenu: function() {
     2871        this.$el.find( '.media-menu' ).toggleClass( 'visible' );
     2872    },
     2873
     2874    /**
     2875     * @param {Object} toolbar
     2876     * @this wp.media.controller.Region
     2877     */
     2878    createToolbar: function( toolbar ) {
     2879        toolbar.view = new wp.media.view.Toolbar({
     2880            controller: this
     2881        });
     2882    },
     2883    /**
     2884     * @param {Object} router
     2885     * @this wp.media.controller.Region
     2886     */
     2887    createRouter: function( router ) {
     2888        router.view = new wp.media.view.Router({
     2889            controller: this
     2890        });
     2891    },
     2892    /**
     2893     * @param {Object} options
     2894     */
     2895    createIframeStates: function( options ) {
     2896        var settings = wp.media.view.settings,
     2897            tabs = settings.tabs,
     2898            tabUrl = settings.tabUrl,
     2899            $postId;
     2900
     2901        if ( ! tabs || ! tabUrl ) {
     2902            return;
     2903        }
     2904
     2905        // Add the post ID to the tab URL if it exists.
     2906        $postId = $('#post_ID');
     2907        if ( $postId.length ) {
     2908            tabUrl += '&post_id=' + $postId.val();
     2909        }
     2910
     2911        // Generate the tab states.
     2912        _.each( tabs, function( title, id ) {
     2913            this.state( 'iframe:' + id ).set( _.defaults({
     2914                tab:     id,
     2915                src:     tabUrl + '&tab=' + id,
     2916                title:   title,
     2917                content: 'iframe',
     2918                menu:    'default'
     2919            }, options ) );
     2920        }, this );
     2921
     2922        this.on( 'content:create:iframe', this.iframeContent, this );
     2923        this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
     2924        this.on( 'menu:render:default', this.iframeMenu, this );
     2925        this.on( 'open', this.hijackThickbox, this );
     2926        this.on( 'close', this.restoreThickbox, this );
     2927    },
     2928
     2929    /**
     2930     * @param {Object} content
     2931     * @this wp.media.controller.Region
     2932     */
     2933    iframeContent: function( content ) {
     2934        this.$el.addClass('hide-toolbar');
     2935        content.view = new wp.media.view.Iframe({
     2936            controller: this
     2937        });
     2938    },
     2939
     2940    iframeContentCleanup: function() {
     2941        this.$el.removeClass('hide-toolbar');
     2942    },
     2943
     2944    iframeMenu: function( view ) {
     2945        var views = {};
     2946
     2947        if ( ! view ) {
     2948            return;
     2949        }
     2950
     2951        _.each( wp.media.view.settings.tabs, function( title, id ) {
     2952            views[ 'iframe:' + id ] = {
     2953                text: this.state( 'iframe:' + id ).get('title'),
     2954                priority: 200
     2955            };
     2956        }, this );
     2957
     2958        view.set( views );
     2959    },
     2960
     2961    hijackThickbox: function() {
     2962        var frame = this;
     2963
     2964        if ( ! window.tb_remove || this._tb_remove ) {
     2965            return;
     2966        }
     2967
     2968        this._tb_remove = window.tb_remove;
     2969        window.tb_remove = function() {
     2970            frame.close();
     2971            frame.reset();
     2972            frame.setState( frame.options.state );
     2973            frame._tb_remove.call( window );
     2974        };
     2975    },
     2976
     2977    restoreThickbox: function() {
     2978        if ( ! this._tb_remove ) {
     2979            return;
     2980        }
     2981
     2982        window.tb_remove = this._tb_remove;
     2983        delete this._tb_remove;
     2984    }
     2985});
     2986
     2987// Map some of the modal's methods to the frame.
     2988_.each(['open','close','attach','detach','escape'], function( method ) {
     2989    /**
     2990     * @function open
     2991     * @memberOf wp.media.view.MediaFrame
    18762992     * @instance
    1877      * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    1878      */
    1879     /**
    1880      * @function off
    1881      * @memberOf wp.media.controller.StateMachine
     2993     *
     2994     * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     2995     */
     2996    /**
     2997     * @function close
     2998     * @memberOf wp.media.view.MediaFrame
    18822999     * @instance
    1883      * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    1884      */
    1885     /**
    1886      * @function trigger
    1887      * @memberOf wp.media.controller.StateMachine
     3000     *
     3001     * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     3002     */
     3003    /**
     3004     * @function attach
     3005     * @memberOf wp.media.view.MediaFrame
    18883006     * @instance
    1889      * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    1890      */
    1891     StateMachine.prototype[ method ] = function() {
    1892         // Ensure that the `states` collection exists so the `StateMachine`
    1893         // can be used as a mixin.
    1894         this.states = this.states || new Backbone.Collection();
    1895         // Forward the method to the `states` collection.
    1896         this.states[ method ].apply( this.states, arguments );
     3007     *
     3008     * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     3009     */
     3010    /**
     3011     * @function detach
     3012     * @memberOf wp.media.view.MediaFrame
     3013     * @instance
     3014     *
     3015     * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     3016     */
     3017    /**
     3018     * @function escape
     3019     * @memberOf wp.media.view.MediaFrame
     3020     * @instance
     3021     *
     3022     * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     3023     */
     3024    MediaFrame.prototype[ method ] = function() {
     3025        if ( this.modal ) {
     3026            this.modal[ method ].apply( this.modal, arguments );
     3027        }
    18973028        return this;
    18983029    };
    18993030});
    19003031
    1901 module.exports = StateMachine;
    1902 
    1903 },{}],17:[function(require,module,exports){
     3032module.exports = MediaFrame;
     3033
     3034
     3035/***/ }),
     3036/* 48 */
     3037/***/ (function(module, exports) {
     3038
     3039var MediaFrame = wp.media.view.MediaFrame,
     3040    l10n = wp.media.view.l10n,
     3041    Select;
     3042
    19043043/**
    1905  * wp.media.controller.State
    1906  *
    1907  * A state is a step in a workflow that when set will trigger the controllers
    1908  * for the regions to be updated as specified in the frame.
    1909  *
    1910  * A state has an event-driven lifecycle:
    1911  *
    1912  *     'ready'      triggers when a state is added to a state machine's collection.
    1913  *     'activate'   triggers when a state is activated by a state machine.
    1914  *     'deactivate' triggers when a state is deactivated by a state machine.
    1915  *     'reset'      is not triggered automatically. It should be invoked by the
    1916  *                  proper controller to reset the state to its default.
    1917  *
    1918  * @memberOf wp.media.controller
     3044 * wp.media.view.MediaFrame.Select
     3045 *
     3046 * A frame for selecting an item or items from the media library.
     3047 *
     3048 * @memberOf wp.media.view.MediaFrame
    19193049 *
    19203050 * @class
    1921  * @augments Backbone.Model
     3051 * @augments wp.media.view.MediaFrame
     3052 * @augments wp.media.view.Frame
     3053 * @augments wp.media.View
     3054 * @augments wp.Backbone.View
     3055 * @augments Backbone.View
     3056 * @mixes wp.media.controller.StateMachine
    19223057 */
    1923 var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{
    1924     /**
    1925      * Constructor.
     3058Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{
     3059    initialize: function() {
     3060        // Call 'initialize' directly on the parent class.
     3061        MediaFrame.prototype.initialize.apply( this, arguments );
     3062
     3063        _.defaults( this.options, {
     3064            selection: [],
     3065            library:   {},
     3066            multiple:  false,
     3067            state:    'library'
     3068        });
     3069
     3070        this.createSelection();
     3071        this.createStates();
     3072        this.bindHandlers();
     3073    },
     3074
     3075    /**
     3076     * Attach a selection collection to the frame.
    19263077     *
    1927      * @since 3.5.0
    1928      */
    1929     constructor: function() {
    1930         this.on( 'activate', this._preActivate, this );
    1931         this.on( 'activate', this.activate, this );
    1932         this.on( 'activate', this._postActivate, this );
    1933         this.on( 'deactivate', this._deactivate, this );
    1934         this.on( 'deactivate', this.deactivate, this );
    1935         this.on( 'reset', this.reset, this );
    1936         this.on( 'ready', this._ready, this );
    1937         this.on( 'ready', this.ready, this );
    1938         /**
    1939          * Call parent constructor with passed arguments
    1940          */
    1941         Backbone.Model.apply( this, arguments );
    1942         this.on( 'change:menu', this._updateMenu, this );
    1943     },
    1944     /**
    1945      * Ready event callback.
     3078     * A selection is a collection of attachments used for a specific purpose
     3079     * by a media frame. e.g. Selecting an attachment (or many) to insert into
     3080     * post content.
    19463081     *
    1947      * @abstract
    1948      * @since 3.5.0
    1949      */
    1950     ready: function() {},
    1951 
    1952     /**
    1953      * Activate event callback.
     3082     * @see media.model.Selection
     3083     */
     3084    createSelection: function() {
     3085        var selection = this.options.selection;
     3086
     3087        if ( ! (selection instanceof wp.media.model.Selection) ) {
     3088            this.options.selection = new wp.media.model.Selection( selection, {
     3089                multiple: this.options.multiple
     3090            });
     3091        }
     3092
     3093        this._selection = {
     3094            attachments: new wp.media.model.Attachments(),
     3095            difference: []
     3096        };
     3097    },
     3098
     3099    /**
     3100     * Create the default states on the frame.
     3101     */
     3102    createStates: function() {
     3103        var options = this.options;
     3104
     3105        if ( this.options.states ) {
     3106            return;
     3107        }
     3108
     3109        // Add the default states.
     3110        this.states.add([
     3111            // Main states.
     3112            new wp.media.controller.Library({
     3113                library:   wp.media.query( options.library ),
     3114                multiple:  options.multiple,
     3115                title:     options.title,
     3116                priority:  20
     3117            })
     3118        ]);
     3119    },
     3120
     3121    /**
     3122     * Bind region mode event callbacks.
    19543123     *
    1955      * @abstract
    1956      * @since 3.5.0
    1957      */
    1958     activate: function() {},
    1959 
    1960     /**
    1961      * Deactivate event callback.
     3124     * @see media.controller.Region.render
     3125     */
     3126    bindHandlers: function() {
     3127        this.on( 'router:create:browse', this.createRouter, this );
     3128        this.on( 'router:render:browse', this.browseRouter, this );
     3129        this.on( 'content:create:browse', this.browseContent, this );
     3130        this.on( 'content:render:upload', this.uploadContent, this );
     3131        this.on( 'toolbar:create:select', this.createSelectToolbar, this );
     3132    },
     3133
     3134    /**
     3135     * Render callback for the router region in the `browse` mode.
    19623136     *
    1963      * @abstract
    1964      * @since 3.5.0
    1965      */
    1966     deactivate: function() {},
    1967 
    1968     /**
    1969      * Reset event callback.
     3137     * @param {wp.media.view.Router} routerView
     3138     */
     3139    browseRouter: function( routerView ) {
     3140        routerView.set({
     3141            upload: {
     3142                text:     l10n.uploadFilesTitle,
     3143                priority: 20
     3144            },
     3145            browse: {
     3146                text:     l10n.mediaLibraryTitle,
     3147                priority: 40
     3148            }
     3149        });
     3150    },
     3151
     3152    /**
     3153     * Render callback for the content region in the `browse` mode.
    19703154     *
    1971      * @abstract
    1972      * @since 3.5.0
    1973      */
    1974     reset: function() {},
    1975 
    1976     /**
    1977      * @access private
    1978      * @since 3.5.0
    1979      */
    1980     _ready: function() {
    1981         this._updateMenu();
    1982     },
    1983 
    1984     /**
    1985      * @access private
    1986      * @since 3.5.0
    1987     */
    1988     _preActivate: function() {
    1989         this.active = true;
    1990     },
    1991 
    1992     /**
    1993      * @access private
    1994      * @since 3.5.0
    1995      */
    1996     _postActivate: function() {
    1997         this.on( 'change:menu', this._menu, this );
    1998         this.on( 'change:titleMode', this._title, this );
    1999         this.on( 'change:content', this._content, this );
    2000         this.on( 'change:toolbar', this._toolbar, this );
    2001 
    2002         this.frame.on( 'title:render:default', this._renderTitle, this );
    2003 
    2004         this._title();
    2005         this._menu();
    2006         this._toolbar();
    2007         this._content();
    2008         this._router();
    2009     },
    2010 
    2011     /**
    2012      * @access private
    2013      * @since 3.5.0
    2014      */
    2015     _deactivate: function() {
    2016         this.active = false;
    2017 
    2018         this.frame.off( 'title:render:default', this._renderTitle, this );
    2019 
    2020         this.off( 'change:menu', this._menu, this );
    2021         this.off( 'change:titleMode', this._title, this );
    2022         this.off( 'change:content', this._content, this );
    2023         this.off( 'change:toolbar', this._toolbar, this );
    2024     },
    2025 
    2026     /**
    2027      * @access private
    2028      * @since 3.5.0
    2029      */
    2030     _title: function() {
    2031         this.frame.title.render( this.get('titleMode') || 'default' );
    2032     },
    2033 
    2034     /**
    2035      * @access private
    2036      * @since 3.5.0
    2037      */
    2038     _renderTitle: function( view ) {
    2039         view.$el.text( this.get('title') || '' );
    2040     },
    2041 
    2042     /**
    2043      * @access private
    2044      * @since 3.5.0
    2045      */
    2046     _router: function() {
    2047         var router = this.frame.router,
    2048             mode = this.get('router'),
    2049             view;
    2050 
    2051         this.frame.$el.toggleClass( 'hide-router', ! mode );
    2052         if ( ! mode ) {
    2053             return;
    2054         }
    2055 
    2056         this.frame.router.render( mode );
    2057 
    2058         view = router.get();
    2059         if ( view && view.select ) {
    2060             view.select( this.frame.content.mode() );
    2061         }
    2062     },
    2063 
    2064     /**
    2065      * @access private
    2066      * @since 3.5.0
    2067      */
    2068     _menu: function() {
    2069         var menu = this.frame.menu,
    2070             mode = this.get('menu'),
    2071             view;
    2072 
    2073         this.frame.$el.toggleClass( 'hide-menu', ! mode );
    2074         if ( ! mode ) {
    2075             return;
    2076         }
    2077 
    2078         menu.mode( mode );
    2079 
    2080         view = menu.get();
    2081         if ( view && view.select ) {
    2082             view.select( this.id );
    2083         }
    2084     },
    2085 
    2086     /**
    2087      * @access private
    2088      * @since 3.5.0
    2089      */
    2090     _updateMenu: function() {
    2091         var previous = this.previous('menu'),
    2092             menu = this.get('menu');
    2093 
    2094         if ( previous ) {
    2095             this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
    2096         }
    2097 
    2098         if ( menu ) {
    2099             this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
    2100         }
    2101     },
    2102 
    2103     /**
    2104      * Create a view in the media menu for the state.
     3155     * @param {wp.media.controller.Region} contentRegion
     3156     */
     3157    browseContent: function( contentRegion ) {
     3158        var state = this.state();
     3159
     3160        this.$el.removeClass('hide-toolbar');
     3161
     3162        // Browse our library of attachments.
     3163        contentRegion.view = new wp.media.view.AttachmentsBrowser({
     3164            controller: this,
     3165            collection: state.get('library'),
     3166            selection:  state.get('selection'),
     3167            model:      state,
     3168            sortable:   state.get('sortable'),
     3169            search:     state.get('searchable'),
     3170            filters:    state.get('filterable'),
     3171            date:       state.get('date'),
     3172            display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
     3173            dragInfo:   state.get('dragInfo'),
     3174
     3175            idealColumnWidth: state.get('idealColumnWidth'),
     3176            suggestedWidth:   state.get('suggestedWidth'),
     3177            suggestedHeight:  state.get('suggestedHeight'),
     3178
     3179            AttachmentView: state.get('AttachmentView')
     3180        });
     3181    },
     3182
     3183    /**
     3184     * Render callback for the content region in the `upload` mode.
     3185     */
     3186    uploadContent: function() {
     3187        this.$el.removeClass( 'hide-toolbar' );
     3188        this.content.set( new wp.media.view.UploaderInline({
     3189            controller: this
     3190        }) );
     3191    },
     3192
     3193    /**
     3194     * Toolbars
    21053195     *
    2106      * @access private
    2107      * @since 3.5.0
    2108      *
    2109      * @param {media.view.Menu} view The menu view.
    2110      */
    2111     _renderMenu: function( view ) {
    2112         var menuItem = this.get('menuItem'),
    2113             title = this.get('title'),
    2114             priority = this.get('priority');
    2115 
    2116         if ( ! menuItem && title ) {
    2117             menuItem = { text: title };
    2118 
    2119             if ( priority ) {
    2120                 menuItem.priority = priority;
    2121             }
    2122         }
    2123 
    2124         if ( ! menuItem ) {
    2125             return;
    2126         }
    2127 
    2128         view.set( this.id, menuItem );
     3196     * @param {Object} toolbar
     3197     * @param {Object} [options={}]
     3198     * @this wp.media.controller.Region
     3199     */
     3200    createSelectToolbar: function( toolbar, options ) {
     3201        options = options || this.options.button || {};
     3202        options.controller = this;
     3203
     3204        toolbar.view = new wp.media.view.Toolbar.Select( options );
    21293205    }
    21303206});
    21313207
    2132 _.each(['toolbar','content'], function( region ) {
    2133     /**
    2134      * @access private
    2135      */
    2136     State.prototype[ '_' + region ] = function() {
    2137         var mode = this.get( region );
    2138         if ( mode ) {
    2139             this.frame[ region ].render( mode );
    2140         }
    2141     };
     3208module.exports = Select;
     3209
     3210
     3211/***/ }),
     3212/* 49 */
     3213/***/ (function(module, exports) {
     3214
     3215var Select = wp.media.view.MediaFrame.Select,
     3216    Library = wp.media.controller.Library,
     3217    l10n = wp.media.view.l10n,
     3218    Post;
     3219
     3220/**
     3221 * wp.media.view.MediaFrame.Post
     3222 *
     3223 * The frame for manipulating media on the Edit Post page.
     3224 *
     3225 * @memberOf wp.media.view.MediaFrame
     3226 *
     3227 * @class
     3228 * @augments wp.media.view.MediaFrame.Select
     3229 * @augments wp.media.view.MediaFrame
     3230 * @augments wp.media.view.Frame
     3231 * @augments wp.media.View
     3232 * @augments wp.Backbone.View
     3233 * @augments Backbone.View
     3234 * @mixes wp.media.controller.StateMachine
     3235 */
     3236Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{
     3237    initialize: function() {
     3238        this.counts = {
     3239            audio: {
     3240                count: wp.media.view.settings.attachmentCounts.audio,
     3241                state: 'playlist'
     3242            },
     3243            video: {
     3244                count: wp.media.view.settings.attachmentCounts.video,
     3245                state: 'video-playlist'
     3246            }
     3247        };
     3248
     3249        _.defaults( this.options, {
     3250            multiple:  true,
     3251            editing:   false,
     3252            state:    'insert',
     3253            metadata:  {}
     3254        });
     3255
     3256        // Call 'initialize' directly on the parent class.
     3257        Select.prototype.initialize.apply( this, arguments );
     3258        this.createIframeStates();
     3259
     3260    },
     3261
     3262    /**
     3263     * Create the default states.
     3264     */
     3265    createStates: function() {
     3266        var options = this.options;
     3267
     3268        this.states.add([
     3269            // Main states.
     3270            new Library({
     3271                id:         'insert',
     3272                title:      l10n.insertMediaTitle,
     3273                priority:   20,
     3274                toolbar:    'main-insert',
     3275                filterable: 'all',
     3276                library:    wp.media.query( options.library ),
     3277                multiple:   options.multiple ? 'reset' : false,
     3278                editable:   true,
     3279
     3280                // If the user isn't allowed to edit fields,
     3281                // can they still edit it locally?
     3282                allowLocalEdits: true,
     3283
     3284                // Show the attachment display settings.
     3285                displaySettings: true,
     3286                // Update user settings when users adjust the
     3287                // attachment display settings.
     3288                displayUserSettings: true
     3289            }),
     3290
     3291            new Library({
     3292                id:         'gallery',
     3293                title:      l10n.createGalleryTitle,
     3294                priority:   40,
     3295                toolbar:    'main-gallery',
     3296                filterable: 'uploaded',
     3297                multiple:   'add',
     3298                editable:   false,
     3299
     3300                library:  wp.media.query( _.defaults({
     3301                    type: 'image'
     3302                }, options.library ) )
     3303            }),
     3304
     3305            // Embed states.
     3306            new wp.media.controller.Embed( { metadata: options.metadata } ),
     3307
     3308            new wp.media.controller.EditImage( { model: options.editImage } ),
     3309
     3310            // Gallery states.
     3311            new wp.media.controller.GalleryEdit({
     3312                library: options.selection,
     3313                editing: options.editing,
     3314                menu:    'gallery'
     3315            }),
     3316
     3317            new wp.media.controller.GalleryAdd(),
     3318
     3319            new Library({
     3320                id:         'playlist',
     3321                title:      l10n.createPlaylistTitle,
     3322                priority:   60,
     3323                toolbar:    'main-playlist',
     3324                filterable: 'uploaded',
     3325                multiple:   'add',
     3326                editable:   false,
     3327
     3328                library:  wp.media.query( _.defaults({
     3329                    type: 'audio'
     3330                }, options.library ) )
     3331            }),
     3332
     3333            // Playlist states.
     3334            new wp.media.controller.CollectionEdit({
     3335                type: 'audio',
     3336                collectionType: 'playlist',
     3337                title:          l10n.editPlaylistTitle,
     3338                SettingsView:   wp.media.view.Settings.Playlist,
     3339                library:        options.selection,
     3340                editing:        options.editing,
     3341                menu:           'playlist',
     3342                dragInfoText:   l10n.playlistDragInfo,
     3343                dragInfo:       false
     3344            }),
     3345
     3346            new wp.media.controller.CollectionAdd({
     3347                type: 'audio',
     3348                collectionType: 'playlist',
     3349                title: l10n.addToPlaylistTitle
     3350            }),
     3351
     3352            new Library({
     3353                id:         'video-playlist',
     3354                title:      l10n.createVideoPlaylistTitle,
     3355                priority:   60,
     3356                toolbar:    'main-video-playlist',
     3357                filterable: 'uploaded',
     3358                multiple:   'add',
     3359                editable:   false,
     3360
     3361                library:  wp.media.query( _.defaults({
     3362                    type: 'video'
     3363                }, options.library ) )
     3364            }),
     3365
     3366            new wp.media.controller.CollectionEdit({
     3367                type: 'video',
     3368                collectionType: 'playlist',
     3369                title:          l10n.editVideoPlaylistTitle,
     3370                SettingsView:   wp.media.view.Settings.Playlist,
     3371                library:        options.selection,
     3372                editing:        options.editing,
     3373                menu:           'video-playlist',
     3374                dragInfoText:   l10n.videoPlaylistDragInfo,
     3375                dragInfo:       false
     3376            }),
     3377
     3378            new wp.media.controller.CollectionAdd({
     3379                type: 'video',
     3380                collectionType: 'playlist',
     3381                title: l10n.addToVideoPlaylistTitle
     3382            })
     3383        ]);
     3384
     3385        if ( wp.media.view.settings.post.featuredImageId ) {
     3386            this.states.add( new wp.media.controller.FeaturedImage() );
     3387        }
     3388    },
     3389
     3390    bindHandlers: function() {
     3391        var handlers, checkCounts;
     3392
     3393        Select.prototype.bindHandlers.apply( this, arguments );
     3394
     3395        this.on( 'activate', this.activate, this );
     3396
     3397        // Only bother checking media type counts if one of the counts is zero
     3398        checkCounts = _.find( this.counts, function( type ) {
     3399            return type.count === 0;
     3400        } );
     3401
     3402        if ( typeof checkCounts !== 'undefined' ) {
     3403            this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
     3404        }
     3405
     3406        this.on( 'menu:create:gallery', this.createMenu, this );
     3407        this.on( 'menu:create:playlist', this.createMenu, this );
     3408        this.on( 'menu:create:video-playlist', this.createMenu, this );
     3409        this.on( 'toolbar:create:main-insert', this.createToolbar, this );
     3410        this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
     3411        this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
     3412        this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
     3413        this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
     3414        this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
     3415
     3416        handlers = {
     3417            menu: {
     3418                'default': 'mainMenu',
     3419                'gallery': 'galleryMenu',
     3420                'playlist': 'playlistMenu',
     3421                'video-playlist': 'videoPlaylistMenu'
     3422            },
     3423
     3424            content: {
     3425                'embed':          'embedContent',
     3426                'edit-image':     'editImageContent',
     3427                'edit-selection': 'editSelectionContent'
     3428            },
     3429
     3430            toolbar: {
     3431                'main-insert':      'mainInsertToolbar',
     3432                'main-gallery':     'mainGalleryToolbar',
     3433                'gallery-edit':     'galleryEditToolbar',
     3434                'gallery-add':      'galleryAddToolbar',
     3435                'main-playlist':    'mainPlaylistToolbar',
     3436                'playlist-edit':    'playlistEditToolbar',
     3437                'playlist-add':     'playlistAddToolbar',
     3438                'main-video-playlist': 'mainVideoPlaylistToolbar',
     3439                'video-playlist-edit': 'videoPlaylistEditToolbar',
     3440                'video-playlist-add': 'videoPlaylistAddToolbar'
     3441            }
     3442        };
     3443
     3444        _.each( handlers, function( regionHandlers, region ) {
     3445            _.each( regionHandlers, function( callback, handler ) {
     3446                this.on( region + ':render:' + handler, this[ callback ], this );
     3447            }, this );
     3448        }, this );
     3449    },
     3450
     3451    activate: function() {
     3452        // Hide menu items for states tied to particular media types if there are no items
     3453        _.each( this.counts, function( type ) {
     3454            if ( type.count < 1 ) {
     3455                this.menuItemVisibility( type.state, 'hide' );
     3456            }
     3457        }, this );
     3458    },
     3459
     3460    mediaTypeCounts: function( model, attr ) {
     3461        if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
     3462            this.counts[ attr ].count++;
     3463            this.menuItemVisibility( this.counts[ attr ].state, 'show' );
     3464        }
     3465    },
     3466
     3467    // Menus
     3468    /**
     3469     * @param {wp.Backbone.View} view
     3470     */
     3471    mainMenu: function( view ) {
     3472        view.set({
     3473            'library-separator': new wp.media.View({
     3474                className: 'separator',
     3475                priority: 100
     3476            })
     3477        });
     3478    },
     3479
     3480    menuItemVisibility: function( state, visibility ) {
     3481        var menu = this.menu.get();
     3482        if ( visibility === 'hide' ) {
     3483            menu.hide( state );
     3484        } else if ( visibility === 'show' ) {
     3485            menu.show( state );
     3486        }
     3487    },
     3488    /**
     3489     * @param {wp.Backbone.View} view
     3490     */
     3491    galleryMenu: function( view ) {
     3492        var lastState = this.lastState(),
     3493            previous = lastState && lastState.id,
     3494            frame = this;
     3495
     3496        view.set({
     3497            cancel: {
     3498                text:     l10n.cancelGalleryTitle,
     3499                priority: 20,
     3500                click:    function() {
     3501                    if ( previous ) {
     3502                        frame.setState( previous );
     3503                    } else {
     3504                        frame.close();
     3505                    }
     3506
     3507                    // Keep focus inside media modal
     3508                    // after canceling a gallery
     3509                    this.controller.modal.focusManager.focus();
     3510                }
     3511            },
     3512            separateCancel: new wp.media.View({
     3513                className: 'separator',
     3514                priority: 40
     3515            })
     3516        });
     3517    },
     3518
     3519    playlistMenu: function( view ) {
     3520        var lastState = this.lastState(),
     3521            previous = lastState && lastState.id,
     3522            frame = this;
     3523
     3524        view.set({
     3525            cancel: {
     3526                text:     l10n.cancelPlaylistTitle,
     3527                priority: 20,
     3528                click:    function() {
     3529                    if ( previous ) {
     3530                        frame.setState( previous );
     3531                    } else {
     3532                        frame.close();
     3533                    }
     3534                }
     3535            },
     3536            separateCancel: new wp.media.View({
     3537                className: 'separator',
     3538                priority: 40
     3539            })
     3540        });
     3541    },
     3542
     3543    videoPlaylistMenu: function( view ) {
     3544        var lastState = this.lastState(),
     3545            previous = lastState && lastState.id,
     3546            frame = this;
     3547
     3548        view.set({
     3549            cancel: {
     3550                text:     l10n.cancelVideoPlaylistTitle,
     3551                priority: 20,
     3552                click:    function() {
     3553                    if ( previous ) {
     3554                        frame.setState( previous );
     3555                    } else {
     3556                        frame.close();
     3557                    }
     3558                }
     3559            },
     3560            separateCancel: new wp.media.View({
     3561                className: 'separator',
     3562                priority: 40
     3563            })
     3564        });
     3565    },
     3566
     3567    // Content
     3568    embedContent: function() {
     3569        var view = new wp.media.view.Embed({
     3570            controller: this,
     3571            model:      this.state()
     3572        }).render();
     3573
     3574        this.content.set( view );
     3575
     3576        if ( ! wp.media.isTouchDevice ) {
     3577            view.url.focus();
     3578        }
     3579    },
     3580
     3581    editSelectionContent: function() {
     3582        var state = this.state(),
     3583            selection = state.get('selection'),
     3584            view;
     3585
     3586        view = new wp.media.view.AttachmentsBrowser({
     3587            controller: this,
     3588            collection: selection,
     3589            selection:  selection,
     3590            model:      state,
     3591            sortable:   true,
     3592            search:     false,
     3593            date:       false,
     3594            dragInfo:   true,
     3595
     3596            AttachmentView: wp.media.view.Attachments.EditSelection
     3597        }).render();
     3598
     3599        view.toolbar.set( 'backToLibrary', {
     3600            text:     l10n.returnToLibrary,
     3601            priority: -100,
     3602
     3603            click: function() {
     3604                this.controller.content.mode('browse');
     3605            }
     3606        });
     3607
     3608        // Browse our library of attachments.
     3609        this.content.set( view );
     3610
     3611        // Trigger the controller to set focus
     3612        this.trigger( 'edit:selection', this );
     3613    },
     3614
     3615    editImageContent: function() {
     3616        var image = this.state().get('image'),
     3617            view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
     3618
     3619        this.content.set( view );
     3620
     3621        // after creating the wrapper view, load the actual editor via an ajax call
     3622        view.loadEditor();
     3623
     3624    },
     3625
     3626    // Toolbars
     3627
     3628    /**
     3629     * @param {wp.Backbone.View} view
     3630     */
     3631    selectionStatusToolbar: function( view ) {
     3632        var editable = this.state().get('editable');
     3633
     3634        view.set( 'selection', new wp.media.view.Selection({
     3635            controller: this,
     3636            collection: this.state().get('selection'),
     3637            priority:   -40,
     3638
     3639            // If the selection is editable, pass the callback to
     3640            // switch the content mode.
     3641            editable: editable && function() {
     3642                this.controller.content.mode('edit-selection');
     3643            }
     3644        }).render() );
     3645    },
     3646
     3647    /**
     3648     * @param {wp.Backbone.View} view
     3649     */
     3650    mainInsertToolbar: function( view ) {
     3651        var controller = this;
     3652
     3653        this.selectionStatusToolbar( view );
     3654
     3655        view.set( 'insert', {
     3656            style:    'primary',
     3657            priority: 80,
     3658            text:     l10n.insertIntoPost,
     3659            requires: { selection: true },
     3660
     3661            /**
     3662             * @callback
     3663             * @fires wp.media.controller.State#insert
     3664             */
     3665            click: function() {
     3666                var state = controller.state(),
     3667                    selection = state.get('selection');
     3668
     3669                controller.close();
     3670                state.trigger( 'insert', selection ).reset();
     3671            }
     3672        });
     3673    },
     3674
     3675    /**
     3676     * @param {wp.Backbone.View} view
     3677     */
     3678    mainGalleryToolbar: function( view ) {
     3679        var controller = this;
     3680
     3681        this.selectionStatusToolbar( view );
     3682
     3683        view.set( 'gallery', {
     3684            style:    'primary',
     3685            text:     l10n.createNewGallery,
     3686            priority: 60,
     3687            requires: { selection: true },
     3688
     3689            click: function() {
     3690                var selection = controller.state().get('selection'),
     3691                    edit = controller.state('gallery-edit'),
     3692                    models = selection.where({ type: 'image' });
     3693
     3694                edit.set( 'library', new wp.media.model.Selection( models, {
     3695                    props:    selection.props.toJSON(),
     3696                    multiple: true
     3697                }) );
     3698
     3699                this.controller.setState('gallery-edit');
     3700
     3701                // Keep focus inside media modal
     3702                // after jumping to gallery view
     3703                this.controller.modal.focusManager.focus();
     3704            }
     3705        });
     3706    },
     3707
     3708    mainPlaylistToolbar: function( view ) {
     3709        var controller = this;
     3710
     3711        this.selectionStatusToolbar( view );
     3712
     3713        view.set( 'playlist', {
     3714            style:    'primary',
     3715            text:     l10n.createNewPlaylist,
     3716            priority: 100,
     3717            requires: { selection: true },
     3718
     3719            click: function() {
     3720                var selection = controller.state().get('selection'),
     3721                    edit = controller.state('playlist-edit'),
     3722                    models = selection.where({ type: 'audio' });
     3723
     3724                edit.set( 'library', new wp.media.model.Selection( models, {
     3725                    props:    selection.props.toJSON(),
     3726                    multiple: true
     3727                }) );
     3728
     3729                this.controller.setState('playlist-edit');
     3730
     3731                // Keep focus inside media modal
     3732                // after jumping to playlist view
     3733                this.controller.modal.focusManager.focus();
     3734            }
     3735        });
     3736    },
     3737
     3738    mainVideoPlaylistToolbar: function( view ) {
     3739        var controller = this;
     3740
     3741        this.selectionStatusToolbar( view );
     3742
     3743        view.set( 'video-playlist', {
     3744            style:    'primary',
     3745            text:     l10n.createNewVideoPlaylist,
     3746            priority: 100,
     3747            requires: { selection: true },
     3748
     3749            click: function() {
     3750                var selection = controller.state().get('selection'),
     3751                    edit = controller.state('video-playlist-edit'),
     3752                    models = selection.where({ type: 'video' });
     3753
     3754                edit.set( 'library', new wp.media.model.Selection( models, {
     3755                    props:    selection.props.toJSON(),
     3756                    multiple: true
     3757                }) );
     3758
     3759                this.controller.setState('video-playlist-edit');
     3760
     3761                // Keep focus inside media modal
     3762                // after jumping to video playlist view
     3763                this.controller.modal.focusManager.focus();
     3764            }
     3765        });
     3766    },
     3767
     3768    featuredImageToolbar: function( toolbar ) {
     3769        this.createSelectToolbar( toolbar, {
     3770            text:  l10n.setFeaturedImage,
     3771            state: this.options.state
     3772        });
     3773    },
     3774
     3775    mainEmbedToolbar: function( toolbar ) {
     3776        toolbar.view = new wp.media.view.Toolbar.Embed({
     3777            controller: this
     3778        });
     3779    },
     3780
     3781    galleryEditToolbar: function() {
     3782        var editing = this.state().get('editing');
     3783        this.toolbar.set( new wp.media.view.Toolbar({
     3784            controller: this,
     3785            items: {
     3786                insert: {
     3787                    style:    'primary',
     3788                    text:     editing ? l10n.updateGallery : l10n.insertGallery,
     3789                    priority: 80,
     3790                    requires: { library: true },
     3791
     3792                    /**
     3793                     * @fires wp.media.controller.State#update
     3794                     */
     3795                    click: function() {
     3796                        var controller = this.controller,
     3797                            state = controller.state();
     3798
     3799                        controller.close();
     3800                        state.trigger( 'update', state.get('library') );
     3801
     3802                        // Restore and reset the default state.
     3803                        controller.setState( controller.options.state );
     3804                        controller.reset();
     3805                    }
     3806                }
     3807            }
     3808        }) );
     3809    },
     3810
     3811    galleryAddToolbar: function() {
     3812        this.toolbar.set( new wp.media.view.Toolbar({
     3813            controller: this,
     3814            items: {
     3815                insert: {
     3816                    style:    'primary',
     3817                    text:     l10n.addToGallery,
     3818                    priority: 80,
     3819                    requires: { selection: true },
     3820
     3821                    /**
     3822                     * @fires wp.media.controller.State#reset
     3823                     */
     3824                    click: function() {
     3825                        var controller = this.controller,
     3826                            state = controller.state(),
     3827                            edit = controller.state('gallery-edit');
     3828
     3829                        edit.get('library').add( state.get('selection').models );
     3830                        state.trigger('reset');
     3831                        controller.setState('gallery-edit');
     3832                    }
     3833                }
     3834            }
     3835        }) );
     3836    },
     3837
     3838    playlistEditToolbar: function() {
     3839        var editing = this.state().get('editing');
     3840        this.toolbar.set( new wp.media.view.Toolbar({
     3841            controller: this,
     3842            items: {
     3843                insert: {
     3844                    style:    'primary',
     3845                    text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
     3846                    priority: 80,
     3847                    requires: { library: true },
     3848
     3849                    /**
     3850                     * @fires wp.media.controller.State#update
     3851                     */
     3852                    click: function() {
     3853                        var controller = this.controller,
     3854                            state = controller.state();
     3855
     3856                        controller.close();
     3857                        state.trigger( 'update', state.get('library') );
     3858
     3859                        // Restore and reset the default state.
     3860                        controller.setState( controller.options.state );
     3861                        controller.reset();
     3862                    }
     3863                }
     3864            }
     3865        }) );
     3866    },
     3867
     3868    playlistAddToolbar: function() {
     3869        this.toolbar.set( new wp.media.view.Toolbar({
     3870            controller: this,
     3871            items: {
     3872                insert: {
     3873                    style:    'primary',
     3874                    text:     l10n.addToPlaylist,
     3875                    priority: 80,
     3876                    requires: { selection: true },
     3877
     3878                    /**
     3879                     * @fires wp.media.controller.State#reset
     3880                     */
     3881                    click: function() {
     3882                        var controller = this.controller,
     3883                            state = controller.state(),
     3884                            edit = controller.state('playlist-edit');
     3885
     3886                        edit.get('library').add( state.get('selection').models );
     3887                        state.trigger('reset');
     3888                        controller.setState('playlist-edit');
     3889                    }
     3890                }
     3891            }
     3892        }) );
     3893    },
     3894
     3895    videoPlaylistEditToolbar: function() {
     3896        var editing = this.state().get('editing');
     3897        this.toolbar.set( new wp.media.view.Toolbar({
     3898            controller: this,
     3899            items: {
     3900                insert: {
     3901                    style:    'primary',
     3902                    text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
     3903                    priority: 140,
     3904                    requires: { library: true },
     3905
     3906                    click: function() {
     3907                        var controller = this.controller,
     3908                            state = controller.state(),
     3909                            library = state.get('library');
     3910
     3911                        library.type = 'video';
     3912
     3913                        controller.close();
     3914                        state.trigger( 'update', library );
     3915
     3916                        // Restore and reset the default state.
     3917                        controller.setState( controller.options.state );
     3918                        controller.reset();
     3919                    }
     3920                }
     3921            }
     3922        }) );
     3923    },
     3924
     3925    videoPlaylistAddToolbar: function() {
     3926        this.toolbar.set( new wp.media.view.Toolbar({
     3927            controller: this,
     3928            items: {
     3929                insert: {
     3930                    style:    'primary',
     3931                    text:     l10n.addToVideoPlaylist,
     3932                    priority: 140,
     3933                    requires: { selection: true },
     3934
     3935                    click: function() {
     3936                        var controller = this.controller,
     3937                            state = controller.state(),
     3938                            edit = controller.state('video-playlist-edit');
     3939
     3940                        edit.get('library').add( state.get('selection').models );
     3941                        state.trigger('reset');
     3942                        controller.setState('video-playlist-edit');
     3943                    }
     3944                }
     3945            }
     3946        }) );
     3947    }
    21423948});
    21433949
    2144 module.exports = State;
    2145 
    2146 },{}],18:[function(require,module,exports){
     3950module.exports = Post;
     3951
     3952
     3953/***/ }),
     3954/* 50 */
     3955/***/ (function(module, exports) {
     3956
     3957var Select = wp.media.view.MediaFrame.Select,
     3958    l10n = wp.media.view.l10n,
     3959    ImageDetails;
     3960
    21473961/**
    2148  * wp.media.selectionSync
    2149  *
    2150  * Sync an attachments selection in a state with another state.
    2151  *
    2152  * Allows for selecting multiple images in the Add Media workflow, and then
    2153  * switching to the Insert Gallery workflow while preserving the attachments selection.
    2154  *
    2155  * @memberOf wp.media
    2156  *
    2157  * @mixin
     3962 * wp.media.view.MediaFrame.ImageDetails
     3963 *
     3964 * A media frame for manipulating an image that's already been inserted
     3965 * into a post.
     3966 *
     3967 * @memberOf wp.media.view.MediaFrame
     3968 *
     3969 * @class
     3970 * @augments wp.media.view.MediaFrame.Select
     3971 * @augments wp.media.view.MediaFrame
     3972 * @augments wp.media.view.Frame
     3973 * @augments wp.media.View
     3974 * @augments wp.Backbone.View
     3975 * @augments Backbone.View
     3976 * @mixes wp.media.controller.StateMachine
    21583977 */
    2159 var selectionSync = {
    2160     /**
    2161      * @since 3.5.0
    2162      */
    2163     syncSelection: function() {
    2164         var selection = this.get('selection'),
    2165             manager = this.frame._selection;
    2166 
    2167         if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     3978ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{
     3979    defaults: {
     3980        id:      'image',
     3981        url:     '',
     3982        menu:    'image-details',
     3983        content: 'image-details',
     3984        toolbar: 'image-details',
     3985        type:    'link',
     3986        title:    l10n.imageDetailsTitle,
     3987        priority: 120
     3988    },
     3989
     3990    initialize: function( options ) {
     3991        this.image = new wp.media.model.PostImage( options.metadata );
     3992        this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
     3993        Select.prototype.initialize.apply( this, arguments );
     3994    },
     3995
     3996    bindHandlers: function() {
     3997        Select.prototype.bindHandlers.apply( this, arguments );
     3998        this.on( 'menu:create:image-details', this.createMenu, this );
     3999        this.on( 'content:create:image-details', this.imageDetailsContent, this );
     4000        this.on( 'content:render:edit-image', this.editImageContent, this );
     4001        this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
     4002        // override the select toolbar
     4003        this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
     4004    },
     4005
     4006    createStates: function() {
     4007        this.states.add([
     4008            new wp.media.controller.ImageDetails({
     4009                image: this.image,
     4010                editable: false
     4011            }),
     4012            new wp.media.controller.ReplaceImage({
     4013                id: 'replace-image',
     4014                library: wp.media.query( { type: 'image' } ),
     4015                image: this.image,
     4016                multiple:  false,
     4017                title:     l10n.imageReplaceTitle,
     4018                toolbar: 'replace',
     4019                priority:  80,
     4020                displaySettings: true
     4021            }),
     4022            new wp.media.controller.EditImage( {
     4023                image: this.image,
     4024                selection: this.options.selection
     4025            } )
     4026        ]);
     4027    },
     4028
     4029    imageDetailsContent: function( options ) {
     4030        options.view = new wp.media.view.ImageDetails({
     4031            controller: this,
     4032            model: this.state().image,
     4033            attachment: this.state().image.attachment
     4034        });
     4035    },
     4036
     4037    editImageContent: function() {
     4038        var state = this.state(),
     4039            model = state.get('image'),
     4040            view;
     4041
     4042        if ( ! model ) {
    21684043            return;
    21694044        }
    21704045
    2171         // If the selection supports multiple items, validate the stored
    2172         // attachments based on the new selection's conditions. Record
    2173         // the attachments that are not included; we'll maintain a
    2174         // reference to those. Other attachments are considered in flux.
    2175         if ( selection.multiple ) {
    2176             selection.reset( [], { silent: true });
    2177             selection.validateAll( manager.attachments );
    2178             manager.difference = _.difference( manager.attachments.models, selection.models );
    2179         }
    2180 
    2181         // Sync the selection's single item with the master.
    2182         selection.single( manager.single );
    2183     },
    2184 
    2185     /**
    2186      * Record the currently active attachments, which is a combination
    2187      * of the selection's attachments and the set of selected
    2188      * attachments that this specific selection considered invalid.
    2189      * Reset the difference and record the single attachment.
    2190      *
    2191      * @since 3.5.0
    2192      */
    2193     recordSelection: function() {
    2194         var selection = this.get('selection'),
    2195             manager = this.frame._selection;
    2196 
    2197         if ( ! this.get('syncSelection') || ! manager || ! selection ) {
    2198             return;
    2199         }
    2200 
    2201         if ( selection.multiple ) {
    2202             manager.attachments.reset( selection.toArray().concat( manager.difference ) );
    2203             manager.difference = [];
    2204         } else {
    2205             manager.attachments.add( selection.toArray() );
    2206         }
    2207 
    2208         manager.single = selection._single;
     4046        view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
     4047
     4048        this.content.set( view );
     4049
     4050        // after bringing in the frame, load the actual editor via an ajax call
     4051        view.loadEditor();
     4052
     4053    },
     4054
     4055    renderImageDetailsToolbar: function() {
     4056        this.toolbar.set( new wp.media.view.Toolbar({
     4057            controller: this,
     4058            items: {
     4059                select: {
     4060                    style:    'primary',
     4061                    text:     l10n.update,
     4062                    priority: 80,
     4063
     4064                    click: function() {
     4065                        var controller = this.controller,
     4066                            state = controller.state();
     4067
     4068                        controller.close();
     4069
     4070                        // not sure if we want to use wp.media.string.image which will create a shortcode or
     4071                        // perhaps wp.html.string to at least to build the <img />
     4072                        state.trigger( 'update', controller.image.toJSON() );
     4073
     4074                        // Restore and reset the default state.
     4075                        controller.setState( controller.options.state );
     4076                        controller.reset();
     4077                    }
     4078                }
     4079            }
     4080        }) );
     4081    },
     4082
     4083    renderReplaceImageToolbar: function() {
     4084        var frame = this,
     4085            lastState = frame.lastState(),
     4086            previous = lastState && lastState.id;
     4087
     4088        this.toolbar.set( new wp.media.view.Toolbar({
     4089            controller: this,
     4090            items: {
     4091                back: {
     4092                    text:     l10n.back,
     4093                    priority: 20,
     4094                    click:    function() {
     4095                        if ( previous ) {
     4096                            frame.setState( previous );
     4097                        } else {
     4098                            frame.close();
     4099                        }
     4100                    }
     4101                },
     4102
     4103                replace: {
     4104                    style:    'primary',
     4105                    text:     l10n.replace,
     4106                    priority: 80,
     4107                    requires: { selection: true },
     4108
     4109                    click: function() {
     4110                        var controller = this.controller,
     4111                            state = controller.state(),
     4112                            selection = state.get( 'selection' ),
     4113                            attachment = selection.single();
     4114
     4115                        controller.close();
     4116
     4117                        controller.image.changeAttachment( attachment, state.display( attachment ) );
     4118
     4119                        // not sure if we want to use wp.media.string.image which will create a shortcode or
     4120                        // perhaps wp.html.string to at least to build the <img />
     4121                        state.trigger( 'replace', controller.image.toJSON() );
     4122
     4123                        // Restore and reset the default state.
     4124                        controller.setState( controller.options.state );
     4125                        controller.reset();
     4126                    }
     4127                }
     4128            }
     4129        }) );
    22094130    }
    2210 };
    2211 
    2212 module.exports = selectionSync;
    2213 
    2214 },{}],19:[function(require,module,exports){
    2215 var media = wp.media,
    2216     $ = jQuery,
    2217     l10n;
    2218 
    2219 media.isTouchDevice = ( 'ontouchend' in document );
    2220 
    2221 // Link any localized strings.
    2222 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
    2223 
    2224 // Link any settings.
    2225 media.view.settings = l10n.settings || {};
    2226 delete l10n.settings;
    2227 
    2228 // Copy the `post` setting over to the model settings.
    2229 media.model.settings.post = media.view.settings.post;
    2230 
    2231 // Check if the browser supports CSS 3.0 transitions
    2232 $.support.transition = (function(){
    2233     var style = document.documentElement.style,
    2234         transitions = {
    2235             WebkitTransition: 'webkitTransitionEnd',
    2236             MozTransition:    'transitionend',
    2237             OTransition:      'oTransitionEnd otransitionend',
    2238             transition:       'transitionend'
    2239         }, transition;
    2240 
    2241     transition = _.find( _.keys( transitions ), function( transition ) {
    2242         return ! _.isUndefined( style[ transition ] );
    2243     });
    2244 
    2245     return transition && {
    2246         end: transitions[ transition ]
    2247     };
    2248 }());
     4131
     4132});
     4133
     4134module.exports = ImageDetails;
     4135
     4136
     4137/***/ }),
     4138/* 51 */
     4139/***/ (function(module, exports) {
     4140
     4141var $ = jQuery,
     4142    Modal;
    22494143
    22504144/**
    2251  * A shared event bus used to provide events into
    2252  * the media workflows that 3rd-party devs can use to hook
    2253  * in.
    2254  */
    2255 media.events = _.extend( {}, Backbone.Events );
    2256 
    2257 /**
    2258  * Makes it easier to bind events using transitions.
    2259  *
    2260  * @param {string} selector
    2261  * @param {Number} sensitivity
    2262  * @returns {Promise}
    2263  */
    2264 media.transition = function( selector, sensitivity ) {
    2265     var deferred = $.Deferred();
    2266 
    2267     sensitivity = sensitivity || 2000;
    2268 
    2269     if ( $.support.transition ) {
    2270         if ( ! (selector instanceof $) ) {
    2271             selector = $( selector );
    2272         }
    2273 
    2274         // Resolve the deferred when the first element finishes animating.
    2275         selector.first().one( $.support.transition.end, deferred.resolve );
    2276 
    2277         // Just in case the event doesn't trigger, fire a callback.
    2278         _.delay( deferred.resolve, sensitivity );
    2279 
    2280     // Otherwise, execute on the spot.
    2281     } else {
    2282         deferred.resolve();
    2283     }
    2284 
    2285     return deferred.promise();
    2286 };
    2287 
    2288 media.controller.Region = require( './controllers/region.js' );
    2289 media.controller.StateMachine = require( './controllers/state-machine.js' );
    2290 media.controller.State = require( './controllers/state.js' );
    2291 
    2292 media.selectionSync = require( './utils/selection-sync.js' );
    2293 media.controller.Library = require( './controllers/library.js' );
    2294 media.controller.ImageDetails = require( './controllers/image-details.js' );
    2295 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
    2296 media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
    2297 media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
    2298 media.controller.CollectionAdd = require( './controllers/collection-add.js' );
    2299 media.controller.FeaturedImage = require( './controllers/featured-image.js' );
    2300 media.controller.ReplaceImage = require( './controllers/replace-image.js' );
    2301 media.controller.EditImage = require( './controllers/edit-image.js' );
    2302 media.controller.MediaLibrary = require( './controllers/media-library.js' );
    2303 media.controller.Embed = require( './controllers/embed.js' );
    2304 media.controller.Cropper = require( './controllers/cropper.js' );
    2305 media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' );
    2306 media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' );
    2307 
    2308 media.View = require( './views/view.js' );
    2309 media.view.Frame = require( './views/frame.js' );
    2310 media.view.MediaFrame = require( './views/media-frame.js' );
    2311 media.view.MediaFrame.Select = require( './views/frame/select.js' );
    2312 media.view.MediaFrame.Post = require( './views/frame/post.js' );
    2313 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
    2314 media.view.Modal = require( './views/modal.js' );
    2315 media.view.FocusManager = require( './views/focus-manager.js' );
    2316 media.view.UploaderWindow = require( './views/uploader/window.js' );
    2317 media.view.EditorUploader = require( './views/uploader/editor.js' );
    2318 media.view.UploaderInline = require( './views/uploader/inline.js' );
    2319 media.view.UploaderStatus = require( './views/uploader/status.js' );
    2320 media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
    2321 media.view.Toolbar = require( './views/toolbar.js' );
    2322 media.view.Toolbar.Select = require( './views/toolbar/select.js' );
    2323 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
    2324 media.view.Button = require( './views/button.js' );
    2325 media.view.ButtonGroup = require( './views/button-group.js' );
    2326 media.view.PriorityList = require( './views/priority-list.js' );
    2327 media.view.MenuItem = require( './views/menu-item.js' );
    2328 media.view.Menu = require( './views/menu.js' );
    2329 media.view.RouterItem = require( './views/router-item.js' );
    2330 media.view.Router = require( './views/router.js' );
    2331 media.view.Sidebar = require( './views/sidebar.js' );
    2332 media.view.Attachment = require( './views/attachment.js' );
    2333 media.view.Attachment.Library = require( './views/attachment/library.js' );
    2334 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
    2335 media.view.Attachments = require( './views/attachments.js' );
    2336 media.view.Search = require( './views/search.js' );
    2337 media.view.AttachmentFilters = require( './views/attachment-filters.js' );
    2338 media.view.DateFilter = require( './views/attachment-filters/date.js' );
    2339 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
    2340 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
    2341 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
    2342 media.view.Selection = require( './views/selection.js' );
    2343 media.view.Attachment.Selection = require( './views/attachment/selection.js' );
    2344 media.view.Attachments.Selection = require( './views/attachments/selection.js' );
    2345 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
    2346 media.view.Settings = require( './views/settings.js' );
    2347 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
    2348 media.view.Settings.Gallery = require( './views/settings/gallery.js' );
    2349 media.view.Settings.Playlist = require( './views/settings/playlist.js' );
    2350 media.view.Attachment.Details = require( './views/attachment/details.js' );
    2351 media.view.AttachmentCompat = require( './views/attachment-compat.js' );
    2352 media.view.Iframe = require( './views/iframe.js' );
    2353 media.view.Embed = require( './views/embed.js' );
    2354 media.view.Label = require( './views/label.js' );
    2355 media.view.EmbedUrl = require( './views/embed/url.js' );
    2356 media.view.EmbedLink = require( './views/embed/link.js' );
    2357 media.view.EmbedImage = require( './views/embed/image.js' );
    2358 media.view.ImageDetails = require( './views/image-details.js' );
    2359 media.view.Cropper = require( './views/cropper.js' );
    2360 media.view.SiteIconCropper = require( './views/site-icon-cropper.js' );
    2361 media.view.SiteIconPreview = require( './views/site-icon-preview.js' );
    2362 media.view.EditImage = require( './views/edit-image.js' );
    2363 media.view.Spinner = require( './views/spinner.js' );
    2364 
    2365 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/customize-image-cropper.js":4,"./controllers/edit-image.js":5,"./controllers/embed.js":6,"./controllers/featured-image.js":7,"./controllers/gallery-add.js":8,"./controllers/gallery-edit.js":9,"./controllers/image-details.js":10,"./controllers/library.js":11,"./controllers/media-library.js":12,"./controllers/region.js":13,"./controllers/replace-image.js":14,"./controllers/site-icon-cropper.js":15,"./controllers/state-machine.js":16,"./controllers/state.js":17,"./utils/selection-sync.js":18,"./views/attachment-compat.js":20,"./views/attachment-filters.js":21,"./views/attachment-filters/all.js":22,"./views/attachment-filters/date.js":23,"./views/attachment-filters/uploaded.js":24,"./views/attachment.js":25,"./views/attachment/details.js":26,"./views/attachment/edit-library.js":27,"./views/attachment/edit-selection.js":28,"./views/attachment/library.js":29,"./views/attachment/selection.js":30,"./views/attachments.js":31,"./views/attachments/browser.js":32,"./views/attachments/selection.js":33,"./views/button-group.js":34,"./views/button.js":35,"./views/cropper.js":36,"./views/edit-image.js":37,"./views/embed.js":38,"./views/embed/image.js":39,"./views/embed/link.js":40,"./views/embed/url.js":41,"./views/focus-manager.js":42,"./views/frame.js":43,"./views/frame/image-details.js":44,"./views/frame/post.js":45,"./views/frame/select.js":46,"./views/iframe.js":47,"./views/image-details.js":48,"./views/label.js":49,"./views/media-frame.js":50,"./views/menu-item.js":51,"./views/menu.js":52,"./views/modal.js":53,"./views/priority-list.js":54,"./views/router-item.js":55,"./views/router.js":56,"./views/search.js":57,"./views/selection.js":58,"./views/settings.js":59,"./views/settings/attachment-display.js":60,"./views/settings/gallery.js":61,"./views/settings/playlist.js":62,"./views/sidebar.js":63,"./views/site-icon-cropper.js":64,"./views/site-icon-preview.js":65,"./views/spinner.js":66,"./views/toolbar.js":67,"./views/toolbar/embed.js":68,"./views/toolbar/select.js":69,"./views/uploader/editor.js":70,"./views/uploader/inline.js":71,"./views/uploader/status-error.js":72,"./views/uploader/status.js":73,"./views/uploader/window.js":74,"./views/view.js":75}],20:[function(require,module,exports){
    2366 var View = wp.media.View,
    2367     AttachmentCompat;
    2368 
    2369 /**
    2370  * wp.media.view.AttachmentCompat
    2371  *
    2372  * A view to display fields added via the `attachment_fields_to_edit` filter.
     4145 * wp.media.view.Modal
     4146 *
     4147 * A modal view, which the media modal uses as its default container.
    23734148 *
    23744149 * @memberOf wp.media.view
     
    23794154 * @augments Backbone.View
    23804155 */
    2381 AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{
    2382     tagName:   'form',
    2383     className: 'compat-item',
     4156Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{
     4157    tagName:  'div',
     4158    template: wp.template('media-modal'),
     4159
     4160    attributes: {
     4161        tabindex: 0
     4162    },
    23844163
    23854164    events: {
    2386         'submit':          'preventDefault',
    2387         'change input':    'save',
    2388         'change select':   'save',
    2389         'change textarea': 'save'
    2390     },
     4165        'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
     4166        'keydown': 'keydown'
     4167    },
     4168
     4169    clickedOpenerEl: null,
    23914170
    23924171    initialize: function() {
    2393         this.listenTo( this.model, 'change:compat', this.render );
    2394     },
    2395     /**
    2396      * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     4172        _.defaults( this.options, {
     4173            container: document.body,
     4174            title:     '',
     4175            propagate: true,
     4176            freeze:    true
     4177        });
     4178
     4179        this.focusManager = new wp.media.view.FocusManager({
     4180            el: this.el
     4181        });
     4182    },
     4183    /**
     4184     * @returns {Object}
     4185     */
     4186    prepare: function() {
     4187        return {
     4188            title: this.options.title
     4189        };
     4190    },
     4191
     4192    /**
     4193     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4194     */
     4195    attach: function() {
     4196        if ( this.views.attached ) {
     4197            return this;
     4198        }
     4199
     4200        if ( ! this.views.rendered ) {
     4201            this.render();
     4202        }
     4203
     4204        this.$el.appendTo( this.options.container );
     4205
     4206        // Manually mark the view as attached and trigger ready.
     4207        this.views.attached = true;
     4208        this.views.ready();
     4209
     4210        return this.propagate('attach');
     4211    },
     4212
     4213    /**
     4214     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4215     */
     4216    detach: function() {
     4217        if ( this.$el.is(':visible') ) {
     4218            this.close();
     4219        }
     4220
     4221        this.$el.detach();
     4222        this.views.attached = false;
     4223        return this.propagate('detach');
     4224    },
     4225
     4226    /**
     4227     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4228     */
     4229    open: function() {
     4230        var $el = this.$el,
     4231            options = this.options,
     4232            mceEditor;
     4233
     4234        if ( $el.is(':visible') ) {
     4235            return this;
     4236        }
     4237
     4238        this.clickedOpenerEl = document.activeElement;
     4239
     4240        if ( ! this.views.attached ) {
     4241            this.attach();
     4242        }
     4243
     4244        // If the `freeze` option is set, record the window's scroll position.
     4245        if ( options.freeze ) {
     4246            this._freeze = {
     4247                scrollTop: $( window ).scrollTop()
     4248            };
     4249        }
     4250
     4251        // Disable page scrolling.
     4252        $( 'body' ).addClass( 'modal-open' );
     4253
     4254        $el.show();
     4255
     4256        // Try to close the onscreen keyboard
     4257        if ( 'ontouchend' in document ) {
     4258            if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
     4259                mceEditor.iframeElement.focus();
     4260                mceEditor.iframeElement.blur();
     4261
     4262                setTimeout( function() {
     4263                    mceEditor.iframeElement.blur();
     4264                }, 100 );
     4265            }
     4266        }
     4267
     4268        this.$el.focus();
     4269
     4270        return this.propagate('open');
     4271    },
     4272
     4273    /**
     4274     * @param {Object} options
     4275     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4276     */
     4277    close: function( options ) {
     4278        var freeze = this._freeze;
     4279
     4280        if ( ! this.views.attached || ! this.$el.is(':visible') ) {
     4281            return this;
     4282        }
     4283
     4284        // Enable page scrolling.
     4285        $( 'body' ).removeClass( 'modal-open' );
     4286
     4287        // Hide modal and remove restricted media modal tab focus once it's closed
     4288        this.$el.hide().undelegate( 'keydown' );
     4289
     4290        // Put focus back in useful location once modal is closed.
     4291        if ( null !== this.clickedOpenerEl ) {
     4292            this.clickedOpenerEl.focus();
     4293        } else {
     4294            $( '#wpbody-content' ).focus();
     4295        }
     4296
     4297        this.propagate('close');
     4298
     4299        // If the `freeze` option is set, restore the container's scroll position.
     4300        if ( freeze ) {
     4301            $( window ).scrollTop( freeze.scrollTop );
     4302        }
     4303
     4304        if ( options && options.escape ) {
     4305            this.propagate('escape');
     4306        }
     4307
     4308        return this;
     4309    },
     4310    /**
     4311     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4312     */
     4313    escape: function() {
     4314        return this.close({ escape: true });
     4315    },
     4316    /**
     4317     * @param {Object} event
     4318     */
     4319    escapeHandler: function( event ) {
     4320        event.preventDefault();
     4321        this.escape();
     4322    },
     4323
     4324    /**
     4325     * @param {Array|Object} content Views to register to '.media-modal-content'
     4326     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4327     */
     4328    content: function( content ) {
     4329        this.views.set( '.media-modal-content', content );
     4330        return this;
     4331    },
     4332
     4333    /**
     4334     * Triggers a modal event and if the `propagate` option is set,
     4335     * forwards events to the modal's controller.
     4336     *
     4337     * @param {string} id
     4338     * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4339     */
     4340    propagate: function( id ) {
     4341        this.trigger( id );
     4342
     4343        if ( this.options.propagate ) {
     4344            this.controller.trigger( id );
     4345        }
     4346
     4347        return this;
     4348    },
     4349    /**
     4350     * @param {Object} event
     4351     */
     4352    keydown: function( event ) {
     4353        // Close the modal when escape is pressed.
     4354        if ( 27 === event.which && this.$el.is(':visible') ) {
     4355            this.escape();
     4356            event.stopImmediatePropagation();
     4357        }
     4358    }
     4359});
     4360
     4361module.exports = Modal;
     4362
     4363
     4364/***/ }),
     4365/* 52 */
     4366/***/ (function(module, exports) {
     4367
     4368/**
     4369 * wp.media.view.FocusManager
     4370 *
     4371 * @memberOf wp.media.view
     4372 *
     4373 * @class
     4374 * @augments wp.media.View
     4375 * @augments wp.Backbone.View
     4376 * @augments Backbone.View
     4377 */
     4378var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{
     4379
     4380    events: {
     4381        'keydown': 'constrainTabbing'
     4382    },
     4383
     4384    focus: function() { // Reset focus on first left menu item
     4385        this.$('.media-menu-item').first().focus();
     4386    },
     4387    /**
     4388     * @param {Object} event
     4389     */
     4390    constrainTabbing: function( event ) {
     4391        var tabbables;
     4392
     4393        // Look for the tab key.
     4394        if ( 9 !== event.keyCode ) {
     4395            return;
     4396        }
     4397
     4398        // Skip the file input added by Plupload.
     4399        tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
     4400
     4401        // Keep tab focus within media modal while it's open
     4402        if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
     4403            tabbables.first().focus();
     4404            return false;
     4405        } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
     4406            tabbables.last().focus();
     4407            return false;
     4408        }
     4409    }
     4410
     4411});
     4412
     4413module.exports = FocusManager;
     4414
     4415
     4416/***/ }),
     4417/* 53 */
     4418/***/ (function(module, exports) {
     4419
     4420var $ = jQuery,
     4421    UploaderWindow;
     4422
     4423/**
     4424 * wp.media.view.UploaderWindow
     4425 *
     4426 * An uploader window that allows for dragging and dropping media.
     4427 *
     4428 * @memberOf wp.media.view
     4429 *
     4430 * @class
     4431 * @augments wp.media.View
     4432 * @augments wp.Backbone.View
     4433 * @augments Backbone.View
     4434 *
     4435 * @param {object} [options]                   Options hash passed to the view.
     4436 * @param {object} [options.uploader]          Uploader properties.
     4437 * @param {jQuery} [options.uploader.browser]
     4438 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
     4439 * @param {object} [options.uploader.params]
     4440 */
     4441UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{
     4442    tagName:   'div',
     4443    className: 'uploader-window',
     4444    template:  wp.template('uploader-window'),
     4445
     4446    initialize: function() {
     4447        var uploader;
     4448
     4449        this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' );
     4450
     4451        uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
     4452            dropzone:  this.$el,
     4453            browser:   this.$browser,
     4454            params:    {}
     4455        });
     4456
     4457        // Ensure the dropzone is a jQuery collection.
     4458        if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
     4459            uploader.dropzone = $( uploader.dropzone );
     4460        }
     4461
     4462        this.controller.on( 'activate', this.refresh, this );
     4463
     4464        this.controller.on( 'detach', function() {
     4465            this.$browser.remove();
     4466        }, this );
     4467    },
     4468
     4469    refresh: function() {
     4470        if ( this.uploader ) {
     4471            this.uploader.refresh();
     4472        }
     4473    },
     4474
     4475    ready: function() {
     4476        var postId = wp.media.view.settings.post.id,
     4477            dropzone;
     4478
     4479        // If the uploader already exists, bail.
     4480        if ( this.uploader ) {
     4481            return;
     4482        }
     4483
     4484        if ( postId ) {
     4485            this.options.uploader.params.post_id = postId;
     4486        }
     4487        this.uploader = new wp.Uploader( this.options.uploader );
     4488
     4489        dropzone = this.uploader.dropzone;
     4490        dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
     4491        dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
     4492
     4493        $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
     4494    },
     4495
     4496    _ready: function() {
     4497        this.controller.trigger( 'uploader:ready' );
     4498    },
     4499
     4500    show: function() {
     4501        var $el = this.$el.show();
     4502
     4503        // Ensure that the animation is triggered by waiting until
     4504        // the transparent element is painted into the DOM.
     4505        _.defer( function() {
     4506            $el.css({ opacity: 1 });
     4507        });
     4508    },
     4509
     4510    hide: function() {
     4511        var $el = this.$el.css({ opacity: 0 });
     4512
     4513        wp.media.transition( $el ).done( function() {
     4514            // Transition end events are subject to race conditions.
     4515            // Make sure that the value is set as intended.
     4516            if ( '0' === $el.css('opacity') ) {
     4517                $el.hide();
     4518            }
     4519        });
     4520
     4521        // https://core.trac.wordpress.org/ticket/27341
     4522        _.delay( function() {
     4523            if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
     4524                $el.hide();
     4525            }
     4526        }, 500 );
     4527    }
     4528});
     4529
     4530module.exports = UploaderWindow;
     4531
     4532
     4533/***/ }),
     4534/* 54 */
     4535/***/ (function(module, exports) {
     4536
     4537var View = wp.media.View,
     4538    l10n = wp.media.view.l10n,
     4539    $ = jQuery,
     4540    EditorUploader;
     4541
     4542/**
     4543 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
     4544 * and relays drag'n'dropped files to a media workflow.
     4545 *
     4546 * wp.media.view.EditorUploader
     4547 *
     4548 * @memberOf wp.media.view
     4549 *
     4550 * @class
     4551 * @augments wp.media.View
     4552 * @augments wp.Backbone.View
     4553 * @augments Backbone.View
     4554 */
     4555EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{
     4556    tagName:   'div',
     4557    className: 'uploader-editor',
     4558    template:  wp.template( 'uploader-editor' ),
     4559
     4560    localDrag: false,
     4561    overContainer: false,
     4562    overDropzone: false,
     4563    draggingFile: null,
     4564
     4565    /**
     4566     * Bind drag'n'drop events to callbacks.
     4567     */
     4568    initialize: function() {
     4569        this.initialized = false;
     4570
     4571        // Bail if not enabled or UA does not support drag'n'drop or File API.
     4572        if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
     4573            return this;
     4574        }
     4575
     4576        this.$document = $(document);
     4577        this.dropzones = [];
     4578        this.files = [];
     4579
     4580        this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
     4581        this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
     4582        this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
     4583        this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
     4584
     4585        this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
     4586        this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
     4587
     4588        this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
     4589            this.localDrag = event.type === 'dragstart';
     4590
     4591            if ( event.type === 'drop' ) {
     4592                this.containerDragleave();
     4593            }
     4594        }, this ) );
     4595
     4596        this.initialized = true;
     4597        return this;
     4598    },
     4599
     4600    /**
     4601     * Check browser support for drag'n'drop.
     4602     *
     4603     * @return Boolean
     4604     */
     4605    browserSupport: function() {
     4606        var supports = false, div = document.createElement('div');
     4607
     4608        supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
     4609        supports = supports && !! ( window.File && window.FileList && window.FileReader );
     4610        return supports;
     4611    },
     4612
     4613    isDraggingFile: function( event ) {
     4614        if ( this.draggingFile !== null ) {
     4615            return this.draggingFile;
     4616        }
     4617
     4618        if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
     4619            return false;
     4620        }
     4621
     4622        this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
     4623            _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
     4624
     4625        return this.draggingFile;
     4626    },
     4627
     4628    refresh: function( e ) {
     4629        var dropzone_id;
     4630        for ( dropzone_id in this.dropzones ) {
     4631            // Hide the dropzones only if dragging has left the screen.
     4632            this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
     4633        }
     4634
     4635        if ( ! _.isUndefined( e ) ) {
     4636            $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
     4637        }
     4638
     4639        if ( ! this.overContainer && ! this.overDropzone ) {
     4640            this.draggingFile = null;
     4641        }
     4642
     4643        return this;
     4644    },
     4645
     4646    render: function() {
     4647        if ( ! this.initialized ) {
     4648            return this;
     4649        }
     4650
     4651        View.prototype.render.apply( this, arguments );
     4652        $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
     4653        return this;
     4654    },
     4655
     4656    attach: function( index, editor ) {
     4657        // Attach a dropzone to an editor.
     4658        var dropzone = this.$el.clone();
     4659        this.dropzones.push( dropzone );
     4660        $( editor ).append( dropzone );
     4661        return this;
     4662    },
     4663
     4664    /**
     4665     * When a file is dropped on the editor uploader, open up an editor media workflow
     4666     * and upload the file immediately.
     4667     *
     4668     * @param  {jQuery.Event} event The 'drop' event.
     4669     */
     4670    drop: function( event ) {
     4671        var $wrap, uploadView;
     4672
     4673        this.containerDragleave( event );
     4674        this.dropzoneDragleave( event );
     4675
     4676        this.files = event.originalEvent.dataTransfer.files;
     4677        if ( this.files.length < 1 ) {
     4678            return;
     4679        }
     4680
     4681        // Set the active editor to the drop target.
     4682        $wrap = $( event.target ).parents( '.wp-editor-wrap' );
     4683        if ( $wrap.length > 0 && $wrap[0].id ) {
     4684            window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
     4685        }
     4686
     4687        if ( ! this.workflow ) {
     4688            this.workflow = wp.media.editor.open( window.wpActiveEditor, {
     4689                frame:    'post',
     4690                state:    'insert',
     4691                title:    l10n.addMedia,
     4692                multiple: true
     4693            });
     4694
     4695            uploadView = this.workflow.uploader;
     4696
     4697            if ( uploadView.uploader && uploadView.uploader.ready ) {
     4698                this.addFiles.apply( this );
     4699            } else {
     4700                this.workflow.on( 'uploader:ready', this.addFiles, this );
     4701            }
     4702        } else {
     4703            this.workflow.state().reset();
     4704            this.addFiles.apply( this );
     4705            this.workflow.open();
     4706        }
     4707
     4708        return false;
     4709    },
     4710
     4711    /**
     4712     * Add the files to the uploader.
     4713     */
     4714    addFiles: function() {
     4715        if ( this.files.length ) {
     4716            this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
     4717            this.files = [];
     4718        }
     4719        return this;
     4720    },
     4721
     4722    containerDragover: function( event ) {
     4723        if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     4724            return;
     4725        }
     4726
     4727        this.overContainer = true;
     4728        this.refresh();
     4729    },
     4730
     4731    containerDragleave: function() {
     4732        this.overContainer = false;
     4733
     4734        // Throttle dragleave because it's called when bouncing from some elements to others.
     4735        _.delay( _.bind( this.refresh, this ), 50 );
     4736    },
     4737
     4738    dropzoneDragover: function( event ) {
     4739        if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     4740            return;
     4741        }
     4742
     4743        this.overDropzone = true;
     4744        this.refresh( event );
     4745        return false;
     4746    },
     4747
     4748    dropzoneDragleave: function( e ) {
     4749        this.overDropzone = false;
     4750        _.delay( _.bind( this.refresh, this, e ), 50 );
     4751    },
     4752
     4753    click: function( e ) {
     4754        // In the rare case where the dropzone gets stuck, hide it on click.
     4755        this.containerDragleave( e );
     4756        this.dropzoneDragleave( e );
     4757        this.localDrag = false;
     4758    }
     4759});
     4760
     4761module.exports = EditorUploader;
     4762
     4763
     4764/***/ }),
     4765/* 55 */
     4766/***/ (function(module, exports) {
     4767
     4768var View = wp.media.View,
     4769    UploaderInline;
     4770
     4771/**
     4772 * wp.media.view.UploaderInline
     4773 *
     4774 * The inline uploader that shows up in the 'Upload Files' tab.
     4775 *
     4776 * @memberOf wp.media.view
     4777 *
     4778 * @class
     4779 * @augments wp.media.View
     4780 * @augments wp.Backbone.View
     4781 * @augments Backbone.View
     4782 */
     4783UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{
     4784    tagName:   'div',
     4785    className: 'uploader-inline',
     4786    template:  wp.template('uploader-inline'),
     4787
     4788    events: {
     4789        'click .close': 'hide'
     4790    },
     4791
     4792    initialize: function() {
     4793        _.defaults( this.options, {
     4794            message: '',
     4795            status:  true,
     4796            canClose: false
     4797        });
     4798
     4799        if ( ! this.options.$browser && this.controller.uploader ) {
     4800            this.options.$browser = this.controller.uploader.$browser;
     4801        }
     4802
     4803        if ( _.isUndefined( this.options.postId ) ) {
     4804            this.options.postId = wp.media.view.settings.post.id;
     4805        }
     4806
     4807        if ( this.options.status ) {
     4808            this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
     4809                controller: this.controller
     4810            }) );
     4811        }
     4812    },
     4813
     4814    prepare: function() {
     4815        var suggestedWidth = this.controller.state().get('suggestedWidth'),
     4816            suggestedHeight = this.controller.state().get('suggestedHeight'),
     4817            data = {};
     4818
     4819        data.message = this.options.message;
     4820        data.canClose = this.options.canClose;
     4821
     4822        if ( suggestedWidth && suggestedHeight ) {
     4823            data.suggestedWidth = suggestedWidth;
     4824            data.suggestedHeight = suggestedHeight;
     4825        }
     4826
     4827        return data;
     4828    },
     4829    /**
     4830     * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    23974831     */
    23984832    dispose: function() {
    2399         if ( this.$(':focus').length ) {
    2400             this.save();
     4833        if ( this.disposing ) {
     4834            /**
     4835             * call 'dispose' directly on the parent class
     4836             */
     4837            return View.prototype.dispose.apply( this, arguments );
     4838        }
     4839
     4840        // Run remove on `dispose`, so we can be sure to refresh the
     4841        // uploader with a view-less DOM. Track whether we're disposing
     4842        // so we don't trigger an infinite loop.
     4843        this.disposing = true;
     4844        return this.remove();
     4845    },
     4846    /**
     4847     * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
     4848     */
     4849    remove: function() {
     4850        /**
     4851         * call 'remove' directly on the parent class
     4852         */
     4853        var result = View.prototype.remove.apply( this, arguments );
     4854
     4855        _.defer( _.bind( this.refresh, this ) );
     4856        return result;
     4857    },
     4858
     4859    refresh: function() {
     4860        var uploader = this.controller.uploader;
     4861
     4862        if ( uploader ) {
     4863            uploader.refresh();
     4864        }
     4865    },
     4866    /**
     4867     * @returns {wp.media.view.UploaderInline}
     4868     */
     4869    ready: function() {
     4870        var $browser = this.options.$browser,
     4871            $placeholder;
     4872
     4873        if ( this.controller.uploader ) {
     4874            $placeholder = this.$('.browser');
     4875
     4876            // Check if we've already replaced the placeholder.
     4877            if ( $placeholder[0] === $browser[0] ) {
     4878                return;
     4879            }
     4880
     4881            $browser.detach().text( $placeholder.text() );
     4882            $browser[0].className = $placeholder[0].className;
     4883            $placeholder.replaceWith( $browser.show() );
     4884        }
     4885
     4886        this.refresh();
     4887        return this;
     4888    },
     4889    show: function() {
     4890        this.$el.removeClass( 'hidden' );
     4891        if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
     4892            this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' );
     4893        }
     4894    },
     4895    hide: function() {
     4896        this.$el.addClass( 'hidden' );
     4897        if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
     4898            this.controller.$uploaderToggler
     4899                .attr( 'aria-expanded', 'false' )
     4900                // Move focus back to the toggle button when closing the uploader.
     4901                .focus();
     4902        }
     4903    }
     4904
     4905});
     4906
     4907module.exports = UploaderInline;
     4908
     4909
     4910/***/ }),
     4911/* 56 */
     4912/***/ (function(module, exports) {
     4913
     4914var View = wp.media.View,
     4915    UploaderStatus;
     4916
     4917/**
     4918 * wp.media.view.UploaderStatus
     4919 *
     4920 * An uploader status for on-going uploads.
     4921 *
     4922 * @memberOf wp.media.view
     4923 *
     4924 * @class
     4925 * @augments wp.media.View
     4926 * @augments wp.Backbone.View
     4927 * @augments Backbone.View
     4928 */
     4929UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{
     4930    className: 'media-uploader-status',
     4931    template:  wp.template('uploader-status'),
     4932
     4933    events: {
     4934        'click .upload-dismiss-errors': 'dismiss'
     4935    },
     4936
     4937    initialize: function() {
     4938        this.queue = wp.Uploader.queue;
     4939        this.queue.on( 'add remove reset', this.visibility, this );
     4940        this.queue.on( 'add remove reset change:percent', this.progress, this );
     4941        this.queue.on( 'add remove reset change:uploading', this.info, this );
     4942
     4943        this.errors = wp.Uploader.errors;
     4944        this.errors.reset();
     4945        this.errors.on( 'add remove reset', this.visibility, this );
     4946        this.errors.on( 'add', this.error, this );
     4947    },
     4948    /**
     4949     * @returns {wp.media.view.UploaderStatus}
     4950     */
     4951    dispose: function() {
     4952        wp.Uploader.queue.off( null, null, this );
     4953        /**
     4954         * call 'dispose' directly on the parent class
     4955         */
     4956        View.prototype.dispose.apply( this, arguments );
     4957        return this;
     4958    },
     4959
     4960    visibility: function() {
     4961        this.$el.toggleClass( 'uploading', !! this.queue.length );
     4962        this.$el.toggleClass( 'errors', !! this.errors.length );
     4963        this.$el.toggle( !! this.queue.length || !! this.errors.length );
     4964    },
     4965
     4966    ready: function() {
     4967        _.each({
     4968            '$bar':      '.media-progress-bar div',
     4969            '$index':    '.upload-index',
     4970            '$total':    '.upload-total',
     4971            '$filename': '.upload-filename'
     4972        }, function( selector, key ) {
     4973            this[ key ] = this.$( selector );
     4974        }, this );
     4975
     4976        this.visibility();
     4977        this.progress();
     4978        this.info();
     4979    },
     4980
     4981    progress: function() {
     4982        var queue = this.queue,
     4983            $bar = this.$bar;
     4984
     4985        if ( ! $bar || ! queue.length ) {
     4986            return;
     4987        }
     4988
     4989        $bar.width( ( queue.reduce( function( memo, attachment ) {
     4990            if ( ! attachment.get('uploading') ) {
     4991                return memo + 100;
     4992            }
     4993
     4994            var percent = attachment.get('percent');
     4995            return memo + ( _.isNumber( percent ) ? percent : 100 );
     4996        }, 0 ) / queue.length ) + '%' );
     4997    },
     4998
     4999    info: function() {
     5000        var queue = this.queue,
     5001            index = 0, active;
     5002
     5003        if ( ! queue.length ) {
     5004            return;
     5005        }
     5006
     5007        active = this.queue.find( function( attachment, i ) {
     5008            index = i;
     5009            return attachment.get('uploading');
     5010        });
     5011
     5012        this.$index.text( index + 1 );
     5013        this.$total.text( queue.length );
     5014        this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
     5015    },
     5016    /**
     5017     * @param {string} filename
     5018     * @returns {string}
     5019     */
     5020    filename: function( filename ) {
     5021        return _.escape( filename );
     5022    },
     5023    /**
     5024     * @param {Backbone.Model} error
     5025     */
     5026    error: function( error ) {
     5027        this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     5028            filename: this.filename( error.get('file').name ),
     5029            message:  error.get('message')
     5030        }), { at: 0 });
     5031    },
     5032
     5033    /**
     5034     * @param {Object} event
     5035     */
     5036    dismiss: function( event ) {
     5037        var errors = this.views.get('.upload-errors');
     5038
     5039        event.preventDefault();
     5040
     5041        if ( errors ) {
     5042            _.invoke( errors, 'remove' );
     5043        }
     5044        wp.Uploader.errors.reset();
     5045    }
     5046});
     5047
     5048module.exports = UploaderStatus;
     5049
     5050
     5051/***/ }),
     5052/* 57 */
     5053/***/ (function(module, exports) {
     5054
     5055/**
     5056 * wp.media.view.UploaderStatusError
     5057 *
     5058 * @memberOf wp.media.view
     5059 *
     5060 * @class
     5061 * @augments wp.media.View
     5062 * @augments wp.Backbone.View
     5063 * @augments Backbone.View
     5064 */
     5065var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{
     5066    className: 'upload-error',
     5067    template:  wp.template('uploader-status-error')
     5068});
     5069
     5070module.exports = UploaderStatusError;
     5071
     5072
     5073/***/ }),
     5074/* 58 */
     5075/***/ (function(module, exports) {
     5076
     5077var View = wp.media.View,
     5078    Toolbar;
     5079
     5080/**
     5081 * wp.media.view.Toolbar
     5082 *
     5083 * A toolbar which consists of a primary and a secondary section. Each sections
     5084 * can be filled with views.
     5085 *
     5086 * @memberOf wp.media.view
     5087 *
     5088 * @class
     5089 * @augments wp.media.View
     5090 * @augments wp.Backbone.View
     5091 * @augments Backbone.View
     5092 */
     5093Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{
     5094    tagName:   'div',
     5095    className: 'media-toolbar',
     5096
     5097    initialize: function() {
     5098        var state = this.controller.state(),
     5099            selection = this.selection = state.get('selection'),
     5100            library = this.library = state.get('library');
     5101
     5102        this._views = {};
     5103
     5104        // The toolbar is composed of two `PriorityList` views.
     5105        this.primary   = new wp.media.view.PriorityList();
     5106        this.secondary = new wp.media.view.PriorityList();
     5107        this.primary.$el.addClass('media-toolbar-primary search-form');
     5108        this.secondary.$el.addClass('media-toolbar-secondary');
     5109
     5110        this.views.set([ this.secondary, this.primary ]);
     5111
     5112        if ( this.options.items ) {
     5113            this.set( this.options.items, { silent: true });
     5114        }
     5115
     5116        if ( ! this.options.silent ) {
     5117            this.render();
     5118        }
     5119
     5120        if ( selection ) {
     5121            selection.on( 'add remove reset', this.refresh, this );
     5122        }
     5123
     5124        if ( library ) {
     5125            library.on( 'add remove reset', this.refresh, this );
     5126        }
     5127    },
     5128    /**
     5129     * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
     5130     */
     5131    dispose: function() {
     5132        if ( this.selection ) {
     5133            this.selection.off( null, null, this );
     5134        }
     5135
     5136        if ( this.library ) {
     5137            this.library.off( null, null, this );
    24015138        }
    24025139        /**
     
    24055142        return View.prototype.dispose.apply( this, arguments );
    24065143    },
    2407     /**
    2408      * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     5144
     5145    ready: function() {
     5146        this.refresh();
     5147    },
     5148
     5149    /**
     5150     * @param {string} id
     5151     * @param {Backbone.View|Object} view
     5152     * @param {Object} [options={}]
     5153     * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     5154     */
     5155    set: function( id, view, options ) {
     5156        var list;
     5157        options = options || {};
     5158
     5159        // Accept an object with an `id` : `view` mapping.
     5160        if ( _.isObject( id ) ) {
     5161            _.each( id, function( view, id ) {
     5162                this.set( id, view, { silent: true });
     5163            }, this );
     5164
     5165        } else {
     5166            if ( ! ( view instanceof Backbone.View ) ) {
     5167                view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
     5168                view = new wp.media.view.Button( view ).render();
     5169            }
     5170
     5171            view.controller = view.controller || this.controller;
     5172
     5173            this._views[ id ] = view;
     5174
     5175            list = view.options.priority < 0 ? 'secondary' : 'primary';
     5176            this[ list ].set( id, view, options );
     5177        }
     5178
     5179        if ( ! options.silent ) {
     5180            this.refresh();
     5181        }
     5182
     5183        return this;
     5184    },
     5185    /**
     5186     * @param {string} id
     5187     * @returns {wp.media.view.Button}
     5188     */
     5189    get: function( id ) {
     5190        return this._views[ id ];
     5191    },
     5192    /**
     5193     * @param {string} id
     5194     * @param {Object} options
     5195     * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     5196     */
     5197    unset: function( id, options ) {
     5198        delete this._views[ id ];
     5199        this.primary.unset( id, options );
     5200        this.secondary.unset( id, options );
     5201
     5202        if ( ! options || ! options.silent ) {
     5203            this.refresh();
     5204        }
     5205        return this;
     5206    },
     5207
     5208    refresh: function() {
     5209        var state = this.controller.state(),
     5210            library = state.get('library'),
     5211            selection = state.get('selection');
     5212
     5213        _.each( this._views, function( button ) {
     5214            if ( ! button.model || ! button.options || ! button.options.requires ) {
     5215                return;
     5216            }
     5217
     5218            var requires = button.options.requires,
     5219                disabled = false;
     5220
     5221            // Prevent insertion of attachments if any of them are still uploading
     5222            if ( selection && selection.models ) {
     5223                disabled = _.some( selection.models, function( attachment ) {
     5224                    return attachment.get('uploading') === true;
     5225                });
     5226            }
     5227
     5228            if ( requires.selection && selection && ! selection.length ) {
     5229                disabled = true;
     5230            } else if ( requires.library && library && ! library.length ) {
     5231                disabled = true;
     5232            }
     5233            button.model.set( 'disabled', disabled );
     5234        });
     5235    }
     5236});
     5237
     5238module.exports = Toolbar;
     5239
     5240
     5241/***/ }),
     5242/* 59 */
     5243/***/ (function(module, exports) {
     5244
     5245var Toolbar = wp.media.view.Toolbar,
     5246    l10n = wp.media.view.l10n,
     5247    Select;
     5248
     5249/**
     5250 * wp.media.view.Toolbar.Select
     5251 *
     5252 * @memberOf wp.media.view.Toolbar
     5253 *
     5254 * @class
     5255 * @augments wp.media.view.Toolbar
     5256 * @augments wp.media.View
     5257 * @augments wp.Backbone.View
     5258 * @augments Backbone.View
     5259 */
     5260Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{
     5261    initialize: function() {
     5262        var options = this.options;
     5263
     5264        _.bindAll( this, 'clickSelect' );
     5265
     5266        _.defaults( options, {
     5267            event: 'select',
     5268            state: false,
     5269            reset: true,
     5270            close: true,
     5271            text:  l10n.select,
     5272
     5273            // Does the button rely on the selection?
     5274            requires: {
     5275                selection: true
     5276            }
     5277        });
     5278
     5279        options.items = _.defaults( options.items || {}, {
     5280            select: {
     5281                style:    'primary',
     5282                text:     options.text,
     5283                priority: 80,
     5284                click:    this.clickSelect,
     5285                requires: options.requires
     5286            }
     5287        });
     5288        // Call 'initialize' directly on the parent class.
     5289        Toolbar.prototype.initialize.apply( this, arguments );
     5290    },
     5291
     5292    clickSelect: function() {
     5293        var options = this.options,
     5294            controller = this.controller;
     5295
     5296        if ( options.close ) {
     5297            controller.close();
     5298        }
     5299
     5300        if ( options.event ) {
     5301            controller.state().trigger( options.event );
     5302        }
     5303
     5304        if ( options.state ) {
     5305            controller.setState( options.state );
     5306        }
     5307
     5308        if ( options.reset ) {
     5309            controller.reset();
     5310        }
     5311    }
     5312});
     5313
     5314module.exports = Select;
     5315
     5316
     5317/***/ }),
     5318/* 60 */
     5319/***/ (function(module, exports) {
     5320
     5321var Select = wp.media.view.Toolbar.Select,
     5322    l10n = wp.media.view.l10n,
     5323    Embed;
     5324
     5325/**
     5326 * wp.media.view.Toolbar.Embed
     5327 *
     5328 * @memberOf wp.media.view.Toolbar
     5329 *
     5330 * @class
     5331 * @augments wp.media.view.Toolbar.Select
     5332 * @augments wp.media.view.Toolbar
     5333 * @augments wp.media.View
     5334 * @augments wp.Backbone.View
     5335 * @augments Backbone.View
     5336 */
     5337Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{
     5338    initialize: function() {
     5339        _.defaults( this.options, {
     5340            text: l10n.insertIntoPost,
     5341            requires: false
     5342        });
     5343        // Call 'initialize' directly on the parent class.
     5344        Select.prototype.initialize.apply( this, arguments );
     5345    },
     5346
     5347    refresh: function() {
     5348        var url = this.controller.state().props.get('url');
     5349        this.get('select').model.set( 'disabled', ! url || url === 'http://' );
     5350        /**
     5351         * call 'refresh' directly on the parent class
     5352         */
     5353        Select.prototype.refresh.apply( this, arguments );
     5354    }
     5355});
     5356
     5357module.exports = Embed;
     5358
     5359
     5360/***/ }),
     5361/* 61 */
     5362/***/ (function(module, exports) {
     5363
     5364/**
     5365 * wp.media.view.Button
     5366 *
     5367 * @memberOf wp.media.view
     5368 *
     5369 * @class
     5370 * @augments wp.media.View
     5371 * @augments wp.Backbone.View
     5372 * @augments Backbone.View
     5373 */
     5374var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{
     5375    tagName:    'button',
     5376    className:  'media-button',
     5377    attributes: { type: 'button' },
     5378
     5379    events: {
     5380        'click': 'click'
     5381    },
     5382
     5383    defaults: {
     5384        text:     '',
     5385        style:    '',
     5386        size:     'large',
     5387        disabled: false
     5388    },
     5389
     5390    initialize: function() {
     5391        /**
     5392         * Create a model with the provided `defaults`.
     5393         *
     5394         * @member {Backbone.Model}
     5395         */
     5396        this.model = new Backbone.Model( this.defaults );
     5397
     5398        // If any of the `options` have a key from `defaults`, apply its
     5399        // value to the `model` and remove it from the `options object.
     5400        _.each( this.defaults, function( def, key ) {
     5401            var value = this.options[ key ];
     5402            if ( _.isUndefined( value ) ) {
     5403                return;
     5404            }
     5405
     5406            this.model.set( key, value );
     5407            delete this.options[ key ];
     5408        }, this );
     5409
     5410        this.listenTo( this.model, 'change', this.render );
     5411    },
     5412    /**
     5413     * @returns {wp.media.view.Button} Returns itself to allow chaining
    24095414     */
    24105415    render: function() {
    2411         var compat = this.model.get('compat');
    2412         if ( ! compat || ! compat.item ) {
    2413             return;
    2414         }
    2415 
    2416         this.views.detach();
    2417         this.$el.html( compat.item );
    2418         this.views.render();
     5416        var classes = [ 'button', this.className ],
     5417            model = this.model.toJSON();
     5418
     5419        if ( model.style ) {
     5420            classes.push( 'button-' + model.style );
     5421        }
     5422
     5423        if ( model.size ) {
     5424            classes.push( 'button-' + model.size );
     5425        }
     5426
     5427        classes = _.uniq( classes.concat( this.options.classes ) );
     5428        this.el.className = classes.join(' ');
     5429
     5430        this.$el.attr( 'disabled', model.disabled );
     5431        this.$el.text( this.model.get('text') );
     5432
    24195433        return this;
    24205434    },
     
    24225436     * @param {Object} event
    24235437     */
    2424     preventDefault: function( event ) {
    2425         event.preventDefault();
     5438    click: function( event ) {
     5439        if ( '#' === this.attributes.href ) {
     5440            event.preventDefault();
     5441        }
     5442
     5443        if ( this.options.click && ! this.model.get('disabled') ) {
     5444            this.options.click.apply( this, arguments );
     5445        }
     5446    }
     5447});
     5448
     5449module.exports = Button;
     5450
     5451
     5452/***/ }),
     5453/* 62 */
     5454/***/ (function(module, exports) {
     5455
     5456var $ = Backbone.$,
     5457    ButtonGroup;
     5458
     5459/**
     5460 * wp.media.view.ButtonGroup
     5461 *
     5462 * @memberOf wp.media.view
     5463 *
     5464 * @class
     5465 * @augments wp.media.View
     5466 * @augments wp.Backbone.View
     5467 * @augments Backbone.View
     5468 */
     5469ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{
     5470    tagName:   'div',
     5471    className: 'button-group button-large media-button-group',
     5472
     5473    initialize: function() {
     5474        /**
     5475         * @member {wp.media.view.Button[]}
     5476         */
     5477        this.buttons = _.map( this.options.buttons || [], function( button ) {
     5478            if ( button instanceof Backbone.View ) {
     5479                return button;
     5480            } else {
     5481                return new wp.media.view.Button( button ).render();
     5482            }
     5483        });
     5484
     5485        delete this.options.buttons;
     5486
     5487        if ( this.options.classes ) {
     5488            this.$el.addClass( this.options.classes );
     5489        }
     5490    },
     5491
     5492    /**
     5493     * @returns {wp.media.view.ButtonGroup}
     5494     */
     5495    render: function() {
     5496        this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
     5497        return this;
     5498    }
     5499});
     5500
     5501module.exports = ButtonGroup;
     5502
     5503
     5504/***/ }),
     5505/* 63 */
     5506/***/ (function(module, exports) {
     5507
     5508/**
     5509 * wp.media.view.PriorityList
     5510 *
     5511 * @memberOf wp.media.view
     5512 *
     5513 * @class
     5514 * @augments wp.media.View
     5515 * @augments wp.Backbone.View
     5516 * @augments Backbone.View
     5517 */
     5518var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{
     5519    tagName:   'div',
     5520
     5521    initialize: function() {
     5522        this._views = {};
     5523
     5524        this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
     5525        delete this.options.views;
     5526
     5527        if ( ! this.options.silent ) {
     5528            this.render();
     5529        }
     5530    },
     5531    /**
     5532     * @param {string} id
     5533     * @param {wp.media.View|Object} view
     5534     * @param {Object} options
     5535     * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
     5536     */
     5537    set: function( id, view, options ) {
     5538        var priority, views, index;
     5539
     5540        options = options || {};
     5541
     5542        // Accept an object with an `id` : `view` mapping.
     5543        if ( _.isObject( id ) ) {
     5544            _.each( id, function( view, id ) {
     5545                this.set( id, view );
     5546            }, this );
     5547            return this;
     5548        }
     5549
     5550        if ( ! (view instanceof Backbone.View) ) {
     5551            view = this.toView( view, id, options );
     5552        }
     5553        view.controller = view.controller || this.controller;
     5554
     5555        this.unset( id );
     5556
     5557        priority = view.options.priority || 10;
     5558        views = this.views.get() || [];
     5559
     5560        _.find( views, function( existing, i ) {
     5561            if ( existing.options.priority > priority ) {
     5562                index = i;
     5563                return true;
     5564            }
     5565        });
     5566
     5567        this._views[ id ] = view;
     5568        this.views.add( view, {
     5569            at: _.isNumber( index ) ? index : views.length || 0
     5570        });
     5571
     5572        return this;
     5573    },
     5574    /**
     5575     * @param {string} id
     5576     * @returns {wp.media.View}
     5577     */
     5578    get: function( id ) {
     5579        return this._views[ id ];
     5580    },
     5581    /**
     5582     * @param {string} id
     5583     * @returns {wp.media.view.PriorityList}
     5584     */
     5585    unset: function( id ) {
     5586        var view = this.get( id );
     5587
     5588        if ( view ) {
     5589            view.remove();
     5590        }
     5591
     5592        delete this._views[ id ];
     5593        return this;
     5594    },
     5595    /**
     5596     * @param {Object} options
     5597     * @returns {wp.media.View}
     5598     */
     5599    toView: function( options ) {
     5600        return new wp.media.View( options );
     5601    }
     5602});
     5603
     5604module.exports = PriorityList;
     5605
     5606
     5607/***/ }),
     5608/* 64 */
     5609/***/ (function(module, exports) {
     5610
     5611var $ = jQuery,
     5612    MenuItem;
     5613
     5614/**
     5615 * wp.media.view.MenuItem
     5616 *
     5617 * @memberOf wp.media.view
     5618 *
     5619 * @class
     5620 * @augments wp.media.View
     5621 * @augments wp.Backbone.View
     5622 * @augments Backbone.View
     5623 */
     5624MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{
     5625    tagName:   'a',
     5626    className: 'media-menu-item',
     5627
     5628    attributes: {
     5629        href: '#'
     5630    },
     5631
     5632    events: {
     5633        'click': '_click'
    24265634    },
    24275635    /**
    24285636     * @param {Object} event
    24295637     */
    2430     save: function( event ) {
    2431         var data = {};
     5638    _click: function( event ) {
     5639        var clickOverride = this.options.click;
    24325640
    24335641        if ( event ) {
     
    24355643        }
    24365644
    2437         _.each( this.$el.serializeArray(), function( pair ) {
    2438             data[ pair.name ] = pair.value;
    2439         });
    2440 
    2441         this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
    2442         this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
    2443     },
    2444 
    2445     postSave: function() {
    2446         this.controller.trigger( 'attachment:compat:ready', ['ready'] );
     5645        if ( clickOverride ) {
     5646            clickOverride.call( this );
     5647        } else {
     5648            this.click();
     5649        }
     5650
     5651        // When selecting a tab along the left side,
     5652        // focus should be transferred into the main panel
     5653        if ( ! wp.media.isTouchDevice ) {
     5654            $('.media-frame-content input').first().focus();
     5655        }
     5656    },
     5657
     5658    click: function() {
     5659        var state = this.options.state;
     5660
     5661        if ( state ) {
     5662            this.controller.setState( state );
     5663            this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
     5664        }
     5665    },
     5666    /**
     5667     * @returns {wp.media.view.MenuItem} returns itself to allow chaining
     5668     */
     5669    render: function() {
     5670        var options = this.options;
     5671
     5672        if ( options.text ) {
     5673            this.$el.text( options.text );
     5674        } else if ( options.html ) {
     5675            this.$el.html( options.html );
     5676        }
     5677
     5678        return this;
    24475679    }
    24485680});
    24495681
    2450 module.exports = AttachmentCompat;
    2451 
    2452 },{}],21:[function(require,module,exports){
     5682module.exports = MenuItem;
     5683
     5684
     5685/***/ }),
     5686/* 65 */
     5687/***/ (function(module, exports) {
     5688
     5689var MenuItem = wp.media.view.MenuItem,
     5690    PriorityList = wp.media.view.PriorityList,
     5691    Menu;
     5692
     5693/**
     5694 * wp.media.view.Menu
     5695 *
     5696 * @memberOf wp.media.view
     5697 *
     5698 * @class
     5699 * @augments wp.media.view.PriorityList
     5700 * @augments wp.media.View
     5701 * @augments wp.Backbone.View
     5702 * @augments Backbone.View
     5703 */
     5704Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{
     5705    tagName:   'div',
     5706    className: 'media-menu',
     5707    property:  'state',
     5708    ItemView:  MenuItem,
     5709    region:    'menu',
     5710
     5711    /* TODO: alternatively hide on any click anywhere
     5712    events: {
     5713        'click': 'click'
     5714    },
     5715
     5716    click: function() {
     5717        this.$el.removeClass( 'visible' );
     5718    },
     5719    */
     5720
     5721    /**
     5722     * @param {Object} options
     5723     * @param {string} id
     5724     * @returns {wp.media.View}
     5725     */
     5726    toView: function( options, id ) {
     5727        options = options || {};
     5728        options[ this.property ] = options[ this.property ] || id;
     5729        return new this.ItemView( options ).render();
     5730    },
     5731
     5732    ready: function() {
     5733        /**
     5734         * call 'ready' directly on the parent class
     5735         */
     5736        PriorityList.prototype.ready.apply( this, arguments );
     5737        this.visibility();
     5738    },
     5739
     5740    set: function() {
     5741        /**
     5742         * call 'set' directly on the parent class
     5743         */
     5744        PriorityList.prototype.set.apply( this, arguments );
     5745        this.visibility();
     5746    },
     5747
     5748    unset: function() {
     5749        /**
     5750         * call 'unset' directly on the parent class
     5751         */
     5752        PriorityList.prototype.unset.apply( this, arguments );
     5753        this.visibility();
     5754    },
     5755
     5756    visibility: function() {
     5757        var region = this.region,
     5758            view = this.controller[ region ].get(),
     5759            views = this.views.get(),
     5760            hide = ! views || views.length < 2;
     5761
     5762        if ( this === view ) {
     5763            this.controller.$el.toggleClass( 'hide-' + region, hide );
     5764        }
     5765    },
     5766    /**
     5767     * @param {string} id
     5768     */
     5769    select: function( id ) {
     5770        var view = this.get( id );
     5771
     5772        if ( ! view ) {
     5773            return;
     5774        }
     5775
     5776        this.deselect();
     5777        view.$el.addClass('active');
     5778    },
     5779
     5780    deselect: function() {
     5781        this.$el.children().removeClass('active');
     5782    },
     5783
     5784    hide: function( id ) {
     5785        var view = this.get( id );
     5786
     5787        if ( ! view ) {
     5788            return;
     5789        }
     5790
     5791        view.$el.addClass('hidden');
     5792    },
     5793
     5794    show: function( id ) {
     5795        var view = this.get( id );
     5796
     5797        if ( ! view ) {
     5798            return;
     5799        }
     5800
     5801        view.$el.removeClass('hidden');
     5802    }
     5803});
     5804
     5805module.exports = Menu;
     5806
     5807
     5808/***/ }),
     5809/* 66 */
     5810/***/ (function(module, exports) {
     5811
     5812/**
     5813 * wp.media.view.RouterItem
     5814 *
     5815 * @memberOf wp.media.view
     5816 *
     5817 * @class
     5818 * @augments wp.media.view.MenuItem
     5819 * @augments wp.media.View
     5820 * @augments wp.Backbone.View
     5821 * @augments Backbone.View
     5822 */
     5823var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{
     5824    /**
     5825     * On click handler to activate the content region's corresponding mode.
     5826     */
     5827    click: function() {
     5828        var contentMode = this.options.contentMode;
     5829        if ( contentMode ) {
     5830            this.controller.content.mode( contentMode );
     5831        }
     5832    }
     5833});
     5834
     5835module.exports = RouterItem;
     5836
     5837
     5838/***/ }),
     5839/* 67 */
     5840/***/ (function(module, exports) {
     5841
     5842var Menu = wp.media.view.Menu,
     5843    Router;
     5844
     5845/**
     5846 * wp.media.view.Router
     5847 *
     5848 * @memberOf wp.media.view
     5849 *
     5850 * @class
     5851 * @augments wp.media.view.Menu
     5852 * @augments wp.media.view.PriorityList
     5853 * @augments wp.media.View
     5854 * @augments wp.Backbone.View
     5855 * @augments Backbone.View
     5856 */
     5857Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{
     5858    tagName:   'div',
     5859    className: 'media-router',
     5860    property:  'contentMode',
     5861    ItemView:  wp.media.view.RouterItem,
     5862    region:    'router',
     5863
     5864    initialize: function() {
     5865        this.controller.on( 'content:render', this.update, this );
     5866        // Call 'initialize' directly on the parent class.
     5867        Menu.prototype.initialize.apply( this, arguments );
     5868    },
     5869
     5870    update: function() {
     5871        var mode = this.controller.content.mode();
     5872        if ( mode ) {
     5873            this.select( mode );
     5874        }
     5875    }
     5876});
     5877
     5878module.exports = Router;
     5879
     5880
     5881/***/ }),
     5882/* 68 */
     5883/***/ (function(module, exports) {
     5884
     5885/**
     5886 * wp.media.view.Sidebar
     5887 *
     5888 * @memberOf wp.media.view
     5889 *
     5890 * @class
     5891 * @augments wp.media.view.PriorityList
     5892 * @augments wp.media.View
     5893 * @augments wp.Backbone.View
     5894 * @augments Backbone.View
     5895 */
     5896var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{
     5897    className: 'media-sidebar'
     5898});
     5899
     5900module.exports = Sidebar;
     5901
     5902
     5903/***/ }),
     5904/* 69 */
     5905/***/ (function(module, exports) {
     5906
     5907var View = wp.media.View,
     5908    $ = jQuery,
     5909    Attachment;
     5910
     5911/**
     5912 * wp.media.view.Attachment
     5913 *
     5914 * @memberOf wp.media.view
     5915 *
     5916 * @class
     5917 * @augments wp.media.View
     5918 * @augments wp.Backbone.View
     5919 * @augments Backbone.View
     5920 */
     5921Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{
     5922    tagName:   'li',
     5923    className: 'attachment',
     5924    template:  wp.template('attachment'),
     5925
     5926    attributes: function() {
     5927        return {
     5928            'tabIndex':     0,
     5929            'role':         'checkbox',
     5930            'aria-label':   this.model.get( 'title' ),
     5931            'aria-checked': false,
     5932            'data-id':      this.model.get( 'id' )
     5933        };
     5934    },
     5935
     5936    events: {
     5937        'click':                          'toggleSelectionHandler',
     5938        'change [data-setting]':          'updateSetting',
     5939        'change [data-setting] input':    'updateSetting',
     5940        'change [data-setting] select':   'updateSetting',
     5941        'change [data-setting] textarea': 'updateSetting',
     5942        'click .attachment-close':        'removeFromLibrary',
     5943        'click .check':                   'checkClickHandler',
     5944        'keydown':                        'toggleSelectionHandler'
     5945    },
     5946
     5947    buttons: {},
     5948
     5949    initialize: function() {
     5950        var selection = this.options.selection,
     5951            options = _.defaults( this.options, {
     5952                rerenderOnModelChange: true
     5953            } );
     5954
     5955        if ( options.rerenderOnModelChange ) {
     5956            this.listenTo( this.model, 'change', this.render );
     5957        } else {
     5958            this.listenTo( this.model, 'change:percent', this.progress );
     5959        }
     5960        this.listenTo( this.model, 'change:title', this._syncTitle );
     5961        this.listenTo( this.model, 'change:caption', this._syncCaption );
     5962        this.listenTo( this.model, 'change:artist', this._syncArtist );
     5963        this.listenTo( this.model, 'change:album', this._syncAlbum );
     5964
     5965        // Update the selection.
     5966        this.listenTo( this.model, 'add', this.select );
     5967        this.listenTo( this.model, 'remove', this.deselect );
     5968        if ( selection ) {
     5969            selection.on( 'reset', this.updateSelect, this );
     5970            // Update the model's details view.
     5971            this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
     5972            this.details( this.model, this.controller.state().get('selection') );
     5973        }
     5974
     5975        this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
     5976    },
     5977    /**
     5978     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     5979     */
     5980    dispose: function() {
     5981        var selection = this.options.selection;
     5982
     5983        // Make sure all settings are saved before removing the view.
     5984        this.updateAll();
     5985
     5986        if ( selection ) {
     5987            selection.off( null, null, this );
     5988        }
     5989        /**
     5990         * call 'dispose' directly on the parent class
     5991         */
     5992        View.prototype.dispose.apply( this, arguments );
     5993        return this;
     5994    },
     5995    /**
     5996     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     5997     */
     5998    render: function() {
     5999        var options = _.defaults( this.model.toJSON(), {
     6000                orientation:   'landscape',
     6001                uploading:     false,
     6002                type:          '',
     6003                subtype:       '',
     6004                icon:          '',
     6005                filename:      '',
     6006                caption:       '',
     6007                title:         '',
     6008                dateFormatted: '',
     6009                width:         '',
     6010                height:        '',
     6011                compat:        false,
     6012                alt:           '',
     6013                description:   ''
     6014            }, this.options );
     6015
     6016        options.buttons  = this.buttons;
     6017        options.describe = this.controller.state().get('describe');
     6018
     6019        if ( 'image' === options.type ) {
     6020            options.size = this.imageSize();
     6021        }
     6022
     6023        options.can = {};
     6024        if ( options.nonces ) {
     6025            options.can.remove = !! options.nonces['delete'];
     6026            options.can.save = !! options.nonces.update;
     6027        }
     6028
     6029        if ( this.controller.state().get('allowLocalEdits') ) {
     6030            options.allowLocalEdits = true;
     6031        }
     6032
     6033        if ( options.uploading && ! options.percent ) {
     6034            options.percent = 0;
     6035        }
     6036
     6037        this.views.detach();
     6038        this.$el.html( this.template( options ) );
     6039
     6040        this.$el.toggleClass( 'uploading', options.uploading );
     6041
     6042        if ( options.uploading ) {
     6043            this.$bar = this.$('.media-progress-bar div');
     6044        } else {
     6045            delete this.$bar;
     6046        }
     6047
     6048        // Check if the model is selected.
     6049        this.updateSelect();
     6050
     6051        // Update the save status.
     6052        this.updateSave();
     6053
     6054        this.views.render();
     6055
     6056        return this;
     6057    },
     6058
     6059    progress: function() {
     6060        if ( this.$bar && this.$bar.length ) {
     6061            this.$bar.width( this.model.get('percent') + '%' );
     6062        }
     6063    },
     6064
     6065    /**
     6066     * @param {Object} event
     6067     */
     6068    toggleSelectionHandler: function( event ) {
     6069        var method;
     6070
     6071        // Don't do anything inside inputs and on the attachment check and remove buttons.
     6072        if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
     6073            return;
     6074        }
     6075
     6076        // Catch arrow events
     6077        if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     6078            this.controller.trigger( 'attachment:keydown:arrow', event );
     6079            return;
     6080        }
     6081
     6082        // Catch enter and space events
     6083        if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     6084            return;
     6085        }
     6086
     6087        event.preventDefault();
     6088
     6089        // In the grid view, bubble up an edit:attachment event to the controller.
     6090        if ( this.controller.isModeActive( 'grid' ) ) {
     6091            if ( this.controller.isModeActive( 'edit' ) ) {
     6092                // Pass the current target to restore focus when closing
     6093                this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
     6094                return;
     6095            }
     6096
     6097            if ( this.controller.isModeActive( 'select' ) ) {
     6098                method = 'toggle';
     6099            }
     6100        }
     6101
     6102        if ( event.shiftKey ) {
     6103            method = 'between';
     6104        } else if ( event.ctrlKey || event.metaKey ) {
     6105            method = 'toggle';
     6106        }
     6107
     6108        this.toggleSelection({
     6109            method: method
     6110        });
     6111
     6112        this.controller.trigger( 'selection:toggle' );
     6113    },
     6114    /**
     6115     * @param {Object} options
     6116     */
     6117    toggleSelection: function( options ) {
     6118        var collection = this.collection,
     6119            selection = this.options.selection,
     6120            model = this.model,
     6121            method = options && options.method,
     6122            single, models, singleIndex, modelIndex;
     6123
     6124        if ( ! selection ) {
     6125            return;
     6126        }
     6127
     6128        single = selection.single();
     6129        method = _.isUndefined( method ) ? selection.multiple : method;
     6130
     6131        // If the `method` is set to `between`, select all models that
     6132        // exist between the current and the selected model.
     6133        if ( 'between' === method && single && selection.multiple ) {
     6134            // If the models are the same, short-circuit.
     6135            if ( single === model ) {
     6136                return;
     6137            }
     6138
     6139            singleIndex = collection.indexOf( single );
     6140            modelIndex  = collection.indexOf( this.model );
     6141
     6142            if ( singleIndex < modelIndex ) {
     6143                models = collection.models.slice( singleIndex, modelIndex + 1 );
     6144            } else {
     6145                models = collection.models.slice( modelIndex, singleIndex + 1 );
     6146            }
     6147
     6148            selection.add( models );
     6149            selection.single( model );
     6150            return;
     6151
     6152        // If the `method` is set to `toggle`, just flip the selection
     6153        // status, regardless of whether the model is the single model.
     6154        } else if ( 'toggle' === method ) {
     6155            selection[ this.selected() ? 'remove' : 'add' ]( model );
     6156            selection.single( model );
     6157            return;
     6158        } else if ( 'add' === method ) {
     6159            selection.add( model );
     6160            selection.single( model );
     6161            return;
     6162        }
     6163
     6164        // Fixes bug that loses focus when selecting a featured image
     6165        if ( ! method ) {
     6166            method = 'add';
     6167        }
     6168
     6169        if ( method !== 'add' ) {
     6170            method = 'reset';
     6171        }
     6172
     6173        if ( this.selected() ) {
     6174            // If the model is the single model, remove it.
     6175            // If it is not the same as the single model,
     6176            // it now becomes the single model.
     6177            selection[ single === model ? 'remove' : 'single' ]( model );
     6178        } else {
     6179            // If the model is not selected, run the `method` on the
     6180            // selection. By default, we `reset` the selection, but the
     6181            // `method` can be set to `add` the model to the selection.
     6182            selection[ method ]( model );
     6183            selection.single( model );
     6184        }
     6185    },
     6186
     6187    updateSelect: function() {
     6188        this[ this.selected() ? 'select' : 'deselect' ]();
     6189    },
     6190    /**
     6191     * @returns {unresolved|Boolean}
     6192     */
     6193    selected: function() {
     6194        var selection = this.options.selection;
     6195        if ( selection ) {
     6196            return !! selection.get( this.model.cid );
     6197        }
     6198    },
     6199    /**
     6200     * @param {Backbone.Model} model
     6201     * @param {Backbone.Collection} collection
     6202     */
     6203    select: function( model, collection ) {
     6204        var selection = this.options.selection,
     6205            controller = this.controller;
     6206
     6207        // Check if a selection exists and if it's the collection provided.
     6208        // If they're not the same collection, bail; we're in another
     6209        // selection's event loop.
     6210        if ( ! selection || ( collection && collection !== selection ) ) {
     6211            return;
     6212        }
     6213
     6214        // Bail if the model is already selected.
     6215        if ( this.$el.hasClass( 'selected' ) ) {
     6216            return;
     6217        }
     6218
     6219        // Add 'selected' class to model, set aria-checked to true.
     6220        this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
     6221        //  Make the checkbox tabable, except in media grid (bulk select mode).
     6222        if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
     6223            this.$( '.check' ).attr( 'tabindex', '0' );
     6224        }
     6225    },
     6226    /**
     6227     * @param {Backbone.Model} model
     6228     * @param {Backbone.Collection} collection
     6229     */
     6230    deselect: function( model, collection ) {
     6231        var selection = this.options.selection;
     6232
     6233        // Check if a selection exists and if it's the collection provided.
     6234        // If they're not the same collection, bail; we're in another
     6235        // selection's event loop.
     6236        if ( ! selection || ( collection && collection !== selection ) ) {
     6237            return;
     6238        }
     6239        this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
     6240            .find( '.check' ).attr( 'tabindex', '-1' );
     6241    },
     6242    /**
     6243     * @param {Backbone.Model} model
     6244     * @param {Backbone.Collection} collection
     6245     */
     6246    details: function( model, collection ) {
     6247        var selection = this.options.selection,
     6248            details;
     6249
     6250        if ( selection !== collection ) {
     6251            return;
     6252        }
     6253
     6254        details = selection.single();
     6255        this.$el.toggleClass( 'details', details === this.model );
     6256    },
     6257    /**
     6258     * @param {string} size
     6259     * @returns {Object}
     6260     */
     6261    imageSize: function( size ) {
     6262        var sizes = this.model.get('sizes'), matched = false;
     6263
     6264        size = size || 'medium';
     6265
     6266        // Use the provided image size if possible.
     6267        if ( sizes ) {
     6268            if ( sizes[ size ] ) {
     6269                matched = sizes[ size ];
     6270            } else if ( sizes.large ) {
     6271                matched = sizes.large;
     6272            } else if ( sizes.thumbnail ) {
     6273                matched = sizes.thumbnail;
     6274            } else if ( sizes.full ) {
     6275                matched = sizes.full;
     6276            }
     6277
     6278            if ( matched ) {
     6279                return _.clone( matched );
     6280            }
     6281        }
     6282
     6283        return {
     6284            url:         this.model.get('url'),
     6285            width:       this.model.get('width'),
     6286            height:      this.model.get('height'),
     6287            orientation: this.model.get('orientation')
     6288        };
     6289    },
     6290    /**
     6291     * @param {Object} event
     6292     */
     6293    updateSetting: function( event ) {
     6294        var $setting = $( event.target ).closest('[data-setting]'),
     6295            setting, value;
     6296
     6297        if ( ! $setting.length ) {
     6298            return;
     6299        }
     6300
     6301        setting = $setting.data('setting');
     6302        value   = event.target.value;
     6303
     6304        if ( this.model.get( setting ) !== value ) {
     6305            this.save( setting, value );
     6306        }
     6307    },
     6308
     6309    /**
     6310     * Pass all the arguments to the model's save method.
     6311     *
     6312     * Records the aggregate status of all save requests and updates the
     6313     * view's classes accordingly.
     6314     */
     6315    save: function() {
     6316        var view = this,
     6317            save = this._save = this._save || { status: 'ready' },
     6318            request = this.model.save.apply( this.model, arguments ),
     6319            requests = save.requests ? $.when( request, save.requests ) : request;
     6320
     6321        // If we're waiting to remove 'Saved.', stop.
     6322        if ( save.savedTimer ) {
     6323            clearTimeout( save.savedTimer );
     6324        }
     6325
     6326        this.updateSave('waiting');
     6327        save.requests = requests;
     6328        requests.always( function() {
     6329            // If we've performed another request since this one, bail.
     6330            if ( save.requests !== requests ) {
     6331                return;
     6332            }
     6333
     6334            view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
     6335            save.savedTimer = setTimeout( function() {
     6336                view.updateSave('ready');
     6337                delete save.savedTimer;
     6338            }, 2000 );
     6339        });
     6340    },
     6341    /**
     6342     * @param {string} status
     6343     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6344     */
     6345    updateSave: function( status ) {
     6346        var save = this._save = this._save || { status: 'ready' };
     6347
     6348        if ( status && status !== save.status ) {
     6349            this.$el.removeClass( 'save-' + save.status );
     6350            save.status = status;
     6351        }
     6352
     6353        this.$el.addClass( 'save-' + save.status );
     6354        return this;
     6355    },
     6356
     6357    updateAll: function() {
     6358        var $settings = this.$('[data-setting]'),
     6359            model = this.model,
     6360            changed;
     6361
     6362        changed = _.chain( $settings ).map( function( el ) {
     6363            var $input = $('input, textarea, select, [value]', el ),
     6364                setting, value;
     6365
     6366            if ( ! $input.length ) {
     6367                return;
     6368            }
     6369
     6370            setting = $(el).data('setting');
     6371            value = $input.val();
     6372
     6373            // Record the value if it changed.
     6374            if ( model.get( setting ) !== value ) {
     6375                return [ setting, value ];
     6376            }
     6377        }).compact().object().value();
     6378
     6379        if ( ! _.isEmpty( changed ) ) {
     6380            model.save( changed );
     6381        }
     6382    },
     6383    /**
     6384     * @param {Object} event
     6385     */
     6386    removeFromLibrary: function( event ) {
     6387        // Catch enter and space events
     6388        if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     6389            return;
     6390        }
     6391
     6392        // Stop propagation so the model isn't selected.
     6393        event.stopPropagation();
     6394
     6395        this.collection.remove( this.model );
     6396    },
     6397
     6398    /**
     6399     * Add the model if it isn't in the selection, if it is in the selection,
     6400     * remove it.
     6401     *
     6402     * @param  {[type]} event [description]
     6403     * @return {[type]}       [description]
     6404     */
     6405    checkClickHandler: function ( event ) {
     6406        var selection = this.options.selection;
     6407        if ( ! selection ) {
     6408            return;
     6409        }
     6410        event.stopPropagation();
     6411        if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
     6412            selection.remove( this.model );
     6413            // Move focus back to the attachment tile (from the check).
     6414            this.$el.focus();
     6415        } else {
     6416            selection.add( this.model );
     6417        }
     6418    }
     6419});
     6420
     6421// Ensure settings remain in sync between attachment views.
     6422_.each({
     6423    caption: '_syncCaption',
     6424    title:   '_syncTitle',
     6425    artist:  '_syncArtist',
     6426    album:   '_syncAlbum'
     6427}, function( method, setting ) {
     6428    /**
     6429     * @function _syncCaption
     6430     * @memberOf wp.media.view.Attachment
     6431     * @instance
     6432     *
     6433     * @param {Backbone.Model} model
     6434     * @param {string} value
     6435     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6436     */
     6437    /**
     6438     * @function _syncTitle
     6439     * @memberOf wp.media.view.Attachment
     6440     * @instance
     6441     *
     6442     * @param {Backbone.Model} model
     6443     * @param {string} value
     6444     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6445     */
     6446    /**
     6447     * @function _syncArtist
     6448     * @memberOf wp.media.view.Attachment
     6449     * @instance
     6450     *
     6451     * @param {Backbone.Model} model
     6452     * @param {string} value
     6453     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6454     */
     6455    /**
     6456     * @function _syncAlbum
     6457     * @memberOf wp.media.view.Attachment
     6458     * @instance
     6459     *
     6460     * @param {Backbone.Model} model
     6461     * @param {string} value
     6462     * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6463     */
     6464    Attachment.prototype[ method ] = function( model, value ) {
     6465        var $setting = this.$('[data-setting="' + setting + '"]');
     6466
     6467        if ( ! $setting.length ) {
     6468            return this;
     6469        }
     6470
     6471        // If the updated value is in sync with the value in the DOM, there
     6472        // is no need to re-render. If we're currently editing the value,
     6473        // it will automatically be in sync, suppressing the re-render for
     6474        // the view we're editing, while updating any others.
     6475        if ( value === $setting.find('input, textarea, select, [value]').val() ) {
     6476            return this;
     6477        }
     6478
     6479        return this.render();
     6480    };
     6481});
     6482
     6483module.exports = Attachment;
     6484
     6485
     6486/***/ }),
     6487/* 70 */
     6488/***/ (function(module, exports) {
     6489
     6490/**
     6491 * wp.media.view.Attachment.Library
     6492 *
     6493 * @memberOf wp.media.view.Attachment
     6494 *
     6495 * @class
     6496 * @augments wp.media.view.Attachment
     6497 * @augments wp.media.View
     6498 * @augments wp.Backbone.View
     6499 * @augments Backbone.View
     6500 */
     6501var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{
     6502    buttons: {
     6503        check: true
     6504    }
     6505});
     6506
     6507module.exports = Library;
     6508
     6509
     6510/***/ }),
     6511/* 71 */
     6512/***/ (function(module, exports) {
     6513
     6514/**
     6515 * wp.media.view.Attachment.EditLibrary
     6516 *
     6517 * @memberOf wp.media.view.Attachment
     6518 *
     6519 * @class
     6520 * @augments wp.media.view.Attachment
     6521 * @augments wp.media.View
     6522 * @augments wp.Backbone.View
     6523 * @augments Backbone.View
     6524 */
     6525var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{
     6526    buttons: {
     6527        close: true
     6528    }
     6529});
     6530
     6531module.exports = EditLibrary;
     6532
     6533
     6534/***/ }),
     6535/* 72 */
     6536/***/ (function(module, exports) {
     6537
     6538var View = wp.media.View,
     6539    $ = jQuery,
     6540    Attachments;
     6541
     6542/**
     6543 * wp.media.view.Attachments
     6544 *
     6545 * @memberOf wp.media.view
     6546 *
     6547 * @class
     6548 * @augments wp.media.View
     6549 * @augments wp.Backbone.View
     6550 * @augments Backbone.View
     6551 */
     6552Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
     6553    tagName:   'ul',
     6554    className: 'attachments',
     6555
     6556    attributes: {
     6557        tabIndex: -1
     6558    },
     6559
     6560    initialize: function() {
     6561        this.el.id = _.uniqueId('__attachments-view-');
     6562
     6563        _.defaults( this.options, {
     6564            refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
     6565            refreshThreshold:   3,
     6566            AttachmentView:     wp.media.view.Attachment,
     6567            sortable:           false,
     6568            resize:             true,
     6569            idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
     6570        });
     6571
     6572        this._viewsByCid = {};
     6573        this.$window = $( window );
     6574        this.resizeEvent = 'resize.media-modal-columns';
     6575
     6576        this.collection.on( 'add', function( attachment ) {
     6577            this.views.add( this.createAttachmentView( attachment ), {
     6578                at: this.collection.indexOf( attachment )
     6579            });
     6580        }, this );
     6581
     6582        this.collection.on( 'remove', function( attachment ) {
     6583            var view = this._viewsByCid[ attachment.cid ];
     6584            delete this._viewsByCid[ attachment.cid ];
     6585
     6586            if ( view ) {
     6587                view.remove();
     6588            }
     6589        }, this );
     6590
     6591        this.collection.on( 'reset', this.render, this );
     6592
     6593        this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
     6594
     6595        // Throttle the scroll handler and bind this.
     6596        this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
     6597
     6598        this.options.scrollElement = this.options.scrollElement || this.el;
     6599        $( this.options.scrollElement ).on( 'scroll', this.scroll );
     6600
     6601        this.initSortable();
     6602
     6603        _.bindAll( this, 'setColumns' );
     6604
     6605        if ( this.options.resize ) {
     6606            this.on( 'ready', this.bindEvents );
     6607            this.controller.on( 'open', this.setColumns );
     6608
     6609            // Call this.setColumns() after this view has been rendered in the DOM so
     6610            // attachments get proper width applied.
     6611            _.defer( this.setColumns, this );
     6612        }
     6613    },
     6614
     6615    bindEvents: function() {
     6616        this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
     6617    },
     6618
     6619    attachmentFocus: function() {
     6620        this.$( 'li:first' ).focus();
     6621    },
     6622
     6623    restoreFocus: function() {
     6624        this.$( 'li.selected:first' ).focus();
     6625    },
     6626
     6627    arrowEvent: function( event ) {
     6628        var attachments = this.$el.children( 'li' ),
     6629            perRow = this.columns,
     6630            index = attachments.filter( ':focus' ).index(),
     6631            row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
     6632
     6633        if ( index === -1 ) {
     6634            return;
     6635        }
     6636
     6637        // Left arrow
     6638        if ( 37 === event.keyCode ) {
     6639            if ( 0 === index ) {
     6640                return;
     6641            }
     6642            attachments.eq( index - 1 ).focus();
     6643        }
     6644
     6645        // Up arrow
     6646        if ( 38 === event.keyCode ) {
     6647            if ( 1 === row ) {
     6648                return;
     6649            }
     6650            attachments.eq( index - perRow ).focus();
     6651        }
     6652
     6653        // Right arrow
     6654        if ( 39 === event.keyCode ) {
     6655            if ( attachments.length === index ) {
     6656                return;
     6657            }
     6658            attachments.eq( index + 1 ).focus();
     6659        }
     6660
     6661        // Down arrow
     6662        if ( 40 === event.keyCode ) {
     6663            if ( Math.ceil( attachments.length / perRow ) === row ) {
     6664                return;
     6665            }
     6666            attachments.eq( index + perRow ).focus();
     6667        }
     6668    },
     6669
     6670    dispose: function() {
     6671        this.collection.props.off( null, null, this );
     6672        if ( this.options.resize ) {
     6673            this.$window.off( this.resizeEvent );
     6674        }
     6675
     6676        /**
     6677         * call 'dispose' directly on the parent class
     6678         */
     6679        View.prototype.dispose.apply( this, arguments );
     6680    },
     6681
     6682    setColumns: function() {
     6683        var prev = this.columns,
     6684            width = this.$el.width();
     6685
     6686        if ( width ) {
     6687            this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
     6688
     6689            if ( ! prev || prev !== this.columns ) {
     6690                this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
     6691            }
     6692        }
     6693    },
     6694
     6695    initSortable: function() {
     6696        var collection = this.collection;
     6697
     6698        if ( ! this.options.sortable || ! $.fn.sortable ) {
     6699            return;
     6700        }
     6701
     6702        this.$el.sortable( _.extend({
     6703            // If the `collection` has a `comparator`, disable sorting.
     6704            disabled: !! collection.comparator,
     6705
     6706            // Change the position of the attachment as soon as the
     6707            // mouse pointer overlaps a thumbnail.
     6708            tolerance: 'pointer',
     6709
     6710            // Record the initial `index` of the dragged model.
     6711            start: function( event, ui ) {
     6712                ui.item.data('sortableIndexStart', ui.item.index());
     6713            },
     6714
     6715            // Update the model's index in the collection.
     6716            // Do so silently, as the view is already accurate.
     6717            update: function( event, ui ) {
     6718                var model = collection.at( ui.item.data('sortableIndexStart') ),
     6719                    comparator = collection.comparator;
     6720
     6721                // Temporarily disable the comparator to prevent `add`
     6722                // from re-sorting.
     6723                delete collection.comparator;
     6724
     6725                // Silently shift the model to its new index.
     6726                collection.remove( model, {
     6727                    silent: true
     6728                });
     6729                collection.add( model, {
     6730                    silent: true,
     6731                    at:     ui.item.index()
     6732                });
     6733
     6734                // Restore the comparator.
     6735                collection.comparator = comparator;
     6736
     6737                // Fire the `reset` event to ensure other collections sync.
     6738                collection.trigger( 'reset', collection );
     6739
     6740                // If the collection is sorted by menu order,
     6741                // update the menu order.
     6742                collection.saveMenuOrder();
     6743            }
     6744        }, this.options.sortable ) );
     6745
     6746        // If the `orderby` property is changed on the `collection`,
     6747        // check to see if we have a `comparator`. If so, disable sorting.
     6748        collection.props.on( 'change:orderby', function() {
     6749            this.$el.sortable( 'option', 'disabled', !! collection.comparator );
     6750        }, this );
     6751
     6752        this.collection.props.on( 'change:orderby', this.refreshSortable, this );
     6753        this.refreshSortable();
     6754    },
     6755
     6756    refreshSortable: function() {
     6757        if ( ! this.options.sortable || ! $.fn.sortable ) {
     6758            return;
     6759        }
     6760
     6761        // If the `collection` has a `comparator`, disable sorting.
     6762        var collection = this.collection,
     6763            orderby = collection.props.get('orderby'),
     6764            enabled = 'menuOrder' === orderby || ! collection.comparator;
     6765
     6766        this.$el.sortable( 'option', 'disabled', ! enabled );
     6767    },
     6768
     6769    /**
     6770     * @param {wp.media.model.Attachment} attachment
     6771     * @returns {wp.media.View}
     6772     */
     6773    createAttachmentView: function( attachment ) {
     6774        var view = new this.options.AttachmentView({
     6775            controller:           this.controller,
     6776            model:                attachment,
     6777            collection:           this.collection,
     6778            selection:            this.options.selection
     6779        });
     6780
     6781        return this._viewsByCid[ attachment.cid ] = view;
     6782    },
     6783
     6784    prepare: function() {
     6785        // Create all of the Attachment views, and replace
     6786        // the list in a single DOM operation.
     6787        if ( this.collection.length ) {
     6788            this.views.set( this.collection.map( this.createAttachmentView, this ) );
     6789
     6790        // If there are no elements, clear the views and load some.
     6791        } else {
     6792            this.views.unset();
     6793            this.collection.more().done( this.scroll );
     6794        }
     6795    },
     6796
     6797    ready: function() {
     6798        // Trigger the scroll event to check if we're within the
     6799        // threshold to query for additional attachments.
     6800        this.scroll();
     6801    },
     6802
     6803    scroll: function() {
     6804        var view = this,
     6805            el = this.options.scrollElement,
     6806            scrollTop = el.scrollTop,
     6807            toolbar;
     6808
     6809        // The scroll event occurs on the document, but the element
     6810        // that should be checked is the document body.
     6811        if ( el === document ) {
     6812            el = document.body;
     6813            scrollTop = $(document).scrollTop();
     6814        }
     6815
     6816        if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
     6817            return;
     6818        }
     6819
     6820        toolbar = this.views.parent.toolbar;
     6821
     6822        // Show the spinner only if we are close to the bottom.
     6823        if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
     6824            toolbar.get('spinner').show();
     6825        }
     6826
     6827        if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
     6828            this.collection.more().done(function() {
     6829                view.scroll();
     6830                toolbar.get('spinner').hide();
     6831            });
     6832        }
     6833    }
     6834});
     6835
     6836module.exports = Attachments;
     6837
     6838
     6839/***/ }),
     6840/* 73 */
     6841/***/ (function(module, exports) {
     6842
     6843var l10n = wp.media.view.l10n,
     6844    Search;
     6845
     6846/**
     6847 * wp.media.view.Search
     6848 *
     6849 * @memberOf wp.media.view
     6850 *
     6851 * @class
     6852 * @augments wp.media.View
     6853 * @augments wp.Backbone.View
     6854 * @augments Backbone.View
     6855 */
     6856Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{
     6857    tagName:   'input',
     6858    className: 'search',
     6859    id:        'media-search-input',
     6860
     6861    attributes: {
     6862        type:        'search',
     6863        placeholder: l10n.searchMediaPlaceholder
     6864    },
     6865
     6866    events: {
     6867        'input':  'search',
     6868        'keyup':  'search'
     6869    },
     6870
     6871    /**
     6872     * @returns {wp.media.view.Search} Returns itself to allow chaining
     6873     */
     6874    render: function() {
     6875        this.el.value = this.model.escape('search');
     6876        return this;
     6877    },
     6878
     6879    search: _.debounce( function( event ) {
     6880        if ( event.target.value ) {
     6881            this.model.set( 'search', event.target.value );
     6882        } else {
     6883            this.model.unset('search');
     6884        }
     6885    }, 300 )
     6886});
     6887
     6888module.exports = Search;
     6889
     6890
     6891/***/ }),
     6892/* 74 */
     6893/***/ (function(module, exports) {
     6894
    24536895var $ = jQuery,
    24546896    AttachmentFilters;
     
    25296971module.exports = AttachmentFilters;
    25306972
    2531 },{}],22:[function(require,module,exports){
    2532 var l10n = wp.media.view.l10n,
    2533     All;
    2534 
    2535 /**
    2536  * wp.media.view.AttachmentFilters.All
    2537  *
    2538  * @memberOf wp.media.view.AttachmentFilters
    2539  *
    2540  * @class
    2541  * @augments wp.media.view.AttachmentFilters
    2542  * @augments wp.media.View
    2543  * @augments wp.Backbone.View
    2544  * @augments Backbone.View
    2545  */
    2546 All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{
    2547     createFilters: function() {
    2548         var filters = {};
    2549 
    2550         _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
    2551             filters[ key ] = {
    2552                 text: text,
    2553                 props: {
    2554                     status:  null,
    2555                     type:    key,
    2556                     uploadedTo: null,
    2557                     orderby: 'date',
    2558                     order:   'DESC'
    2559                 }
    2560             };
    2561         });
    2562 
    2563         filters.all = {
    2564             text:  l10n.allMediaItems,
    2565             props: {
    2566                 status:  null,
    2567                 type:    null,
    2568                 uploadedTo: null,
    2569                 orderby: 'date',
    2570                 order:   'DESC'
    2571             },
    2572             priority: 10
    2573         };
    2574 
    2575         if ( wp.media.view.settings.post.id ) {
    2576             filters.uploaded = {
    2577                 text:  l10n.uploadedToThisPost,
    2578                 props: {
    2579                     status:  null,
    2580                     type:    null,
    2581                     uploadedTo: wp.media.view.settings.post.id,
    2582                     orderby: 'menuOrder',
    2583                     order:   'ASC'
    2584                 },
    2585                 priority: 20
    2586             };
    2587         }
    2588 
    2589         filters.unattached = {
    2590             text:  l10n.unattached,
    2591             props: {
    2592                 status:     null,
    2593                 uploadedTo: 0,
    2594                 type:       null,
    2595                 orderby:    'menuOrder',
    2596                 order:      'ASC'
    2597             },
    2598             priority: 50
    2599         };
    2600 
    2601         if ( wp.media.view.settings.mediaTrash &&
    2602             this.controller.isModeActive( 'grid' ) ) {
    2603 
    2604             filters.trash = {
    2605                 text:  l10n.trash,
    2606                 props: {
    2607                     uploadedTo: null,
    2608                     status:     'trash',
    2609                     type:       null,
    2610                     orderby:    'date',
    2611                     order:      'DESC'
    2612                 },
    2613                 priority: 50
    2614             };
    2615         }
    2616 
    2617         this.filters = filters;
    2618     }
    2619 });
    2620 
    2621 module.exports = All;
    2622 
    2623 },{}],23:[function(require,module,exports){
     6973
     6974/***/ }),
     6975/* 75 */
     6976/***/ (function(module, exports) {
     6977
    26246978var l10n = wp.media.view.l10n,
    26256979    DateFilter;
     
    26647018module.exports = DateFilter;
    26657019
    2666 },{}],24:[function(require,module,exports){
     7020
     7021/***/ }),
     7022/* 76 */
     7023/***/ (function(module, exports) {
     7024
    26677025var l10n = wp.media.view.l10n,
    26687026    Uploaded;
     
    27257083module.exports = Uploaded;
    27267084
    2727 },{}],25:[function(require,module,exports){
    2728 var View = wp.media.View,
    2729     $ = jQuery,
    2730     Attachment;
     7085
     7086/***/ }),
     7087/* 77 */
     7088/***/ (function(module, exports) {
     7089
     7090var l10n = wp.media.view.l10n,
     7091    All;
    27317092
    27327093/**
    2733  * wp.media.view.Attachment
    2734  *
    2735  * @memberOf wp.media.view
     7094 * wp.media.view.AttachmentFilters.All
     7095 *
     7096 * @memberOf wp.media.view.AttachmentFilters
    27367097 *
    27377098 * @class
     7099 * @augments wp.media.view.AttachmentFilters
    27387100 * @augments wp.media.View
    27397101 * @augments wp.Backbone.View
    27407102 * @augments Backbone.View
    27417103 */
    2742 Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{
    2743     tagName:   'li',
    2744     className: 'attachment',
    2745     template:  wp.template('attachment'),
    2746 
    2747     attributes: function() {
    2748         return {
    2749             'tabIndex':     0,
    2750             'role':         'checkbox',
    2751             'aria-label':   this.model.get( 'title' ),
    2752             'aria-checked': false,
    2753             'data-id':      this.model.get( 'id' )
     7104All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{
     7105    createFilters: function() {
     7106        var filters = {};
     7107
     7108        _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
     7109            filters[ key ] = {
     7110                text: text,
     7111                props: {
     7112                    status:  null,
     7113                    type:    key,
     7114                    uploadedTo: null,
     7115                    orderby: 'date',
     7116                    order:   'DESC'
     7117                }
     7118            };
     7119        });
     7120
     7121        filters.all = {
     7122            text:  l10n.allMediaItems,
     7123            props: {
     7124                status:  null,
     7125                type:    null,
     7126                uploadedTo: null,
     7127                orderby: 'date',
     7128                order:   'DESC'
     7129            },
     7130            priority: 10
    27547131        };
    2755     },
    2756 
    2757     events: {
    2758         'click':                          'toggleSelectionHandler',
    2759         'change [data-setting]':          'updateSetting',
    2760         'change [data-setting] input':    'updateSetting',
    2761         'change [data-setting] select':   'updateSetting',
    2762         'change [data-setting] textarea': 'updateSetting',
    2763         'click .attachment-close':        'removeFromLibrary',
    2764         'click .check':                   'checkClickHandler',
    2765         'keydown':                        'toggleSelectionHandler'
    2766     },
    2767 
    2768     buttons: {},
    2769 
    2770     initialize: function() {
    2771         var selection = this.options.selection,
    2772             options = _.defaults( this.options, {
    2773                 rerenderOnModelChange: true
    2774             } );
    2775 
    2776         if ( options.rerenderOnModelChange ) {
    2777             this.listenTo( this.model, 'change', this.render );
    2778         } else {
    2779             this.listenTo( this.model, 'change:percent', this.progress );
    2780         }
    2781         this.listenTo( this.model, 'change:title', this._syncTitle );
    2782         this.listenTo( this.model, 'change:caption', this._syncCaption );
    2783         this.listenTo( this.model, 'change:artist', this._syncArtist );
    2784         this.listenTo( this.model, 'change:album', this._syncAlbum );
    2785 
    2786         // Update the selection.
    2787         this.listenTo( this.model, 'add', this.select );
    2788         this.listenTo( this.model, 'remove', this.deselect );
    2789         if ( selection ) {
    2790             selection.on( 'reset', this.updateSelect, this );
    2791             // Update the model's details view.
    2792             this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
    2793             this.details( this.model, this.controller.state().get('selection') );
    2794         }
    2795 
    2796         this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
    2797     },
    2798     /**
    2799      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    2800      */
    2801     dispose: function() {
    2802         var selection = this.options.selection;
    2803 
    2804         // Make sure all settings are saved before removing the view.
    2805         this.updateAll();
    2806 
    2807         if ( selection ) {
    2808             selection.off( null, null, this );
    2809         }
    2810         /**
    2811          * call 'dispose' directly on the parent class
    2812          */
    2813         View.prototype.dispose.apply( this, arguments );
    2814         return this;
    2815     },
    2816     /**
    2817      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    2818      */
    2819     render: function() {
    2820         var options = _.defaults( this.model.toJSON(), {
    2821                 orientation:   'landscape',
    2822                 uploading:     false,
    2823                 type:          '',
    2824                 subtype:       '',
    2825                 icon:          '',
    2826                 filename:      '',
    2827                 caption:       '',
    2828                 title:         '',
    2829                 dateFormatted: '',
    2830                 width:         '',
    2831                 height:        '',
    2832                 compat:        false,
    2833                 alt:           '',
    2834                 description:   ''
    2835             }, this.options );
    2836 
    2837         options.buttons  = this.buttons;
    2838         options.describe = this.controller.state().get('describe');
    2839 
    2840         if ( 'image' === options.type ) {
    2841             options.size = this.imageSize();
    2842         }
    2843 
    2844         options.can = {};
    2845         if ( options.nonces ) {
    2846             options.can.remove = !! options.nonces['delete'];
    2847             options.can.save = !! options.nonces.update;
    2848         }
    2849 
    2850         if ( this.controller.state().get('allowLocalEdits') ) {
    2851             options.allowLocalEdits = true;
    2852         }
    2853 
    2854         if ( options.uploading && ! options.percent ) {
    2855             options.percent = 0;
    2856         }
    2857 
    2858         this.views.detach();
    2859         this.$el.html( this.template( options ) );
    2860 
    2861         this.$el.toggleClass( 'uploading', options.uploading );
    2862 
    2863         if ( options.uploading ) {
    2864             this.$bar = this.$('.media-progress-bar div');
    2865         } else {
    2866             delete this.$bar;
    2867         }
    2868 
    2869         // Check if the model is selected.
    2870         this.updateSelect();
    2871 
    2872         // Update the save status.
    2873         this.updateSave();
    2874 
    2875         this.views.render();
    2876 
    2877         return this;
    2878     },
    2879 
    2880     progress: function() {
    2881         if ( this.$bar && this.$bar.length ) {
    2882             this.$bar.width( this.model.get('percent') + '%' );
    2883         }
    2884     },
    2885 
    2886     /**
    2887      * @param {Object} event
    2888      */
    2889     toggleSelectionHandler: function( event ) {
    2890         var method;
    2891 
    2892         // Don't do anything inside inputs and on the attachment check and remove buttons.
    2893         if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
    2894             return;
    2895         }
    2896 
    2897         // Catch arrow events
    2898         if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    2899             this.controller.trigger( 'attachment:keydown:arrow', event );
    2900             return;
    2901         }
    2902 
    2903         // Catch enter and space events
    2904         if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    2905             return;
    2906         }
    2907 
    2908         event.preventDefault();
    2909 
    2910         // In the grid view, bubble up an edit:attachment event to the controller.
    2911         if ( this.controller.isModeActive( 'grid' ) ) {
    2912             if ( this.controller.isModeActive( 'edit' ) ) {
    2913                 // Pass the current target to restore focus when closing
    2914                 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
    2915                 return;
    2916             }
    2917 
    2918             if ( this.controller.isModeActive( 'select' ) ) {
    2919                 method = 'toggle';
    2920             }
    2921         }
    2922 
    2923         if ( event.shiftKey ) {
    2924             method = 'between';
    2925         } else if ( event.ctrlKey || event.metaKey ) {
    2926             method = 'toggle';
    2927         }
    2928 
    2929         this.toggleSelection({
    2930             method: method
    2931         });
    2932 
    2933         this.controller.trigger( 'selection:toggle' );
    2934     },
    2935     /**
    2936      * @param {Object} options
    2937      */
    2938     toggleSelection: function( options ) {
    2939         var collection = this.collection,
    2940             selection = this.options.selection,
    2941             model = this.model,
    2942             method = options && options.method,
    2943             single, models, singleIndex, modelIndex;
    2944 
    2945         if ( ! selection ) {
    2946             return;
    2947         }
    2948 
    2949         single = selection.single();
    2950         method = _.isUndefined( method ) ? selection.multiple : method;
    2951 
    2952         // If the `method` is set to `between`, select all models that
    2953         // exist between the current and the selected model.
    2954         if ( 'between' === method && single && selection.multiple ) {
    2955             // If the models are the same, short-circuit.
    2956             if ( single === model ) {
    2957                 return;
    2958             }
    2959 
    2960             singleIndex = collection.indexOf( single );
    2961             modelIndex  = collection.indexOf( this.model );
    2962 
    2963             if ( singleIndex < modelIndex ) {
    2964                 models = collection.models.slice( singleIndex, modelIndex + 1 );
    2965             } else {
    2966                 models = collection.models.slice( modelIndex, singleIndex + 1 );
    2967             }
    2968 
    2969             selection.add( models );
    2970             selection.single( model );
    2971             return;
    2972 
    2973         // If the `method` is set to `toggle`, just flip the selection
    2974         // status, regardless of whether the model is the single model.
    2975         } else if ( 'toggle' === method ) {
    2976             selection[ this.selected() ? 'remove' : 'add' ]( model );
    2977             selection.single( model );
    2978             return;
    2979         } else if ( 'add' === method ) {
    2980             selection.add( model );
    2981             selection.single( model );
    2982             return;
    2983         }
    2984 
    2985         // Fixes bug that loses focus when selecting a featured image
    2986         if ( ! method ) {
    2987             method = 'add';
    2988         }
    2989 
    2990         if ( method !== 'add' ) {
    2991             method = 'reset';
    2992         }
    2993 
    2994         if ( this.selected() ) {
    2995             // If the model is the single model, remove it.
    2996             // If it is not the same as the single model,
    2997             // it now becomes the single model.
    2998             selection[ single === model ? 'remove' : 'single' ]( model );
    2999         } else {
    3000             // If the model is not selected, run the `method` on the
    3001             // selection. By default, we `reset` the selection, but the
    3002             // `method` can be set to `add` the model to the selection.
    3003             selection[ method ]( model );
    3004             selection.single( model );
    3005         }
    3006     },
    3007 
    3008     updateSelect: function() {
    3009         this[ this.selected() ? 'select' : 'deselect' ]();
    3010     },
    3011     /**
    3012      * @returns {unresolved|Boolean}
    3013      */
    3014     selected: function() {
    3015         var selection = this.options.selection;
    3016         if ( selection ) {
    3017             return !! selection.get( this.model.cid );
    3018         }
    3019     },
    3020     /**
    3021      * @param {Backbone.Model} model
    3022      * @param {Backbone.Collection} collection
    3023      */
    3024     select: function( model, collection ) {
    3025         var selection = this.options.selection,
    3026             controller = this.controller;
    3027 
    3028         // Check if a selection exists and if it's the collection provided.
    3029         // If they're not the same collection, bail; we're in another
    3030         // selection's event loop.
    3031         if ( ! selection || ( collection && collection !== selection ) ) {
    3032             return;
    3033         }
    3034 
    3035         // Bail if the model is already selected.
    3036         if ( this.$el.hasClass( 'selected' ) ) {
    3037             return;
    3038         }
    3039 
    3040         // Add 'selected' class to model, set aria-checked to true.
    3041         this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
    3042         //  Make the checkbox tabable, except in media grid (bulk select mode).
    3043         if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
    3044             this.$( '.check' ).attr( 'tabindex', '0' );
    3045         }
    3046     },
    3047     /**
    3048      * @param {Backbone.Model} model
    3049      * @param {Backbone.Collection} collection
    3050      */
    3051     deselect: function( model, collection ) {
    3052         var selection = this.options.selection;
    3053 
    3054         // Check if a selection exists and if it's the collection provided.
    3055         // If they're not the same collection, bail; we're in another
    3056         // selection's event loop.
    3057         if ( ! selection || ( collection && collection !== selection ) ) {
    3058             return;
    3059         }
    3060         this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
    3061             .find( '.check' ).attr( 'tabindex', '-1' );
    3062     },
    3063     /**
    3064      * @param {Backbone.Model} model
    3065      * @param {Backbone.Collection} collection
    3066      */
    3067     details: function( model, collection ) {
    3068         var selection = this.options.selection,
    3069             details;
    3070 
    3071         if ( selection !== collection ) {
    3072             return;
    3073         }
    3074 
    3075         details = selection.single();
    3076         this.$el.toggleClass( 'details', details === this.model );
    3077     },
    3078     /**
    3079      * @param {string} size
    3080      * @returns {Object}
    3081      */
    3082     imageSize: function( size ) {
    3083         var sizes = this.model.get('sizes'), matched = false;
    3084 
    3085         size = size || 'medium';
    3086 
    3087         // Use the provided image size if possible.
    3088         if ( sizes ) {
    3089             if ( sizes[ size ] ) {
    3090                 matched = sizes[ size ];
    3091             } else if ( sizes.large ) {
    3092                 matched = sizes.large;
    3093             } else if ( sizes.thumbnail ) {
    3094                 matched = sizes.thumbnail;
    3095             } else if ( sizes.full ) {
    3096                 matched = sizes.full;
    3097             }
    3098 
    3099             if ( matched ) {
    3100                 return _.clone( matched );
    3101             }
    3102         }
    3103 
    3104         return {
    3105             url:         this.model.get('url'),
    3106             width:       this.model.get('width'),
    3107             height:      this.model.get('height'),
    3108             orientation: this.model.get('orientation')
     7132
     7133        if ( wp.media.view.settings.post.id ) {
     7134            filters.uploaded = {
     7135                text:  l10n.uploadedToThisPost,
     7136                props: {
     7137                    status:  null,
     7138                    type:    null,
     7139                    uploadedTo: wp.media.view.settings.post.id,
     7140                    orderby: 'menuOrder',
     7141                    order:   'ASC'
     7142                },
     7143                priority: 20
     7144            };
     7145        }
     7146
     7147        filters.unattached = {
     7148            text:  l10n.unattached,
     7149            props: {
     7150                status:     null,
     7151                uploadedTo: 0,
     7152                type:       null,
     7153                orderby:    'menuOrder',
     7154                order:      'ASC'
     7155            },
     7156            priority: 50
    31097157        };
    3110     },
    3111     /**
    3112      * @param {Object} event
    3113      */
    3114     updateSetting: function( event ) {
    3115         var $setting = $( event.target ).closest('[data-setting]'),
    3116             setting, value;
    3117 
    3118         if ( ! $setting.length ) {
    3119             return;
    3120         }
    3121 
    3122         setting = $setting.data('setting');
    3123         value   = event.target.value;
    3124 
    3125         if ( this.model.get( setting ) !== value ) {
    3126             this.save( setting, value );
    3127         }
    3128     },
    3129 
    3130     /**
    3131      * Pass all the arguments to the model's save method.
    3132      *
    3133      * Records the aggregate status of all save requests and updates the
    3134      * view's classes accordingly.
    3135      */
    3136     save: function() {
    3137         var view = this,
    3138             save = this._save = this._save || { status: 'ready' },
    3139             request = this.model.save.apply( this.model, arguments ),
    3140             requests = save.requests ? $.when( request, save.requests ) : request;
    3141 
    3142         // If we're waiting to remove 'Saved.', stop.
    3143         if ( save.savedTimer ) {
    3144             clearTimeout( save.savedTimer );
    3145         }
    3146 
    3147         this.updateSave('waiting');
    3148         save.requests = requests;
    3149         requests.always( function() {
    3150             // If we've performed another request since this one, bail.
    3151             if ( save.requests !== requests ) {
    3152                 return;
    3153             }
    3154 
    3155             view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
    3156             save.savedTimer = setTimeout( function() {
    3157                 view.updateSave('ready');
    3158                 delete save.savedTimer;
    3159             }, 2000 );
    3160         });
    3161     },
    3162     /**
    3163      * @param {string} status
    3164      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3165      */
    3166     updateSave: function( status ) {
    3167         var save = this._save = this._save || { status: 'ready' };
    3168 
    3169         if ( status && status !== save.status ) {
    3170             this.$el.removeClass( 'save-' + save.status );
    3171             save.status = status;
    3172         }
    3173 
    3174         this.$el.addClass( 'save-' + save.status );
    3175         return this;
    3176     },
    3177 
    3178     updateAll: function() {
    3179         var $settings = this.$('[data-setting]'),
    3180             model = this.model,
    3181             changed;
    3182 
    3183         changed = _.chain( $settings ).map( function( el ) {
    3184             var $input = $('input, textarea, select, [value]', el ),
    3185                 setting, value;
    3186 
    3187             if ( ! $input.length ) {
    3188                 return;
    3189             }
    3190 
    3191             setting = $(el).data('setting');
    3192             value = $input.val();
    3193 
    3194             // Record the value if it changed.
    3195             if ( model.get( setting ) !== value ) {
    3196                 return [ setting, value ];
    3197             }
    3198         }).compact().object().value();
    3199 
    3200         if ( ! _.isEmpty( changed ) ) {
    3201             model.save( changed );
    3202         }
    3203     },
    3204     /**
    3205      * @param {Object} event
    3206      */
    3207     removeFromLibrary: function( event ) {
    3208         // Catch enter and space events
    3209         if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    3210             return;
    3211         }
    3212 
    3213         // Stop propagation so the model isn't selected.
    3214         event.stopPropagation();
    3215 
    3216         this.collection.remove( this.model );
    3217     },
    3218 
    3219     /**
    3220      * Add the model if it isn't in the selection, if it is in the selection,
    3221      * remove it.
    3222      *
    3223      * @param  {[type]} event [description]
    3224      * @return {[type]}       [description]
    3225      */
    3226     checkClickHandler: function ( event ) {
    3227         var selection = this.options.selection;
    3228         if ( ! selection ) {
    3229             return;
    3230         }
    3231         event.stopPropagation();
    3232         if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
    3233             selection.remove( this.model );
    3234             // Move focus back to the attachment tile (from the check).
    3235             this.$el.focus();
    3236         } else {
    3237             selection.add( this.model );
    3238         }
     7158
     7159        if ( wp.media.view.settings.mediaTrash &&
     7160            this.controller.isModeActive( 'grid' ) ) {
     7161
     7162            filters.trash = {
     7163                text:  l10n.trash,
     7164                props: {
     7165                    uploadedTo: null,
     7166                    status:     'trash',
     7167                    type:       null,
     7168                    orderby:    'date',
     7169                    order:      'DESC'
     7170                },
     7171                priority: 50
     7172            };
     7173        }
     7174
     7175        this.filters = filters;
    32397176    }
    32407177});
    32417178
    3242 // Ensure settings remain in sync between attachment views.
    3243 _.each({
    3244     caption: '_syncCaption',
    3245     title:   '_syncTitle',
    3246     artist:  '_syncArtist',
    3247     album:   '_syncAlbum'
    3248 }, function( method, setting ) {
    3249     /**
    3250      * @function _syncCaption
    3251      * @memberOf wp.media.view.Attachment
    3252      * @instance
    3253      *
    3254      * @param {Backbone.Model} model
    3255      * @param {string} value
    3256      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3257      */
    3258     /**
    3259      * @function _syncTitle
    3260      * @memberOf wp.media.view.Attachment
    3261      * @instance
    3262      *
    3263      * @param {Backbone.Model} model
    3264      * @param {string} value
    3265      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3266      */
    3267     /**
    3268      * @function _syncArtist
    3269      * @memberOf wp.media.view.Attachment
    3270      * @instance
    3271      *
    3272      * @param {Backbone.Model} model
    3273      * @param {string} value
    3274      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3275      */
    3276     /**
    3277      * @function _syncAlbum
    3278      * @memberOf wp.media.view.Attachment
    3279      * @instance
    3280      *
    3281      * @param {Backbone.Model} model
    3282      * @param {string} value
    3283      * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3284      */
    3285     Attachment.prototype[ method ] = function( model, value ) {
    3286         var $setting = this.$('[data-setting="' + setting + '"]');
    3287 
    3288         if ( ! $setting.length ) {
    3289             return this;
    3290         }
    3291 
    3292         // If the updated value is in sync with the value in the DOM, there
    3293         // is no need to re-render. If we're currently editing the value,
    3294         // it will automatically be in sync, suppressing the re-render for
    3295         // the view we're editing, while updating any others.
    3296         if ( value === $setting.find('input, textarea, select, [value]').val() ) {
    3297             return this;
    3298         }
    3299 
    3300         return this.render();
    3301     };
    3302 });
    3303 
    3304 module.exports = Attachment;
    3305 
    3306 },{}],26:[function(require,module,exports){
    3307 var Attachment = wp.media.view.Attachment,
    3308     l10n = wp.media.view.l10n,
    3309     Details;
    3310 
    3311 /**
    3312  * wp.media.view.Attachment.Details
    3313  *
    3314  * @memberOf wp.media.view.Attachment
    3315  *
    3316  * @class
    3317  * @augments wp.media.view.Attachment
    3318  * @augments wp.media.View
    3319  * @augments wp.Backbone.View
    3320  * @augments Backbone.View
    3321  */
    3322 Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{
    3323     tagName:   'div',
    3324     className: 'attachment-details',
    3325     template:  wp.template('attachment-details'),
    3326 
    3327     attributes: function() {
    3328         return {
    3329             'tabIndex':     0,
    3330             'data-id':      this.model.get( 'id' )
    3331         };
    3332     },
    3333 
    3334     events: {
    3335         'change [data-setting]':          'updateSetting',
    3336         'change [data-setting] input':    'updateSetting',
    3337         'change [data-setting] select':   'updateSetting',
    3338         'change [data-setting] textarea': 'updateSetting',
    3339         'click .delete-attachment':       'deleteAttachment',
    3340         'click .trash-attachment':        'trashAttachment',
    3341         'click .untrash-attachment':      'untrashAttachment',
    3342         'click .edit-attachment':         'editAttachment',
    3343         'keydown':                        'toggleSelectionHandler'
    3344     },
    3345 
    3346     initialize: function() {
    3347         this.options = _.defaults( this.options, {
    3348             rerenderOnModelChange: false
    3349         });
    3350 
    3351         this.on( 'ready', this.initialFocus );
    3352         // Call 'initialize' directly on the parent class.
    3353         Attachment.prototype.initialize.apply( this, arguments );
    3354     },
    3355 
    3356     initialFocus: function() {
    3357         if ( ! wp.media.isTouchDevice ) {
    3358             /*
    3359             Previously focused the first ':input' (the readonly URL text field).
    3360             Since the first ':input' is now a button (delete/trash): when pressing
    3361             spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
    3362             as soon as focus is moved. Explicitly target the first text field for now.
    3363             @todo change initial focus logic, also for accessibility.
    3364             */
    3365             this.$( 'input[type="text"]' ).eq( 0 ).focus();
    3366         }
    3367     },
    3368     /**
    3369      * @param {Object} event
    3370      */
    3371     deleteAttachment: function( event ) {
    3372         event.preventDefault();
    3373 
    3374         if ( window.confirm( l10n.warnDelete ) ) {
    3375             this.model.destroy();
    3376             // Keep focus inside media modal
    3377             // after image is deleted
    3378             this.controller.modal.focusManager.focus();
    3379         }
    3380     },
    3381     /**
    3382      * @param {Object} event
    3383      */
    3384     trashAttachment: function( event ) {
    3385         var library = this.controller.library;
    3386         event.preventDefault();
    3387 
    3388         if ( wp.media.view.settings.mediaTrash &&
    3389             'edit-metadata' === this.controller.content.mode() ) {
    3390 
    3391             this.model.set( 'status', 'trash' );
    3392             this.model.save().done( function() {
    3393                 library._requery( true );
    3394             } );
    3395         }  else {
    3396             this.model.destroy();
    3397         }
    3398     },
    3399     /**
    3400      * @param {Object} event
    3401      */
    3402     untrashAttachment: function( event ) {
    3403         var library = this.controller.library;
    3404         event.preventDefault();
    3405 
    3406         this.model.set( 'status', 'inherit' );
    3407         this.model.save().done( function() {
    3408             library._requery( true );
    3409         } );
    3410     },
    3411     /**
    3412      * @param {Object} event
    3413      */
    3414     editAttachment: function( event ) {
    3415         var editState = this.controller.states.get( 'edit-image' );
    3416         if ( window.imageEdit && editState ) {
    3417             event.preventDefault();
    3418 
    3419             editState.set( 'image', this.model );
    3420             this.controller.setState( 'edit-image' );
    3421         } else {
    3422             this.$el.addClass('needs-refresh');
    3423         }
    3424     },
    3425     /**
    3426      * When reverse tabbing(shift+tab) out of the right details panel, deliver
    3427      * the focus to the item in the list that was being edited.
    3428      *
    3429      * @param {Object} event
    3430      */
    3431     toggleSelectionHandler: function( event ) {
    3432         if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
    3433             this.controller.trigger( 'attachment:details:shift-tab', event );
    3434             return false;
    3435         }
    3436 
    3437         if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    3438             this.controller.trigger( 'attachment:keydown:arrow', event );
    3439             return;
    3440         }
    3441     }
    3442 });
    3443 
    3444 module.exports = Details;
    3445 
    3446 },{}],27:[function(require,module,exports){
    3447 /**
    3448  * wp.media.view.Attachment.EditLibrary
    3449  *
    3450  * @memberOf wp.media.view.Attachment
    3451  *
    3452  * @class
    3453  * @augments wp.media.view.Attachment
    3454  * @augments wp.media.View
    3455  * @augments wp.Backbone.View
    3456  * @augments Backbone.View
    3457  */
    3458 var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{
    3459     buttons: {
    3460         close: true
    3461     }
    3462 });
    3463 
    3464 module.exports = EditLibrary;
    3465 
    3466 },{}],28:[function(require,module,exports){
    3467 /**
    3468  * wp.media.view.Attachment.EditSelection
    3469  *
    3470  * @memberOf wp.media.view.Attachment
    3471  *
    3472  * @class
    3473  * @augments wp.media.view.Attachment.Selection
    3474  * @augments wp.media.view.Attachment
    3475  * @augments wp.media.View
    3476  * @augments wp.Backbone.View
    3477  * @augments Backbone.View
    3478  */
    3479 var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{
    3480     buttons: {
    3481         close: true
    3482     }
    3483 });
    3484 
    3485 module.exports = EditSelection;
    3486 
    3487 },{}],29:[function(require,module,exports){
    3488 /**
    3489  * wp.media.view.Attachment.Library
    3490  *
    3491  * @memberOf wp.media.view.Attachment
    3492  *
    3493  * @class
    3494  * @augments wp.media.view.Attachment
    3495  * @augments wp.media.View
    3496  * @augments wp.Backbone.View
    3497  * @augments Backbone.View
    3498  */
    3499 var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{
    3500     buttons: {
    3501         check: true
    3502     }
    3503 });
    3504 
    3505 module.exports = Library;
    3506 
    3507 },{}],30:[function(require,module,exports){
    3508 /**
    3509  * wp.media.view.Attachment.Selection
    3510  *
    3511  * @memberOf wp.media.view.Attachment
    3512  *
    3513  * @class
    3514  * @augments wp.media.view.Attachment
    3515  * @augments wp.media.View
    3516  * @augments wp.Backbone.View
    3517  * @augments Backbone.View
    3518  */
    3519 var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{
    3520     className: 'attachment selection',
    3521 
    3522     // On click, just select the model, instead of removing the model from
    3523     // the selection.
    3524     toggleSelection: function() {
    3525         this.options.selection.single( this.model );
    3526     }
    3527 });
    3528 
    3529 module.exports = Selection;
    3530 
    3531 },{}],31:[function(require,module,exports){
    3532 var View = wp.media.View,
    3533     $ = jQuery,
    3534     Attachments;
    3535 
    3536 /**
    3537  * wp.media.view.Attachments
    3538  *
    3539  * @memberOf wp.media.view
    3540  *
    3541  * @class
    3542  * @augments wp.media.View
    3543  * @augments wp.Backbone.View
    3544  * @augments Backbone.View
    3545  */
    3546 Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
    3547     tagName:   'ul',
    3548     className: 'attachments',
    3549 
    3550     attributes: {
    3551         tabIndex: -1
    3552     },
    3553 
    3554     initialize: function() {
    3555         this.el.id = _.uniqueId('__attachments-view-');
    3556 
    3557         _.defaults( this.options, {
    3558             refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
    3559             refreshThreshold:   3,
    3560             AttachmentView:     wp.media.view.Attachment,
    3561             sortable:           false,
    3562             resize:             true,
    3563             idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
    3564         });
    3565 
    3566         this._viewsByCid = {};
    3567         this.$window = $( window );
    3568         this.resizeEvent = 'resize.media-modal-columns';
    3569 
    3570         this.collection.on( 'add', function( attachment ) {
    3571             this.views.add( this.createAttachmentView( attachment ), {
    3572                 at: this.collection.indexOf( attachment )
    3573             });
    3574         }, this );
    3575 
    3576         this.collection.on( 'remove', function( attachment ) {
    3577             var view = this._viewsByCid[ attachment.cid ];
    3578             delete this._viewsByCid[ attachment.cid ];
    3579 
    3580             if ( view ) {
    3581                 view.remove();
    3582             }
    3583         }, this );
    3584 
    3585         this.collection.on( 'reset', this.render, this );
    3586 
    3587         this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
    3588 
    3589         // Throttle the scroll handler and bind this.
    3590         this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
    3591 
    3592         this.options.scrollElement = this.options.scrollElement || this.el;
    3593         $( this.options.scrollElement ).on( 'scroll', this.scroll );
    3594 
    3595         this.initSortable();
    3596 
    3597         _.bindAll( this, 'setColumns' );
    3598 
    3599         if ( this.options.resize ) {
    3600             this.on( 'ready', this.bindEvents );
    3601             this.controller.on( 'open', this.setColumns );
    3602 
    3603             // Call this.setColumns() after this view has been rendered in the DOM so
    3604             // attachments get proper width applied.
    3605             _.defer( this.setColumns, this );
    3606         }
    3607     },
    3608 
    3609     bindEvents: function() {
    3610         this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
    3611     },
    3612 
    3613     attachmentFocus: function() {
    3614         this.$( 'li:first' ).focus();
    3615     },
    3616 
    3617     restoreFocus: function() {
    3618         this.$( 'li.selected:first' ).focus();
    3619     },
    3620 
    3621     arrowEvent: function( event ) {
    3622         var attachments = this.$el.children( 'li' ),
    3623             perRow = this.columns,
    3624             index = attachments.filter( ':focus' ).index(),
    3625             row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
    3626 
    3627         if ( index === -1 ) {
    3628             return;
    3629         }
    3630 
    3631         // Left arrow
    3632         if ( 37 === event.keyCode ) {
    3633             if ( 0 === index ) {
    3634                 return;
    3635             }
    3636             attachments.eq( index - 1 ).focus();
    3637         }
    3638 
    3639         // Up arrow
    3640         if ( 38 === event.keyCode ) {
    3641             if ( 1 === row ) {
    3642                 return;
    3643             }
    3644             attachments.eq( index - perRow ).focus();
    3645         }
    3646 
    3647         // Right arrow
    3648         if ( 39 === event.keyCode ) {
    3649             if ( attachments.length === index ) {
    3650                 return;
    3651             }
    3652             attachments.eq( index + 1 ).focus();
    3653         }
    3654 
    3655         // Down arrow
    3656         if ( 40 === event.keyCode ) {
    3657             if ( Math.ceil( attachments.length / perRow ) === row ) {
    3658                 return;
    3659             }
    3660             attachments.eq( index + perRow ).focus();
    3661         }
    3662     },
    3663 
    3664     dispose: function() {
    3665         this.collection.props.off( null, null, this );
    3666         if ( this.options.resize ) {
    3667             this.$window.off( this.resizeEvent );
    3668         }
    3669 
    3670         /**
    3671          * call 'dispose' directly on the parent class
    3672          */
    3673         View.prototype.dispose.apply( this, arguments );
    3674     },
    3675 
    3676     setColumns: function() {
    3677         var prev = this.columns,
    3678             width = this.$el.width();
    3679 
    3680         if ( width ) {
    3681             this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
    3682 
    3683             if ( ! prev || prev !== this.columns ) {
    3684                 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
    3685             }
    3686         }
    3687     },
    3688 
    3689     initSortable: function() {
    3690         var collection = this.collection;
    3691 
    3692         if ( ! this.options.sortable || ! $.fn.sortable ) {
    3693             return;
    3694         }
    3695 
    3696         this.$el.sortable( _.extend({
    3697             // If the `collection` has a `comparator`, disable sorting.
    3698             disabled: !! collection.comparator,
    3699 
    3700             // Change the position of the attachment as soon as the
    3701             // mouse pointer overlaps a thumbnail.
    3702             tolerance: 'pointer',
    3703 
    3704             // Record the initial `index` of the dragged model.
    3705             start: function( event, ui ) {
    3706                 ui.item.data('sortableIndexStart', ui.item.index());
    3707             },
    3708 
    3709             // Update the model's index in the collection.
    3710             // Do so silently, as the view is already accurate.
    3711             update: function( event, ui ) {
    3712                 var model = collection.at( ui.item.data('sortableIndexStart') ),
    3713                     comparator = collection.comparator;
    3714 
    3715                 // Temporarily disable the comparator to prevent `add`
    3716                 // from re-sorting.
    3717                 delete collection.comparator;
    3718 
    3719                 // Silently shift the model to its new index.
    3720                 collection.remove( model, {
    3721                     silent: true
    3722                 });
    3723                 collection.add( model, {
    3724                     silent: true,
    3725                     at:     ui.item.index()
    3726                 });
    3727 
    3728                 // Restore the comparator.
    3729                 collection.comparator = comparator;
    3730 
    3731                 // Fire the `reset` event to ensure other collections sync.
    3732                 collection.trigger( 'reset', collection );
    3733 
    3734                 // If the collection is sorted by menu order,
    3735                 // update the menu order.
    3736                 collection.saveMenuOrder();
    3737             }
    3738         }, this.options.sortable ) );
    3739 
    3740         // If the `orderby` property is changed on the `collection`,
    3741         // check to see if we have a `comparator`. If so, disable sorting.
    3742         collection.props.on( 'change:orderby', function() {
    3743             this.$el.sortable( 'option', 'disabled', !! collection.comparator );
    3744         }, this );
    3745 
    3746         this.collection.props.on( 'change:orderby', this.refreshSortable, this );
    3747         this.refreshSortable();
    3748     },
    3749 
    3750     refreshSortable: function() {
    3751         if ( ! this.options.sortable || ! $.fn.sortable ) {
    3752             return;
    3753         }
    3754 
    3755         // If the `collection` has a `comparator`, disable sorting.
    3756         var collection = this.collection,
    3757             orderby = collection.props.get('orderby'),
    3758             enabled = 'menuOrder' === orderby || ! collection.comparator;
    3759 
    3760         this.$el.sortable( 'option', 'disabled', ! enabled );
    3761     },
    3762 
    3763     /**
    3764      * @param {wp.media.model.Attachment} attachment
    3765      * @returns {wp.media.View}
    3766      */
    3767     createAttachmentView: function( attachment ) {
    3768         var view = new this.options.AttachmentView({
    3769             controller:           this.controller,
    3770             model:                attachment,
    3771             collection:           this.collection,
    3772             selection:            this.options.selection
    3773         });
    3774 
    3775         return this._viewsByCid[ attachment.cid ] = view;
    3776     },
    3777 
    3778     prepare: function() {
    3779         // Create all of the Attachment views, and replace
    3780         // the list in a single DOM operation.
    3781         if ( this.collection.length ) {
    3782             this.views.set( this.collection.map( this.createAttachmentView, this ) );
    3783 
    3784         // If there are no elements, clear the views and load some.
    3785         } else {
    3786             this.views.unset();
    3787             this.collection.more().done( this.scroll );
    3788         }
    3789     },
    3790 
    3791     ready: function() {
    3792         // Trigger the scroll event to check if we're within the
    3793         // threshold to query for additional attachments.
    3794         this.scroll();
    3795     },
    3796 
    3797     scroll: function() {
    3798         var view = this,
    3799             el = this.options.scrollElement,
    3800             scrollTop = el.scrollTop,
    3801             toolbar;
    3802 
    3803         // The scroll event occurs on the document, but the element
    3804         // that should be checked is the document body.
    3805         if ( el === document ) {
    3806             el = document.body;
    3807             scrollTop = $(document).scrollTop();
    3808         }
    3809 
    3810         if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
    3811             return;
    3812         }
    3813 
    3814         toolbar = this.views.parent.toolbar;
    3815 
    3816         // Show the spinner only if we are close to the bottom.
    3817         if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
    3818             toolbar.get('spinner').show();
    3819         }
    3820 
    3821         if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
    3822             this.collection.more().done(function() {
    3823                 view.scroll();
    3824                 toolbar.get('spinner').hide();
    3825             });
    3826         }
    3827     }
    3828 });
    3829 
    3830 module.exports = Attachments;
    3831 
    3832 },{}],32:[function(require,module,exports){
     7179module.exports = All;
     7180
     7181
     7182/***/ }),
     7183/* 78 */
     7184/***/ (function(module, exports) {
     7185
    38337186var View = wp.media.View,
    38347187    mediaTrash = wp.media.view.settings.mediaTrash,
     
    43087661module.exports = AttachmentsBrowser;
    43097662
    4310 },{}],33:[function(require,module,exports){
     7663
     7664/***/ }),
     7665/* 79 */
     7666/***/ (function(module, exports) {
     7667
     7668var l10n = wp.media.view.l10n,
     7669    Selection;
     7670
     7671/**
     7672 * wp.media.view.Selection
     7673 *
     7674 * @memberOf wp.media.view
     7675 *
     7676 * @class
     7677 * @augments wp.media.View
     7678 * @augments wp.Backbone.View
     7679 * @augments Backbone.View
     7680 */
     7681Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */{
     7682    tagName:   'div',
     7683    className: 'media-selection',
     7684    template:  wp.template('media-selection'),
     7685
     7686    events: {
     7687        'click .edit-selection':  'edit',
     7688        'click .clear-selection': 'clear'
     7689    },
     7690
     7691    initialize: function() {
     7692        _.defaults( this.options, {
     7693            editable:  false,
     7694            clearable: true
     7695        });
     7696
     7697        /**
     7698         * @member {wp.media.view.Attachments.Selection}
     7699         */
     7700        this.attachments = new wp.media.view.Attachments.Selection({
     7701            controller: this.controller,
     7702            collection: this.collection,
     7703            selection:  this.collection,
     7704            model:      new Backbone.Model()
     7705        });
     7706
     7707        this.views.set( '.selection-view', this.attachments );
     7708        this.collection.on( 'add remove reset', this.refresh, this );
     7709        this.controller.on( 'content:activate', this.refresh, this );
     7710    },
     7711
     7712    ready: function() {
     7713        this.refresh();
     7714    },
     7715
     7716    refresh: function() {
     7717        // If the selection hasn't been rendered, bail.
     7718        if ( ! this.$el.children().length ) {
     7719            return;
     7720        }
     7721
     7722        var collection = this.collection,
     7723            editing = 'edit-selection' === this.controller.content.mode();
     7724
     7725        // If nothing is selected, display nothing.
     7726        this.$el.toggleClass( 'empty', ! collection.length );
     7727        this.$el.toggleClass( 'one', 1 === collection.length );
     7728        this.$el.toggleClass( 'editing', editing );
     7729
     7730        this.$('.count').text( l10n.selected.replace('%d', collection.length) );
     7731    },
     7732
     7733    edit: function( event ) {
     7734        event.preventDefault();
     7735        if ( this.options.editable ) {
     7736            this.options.editable.call( this, this.collection );
     7737        }
     7738    },
     7739
     7740    clear: function( event ) {
     7741        event.preventDefault();
     7742        this.collection.reset();
     7743
     7744        // Keep focus inside media modal
     7745        // after clear link is selected
     7746        this.controller.modal.focusManager.focus();
     7747    }
     7748});
     7749
     7750module.exports = Selection;
     7751
     7752
     7753/***/ }),
     7754/* 80 */
     7755/***/ (function(module, exports) {
     7756
     7757/**
     7758 * wp.media.view.Attachment.Selection
     7759 *
     7760 * @memberOf wp.media.view.Attachment
     7761 *
     7762 * @class
     7763 * @augments wp.media.view.Attachment
     7764 * @augments wp.media.View
     7765 * @augments wp.Backbone.View
     7766 * @augments Backbone.View
     7767 */
     7768var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{
     7769    className: 'attachment selection',
     7770
     7771    // On click, just select the model, instead of removing the model from
     7772    // the selection.
     7773    toggleSelection: function() {
     7774        this.options.selection.single( this.model );
     7775    }
     7776});
     7777
     7778module.exports = Selection;
     7779
     7780
     7781/***/ }),
     7782/* 81 */
     7783/***/ (function(module, exports) {
     7784
    43117785var Attachments = wp.media.view.Attachments,
    43127786    Selection;
     
    43407814module.exports = Selection;
    43417815
    4342 },{}],34:[function(require,module,exports){
    4343 var $ = Backbone.$,
    4344     ButtonGroup;
     7816
     7817/***/ }),
     7818/* 82 */
     7819/***/ (function(module, exports) {
    43457820
    43467821/**
    4347  * wp.media.view.ButtonGroup
     7822 * wp.media.view.Attachment.EditSelection
     7823 *
     7824 * @memberOf wp.media.view.Attachment
     7825 *
     7826 * @class
     7827 * @augments wp.media.view.Attachment.Selection
     7828 * @augments wp.media.view.Attachment
     7829 * @augments wp.media.View
     7830 * @augments wp.Backbone.View
     7831 * @augments Backbone.View
     7832 */
     7833var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{
     7834    buttons: {
     7835        close: true
     7836    }
     7837});
     7838
     7839module.exports = EditSelection;
     7840
     7841
     7842/***/ }),
     7843/* 83 */
     7844/***/ (function(module, exports) {
     7845
     7846var View = wp.media.View,
     7847    $ = Backbone.$,
     7848    Settings;
     7849
     7850/**
     7851 * wp.media.view.Settings
    43487852 *
    43497853 * @memberOf wp.media.view
     
    43547858 * @augments Backbone.View
    43557859 */
    4356 ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{
    4357     tagName:   'div',
    4358     className: 'button-group button-large media-button-group',
     7860Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{
     7861    events: {
     7862        'click button':    'updateHandler',
     7863        'change input':    'updateHandler',
     7864        'change select':   'updateHandler',
     7865        'change textarea': 'updateHandler'
     7866    },
    43597867
    43607868    initialize: function() {
    4361         /**
    4362          * @member {wp.media.view.Button[]}
    4363          */
    4364         this.buttons = _.map( this.options.buttons || [], function( button ) {
    4365             if ( button instanceof Backbone.View ) {
    4366                 return button;
     7869        this.model = this.model || new Backbone.Model();
     7870        this.listenTo( this.model, 'change', this.updateChanges );
     7871    },
     7872
     7873    prepare: function() {
     7874        return _.defaults({
     7875            model: this.model.toJSON()
     7876        }, this.options );
     7877    },
     7878    /**
     7879     * @returns {wp.media.view.Settings} Returns itself to allow chaining
     7880     */
     7881    render: function() {
     7882        View.prototype.render.apply( this, arguments );
     7883        // Select the correct values.
     7884        _( this.model.attributes ).chain().keys().each( this.update, this );
     7885        return this;
     7886    },
     7887    /**
     7888     * @param {string} key
     7889     */
     7890    update: function( key ) {
     7891        var value = this.model.get( key ),
     7892            $setting = this.$('[data-setting="' + key + '"]'),
     7893            $buttons, $value;
     7894
     7895        // Bail if we didn't find a matching setting.
     7896        if ( ! $setting.length ) {
     7897            return;
     7898        }
     7899
     7900        // Attempt to determine how the setting is rendered and update
     7901        // the selected value.
     7902
     7903        // Handle dropdowns.
     7904        if ( $setting.is('select') ) {
     7905            $value = $setting.find('[value="' + value + '"]');
     7906
     7907            if ( $value.length ) {
     7908                $setting.find('option').prop( 'selected', false );
     7909                $value.prop( 'selected', true );
    43677910            } else {
    4368                 return new wp.media.view.Button( button ).render();
     7911                // If we can't find the desired value, record what *is* selected.
     7912                this.model.set( key, $setting.find(':selected').val() );
    43697913            }
    4370         });
    4371 
    4372         delete this.options.buttons;
    4373 
    4374         if ( this.options.classes ) {
    4375             this.$el.addClass( this.options.classes );
    4376         }
    4377     },
    4378 
    4379     /**
    4380      * @returns {wp.media.view.ButtonGroup}
    4381      */
    4382     render: function() {
    4383         this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
    4384         return this;
     7914
     7915        // Handle button groups.
     7916        } else if ( $setting.hasClass('button-group') ) {
     7917            $buttons = $setting.find('button').removeClass('active');
     7918            $buttons.filter( '[value="' + value + '"]' ).addClass('active');
     7919
     7920        // Handle text inputs and textareas.
     7921        } else if ( $setting.is('input[type="text"], textarea') ) {
     7922            if ( ! $setting.is(':focus') ) {
     7923                $setting.val( value );
     7924            }
     7925        // Handle checkboxes.
     7926        } else if ( $setting.is('input[type="checkbox"]') ) {
     7927            $setting.prop( 'checked', !! value && 'false' !== value );
     7928        }
     7929    },
     7930    /**
     7931     * @param {Object} event
     7932     */
     7933    updateHandler: function( event ) {
     7934        var $setting = $( event.target ).closest('[data-setting]'),
     7935            value = event.target.value,
     7936            userSetting;
     7937
     7938        event.preventDefault();
     7939
     7940        if ( ! $setting.length ) {
     7941            return;
     7942        }
     7943
     7944        // Use the correct value for checkboxes.
     7945        if ( $setting.is('input[type="checkbox"]') ) {
     7946            value = $setting[0].checked;
     7947        }
     7948
     7949        // Update the corresponding setting.
     7950        this.model.set( $setting.data('setting'), value );
     7951
     7952        // If the setting has a corresponding user setting,
     7953        // update that as well.
     7954        if ( userSetting = $setting.data('userSetting') ) {
     7955            window.setUserSetting( userSetting, value );
     7956        }
     7957    },
     7958
     7959    updateChanges: function( model ) {
     7960        if ( model.hasChanged() ) {
     7961            _( model.changed ).chain().keys().each( this.update, this );
     7962        }
    43857963    }
    43867964});
    43877965
    4388 module.exports = ButtonGroup;
    4389 
    4390 },{}],35:[function(require,module,exports){
     7966module.exports = Settings;
     7967
     7968
     7969/***/ }),
     7970/* 84 */
     7971/***/ (function(module, exports) {
     7972
     7973var Settings = wp.media.view.Settings,
     7974    AttachmentDisplay;
     7975
    43917976/**
    4392  * wp.media.view.Button
     7977 * wp.media.view.Settings.AttachmentDisplay
     7978 *
     7979 * @memberOf wp.media.view.Settings
     7980 *
     7981 * @class
     7982 * @augments wp.media.view.Settings
     7983 * @augments wp.media.View
     7984 * @augments wp.Backbone.View
     7985 * @augments Backbone.View
     7986 */
     7987AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.AttachmentDisplay.prototype */{
     7988    className: 'attachment-display-settings',
     7989    template:  wp.template('attachment-display-settings'),
     7990
     7991    initialize: function() {
     7992        var attachment = this.options.attachment;
     7993
     7994        _.defaults( this.options, {
     7995            userSettings: false
     7996        });
     7997        // Call 'initialize' directly on the parent class.
     7998        Settings.prototype.initialize.apply( this, arguments );
     7999        this.listenTo( this.model, 'change:link', this.updateLinkTo );
     8000
     8001        if ( attachment ) {
     8002            attachment.on( 'change:uploading', this.render, this );
     8003        }
     8004    },
     8005
     8006    dispose: function() {
     8007        var attachment = this.options.attachment;
     8008        if ( attachment ) {
     8009            attachment.off( null, null, this );
     8010        }
     8011        /**
     8012         * call 'dispose' directly on the parent class
     8013         */
     8014        Settings.prototype.dispose.apply( this, arguments );
     8015    },
     8016    /**
     8017     * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
     8018     */
     8019    render: function() {
     8020        var attachment = this.options.attachment;
     8021        if ( attachment ) {
     8022            _.extend( this.options, {
     8023                sizes: attachment.get('sizes'),
     8024                type:  attachment.get('type')
     8025            });
     8026        }
     8027        /**
     8028         * call 'render' directly on the parent class
     8029         */
     8030        Settings.prototype.render.call( this );
     8031        this.updateLinkTo();
     8032        return this;
     8033    },
     8034
     8035    updateLinkTo: function() {
     8036        var linkTo = this.model.get('link'),
     8037            $input = this.$('.link-to-custom'),
     8038            attachment = this.options.attachment;
     8039
     8040        if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
     8041            $input.addClass( 'hidden' );
     8042            return;
     8043        }
     8044
     8045        if ( attachment ) {
     8046            if ( 'post' === linkTo ) {
     8047                $input.val( attachment.get('link') );
     8048            } else if ( 'file' === linkTo ) {
     8049                $input.val( attachment.get('url') );
     8050            } else if ( ! this.model.get('linkUrl') ) {
     8051                $input.val('http://');
     8052            }
     8053
     8054            $input.prop( 'readonly', 'custom' !== linkTo );
     8055        }
     8056
     8057        $input.removeClass( 'hidden' );
     8058
     8059        // If the input is visible, focus and select its contents.
     8060        if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
     8061            $input.focus()[0].select();
     8062        }
     8063    }
     8064});
     8065
     8066module.exports = AttachmentDisplay;
     8067
     8068
     8069/***/ }),
     8070/* 85 */
     8071/***/ (function(module, exports) {
     8072
     8073/**
     8074 * wp.media.view.Settings.Gallery
     8075 *
     8076 * @memberOf wp.media.view.Settings
     8077 *
     8078 * @class
     8079 * @augments wp.media.view.Settings
     8080 * @augments wp.media.View
     8081 * @augments wp.Backbone.View
     8082 * @augments Backbone.View
     8083 */
     8084var Gallery = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Gallery.prototype */{
     8085    className: 'collection-settings gallery-settings',
     8086    template:  wp.template('gallery-settings')
     8087});
     8088
     8089module.exports = Gallery;
     8090
     8091
     8092/***/ }),
     8093/* 86 */
     8094/***/ (function(module, exports) {
     8095
     8096/**
     8097 * wp.media.view.Settings.Playlist
     8098 *
     8099 * @memberOf wp.media.view.Settings
     8100 *
     8101 * @class
     8102 * @augments wp.media.view.Settings
     8103 * @augments wp.media.View
     8104 * @augments wp.Backbone.View
     8105 * @augments Backbone.View
     8106 */
     8107var Playlist = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Playlist.prototype */{
     8108    className: 'collection-settings playlist-settings',
     8109    template:  wp.template('playlist-settings')
     8110});
     8111
     8112module.exports = Playlist;
     8113
     8114
     8115/***/ }),
     8116/* 87 */
     8117/***/ (function(module, exports) {
     8118
     8119var Attachment = wp.media.view.Attachment,
     8120    l10n = wp.media.view.l10n,
     8121    Details;
     8122
     8123/**
     8124 * wp.media.view.Attachment.Details
     8125 *
     8126 * @memberOf wp.media.view.Attachment
     8127 *
     8128 * @class
     8129 * @augments wp.media.view.Attachment
     8130 * @augments wp.media.View
     8131 * @augments wp.Backbone.View
     8132 * @augments Backbone.View
     8133 */
     8134Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{
     8135    tagName:   'div',
     8136    className: 'attachment-details',
     8137    template:  wp.template('attachment-details'),
     8138
     8139    attributes: function() {
     8140        return {
     8141            'tabIndex':     0,
     8142            'data-id':      this.model.get( 'id' )
     8143        };
     8144    },
     8145
     8146    events: {
     8147        'change [data-setting]':          'updateSetting',
     8148        'change [data-setting] input':    'updateSetting',
     8149        'change [data-setting] select':   'updateSetting',
     8150        'change [data-setting] textarea': 'updateSetting',
     8151        'click .delete-attachment':       'deleteAttachment',
     8152        'click .trash-attachment':        'trashAttachment',
     8153        'click .untrash-attachment':      'untrashAttachment',
     8154        'click .edit-attachment':         'editAttachment',
     8155        'keydown':                        'toggleSelectionHandler'
     8156    },
     8157
     8158    initialize: function() {
     8159        this.options = _.defaults( this.options, {
     8160            rerenderOnModelChange: false
     8161        });
     8162
     8163        this.on( 'ready', this.initialFocus );
     8164        // Call 'initialize' directly on the parent class.
     8165        Attachment.prototype.initialize.apply( this, arguments );
     8166    },
     8167
     8168    initialFocus: function() {
     8169        if ( ! wp.media.isTouchDevice ) {
     8170            /*
     8171            Previously focused the first ':input' (the readonly URL text field).
     8172            Since the first ':input' is now a button (delete/trash): when pressing
     8173            spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
     8174            as soon as focus is moved. Explicitly target the first text field for now.
     8175            @todo change initial focus logic, also for accessibility.
     8176            */
     8177            this.$( 'input[type="text"]' ).eq( 0 ).focus();
     8178        }
     8179    },
     8180    /**
     8181     * @param {Object} event
     8182     */
     8183    deleteAttachment: function( event ) {
     8184        event.preventDefault();
     8185
     8186        if ( window.confirm( l10n.warnDelete ) ) {
     8187            this.model.destroy();
     8188            // Keep focus inside media modal
     8189            // after image is deleted
     8190            this.controller.modal.focusManager.focus();
     8191        }
     8192    },
     8193    /**
     8194     * @param {Object} event
     8195     */
     8196    trashAttachment: function( event ) {
     8197        var library = this.controller.library;
     8198        event.preventDefault();
     8199
     8200        if ( wp.media.view.settings.mediaTrash &&
     8201            'edit-metadata' === this.controller.content.mode() ) {
     8202
     8203            this.model.set( 'status', 'trash' );
     8204            this.model.save().done( function() {
     8205                library._requery( true );
     8206            } );
     8207        }  else {
     8208            this.model.destroy();
     8209        }
     8210    },
     8211    /**
     8212     * @param {Object} event
     8213     */
     8214    untrashAttachment: function( event ) {
     8215        var library = this.controller.library;
     8216        event.preventDefault();
     8217
     8218        this.model.set( 'status', 'inherit' );
     8219        this.model.save().done( function() {
     8220            library._requery( true );
     8221        } );
     8222    },
     8223    /**
     8224     * @param {Object} event
     8225     */
     8226    editAttachment: function( event ) {
     8227        var editState = this.controller.states.get( 'edit-image' );
     8228        if ( window.imageEdit && editState ) {
     8229            event.preventDefault();
     8230
     8231            editState.set( 'image', this.model );
     8232            this.controller.setState( 'edit-image' );
     8233        } else {
     8234            this.$el.addClass('needs-refresh');
     8235        }
     8236    },
     8237    /**
     8238     * When reverse tabbing(shift+tab) out of the right details panel, deliver
     8239     * the focus to the item in the list that was being edited.
     8240     *
     8241     * @param {Object} event
     8242     */
     8243    toggleSelectionHandler: function( event ) {
     8244        if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
     8245            this.controller.trigger( 'attachment:details:shift-tab', event );
     8246            return false;
     8247        }
     8248
     8249        if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     8250            this.controller.trigger( 'attachment:keydown:arrow', event );
     8251            return;
     8252        }
     8253    }
     8254});
     8255
     8256module.exports = Details;
     8257
     8258
     8259/***/ }),
     8260/* 88 */
     8261/***/ (function(module, exports) {
     8262
     8263var View = wp.media.View,
     8264    AttachmentCompat;
     8265
     8266/**
     8267 * wp.media.view.AttachmentCompat
     8268 *
     8269 * A view to display fields added via the `attachment_fields_to_edit` filter.
    43938270 *
    43948271 * @memberOf wp.media.view
     
    43998276 * @augments Backbone.View
    44008277 */
    4401 var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{
    4402     tagName:    'button',
    4403     className:  'media-button',
    4404     attributes: { type: 'button' },
     8278AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{
     8279    tagName:   'form',
     8280    className: 'compat-item',
    44058281
    44068282    events: {
    4407         'click': 'click'
    4408     },
    4409 
    4410     defaults: {
    4411         text:     '',
    4412         style:    '',
    4413         size:     'large',
    4414         disabled: false
     8283        'submit':          'preventDefault',
     8284        'change input':    'save',
     8285        'change select':   'save',
     8286        'change textarea': 'save'
    44158287    },
    44168288
    44178289    initialize: function() {
     8290        this.listenTo( this.model, 'change:compat', this.render );
     8291    },
     8292    /**
     8293     * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     8294     */
     8295    dispose: function() {
     8296        if ( this.$(':focus').length ) {
     8297            this.save();
     8298        }
    44188299        /**
    4419          * Create a model with the provided `defaults`.
    4420          *
    4421          * @member {Backbone.Model}
     8300         * call 'dispose' directly on the parent class
    44228301         */
    4423         this.model = new Backbone.Model( this.defaults );
    4424 
    4425         // If any of the `options` have a key from `defaults`, apply its
    4426         // value to the `model` and remove it from the `options object.
    4427         _.each( this.defaults, function( def, key ) {
    4428             var value = this.options[ key ];
    4429             if ( _.isUndefined( value ) ) {
    4430                 return;
    4431             }
    4432 
    4433             this.model.set( key, value );
    4434             delete this.options[ key ];
    4435         }, this );
    4436 
    4437         this.listenTo( this.model, 'change', this.render );
    4438     },
    4439     /**
    4440      * @returns {wp.media.view.Button} Returns itself to allow chaining
     8302        return View.prototype.dispose.apply( this, arguments );
     8303    },
     8304    /**
     8305     * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    44418306     */
    44428307    render: function() {
    4443         var classes = [ 'button', this.className ],
    4444             model = this.model.toJSON();
    4445 
    4446         if ( model.style ) {
    4447             classes.push( 'button-' + model.style );
    4448         }
    4449 
    4450         if ( model.size ) {
    4451             classes.push( 'button-' + model.size );
    4452         }
    4453 
    4454         classes = _.uniq( classes.concat( this.options.classes ) );
    4455         this.el.className = classes.join(' ');
    4456 
    4457         this.$el.attr( 'disabled', model.disabled );
    4458         this.$el.text( this.model.get('text') );
    4459 
     8308        var compat = this.model.get('compat');
     8309        if ( ! compat || ! compat.item ) {
     8310            return;
     8311        }
     8312
     8313        this.views.detach();
     8314        this.$el.html( compat.item );
     8315        this.views.render();
    44608316        return this;
    44618317    },
     
    44638319     * @param {Object} event
    44648320     */
    4465     click: function( event ) {
    4466         if ( '#' === this.attributes.href ) {
     8321    preventDefault: function( event ) {
     8322        event.preventDefault();
     8323    },
     8324    /**
     8325     * @param {Object} event
     8326     */
     8327    save: function( event ) {
     8328        var data = {};
     8329
     8330        if ( event ) {
    44678331            event.preventDefault();
    44688332        }
    44698333
    4470         if ( this.options.click && ! this.model.get('disabled') ) {
    4471             this.options.click.apply( this, arguments );
    4472         }
     8334        _.each( this.$el.serializeArray(), function( pair ) {
     8335            data[ pair.name ] = pair.value;
     8336        });
     8337
     8338        this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
     8339        this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
     8340    },
     8341
     8342    postSave: function() {
     8343        this.controller.trigger( 'attachment:compat:ready', ['ready'] );
    44738344    }
    44748345});
    44758346
    4476 module.exports = Button;
    4477 
    4478 },{}],36:[function(require,module,exports){
    4479 var View = wp.media.View,
    4480     UploaderStatus = wp.media.view.UploaderStatus,
    4481     l10n = wp.media.view.l10n,
    4482     $ = jQuery,
    4483     Cropper;
     8347module.exports = AttachmentCompat;
     8348
     8349
     8350/***/ }),
     8351/* 89 */
     8352/***/ (function(module, exports) {
    44848353
    44858354/**
    4486  * wp.media.view.Cropper
    4487  *
    4488  * Uses the imgAreaSelect plugin to allow a user to crop an image.
    4489  *
    4490  * Takes imgAreaSelect options from
    4491  * wp.customize.HeaderControl.calculateImageSelectOptions via
    4492  * wp.customize.HeaderControl.openMM.
     8355 * wp.media.view.Iframe
    44938356 *
    44948357 * @memberOf wp.media.view
     
    44998362 * @augments Backbone.View
    45008363 */
    4501 Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{
    4502     className: 'crop-content',
    4503     template: wp.template('crop-content'),
    4504     initialize: function() {
    4505         _.bindAll(this, 'onImageLoad');
    4506     },
    4507     ready: function() {
    4508         this.controller.frame.on('content:error:crop', this.onError, this);
    4509         this.$image = this.$el.find('.crop-image');
    4510         this.$image.on('load', this.onImageLoad);
    4511         $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
    4512     },
    4513     remove: function() {
    4514         $(window).off('resize.cropper');
    4515         this.$el.remove();
    4516         this.$el.off();
    4517         View.prototype.remove.apply(this, arguments);
    4518     },
    4519     prepare: function() {
    4520         return {
    4521             title: l10n.cropYourImage,
    4522             url: this.options.attachment.get('url')
    4523         };
    4524     },
    4525     onImageLoad: function() {
    4526         var imgOptions = this.controller.get('imgSelectOptions'),
    4527             imgSelect;
    4528 
    4529         if (typeof imgOptions === 'function') {
    4530             imgOptions = imgOptions(this.options.attachment, this.controller);
    4531         }
    4532 
    4533         imgOptions = _.extend(imgOptions, {
    4534             parent: this.$el,
    4535             onInit: function() {
    4536                 this.parent.children().on( 'mousedown touchstart', function( e ){
    4537 
    4538                     if ( e.shiftKey ) {
    4539                         imgSelect.setOptions( {
    4540                             aspectRatio: '1:1'
    4541                         } );
    4542                     } else {
    4543                         imgSelect.setOptions( {
    4544                             aspectRatio: false
    4545                         } );
    4546                     }
    4547                 } );
    4548             }
    4549         } );
    4550         this.trigger('image-loaded');
    4551         imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
    4552     },
    4553     onError: function() {
    4554         var filename = this.options.attachment.get('filename');
    4555 
    4556         this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    4557             filename: UploaderStatus.prototype.filename(filename),
    4558             message: window._wpMediaViewsL10n.cropError
    4559         }), { at: 0 });
     8364var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{
     8365    className: 'media-iframe',
     8366    /**
     8367     * @returns {wp.media.view.Iframe} Returns itself to allow chaining
     8368     */
     8369    render: function() {
     8370        this.views.detach();
     8371        this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
     8372        this.views.render();
     8373        return this;
    45608374    }
    45618375});
    45628376
    4563 module.exports = Cropper;
    4564 
    4565 },{}],37:[function(require,module,exports){
    4566 var View = wp.media.View,
    4567     EditImage;
    4568 
    4569 /**
    4570  * wp.media.view.EditImage
    4571  *
    4572  * @memberOf wp.media.view
    4573  *
    4574  * @class
    4575  * @augments wp.media.View
    4576  * @augments wp.Backbone.View
    4577  * @augments Backbone.View
    4578  */
    4579 EditImage = View.extend(/** @lends wp.media.view.EditImage.prototype */{
    4580     className: 'image-editor',
    4581     template: wp.template('image-editor'),
    4582 
    4583     initialize: function( options ) {
    4584         this.editor = window.imageEdit;
    4585         this.controller = options.controller;
    4586         View.prototype.initialize.apply( this, arguments );
    4587     },
    4588 
    4589     prepare: function() {
    4590         return this.model.toJSON();
    4591     },
    4592 
    4593     loadEditor: function() {
    4594         var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
    4595         dfd.done( _.bind( this.focus, this ) );
    4596     },
    4597 
    4598     focus: function() {
    4599         this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
    4600     },
    4601 
    4602     back: function() {
    4603         var lastState = this.controller.lastState();
    4604         this.controller.setState( lastState );
    4605     },
    4606 
    4607     refresh: function() {
    4608         this.model.fetch();
    4609     },
    4610 
    4611     save: function() {
    4612         var lastState = this.controller.lastState();
    4613 
    4614         this.model.fetch().done( _.bind( function() {
    4615             this.controller.setState( lastState );
    4616         }, this ) );
    4617     }
    4618 
    4619 });
    4620 
    4621 module.exports = EditImage;
    4622 
    4623 },{}],38:[function(require,module,exports){
     8377module.exports = Iframe;
     8378
     8379
     8380/***/ }),
     8381/* 90 */
     8382/***/ (function(module, exports) {
     8383
    46248384/**
    46258385 * wp.media.view.Embed
     
    46878447module.exports = Embed;
    46888448
    4689 },{}],39:[function(require,module,exports){
    4690 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    4691     EmbedImage;
     8449
     8450/***/ }),
     8451/* 91 */
     8452/***/ (function(module, exports) {
    46928453
    46938454/**
    4694  * wp.media.view.EmbedImage
     8455 * wp.media.view.Label
    46958456 *
    46968457 * @memberOf wp.media.view
    46978458 *
    46988459 * @class
    4699  * @augments wp.media.view.Settings.AttachmentDisplay
    4700  * @augments wp.media.view.Settings
    47018460 * @augments wp.media.View
    47028461 * @augments wp.Backbone.View
    47038462 * @augments Backbone.View
    47048463 */
    4705 EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{
    4706     className: 'embed-media-settings',
    4707     template:  wp.template('embed-image-settings'),
     8464var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{
     8465    tagName: 'label',
     8466    className: 'screen-reader-text',
    47088467
    47098468    initialize: function() {
    4710         /**
    4711          * Call `initialize` directly on parent class with passed arguments
    4712          */
    4713         AttachmentDisplay.prototype.initialize.apply( this, arguments );
    4714         this.listenTo( this.model, 'change:url', this.updateImage );
    4715     },
    4716 
    4717     updateImage: function() {
    4718         this.$('img').attr( 'src', this.model.get('url') );
     8469        this.value = this.options.value;
     8470    },
     8471
     8472    render: function() {
     8473        this.$el.html( this.value );
     8474
     8475        return this;
    47198476    }
    47208477});
    47218478
    4722 module.exports = EmbedImage;
    4723 
    4724 },{}],40:[function(require,module,exports){
     8479module.exports = Label;
     8480
     8481
     8482/***/ }),
     8483/* 92 */
     8484/***/ (function(module, exports) {
     8485
     8486var View = wp.media.View,
     8487    $ = jQuery,
     8488    EmbedUrl;
     8489
     8490/**
     8491 * wp.media.view.EmbedUrl
     8492 *
     8493 * @memberOf wp.media.view
     8494 *
     8495 * @class
     8496 * @augments wp.media.View
     8497 * @augments wp.Backbone.View
     8498 * @augments Backbone.View
     8499 */
     8500EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{
     8501    tagName:   'label',
     8502    className: 'embed-url',
     8503
     8504    events: {
     8505        'input':  'url',
     8506        'keyup':  'url',
     8507        'change': 'url'
     8508    },
     8509
     8510    initialize: function() {
     8511        this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
     8512        this.input = this.$input[0];
     8513
     8514        this.spinner = $('<span class="spinner" />')[0];
     8515        this.$el.append([ this.input, this.spinner ]);
     8516
     8517        this.listenTo( this.model, 'change:url', this.render );
     8518
     8519        if ( this.model.get( 'url' ) ) {
     8520            _.delay( _.bind( function () {
     8521                this.model.trigger( 'change:url' );
     8522            }, this ), 500 );
     8523        }
     8524    },
     8525    /**
     8526     * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
     8527     */
     8528    render: function() {
     8529        var $input = this.$input;
     8530
     8531        if ( $input.is(':focus') ) {
     8532            return;
     8533        }
     8534
     8535        this.input.value = this.model.get('url') || 'http://';
     8536        /**
     8537         * Call `render` directly on parent class with passed arguments
     8538         */
     8539        View.prototype.render.apply( this, arguments );
     8540        return this;
     8541    },
     8542
     8543    ready: function() {
     8544        if ( ! wp.media.isTouchDevice ) {
     8545            this.focus();
     8546        }
     8547    },
     8548
     8549    url: function( event ) {
     8550        this.model.set( 'url', $.trim( event.target.value ) );
     8551    },
     8552
     8553    /**
     8554     * If the input is visible, focus and select its contents.
     8555     */
     8556    focus: function() {
     8557        var $input = this.$input;
     8558        if ( $input.is(':visible') ) {
     8559            $input.focus()[0].select();
     8560        }
     8561    }
     8562});
     8563
     8564module.exports = EmbedUrl;
     8565
     8566
     8567/***/ }),
     8568/* 93 */
     8569/***/ (function(module, exports) {
     8570
    47258571var $ = jQuery,
    47268572    EmbedLink;
     
    48218667module.exports = EmbedLink;
    48228668
    4823 },{}],41:[function(require,module,exports){
    4824 var View = wp.media.View,
    4825     $ = jQuery,
    4826     EmbedUrl;
     8669
     8670/***/ }),
     8671/* 94 */
     8672/***/ (function(module, exports) {
     8673
     8674var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     8675    EmbedImage;
    48278676
    48288677/**
    4829  * wp.media.view.EmbedUrl
     8678 * wp.media.view.EmbedImage
    48308679 *
    48318680 * @memberOf wp.media.view
    48328681 *
    48338682 * @class
     8683 * @augments wp.media.view.Settings.AttachmentDisplay
     8684 * @augments wp.media.view.Settings
    48348685 * @augments wp.media.View
    48358686 * @augments wp.Backbone.View
    48368687 * @augments Backbone.View
    48378688 */
    4838 EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{
    4839     tagName:   'label',
    4840     className: 'embed-url',
    4841 
    4842     events: {
    4843         'input':  'url',
    4844         'keyup':  'url',
    4845         'change': 'url'
    4846     },
     8689EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{
     8690    className: 'embed-media-settings',
     8691    template:  wp.template('embed-image-settings'),
    48478692
    48488693    initialize: function() {
    4849         this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
    4850         this.input = this.$input[0];
    4851 
    4852         this.spinner = $('<span class="spinner" />')[0];
    4853         this.$el.append([ this.input, this.spinner ]);
    4854 
    4855         this.listenTo( this.model, 'change:url', this.render );
    4856 
    4857         if ( this.model.get( 'url' ) ) {
    4858             _.delay( _.bind( function () {
    4859                 this.model.trigger( 'change:url' );
    4860             }, this ), 500 );
    4861         }
    4862     },
    4863     /**
    4864      * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
    4865      */
    4866     render: function() {
    4867         var $input = this.$input;
    4868 
    4869         if ( $input.is(':focus') ) {
    4870             return;
    4871         }
    4872 
    4873         this.input.value = this.model.get('url') || 'http://';
    48748694        /**
    4875          * Call `render` directly on parent class with passed arguments
     8695         * Call `initialize` directly on parent class with passed arguments
    48768696         */
    4877         View.prototype.render.apply( this, arguments );
    4878         return this;
    4879     },
    4880 
    4881     ready: function() {
    4882         if ( ! wp.media.isTouchDevice ) {
    4883             this.focus();
    4884         }
    4885     },
    4886 
    4887     url: function( event ) {
    4888         this.model.set( 'url', $.trim( event.target.value ) );
    4889     },
    4890 
    4891     /**
    4892      * If the input is visible, focus and select its contents.
    4893      */
    4894     focus: function() {
    4895         var $input = this.$input;
    4896         if ( $input.is(':visible') ) {
    4897             $input.focus()[0].select();
    4898         }
     8697        AttachmentDisplay.prototype.initialize.apply( this, arguments );
     8698        this.listenTo( this.model, 'change:url', this.updateImage );
     8699    },
     8700
     8701    updateImage: function() {
     8702        this.$('img').attr( 'src', this.model.get('url') );
    48998703    }
    49008704});
    49018705
    4902 module.exports = EmbedUrl;
    4903 
    4904 },{}],42:[function(require,module,exports){
    4905 /**
    4906  * wp.media.view.FocusManager
    4907  *
    4908  * @memberOf wp.media.view
    4909  *
    4910  * @class
    4911  * @augments wp.media.View
    4912  * @augments wp.Backbone.View
    4913  * @augments Backbone.View
    4914  */
    4915 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{
    4916 
    4917     events: {
    4918         'keydown': 'constrainTabbing'
    4919     },
    4920 
    4921     focus: function() { // Reset focus on first left menu item
    4922         this.$('.media-menu-item').first().focus();
    4923     },
    4924     /**
    4925      * @param {Object} event
    4926      */
    4927     constrainTabbing: function( event ) {
    4928         var tabbables;
    4929 
    4930         // Look for the tab key.
    4931         if ( 9 !== event.keyCode ) {
    4932             return;
    4933         }
    4934 
    4935         // Skip the file input added by Plupload.
    4936         tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
    4937 
    4938         // Keep tab focus within media modal while it's open
    4939         if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
    4940             tabbables.first().focus();
    4941             return false;
    4942         } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
    4943             tabbables.last().focus();
    4944             return false;
    4945         }
    4946     }
    4947 
    4948 });
    4949 
    4950 module.exports = FocusManager;
    4951 
    4952 },{}],43:[function(require,module,exports){
    4953 /**
    4954  * wp.media.view.Frame
    4955  *
    4956  * A frame is a composite view consisting of one or more regions and one or more
    4957  * states.
    4958  *
    4959  * @memberOf wp.media.view
    4960  *
    4961  * @see wp.media.controller.State
    4962  * @see wp.media.controller.Region
    4963  *
    4964  * @class
    4965  * @augments wp.media.View
    4966  * @augments wp.Backbone.View
    4967  * @augments Backbone.View
    4968  * @mixes wp.media.controller.StateMachine
    4969  */
    4970 var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{
    4971     initialize: function() {
    4972         _.defaults( this.options, {
    4973             mode: [ 'select' ]
    4974         });
    4975         this._createRegions();
    4976         this._createStates();
    4977         this._createModes();
    4978     },
    4979 
    4980     _createRegions: function() {
    4981         // Clone the regions array.
    4982         this.regions = this.regions ? this.regions.slice() : [];
    4983 
    4984         // Initialize regions.
    4985         _.each( this.regions, function( region ) {
    4986             this[ region ] = new wp.media.controller.Region({
    4987                 view:     this,
    4988                 id:       region,
    4989                 selector: '.media-frame-' + region
    4990             });
    4991         }, this );
    4992     },
    4993     /**
    4994      * Create the frame's states.
    4995      *
    4996      * @see wp.media.controller.State
    4997      * @see wp.media.controller.StateMachine
    4998      *
    4999      * @fires wp.media.controller.State#ready
    5000      */
    5001     _createStates: function() {
    5002         // Create the default `states` collection.
    5003         this.states = new Backbone.Collection( null, {
    5004             model: wp.media.controller.State
    5005         });
    5006 
    5007         // Ensure states have a reference to the frame.
    5008         this.states.on( 'add', function( model ) {
    5009             model.frame = this;
    5010             model.trigger('ready');
    5011         }, this );
    5012 
    5013         if ( this.options.states ) {
    5014             this.states.add( this.options.states );
    5015         }
    5016     },
    5017 
    5018     /**
    5019      * A frame can be in a mode or multiple modes at one time.
    5020      *
    5021      * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
    5022      */
    5023     _createModes: function() {
    5024         // Store active "modes" that the frame is in. Unrelated to region modes.
    5025         this.activeModes = new Backbone.Collection();
    5026         this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
    5027 
    5028         _.each( this.options.mode, function( mode ) {
    5029             this.activateMode( mode );
    5030         }, this );
    5031     },
    5032     /**
    5033      * Reset all states on the frame to their defaults.
    5034      *
    5035      * @returns {wp.media.view.Frame} Returns itself to allow chaining
    5036      */
    5037     reset: function() {
    5038         this.states.invoke( 'trigger', 'reset' );
    5039         return this;
    5040     },
    5041     /**
    5042      * Map activeMode collection events to the frame.
    5043      */
    5044     triggerModeEvents: function( model, collection, options ) {
    5045         var collectionEvent,
    5046             modeEventMap = {
    5047                 add: 'activate',
    5048                 remove: 'deactivate'
    5049             },
    5050             eventToTrigger;
    5051         // Probably a better way to do this.
    5052         _.each( options, function( value, key ) {
    5053             if ( value ) {
    5054                 collectionEvent = key;
    5055             }
    5056         } );
    5057 
    5058         if ( ! _.has( modeEventMap, collectionEvent ) ) {
    5059             return;
    5060         }
    5061 
    5062         eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
    5063         this.trigger( eventToTrigger );
    5064     },
    5065     /**
    5066      * Activate a mode on the frame.
    5067      *
    5068      * @param string mode Mode ID.
    5069      * @returns {this} Returns itself to allow chaining.
    5070      */
    5071     activateMode: function( mode ) {
    5072         // Bail if the mode is already active.
    5073         if ( this.isModeActive( mode ) ) {
    5074             return;
    5075         }
    5076         this.activeModes.add( [ { id: mode } ] );
    5077         // Add a CSS class to the frame so elements can be styled for the mode.
    5078         this.$el.addClass( 'mode-' + mode );
    5079 
    5080         return this;
    5081     },
    5082     /**
    5083      * Deactivate a mode on the frame.
    5084      *
    5085      * @param string mode Mode ID.
    5086      * @returns {this} Returns itself to allow chaining.
    5087      */
    5088     deactivateMode: function( mode ) {
    5089         // Bail if the mode isn't active.
    5090         if ( ! this.isModeActive( mode ) ) {
    5091             return this;
    5092         }
    5093         this.activeModes.remove( this.activeModes.where( { id: mode } ) );
    5094         this.$el.removeClass( 'mode-' + mode );
    5095         /**
    5096          * Frame mode deactivation event.
    5097          *
    5098          * @event wp.media.view.Frame#{mode}:deactivate
    5099          */
    5100         this.trigger( mode + ':deactivate' );
    5101 
    5102         return this;
    5103     },
    5104     /**
    5105      * Check if a mode is enabled on the frame.
    5106      *
    5107      * @param  string mode Mode ID.
    5108      * @return bool
    5109      */
    5110     isModeActive: function( mode ) {
    5111         return Boolean( this.activeModes.where( { id: mode } ).length );
    5112     }
    5113 });
    5114 
    5115 // Make the `Frame` a `StateMachine`.
    5116 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
    5117 
    5118 module.exports = Frame;
    5119 
    5120 },{}],44:[function(require,module,exports){
    5121 var Select = wp.media.view.MediaFrame.Select,
    5122     l10n = wp.media.view.l10n,
    5123     ImageDetails;
    5124 
    5125 /**
    5126  * wp.media.view.MediaFrame.ImageDetails
    5127  *
    5128  * A media frame for manipulating an image that's already been inserted
    5129  * into a post.
    5130  *
    5131  * @memberOf wp.media.view.MediaFrame
    5132  *
    5133  * @class
    5134  * @augments wp.media.view.MediaFrame.Select
    5135  * @augments wp.media.view.MediaFrame
    5136  * @augments wp.media.view.Frame
    5137  * @augments wp.media.View
    5138  * @augments wp.Backbone.View
    5139  * @augments Backbone.View
    5140  * @mixes wp.media.controller.StateMachine
    5141  */
    5142 ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{
    5143     defaults: {
    5144         id:      'image',
    5145         url:     '',
    5146         menu:    'image-details',
    5147         content: 'image-details',
    5148         toolbar: 'image-details',
    5149         type:    'link',
    5150         title:    l10n.imageDetailsTitle,
    5151         priority: 120
    5152     },
    5153 
    5154     initialize: function( options ) {
    5155         this.image = new wp.media.model.PostImage( options.metadata );
    5156         this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
    5157         Select.prototype.initialize.apply( this, arguments );
    5158     },
    5159 
    5160     bindHandlers: function() {
    5161         Select.prototype.bindHandlers.apply( this, arguments );
    5162         this.on( 'menu:create:image-details', this.createMenu, this );
    5163         this.on( 'content:create:image-details', this.imageDetailsContent, this );
    5164         this.on( 'content:render:edit-image', this.editImageContent, this );
    5165         this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
    5166         // override the select toolbar
    5167         this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
    5168     },
    5169 
    5170     createStates: function() {
    5171         this.states.add([
    5172             new wp.media.controller.ImageDetails({
    5173                 image: this.image,
    5174                 editable: false
    5175             }),
    5176             new wp.media.controller.ReplaceImage({
    5177                 id: 'replace-image',
    5178                 library: wp.media.query( { type: 'image' } ),
    5179                 image: this.image,
    5180                 multiple:  false,
    5181                 title:     l10n.imageReplaceTitle,
    5182                 toolbar: 'replace',
    5183                 priority:  80,
    5184                 displaySettings: true
    5185             }),
    5186             new wp.media.controller.EditImage( {
    5187                 image: this.image,
    5188                 selection: this.options.selection
    5189             } )
    5190         ]);
    5191     },
    5192 
    5193     imageDetailsContent: function( options ) {
    5194         options.view = new wp.media.view.ImageDetails({
    5195             controller: this,
    5196             model: this.state().image,
    5197             attachment: this.state().image.attachment
    5198         });
    5199     },
    5200 
    5201     editImageContent: function() {
    5202         var state = this.state(),
    5203             model = state.get('image'),
    5204             view;
    5205 
    5206         if ( ! model ) {
    5207             return;
    5208         }
    5209 
    5210         view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
    5211 
    5212         this.content.set( view );
    5213 
    5214         // after bringing in the frame, load the actual editor via an ajax call
    5215         view.loadEditor();
    5216 
    5217     },
    5218 
    5219     renderImageDetailsToolbar: function() {
    5220         this.toolbar.set( new wp.media.view.Toolbar({
    5221             controller: this,
    5222             items: {
    5223                 select: {
    5224                     style:    'primary',
    5225                     text:     l10n.update,
    5226                     priority: 80,
    5227 
    5228                     click: function() {
    5229                         var controller = this.controller,
    5230                             state = controller.state();
    5231 
    5232                         controller.close();
    5233 
    5234                         // not sure if we want to use wp.media.string.image which will create a shortcode or
    5235                         // perhaps wp.html.string to at least to build the <img />
    5236                         state.trigger( 'update', controller.image.toJSON() );
    5237 
    5238                         // Restore and reset the default state.
    5239                         controller.setState( controller.options.state );
    5240                         controller.reset();
    5241                     }
    5242                 }
    5243             }
    5244         }) );
    5245     },
    5246 
    5247     renderReplaceImageToolbar: function() {
    5248         var frame = this,
    5249             lastState = frame.lastState(),
    5250             previous = lastState && lastState.id;
    5251 
    5252         this.toolbar.set( new wp.media.view.Toolbar({
    5253             controller: this,
    5254             items: {
    5255                 back: {
    5256                     text:     l10n.back,
    5257                     priority: 20,
    5258                     click:    function() {
    5259                         if ( previous ) {
    5260                             frame.setState( previous );
    5261                         } else {
    5262                             frame.close();
    5263                         }
    5264                     }
    5265                 },
    5266 
    5267                 replace: {
    5268                     style:    'primary',
    5269                     text:     l10n.replace,
    5270                     priority: 80,
    5271                     requires: { selection: true },
    5272 
    5273                     click: function() {
    5274                         var controller = this.controller,
    5275                             state = controller.state(),
    5276                             selection = state.get( 'selection' ),
    5277                             attachment = selection.single();
    5278 
    5279                         controller.close();
    5280 
    5281                         controller.image.changeAttachment( attachment, state.display( attachment ) );
    5282 
    5283                         // not sure if we want to use wp.media.string.image which will create a shortcode or
    5284                         // perhaps wp.html.string to at least to build the <img />
    5285                         state.trigger( 'replace', controller.image.toJSON() );
    5286 
    5287                         // Restore and reset the default state.
    5288                         controller.setState( controller.options.state );
    5289                         controller.reset();
    5290                     }
    5291                 }
    5292             }
    5293         }) );
    5294     }
    5295 
    5296 });
    5297 
    5298 module.exports = ImageDetails;
    5299 
    5300 },{}],45:[function(require,module,exports){
    5301 var Select = wp.media.view.MediaFrame.Select,
    5302     Library = wp.media.controller.Library,
    5303     l10n = wp.media.view.l10n,
    5304     Post;
    5305 
    5306 /**
    5307  * wp.media.view.MediaFrame.Post
    5308  *
    5309  * The frame for manipulating media on the Edit Post page.
    5310  *
    5311  * @memberOf wp.media.view.MediaFrame
    5312  *
    5313  * @class
    5314  * @augments wp.media.view.MediaFrame.Select
    5315  * @augments wp.media.view.MediaFrame
    5316  * @augments wp.media.view.Frame
    5317  * @augments wp.media.View
    5318  * @augments wp.Backbone.View
    5319  * @augments Backbone.View
    5320  * @mixes wp.media.controller.StateMachine
    5321  */
    5322 Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{
    5323     initialize: function() {
    5324         this.counts = {
    5325             audio: {
    5326                 count: wp.media.view.settings.attachmentCounts.audio,
    5327                 state: 'playlist'
    5328             },
    5329             video: {
    5330                 count: wp.media.view.settings.attachmentCounts.video,
    5331                 state: 'video-playlist'
    5332             }
    5333         };
    5334 
    5335         _.defaults( this.options, {
    5336             multiple:  true,
    5337             editing:   false,
    5338             state:    'insert',
    5339             metadata:  {}
    5340         });
    5341 
    5342         // Call 'initialize' directly on the parent class.
    5343         Select.prototype.initialize.apply( this, arguments );
    5344         this.createIframeStates();
    5345 
    5346     },
    5347 
    5348     /**
    5349      * Create the default states.
    5350      */
    5351     createStates: function() {
    5352         var options = this.options;
    5353 
    5354         this.states.add([
    5355             // Main states.
    5356             new Library({
    5357                 id:         'insert',
    5358                 title:      l10n.insertMediaTitle,
    5359                 priority:   20,
    5360                 toolbar:    'main-insert',
    5361                 filterable: 'all',
    5362                 library:    wp.media.query( options.library ),
    5363                 multiple:   options.multiple ? 'reset' : false,
    5364                 editable:   true,
    5365 
    5366                 // If the user isn't allowed to edit fields,
    5367                 // can they still edit it locally?
    5368                 allowLocalEdits: true,
    5369 
    5370                 // Show the attachment display settings.
    5371                 displaySettings: true,
    5372                 // Update user settings when users adjust the
    5373                 // attachment display settings.
    5374                 displayUserSettings: true
    5375             }),
    5376 
    5377             new Library({
    5378                 id:         'gallery',
    5379                 title:      l10n.createGalleryTitle,
    5380                 priority:   40,
    5381                 toolbar:    'main-gallery',
    5382                 filterable: 'uploaded',
    5383                 multiple:   'add',
    5384                 editable:   false,
    5385 
    5386                 library:  wp.media.query( _.defaults({
    5387                     type: 'image'
    5388                 }, options.library ) )
    5389             }),
    5390 
    5391             // Embed states.
    5392             new wp.media.controller.Embed( { metadata: options.metadata } ),
    5393 
    5394             new wp.media.controller.EditImage( { model: options.editImage } ),
    5395 
    5396             // Gallery states.
    5397             new wp.media.controller.GalleryEdit({
    5398                 library: options.selection,
    5399                 editing: options.editing,
    5400                 menu:    'gallery'
    5401             }),
    5402 
    5403             new wp.media.controller.GalleryAdd(),
    5404 
    5405             new Library({
    5406                 id:         'playlist',
    5407                 title:      l10n.createPlaylistTitle,
    5408                 priority:   60,
    5409                 toolbar:    'main-playlist',
    5410                 filterable: 'uploaded',
    5411                 multiple:   'add',
    5412                 editable:   false,
    5413 
    5414                 library:  wp.media.query( _.defaults({
    5415                     type: 'audio'
    5416                 }, options.library ) )
    5417             }),
    5418 
    5419             // Playlist states.
    5420             new wp.media.controller.CollectionEdit({
    5421                 type: 'audio',
    5422                 collectionType: 'playlist',
    5423                 title:          l10n.editPlaylistTitle,
    5424                 SettingsView:   wp.media.view.Settings.Playlist,
    5425                 library:        options.selection,
    5426                 editing:        options.editing,
    5427                 menu:           'playlist',
    5428                 dragInfoText:   l10n.playlistDragInfo,
    5429                 dragInfo:       false
    5430             }),
    5431 
    5432             new wp.media.controller.CollectionAdd({
    5433                 type: 'audio',
    5434                 collectionType: 'playlist',
    5435                 title: l10n.addToPlaylistTitle
    5436             }),
    5437 
    5438             new Library({
    5439                 id:         'video-playlist',
    5440                 title:      l10n.createVideoPlaylistTitle,
    5441                 priority:   60,
    5442                 toolbar:    'main-video-playlist',
    5443                 filterable: 'uploaded',
    5444                 multiple:   'add',
    5445                 editable:   false,
    5446 
    5447                 library:  wp.media.query( _.defaults({
    5448                     type: 'video'
    5449                 }, options.library ) )
    5450             }),
    5451 
    5452             new wp.media.controller.CollectionEdit({
    5453                 type: 'video',
    5454                 collectionType: 'playlist',
    5455                 title:          l10n.editVideoPlaylistTitle,
    5456                 SettingsView:   wp.media.view.Settings.Playlist,
    5457                 library:        options.selection,
    5458                 editing:        options.editing,
    5459                 menu:           'video-playlist',
    5460                 dragInfoText:   l10n.videoPlaylistDragInfo,
    5461                 dragInfo:       false
    5462             }),
    5463 
    5464             new wp.media.controller.CollectionAdd({
    5465                 type: 'video',
    5466                 collectionType: 'playlist',
    5467                 title: l10n.addToVideoPlaylistTitle
    5468             })
    5469         ]);
    5470 
    5471         if ( wp.media.view.settings.post.featuredImageId ) {
    5472             this.states.add( new wp.media.controller.FeaturedImage() );
    5473         }
    5474     },
    5475 
    5476     bindHandlers: function() {
    5477         var handlers, checkCounts;
    5478 
    5479         Select.prototype.bindHandlers.apply( this, arguments );
    5480 
    5481         this.on( 'activate', this.activate, this );
    5482 
    5483         // Only bother checking media type counts if one of the counts is zero
    5484         checkCounts = _.find( this.counts, function( type ) {
    5485             return type.count === 0;
    5486         } );
    5487 
    5488         if ( typeof checkCounts !== 'undefined' ) {
    5489             this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
    5490         }
    5491 
    5492         this.on( 'menu:create:gallery', this.createMenu, this );
    5493         this.on( 'menu:create:playlist', this.createMenu, this );
    5494         this.on( 'menu:create:video-playlist', this.createMenu, this );
    5495         this.on( 'toolbar:create:main-insert', this.createToolbar, this );
    5496         this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
    5497         this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
    5498         this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
    5499         this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
    5500         this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
    5501 
    5502         handlers = {
    5503             menu: {
    5504                 'default': 'mainMenu',
    5505                 'gallery': 'galleryMenu',
    5506                 'playlist': 'playlistMenu',
    5507                 'video-playlist': 'videoPlaylistMenu'
    5508             },
    5509 
    5510             content: {
    5511                 'embed':          'embedContent',
    5512                 'edit-image':     'editImageContent',
    5513                 'edit-selection': 'editSelectionContent'
    5514             },
    5515 
    5516             toolbar: {
    5517                 'main-insert':      'mainInsertToolbar',
    5518                 'main-gallery':     'mainGalleryToolbar',
    5519                 'gallery-edit':     'galleryEditToolbar',
    5520                 'gallery-add':      'galleryAddToolbar',
    5521                 'main-playlist':    'mainPlaylistToolbar',
    5522                 'playlist-edit':    'playlistEditToolbar',
    5523                 'playlist-add':     'playlistAddToolbar',
    5524                 'main-video-playlist': 'mainVideoPlaylistToolbar',
    5525                 'video-playlist-edit': 'videoPlaylistEditToolbar',
    5526                 'video-playlist-add': 'videoPlaylistAddToolbar'
    5527             }
    5528         };
    5529 
    5530         _.each( handlers, function( regionHandlers, region ) {
    5531             _.each( regionHandlers, function( callback, handler ) {
    5532                 this.on( region + ':render:' + handler, this[ callback ], this );
    5533             }, this );
    5534         }, this );
    5535     },
    5536 
    5537     activate: function() {
    5538         // Hide menu items for states tied to particular media types if there are no items
    5539         _.each( this.counts, function( type ) {
    5540             if ( type.count < 1 ) {
    5541                 this.menuItemVisibility( type.state, 'hide' );
    5542             }
    5543         }, this );
    5544     },
    5545 
    5546     mediaTypeCounts: function( model, attr ) {
    5547         if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
    5548             this.counts[ attr ].count++;
    5549             this.menuItemVisibility( this.counts[ attr ].state, 'show' );
    5550         }
    5551     },
    5552 
    5553     // Menus
    5554     /**
    5555      * @param {wp.Backbone.View} view
    5556      */
    5557     mainMenu: function( view ) {
    5558         view.set({
    5559             'library-separator': new wp.media.View({
    5560                 className: 'separator',
    5561                 priority: 100
    5562             })
    5563         });
    5564     },
    5565 
    5566     menuItemVisibility: function( state, visibility ) {
    5567         var menu = this.menu.get();
    5568         if ( visibility === 'hide' ) {
    5569             menu.hide( state );
    5570         } else if ( visibility === 'show' ) {
    5571             menu.show( state );
    5572         }
    5573     },
    5574     /**
    5575      * @param {wp.Backbone.View} view
    5576      */
    5577     galleryMenu: function( view ) {
    5578         var lastState = this.lastState(),
    5579             previous = lastState && lastState.id,
    5580             frame = this;
    5581 
    5582         view.set({
    5583             cancel: {
    5584                 text:     l10n.cancelGalleryTitle,
    5585                 priority: 20,
    5586                 click:    function() {
    5587                     if ( previous ) {
    5588                         frame.setState( previous );
    5589                     } else {
    5590                         frame.close();
    5591                     }
    5592 
    5593                     // Keep focus inside media modal
    5594                     // after canceling a gallery
    5595                     this.controller.modal.focusManager.focus();
    5596                 }
    5597             },
    5598             separateCancel: new wp.media.View({
    5599                 className: 'separator',
    5600                 priority: 40
    5601             })
    5602         });
    5603     },
    5604 
    5605     playlistMenu: function( view ) {
    5606         var lastState = this.lastState(),
    5607             previous = lastState && lastState.id,
    5608             frame = this;
    5609 
    5610         view.set({
    5611             cancel: {
    5612                 text:     l10n.cancelPlaylistTitle,
    5613                 priority: 20,
    5614                 click:    function() {
    5615                     if ( previous ) {
    5616                         frame.setState( previous );
    5617                     } else {
    5618                         frame.close();
    5619                     }
    5620                 }
    5621             },
    5622             separateCancel: new wp.media.View({
    5623                 className: 'separator',
    5624                 priority: 40
    5625             })
    5626         });
    5627     },
    5628 
    5629     videoPlaylistMenu: function( view ) {
    5630         var lastState = this.lastState(),
    5631             previous = lastState && lastState.id,
    5632             frame = this;
    5633 
    5634         view.set({
    5635             cancel: {
    5636                 text:     l10n.cancelVideoPlaylistTitle,
    5637                 priority: 20,
    5638                 click:    function() {
    5639                     if ( previous ) {
    5640                         frame.setState( previous );
    5641                     } else {
    5642                         frame.close();
    5643                     }
    5644                 }
    5645             },
    5646             separateCancel: new wp.media.View({
    5647                 className: 'separator',
    5648                 priority: 40
    5649             })
    5650         });
    5651     },
    5652 
    5653     // Content
    5654     embedContent: function() {
    5655         var view = new wp.media.view.Embed({
    5656             controller: this,
    5657             model:      this.state()
    5658         }).render();
    5659 
    5660         this.content.set( view );
    5661 
    5662         if ( ! wp.media.isTouchDevice ) {
    5663             view.url.focus();
    5664         }
    5665     },
    5666 
    5667     editSelectionContent: function() {
    5668         var state = this.state(),
    5669             selection = state.get('selection'),
    5670             view;
    5671 
    5672         view = new wp.media.view.AttachmentsBrowser({
    5673             controller: this,
    5674             collection: selection,
    5675             selection:  selection,
    5676             model:      state,
    5677             sortable:   true,
    5678             search:     false,
    5679             date:       false,
    5680             dragInfo:   true,
    5681 
    5682             AttachmentView: wp.media.view.Attachments.EditSelection
    5683         }).render();
    5684 
    5685         view.toolbar.set( 'backToLibrary', {
    5686             text:     l10n.returnToLibrary,
    5687             priority: -100,
    5688 
    5689             click: function() {
    5690                 this.controller.content.mode('browse');
    5691             }
    5692         });
    5693 
    5694         // Browse our library of attachments.
    5695         this.content.set( view );
    5696 
    5697         // Trigger the controller to set focus
    5698         this.trigger( 'edit:selection', this );
    5699     },
    5700 
    5701     editImageContent: function() {
    5702         var image = this.state().get('image'),
    5703             view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
    5704 
    5705         this.content.set( view );
    5706 
    5707         // after creating the wrapper view, load the actual editor via an ajax call
    5708         view.loadEditor();
    5709 
    5710     },
    5711 
    5712     // Toolbars
    5713 
    5714     /**
    5715      * @param {wp.Backbone.View} view
    5716      */
    5717     selectionStatusToolbar: function( view ) {
    5718         var editable = this.state().get('editable');
    5719 
    5720         view.set( 'selection', new wp.media.view.Selection({
    5721             controller: this,
    5722             collection: this.state().get('selection'),
    5723             priority:   -40,
    5724 
    5725             // If the selection is editable, pass the callback to
    5726             // switch the content mode.
    5727             editable: editable && function() {
    5728                 this.controller.content.mode('edit-selection');
    5729             }
    5730         }).render() );
    5731     },
    5732 
    5733     /**
    5734      * @param {wp.Backbone.View} view
    5735      */
    5736     mainInsertToolbar: function( view ) {
    5737         var controller = this;
    5738 
    5739         this.selectionStatusToolbar( view );
    5740 
    5741         view.set( 'insert', {
    5742             style:    'primary',
    5743             priority: 80,
    5744             text:     l10n.insertIntoPost,
    5745             requires: { selection: true },
    5746 
    5747             /**
    5748              * @callback
    5749              * @fires wp.media.controller.State#insert
    5750              */
    5751             click: function() {
    5752                 var state = controller.state(),
    5753                     selection = state.get('selection');
    5754 
    5755                 controller.close();
    5756                 state.trigger( 'insert', selection ).reset();
    5757             }
    5758         });
    5759     },
    5760 
    5761     /**
    5762      * @param {wp.Backbone.View} view
    5763      */
    5764     mainGalleryToolbar: function( view ) {
    5765         var controller = this;
    5766 
    5767         this.selectionStatusToolbar( view );
    5768 
    5769         view.set( 'gallery', {
    5770             style:    'primary',
    5771             text:     l10n.createNewGallery,
    5772             priority: 60,
    5773             requires: { selection: true },
    5774 
    5775             click: function() {
    5776                 var selection = controller.state().get('selection'),
    5777                     edit = controller.state('gallery-edit'),
    5778                     models = selection.where({ type: 'image' });
    5779 
    5780                 edit.set( 'library', new wp.media.model.Selection( models, {
    5781                     props:    selection.props.toJSON(),
    5782                     multiple: true
    5783                 }) );
    5784 
    5785                 this.controller.setState('gallery-edit');
    5786 
    5787                 // Keep focus inside media modal
    5788                 // after jumping to gallery view
    5789                 this.controller.modal.focusManager.focus();
    5790             }
    5791         });
    5792     },
    5793 
    5794     mainPlaylistToolbar: function( view ) {
    5795         var controller = this;
    5796 
    5797         this.selectionStatusToolbar( view );
    5798 
    5799         view.set( 'playlist', {
    5800             style:    'primary',
    5801             text:     l10n.createNewPlaylist,
    5802             priority: 100,
    5803             requires: { selection: true },
    5804 
    5805             click: function() {
    5806                 var selection = controller.state().get('selection'),
    5807                     edit = controller.state('playlist-edit'),
    5808                     models = selection.where({ type: 'audio' });
    5809 
    5810                 edit.set( 'library', new wp.media.model.Selection( models, {
    5811                     props:    selection.props.toJSON(),
    5812                     multiple: true
    5813                 }) );
    5814 
    5815                 this.controller.setState('playlist-edit');
    5816 
    5817                 // Keep focus inside media modal
    5818                 // after jumping to playlist view
    5819                 this.controller.modal.focusManager.focus();
    5820             }
    5821         });
    5822     },
    5823 
    5824     mainVideoPlaylistToolbar: function( view ) {
    5825         var controller = this;
    5826 
    5827         this.selectionStatusToolbar( view );
    5828 
    5829         view.set( 'video-playlist', {
    5830             style:    'primary',
    5831             text:     l10n.createNewVideoPlaylist,
    5832             priority: 100,
    5833             requires: { selection: true },
    5834 
    5835             click: function() {
    5836                 var selection = controller.state().get('selection'),
    5837                     edit = controller.state('video-playlist-edit'),
    5838                     models = selection.where({ type: 'video' });
    5839 
    5840                 edit.set( 'library', new wp.media.model.Selection( models, {
    5841                     props:    selection.props.toJSON(),
    5842                     multiple: true
    5843                 }) );
    5844 
    5845                 this.controller.setState('video-playlist-edit');
    5846 
    5847                 // Keep focus inside media modal
    5848                 // after jumping to video playlist view
    5849                 this.controller.modal.focusManager.focus();
    5850             }
    5851         });
    5852     },
    5853 
    5854     featuredImageToolbar: function( toolbar ) {
    5855         this.createSelectToolbar( toolbar, {
    5856             text:  l10n.setFeaturedImage,
    5857             state: this.options.state
    5858         });
    5859     },
    5860 
    5861     mainEmbedToolbar: function( toolbar ) {
    5862         toolbar.view = new wp.media.view.Toolbar.Embed({
    5863             controller: this
    5864         });
    5865     },
    5866 
    5867     galleryEditToolbar: function() {
    5868         var editing = this.state().get('editing');
    5869         this.toolbar.set( new wp.media.view.Toolbar({
    5870             controller: this,
    5871             items: {
    5872                 insert: {
    5873                     style:    'primary',
    5874                     text:     editing ? l10n.updateGallery : l10n.insertGallery,
    5875                     priority: 80,
    5876                     requires: { library: true },
    5877 
    5878                     /**
    5879                      * @fires wp.media.controller.State#update
    5880                      */
    5881                     click: function() {
    5882                         var controller = this.controller,
    5883                             state = controller.state();
    5884 
    5885                         controller.close();
    5886                         state.trigger( 'update', state.get('library') );
    5887 
    5888                         // Restore and reset the default state.
    5889                         controller.setState( controller.options.state );
    5890                         controller.reset();
    5891                     }
    5892                 }
    5893             }
    5894         }) );
    5895     },
    5896 
    5897     galleryAddToolbar: function() {
    5898         this.toolbar.set( new wp.media.view.Toolbar({
    5899             controller: this,
    5900             items: {
    5901                 insert: {
    5902                     style:    'primary',
    5903                     text:     l10n.addToGallery,
    5904                     priority: 80,
    5905                     requires: { selection: true },
    5906 
    5907                     /**
    5908                      * @fires wp.media.controller.State#reset
    5909                      */
    5910                     click: function() {
    5911                         var controller = this.controller,
    5912                             state = controller.state(),
    5913                             edit = controller.state('gallery-edit');
    5914 
    5915                         edit.get('library').add( state.get('selection').models );
    5916                         state.trigger('reset');
    5917                         controller.setState('gallery-edit');
    5918                     }
    5919                 }
    5920             }
    5921         }) );
    5922     },
    5923 
    5924     playlistEditToolbar: function() {
    5925         var editing = this.state().get('editing');
    5926         this.toolbar.set( new wp.media.view.Toolbar({
    5927             controller: this,
    5928             items: {
    5929                 insert: {
    5930                     style:    'primary',
    5931                     text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
    5932                     priority: 80,
    5933                     requires: { library: true },
    5934 
    5935                     /**
    5936                      * @fires wp.media.controller.State#update
    5937                      */
    5938                     click: function() {
    5939                         var controller = this.controller,
    5940                             state = controller.state();
    5941 
    5942                         controller.close();
    5943                         state.trigger( 'update', state.get('library') );
    5944 
    5945                         // Restore and reset the default state.
    5946                         controller.setState( controller.options.state );
    5947                         controller.reset();
    5948                     }
    5949                 }
    5950             }
    5951         }) );
    5952     },
    5953 
    5954     playlistAddToolbar: function() {
    5955         this.toolbar.set( new wp.media.view.Toolbar({
    5956             controller: this,
    5957             items: {
    5958                 insert: {
    5959                     style:    'primary',
    5960                     text:     l10n.addToPlaylist,
    5961                     priority: 80,
    5962                     requires: { selection: true },
    5963 
    5964                     /**
    5965                      * @fires wp.media.controller.State#reset
    5966                      */
    5967                     click: function() {
    5968                         var controller = this.controller,
    5969                             state = controller.state(),
    5970                             edit = controller.state('playlist-edit');
    5971 
    5972                         edit.get('library').add( state.get('selection').models );
    5973                         state.trigger('reset');
    5974                         controller.setState('playlist-edit');
    5975                     }
    5976                 }
    5977             }
    5978         }) );
    5979     },
    5980 
    5981     videoPlaylistEditToolbar: function() {
    5982         var editing = this.state().get('editing');
    5983         this.toolbar.set( new wp.media.view.Toolbar({
    5984             controller: this,
    5985             items: {
    5986                 insert: {
    5987                     style:    'primary',
    5988                     text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
    5989                     priority: 140,
    5990                     requires: { library: true },
    5991 
    5992                     click: function() {
    5993                         var controller = this.controller,
    5994                             state = controller.state(),
    5995                             library = state.get('library');
    5996 
    5997                         library.type = 'video';
    5998 
    5999                         controller.close();
    6000                         state.trigger( 'update', library );
    6001 
    6002                         // Restore and reset the default state.
    6003                         controller.setState( controller.options.state );
    6004                         controller.reset();
    6005                     }
    6006                 }
    6007             }
    6008         }) );
    6009     },
    6010 
    6011     videoPlaylistAddToolbar: function() {
    6012         this.toolbar.set( new wp.media.view.Toolbar({
    6013             controller: this,
    6014             items: {
    6015                 insert: {
    6016                     style:    'primary',
    6017                     text:     l10n.addToVideoPlaylist,
    6018                     priority: 140,
    6019                     requires: { selection: true },
    6020 
    6021                     click: function() {
    6022                         var controller = this.controller,
    6023                             state = controller.state(),
    6024                             edit = controller.state('video-playlist-edit');
    6025 
    6026                         edit.get('library').add( state.get('selection').models );
    6027                         state.trigger('reset');
    6028                         controller.setState('video-playlist-edit');
    6029                     }
    6030                 }
    6031             }
    6032         }) );
    6033     }
    6034 });
    6035 
    6036 module.exports = Post;
    6037 
    6038 },{}],46:[function(require,module,exports){
    6039 var MediaFrame = wp.media.view.MediaFrame,
    6040     l10n = wp.media.view.l10n,
    6041     Select;
    6042 
    6043 /**
    6044  * wp.media.view.MediaFrame.Select
    6045  *
    6046  * A frame for selecting an item or items from the media library.
    6047  *
    6048  * @memberOf wp.media.view.MediaFrame
    6049  *
    6050  * @class
    6051  * @augments wp.media.view.MediaFrame
    6052  * @augments wp.media.view.Frame
    6053  * @augments wp.media.View
    6054  * @augments wp.Backbone.View
    6055  * @augments Backbone.View
    6056  * @mixes wp.media.controller.StateMachine
    6057  */
    6058 Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{
    6059     initialize: function() {
    6060         // Call 'initialize' directly on the parent class.
    6061         MediaFrame.prototype.initialize.apply( this, arguments );
    6062 
    6063         _.defaults( this.options, {
    6064             selection: [],
    6065             library:   {},
    6066             multiple:  false,
    6067             state:    'library'
    6068         });
    6069 
    6070         this.createSelection();
    6071         this.createStates();
    6072         this.bindHandlers();
    6073     },
    6074 
    6075     /**
    6076      * Attach a selection collection to the frame.
    6077      *
    6078      * A selection is a collection of attachments used for a specific purpose
    6079      * by a media frame. e.g. Selecting an attachment (or many) to insert into
    6080      * post content.
    6081      *
    6082      * @see media.model.Selection
    6083      */
    6084     createSelection: function() {
    6085         var selection = this.options.selection;
    6086 
    6087         if ( ! (selection instanceof wp.media.model.Selection) ) {
    6088             this.options.selection = new wp.media.model.Selection( selection, {
    6089                 multiple: this.options.multiple
    6090             });
    6091         }
    6092 
    6093         this._selection = {
    6094             attachments: new wp.media.model.Attachments(),
    6095             difference: []
    6096         };
    6097     },
    6098 
    6099     /**
    6100      * Create the default states on the frame.
    6101      */
    6102     createStates: function() {
    6103         var options = this.options;
    6104 
    6105         if ( this.options.states ) {
    6106             return;
    6107         }
    6108 
    6109         // Add the default states.
    6110         this.states.add([
    6111             // Main states.
    6112             new wp.media.controller.Library({
    6113                 library:   wp.media.query( options.library ),
    6114                 multiple:  options.multiple,
    6115                 title:     options.title,
    6116                 priority:  20
    6117             })
    6118         ]);
    6119     },
    6120 
    6121     /**
    6122      * Bind region mode event callbacks.
    6123      *
    6124      * @see media.controller.Region.render
    6125      */
    6126     bindHandlers: function() {
    6127         this.on( 'router:create:browse', this.createRouter, this );
    6128         this.on( 'router:render:browse', this.browseRouter, this );
    6129         this.on( 'content:create:browse', this.browseContent, this );
    6130         this.on( 'content:render:upload', this.uploadContent, this );
    6131         this.on( 'toolbar:create:select', this.createSelectToolbar, this );
    6132     },
    6133 
    6134     /**
    6135      * Render callback for the router region in the `browse` mode.
    6136      *
    6137      * @param {wp.media.view.Router} routerView
    6138      */
    6139     browseRouter: function( routerView ) {
    6140         routerView.set({
    6141             upload: {
    6142                 text:     l10n.uploadFilesTitle,
    6143                 priority: 20
    6144             },
    6145             browse: {
    6146                 text:     l10n.mediaLibraryTitle,
    6147                 priority: 40
    6148             }
    6149         });
    6150     },
    6151 
    6152     /**
    6153      * Render callback for the content region in the `browse` mode.
    6154      *
    6155      * @param {wp.media.controller.Region} contentRegion
    6156      */
    6157     browseContent: function( contentRegion ) {
    6158         var state = this.state();
    6159 
    6160         this.$el.removeClass('hide-toolbar');
    6161 
    6162         // Browse our library of attachments.
    6163         contentRegion.view = new wp.media.view.AttachmentsBrowser({
    6164             controller: this,
    6165             collection: state.get('library'),
    6166             selection:  state.get('selection'),
    6167             model:      state,
    6168             sortable:   state.get('sortable'),
    6169             search:     state.get('searchable'),
    6170             filters:    state.get('filterable'),
    6171             date:       state.get('date'),
    6172             display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
    6173             dragInfo:   state.get('dragInfo'),
    6174 
    6175             idealColumnWidth: state.get('idealColumnWidth'),
    6176             suggestedWidth:   state.get('suggestedWidth'),
    6177             suggestedHeight:  state.get('suggestedHeight'),
    6178 
    6179             AttachmentView: state.get('AttachmentView')
    6180         });
    6181     },
    6182 
    6183     /**
    6184      * Render callback for the content region in the `upload` mode.
    6185      */
    6186     uploadContent: function() {
    6187         this.$el.removeClass( 'hide-toolbar' );
    6188         this.content.set( new wp.media.view.UploaderInline({
    6189             controller: this
    6190         }) );
    6191     },
    6192 
    6193     /**
    6194      * Toolbars
    6195      *
    6196      * @param {Object} toolbar
    6197      * @param {Object} [options={}]
    6198      * @this wp.media.controller.Region
    6199      */
    6200     createSelectToolbar: function( toolbar, options ) {
    6201         options = options || this.options.button || {};
    6202         options.controller = this;
    6203 
    6204         toolbar.view = new wp.media.view.Toolbar.Select( options );
    6205     }
    6206 });
    6207 
    6208 module.exports = Select;
    6209 
    6210 },{}],47:[function(require,module,exports){
    6211 /**
    6212  * wp.media.view.Iframe
    6213  *
    6214  * @memberOf wp.media.view
    6215  *
    6216  * @class
    6217  * @augments wp.media.View
    6218  * @augments wp.Backbone.View
    6219  * @augments Backbone.View
    6220  */
    6221 var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{
    6222     className: 'media-iframe',
    6223     /**
    6224      * @returns {wp.media.view.Iframe} Returns itself to allow chaining
    6225      */
    6226     render: function() {
    6227         this.views.detach();
    6228         this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
    6229         this.views.render();
    6230         return this;
    6231     }
    6232 });
    6233 
    6234 module.exports = Iframe;
    6235 
    6236 },{}],48:[function(require,module,exports){
     8706module.exports = EmbedImage;
     8707
     8708
     8709/***/ }),
     8710/* 95 */
     8711/***/ (function(module, exports) {
     8712
    62378713var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    62388714    $ = jQuery,
     
    64048880module.exports = ImageDetails;
    64058881
    6406 },{}],49:[function(require,module,exports){
     8882
     8883/***/ }),
     8884/* 96 */
     8885/***/ (function(module, exports) {
     8886
     8887var View = wp.media.View,
     8888    UploaderStatus = wp.media.view.UploaderStatus,
     8889    l10n = wp.media.view.l10n,
     8890    $ = jQuery,
     8891    Cropper;
     8892
    64078893/**
    6408  * wp.media.view.Label
     8894 * wp.media.view.Cropper
     8895 *
     8896 * Uses the imgAreaSelect plugin to allow a user to crop an image.
     8897 *
     8898 * Takes imgAreaSelect options from
     8899 * wp.customize.HeaderControl.calculateImageSelectOptions via
     8900 * wp.customize.HeaderControl.openMM.
    64098901 *
    64108902 * @memberOf wp.media.view
     
    64158907 * @augments Backbone.View
    64168908 */
    6417 var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{
    6418     tagName: 'label',
    6419     className: 'screen-reader-text',
    6420 
     8909Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{
     8910    className: 'crop-content',
     8911    template: wp.template('crop-content'),
    64218912    initialize: function() {
    6422         this.value = this.options.value;
    6423     },
    6424 
    6425     render: function() {
    6426         this.$el.html( this.value );
    6427 
    6428         return this;
     8913        _.bindAll(this, 'onImageLoad');
     8914    },
     8915    ready: function() {
     8916        this.controller.frame.on('content:error:crop', this.onError, this);
     8917        this.$image = this.$el.find('.crop-image');
     8918        this.$image.on('load', this.onImageLoad);
     8919        $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
     8920    },
     8921    remove: function() {
     8922        $(window).off('resize.cropper');
     8923        this.$el.remove();
     8924        this.$el.off();
     8925        View.prototype.remove.apply(this, arguments);
     8926    },
     8927    prepare: function() {
     8928        return {
     8929            title: l10n.cropYourImage,
     8930            url: this.options.attachment.get('url')
     8931        };
     8932    },
     8933    onImageLoad: function() {
     8934        var imgOptions = this.controller.get('imgSelectOptions'),
     8935            imgSelect;
     8936
     8937        if (typeof imgOptions === 'function') {
     8938            imgOptions = imgOptions(this.options.attachment, this.controller);
     8939        }
     8940
     8941        imgOptions = _.extend(imgOptions, {
     8942            parent: this.$el,
     8943            onInit: function() {
     8944                this.parent.children().on( 'mousedown touchstart', function( e ){
     8945
     8946                    if ( e.shiftKey ) {
     8947                        imgSelect.setOptions( {
     8948                            aspectRatio: '1:1'
     8949                        } );
     8950                    } else {
     8951                        imgSelect.setOptions( {
     8952                            aspectRatio: false
     8953                        } );
     8954                    }
     8955                } );
     8956            }
     8957        } );
     8958        this.trigger('image-loaded');
     8959        imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
     8960    },
     8961    onError: function() {
     8962        var filename = this.options.attachment.get('filename');
     8963
     8964        this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     8965            filename: UploaderStatus.prototype.filename(filename),
     8966            message: window._wpMediaViewsL10n.cropError
     8967        }), { at: 0 });
    64298968    }
    64308969});
    64318970
    6432 module.exports = Label;
    6433 
    6434 },{}],50:[function(require,module,exports){
    6435 var Frame = wp.media.view.Frame,
    6436     $ = jQuery,
    6437     MediaFrame;
    6438 
    6439 /**
    6440  * wp.media.view.MediaFrame
    6441  *
    6442  * The frame used to create the media modal.
    6443  *
    6444  * @memberOf wp.media.view
    6445  *
    6446  * @class
    6447  * @augments wp.media.view.Frame
    6448  * @augments wp.media.View
    6449  * @augments wp.Backbone.View
    6450  * @augments Backbone.View
    6451  * @mixes wp.media.controller.StateMachine
    6452  */
    6453 MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{
    6454     className: 'media-frame',
    6455     template:  wp.template('media-frame'),
    6456     regions:   ['menu','title','content','toolbar','router'],
    6457 
    6458     events: {
    6459         'click div.media-frame-title h1': 'toggleMenu'
    6460     },
    6461 
    6462     /**
    6463      * @constructs
    6464      */
    6465     initialize: function() {
    6466         Frame.prototype.initialize.apply( this, arguments );
    6467 
    6468         _.defaults( this.options, {
    6469             title:    '',
    6470             modal:    true,
    6471             uploader: true
    6472         });
    6473 
    6474         // Ensure core UI is enabled.
    6475         this.$el.addClass('wp-core-ui');
    6476 
    6477         // Initialize modal container view.
    6478         if ( this.options.modal ) {
    6479             this.modal = new wp.media.view.Modal({
    6480                 controller: this,
    6481                 title:      this.options.title
    6482             });
    6483 
    6484             this.modal.content( this );
    6485         }
    6486 
    6487         // Force the uploader off if the upload limit has been exceeded or
    6488         // if the browser isn't supported.
    6489         if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
    6490             this.options.uploader = false;
    6491         }
    6492 
    6493         // Initialize window-wide uploader.
    6494         if ( this.options.uploader ) {
    6495             this.uploader = new wp.media.view.UploaderWindow({
    6496                 controller: this,
    6497                 uploader: {
    6498                     dropzone:  this.modal ? this.modal.$el : this.$el,
    6499                     container: this.$el
    6500                 }
    6501             });
    6502             this.views.set( '.media-frame-uploader', this.uploader );
    6503         }
    6504 
    6505         this.on( 'attach', _.bind( this.views.ready, this.views ), this );
    6506 
    6507         // Bind default title creation.
    6508         this.on( 'title:create:default', this.createTitle, this );
    6509         this.title.mode('default');
    6510 
    6511         this.on( 'title:render', function( view ) {
    6512             view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
    6513         });
    6514 
    6515         // Bind default menu.
    6516         this.on( 'menu:create:default', this.createMenu, this );
    6517     },
    6518     /**
    6519      * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6520      */
    6521     render: function() {
    6522         // Activate the default state if no active state exists.
    6523         if ( ! this.state() && this.options.state ) {
    6524             this.setState( this.options.state );
    6525         }
    6526         /**
    6527          * call 'render' directly on the parent class
    6528          */
    6529         return Frame.prototype.render.apply( this, arguments );
    6530     },
    6531     /**
    6532      * @param {Object} title
    6533      * @this wp.media.controller.Region
    6534      */
    6535     createTitle: function( title ) {
    6536         title.view = new wp.media.View({
    6537             controller: this,
    6538             tagName: 'h1'
    6539         });
    6540     },
    6541     /**
    6542      * @param {Object} menu
    6543      * @this wp.media.controller.Region
    6544      */
    6545     createMenu: function( menu ) {
    6546         menu.view = new wp.media.view.Menu({
    6547             controller: this
    6548         });
    6549     },
    6550 
    6551     toggleMenu: function() {
    6552         this.$el.find( '.media-menu' ).toggleClass( 'visible' );
    6553     },
    6554 
    6555     /**
    6556      * @param {Object} toolbar
    6557      * @this wp.media.controller.Region
    6558      */
    6559     createToolbar: function( toolbar ) {
    6560         toolbar.view = new wp.media.view.Toolbar({
    6561             controller: this
    6562         });
    6563     },
    6564     /**
    6565      * @param {Object} router
    6566      * @this wp.media.controller.Region
    6567      */
    6568     createRouter: function( router ) {
    6569         router.view = new wp.media.view.Router({
    6570             controller: this
    6571         });
    6572     },
    6573     /**
    6574      * @param {Object} options
    6575      */
    6576     createIframeStates: function( options ) {
    6577         var settings = wp.media.view.settings,
    6578             tabs = settings.tabs,
    6579             tabUrl = settings.tabUrl,
    6580             $postId;
    6581 
    6582         if ( ! tabs || ! tabUrl ) {
    6583             return;
    6584         }
    6585 
    6586         // Add the post ID to the tab URL if it exists.
    6587         $postId = $('#post_ID');
    6588         if ( $postId.length ) {
    6589             tabUrl += '&post_id=' + $postId.val();
    6590         }
    6591 
    6592         // Generate the tab states.
    6593         _.each( tabs, function( title, id ) {
    6594             this.state( 'iframe:' + id ).set( _.defaults({
    6595                 tab:     id,
    6596                 src:     tabUrl + '&tab=' + id,
    6597                 title:   title,
    6598                 content: 'iframe',
    6599                 menu:    'default'
    6600             }, options ) );
    6601         }, this );
    6602 
    6603         this.on( 'content:create:iframe', this.iframeContent, this );
    6604         this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
    6605         this.on( 'menu:render:default', this.iframeMenu, this );
    6606         this.on( 'open', this.hijackThickbox, this );
    6607         this.on( 'close', this.restoreThickbox, this );
    6608     },
    6609 
    6610     /**
    6611      * @param {Object} content
    6612      * @this wp.media.controller.Region
    6613      */
    6614     iframeContent: function( content ) {
    6615         this.$el.addClass('hide-toolbar');
    6616         content.view = new wp.media.view.Iframe({
    6617             controller: this
    6618         });
    6619     },
    6620 
    6621     iframeContentCleanup: function() {
    6622         this.$el.removeClass('hide-toolbar');
    6623     },
    6624 
    6625     iframeMenu: function( view ) {
    6626         var views = {};
    6627 
    6628         if ( ! view ) {
    6629             return;
    6630         }
    6631 
    6632         _.each( wp.media.view.settings.tabs, function( title, id ) {
    6633             views[ 'iframe:' + id ] = {
    6634                 text: this.state( 'iframe:' + id ).get('title'),
    6635                 priority: 200
    6636             };
    6637         }, this );
    6638 
    6639         view.set( views );
    6640     },
    6641 
    6642     hijackThickbox: function() {
    6643         var frame = this;
    6644 
    6645         if ( ! window.tb_remove || this._tb_remove ) {
    6646             return;
    6647         }
    6648 
    6649         this._tb_remove = window.tb_remove;
    6650         window.tb_remove = function() {
    6651             frame.close();
    6652             frame.reset();
    6653             frame.setState( frame.options.state );
    6654             frame._tb_remove.call( window );
    6655         };
    6656     },
    6657 
    6658     restoreThickbox: function() {
    6659         if ( ! this._tb_remove ) {
    6660             return;
    6661         }
    6662 
    6663         window.tb_remove = this._tb_remove;
    6664         delete this._tb_remove;
    6665     }
    6666 });
    6667 
    6668 // Map some of the modal's methods to the frame.
    6669 _.each(['open','close','attach','detach','escape'], function( method ) {
    6670     /**
    6671      * @function open
    6672      * @memberOf wp.media.view.MediaFrame
    6673      * @instance
    6674      *
    6675      * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6676      */
    6677     /**
    6678      * @function close
    6679      * @memberOf wp.media.view.MediaFrame
    6680      * @instance
    6681      *
    6682      * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6683      */
    6684     /**
    6685      * @function attach
    6686      * @memberOf wp.media.view.MediaFrame
    6687      * @instance
    6688      *
    6689      * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6690      */
    6691     /**
    6692      * @function detach
    6693      * @memberOf wp.media.view.MediaFrame
    6694      * @instance
    6695      *
    6696      * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6697      */
    6698     /**
    6699      * @function escape
    6700      * @memberOf wp.media.view.MediaFrame
    6701      * @instance
    6702      *
    6703      * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6704      */
    6705     MediaFrame.prototype[ method ] = function() {
    6706         if ( this.modal ) {
    6707             this.modal[ method ].apply( this.modal, arguments );
    6708         }
    6709         return this;
    6710     };
    6711 });
    6712 
    6713 module.exports = MediaFrame;
    6714 
    6715 },{}],51:[function(require,module,exports){
    6716 var $ = jQuery,
    6717     MenuItem;
    6718 
    6719 /**
    6720  * wp.media.view.MenuItem
    6721  *
    6722  * @memberOf wp.media.view
    6723  *
    6724  * @class
    6725  * @augments wp.media.View
    6726  * @augments wp.Backbone.View
    6727  * @augments Backbone.View
    6728  */
    6729 MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{
    6730     tagName:   'a',
    6731     className: 'media-menu-item',
    6732 
    6733     attributes: {
    6734         href: '#'
    6735     },
    6736 
    6737     events: {
    6738         'click': '_click'
    6739     },
    6740     /**
    6741      * @param {Object} event
    6742      */
    6743     _click: function( event ) {
    6744         var clickOverride = this.options.click;
    6745 
    6746         if ( event ) {
    6747             event.preventDefault();
    6748         }
    6749 
    6750         if ( clickOverride ) {
    6751             clickOverride.call( this );
    6752         } else {
    6753             this.click();
    6754         }
    6755 
    6756         // When selecting a tab along the left side,
    6757         // focus should be transferred into the main panel
    6758         if ( ! wp.media.isTouchDevice ) {
    6759             $('.media-frame-content input').first().focus();
    6760         }
    6761     },
    6762 
    6763     click: function() {
    6764         var state = this.options.state;
    6765 
    6766         if ( state ) {
    6767             this.controller.setState( state );
    6768             this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
    6769         }
    6770     },
    6771     /**
    6772      * @returns {wp.media.view.MenuItem} returns itself to allow chaining
    6773      */
    6774     render: function() {
    6775         var options = this.options;
    6776 
    6777         if ( options.text ) {
    6778             this.$el.text( options.text );
    6779         } else if ( options.html ) {
    6780             this.$el.html( options.html );
    6781         }
    6782 
    6783         return this;
    6784     }
    6785 });
    6786 
    6787 module.exports = MenuItem;
    6788 
    6789 },{}],52:[function(require,module,exports){
    6790 var MenuItem = wp.media.view.MenuItem,
    6791     PriorityList = wp.media.view.PriorityList,
    6792     Menu;
    6793 
    6794 /**
    6795  * wp.media.view.Menu
    6796  *
    6797  * @memberOf wp.media.view
    6798  *
    6799  * @class
    6800  * @augments wp.media.view.PriorityList
    6801  * @augments wp.media.View
    6802  * @augments wp.Backbone.View
    6803  * @augments Backbone.View
    6804  */
    6805 Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{
    6806     tagName:   'div',
    6807     className: 'media-menu',
    6808     property:  'state',
    6809     ItemView:  MenuItem,
    6810     region:    'menu',
    6811 
    6812     /* TODO: alternatively hide on any click anywhere
    6813     events: {
    6814         'click': 'click'
    6815     },
    6816 
    6817     click: function() {
    6818         this.$el.removeClass( 'visible' );
    6819     },
    6820     */
    6821 
    6822     /**
    6823      * @param {Object} options
    6824      * @param {string} id
    6825      * @returns {wp.media.View}
    6826      */
    6827     toView: function( options, id ) {
    6828         options = options || {};
    6829         options[ this.property ] = options[ this.property ] || id;
    6830         return new this.ItemView( options ).render();
    6831     },
    6832 
    6833     ready: function() {
    6834         /**
    6835          * call 'ready' directly on the parent class
    6836          */
    6837         PriorityList.prototype.ready.apply( this, arguments );
    6838         this.visibility();
    6839     },
    6840 
    6841     set: function() {
    6842         /**
    6843          * call 'set' directly on the parent class
    6844          */
    6845         PriorityList.prototype.set.apply( this, arguments );
    6846         this.visibility();
    6847     },
    6848 
    6849     unset: function() {
    6850         /**
    6851          * call 'unset' directly on the parent class
    6852          */
    6853         PriorityList.prototype.unset.apply( this, arguments );
    6854         this.visibility();
    6855     },
    6856 
    6857     visibility: function() {
    6858         var region = this.region,
    6859             view = this.controller[ region ].get(),
    6860             views = this.views.get(),
    6861             hide = ! views || views.length < 2;
    6862 
    6863         if ( this === view ) {
    6864             this.controller.$el.toggleClass( 'hide-' + region, hide );
    6865         }
    6866     },
    6867     /**
    6868      * @param {string} id
    6869      */
    6870     select: function( id ) {
    6871         var view = this.get( id );
    6872 
    6873         if ( ! view ) {
    6874             return;
    6875         }
    6876 
    6877         this.deselect();
    6878         view.$el.addClass('active');
    6879     },
    6880 
    6881     deselect: function() {
    6882         this.$el.children().removeClass('active');
    6883     },
    6884 
    6885     hide: function( id ) {
    6886         var view = this.get( id );
    6887 
    6888         if ( ! view ) {
    6889             return;
    6890         }
    6891 
    6892         view.$el.addClass('hidden');
    6893     },
    6894 
    6895     show: function( id ) {
    6896         var view = this.get( id );
    6897 
    6898         if ( ! view ) {
    6899             return;
    6900         }
    6901 
    6902         view.$el.removeClass('hidden');
    6903     }
    6904 });
    6905 
    6906 module.exports = Menu;
    6907 
    6908 },{}],53:[function(require,module,exports){
    6909 var $ = jQuery,
    6910     Modal;
    6911 
    6912 /**
    6913  * wp.media.view.Modal
    6914  *
    6915  * A modal view, which the media modal uses as its default container.
    6916  *
    6917  * @memberOf wp.media.view
    6918  *
    6919  * @class
    6920  * @augments wp.media.View
    6921  * @augments wp.Backbone.View
    6922  * @augments Backbone.View
    6923  */
    6924 Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{
    6925     tagName:  'div',
    6926     template: wp.template('media-modal'),
    6927 
    6928     attributes: {
    6929         tabindex: 0
    6930     },
    6931 
    6932     events: {
    6933         'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
    6934         'keydown': 'keydown'
    6935     },
    6936 
    6937     clickedOpenerEl: null,
    6938 
    6939     initialize: function() {
    6940         _.defaults( this.options, {
    6941             container: document.body,
    6942             title:     '',
    6943             propagate: true,
    6944             freeze:    true
    6945         });
    6946 
    6947         this.focusManager = new wp.media.view.FocusManager({
    6948             el: this.el
    6949         });
    6950     },
    6951     /**
    6952      * @returns {Object}
    6953      */
    6954     prepare: function() {
    6955         return {
    6956             title: this.options.title
    6957         };
    6958     },
    6959 
    6960     /**
    6961      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6962      */
    6963     attach: function() {
    6964         if ( this.views.attached ) {
    6965             return this;
    6966         }
    6967 
    6968         if ( ! this.views.rendered ) {
    6969             this.render();
    6970         }
    6971 
    6972         this.$el.appendTo( this.options.container );
    6973 
    6974         // Manually mark the view as attached and trigger ready.
    6975         this.views.attached = true;
    6976         this.views.ready();
    6977 
    6978         return this.propagate('attach');
    6979     },
    6980 
    6981     /**
    6982      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6983      */
    6984     detach: function() {
    6985         if ( this.$el.is(':visible') ) {
    6986             this.close();
    6987         }
    6988 
    6989         this.$el.detach();
    6990         this.views.attached = false;
    6991         return this.propagate('detach');
    6992     },
    6993 
    6994     /**
    6995      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6996      */
    6997     open: function() {
    6998         var $el = this.$el,
    6999             options = this.options,
    7000             mceEditor;
    7001 
    7002         if ( $el.is(':visible') ) {
    7003             return this;
    7004         }
    7005 
    7006         this.clickedOpenerEl = document.activeElement;
    7007 
    7008         if ( ! this.views.attached ) {
    7009             this.attach();
    7010         }
    7011 
    7012         // If the `freeze` option is set, record the window's scroll position.
    7013         if ( options.freeze ) {
    7014             this._freeze = {
    7015                 scrollTop: $( window ).scrollTop()
    7016             };
    7017         }
    7018 
    7019         // Disable page scrolling.
    7020         $( 'body' ).addClass( 'modal-open' );
    7021 
    7022         $el.show();
    7023 
    7024         // Try to close the onscreen keyboard
    7025         if ( 'ontouchend' in document ) {
    7026             if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
    7027                 mceEditor.iframeElement.focus();
    7028                 mceEditor.iframeElement.blur();
    7029 
    7030                 setTimeout( function() {
    7031                     mceEditor.iframeElement.blur();
    7032                 }, 100 );
    7033             }
    7034         }
    7035 
    7036         this.$el.focus();
    7037 
    7038         return this.propagate('open');
    7039     },
    7040 
    7041     /**
    7042      * @param {Object} options
    7043      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    7044      */
    7045     close: function( options ) {
    7046         var freeze = this._freeze;
    7047 
    7048         if ( ! this.views.attached || ! this.$el.is(':visible') ) {
    7049             return this;
    7050         }
    7051 
    7052         // Enable page scrolling.
    7053         $( 'body' ).removeClass( 'modal-open' );
    7054 
    7055         // Hide modal and remove restricted media modal tab focus once it's closed
    7056         this.$el.hide().undelegate( 'keydown' );
    7057 
    7058         // Put focus back in useful location once modal is closed.
    7059         if ( null !== this.clickedOpenerEl ) {
    7060             this.clickedOpenerEl.focus();
    7061         } else {
    7062             $( '#wpbody-content' ).focus();
    7063         }
    7064 
    7065         this.propagate('close');
    7066 
    7067         // If the `freeze` option is set, restore the container's scroll position.
    7068         if ( freeze ) {
    7069             $( window ).scrollTop( freeze.scrollTop );
    7070         }
    7071 
    7072         if ( options && options.escape ) {
    7073             this.propagate('escape');
    7074         }
    7075 
    7076         return this;
    7077     },
    7078     /**
    7079      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    7080      */
    7081     escape: function() {
    7082         return this.close({ escape: true });
    7083     },
    7084     /**
    7085      * @param {Object} event
    7086      */
    7087     escapeHandler: function( event ) {
    7088         event.preventDefault();
    7089         this.escape();
    7090     },
    7091 
    7092     /**
    7093      * @param {Array|Object} content Views to register to '.media-modal-content'
    7094      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    7095      */
    7096     content: function( content ) {
    7097         this.views.set( '.media-modal-content', content );
    7098         return this;
    7099     },
    7100 
    7101     /**
    7102      * Triggers a modal event and if the `propagate` option is set,
    7103      * forwards events to the modal's controller.
    7104      *
    7105      * @param {string} id
    7106      * @returns {wp.media.view.Modal} Returns itself to allow chaining
    7107      */
    7108     propagate: function( id ) {
    7109         this.trigger( id );
    7110 
    7111         if ( this.options.propagate ) {
    7112             this.controller.trigger( id );
    7113         }
    7114 
    7115         return this;
    7116     },
    7117     /**
    7118      * @param {Object} event
    7119      */
    7120     keydown: function( event ) {
    7121         // Close the modal when escape is pressed.
    7122         if ( 27 === event.which && this.$el.is(':visible') ) {
    7123             this.escape();
    7124             event.stopImmediatePropagation();
    7125         }
    7126     }
    7127 });
    7128 
    7129 module.exports = Modal;
    7130 
    7131 },{}],54:[function(require,module,exports){
    7132 /**
    7133  * wp.media.view.PriorityList
    7134  *
    7135  * @memberOf wp.media.view
    7136  *
    7137  * @class
    7138  * @augments wp.media.View
    7139  * @augments wp.Backbone.View
    7140  * @augments Backbone.View
    7141  */
    7142 var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{
    7143     tagName:   'div',
    7144 
    7145     initialize: function() {
    7146         this._views = {};
    7147 
    7148         this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
    7149         delete this.options.views;
    7150 
    7151         if ( ! this.options.silent ) {
    7152             this.render();
    7153         }
    7154     },
    7155     /**
    7156      * @param {string} id
    7157      * @param {wp.media.View|Object} view
    7158      * @param {Object} options
    7159      * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
    7160      */
    7161     set: function( id, view, options ) {
    7162         var priority, views, index;
    7163 
    7164         options = options || {};
    7165 
    7166         // Accept an object with an `id` : `view` mapping.
    7167         if ( _.isObject( id ) ) {
    7168             _.each( id, function( view, id ) {
    7169                 this.set( id, view );
    7170             }, this );
    7171             return this;
    7172         }
    7173 
    7174         if ( ! (view instanceof Backbone.View) ) {
    7175             view = this.toView( view, id, options );
    7176         }
    7177         view.controller = view.controller || this.controller;
    7178 
    7179         this.unset( id );
    7180 
    7181         priority = view.options.priority || 10;
    7182         views = this.views.get() || [];
    7183 
    7184         _.find( views, function( existing, i ) {
    7185             if ( existing.options.priority > priority ) {
    7186                 index = i;
    7187                 return true;
    7188             }
    7189         });
    7190 
    7191         this._views[ id ] = view;
    7192         this.views.add( view, {
    7193             at: _.isNumber( index ) ? index : views.length || 0
    7194         });
    7195 
    7196         return this;
    7197     },
    7198     /**
    7199      * @param {string} id
    7200      * @returns {wp.media.View}
    7201      */
    7202     get: function( id ) {
    7203         return this._views[ id ];
    7204     },
    7205     /**
    7206      * @param {string} id
    7207      * @returns {wp.media.view.PriorityList}
    7208      */
    7209     unset: function( id ) {
    7210         var view = this.get( id );
    7211 
    7212         if ( view ) {
    7213             view.remove();
    7214         }
    7215 
    7216         delete this._views[ id ];
    7217         return this;
    7218     },
    7219     /**
    7220      * @param {Object} options
    7221      * @returns {wp.media.View}
    7222      */
    7223     toView: function( options ) {
    7224         return new wp.media.View( options );
    7225     }
    7226 });
    7227 
    7228 module.exports = PriorityList;
    7229 
    7230 },{}],55:[function(require,module,exports){
    7231 /**
    7232  * wp.media.view.RouterItem
    7233  *
    7234  * @memberOf wp.media.view
    7235  *
    7236  * @class
    7237  * @augments wp.media.view.MenuItem
    7238  * @augments wp.media.View
    7239  * @augments wp.Backbone.View
    7240  * @augments Backbone.View
    7241  */
    7242 var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{
    7243     /**
    7244      * On click handler to activate the content region's corresponding mode.
    7245      */
    7246     click: function() {
    7247         var contentMode = this.options.contentMode;
    7248         if ( contentMode ) {
    7249             this.controller.content.mode( contentMode );
    7250         }
    7251     }
    7252 });
    7253 
    7254 module.exports = RouterItem;
    7255 
    7256 },{}],56:[function(require,module,exports){
    7257 var Menu = wp.media.view.Menu,
    7258     Router;
    7259 
    7260 /**
    7261  * wp.media.view.Router
    7262  *
    7263  * @memberOf wp.media.view
    7264  *
    7265  * @class
    7266  * @augments wp.media.view.Menu
    7267  * @augments wp.media.view.PriorityList
    7268  * @augments wp.media.View
    7269  * @augments wp.Backbone.View
    7270  * @augments Backbone.View
    7271  */
    7272 Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{
    7273     tagName:   'div',
    7274     className: 'media-router',
    7275     property:  'contentMode',
    7276     ItemView:  wp.media.view.RouterItem,
    7277     region:    'router',
    7278 
    7279     initialize: function() {
    7280         this.controller.on( 'content:render', this.update, this );
    7281         // Call 'initialize' directly on the parent class.
    7282         Menu.prototype.initialize.apply( this, arguments );
    7283     },
    7284 
    7285     update: function() {
    7286         var mode = this.controller.content.mode();
    7287         if ( mode ) {
    7288             this.select( mode );
    7289         }
    7290     }
    7291 });
    7292 
    7293 module.exports = Router;
    7294 
    7295 },{}],57:[function(require,module,exports){
    7296 var l10n = wp.media.view.l10n,
    7297     Search;
    7298 
    7299 /**
    7300  * wp.media.view.Search
    7301  *
    7302  * @memberOf wp.media.view
    7303  *
    7304  * @class
    7305  * @augments wp.media.View
    7306  * @augments wp.Backbone.View
    7307  * @augments Backbone.View
    7308  */
    7309 Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{
    7310     tagName:   'input',
    7311     className: 'search',
    7312     id:        'media-search-input',
    7313 
    7314     attributes: {
    7315         type:        'search',
    7316         placeholder: l10n.searchMediaPlaceholder
    7317     },
    7318 
    7319     events: {
    7320         'input':  'search',
    7321         'keyup':  'search'
    7322     },
    7323 
    7324     /**
    7325      * @returns {wp.media.view.Search} Returns itself to allow chaining
    7326      */
    7327     render: function() {
    7328         this.el.value = this.model.escape('search');
    7329         return this;
    7330     },
    7331 
    7332     search: _.debounce( function( event ) {
    7333         if ( event.target.value ) {
    7334             this.model.set( 'search', event.target.value );
    7335         } else {
    7336             this.model.unset('search');
    7337         }
    7338     }, 300 )
    7339 });
    7340 
    7341 module.exports = Search;
    7342 
    7343 },{}],58:[function(require,module,exports){
    7344 var l10n = wp.media.view.l10n,
    7345     Selection;
    7346 
    7347 /**
    7348  * wp.media.view.Selection
    7349  *
    7350  * @memberOf wp.media.view
    7351  *
    7352  * @class
    7353  * @augments wp.media.View
    7354  * @augments wp.Backbone.View
    7355  * @augments Backbone.View
    7356  */
    7357 Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */{
    7358     tagName:   'div',
    7359     className: 'media-selection',
    7360     template:  wp.template('media-selection'),
    7361 
    7362     events: {
    7363         'click .edit-selection':  'edit',
    7364         'click .clear-selection': 'clear'
    7365     },
    7366 
    7367     initialize: function() {
    7368         _.defaults( this.options, {
    7369             editable:  false,
    7370             clearable: true
    7371         });
    7372 
    7373         /**
    7374          * @member {wp.media.view.Attachments.Selection}
    7375          */
    7376         this.attachments = new wp.media.view.Attachments.Selection({
    7377             controller: this.controller,
    7378             collection: this.collection,
    7379             selection:  this.collection,
    7380             model:      new Backbone.Model()
    7381         });
    7382 
    7383         this.views.set( '.selection-view', this.attachments );
    7384         this.collection.on( 'add remove reset', this.refresh, this );
    7385         this.controller.on( 'content:activate', this.refresh, this );
    7386     },
    7387 
    7388     ready: function() {
    7389         this.refresh();
    7390     },
    7391 
    7392     refresh: function() {
    7393         // If the selection hasn't been rendered, bail.
    7394         if ( ! this.$el.children().length ) {
    7395             return;
    7396         }
    7397 
    7398         var collection = this.collection,
    7399             editing = 'edit-selection' === this.controller.content.mode();
    7400 
    7401         // If nothing is selected, display nothing.
    7402         this.$el.toggleClass( 'empty', ! collection.length );
    7403         this.$el.toggleClass( 'one', 1 === collection.length );
    7404         this.$el.toggleClass( 'editing', editing );
    7405 
    7406         this.$('.count').text( l10n.selected.replace('%d', collection.length) );
    7407     },
    7408 
    7409     edit: function( event ) {
    7410         event.preventDefault();
    7411         if ( this.options.editable ) {
    7412             this.options.editable.call( this, this.collection );
    7413         }
    7414     },
    7415 
    7416     clear: function( event ) {
    7417         event.preventDefault();
    7418         this.collection.reset();
    7419 
    7420         // Keep focus inside media modal
    7421         // after clear link is selected
    7422         this.controller.modal.focusManager.focus();
    7423     }
    7424 });
    7425 
    7426 module.exports = Selection;
    7427 
    7428 },{}],59:[function(require,module,exports){
    7429 var View = wp.media.View,
    7430     $ = Backbone.$,
    7431     Settings;
    7432 
    7433 /**
    7434  * wp.media.view.Settings
    7435  *
    7436  * @memberOf wp.media.view
    7437  *
    7438  * @class
    7439  * @augments wp.media.View
    7440  * @augments wp.Backbone.View
    7441  * @augments Backbone.View
    7442  */
    7443 Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{
    7444     events: {
    7445         'click button':    'updateHandler',
    7446         'change input':    'updateHandler',
    7447         'change select':   'updateHandler',
    7448         'change textarea': 'updateHandler'
    7449     },
    7450 
    7451     initialize: function() {
    7452         this.model = this.model || new Backbone.Model();
    7453         this.listenTo( this.model, 'change', this.updateChanges );
    7454     },
    7455 
    7456     prepare: function() {
    7457         return _.defaults({
    7458             model: this.model.toJSON()
    7459         }, this.options );
    7460     },
    7461     /**
    7462      * @returns {wp.media.view.Settings} Returns itself to allow chaining
    7463      */
    7464     render: function() {
    7465         View.prototype.render.apply( this, arguments );
    7466         // Select the correct values.
    7467         _( this.model.attributes ).chain().keys().each( this.update, this );
    7468         return this;
    7469     },
    7470     /**
    7471      * @param {string} key
    7472      */
    7473     update: function( key ) {
    7474         var value = this.model.get( key ),
    7475             $setting = this.$('[data-setting="' + key + '"]'),
    7476             $buttons, $value;
    7477 
    7478         // Bail if we didn't find a matching setting.
    7479         if ( ! $setting.length ) {
    7480             return;
    7481         }
    7482 
    7483         // Attempt to determine how the setting is rendered and update
    7484         // the selected value.
    7485 
    7486         // Handle dropdowns.
    7487         if ( $setting.is('select') ) {
    7488             $value = $setting.find('[value="' + value + '"]');
    7489 
    7490             if ( $value.length ) {
    7491                 $setting.find('option').prop( 'selected', false );
    7492                 $value.prop( 'selected', true );
    7493             } else {
    7494                 // If we can't find the desired value, record what *is* selected.
    7495                 this.model.set( key, $setting.find(':selected').val() );
    7496             }
    7497 
    7498         // Handle button groups.
    7499         } else if ( $setting.hasClass('button-group') ) {
    7500             $buttons = $setting.find('button').removeClass('active');
    7501             $buttons.filter( '[value="' + value + '"]' ).addClass('active');
    7502 
    7503         // Handle text inputs and textareas.
    7504         } else if ( $setting.is('input[type="text"], textarea') ) {
    7505             if ( ! $setting.is(':focus') ) {
    7506                 $setting.val( value );
    7507             }
    7508         // Handle checkboxes.
    7509         } else if ( $setting.is('input[type="checkbox"]') ) {
    7510             $setting.prop( 'checked', !! value && 'false' !== value );
    7511         }
    7512     },
    7513     /**
    7514      * @param {Object} event
    7515      */
    7516     updateHandler: function( event ) {
    7517         var $setting = $( event.target ).closest('[data-setting]'),
    7518             value = event.target.value,
    7519             userSetting;
    7520 
    7521         event.preventDefault();
    7522 
    7523         if ( ! $setting.length ) {
    7524             return;
    7525         }
    7526 
    7527         // Use the correct value for checkboxes.
    7528         if ( $setting.is('input[type="checkbox"]') ) {
    7529             value = $setting[0].checked;
    7530         }
    7531 
    7532         // Update the corresponding setting.
    7533         this.model.set( $setting.data('setting'), value );
    7534 
    7535         // If the setting has a corresponding user setting,
    7536         // update that as well.
    7537         if ( userSetting = $setting.data('userSetting') ) {
    7538             window.setUserSetting( userSetting, value );
    7539         }
    7540     },
    7541 
    7542     updateChanges: function( model ) {
    7543         if ( model.hasChanged() ) {
    7544             _( model.changed ).chain().keys().each( this.update, this );
    7545         }
    7546     }
    7547 });
    7548 
    7549 module.exports = Settings;
    7550 
    7551 },{}],60:[function(require,module,exports){
    7552 var Settings = wp.media.view.Settings,
    7553     AttachmentDisplay;
    7554 
    7555 /**
    7556  * wp.media.view.Settings.AttachmentDisplay
    7557  *
    7558  * @memberOf wp.media.view.Settings
    7559  *
    7560  * @class
    7561  * @augments wp.media.view.Settings
    7562  * @augments wp.media.View
    7563  * @augments wp.Backbone.View
    7564  * @augments Backbone.View
    7565  */
    7566 AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.AttachmentDisplay.prototype */{
    7567     className: 'attachment-display-settings',
    7568     template:  wp.template('attachment-display-settings'),
    7569 
    7570     initialize: function() {
    7571         var attachment = this.options.attachment;
    7572 
    7573         _.defaults( this.options, {
    7574             userSettings: false
    7575         });
    7576         // Call 'initialize' directly on the parent class.
    7577         Settings.prototype.initialize.apply( this, arguments );
    7578         this.listenTo( this.model, 'change:link', this.updateLinkTo );
    7579 
    7580         if ( attachment ) {
    7581             attachment.on( 'change:uploading', this.render, this );
    7582         }
    7583     },
    7584 
    7585     dispose: function() {
    7586         var attachment = this.options.attachment;
    7587         if ( attachment ) {
    7588             attachment.off( null, null, this );
    7589         }
    7590         /**
    7591          * call 'dispose' directly on the parent class
    7592          */
    7593         Settings.prototype.dispose.apply( this, arguments );
    7594     },
    7595     /**
    7596      * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
    7597      */
    7598     render: function() {
    7599         var attachment = this.options.attachment;
    7600         if ( attachment ) {
    7601             _.extend( this.options, {
    7602                 sizes: attachment.get('sizes'),
    7603                 type:  attachment.get('type')
    7604             });
    7605         }
    7606         /**
    7607          * call 'render' directly on the parent class
    7608          */
    7609         Settings.prototype.render.call( this );
    7610         this.updateLinkTo();
    7611         return this;
    7612     },
    7613 
    7614     updateLinkTo: function() {
    7615         var linkTo = this.model.get('link'),
    7616             $input = this.$('.link-to-custom'),
    7617             attachment = this.options.attachment;
    7618 
    7619         if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
    7620             $input.addClass( 'hidden' );
    7621             return;
    7622         }
    7623 
    7624         if ( attachment ) {
    7625             if ( 'post' === linkTo ) {
    7626                 $input.val( attachment.get('link') );
    7627             } else if ( 'file' === linkTo ) {
    7628                 $input.val( attachment.get('url') );
    7629             } else if ( ! this.model.get('linkUrl') ) {
    7630                 $input.val('http://');
    7631             }
    7632 
    7633             $input.prop( 'readonly', 'custom' !== linkTo );
    7634         }
    7635 
    7636         $input.removeClass( 'hidden' );
    7637 
    7638         // If the input is visible, focus and select its contents.
    7639         if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
    7640             $input.focus()[0].select();
    7641         }
    7642     }
    7643 });
    7644 
    7645 module.exports = AttachmentDisplay;
    7646 
    7647 },{}],61:[function(require,module,exports){
    7648 /**
    7649  * wp.media.view.Settings.Gallery
    7650  *
    7651  * @memberOf wp.media.view.Settings
    7652  *
    7653  * @class
    7654  * @augments wp.media.view.Settings
    7655  * @augments wp.media.View
    7656  * @augments wp.Backbone.View
    7657  * @augments Backbone.View
    7658  */
    7659 var Gallery = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Gallery.prototype */{
    7660     className: 'collection-settings gallery-settings',
    7661     template:  wp.template('gallery-settings')
    7662 });
    7663 
    7664 module.exports = Gallery;
    7665 
    7666 },{}],62:[function(require,module,exports){
    7667 /**
    7668  * wp.media.view.Settings.Playlist
    7669  *
    7670  * @memberOf wp.media.view.Settings
    7671  *
    7672  * @class
    7673  * @augments wp.media.view.Settings
    7674  * @augments wp.media.View
    7675  * @augments wp.Backbone.View
    7676  * @augments Backbone.View
    7677  */
    7678 var Playlist = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Playlist.prototype */{
    7679     className: 'collection-settings playlist-settings',
    7680     template:  wp.template('playlist-settings')
    7681 });
    7682 
    7683 module.exports = Playlist;
    7684 
    7685 },{}],63:[function(require,module,exports){
    7686 /**
    7687  * wp.media.view.Sidebar
    7688  *
    7689  * @memberOf wp.media.view
    7690  *
    7691  * @class
    7692  * @augments wp.media.view.PriorityList
    7693  * @augments wp.media.View
    7694  * @augments wp.Backbone.View
    7695  * @augments Backbone.View
    7696  */
    7697 var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{
    7698     className: 'media-sidebar'
    7699 });
    7700 
    7701 module.exports = Sidebar;
    7702 
    7703 },{}],64:[function(require,module,exports){
     8971module.exports = Cropper;
     8972
     8973
     8974/***/ }),
     8975/* 97 */
     8976/***/ (function(module, exports) {
     8977
    77048978var View = wp.media.view,
    77058979    SiteIconCropper;
     
    77469020module.exports = SiteIconCropper;
    77479021
    7748 },{}],65:[function(require,module,exports){
     9022
     9023/***/ }),
     9024/* 98 */
     9025/***/ (function(module, exports) {
     9026
    77499027var View = wp.media.View,
    77509028    $ = jQuery,
     
    78049082module.exports = SiteIconPreview;
    78059083
    7806 },{}],66:[function(require,module,exports){
     9084
     9085/***/ }),
     9086/* 99 */
     9087/***/ (function(module, exports) {
     9088
     9089var View = wp.media.View,
     9090    EditImage;
     9091
     9092/**
     9093 * wp.media.view.EditImage
     9094 *
     9095 * @memberOf wp.media.view
     9096 *
     9097 * @class
     9098 * @augments wp.media.View
     9099 * @augments wp.Backbone.View
     9100 * @augments Backbone.View
     9101 */
     9102EditImage = View.extend(/** @lends wp.media.view.EditImage.prototype */{
     9103    className: 'image-editor',
     9104    template: wp.template('image-editor'),
     9105
     9106    initialize: function( options ) {
     9107        this.editor = window.imageEdit;
     9108        this.controller = options.controller;
     9109        View.prototype.initialize.apply( this, arguments );
     9110    },
     9111
     9112    prepare: function() {
     9113        return this.model.toJSON();
     9114    },
     9115
     9116    loadEditor: function() {
     9117        var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
     9118        dfd.done( _.bind( this.focus, this ) );
     9119    },
     9120
     9121    focus: function() {
     9122        this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
     9123    },
     9124
     9125    back: function() {
     9126        var lastState = this.controller.lastState();
     9127        this.controller.setState( lastState );
     9128    },
     9129
     9130    refresh: function() {
     9131        this.model.fetch();
     9132    },
     9133
     9134    save: function() {
     9135        var lastState = this.controller.lastState();
     9136
     9137        this.model.fetch().done( _.bind( function() {
     9138            this.controller.setState( lastState );
     9139        }, this ) );
     9140    }
     9141
     9142});
     9143
     9144module.exports = EditImage;
     9145
     9146
     9147/***/ }),
     9148/* 100 */
     9149/***/ (function(module, exports) {
     9150
    78079151/**
    78089152 * wp.media.view.Spinner
     
    78419185module.exports = Spinner;
    78429186
    7843 },{}],67:[function(require,module,exports){
    7844 var View = wp.media.View,
    7845     Toolbar;
    7846 
    7847 /**
    7848  * wp.media.view.Toolbar
    7849  *
    7850  * A toolbar which consists of a primary and a secondary section. Each sections
    7851  * can be filled with views.
    7852  *
    7853  * @memberOf wp.media.view
    7854  *
    7855  * @class
    7856  * @augments wp.media.View
    7857  * @augments wp.Backbone.View
    7858  * @augments Backbone.View
    7859  */
    7860 Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{
    7861     tagName:   'div',
    7862     className: 'media-toolbar',
    7863 
    7864     initialize: function() {
    7865         var state = this.controller.state(),
    7866             selection = this.selection = state.get('selection'),
    7867             library = this.library = state.get('library');
    7868 
    7869         this._views = {};
    7870 
    7871         // The toolbar is composed of two `PriorityList` views.
    7872         this.primary   = new wp.media.view.PriorityList();
    7873         this.secondary = new wp.media.view.PriorityList();
    7874         this.primary.$el.addClass('media-toolbar-primary search-form');
    7875         this.secondary.$el.addClass('media-toolbar-secondary');
    7876 
    7877         this.views.set([ this.secondary, this.primary ]);
    7878 
    7879         if ( this.options.items ) {
    7880             this.set( this.options.items, { silent: true });
    7881         }
    7882 
    7883         if ( ! this.options.silent ) {
    7884             this.render();
    7885         }
    7886 
    7887         if ( selection ) {
    7888             selection.on( 'add remove reset', this.refresh, this );
    7889         }
    7890 
    7891         if ( library ) {
    7892             library.on( 'add remove reset', this.refresh, this );
    7893         }
    7894     },
    7895     /**
    7896      * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
    7897      */
    7898     dispose: function() {
    7899         if ( this.selection ) {
    7900             this.selection.off( null, null, this );
    7901         }
    7902 
    7903         if ( this.library ) {
    7904             this.library.off( null, null, this );
    7905         }
    7906         /**
    7907          * call 'dispose' directly on the parent class
    7908          */
    7909         return View.prototype.dispose.apply( this, arguments );
    7910     },
    7911 
    7912     ready: function() {
    7913         this.refresh();
    7914     },
    7915 
    7916     /**
    7917      * @param {string} id
    7918      * @param {Backbone.View|Object} view
    7919      * @param {Object} [options={}]
    7920      * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
    7921      */
    7922     set: function( id, view, options ) {
    7923         var list;
    7924         options = options || {};
    7925 
    7926         // Accept an object with an `id` : `view` mapping.
    7927         if ( _.isObject( id ) ) {
    7928             _.each( id, function( view, id ) {
    7929                 this.set( id, view, { silent: true });
    7930             }, this );
    7931 
    7932         } else {
    7933             if ( ! ( view instanceof Backbone.View ) ) {
    7934                 view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
    7935                 view = new wp.media.view.Button( view ).render();
    7936             }
    7937 
    7938             view.controller = view.controller || this.controller;
    7939 
    7940             this._views[ id ] = view;
    7941 
    7942             list = view.options.priority < 0 ? 'secondary' : 'primary';
    7943             this[ list ].set( id, view, options );
    7944         }
    7945 
    7946         if ( ! options.silent ) {
    7947             this.refresh();
    7948         }
    7949 
    7950         return this;
    7951     },
    7952     /**
    7953      * @param {string} id
    7954      * @returns {wp.media.view.Button}
    7955      */
    7956     get: function( id ) {
    7957         return this._views[ id ];
    7958     },
    7959     /**
    7960      * @param {string} id
    7961      * @param {Object} options
    7962      * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
    7963      */
    7964     unset: function( id, options ) {
    7965         delete this._views[ id ];
    7966         this.primary.unset( id, options );
    7967         this.secondary.unset( id, options );
    7968 
    7969         if ( ! options || ! options.silent ) {
    7970             this.refresh();
    7971         }
    7972         return this;
    7973     },
    7974 
    7975     refresh: function() {
    7976         var state = this.controller.state(),
    7977             library = state.get('library'),
    7978             selection = state.get('selection');
    7979 
    7980         _.each( this._views, function( button ) {
    7981             if ( ! button.model || ! button.options || ! button.options.requires ) {
    7982                 return;
    7983             }
    7984 
    7985             var requires = button.options.requires,
    7986                 disabled = false;
    7987 
    7988             // Prevent insertion of attachments if any of them are still uploading
    7989             if ( selection && selection.models ) {
    7990                 disabled = _.some( selection.models, function( attachment ) {
    7991                     return attachment.get('uploading') === true;
    7992                 });
    7993             }
    7994 
    7995             if ( requires.selection && selection && ! selection.length ) {
    7996                 disabled = true;
    7997             } else if ( requires.library && library && ! library.length ) {
    7998                 disabled = true;
    7999             }
    8000             button.model.set( 'disabled', disabled );
    8001         });
    8002     }
    8003 });
    8004 
    8005 module.exports = Toolbar;
    8006 
    8007 },{}],68:[function(require,module,exports){
    8008 var Select = wp.media.view.Toolbar.Select,
    8009     l10n = wp.media.view.l10n,
    8010     Embed;
    8011 
    8012 /**
    8013  * wp.media.view.Toolbar.Embed
    8014  *
    8015  * @memberOf wp.media.view.Toolbar
    8016  *
    8017  * @class
    8018  * @augments wp.media.view.Toolbar.Select
    8019  * @augments wp.media.view.Toolbar
    8020  * @augments wp.media.View
    8021  * @augments wp.Backbone.View
    8022  * @augments Backbone.View
    8023  */
    8024 Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{
    8025     initialize: function() {
    8026         _.defaults( this.options, {
    8027             text: l10n.insertIntoPost,
    8028             requires: false
    8029         });
    8030         // Call 'initialize' directly on the parent class.
    8031         Select.prototype.initialize.apply( this, arguments );
    8032     },
    8033 
    8034     refresh: function() {
    8035         var url = this.controller.state().props.get('url');
    8036         this.get('select').model.set( 'disabled', ! url || url === 'http://' );
    8037         /**
    8038          * call 'refresh' directly on the parent class
    8039          */
    8040         Select.prototype.refresh.apply( this, arguments );
    8041     }
    8042 });
    8043 
    8044 module.exports = Embed;
    8045 
    8046 },{}],69:[function(require,module,exports){
    8047 var Toolbar = wp.media.view.Toolbar,
    8048     l10n = wp.media.view.l10n,
    8049     Select;
    8050 
    8051 /**
    8052  * wp.media.view.Toolbar.Select
    8053  *
    8054  * @memberOf wp.media.view.Toolbar
    8055  *
    8056  * @class
    8057  * @augments wp.media.view.Toolbar
    8058  * @augments wp.media.View
    8059  * @augments wp.Backbone.View
    8060  * @augments Backbone.View
    8061  */
    8062 Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{
    8063     initialize: function() {
    8064         var options = this.options;
    8065 
    8066         _.bindAll( this, 'clickSelect' );
    8067 
    8068         _.defaults( options, {
    8069             event: 'select',
    8070             state: false,
    8071             reset: true,
    8072             close: true,
    8073             text:  l10n.select,
    8074 
    8075             // Does the button rely on the selection?
    8076             requires: {
    8077                 selection: true
    8078             }
    8079         });
    8080 
    8081         options.items = _.defaults( options.items || {}, {
    8082             select: {
    8083                 style:    'primary',
    8084                 text:     options.text,
    8085                 priority: 80,
    8086                 click:    this.clickSelect,
    8087                 requires: options.requires
    8088             }
    8089         });
    8090         // Call 'initialize' directly on the parent class.
    8091         Toolbar.prototype.initialize.apply( this, arguments );
    8092     },
    8093 
    8094     clickSelect: function() {
    8095         var options = this.options,
    8096             controller = this.controller;
    8097 
    8098         if ( options.close ) {
    8099             controller.close();
    8100         }
    8101 
    8102         if ( options.event ) {
    8103             controller.state().trigger( options.event );
    8104         }
    8105 
    8106         if ( options.state ) {
    8107             controller.setState( options.state );
    8108         }
    8109 
    8110         if ( options.reset ) {
    8111             controller.reset();
    8112         }
    8113     }
    8114 });
    8115 
    8116 module.exports = Select;
    8117 
    8118 },{}],70:[function(require,module,exports){
    8119 var View = wp.media.View,
    8120     l10n = wp.media.view.l10n,
    8121     $ = jQuery,
    8122     EditorUploader;
    8123 
    8124 /**
    8125  * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
    8126  * and relays drag'n'dropped files to a media workflow.
    8127  *
    8128  * wp.media.view.EditorUploader
    8129  *
    8130  * @memberOf wp.media.view
    8131  *
    8132  * @class
    8133  * @augments wp.media.View
    8134  * @augments wp.Backbone.View
    8135  * @augments Backbone.View
    8136  */
    8137 EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{
    8138     tagName:   'div',
    8139     className: 'uploader-editor',
    8140     template:  wp.template( 'uploader-editor' ),
    8141 
    8142     localDrag: false,
    8143     overContainer: false,
    8144     overDropzone: false,
    8145     draggingFile: null,
    8146 
    8147     /**
    8148      * Bind drag'n'drop events to callbacks.
    8149      */
    8150     initialize: function() {
    8151         this.initialized = false;
    8152 
    8153         // Bail if not enabled or UA does not support drag'n'drop or File API.
    8154         if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
    8155             return this;
    8156         }
    8157 
    8158         this.$document = $(document);
    8159         this.dropzones = [];
    8160         this.files = [];
    8161 
    8162         this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
    8163         this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
    8164         this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
    8165         this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
    8166 
    8167         this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
    8168         this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
    8169 
    8170         this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
    8171             this.localDrag = event.type === 'dragstart';
    8172 
    8173             if ( event.type === 'drop' ) {
    8174                 this.containerDragleave();
    8175             }
    8176         }, this ) );
    8177 
    8178         this.initialized = true;
    8179         return this;
    8180     },
    8181 
    8182     /**
    8183      * Check browser support for drag'n'drop.
    8184      *
    8185      * @return Boolean
    8186      */
    8187     browserSupport: function() {
    8188         var supports = false, div = document.createElement('div');
    8189 
    8190         supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
    8191         supports = supports && !! ( window.File && window.FileList && window.FileReader );
    8192         return supports;
    8193     },
    8194 
    8195     isDraggingFile: function( event ) {
    8196         if ( this.draggingFile !== null ) {
    8197             return this.draggingFile;
    8198         }
    8199 
    8200         if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
    8201             return false;
    8202         }
    8203 
    8204         this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
    8205             _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
    8206 
    8207         return this.draggingFile;
    8208     },
    8209 
    8210     refresh: function( e ) {
    8211         var dropzone_id;
    8212         for ( dropzone_id in this.dropzones ) {
    8213             // Hide the dropzones only if dragging has left the screen.
    8214             this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
    8215         }
    8216 
    8217         if ( ! _.isUndefined( e ) ) {
    8218             $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
    8219         }
    8220 
    8221         if ( ! this.overContainer && ! this.overDropzone ) {
    8222             this.draggingFile = null;
    8223         }
    8224 
    8225         return this;
    8226     },
    8227 
    8228     render: function() {
    8229         if ( ! this.initialized ) {
    8230             return this;
    8231         }
    8232 
    8233         View.prototype.render.apply( this, arguments );
    8234         $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
    8235         return this;
    8236     },
    8237 
    8238     attach: function( index, editor ) {
    8239         // Attach a dropzone to an editor.
    8240         var dropzone = this.$el.clone();
    8241         this.dropzones.push( dropzone );
    8242         $( editor ).append( dropzone );
    8243         return this;
    8244     },
    8245 
    8246     /**
    8247      * When a file is dropped on the editor uploader, open up an editor media workflow
    8248      * and upload the file immediately.
    8249      *
    8250      * @param  {jQuery.Event} event The 'drop' event.
    8251      */
    8252     drop: function( event ) {
    8253         var $wrap, uploadView;
    8254 
    8255         this.containerDragleave( event );
    8256         this.dropzoneDragleave( event );
    8257 
    8258         this.files = event.originalEvent.dataTransfer.files;
    8259         if ( this.files.length < 1 ) {
    8260             return;
    8261         }
    8262 
    8263         // Set the active editor to the drop target.
    8264         $wrap = $( event.target ).parents( '.wp-editor-wrap' );
    8265         if ( $wrap.length > 0 && $wrap[0].id ) {
    8266             window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
    8267         }
    8268 
    8269         if ( ! this.workflow ) {
    8270             this.workflow = wp.media.editor.open( window.wpActiveEditor, {
    8271                 frame:    'post',
    8272                 state:    'insert',
    8273                 title:    l10n.addMedia,
    8274                 multiple: true
    8275             });
    8276 
    8277             uploadView = this.workflow.uploader;
    8278 
    8279             if ( uploadView.uploader && uploadView.uploader.ready ) {
    8280                 this.addFiles.apply( this );
    8281             } else {
    8282                 this.workflow.on( 'uploader:ready', this.addFiles, this );
    8283             }
    8284         } else {
    8285             this.workflow.state().reset();
    8286             this.addFiles.apply( this );
    8287             this.workflow.open();
    8288         }
    8289 
    8290         return false;
    8291     },
    8292 
    8293     /**
    8294      * Add the files to the uploader.
    8295      */
    8296     addFiles: function() {
    8297         if ( this.files.length ) {
    8298             this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
    8299             this.files = [];
    8300         }
    8301         return this;
    8302     },
    8303 
    8304     containerDragover: function( event ) {
    8305         if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    8306             return;
    8307         }
    8308 
    8309         this.overContainer = true;
    8310         this.refresh();
    8311     },
    8312 
    8313     containerDragleave: function() {
    8314         this.overContainer = false;
    8315 
    8316         // Throttle dragleave because it's called when bouncing from some elements to others.
    8317         _.delay( _.bind( this.refresh, this ), 50 );
    8318     },
    8319 
    8320     dropzoneDragover: function( event ) {
    8321         if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    8322             return;
    8323         }
    8324 
    8325         this.overDropzone = true;
    8326         this.refresh( event );
    8327         return false;
    8328     },
    8329 
    8330     dropzoneDragleave: function( e ) {
    8331         this.overDropzone = false;
    8332         _.delay( _.bind( this.refresh, this, e ), 50 );
    8333     },
    8334 
    8335     click: function( e ) {
    8336         // In the rare case where the dropzone gets stuck, hide it on click.
    8337         this.containerDragleave( e );
    8338         this.dropzoneDragleave( e );
    8339         this.localDrag = false;
    8340     }
    8341 });
    8342 
    8343 module.exports = EditorUploader;
    8344 
    8345 },{}],71:[function(require,module,exports){
    8346 var View = wp.media.View,
    8347     UploaderInline;
    8348 
    8349 /**
    8350  * wp.media.view.UploaderInline
    8351  *
    8352  * The inline uploader that shows up in the 'Upload Files' tab.
    8353  *
    8354  * @memberOf wp.media.view
    8355  *
    8356  * @class
    8357  * @augments wp.media.View
    8358  * @augments wp.Backbone.View
    8359  * @augments Backbone.View
    8360  */
    8361 UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{
    8362     tagName:   'div',
    8363     className: 'uploader-inline',
    8364     template:  wp.template('uploader-inline'),
    8365 
    8366     events: {
    8367         'click .close': 'hide'
    8368     },
    8369 
    8370     initialize: function() {
    8371         _.defaults( this.options, {
    8372             message: '',
    8373             status:  true,
    8374             canClose: false
    8375         });
    8376 
    8377         if ( ! this.options.$browser && this.controller.uploader ) {
    8378             this.options.$browser = this.controller.uploader.$browser;
    8379         }
    8380 
    8381         if ( _.isUndefined( this.options.postId ) ) {
    8382             this.options.postId = wp.media.view.settings.post.id;
    8383         }
    8384 
    8385         if ( this.options.status ) {
    8386             this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
    8387                 controller: this.controller
    8388             }) );
    8389         }
    8390     },
    8391 
    8392     prepare: function() {
    8393         var suggestedWidth = this.controller.state().get('suggestedWidth'),
    8394             suggestedHeight = this.controller.state().get('suggestedHeight'),
    8395             data = {};
    8396 
    8397         data.message = this.options.message;
    8398         data.canClose = this.options.canClose;
    8399 
    8400         if ( suggestedWidth && suggestedHeight ) {
    8401             data.suggestedWidth = suggestedWidth;
    8402             data.suggestedHeight = suggestedHeight;
    8403         }
    8404 
    8405         return data;
    8406     },
    8407     /**
    8408      * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    8409      */
    8410     dispose: function() {
    8411         if ( this.disposing ) {
    8412             /**
    8413              * call 'dispose' directly on the parent class
    8414              */
    8415             return View.prototype.dispose.apply( this, arguments );
    8416         }
    8417 
    8418         // Run remove on `dispose`, so we can be sure to refresh the
    8419         // uploader with a view-less DOM. Track whether we're disposing
    8420         // so we don't trigger an infinite loop.
    8421         this.disposing = true;
    8422         return this.remove();
    8423     },
    8424     /**
    8425      * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    8426      */
    8427     remove: function() {
    8428         /**
    8429          * call 'remove' directly on the parent class
    8430          */
    8431         var result = View.prototype.remove.apply( this, arguments );
    8432 
    8433         _.defer( _.bind( this.refresh, this ) );
    8434         return result;
    8435     },
    8436 
    8437     refresh: function() {
    8438         var uploader = this.controller.uploader;
    8439 
    8440         if ( uploader ) {
    8441             uploader.refresh();
    8442         }
    8443     },
    8444     /**
    8445      * @returns {wp.media.view.UploaderInline}
    8446      */
    8447     ready: function() {
    8448         var $browser = this.options.$browser,
    8449             $placeholder;
    8450 
    8451         if ( this.controller.uploader ) {
    8452             $placeholder = this.$('.browser');
    8453 
    8454             // Check if we've already replaced the placeholder.
    8455             if ( $placeholder[0] === $browser[0] ) {
    8456                 return;
    8457             }
    8458 
    8459             $browser.detach().text( $placeholder.text() );
    8460             $browser[0].className = $placeholder[0].className;
    8461             $placeholder.replaceWith( $browser.show() );
    8462         }
    8463 
    8464         this.refresh();
    8465         return this;
    8466     },
    8467     show: function() {
    8468         this.$el.removeClass( 'hidden' );
    8469         if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
    8470             this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' );
    8471         }
    8472     },
    8473     hide: function() {
    8474         this.$el.addClass( 'hidden' );
    8475         if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
    8476             this.controller.$uploaderToggler
    8477                 .attr( 'aria-expanded', 'false' )
    8478                 // Move focus back to the toggle button when closing the uploader.
    8479                 .focus();
    8480         }
    8481     }
    8482 
    8483 });
    8484 
    8485 module.exports = UploaderInline;
    8486 
    8487 },{}],72:[function(require,module,exports){
    8488 /**
    8489  * wp.media.view.UploaderStatusError
    8490  *
    8491  * @memberOf wp.media.view
    8492  *
    8493  * @class
    8494  * @augments wp.media.View
    8495  * @augments wp.Backbone.View
    8496  * @augments Backbone.View
    8497  */
    8498 var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{
    8499     className: 'upload-error',
    8500     template:  wp.template('uploader-status-error')
    8501 });
    8502 
    8503 module.exports = UploaderStatusError;
    8504 
    8505 },{}],73:[function(require,module,exports){
    8506 var View = wp.media.View,
    8507     UploaderStatus;
    8508 
    8509 /**
    8510  * wp.media.view.UploaderStatus
    8511  *
    8512  * An uploader status for on-going uploads.
    8513  *
    8514  * @memberOf wp.media.view
    8515  *
    8516  * @class
    8517  * @augments wp.media.View
    8518  * @augments wp.Backbone.View
    8519  * @augments Backbone.View
    8520  */
    8521 UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{
    8522     className: 'media-uploader-status',
    8523     template:  wp.template('uploader-status'),
    8524 
    8525     events: {
    8526         'click .upload-dismiss-errors': 'dismiss'
    8527     },
    8528 
    8529     initialize: function() {
    8530         this.queue = wp.Uploader.queue;
    8531         this.queue.on( 'add remove reset', this.visibility, this );
    8532         this.queue.on( 'add remove reset change:percent', this.progress, this );
    8533         this.queue.on( 'add remove reset change:uploading', this.info, this );
    8534 
    8535         this.errors = wp.Uploader.errors;
    8536         this.errors.reset();
    8537         this.errors.on( 'add remove reset', this.visibility, this );
    8538         this.errors.on( 'add', this.error, this );
    8539     },
    8540     /**
    8541      * @returns {wp.media.view.UploaderStatus}
    8542      */
    8543     dispose: function() {
    8544         wp.Uploader.queue.off( null, null, this );
    8545         /**
    8546          * call 'dispose' directly on the parent class
    8547          */
    8548         View.prototype.dispose.apply( this, arguments );
    8549         return this;
    8550     },
    8551 
    8552     visibility: function() {
    8553         this.$el.toggleClass( 'uploading', !! this.queue.length );
    8554         this.$el.toggleClass( 'errors', !! this.errors.length );
    8555         this.$el.toggle( !! this.queue.length || !! this.errors.length );
    8556     },
    8557 
    8558     ready: function() {
    8559         _.each({
    8560             '$bar':      '.media-progress-bar div',
    8561             '$index':    '.upload-index',
    8562             '$total':    '.upload-total',
    8563             '$filename': '.upload-filename'
    8564         }, function( selector, key ) {
    8565             this[ key ] = this.$( selector );
    8566         }, this );
    8567 
    8568         this.visibility();
    8569         this.progress();
    8570         this.info();
    8571     },
    8572 
    8573     progress: function() {
    8574         var queue = this.queue,
    8575             $bar = this.$bar;
    8576 
    8577         if ( ! $bar || ! queue.length ) {
    8578             return;
    8579         }
    8580 
    8581         $bar.width( ( queue.reduce( function( memo, attachment ) {
    8582             if ( ! attachment.get('uploading') ) {
    8583                 return memo + 100;
    8584             }
    8585 
    8586             var percent = attachment.get('percent');
    8587             return memo + ( _.isNumber( percent ) ? percent : 100 );
    8588         }, 0 ) / queue.length ) + '%' );
    8589     },
    8590 
    8591     info: function() {
    8592         var queue = this.queue,
    8593             index = 0, active;
    8594 
    8595         if ( ! queue.length ) {
    8596             return;
    8597         }
    8598 
    8599         active = this.queue.find( function( attachment, i ) {
    8600             index = i;
    8601             return attachment.get('uploading');
    8602         });
    8603 
    8604         this.$index.text( index + 1 );
    8605         this.$total.text( queue.length );
    8606         this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
    8607     },
    8608     /**
    8609      * @param {string} filename
    8610      * @returns {string}
    8611      */
    8612     filename: function( filename ) {
    8613         return _.escape( filename );
    8614     },
    8615     /**
    8616      * @param {Backbone.Model} error
    8617      */
    8618     error: function( error ) {
    8619         this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    8620             filename: this.filename( error.get('file').name ),
    8621             message:  error.get('message')
    8622         }), { at: 0 });
    8623     },
    8624 
    8625     /**
    8626      * @param {Object} event
    8627      */
    8628     dismiss: function( event ) {
    8629         var errors = this.views.get('.upload-errors');
    8630 
    8631         event.preventDefault();
    8632 
    8633         if ( errors ) {
    8634             _.invoke( errors, 'remove' );
    8635         }
    8636         wp.Uploader.errors.reset();
    8637     }
    8638 });
    8639 
    8640 module.exports = UploaderStatus;
    8641 
    8642 },{}],74:[function(require,module,exports){
    8643 var $ = jQuery,
    8644     UploaderWindow;
    8645 
    8646 /**
    8647  * wp.media.view.UploaderWindow
    8648  *
    8649  * An uploader window that allows for dragging and dropping media.
    8650  *
    8651  * @memberOf wp.media.view
    8652  *
    8653  * @class
    8654  * @augments wp.media.View
    8655  * @augments wp.Backbone.View
    8656  * @augments Backbone.View
    8657  *
    8658  * @param {object} [options]                   Options hash passed to the view.
    8659  * @param {object} [options.uploader]          Uploader properties.
    8660  * @param {jQuery} [options.uploader.browser]
    8661  * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
    8662  * @param {object} [options.uploader.params]
    8663  */
    8664 UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{
    8665     tagName:   'div',
    8666     className: 'uploader-window',
    8667     template:  wp.template('uploader-window'),
    8668 
    8669     initialize: function() {
    8670         var uploader;
    8671 
    8672         this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' );
    8673 
    8674         uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
    8675             dropzone:  this.$el,
    8676             browser:   this.$browser,
    8677             params:    {}
    8678         });
    8679 
    8680         // Ensure the dropzone is a jQuery collection.
    8681         if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
    8682             uploader.dropzone = $( uploader.dropzone );
    8683         }
    8684 
    8685         this.controller.on( 'activate', this.refresh, this );
    8686 
    8687         this.controller.on( 'detach', function() {
    8688             this.$browser.remove();
    8689         }, this );
    8690     },
    8691 
    8692     refresh: function() {
    8693         if ( this.uploader ) {
    8694             this.uploader.refresh();
    8695         }
    8696     },
    8697 
    8698     ready: function() {
    8699         var postId = wp.media.view.settings.post.id,
    8700             dropzone;
    8701 
    8702         // If the uploader already exists, bail.
    8703         if ( this.uploader ) {
    8704             return;
    8705         }
    8706 
    8707         if ( postId ) {
    8708             this.options.uploader.params.post_id = postId;
    8709         }
    8710         this.uploader = new wp.Uploader( this.options.uploader );
    8711 
    8712         dropzone = this.uploader.dropzone;
    8713         dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
    8714         dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
    8715 
    8716         $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
    8717     },
    8718 
    8719     _ready: function() {
    8720         this.controller.trigger( 'uploader:ready' );
    8721     },
    8722 
    8723     show: function() {
    8724         var $el = this.$el.show();
    8725 
    8726         // Ensure that the animation is triggered by waiting until
    8727         // the transparent element is painted into the DOM.
    8728         _.defer( function() {
    8729             $el.css({ opacity: 1 });
    8730         });
    8731     },
    8732 
    8733     hide: function() {
    8734         var $el = this.$el.css({ opacity: 0 });
    8735 
    8736         wp.media.transition( $el ).done( function() {
    8737             // Transition end events are subject to race conditions.
    8738             // Make sure that the value is set as intended.
    8739             if ( '0' === $el.css('opacity') ) {
    8740                 $el.hide();
    8741             }
    8742         });
    8743 
    8744         // https://core.trac.wordpress.org/ticket/27341
    8745         _.delay( function() {
    8746             if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
    8747                 $el.hide();
    8748             }
    8749         }, 500 );
    8750     }
    8751 });
    8752 
    8753 module.exports = UploaderWindow;
    8754 
    8755 },{}],75:[function(require,module,exports){
    8756 /**
    8757  * wp.media.View
    8758  *
    8759  * The base view class for media.
    8760  *
    8761  * Undelegating events, removing events from the model, and
    8762  * removing events from the controller mirror the code for
    8763  * `Backbone.View.dispose` in Backbone 0.9.8 development.
    8764  *
    8765  * This behavior has since been removed, and should not be used
    8766  * outside of the media manager.
    8767  *
    8768  * @memberOf wp.media
    8769  *
    8770  * @class
    8771  * @augments wp.Backbone.View
    8772  * @augments Backbone.View
    8773  */
    8774 var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{
    8775     constructor: function( options ) {
    8776         if ( options && options.controller ) {
    8777             this.controller = options.controller;
    8778         }
    8779         wp.Backbone.View.apply( this, arguments );
    8780     },
    8781     /**
    8782      * @todo The internal comment mentions this might have been a stop-gap
    8783      *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
    8784      *       care of this in Backbone.View now.
    8785      *
    8786      * @returns {wp.media.View} Returns itself to allow chaining
    8787      */
    8788     dispose: function() {
    8789         // Undelegating events, removing events from the model, and
    8790         // removing events from the controller mirror the code for
    8791         // `Backbone.View.dispose` in Backbone 0.9.8 development.
    8792         this.undelegateEvents();
    8793 
    8794         if ( this.model && this.model.off ) {
    8795             this.model.off( null, null, this );
    8796         }
    8797 
    8798         if ( this.collection && this.collection.off ) {
    8799             this.collection.off( null, null, this );
    8800         }
    8801 
    8802         // Unbind controller events.
    8803         if ( this.controller && this.controller.off ) {
    8804             this.controller.off( null, null, this );
    8805         }
    8806 
    8807         return this;
    8808     },
    8809     /**
    8810      * @returns {wp.media.View} Returns itself to allow chaining
    8811      */
    8812     remove: function() {
    8813         this.dispose();
    8814         /**
    8815          * call 'remove' directly on the parent class
    8816          */
    8817         return wp.Backbone.View.prototype.remove.apply( this, arguments );
    8818     }
    8819 });
    8820 
    8821 module.exports = View;
    8822 
    8823 },{}]},{},[19]);
     9187
     9188/***/ })
     9189/******/ ]));
Note: See TracChangeset for help on using the changeset viewer.