Make WordPress Core

Ticket #40894: 40894.10.diff

File 40894.10.diff, 527.0 KB (added by adamsilverstein, 7 years ago)
  • .jshintrc

    diff --git .jshintrc .jshintrc
    index 0a082dded5..278eac22c3 100644
     
    2121    "Backbone": false,
    2222    "jQuery": false,
    2323    "JSON": false,
    24     "wp": false
     24    "wp": false,
     25    "export": false,
     26    "module": false,
     27    "require": false
    2528  }
    2629}
  • Gruntfile.js

    diff --git Gruntfile.js Gruntfile.js
    index d13be10800..1be0a88159 100644
     
    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'),
    58                fs = require( 'fs' ),
    module.exports = function(grunt) { 
    710                SOURCE_DIR = 'src/',
    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.
    1516        require('matchdep').filterDev(['grunt-*', '!grunt-legacy-util']).forEach( grunt.loadNpmTasks );
    1617        // Load legacy utils
    1718        grunt.util = require('grunt-legacy-util');
    1819
    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         } );
    24 
    2520        // Project configuration.
    2621        grunt.initConfig({
    2722                postcss: {
    module.exports = function(grunt) { 
    177172                                }
    178173                        }
    179174                },
    180                 browserify: mediaConfig,
    181175                sass: {
    182176                        colors: {
    183177                                expand: true,
    module.exports = function(grunt) { 
    338332                                ]
    339333                        },
    340334                        media: {
    341                                 options: {
    342                                         browserify: true
    343                                 },
    344335                                src: [
    345336                                        SOURCE_DIR + 'wp-includes/js/media/**/*.js'
    346337                                ]
    module.exports = function(grunt) { 
    553544                                dest: SOURCE_DIR + 'wp-includes/js/jquery/jquery.masonry.min.js'
    554545                        }
    555546                },
    556 
     547                webpack: {
     548                        prod: webpackConfig,
     549                        dev: webpackDevConfig
     550                },
    557551                concat: {
    558552                        tinymce: {
    559553                                options: {
    module.exports = function(grunt) { 
    719713                                }
    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: {
    725723                                files: [SOURCE_DIR + 'wp-admin/css/colors/**'],
    module.exports = function(grunt) { 
    757755
    758756        // Register tasks.
    759757
     758        // Webpack task.
     759        grunt.loadNpmTasks( 'grunt-webpack' );
     760
    760761        // RTL task.
    761762        grunt.registerTask('rtl', ['rtlcss:core', 'rtlcss:colors']);
    762763
    module.exports = function(grunt) { 
    780781        grunt.renameTask( 'watch', '_watch' );
    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                         } );
     784                if ( ! this.args.length || this.args.indexOf( 'webpack' ) > -1 ) {
    790785
    791                         grunt.task.run( 'browserify' );
     786                        grunt.task.run( 'webpack:dev' );
    792787                }
    793788
    794789                grunt.task.run( '_' + this.nameArgs );
    module.exports = function(grunt) { 
    799794        ] );
    800795
    801796        grunt.registerTask( 'precommit:js', [
    802                 'browserify',
     797                'webpack:prod',
    803798                'jshint:corejs',
    804799                'uglify:masonry',
    805800                'qunit:compiled'
    module.exports = function(grunt) { 
    978973        grunt.event.on('watch', function( action, filepath, target ) {
    979974                var src;
    980975
    981                 if ( [ 'all', 'rtl', 'browserify' ].indexOf( target ) === -1 ) {
     976                if ( [ 'all', 'rtl', 'webpack' ].indexOf( target ) === -1 ) {
    982977                        return;
    983978                }
    984979
  • package.json

    diff --git package.json package.json
    index 71695dfb77..26a787bd45 100644
     
    1515    "autoprefixer": "^6.5.1",
    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",
    2120    "grunt-contrib-concat": "~1.0.0",
     
    3635    "grunt-rtlcss": "~2.0.1",
    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}
  • src/wp-includes/js/media-audiovideo.js

    diff --git src/wp-includes/js/media-audiovideo.js src/wp-includes/js/media-audiovideo.js
    index 0a819747d2..cb2abd57ae 100644
     
    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 || {},
    472        l10n = window._wpMediaViewsL10n || {};
    wp.media.video = { 
    268336        }
    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){
    282 var State = wp.media.controller.State,
    283         l10n = wp.media.view.l10n,
    284         AudioDetails;
    285 
    286 /**
    287  * wp.media.controller.AudioDetails
    288  *
    289  * The controller for the Audio Details state
    290  *
    291  * @memberOf wp.media.controller
    292  *
    293  * @class
    294  * @augments wp.media.controller.State
    295  * @augments Backbone.Model
    296  */
    297 AudioDetails = State.extend(/** @lends wp.media.controller.AudioDetails.prototype */{
    298         defaults: {
    299                 id: 'audio-details',
    300                 toolbar: 'audio-details',
    301                 title: l10n.audioDetailsTitle,
    302                 content: 'audio-details',
    303                 menu: 'audio-details',
    304                 router: false,
    305                 priority: 60
    306         },
    307 
    308         initialize: function( options ) {
    309                 this.media = options.media;
    310                 State.prototype.initialize.apply( this, arguments );
    311         }
    312 });
    313 
    314 module.exports = AudioDetails;
    315 
    316 },{}],3:[function(require,module,exports){
    317 /**
    318  * wp.media.controller.VideoDetails
    319  *
    320  * The controller for the Video Details state
    321  *
    322  * @memberOf wp.media.controller
    323  *
    324  * @class
    325  * @augments wp.media.controller.State
    326  * @augments Backbone.Model
    327  */
    328 var State = wp.media.controller.State,
    329         l10n = wp.media.view.l10n,
    330         VideoDetails;
    331 
    332 VideoDetails = State.extend(/** @lends wp.media.controller.VideoDetails.prototype */{
    333         defaults: {
    334                 id: 'video-details',
    335                 toolbar: 'video-details',
    336                 title: l10n.videoDetailsTitle,
    337                 content: 'video-details',
    338                 menu: 'video-details',
    339                 router: false,
    340                 priority: 60
    341         },
     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 );
    342348
    343         initialize: function( options ) {
    344                 this.media = options.media;
    345                 State.prototype.initialize.apply( this, arguments );
    346         }
    347 });
    348349
    349 module.exports = VideoDetails;
     350/***/ }),
     351/* 1 */
     352/***/ (function(module, exports) {
    350353
    351 },{}],4:[function(require,module,exports){
    352354/**
    353355 * wp.media.model.PostMedia
    354356 *
    var PostMedia = Backbone.Model.extend(/** @lends wp.media.model.PostMedia.protot 
    392394
    393395module.exports = PostMedia;
    394396
    395 },{}],5:[function(require,module,exports){
    396 var MediaDetails = wp.media.view.MediaDetails,
     397
     398/***/ }),
     399/* 2 */
     400/***/ (function(module, exports) {
     401
     402var State = wp.media.controller.State,
     403        l10n = wp.media.view.l10n,
    397404        AudioDetails;
    398405
    399406/**
    400  * wp.media.view.AudioDetails
     407 * wp.media.controller.AudioDetails
    401408 *
    402  * @memberOf wp.media.view
     409 * The controller for the Audio Details state
     410 *
     411 * @memberOf wp.media.controller
    403412 *
    404413 * @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
     414 * @augments wp.media.controller.State
     415 * @augments Backbone.Model
    411416 */
    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                 }
     417AudioDetails = State.extend(/** @lends wp.media.controller.AudioDetails.prototype */{
     418        defaults: {
     419                id: 'audio-details',
     420                toolbar: 'audio-details',
     421                title: l10n.audioDetailsTitle,
     422                content: 'audio-details',
     423                menu: 'audio-details',
     424                router: false,
     425                priority: 60
     426        },
    428427
    429                 return this;
     428        initialize: function( options ) {
     429                this.media = options.media;
     430                State.prototype.initialize.apply( this, arguments );
    430431        }
    431432});
    432433
    433434module.exports = AudioDetails;
    434435
    435 },{}],6:[function(require,module,exports){
    436 var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
    437         MediaLibrary = wp.media.controller.MediaLibrary,
    438436
    439         l10n = wp.media.view.l10n,
    440         AudioDetails;
     437/***/ }),
     438/* 3 */
     439/***/ (function(module, exports) {
    441440
    442441/**
    443  * wp.media.view.MediaFrame.AudioDetails
     442 * wp.media.controller.VideoDetails
    444443 *
    445  * @memberOf wp.media.view.MediaFrame
     444 * The controller for the Video Details state
     445 *
     446 * @memberOf wp.media.controller
    446447 *
    447448 * @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
     449 * @augments wp.media.controller.State
     450 * @augments Backbone.Model
    456451 */
    457 AudioDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.AudioDetails.prototype */{
     452var State = wp.media.controller.State,
     453        l10n = wp.media.view.l10n,
     454        VideoDetails;
     455
     456VideoDetails = State.extend(/** @lends wp.media.controller.VideoDetails.prototype */{
    458457        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
     458                id: 'video-details',
     459                toolbar: 'video-details',
     460                title: l10n.videoDetailsTitle,
     461                content: 'video-details',
     462                menu: 'video-details',
     463                router: false,
     464                priority: 60
    467465        },
    468466
    469467        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                 ]);
     468                this.media = options.media;
     469                State.prototype.initialize.apply( this, arguments );
    508470        }
    509471});
    510472
    511 module.exports = AudioDetails;
     473module.exports = VideoDetails;
     474
     475
     476/***/ }),
     477/* 4 */
     478/***/ (function(module, exports) {
    512479
    513 },{}],7:[function(require,module,exports){
    514480var Select = wp.media.view.MediaFrame.Select,
    515481        l10n = wp.media.view.l10n,
    516482        MediaDetails;
    MediaDetails = Select.extend(/** @lends wp.media.view.MediaFrame.MediaDetails.pr 
    642608
    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,
    648700        l10n = wp.media.view.l10n,
    VideoDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.VideoDeta 
    779831
    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,
    785841        $ = jQuery,
    MediaDetails = AttachmentDisplay.extend(/** @lends wp.media.view.MediaDetails.pr 
    9491005
    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;
    9551059
    VideoDetails = MediaDetails.extend(/** @lends wp.media.view.VideoDetails.prototy 
    9941098
    9951099module.exports = VideoDetails;
    9961100
    997 },{}]},{},[1]);
     1101
     1102/***/ })
     1103/******/ ]);
     1104 No newline at end of file
  • src/wp-includes/js/media-grid.js

    diff --git src/wp-includes/js/media-grid.js src/wp-includes/js/media-grid.js
    index 6e35fb52a6..c9babd6d50 100644
     
    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;
    499
    EditAttachmentMetadata = wp.media.controller.State.extend(/** @lends wp.media.co 
    28123
    29124module.exports = EditAttachmentMetadata;
    30125
    31 },{}],2:[function(require,module,exports){
    32 var media = wp.media;
    33126
    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){
     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
    45137/**
    46  * wp.media.view.MediaFrame.Manage.Router
     138 * wp.media.view.MediaFrame.Manage
    47139 *
    48  * A router for handling the browser history and application state.
     140 * A generic management frame workflow.
    49141 *
    50  * @memberOf wp.media.view.MediaFrame.Manage
     142 * Used in the media grid view.
     143 *
     144 * @memberOf wp.media.view.MediaFrame
    51145 *
    52146 * @class
    53  * @augments Backbone.Router
     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
    54153 */
    55 var Router = Backbone.Router.extend(/** @lends wp.media.view.MediaFrame.Manage.Router.prototype */{
    56         routes: {
    57                 'upload.php?item=:slug&mode=edit': 'editItem',
    58                 'upload.php?item=:slug':           'showItem',
    59                 'upload.php?search=:query':        'search',
    60                 'upload.php':                      'reset'
    61         },
     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                });
    62169
    63         // Map routes against the page URL
    64         baseUrl: function( url ) {
    65                 return 'upload.php' + url;
    66         },
     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 ) );
    67177
    68         reset: function() {
    69                 var frame = wp.media.frames.edit;
     178                this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) );
    70179
    71                 if ( frame ) {
    72                         frame.close();
     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;
    73187                }
    74         },
    75188
    76         // Respond to the search route by filling the search field and trigggering the input event
    77         search: function( query ) {
    78                 jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
    79         },
     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 );
    80200
    81         // Show the modal with a specific item
    82         showItem: function( query ) {
    83                 var media = wp.media,
    84                         frame = media.frames.browse,
    85                         library = frame.state().get('library'),
    86                         item;
     201                        this.options.uploader = false;
     202                }
    87203
    88                 // Trigger the media frame to open the correct item
    89                 item = library.findWhere( { id: parseInt( query, 10 ) } );
    90                 item.set( 'skipHistory', true );
     204                this.gridRouter = new wp.media.view.MediaFrame.Manage.Router();
    91205
    92                 if ( item ) {
    93                         frame.trigger( 'edit:attachment', item );
    94                 } else {
    95                         item = media.attachment( query );
    96                         frame.listenTo( item, 'change', function( model ) {
    97                                 frame.stopListening( item );
    98                                 frame.trigger( 'edit:attachment', model );
    99                         } );
    100                         item.fetch();
    101                 }
    102         },
     206                // Call 'initialize' directly on the parent class.
     207                MediaFrame.prototype.initialize.apply( this, arguments );
    103208
    104         // Show the modal in edit mode with a specific item.
    105         editItem: function( query ) {
    106                 this.showItem( query );
    107                 wp.media.frames.edit.content.mode( 'edit-details' );
    108         }
    109 });
     209                // Append the frame view directly the supplied container.
     210                this.$el.appendTo( this.options.container );
    110211
    111 module.exports = Router;
     212                this.createStates();
     213                this.bindRegionModeHandlers();
     214                this.render();
     215                this.bindSearchHandler();
    112216
    113 },{}],4:[function(require,module,exports){
    114 var Details = wp.media.view.Attachment.Details,
    115         TwoColumn;
     217                wp.media.frames.browse = this;
     218        },
    116219
    117 /**
    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
    124  *
    125  * @class
    126  * @augments wp.media.view.Attachment.Details
    127  * @augments wp.media.view.Attachment
    128  * @augments wp.media.View
    129  * @augments wp.Backbone.View
    130  * @augments Backbone.View
    131  */
    132 TwoColumn = Details.extend(/** @lends wp.media.view.Attachment.Details.TowColumn.prototype */{
    133         template: wp.template( 'attachment-details-two-column' ),
     220        bindSearchHandler: function() {
     221                var search = this.$( '#media-search-input' ),
     222                        searchView = this.browserView.toolbar.get( 'search' ).$el,
     223                        listMode = this.$( '.view-list' ),
    134224
    135         initialize: function() {
    136                 this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) );
     225                        input  = _.throttle( function (e) {
     226                                var val = $( e.currentTarget ).val(),
     227                                        url = '';
    137228
    138                 Details.prototype.initialize.apply( this, arguments );
    139         },
     229                                if ( val ) {
     230                                        url += '?search=' + val;
     231                                        this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
     232                                }
     233                        }, 1000 );
    140234
    141         editAttachment: function( event ) {
    142                 if ( event ) {
    143                         event.preventDefault();
    144                 }
    145                 this.controller.content.mode( 'edit-image' );
     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                        });
    146252        },
    147253
    148254        /**
    149          * Noop this from parent class, doesn't apply here.
     255         * Create the default states for the frame.
    150256         */
    151         toggleSelectionHandler: function() {},
    152 
    153         render: function() {
    154                 Details.prototype.render.apply( this, arguments );
     257        createStates: function() {
     258                var options = this.options;
    155259
    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                 } );
    161         }
    162 });
     260                if ( this.options.states ) {
     261                        return;
     262                }
    163263
    164 module.exports = TwoColumn;
     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        },
    165278
    166 },{}],5:[function(require,module,exports){
    167 var Button = wp.media.view.Button,
    168         DeleteSelected = wp.media.view.DeleteSelectedButton,
    169         DeleteSelectedPermanently;
     279        /**
     280         * Bind region mode activation events to proper handlers.
     281         */
     282        bindRegionModeHandlers: function() {
     283                this.on( 'content:create:browse', this.browseContent, this );
    170284
    171 /**
    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
    177  *
    178  * @class
    179  * @augments wp.media.view.DeleteSelectedButton
    180  * @augments wp.media.view.Button
    181  * @augments wp.media.View
    182  * @augments wp.Backbone.View
    183  * @augments Backbone.View
    184  */
    185 DeleteSelectedPermanently = DeleteSelected.extend(/** @lends wp.media.view.DeleteSelectedPermanentlyButton.prototype */{
    186         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         },
     285                // Handle a frame-level event for editing an attachment.
     286                this.on( 'edit:attachment', this.openEditAttachmentModal, this );
    191287
    192         filterChange: function( model ) {
    193                 this.canShow = ( 'trash' === model.get( 'status' ) );
     288                this.on( 'select:activate', this.bindKeydown, this );
     289                this.on( 'select:deactivate', this.unbindKeydown, this );
    194290        },
    195291
    196         selectActivate: function() {
    197                 this.toggleDisabled();
    198                 this.$el.toggleClass( 'hidden', ! this.canShow );
     292        handleKeydown: function( e ) {
     293                if ( 27 === e.which ) {
     294                        e.preventDefault();
     295                        this.deactivateMode( 'select' ).activateMode( 'edit' );
     296                }
    199297        },
    200298
    201         selectDeactivate: function() {
    202                 this.toggleDisabled();
    203                 this.$el.addClass( 'hidden' );
     299        bindKeydown: function() {
     300                this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) );
    204301        },
    205302
    206         render: function() {
    207                 Button.prototype.render.apply( this, arguments );
    208                 this.selectActivate();
    209                 return this;
    210         }
    211 });
     303        unbindKeydown: function() {
     304                this.$body.off( 'keydown.select' );
     305        },
    212306
    213 module.exports = DeleteSelectedPermanently;
     307        fixPosition: function() {
     308                var $browser, $toolbar;
     309                if ( ! this.isModeActive( 'select' ) ) {
     310                        return;
     311                }
    214312
    215 },{}],6:[function(require,module,exports){
    216 var Button = wp.media.view.Button,
    217         l10n = wp.media.view.l10n,
    218         DeleteSelected;
     313                $browser = this.$('.attachments-browser');
     314                $toolbar = $browser.find('.media-toolbar');
    219315
    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 );
     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', '');
    238323                }
    239                 this.controller.on( 'selection:toggle', this.toggleDisabled, this );
    240324        },
    241325
    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 );
     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 );
    247345                } else {
    248                         this.model.set( 'text', l10n.deleteSelected );
     346                        wp.media.frames.edit = wp.media( {
     347                                frame:       'edit-attachments',
     348                                controller:  this,
     349                                library:     this.state().get('library'),
     350                                model:       model
     351                        } );
    249352                }
    250353        },
    251354
    252         toggleDisabled: function() {
    253                 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
     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 );
    254390        },
    255391
    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' );
     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                        } );
    262413                }
    263                 this.toggleDisabled();
    264                 return this;
    265414        }
    266415});
    267416
    268 module.exports = DeleteSelected;
     417module.exports = Manage;
    269418
    270 },{}],7:[function(require,module,exports){
    271419
    272 var Button = wp.media.view.Button,
    273         l10n = wp.media.view.l10n,
    274         SelectModeToggle;
     420/***/ }),
     421/* 13 */
     422/***/ (function(module, exports) {
     423
     424var Details = wp.media.view.Attachment.Details,
     425        TwoColumn;
    275426
    276427/**
    277  * wp.media.view.SelectModeToggleButton
     428 * wp.media.view.Attachment.Details.TwoColumn
    278429 *
    279  * @memberOf wp.media.view
     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
    280434 *
    281435 * @class
    282  * @augments wp.media.view.Button
     436 * @augments wp.media.view.Attachment.Details
     437 * @augments wp.media.view.Attachment
    283438 * @augments wp.media.View
    284439 * @augments wp.Backbone.View
    285440 * @augments Backbone.View
    286441 */
    287 SelectModeToggle = Button.extend(/** @lends wp.media.view.SelectModeToggle.prototype */{
    288         initialize: function() {
    289                 _.defaults( this.options, {
    290                         size : ''
    291                 } );
     442TwoColumn = Details.extend(/** @lends wp.media.view.Attachment.Details.TowColumn.prototype */{
     443        template: wp.template( 'attachment-details-two-column' ),
    292444
    293                 Button.prototype.initialize.apply( this, arguments );
    294                 this.controller.on( 'select:activate select:deactivate', this.toggleBulkEditHandler, this );
    295                 this.controller.on( 'selection:action:done', this.back, this );
    296         },
     445        initialize: function() {
     446                this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) );
    297447
    298         back: function () {
    299                 this.controller.deactivateMode( 'select' ).activateMode( 'edit' );
     448                Details.prototype.initialize.apply( this, arguments );
    300449        },
    301450
    302         click: function() {
    303                 Button.prototype.click.apply( this, arguments );
    304                 if ( this.controller.isModeActive( 'select' ) ) {
    305                         this.back();
    306                 } else {
    307                         this.controller.deactivateMode( 'edit' ).activateMode( 'select' );
     451        editAttachment: function( event ) {
     452                if ( event ) {
     453                        event.preventDefault();
    308454                }
     455                this.controller.content.mode( 'edit-image' );
    309456        },
    310457
    311         render: function() {
    312                 Button.prototype.render.apply( this, arguments );
    313                 this.$el.addClass( 'select-mode-toggle-button' );
    314                 return this;
    315         },
    316 
    317         toggleBulkEditHandler: function() {
    318                 var toolbar = this.controller.content.get().toolbar, children;
     458        /**
     459         * Noop this from parent class, doesn't apply here.
     460         */
     461        toggleSelectionHandler: function() {},
    319462
    320                 children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' );
     463        render: function() {
     464                Details.prototype.render.apply( this, arguments );
    321465
    322                 // TODO: the Frame should be doing all of this.
    323                 if ( this.controller.isModeActive( 'select' ) ) {
    324                         this.model.set( {
    325                                 size: 'large',
    326                                 text: l10n.cancelSelection
    327                         } );
    328                         children.not( '.spinner, .media-button' ).hide();
    329                         this.$el.show();
    330                         toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' );
    331                 } else {
    332                         this.model.set( {
    333                                 size: '',
    334                                 text: l10n.bulkSelect
    335                         } );
    336                         this.controller.content.get().$el.removeClass( 'fixed' );
    337                         toolbar.$el.css( 'width', '' );
    338                         toolbar.$( '.delete-selected-button' ).addClass( 'hidden' );
    339                         children.not( '.media-button' ).show();
    340                         this.controller.state().get( 'selection' ).reset();
    341                 }
     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                } );
    342471        }
    343472});
    344473
    345 module.exports = SelectModeToggle;
     474module.exports = TwoColumn;
    346475
    347 },{}],8:[function(require,module,exports){
    348 var View = wp.media.View,
    349         EditImage = wp.media.view.EditImage,
    350         Details;
     476
     477/***/ }),
     478/* 14 */
     479/***/ (function(module, exports) {
    351480
    352481/**
    353  * wp.media.view.EditImage.Details
     482 * wp.media.view.MediaFrame.Manage.Router
    354483 *
    355  * @memberOf wp.media.view.EditImage
     484 * A router for handling the browser history and application state.
     485 *
     486 * @memberOf wp.media.view.MediaFrame.Manage
    356487 *
    357488 * @class
    358  * @augments wp.media.view.EditImage
    359  * @augments wp.media.View
    360  * @augments wp.Backbone.View
    361  * @augments Backbone.View
     489 * @augments Backbone.Router
    362490 */
    363 Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype */{
     491var Router = Backbone.Router.extend(/** @lends wp.media.view.MediaFrame.Manage.Router.prototype */{
     492        routes: {
     493                'upload.php?item=:slug&mode=edit': 'editItem',
     494                'upload.php?item=:slug':           'showItem',
     495                'upload.php?search=:query':        'search',
     496                'upload.php':                      'reset'
     497        },
     498
     499        // Map routes against the page URL
     500        baseUrl: function( url ) {
     501                return 'upload.php' + url;
     502        },
     503
     504        reset: function() {
     505                var frame = wp.media.frames.edit;
     506
     507                if ( frame ) {
     508                        frame.close();
     509                }
     510        },
     511
     512        // Respond to the search route by filling the search field and trigggering the input event
     513        search: function( query ) {
     514                jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
     515        },
     516
     517        // Show the modal with a specific item
     518        showItem: function( query ) {
     519                var media = wp.media,
     520                        frame = media.frames.browse,
     521                        library = frame.state().get('library'),
     522                        item;
     523
     524                // Trigger the media frame to open the correct item
     525                item = library.findWhere( { id: parseInt( query, 10 ) } );
     526                item.set( 'skipHistory', true );
     527
     528                if ( item ) {
     529                        frame.trigger( 'edit:attachment', item );
     530                } else {
     531                        item = media.attachment( query );
     532                        frame.listenTo( item, 'change', function( model ) {
     533                                frame.stopListening( item );
     534                                frame.trigger( 'edit:attachment', model );
     535                        } );
     536                        item.fetch();
     537                }
     538        },
     539
     540        // Show the modal in edit mode with a specific item.
     541        editItem: function( query ) {
     542                this.showItem( query );
     543                wp.media.frames.edit.content.mode( 'edit-details' );
     544        }
     545});
     546
     547module.exports = Router;
     548
     549
     550/***/ }),
     551/* 15 */
     552/***/ (function(module, exports) {
     553
     554var View = wp.media.View,
     555        EditImage = wp.media.view.EditImage,
     556        Details;
     557
     558/**
     559 * wp.media.view.EditImage.Details
     560 *
     561 * @memberOf wp.media.view.EditImage
     562 *
     563 * @class
     564 * @augments wp.media.view.EditImage
     565 * @augments wp.media.View
     566 * @augments wp.Backbone.View
     567 * @augments Backbone.View
     568 */
     569Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype */{
    364570        initialize: function( options ) {
    365571                this.editor = window.imageEdit;
    366572                this.frame = options.frame;
    Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype 
    381587
    382588module.exports = Details;
    383589
    384 },{}],9:[function(require,module,exports){
     590
     591/***/ }),
     592/* 16 */
     593/***/ (function(module, exports) {
     594
    385595var Frame = wp.media.view.Frame,
    386596        MediaFrame = wp.media.view.MediaFrame,
    387597
    EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta 
    643853
    644854module.exports = EditAttachments;
    645855
    646 },{}],10:[function(require,module,exports){
    647 var MediaFrame = wp.media.view.MediaFrame,
    648         Library = wp.media.controller.Library,
    649856
    650         $ = Backbone.$,
    651         Manage;
     857/***/ }),
     858/* 17 */
     859/***/ (function(module, exports) {
     860
     861
     862var Button = wp.media.view.Button,
     863        l10n = wp.media.view.l10n,
     864        SelectModeToggle;
    652865
    653866/**
    654  * wp.media.view.MediaFrame.Manage
    655  *
    656  * A generic management frame workflow.
    657  *
    658  * Used in the media grid view.
     867 * wp.media.view.SelectModeToggleButton
    659868 *
    660  * @memberOf wp.media.view.MediaFrame
     869 * @memberOf wp.media.view
    661870 *
    662871 * @class
    663  * @augments wp.media.view.MediaFrame
    664  * @augments wp.media.view.Frame
     872 * @augments wp.media.view.Button
    665873 * @augments wp.media.View
    666874 * @augments wp.Backbone.View
    667875 * @augments Backbone.View
    668  * @mixes wp.media.controller.StateMachine
    669876 */
    670 Manage = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Manage.prototype */{
    671         /**
    672          * @constructs
    673          */
     877SelectModeToggle = Button.extend(/** @lends wp.media.view.SelectModeToggle.prototype */{
    674878        initialize: function() {
    675879                _.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');
     880                        size : ''
     881                } );
    698882
    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                 }
     883                Button.prototype.initialize.apply( this, arguments );
     884                this.controller.on( 'select:activate select:deactivate', this.toggleBulkEditHandler, this );
     885                this.controller.on( 'selection:action:done', this.back, this );
     886        },
    704887
    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 );
     888        back: function () {
     889                this.controller.deactivateMode( 'select' ).activateMode( 'edit' );
     890        },
    716891
    717                         this.options.uploader = false;
     892        click: function() {
     893                Button.prototype.click.apply( this, arguments );
     894                if ( this.controller.isModeActive( 'select' ) ) {
     895                        this.back();
     896                } else {
     897                        this.controller.deactivateMode( 'edit' ).activateMode( 'select' );
    718898                }
    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;
    734899        },
    735900
    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                         });
     901        render: function() {
     902                Button.prototype.render.apply( this, arguments );
     903                this.$el.addClass( 'select-mode-toggle-button' );
     904                return this;
    768905        },
    769906
    770         /**
    771          * Create the default states for the frame.
    772          */
    773         createStates: function() {
    774                 var options = this.options;
     907        toggleBulkEditHandler: function() {
     908                var toolbar = this.controller.content.get().toolbar, children;
    775909
    776                 if ( this.options.states ) {
    777                         return;
     910                children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' );
     911
     912                // TODO: the Frame should be doing all of this.
     913                if ( this.controller.isModeActive( 'select' ) ) {
     914                        this.model.set( {
     915                                size: 'large',
     916                                text: l10n.cancelSelection
     917                        } );
     918                        children.not( '.spinner, .media-button' ).hide();
     919                        this.$el.show();
     920                        toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' );
     921                } else {
     922                        this.model.set( {
     923                                size: '',
     924                                text: l10n.bulkSelect
     925                        } );
     926                        this.controller.content.get().$el.removeClass( 'fixed' );
     927                        toolbar.$el.css( 'width', '' );
     928                        toolbar.$( '.delete-selected-button' ).addClass( 'hidden' );
     929                        children.not( '.media-button' ).show();
     930                        this.controller.state().get( 'selection' ).reset();
    778931                }
     932        }
     933});
    779934
    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         },
     935module.exports = SelectModeToggle;
    794936
    795         /**
    796          * Bind region mode activation events to proper handlers.
    797          */
    798         bindRegionModeHandlers: function() {
    799                 this.on( 'content:create:browse', this.browseContent, this );
    800937
    801                 // Handle a frame-level event for editing an attachment.
    802                 this.on( 'edit:attachment', this.openEditAttachmentModal, this );
     938/***/ }),
     939/* 18 */
     940/***/ (function(module, exports) {
    803941
    804                 this.on( 'select:activate', this.bindKeydown, this );
    805                 this.on( 'select:deactivate', this.unbindKeydown, this );
    806         },
     942var Button = wp.media.view.Button,
     943        l10n = wp.media.view.l10n,
     944        DeleteSelected;
    807945
    808         handleKeydown: function( e ) {
    809                 if ( 27 === e.which ) {
    810                         e.preventDefault();
    811                         this.deactivateMode( 'select' ).activateMode( 'edit' );
     946/**
     947 * wp.media.view.DeleteSelectedButton
     948 *
     949 * A button that handles bulk Delete/Trash logic
     950 *
     951 * @memberOf wp.media.view
     952 *
     953 * @class
     954 * @augments wp.media.view.Button
     955 * @augments wp.media.View
     956 * @augments wp.Backbone.View
     957 * @augments Backbone.View
     958 */
     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 );
    812964                }
     965                this.controller.on( 'selection:toggle', this.toggleDisabled, this );
    813966        },
    814967
    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');
     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 );
    836973                } else {
    837                         $browser.removeClass( 'fixed' );
    838                         $toolbar.css('width', '');
     974                        this.model.set( 'text', l10n.deleteSelected );
    839975                }
    840976        },
    841977
    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                 }
     978        toggleDisabled: function() {
     979                this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
    852980        },
    853981
    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 );
     982        render: function() {
     983                Button.prototype.render.apply( this, arguments );
     984                if ( this.controller.isModeActive( 'select' ) ) {
     985                        this.$el.addClass( 'delete-selected-button' );
    861986                } 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                         } );
     987                        this.$el.addClass( 'delete-selected-button hidden' );
    868988                }
    869         },
     989                this.toggleDisabled();
     990                return this;
     991        }
     992});
    870993
    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();
     994module.exports = DeleteSelected;
    880995
    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',
    894996
    895                         suggestedWidth:  state.get('suggestedWidth'),
    896                         suggestedHeight: state.get('suggestedHeight'),
     997/***/ }),
     998/* 19 */
     999/***/ (function(module, exports) {
    8971000
    898                         AttachmentView: state.get('AttachmentView'),
     1001var Button = wp.media.view.Button,
     1002        DeleteSelected = wp.media.view.DeleteSelectedButton,
     1003        DeleteSelectedPermanently;
    8991004
    900                         scrollElement: document
    901                 });
    902                 this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) );
     1005/**
     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
     1011 *
     1012 * @class
     1013 * @augments wp.media.view.DeleteSelectedButton
     1014 * @augments wp.media.view.Button
     1015 * @augments wp.media.View
     1016 * @augments wp.Backbone.View
     1017 * @augments Backbone.View
     1018 */
     1019DeleteSelectedPermanently = DeleteSelected.extend(/** @lends wp.media.view.DeleteSelectedPermanentlyButton.prototype */{
     1020        initialize: function() {
     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        },
    9031025
    904                 this.errors = wp.Uploader.errors;
    905                 this.errors.on( 'add remove reset', this.sidebarVisibility, this );
     1026        filterChange: function( model ) {
     1027                this.canShow = ( 'trash' === model.get( 'status' ) );
    9061028        },
    9071029
    908         sidebarVisibility: function() {
    909                 this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length );
     1030        selectActivate: function() {
     1031                this.toggleDisabled();
     1032                this.$el.toggleClass( 'hidden', ! this.canShow );
    9101033        },
    9111034
    912         bindDeferred: function() {
    913                 if ( ! this.browserView.dfd ) {
    914                         return;
    915                 }
    916                 this.browserView.dfd.done( _.bind( this.startHistory, this ) );
     1035        selectDeactivate: function() {
     1036                this.toggleDisabled();
     1037                this.$el.addClass( 'hidden' );
    9171038        },
    9181039
    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                 }
     1040        render: function() {
     1041                Button.prototype.render.apply( this, arguments );
     1042                this.selectActivate();
     1043                return this;
    9301044        }
    9311045});
    9321046
    933 module.exports = Manage;
     1047module.exports = DeleteSelectedPermanently;
     1048
    9341049
    935 },{}]},{},[2]);
     1050/***/ })
     1051/******/ ]);
     1052 No newline at end of file
  • src/wp-includes/js/media-models.js

    diff --git src/wp-includes/js/media-models.js src/wp-includes/js/media-models.js
    index caa7fcb590..af64387257 100644
     
    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;
    473
    l10n = media.model.l10n = window._wpMediaModelsL10n || {}; 
    65134media.model.settings = l10n.settings || {};
    66135delete l10n.settings;
    67136
    68 Attachment = media.model.Attachment = require( './models/attachment.js' );
    69 Attachments = media.model.Attachments = require( './models/attachments.js' );
     137Attachment = media.model.Attachment = __webpack_require__( 21 );
     138Attachments = media.model.Attachments = __webpack_require__( 22 );
    70139
    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' );
     140media.model.Query = __webpack_require__( 23 );
     141media.model.PostImage = __webpack_require__( 24 );
     142media.model.Selection = __webpack_require__( 25 );
    74143
    75144/**
    76145 * ========================================================================
    $(window).on('unload', function(){ 
    238307        window.wp = null;
    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;
    244318
    Attachment = Backbone.Model.extend(/** @lends wp.media.model.Attachment.prototyp 
    409483
    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
    415494 *
    var Attachments = Backbone.Collection.extend(/** @lends wp.media.model.Attachmen 
    9541033
    9551034module.exports = Attachments;
    9561035
    957 },{}],4:[function(require,module,exports){
    958 /**
    959  * wp.media.model.PostImage
    960  *
    961  * An instance of an image that's been embedded into a post.
    962  *
    963  * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails.
    964  *
    965  * @memberOf wp.media.model
    966  *
    967  * @class
    968  * @augments Backbone.Model
    969  *
    970  * @param {int} [attributes]               Initial model attributes.
    971  * @param {int} [attributes.attachment_id] ID of the attachment.
    972  **/
    973 var PostImage = Backbone.Model.extend(/** @lends wp.media.model.PostImage.prototype */{
    974 
    975         initialize: function( attributes ) {
    976                 var Attachment = wp.media.model.Attachment;
    977                 this.attachment = false;
    9781036
    979                 if ( attributes.attachment_id ) {
    980                         this.attachment = Attachment.get( attributes.attachment_id );
    981                         if ( this.attachment.get( 'url' ) ) {
    982                                 this.dfd = jQuery.Deferred();
    983                                 this.dfd.resolve();
    984                         } else {
    985                                 this.dfd = this.attachment.fetch();
    986                         }
    987                         this.bindAttachmentListeners();
    988                 }
     1037/***/ }),
    9891038
    990                 // keep url in sync with changes to the type of link
    991                 this.on( 'change:link', this.updateLinkUrl, this );
    992                 this.on( 'change:size', this.updateSize, this );
     1039/***/ 23:
     1040/***/ (function(module, exports) {
    9931041
    994                 this.setLinkTypeFromUrl();
    995                 this.setAspectRatio();
    996 
    997                 this.set( 'originalUrl', attributes.url );
    998         },
    999 
    1000         bindAttachmentListeners: function() {
    1001                 this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );
    1002                 this.listenTo( this.attachment, 'sync', this.setAspectRatio );
    1003                 this.listenTo( this.attachment, 'change', this.updateSize );
    1004         },
    1005 
    1006         changeAttachment: function( attachment, props ) {
    1007                 this.stopListening( this.attachment );
    1008                 this.attachment = attachment;
    1009                 this.bindAttachmentListeners();
    1010 
    1011                 this.set( 'attachment_id', this.attachment.get( 'id' ) );
    1012                 this.set( 'caption', this.attachment.get( 'caption' ) );
    1013                 this.set( 'alt', this.attachment.get( 'alt' ) );
    1014                 this.set( 'size', props.get( 'size' ) );
    1015                 this.set( 'align', props.get( 'align' ) );
    1016                 this.set( 'link', props.get( 'link' ) );
    1017                 this.updateLinkUrl();
    1018                 this.updateSize();
    1019         },
    1020 
    1021         setLinkTypeFromUrl: function() {
    1022                 var linkUrl = this.get( 'linkUrl' ),
    1023                         type;
    1024 
    1025                 if ( ! linkUrl ) {
    1026                         this.set( 'link', 'none' );
    1027                         return;
    1028                 }
    1029 
    1030                 // default to custom if there is a linkUrl
    1031                 type = 'custom';
    1032 
    1033                 if ( this.attachment ) {
    1034                         if ( this.attachment.get( 'url' ) === linkUrl ) {
    1035                                 type = 'file';
    1036                         } else if ( this.attachment.get( 'link' ) === linkUrl ) {
    1037                                 type = 'post';
    1038                         }
    1039                 } else {
    1040                         if ( this.get( 'url' ) === linkUrl ) {
    1041                                 type = 'file';
    1042                         }
    1043                 }
    1044 
    1045                 this.set( 'link', type );
    1046         },
    1047 
    1048         updateLinkUrl: function() {
    1049                 var link = this.get( 'link' ),
    1050                         url;
    1051 
    1052                 switch( link ) {
    1053                         case 'file':
    1054                                 if ( this.attachment ) {
    1055                                         url = this.attachment.get( 'url' );
    1056                                 } else {
    1057                                         url = this.get( 'url' );
    1058                                 }
    1059                                 this.set( 'linkUrl', url );
    1060                                 break;
    1061                         case 'post':
    1062                                 this.set( 'linkUrl', this.attachment.get( 'link' ) );
    1063                                 break;
    1064                         case 'none':
    1065                                 this.set( 'linkUrl', '' );
    1066                                 break;
    1067                 }
    1068         },
    1069 
    1070         updateSize: function() {
    1071                 var size;
    1072 
    1073                 if ( ! this.attachment ) {
    1074                         return;
    1075                 }
    1076 
    1077                 if ( this.get( 'size' ) === 'custom' ) {
    1078                         this.set( 'width', this.get( 'customWidth' ) );
    1079                         this.set( 'height', this.get( 'customHeight' ) );
    1080                         this.set( 'url', this.get( 'originalUrl' ) );
    1081                         return;
    1082                 }
    1083 
    1084                 size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ];
    1085 
    1086                 if ( ! size ) {
    1087                         return;
    1088                 }
    1089 
    1090                 this.set( 'url', size.url );
    1091                 this.set( 'width', size.width );
    1092                 this.set( 'height', size.height );
    1093         },
    1094 
    1095         setAspectRatio: function() {
    1096                 var full;
    1097 
    1098                 if ( this.attachment && this.attachment.get( 'sizes' ) ) {
    1099                         full = this.attachment.get( 'sizes' ).full;
    1100 
    1101                         if ( full ) {
    1102                                 this.set( 'aspectRatio', full.width / full.height );
    1103                                 return;
    1104                         }
    1105                 }
    1106 
    1107                 this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) );
    1108         }
    1109 });
    1110 
    1111 module.exports = PostImage;
    1112 
    1113 },{}],5:[function(require,module,exports){
    11141042var Attachments = wp.media.model.Attachments,
    11151043        Query;
    11161044
    Query = Attachments.extend(/** @lends wp.media.model.Query.prototype */{ 
    14181346
    14191347module.exports = Query;
    14201348
    1421 },{}],6:[function(require,module,exports){
     1349
     1350/***/ }),
     1351
     1352/***/ 24:
     1353/***/ (function(module, exports) {
     1354
     1355/**
     1356 * wp.media.model.PostImage
     1357 *
     1358 * An instance of an image that's been embedded into a post.
     1359 *
     1360 * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails.
     1361 *
     1362 * @memberOf wp.media.model
     1363 *
     1364 * @class
     1365 * @augments Backbone.Model
     1366 *
     1367 * @param {int} [attributes]               Initial model attributes.
     1368 * @param {int} [attributes.attachment_id] ID of the attachment.
     1369 **/
     1370var PostImage = Backbone.Model.extend(/** @lends wp.media.model.PostImage.prototype */{
     1371
     1372        initialize: function( attributes ) {
     1373                var Attachment = wp.media.model.Attachment;
     1374                this.attachment = false;
     1375
     1376                if ( attributes.attachment_id ) {
     1377                        this.attachment = Attachment.get( attributes.attachment_id );
     1378                        if ( this.attachment.get( 'url' ) ) {
     1379                                this.dfd = jQuery.Deferred();
     1380                                this.dfd.resolve();
     1381                        } else {
     1382                                this.dfd = this.attachment.fetch();
     1383                        }
     1384                        this.bindAttachmentListeners();
     1385                }
     1386
     1387                // keep url in sync with changes to the type of link
     1388                this.on( 'change:link', this.updateLinkUrl, this );
     1389                this.on( 'change:size', this.updateSize, this );
     1390
     1391                this.setLinkTypeFromUrl();
     1392                this.setAspectRatio();
     1393
     1394                this.set( 'originalUrl', attributes.url );
     1395        },
     1396
     1397        bindAttachmentListeners: function() {
     1398                this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );
     1399                this.listenTo( this.attachment, 'sync', this.setAspectRatio );
     1400                this.listenTo( this.attachment, 'change', this.updateSize );
     1401        },
     1402
     1403        changeAttachment: function( attachment, props ) {
     1404                this.stopListening( this.attachment );
     1405                this.attachment = attachment;
     1406                this.bindAttachmentListeners();
     1407
     1408                this.set( 'attachment_id', this.attachment.get( 'id' ) );
     1409                this.set( 'caption', this.attachment.get( 'caption' ) );
     1410                this.set( 'alt', this.attachment.get( 'alt' ) );
     1411                this.set( 'size', props.get( 'size' ) );
     1412                this.set( 'align', props.get( 'align' ) );
     1413                this.set( 'link', props.get( 'link' ) );
     1414                this.updateLinkUrl();
     1415                this.updateSize();
     1416        },
     1417
     1418        setLinkTypeFromUrl: function() {
     1419                var linkUrl = this.get( 'linkUrl' ),
     1420                        type;
     1421
     1422                if ( ! linkUrl ) {
     1423                        this.set( 'link', 'none' );
     1424                        return;
     1425                }
     1426
     1427                // default to custom if there is a linkUrl
     1428                type = 'custom';
     1429
     1430                if ( this.attachment ) {
     1431                        if ( this.attachment.get( 'url' ) === linkUrl ) {
     1432                                type = 'file';
     1433                        } else if ( this.attachment.get( 'link' ) === linkUrl ) {
     1434                                type = 'post';
     1435                        }
     1436                } else {
     1437                        if ( this.get( 'url' ) === linkUrl ) {
     1438                                type = 'file';
     1439                        }
     1440                }
     1441
     1442                this.set( 'link', type );
     1443        },
     1444
     1445        updateLinkUrl: function() {
     1446                var link = this.get( 'link' ),
     1447                        url;
     1448
     1449                switch( link ) {
     1450                        case 'file':
     1451                                if ( this.attachment ) {
     1452                                        url = this.attachment.get( 'url' );
     1453                                } else {
     1454                                        url = this.get( 'url' );
     1455                                }
     1456                                this.set( 'linkUrl', url );
     1457                                break;
     1458                        case 'post':
     1459                                this.set( 'linkUrl', this.attachment.get( 'link' ) );
     1460                                break;
     1461                        case 'none':
     1462                                this.set( 'linkUrl', '' );
     1463                                break;
     1464                }
     1465        },
     1466
     1467        updateSize: function() {
     1468                var size;
     1469
     1470                if ( ! this.attachment ) {
     1471                        return;
     1472                }
     1473
     1474                if ( this.get( 'size' ) === 'custom' ) {
     1475                        this.set( 'width', this.get( 'customWidth' ) );
     1476                        this.set( 'height', this.get( 'customHeight' ) );
     1477                        this.set( 'url', this.get( 'originalUrl' ) );
     1478                        return;
     1479                }
     1480
     1481                size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ];
     1482
     1483                if ( ! size ) {
     1484                        return;
     1485                }
     1486
     1487                this.set( 'url', size.url );
     1488                this.set( 'width', size.width );
     1489                this.set( 'height', size.height );
     1490        },
     1491
     1492        setAspectRatio: function() {
     1493                var full;
     1494
     1495                if ( this.attachment && this.attachment.get( 'sizes' ) ) {
     1496                        full = this.attachment.get( 'sizes' ).full;
     1497
     1498                        if ( full ) {
     1499                                this.set( 'aspectRatio', full.width / full.height );
     1500                                return;
     1501                        }
     1502                }
     1503
     1504                this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) );
     1505        }
     1506});
     1507
     1508module.exports = PostImage;
     1509
     1510
     1511/***/ }),
     1512
     1513/***/ 25:
     1514/***/ (function(module, exports) {
     1515
    14221516var Attachments = wp.media.model.Attachments,
    14231517        Selection;
    14241518
    Selection = Attachments.extend(/** @lends wp.media.model.Selection.prototype */{ 
    15171611
    15181612module.exports = Selection;
    15191613
    1520 },{}]},{},[1]);
     1614
     1615/***/ })
     1616
     1617/******/ });
     1618 No newline at end of file
  • src/wp-includes/js/media-views.js

    diff --git src/wp-includes/js/media-views.js src/wp-includes/js/media-views.js
    index 18e6444691..2e462971e2 100644
     
    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__) {
    569
    6 /**
    7  * wp.media.controller.CollectionAdd
    8  *
    9  * A state for adding attachments to a collection (e.g. video playlist).
    10  *
    11  * @memberOf wp.media.controller
    12  *
    13  * @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').
    40  */
    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',
     70var media = wp.media,
     71        $ = jQuery,
     72        l10n;
    4773
    48                 priority:      100,
    49                 syncSelection: false
    50         }, Library.prototype.defaults ),
     74media.isTouchDevice = ( 'ontouchend' in document );
    5175
    52         /**
    53          * @since 3.9.0
    54          */
    55         initialize: function() {
    56                 var collectionType = this.get('collectionType');
     76// Link any localized strings.
     77l10n = media.view.l10n = window._wpMediaViewsL10n || {};
    5778
    58                 if ( 'video' === this.get( 'type' ) ) {
    59                         collectionType = 'video-' + collectionType;
    60                 }
     79// Link any settings.
     80media.view.settings = l10n.settings || {};
     81delete l10n.settings;
    6182
    62                 this.set( 'id', collectionType + '-library' );
    63                 this.set( 'toolbar', collectionType + '-add' );
    64                 this.set( 'menu', collectionType );
     83// Copy the `post` setting over to the model settings.
     84media.model.settings.post = media.view.settings.post;
    6585
    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         },
     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;
    7295
    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');
     96        transition = _.find( _.keys( transitions ), function( transition ) {
     97                return ! _.isUndefined( style[ transition ] );
     98        });
    8099
    81                 if ( editLibrary && editLibrary !== edit ) {
    82                         library.unobserve( editLibrary );
     100        return transition && {
     101                end: transitions[ transition ]
     102        };
     103}());
     104
     105/**
     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 );
    83127                }
    84128
    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                 };
     129                // Resolve the deferred when the first element finishes animating.
     130                selector.first().one( $.support.transition.end, deferred.resolve );
    90131
    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);
     132                // Just in case the event doesn't trigger, fire a callback.
     133                _.delay( deferred.resolve, sensitivity );
    97134
    98                 Library.prototype.activate.apply( this, arguments );
     135        // Otherwise, execute on the spot.
     136        } else {
     137                deferred.resolve();
    99138        }
    100 });
    101139
    102 module.exports = CollectionAdd;
     140        return deferred.promise();
     141};
    103142
    104 },{}],2:[function(require,module,exports){
    105 var Library = wp.media.controller.Library,
    106         l10n = wp.media.view.l10n,
    107         $ = jQuery,
    108         CollectionEdit;
     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) {
    109224
    110225/**
    111  * wp.media.controller.CollectionEdit
     226 * wp.media.controller.Region
    112227 *
    113  * A state for editing a collection, which is used by audio and video playlists,
    114  * and can be used for other collections.
     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.
    115237 *
    116238 * @memberOf wp.media.controller
    117239 *
    118240 * @class
    119  * @augments wp.media.controller.Library
    120  * @augments wp.media.controller.State
    121  * @augments Backbone.Model
    122241 *
    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').
     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.
    146246 */
    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         },
     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;
    162253
     254_.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{
    163255        /**
    164          * @since 3.9.0
     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.
    165266         */
    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() );
     267        mode: function( mode ) {
     268                if ( ! mode ) {
     269                        return this._mode;
    179270                }
    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 );
     271                // Bail if we're trying to change to the current mode.
     272                if ( mode === this._mode ) {
     273                        return this;
    183274                }
    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');
    192275
    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 );
     276                /**
     277                 * Region mode deactivation event.
     278                 *
     279                 * @event wp.media.controller.Region#deactivate
     280                 */
     281                this.trigger('deactivate');
    198282
    199                 this.frame.on( 'content:render:browse', this.renderSettings, this );
     283                this._mode = mode;
     284                this.render( mode );
    200285
    201                 Library.prototype.activate.apply( this, arguments );
     286                /**
     287                 * Region mode activation event.
     288                 *
     289                 * @event wp.media.controller.Region#activate
     290                 */
     291                this.trigger('activate');
     292                return this;
    202293        },
    203 
    204294        /**
    205          * @since 3.9.0
     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
    206305         */
    207         deactivate: function() {
    208                 // Stop watching for uploaded attachments.
    209                 this.get('library').unobserve( wp.Uploader.queue );
     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                }
    210311
    211                 this.frame.off( 'content:render:browse', this.renderSettings, this );
     312                var set = { view: null },
     313                        view;
    212314
    213                 Library.prototype.deactivate.apply( this, arguments );
     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;
    214340        },
    215341
    216342        /**
    217          * Render the collection embed settings view in the browser sidebar.
    218          *
    219          * @todo This is against the pattern elsewhere in media. Typically the frame
    220          *       is responsible for adding region mode callbacks. Explain.
     343         * Get the region's view.
    221344         *
    222          * @since 3.9.0
     345         * @since 3.5.0
    223346         *
    224          * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
     347         * @returns {wp.media.View}
    225348         */
    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 = {};
     349        get: function() {
     350                return this.view.views.first( this.selector );
     351        },
    232352
    233                 if ( ! library || ! attachmentsBrowserView ) {
    234                         return;
     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;
    235365                }
     366                return this.view.views.set( this.selector, views, options );
     367        },
    236368
    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 );
     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;
    246379
    247                 if ( dragInfoText ) {
    248                         attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
    249                                 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
    250                                 priority: -40
    251                         }) );
     380                if ( ! this._mode ) {
     381                        return;
    252382                }
    253383
    254                 // Add the 'Reverse order' button to the toolbar.
    255                 attachmentsBrowserView.toolbar.set( 'reverse', {
    256                         text:     l10n.reverseOrder,
    257                         priority: 80,
     384                args = _.toArray( arguments );
     385                base = this.id + ':' + event;
    258386
    259                         click: function() {
    260                                 library.reset( library.toArray().reverse() );
    261                         }
    262                 });
     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;
    263395        }
    264396});
    265397
    266 module.exports = CollectionEdit;
     398module.exports = Region;
    267399
    268 },{}],3:[function(require,module,exports){
    269 var l10n = wp.media.view.l10n,
    270         Cropper;
     400
     401/***/ }),
     402/* 28 */
     403/***/ (function(module, exports) {
    271404
    272405/**
    273  * wp.media.controller.Cropper
     406 * wp.media.controller.StateMachine
    274407 *
    275  * A state for cropping an image.
     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.
    276412 *
    277413 * @memberOf wp.media.controller
    278414 *
     415 * @since 3.5.0
     416 *
    279417 * @class
    280  * @augments wp.media.controller.State
    281418 * @augments Backbone.Model
     419 * @mixin
     420 * @mixes Backbone.Events
     421 *
     422 * @param {Array} states
    282423 */
    283 Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{
    284         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         },
     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};
    296428
    297         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         },
     429// Use Backbone's self-propagating `extend` inheritance method.
     430StateMachine.extend = Backbone.Model.extend;
    302431
    303         deactivate: function() {
    304                 this.frame.toolbar.mode('browse');
    305         },
     432_.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{
     433        /**
     434         * Fetch a state.
     435         *
     436         * If no `id` is provided, returns the active state.
     437         *
     438         * Implicitly creates states.
     439         *
     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();
    306451
    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);
     452                // Default to the active state.
     453                id = id || this._state;
    314454
     455                if ( id && ! this.states.get( id ) ) {
     456                        this.states.add({ id: id });
     457                }
     458                return this.states.get( id );
    315459        },
    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;
    324460
    325                 canSkipCrop = this.get('canSkipCrop') || false;
     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();
    326479
    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                                 }
    354                         }
    355                 };
     480                if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
     481                        return this;
     482                }
    356483
    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                         });
     484                if ( previous ) {
     485                        previous.trigger('deactivate');
     486                        this._lastState = previous.id;
    372487                }
    373488
    374                 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
     489                this._state = id;
     490                this.state().trigger('activate');
     491
     492                return this;
    375493        },
    376494
    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                 ) );
     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                }
    387510        }
    388511});
    389512
    390 module.exports = Cropper;
    391 
    392 },{}],4:[function(require,module,exports){
    393 var Controller = wp.media.controller,
    394         CustomizeImageCropper;
    395 
    396 /**
    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         }
     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        };
    433541});
    434542
    435 module.exports = CustomizeImageCropper;
     543module.exports = StateMachine;
    436544
    437 },{}],5:[function(require,module,exports){
    438 var l10n = wp.media.view.l10n,
    439         EditImage;
     545
     546/***/ }),
     547/* 29 */
     548/***/ (function(module, exports) {
    440549
    441550/**
    442  * wp.media.controller.EditImage
     551 * wp.media.controller.State
    443552 *
    444  * A state for editing (cropping, etc.) an image.
     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.
    445563 *
    446564 * @memberOf wp.media.controller
    447565 *
    448566 * @class
    449  * @augments wp.media.controller.State
    450567 * @augments Backbone.Model
    451  *
    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.
    460568 */
    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:     ''
     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 );
    469589        },
     590        /**
     591         * Ready event callback.
     592         *
     593         * @abstract
     594         * @since 3.5.0
     595         */
     596        ready: function() {},
    470597
    471598        /**
    472          * @since 3.9.0
     599         * Activate event callback.
     600         *
     601         * @abstract
     602         * @since 3.5.0
    473603         */
    474         activate: function() {
    475                 this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
    476         },
     604        activate: function() {},
    477605
    478606        /**
    479          * @since 3.9.0
     607         * Deactivate event callback.
     608         *
     609         * @abstract
     610         * @since 3.5.0
    480611         */
    481         deactivate: function() {
    482                 this.frame.off( 'toolbar:render:edit-image' );
    483         },
     612        deactivate: function() {},
    484613
    485614        /**
    486          * @since 3.9.0
     615         * Reset event callback.
     616         *
     617         * @abstract
     618         * @since 3.5.0
    487619         */
    488         toolbar: function() {
    489                 var frame = this.frame,
    490                         lastState = frame.lastState(),
    491                         previous = lastState && lastState.id;
     620        reset: function() {},
    492621
    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                 }) );
    510         }
    511 });
     622        /**
     623         * @access private
     624         * @since 3.5.0
     625         */
     626        _ready: function() {
     627                this._updateMenu();
     628        },
    512629
    513 module.exports = EditImage;
     630        /**
     631         * @access private
     632         * @since 3.5.0
     633        */
     634        _preActivate: function() {
     635                this.active = true;
     636        },
    514637
    515 },{}],6:[function(require,module,exports){
    516 var l10n = wp.media.view.l10n,
    517         $ = Backbone.$,
    518         Embed;
     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 );
    519647
    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: {}
     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();
    554655        },
    555656
    556         // The amount of time used when debouncing the scan.
    557         sensitivity: 400,
     657        /**
     658         * @access private
     659         * @since 3.5.0
     660         */
     661        _deactivate: function() {
     662                this.active = false;
    558663
    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 );
     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 );
    566670        },
    567671
    568672        /**
    569          * Trigger a scan of the embedded URL's content for metadata required to embed.
    570          *
    571          * @fires wp.media.controller.Embed#scan
     673         * @access private
     674         * @since 3.5.0
    572675         */
    573         scan: function() {
    574                 var scanners,
    575                         embed = this,
    576                         attributes = {
    577                                 type: 'link',
    578                                 scanners: []
    579                         };
     676        _title: function() {
     677                this.frame.title.render( this.get('titleMode') || 'default' );
     678        },
    580679
    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                 }
     680        /**
     681         * @access private
     682         * @since 3.5.0
     683         */
     684        _renderTitle: function( view ) {
     685                view.$el.text( this.get('title') || '' );
     686        },
    587687
    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;
     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;
    597700                }
    598701
    599                 attributes.loading = !! attributes.scanners;
    600                 this.set( attributes );
     702                this.frame.router.render( mode );
     703
     704                view = router.get();
     705                if ( view && view.select ) {
     706                        view.select( this.frame.content.mode() );
     707                }
    601708        },
     709
    602710        /**
    603          * Try scanning the embed as an image to discover its dimensions.
    604          *
    605          * @param {Object} attributes
     711         * @access private
     712         * @since 3.5.0
    606713         */
    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();
     714        _menu: function() {
     715                var menu = this.frame.menu,
     716                        mode = this.get('menu'),
     717                        view;
    613718
    614                 attributes.scanners.push( deferred.promise() );
     719                this.frame.$el.toggleClass( 'hide-menu', ! mode );
     720                if ( ! mode ) {
     721                        return;
     722                }
    615723
    616                 // Try to load the image and find its width/height.
    617                 image.onload = function() {
    618                         deferred.resolve();
     724                menu.mode( mode );
    619725
    620                         if ( state !== frame.state() || url !== state.props.get('url') ) {
    621                                 return;
    622                         }
     726                view = menu.get();
     727                if ( view && view.select ) {
     728                        view.select( this.id );
     729                }
     730        },
    623731
    624                         state.set({
    625                                 type: 'image'
    626                         });
     732        /**
     733         * @access private
     734         * @since 3.5.0
     735         */
     736        _updateMenu: function() {
     737                var previous = this.previous('menu'),
     738                        menu = this.get('menu');
    627739
    628                         state.props.set({
    629                                 width:  image.width,
    630                                 height: image.height
    631                         });
    632                 };
     740                if ( previous ) {
     741                        this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
     742                }
    633743
    634                 image.onerror = deferred.reject;
    635                 image.src = url;
     744                if ( menu ) {
     745                        this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
     746                }
    636747        },
    637748
    638         refresh: function() {
    639                 this.frame.toolbar.get().refresh();
    640         },
     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');
    641761
    642         reset: function() {
    643                 this.props.clear().set({ url: '' });
     762                if ( ! menuItem && title ) {
     763                        menuItem = { text: title };
    644764
    645                 if ( this.active ) {
    646                         this.refresh();
     765                        if ( priority ) {
     766                                menuItem.priority = priority;
     767                        }
     768                }
     769
     770                if ( ! menuItem ) {
     771                        return;
    647772                }
     773
     774                view.set( this.id, menuItem );
    648775        }
    649776});
    650777
    651 module.exports = Embed;
     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;
    652791
    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;
     792
     793/***/ }),
     794/* 30 */
     795/***/ (function(module, exports) {
    658796
    659797/**
    660  * wp.media.controller.FeaturedImage
     798 * wp.media.selectionSync
    661799 *
    662  * A state for selecting a featured image for a post.
     800 * Sync an attachments selection in a state with another state.
    663801 *
    664  * @memberOf wp.media.controller
     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.
    665804 *
    666  * @class
    667  * @augments wp.media.controller.Library
    668  * @augments wp.media.controller.State
    669  * @augments Backbone.Model
     805 * @memberOf wp.media
    670806 *
    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.
     807 * @mixin
    691808 */
    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 
     809var selectionSync = {
    703810        /**
    704811         * @since 3.5.0
    705812         */
    706         initialize: function() {
    707                 var library, comparator;
     813        syncSelection: function() {
     814                var selection = this.get('selection'),
     815                        manager = this.frame._selection;
    708816
    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' }) );
     817                if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     818                        return;
    712819                }
    713820
    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 );
     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                }
    754830
    755                 Library.prototype.deactivate.apply( this, arguments );
     831                // Sync the selection's single item with the master.
     832                selection.single( manager.single );
    756833        },
    757834
    758835        /**
     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         *
    759841         * @since 3.5.0
    760842         */
    761         updateSelection: function() {
     843        recordSelection: function() {
    762844                var selection = this.get('selection'),
    763                         id = wp.media.view.settings.post.featuredImageId,
    764                         attachment;
     845                        manager = this.frame._selection;
    765846
    766                 if ( '' !== id && -1 !== id ) {
    767                         attachment = Attachment.get( id );
    768                         attachment.fetch();
     847                if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     848                        return;
    769849                }
    770850
    771                 selection.reset( attachment ? [ attachment ] : [] );
     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;
    772859        }
    773 });
     860};
    774861
    775 module.exports = FeaturedImage;
     862module.exports = selectionSync;
    776863
    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;
     864
     865/***/ }),
     866/* 31 */
     867/***/ (function(module, exports) {
     868
     869var l10n = wp.media.view.l10n,
     870        getUserSetting = window.getUserSetting,
     871        setUserSetting = window.setUserSetting,
     872        Library;
    782873
    783874/**
    784  * wp.media.controller.GalleryAdd
     875 * wp.media.controller.Library
    785876 *
    786  * A state for selecting more images to add to a gallery.
     877 * A state for choosing an attachment or group of attachments from the media library.
    787878 *
    788879 * @memberOf wp.media.controller
    789880 *
    790881 * @class
    791  * @augments wp.media.controller.Library
    792882 * @augments wp.media.controller.State
    793883 * @augments Backbone.Model
     884 * @mixes media.selectionSync
    794885 *
    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.
     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.
    815909 */
    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 ),
     910Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{
     911        defaults: {
     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        },
    827927
    828928        /**
     929         * If a library isn't provided, query all media items.
     930         * If a selection instance isn't provided, create one.
     931         *
    829932         * @since 3.5.0
    830933         */
    831934        initialize: function() {
    832                 // If a library wasn't supplied, create a library of images.
     935                var selection = this.get('selection'),
     936                        props;
     937
    833938                if ( ! this.get('library') ) {
    834                         this.set( 'library', wp.media.query({ type: 'image' }) );
     939                        this.set( 'library', wp.media.query() );
    835940                }
    836941
    837                 Library.prototype.initialize.apply( this, arguments );
     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();
    838957        },
    839958
    840959        /**
    841960         * @since 3.5.0
    842961         */
    843962        activate: function() {
    844                 var library = this.get('library'),
    845                         edit    = this.frame.state('gallery-edit').get('library');
     963                this.syncSelection();
    846964
    847                 if ( this.editLibrary && this.editLibrary !== edit ) {
    848                         library.unobserve( this.editLibrary );
    849                 }
     965                wp.Uploader.queue.on( 'add', this.uploading, this );
    850966
    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                 };
     967                this.get('selection').on( 'add remove reset', this.refreshContent, this );
    856968
    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;
     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        },
    863974
    864                 Library.prototype.activate.apply( this, arguments );
    865         }
    866 });
     975        /**
     976         * @since 3.5.0
     977         */
     978        deactivate: function() {
     979                this.recordSelection();
    867980
    868 module.exports = GalleryAdd;
     981                this.frame.off( 'content:activate', this.saveContentMode, this );
    869982
    870 },{}],9:[function(require,module,exports){
    871 var Library = wp.media.controller.Library,
    872         l10n = wp.media.view.l10n,
    873         GalleryEdit;
     983                // Unbind all event handlers that use this state as the context
     984                // from the selection.
     985                this.get('selection').off( null, null, this );
    874986
    875 /**
    876  * wp.media.controller.GalleryEdit
    877  *
    878  * A state for editing a gallery's images and settings.
    879  *
    880  * @memberOf wp.media.controller
    881  *
    882  * @class
    883  * @augments wp.media.controller.Library
    884  * @augments wp.media.controller.State
    885  * @augments Backbone.Model
    886  *
    887  * @param {object}                     [attributes]                       The attributes hash passed to the state.
    888  * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
    889  * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
    890  * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
    891  *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
    892  * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
    893  * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
    894  * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    895  * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
    896  * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
    897  * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
    898  * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    899  * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
    900  * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
    901  * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
    902  * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
    903  * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
    904  * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
    905  *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
    906  * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
    907  *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    908  */
    909 GalleryEdit = Library.extend(/** @lends wp.media.controller.GalleryEdit.prototype */{
    910         defaults: {
    911                 id:               'gallery-edit',
    912                 title:            l10n.editGalleryTitle,
    913                 multiple:         false,
    914                 searchable:       false,
    915                 sortable:         true,
    916                 date:             false,
    917                 display:          false,
    918                 content:          'browse',
    919                 toolbar:          'gallery-edit',
    920                 describe:         true,
    921                 displaySettings:  true,
    922                 dragInfo:         true,
    923                 idealColumnWidth: 170,
    924                 editing:          false,
    925                 priority:         60,
    926                 syncSelection:    false
     987                wp.Uploader.queue.off( null, null, this );
    927988        },
    928989
    929990        /**
     991         * Reset the library to its initial state.
     992         *
    930993         * @since 3.5.0
    931994         */
    932         initialize: function() {
    933                 // If we haven't been provided a `library`, create a `Selection`.
    934                 if ( ! this.get('library') ) {
    935                         this.set( 'library', new wp.media.model.Selection() );
     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 ) );
    9361031                }
     1032                return displays[ attachment.cid ];
     1033        },
    9371034
    938                 // The single `Attachment` view to be used in the `Attachments` view.
    939                 if ( ! this.get('AttachmentView') ) {
    940                         this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     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';
    9411050                }
    9421051
    943                 Library.prototype.initialize.apply( this, arguments );
     1052                return settings;
    9441053        },
    9451054
    9461055        /**
    947          * @since 3.5.0
     1056         * Whether an attachment is image.
     1057         *
     1058         * @since 4.4.1
     1059         *
     1060         * @param {wp.media.model.Attachment} attachment
     1061         * @returns {Boolean}
    9481062         */
    949         activate: function() {
    950                 var library = this.get('library');
     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                }
    9511068
    952                 // Limit the library to images only.
    953                 library.props.set( 'type', 'image' );
     1069                return attachment.get('type') === 'image';
     1070        },
    9541071
    955                 // Watch for uploaded attachments.
    956                 this.get('library').observe( wp.Uploader.queue );
     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;
     1086                        }
     1087                }
    9571088
    958                 this.frame.on( 'content:render:browse', this.gallerySettings, this );
     1089                return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
     1090        },
    9591091
    960                 Library.prototype.activate.apply( this, arguments );
     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                }
    9611109        },
    9621110
    9631111        /**
     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         *
    9641122         * @since 3.5.0
     1123         *
     1124         * @param {wp.media.model.Attachment} attachment
    9651125         */
    966         deactivate: function() {
    967                 // Stop watching for uploaded attachments.
    968                 this.get('library').unobserve( wp.Uploader.queue );
     1126        uploading: function( attachment ) {
     1127                var content = this.frame.content;
    9691128
    970                 this.frame.off( 'content:render:browse', this.gallerySettings, this );
     1129                if ( 'upload' === content.mode() ) {
     1130                        this.frame.content.mode('browse');
     1131                }
    9711132
    972                 Library.prototype.deactivate.apply( this, arguments );
     1133                if ( this.get( 'autoSelect' ) ) {
     1134                        this.get('selection').add( attachment );
     1135                        this.frame.trigger( 'library:selection:add' );
     1136                }
    9731137        },
    9741138
    9751139        /**
    976          * @since 3.5.0
     1140         * Persist the mode of the content region as a user setting.
    9771141         *
    978          * @param browser
     1142         * @since 3.5.0
    9791143         */
    980         gallerySettings: function( browser ) {
    981                 if ( ! this.get('displaySettings') ) {
     1144        saveContentMode: function() {
     1145                if ( 'browse' !== this.get('router') ) {
    9821146                        return;
    9831147                }
    9841148
    985                 var library = this.get('library');
     1149                var mode = this.frame.content.mode(),
     1150                        view = this.frame.router.get();
    9861151
    987                 if ( ! library || ! browser ) {
    988                         return;
     1152                if ( view && view.get( mode ) ) {
     1153                        setUserSetting( 'libraryContent', mode );
    9891154                }
     1155        }
     1156});
    9901157
    991                 library.gallery = library.gallery || new Backbone.Model();
     1158// Make selectionSync available on any Media Library state.
     1159_.extend( Library.prototype, wp.media.selectionSync );
    9921160
    993                 browser.sidebar.set({
    994                         gallery: new wp.media.view.Settings.Gallery({
    995                                 controller: this,
    996                                 model:      library.gallery,
    997                                 priority:   40
    998                         })
    999                 });
     1161module.exports = Library;
    10001162
    1001                 browser.toolbar.set( 'reverse', {
    1002                         text:     l10n.reverseOrder,
    1003                         priority: 80,
    1004 
    1005                         click: function() {
    1006                                 library.reset( library.toArray().reverse() );
    1007                         }
    1008                 });
    1009         }
    1010 });
    10111163
    1012 module.exports = GalleryEdit;
     1164/***/ }),
     1165/* 32 */
     1166/***/ (function(module, exports) {
    10131167
    1014 },{}],10:[function(require,module,exports){
    10151168var State = wp.media.controller.State,
    10161169        Library = wp.media.controller.Library,
    10171170        l10n = wp.media.view.l10n,
    ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototyp 
    10751228
    10761229module.exports = ImageDetails;
    10771230
    1078 },{}],11:[function(require,module,exports){
    1079 var l10n = wp.media.view.l10n,
    1080         getUserSetting = window.getUserSetting,
    1081         setUserSetting = window.setUserSetting,
    1082         Library;
     1231
     1232/***/ }),
     1233/* 33 */
     1234/***/ (function(module, exports) {
     1235
     1236var Library = wp.media.controller.Library,
     1237        l10n = wp.media.view.l10n,
     1238        GalleryEdit;
    10831239
    10841240/**
    1085  * wp.media.controller.Library
     1241 * wp.media.controller.GalleryEdit
    10861242 *
    1087  * A state for choosing an attachment or group of attachments from the media library.
     1243 * A state for editing a gallery's images and settings.
    10881244 *
    10891245 * @memberOf wp.media.controller
    10901246 *
    10911247 * @class
     1248 * @augments wp.media.controller.Library
    10921249 * @augments wp.media.controller.State
    10931250 * @augments Backbone.Model
    1094  * @mixes media.selectionSync
    10951251 *
    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.
     1252 * @param {object}                     [attributes]                       The attributes hash passed to the state.
     1253 * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
     1254 * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
     1255 * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
     1256 *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
     1257 * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
     1258 * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
     1259 * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1260 * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
     1261 * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
     1262 * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
     1263 * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     1264 * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
     1265 * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
     1266 * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
     1267 * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
     1268 * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
     1269 * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
     1270 *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
     1271 * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
     1272 *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    11191273 */
    1120 Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{
     1274GalleryEdit = Library.extend(/** @lends wp.media.controller.GalleryEdit.prototype */{
    11211275        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
     1276                id:               'gallery-edit',
     1277                title:            l10n.editGalleryTitle,
     1278                multiple:         false,
     1279                searchable:       false,
     1280                sortable:         true,
     1281                date:             false,
     1282                display:          false,
     1283                content:          'browse',
     1284                toolbar:          'gallery-edit',
     1285                describe:         true,
     1286                displaySettings:  true,
     1287                dragInfo:         true,
     1288                idealColumnWidth: 170,
     1289                editing:          false,
     1290                priority:         60,
     1291                syncSelection:    false
    11361292        },
    11371293
    11381294        /**
    1139          * If a library isn't provided, query all media items.
    1140          * If a selection instance isn't provided, create one.
    1141          *
    11421295         * @since 3.5.0
    11431296         */
    11441297        initialize: function() {
    1145                 var selection = this.get('selection'),
    1146                         props;
    1147 
     1298                // If we haven't been provided a `library`, create a `Selection`.
    11481299                if ( ! this.get('library') ) {
    1149                         this.set( 'library', wp.media.query() );
     1300                        this.set( 'library', new wp.media.model.Selection() );
    11501301                }
    11511302
    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                         }) );
     1303                // The single `Attachment` view to be used in the `Attachments` view.
     1304                if ( ! this.get('AttachmentView') ) {
     1305                        this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    11641306                }
    11651307
    1166                 this.resetDisplays();
     1308                Library.prototype.initialize.apply( this, arguments );
    11671309        },
    11681310
    11691311        /**
    11701312         * @since 3.5.0
    11711313         */
    11721314        activate: function() {
    1173                 this.syncSelection();
     1315                var library = this.get('library');
    11741316
    1175                 wp.Uploader.queue.on( 'add', this.uploading, this );
     1317                // Limit the library to images only.
     1318                library.props.set( 'type', 'image' );
    11761319
    1177                 this.get('selection').on( 'add remove reset', this.refreshContent, this );
     1320                // Watch for uploaded attachments.
     1321                this.get('library').observe( wp.Uploader.queue );
    11781322
    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                 }
     1323                this.frame.on( 'content:render:browse', this.gallerySettings, this );
     1324
     1325                Library.prototype.activate.apply( this, arguments );
    11831326        },
    11841327
    11851328        /**
    11861329         * @since 3.5.0
    11871330         */
    11881331        deactivate: function() {
    1189                 this.recordSelection();
    1190 
    1191                 this.frame.off( 'content:activate', this.saveContentMode, this );
     1332                // Stop watching for uploaded attachments.
     1333                this.get('library').unobserve( wp.Uploader.queue );
    11921334
    1193                 // Unbind all event handlers that use this state as the context
    1194                 // from the selection.
    1195                 this.get('selection').off( null, null, this );
     1335                this.frame.off( 'content:render:browse', this.gallerySettings, this );
    11961336
    1197                 wp.Uploader.queue.off( null, null, this );
     1337                Library.prototype.deactivate.apply( this, arguments );
    11981338        },
    11991339
    12001340        /**
    1201          * Reset the library to its initial state.
    1202          *
    12031341         * @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.
    12151342         *
    1216          * @since 3.5.0
     1343         * @param browser
    12171344         */
    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         },
     1345        gallerySettings: function( browser ) {
     1346                if ( ! this.get('displaySettings') ) {
     1347                        return;
     1348                }
    12271349
    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;
     1350                var library = this.get('library');
    12381351
    1239                 if ( ! displays[ attachment.cid ] ) {
    1240                         displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
     1352                if ( ! library || ! browser ) {
     1353                        return;
    12411354                }
    1242                 return displays[ attachment.cid ];
    1243         },
    12441355
    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 );
     1356                library.gallery = library.gallery || new Backbone.Model();
    12551357
    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                 }
     1358                browser.sidebar.set({
     1359                        gallery: new wp.media.view.Settings.Gallery({
     1360                                controller: this,
     1361                                model:      library.gallery,
     1362                                priority:   40
     1363                        })
     1364                });
    12611365
    1262                 return settings;
    1263         },
     1366                browser.toolbar.set( 'reverse', {
     1367                        text:     l10n.reverseOrder,
     1368                        priority: 80,
    12641369
    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                 }
     1370                        click: function() {
     1371                                library.reset( library.toArray().reverse() );
     1372                        }
     1373                });
     1374        }
     1375});
    12781376
    1279                 return attachment.get('type') === 'image';
    1280         },
     1377module.exports = GalleryEdit;
    12811378
    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         },
    13011379
     1380/***/ }),
     1381/* 34 */
     1382/***/ (function(module, exports) {
    13021383
    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();
     1384var Selection = wp.media.model.Selection,
     1385        Library = wp.media.controller.Library,
     1386        l10n = wp.media.view.l10n,
     1387        GalleryAdd;
    13151388
    1316                 if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
    1317                         this.frame.content.render( this.get('content') );
    1318                 }
    1319         },
     1389/**
     1390 * wp.media.controller.GalleryAdd
     1391 *
     1392 * A state for selecting more images to add to a gallery.
     1393 *
     1394 * @memberOf wp.media.controller
     1395 *
     1396 * @class
     1397 * @augments wp.media.controller.Library
     1398 * @augments wp.media.controller.State
     1399 * @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.
     1421 */
     1422GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{
     1423        defaults: _.defaults({
     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
     1432        }, Library.prototype.defaults ),
    13201433
    13211434        /**
    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          *
    13321435         * @since 3.5.0
    1333          *
    1334          * @param {wp.media.model.Attachment} attachment
    13351436         */
    1336         uploading: function( attachment ) {
    1337                 var content = this.frame.content;
    1338 
    1339                 if ( 'upload' === content.mode() ) {
    1340                         this.frame.content.mode('browse');
     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' }) );
    13411441                }
    13421442
    1343                 if ( this.get( 'autoSelect' ) ) {
    1344                         this.get('selection').add( attachment );
    1345                         this.frame.trigger( 'library:selection:add' );
    1346                 }
     1443                Library.prototype.initialize.apply( this, arguments );
    13471444        },
    13481445
    13491446        /**
    1350          * Persist the mode of the content region as a user setting.
    1351          *
    13521447         * @since 3.5.0
    13531448         */
    1354         saveContentMode: function() {
    1355                 if ( 'browse' !== this.get('router') ) {
    1356                         return;
     1449        activate: function() {
     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 );
    13571455                }
    13581456
    1359                 var mode = this.frame.content.mode(),
    1360                         view = this.frame.router.get();
     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                };
    13611462
    1362                 if ( view && view.get( mode ) ) {
    1363                         setUserSetting( 'libraryContent', mode );
    1364                 }
     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
     1470                Library.prototype.activate.apply( this, arguments );
    13651471        }
    13661472});
    13671473
    1368 // Make selectionSync available on any Media Library state.
    1369 _.extend( Library.prototype, wp.media.selectionSync );
     1474module.exports = GalleryAdd;
    13701475
    1371 module.exports = Library;
    13721476
    1373 },{}],12:[function(require,module,exports){
     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
    13741486/**
    1375  * wp.media.controller.MediaLibrary
     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.
    13761491 *
    13771492 * @memberOf wp.media.controller
    13781493 *
    module.exports = Library; 
    13801495 * @augments wp.media.controller.Library
    13811496 * @augments wp.media.controller.State
    13821497 * @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').
    13831522 */
    1384 var Library = wp.media.controller.Library,
    1385         MediaLibrary;
    1386 
    1387 MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{
    1388         defaults: _.defaults({
    1389                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
    1390                 filterable:      'uploaded',
    1391 
    1392                 displaySettings: false,
    1393                 priority:        80,
    1394                 syncSelection:   false
    1395         }, Library.prototype.defaults ),
     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        },
    13961538
    13971539        /**
    13981540         * @since 3.9.0
    1399          *
    1400          * @param options
    14011541         */
    1402         initialize: function( options ) {
    1403                 this.media = options.media;
    1404                 this.type = options.type;
    1405                 this.set( 'library', wp.media.query({ type: this.type }) );
     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' );
    14061551
     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                }
    14071560                Library.prototype.initialize.apply( this, arguments );
    14081561        },
    14091562
    MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.protot 
    14111564         * @since 3.9.0
    14121565         */
    14131566        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                 }
     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
    14191577                Library.prototype.activate.apply( this, arguments );
    1420         }
    1421 });
     1578        },
    14221579
    1423 module.exports = MediaLibrary;
     1580        /**
     1581         * @since 3.9.0
     1582         */
     1583        deactivate: function() {
     1584                // Stop watching for uploaded attachments.
     1585                this.get('library').unobserve( wp.Uploader.queue );
    14241586
    1425 },{}],13:[function(require,module,exports){
    1426 /**
    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.
    1438  *
    1439  * @memberOf wp.media.controller
    1440  *
    1441  * @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.
    1447  */
    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.
    1458          *
    1459          * @since 3.5.0
    1460          *
    1461          * @param {string} mode
    1462          *
    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 );
     1587                this.frame.off( 'content:render:browse', this.renderSettings, this );
    14861588
    1487                 /**
    1488                  * Region mode activation event.
    1489                  *
    1490                  * @event wp.media.controller.Region#activate
    1491                  */
    1492                 this.trigger('activate');
    1493                 return this;
     1589                Library.prototype.deactivate.apply( this, arguments );
    14941590        },
     1591
    14951592        /**
    1496          * Render a mode.
    1497          *
    1498          * @since 3.5.0
     1593         * Render the collection embed settings view in the browser sidebar.
    14991594         *
    1500          * @param {string} mode
     1595         * @todo This is against the pattern elsewhere in media. Typically the frame
     1596         *       is responsible for adding region mode callbacks. Explain.
    15011597         *
    1502          * @fires Region#create
    1503          * @fires Region#render
     1598         * @since 3.9.0
    15041599         *
    1505          * @returns {wp.media.controller.Region} Returns itself to allow chaining
     1600         * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
    15061601         */
    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;
     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 = {};
    15271608
    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 );
     1609                if ( ! library || ! attachmentsBrowserView ) {
     1610                        return;
    15391611                }
    1540                 return this;
    1541         },
    15421612
    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         },
     1613                library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
    15531614
    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         },
     1615                obj[ collectionType ] = new SettingsView({
     1616                        controller: this,
     1617                        model:      library[ collectionType ],
     1618                        priority:   40
     1619                });
    15691620
    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;
     1621                attachmentsBrowserView.sidebar.set( obj );
    15801622
    1581                 if ( ! this._mode ) {
    1582                         return;
     1623                if ( dragInfoText ) {
     1624                        attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
     1625                                el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
     1626                                priority: -40
     1627                        }) );
    15831628                }
    15841629
    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 );
     1630                // Add the 'Reverse order' button to the toolbar.
     1631                attachmentsBrowserView.toolbar.set( 'reverse', {
     1632                        text:     l10n.reverseOrder,
     1633                        priority: 80,
    15911634
    1592                 // Trigger `{this.id}:{event}` event on the frame.
    1593                 args[0] = base;
    1594                 this.view.trigger.apply( this.view, args );
    1595                 return this;
     1635                        click: function() {
     1636                                library.reset( library.toArray().reverse() );
     1637                        }
     1638                });
    15961639        }
    15971640});
    15981641
    1599 module.exports = Region;
     1642module.exports = CollectionEdit;
    16001643
    1601 },{}],14:[function(require,module,exports){
    1602 var Library = wp.media.controller.Library,
    1603         l10n = wp.media.view.l10n,
    1604         ReplaceImage;
     1644
     1645/***/ }),
     1646/* 36 */
     1647/***/ (function(module, exports) {
     1648
     1649var Selection = wp.media.model.Selection,
     1650        Library = wp.media.controller.Library,
     1651        CollectionAdd;
    16051652
    16061653/**
    1607  * wp.media.controller.ReplaceImage
     1654 * wp.media.controller.CollectionAdd
    16081655 *
    1609  * A state for replacing an image.
     1656 * A state for adding attachments to a collection (e.g. video playlist).
    16101657 *
    16111658 * @memberOf wp.media.controller
    16121659 *
    var Library = wp.media.controller.Library, 
    16161663 * @augments Backbone.Model
    16171664 *
    16181665 * @param {object}                     [attributes]                         The attributes hash passed to the state.
    1619  * @param {string}                     [attributes.id=replace-image]        Unique identifier.
    1620  * @param {string}                     [attributes.title=Replace Image]     Title for the state. Displays in the media menu and the frame's title region.
     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.
    16211669 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    1622  *                                                                          If one is not supplied, a collection of all images will be created.
    1623  * @param {boolean}                    [attributes.multiple=false]          Whether multi-select is enabled.
     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.
    16241674 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    16251675 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    1626  * @param {string}                     [attributes.menu=default]            Initial mode for the menu region.
    16271676 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    1628  * @param {string}                     [attributes.toolbar=replace]         Initial mode for the toolbar region.
    1629  * @param {int}                        [attributes.priority=60]             The priority for the state link in the media menu.
     1677 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    16301678 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    1631  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    1632  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    16331679 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    16341680 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1635  * @param {boolean}                    [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    16361681 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1637  * @param {boolean}                    [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
     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').
    16381687 */
    1639 ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.prototype */{
    1640         defaults: _.defaults({
    1641                 id:            'replace-image',
    1642                 title:         l10n.replaceImageTitle,
    1643                 multiple:      false,
     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
    16441693                filterable:    'uploaded',
    1645                 toolbar:       'replace',
    1646                 menu:          false,
    1647                 priority:      60,
    1648                 syncSelection: true
     1694
     1695                priority:      100,
     1696                syncSelection: false
    16491697        }, Library.prototype.defaults ),
    16501698
    16511699        /**
    16521700         * @since 3.9.0
    1653          *
    1654          * @param options
    16551701         */
    1656         initialize: function( options ) {
    1657                 var library, comparator;
     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 );
    16581712
    1659                 this.image = options.image;
    16601713                // If we haven't been provided a `library`, create a `Selection`.
    16611714                if ( ! this.get('library') ) {
    1662                         this.set( 'library', wp.media.query({ type: 'image' }) );
     1715                        this.set( 'library', wp.media.query({ type: this.get('type') }) );
    16631716                }
    1664 
    16651717                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;
    16661820
    1667                 library    = this.get('library');
    1668                 comparator = library.comparator;
    1669 
    16701821                // Overload the library's comparator to push items that are not in
    16711822                // the mirrored query to the front of the aggregate collection.
    16721823                library.comparator = function( a, b ) {
    ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.protot 
    16881839        },
    16891840
    16901841        /**
    1691          * @since 3.9.0
     1842         * @since 3.5.0
    16921843         */
    16931844        activate: function() {
    16941845                this.updateSelection();
     1846                this.frame.on( 'open', this.updateSelection, this );
     1847
    16951848                Library.prototype.activate.apply( this, arguments );
    16961849        },
    16971850
    16981851        /**
    1699          * @since 3.9.0
     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
    17001862         */
    17011863        updateSelection: function() {
    17021864                var selection = this.get('selection'),
    1703                         attachment = this.image.attachment;
     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                }
    17041872
    17051873                selection.reset( attachment ? [ attachment ] : [] );
    17061874        }
    17071875});
    17081876
    1709 module.exports = ReplaceImage;
     1877module.exports = FeaturedImage;
    17101878
    1711 },{}],15:[function(require,module,exports){
    1712 var Controller = wp.media.controller,
    1713         SiteIconCropper;
     1879
     1880/***/ }),
     1881/* 38 */
     1882/***/ (function(module, exports) {
     1883
     1884var Library = wp.media.controller.Library,
     1885        l10n = wp.media.view.l10n,
     1886        ReplaceImage;
    17141887
    17151888/**
    1716  * wp.media.controller.SiteIconCropper
     1889 * wp.media.controller.ReplaceImage
    17171890 *
    1718  * A state for cropping a Site Icon.
     1891 * A state for replacing an image.
    17191892 *
    17201893 * @memberOf wp.media.controller
    17211894 *
    17221895 * @class
    1723  * @augments wp.media.controller.Cropper
     1896 * @augments wp.media.controller.Library
    17241897 * @augments wp.media.controller.State
    17251898 * @augments Backbone.Model
     1899 *
     1900 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     1901 * @param {string}                     [attributes.id=replace-image]        Unique identifier.
     1902 * @param {string}                     [attributes.title=Replace Image]     Title for the state. Displays in the media menu and the frame's title region.
     1903 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     1904 *                                                                          If one is not supplied, a collection of all images will be created.
     1905 * @param {boolean}                    [attributes.multiple=false]          Whether multi-select is enabled.
     1906 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     1907 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     1908 * @param {string}                     [attributes.menu=default]            Initial mode for the menu region.
     1909 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     1910 * @param {string}                     [attributes.toolbar=replace]         Initial mode for the toolbar region.
     1911 * @param {int}                        [attributes.priority=60]             The priority for the state link in the media menu.
     1912 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     1913 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     1914 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     1915 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1916 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     1917 * @param {boolean}                    [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     1918 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     1919 * @param {boolean}                    [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    17261920 */
    1727 SiteIconCropper = Controller.Cropper.extend(/** @lends wp.media.controller.SiteIconCropper.prototype */{
    1728         activate: function() {
    1729                 this.frame.on( 'content:create:crop', this.createCropContent, this );
    1730                 this.frame.on( 'close', this.removeCropper, this );
    1731                 this.set('selection', new Backbone.Collection(this.frame._selection.single));
    1732         },
     1921ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.prototype */{
     1922        defaults: _.defaults({
     1923                id:            'replace-image',
     1924                title:         l10n.replaceImageTitle,
     1925                multiple:      false,
     1926                filterable:    'uploaded',
     1927                toolbar:       'replace',
     1928                menu:          false,
     1929                priority:      60,
     1930                syncSelection: true
     1931        }, Library.prototype.defaults ),
    17331932
    1734         createCropContent: function() {
    1735                 this.cropperView = new wp.media.view.SiteIconCropper({
    1736                         controller: this,
    1737                         attachment: this.get('selection').first()
    1738                 });
    1739                 this.cropperView.on('image-loaded', this.createCropToolbar, this);
    1740                 this.frame.content.set(this.cropperView);
     1933        /**
     1934         * @since 3.9.0
     1935         *
     1936         * @param options
     1937         */
     1938        initialize: function( options ) {
     1939                var library, comparator;
     1940
     1941                this.image = options.image;
     1942                // If we haven't been provided a `library`, create a `Selection`.
     1943                if ( ! this.get('library') ) {
     1944                        this.set( 'library', wp.media.query({ type: 'image' }) );
     1945                }
     1946
     1947                Library.prototype.initialize.apply( this, arguments );
     1948
     1949                library    = this.get('library');
     1950                comparator = library.comparator;
     1951
     1952                // Overload the library's comparator to push items that are not in
     1953                // the mirrored query to the front of the aggregate collection.
     1954                library.comparator = function( a, b ) {
     1955                        var aInQuery = !! this.mirroring.get( a.cid ),
     1956                                bInQuery = !! this.mirroring.get( b.cid );
     1957
     1958                        if ( ! aInQuery && bInQuery ) {
     1959                                return -1;
     1960                        } else if ( aInQuery && ! bInQuery ) {
     1961                                return 1;
     1962                        } else {
     1963                                return comparator.apply( this, arguments );
     1964                        }
     1965                };
    17411966
     1967                // Add all items in the selection to the library, so any featured
     1968                // images that are not initially loaded still appear.
     1969                library.observe( this.get('selection') );
    17421970        },
    17431971
    1744         doCrop: function( attachment ) {
    1745                 var cropDetails = attachment.get( 'cropDetails' ),
    1746                         control = this.get( 'control' );
     1972        /**
     1973         * @since 3.9.0
     1974         */
     1975        activate: function() {
     1976                this.updateSelection();
     1977                Library.prototype.activate.apply( this, arguments );
     1978        },
    17471979
    1748                 cropDetails.dst_width  = control.params.width;
    1749                 cropDetails.dst_height = control.params.height;
     1980        /**
     1981         * @since 3.9.0
     1982         */
     1983        updateSelection: function() {
     1984                var selection = this.get('selection'),
     1985                        attachment = this.image.attachment;
    17501986
    1751                 return wp.ajax.post( 'crop-image', {
    1752                         nonce: attachment.get( 'nonces' ).edit,
    1753                         id: attachment.get( 'id' ),
    1754                         context: 'site-icon',
    1755                         cropDetails: cropDetails
    1756                 } );
     1987                selection.reset( attachment ? [ attachment ] : [] );
    17571988        }
    17581989});
    17591990
    1760 module.exports = SiteIconCropper;
     1991module.exports = ReplaceImage;
     1992
     1993
     1994/***/ }),
     1995/* 39 */
     1996/***/ (function(module, exports) {
     1997
     1998var l10n = wp.media.view.l10n,
     1999        EditImage;
    17612000
    1762 },{}],16:[function(require,module,exports){
    17632001/**
    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.
     2002 * wp.media.controller.EditImage
    17682003 *
    1769  * States are stored as models in a Backbone collection.
     2004 * A state for editing (cropping, etc.) an image.
    17702005 *
    17712006 * @memberOf wp.media.controller
    17722007 *
    1773  * @since 3.5.0
    1774  *
    17752008 * @class
     2009 * @augments wp.media.controller.State
    17762010 * @augments Backbone.Model
    1777  * @mixin
    1778  * @mixes Backbone.Events
    17792011 *
    1780  * @param {Array} states
     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.
    17812020 */
    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;
     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        },
    17892030
    1790 _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{
    17912031        /**
    1792          * Fetch a state.
    1793          *
    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
     2032         * @since 3.9.0
    18062033         */
    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 );
     2034        activate: function() {
     2035                this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
    18172036        },
    18182037
    18192038        /**
    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
     2039         * @since 3.9.0
    18342040         */
    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');
    1849 
    1850                 return this;
     2041        deactivate: function() {
     2042                this.frame.off( 'toolbar:render:edit-image' );
    18512043        },
    18522044
    18532045        /**
    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
     2046         * @since 3.9.0
    18632047         */
    1864         lastState: function() {
    1865                 if ( this._lastState ) {
    1866                         return this.state( this._lastState );
    1867                 }
     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                }) );
    18682070        }
    18692071});
    18702072
    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
    1876          * @instance
    1877          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    1878          */
    1879         /**
    1880          * @function off
    1881          * @memberOf wp.media.controller.StateMachine
    1882          * @instance
    1883          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    1884          */
    1885         /**
    1886          * @function trigger
    1887          * @memberOf wp.media.controller.StateMachine
    1888          * @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 );
    1897                 return this;
    1898         };
    1899 });
     2073module.exports = EditImage;
    19002074
    1901 module.exports = StateMachine;
    19022075
    1903 },{}],17:[function(require,module,exports){
     2076/***/ }),
     2077/* 40 */
     2078/***/ (function(module, exports) {
     2079
    19042080/**
    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.
     2081 * wp.media.controller.MediaLibrary
    19172082 *
    19182083 * @memberOf wp.media.controller
    19192084 *
    19202085 * @class
     2086 * @augments wp.media.controller.Library
     2087 * @augments wp.media.controller.State
    19212088 * @augments Backbone.Model
    19222089 */
    1923 var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{
    1924         /**
    1925          * Constructor.
    1926          *
    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.
    1946          *
    1947          * @abstract
    1948          * @since 3.5.0
    1949          */
    1950         ready: function() {},
     2090var Library = wp.media.controller.Library,
     2091        MediaLibrary;
    19512092
    1952         /**
    1953          * Activate event callback.
    1954          *
    1955          * @abstract
    1956          * @since 3.5.0
    1957          */
    1958         activate: function() {},
     2093MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{
     2094        defaults: _.defaults({
     2095                // Attachments browser defaults. @see media.view.AttachmentsBrowser
     2096                filterable:      'uploaded',
    19592097
    1960         /**
    1961          * Deactivate event callback.
    1962          *
    1963          * @abstract
    1964          * @since 3.5.0
    1965          */
    1966         deactivate: function() {},
     2098                displaySettings: false,
     2099                priority:        80,
     2100                syncSelection:   false
     2101        }, Library.prototype.defaults ),
    19672102
    19682103        /**
    1969          * Reset event callback.
     2104         * @since 3.9.0
    19702105         *
    1971          * @abstract
    1972          * @since 3.5.0
    1973          */
    1974         reset: function() {},
    1975 
    1976         /**
    1977          * @access private
    1978          * @since 3.5.0
     2106         * @param options
    19792107         */
    1980         _ready: function() {
    1981                 this._updateMenu();
    1982         },
     2108        initialize: function( options ) {
     2109                this.media = options.media;
     2110                this.type = options.type;
     2111                this.set( 'library', wp.media.query({ type: this.type }) );
    19832112
    1984         /**
    1985          * @access private
    1986          * @since 3.5.0
    1987         */
    1988         _preActivate: function() {
    1989                 this.active = true;
     2113                Library.prototype.initialize.apply( this, arguments );
    19902114        },
    19912115
    19922116        /**
    1993          * @access private
    1994          * @since 3.5.0
     2117         * @since 3.9.0
    19952118         */
    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 );
     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});
    20012128
    2002                 this.frame.on( 'title:render:default', this._renderTitle, this );
     2129module.exports = MediaLibrary;
    20032130
    2004                 this._title();
    2005                 this._menu();
    2006                 this._toolbar();
    2007                 this._content();
    2008                 this._router();
    2009         },
    20102131
    2011         /**
    2012          * @access private
    2013          * @since 3.5.0
    2014          */
    2015         _deactivate: function() {
    2016                 this.active = false;
     2132/***/ }),
     2133/* 41 */
     2134/***/ (function(module, exports) {
    20172135
    2018                 this.frame.off( 'title:render:default', this._renderTitle, this );
     2136var l10n = wp.media.view.l10n,
     2137        $ = Backbone.$,
     2138        Embed;
    20192139
    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 );
     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: {}
    20242174        },
    20252175
    2026         /**
    2027          * @access private
    2028          * @since 3.5.0
    2029          */
    2030         _title: function() {
    2031                 this.frame.title.render( this.get('titleMode') || 'default' );
    2032         },
     2176        // The amount of time used when debouncing the scan.
     2177        sensitivity: 400,
    20332178
    2034         /**
    2035          * @access private
    2036          * @since 3.5.0
    2037          */
    2038         _renderTitle: function( view ) {
    2039                 view.$el.text( this.get('title') || '' );
     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 );
    20402186        },
    20412187
    20422188        /**
    2043          * @access private
    2044          * @since 3.5.0
     2189         * Trigger a scan of the embedded URL's content for metadata required to embed.
     2190         *
     2191         * @fires wp.media.controller.Embed#scan
    20452192         */
    2046         _router: function() {
    2047                 var router = this.frame.router,
    2048                         mode = this.get('router'),
    2049                         view;
     2193        scan: function() {
     2194                var scanners,
     2195                        embed = this,
     2196                        attributes = {
     2197                                type: 'link',
     2198                                scanners: []
     2199                        };
    20502200
    2051                 this.frame.$el.toggleClass( 'hide-router', ! mode );
    2052                 if ( ! mode ) {
    2053                         return;
     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 );
    20542206                }
    20552207
    2056                 this.frame.router.render( mode );
    2057 
    2058                 view = router.get();
    2059                 if ( view && view.select ) {
    2060                         view.select( this.frame.content.mode() );
     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;
    20612217                }
    2062         },
    20632218
     2219                attributes.loading = !! attributes.scanners;
     2220                this.set( attributes );
     2221        },
    20642222        /**
    2065          * @access private
    2066          * @since 3.5.0
     2223         * Try scanning the embed as an image to discover its dimensions.
     2224         *
     2225         * @param {Object} attributes
    20672226         */
    2068         _menu: function() {
    2069                 var menu = this.frame.menu,
    2070                         mode = this.get('menu'),
    2071                         view;
     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();
    20722233
    2073                 this.frame.$el.toggleClass( 'hide-menu', ! mode );
    2074                 if ( ! mode ) {
    2075                         return;
    2076                 }
     2234                attributes.scanners.push( deferred.promise() );
    20772235
    2078                 menu.mode( mode );
     2236                // Try to load the image and find its width/height.
     2237                image.onload = function() {
     2238                        deferred.resolve();
    20792239
    2080                 view = menu.get();
    2081                 if ( view && view.select ) {
    2082                         view.select( this.id );
    2083                 }
    2084         },
     2240                        if ( state !== frame.state() || url !== state.props.get('url') ) {
     2241                                return;
     2242                        }
    20852243
    2086         /**
    2087          * @access private
    2088          * @since 3.5.0
    2089          */
    2090         _updateMenu: function() {
    2091                 var previous = this.previous('menu'),
    2092                         menu = this.get('menu');
     2244                        state.set({
     2245                                type: 'image'
     2246                        });
    20932247
    2094                 if ( previous ) {
    2095                         this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
    2096                 }
     2248                        state.props.set({
     2249                                width:  image.width,
     2250                                height: image.height
     2251                        });
     2252                };
    20972253
    2098                 if ( menu ) {
    2099                         this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
    2100                 }
     2254                image.onerror = deferred.reject;
     2255                image.src = url;
    21012256        },
    21022257
    2103         /**
    2104          * Create a view in the media menu for the state.
    2105          *
    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 };
     2258        refresh: function() {
     2259                this.frame.toolbar.get().refresh();
     2260        },
    21182261
    2119                         if ( priority ) {
    2120                                 menuItem.priority = priority;
    2121                         }
    2122                 }
     2262        reset: function() {
     2263                this.props.clear().set({ url: '' });
    21232264
    2124                 if ( ! menuItem ) {
    2125                         return;
     2265                if ( this.active ) {
     2266                        this.refresh();
    21262267                }
    2127 
    2128                 view.set( this.id, menuItem );
    21292268        }
    21302269});
    21312270
    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         };
    2142 });
     2271module.exports = Embed;
    21432272
    2144 module.exports = State;
    21452273
    2146 },{}],18:[function(require,module,exports){
     2274/***/ }),
     2275/* 42 */
     2276/***/ (function(module, exports) {
     2277
     2278var l10n = wp.media.view.l10n,
     2279        Cropper;
     2280
    21472281/**
    2148  * wp.media.selectionSync
    2149  *
    2150  * Sync an attachments selection in a state with another state.
     2282 * wp.media.controller.Cropper
    21512283 *
    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.
     2284 * A state for cropping an image.
    21542285 *
    2155  * @memberOf wp.media
     2286 * @memberOf wp.media.controller
    21562287 *
    2157  * @mixin
     2288 * @class
     2289 * @augments wp.media.controller.State
     2290 * @augments Backbone.Model
    21582291 */
    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 ) {
    2168                         return;
    2169                 }
     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,
    21702301
    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                 }
     2302                // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state.
     2303                doCropArgs: {}
     2304        },
    21802305
    2181                 // Sync the selection's single item with the master.
    2182                 selection.single( manager.single );
     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));
    21832310        },
    21842311
    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;
     2312        deactivate: function() {
     2313                this.frame.toolbar.mode('browse');
     2314        },
    21962315
    2197                 if ( ! this.get('syncSelection') || ! manager || ! selection ) {
    2198                         return;
    2199                 }
     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);
    22002323
    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                 }
     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;
    22072333
    2208                 manager.single = selection._single;
    2209         }
    2210 };
     2334                canSkipCrop = this.get('canSkipCrop') || false;
    22112335
    2212 module.exports = selectionSync;
     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 },
    22132344
    2214 },{}],19:[function(require,module,exports){
    2215 var media = wp.media,
    2216         $ = jQuery,
    2217         l10n;
     2345                                        click: function() {
     2346                                                var controller = this.controller,
     2347                                                        selection;
    22182348
    2219 media.isTouchDevice = ( 'ontouchend' in document );
     2349                                                selection = controller.state().get('selection').first();
     2350                                                selection.set({cropDetails: controller.state().imgSelect.getSelection()});
    22202351
    2221 // Link any localized strings.
    2222 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
     2352                                                this.$el.text(l10n.cropping);
     2353                                                this.$el.attr('disabled', true);
    22232354
    2224 // Link any settings.
    2225 media.view.settings = l10n.settings || {};
    2226 delete l10n.settings;
     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                };
    22272365
    2228 // Copy the `post` setting over to the model settings.
    2229 media.model.settings.post = media.view.settings.post;
     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                }
    22302382
    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;
     2383                this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
     2384        },
    22402385
    2241         transition = _.find( _.keys( transitions ), function( transition ) {
    2242                 return ! _.isUndefined( style[ transition ] );
    2243         });
     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});
    22442398
    2245         return transition && {
    2246                 end: transitions[ transition ]
    2247         };
    2248 }());
     2399module.exports = Cropper;
    22492400
    2250 /**
    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 );
     2401
     2402/***/ }),
     2403/* 43 */
     2404/***/ (function(module, exports) {
     2405
     2406var Controller = wp.media.controller,
     2407        CustomizeImageCropper;
    22562408
    22572409/**
    2258  * Makes it easier to bind events using transitions.
     2410 * wp.media.controller.CustomizeImageCropper
    22592411 *
    2260  * @param {string} selector
    2261  * @param {Number} sensitivity
    2262  * @returns {Promise}
     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
    22632420 */
    2264 media.transition = function( selector, sensitivity ) {
    2265         var deferred = $.Deferred();
     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;
    22662426
    2267         sensitivity = sensitivity || 2000;
     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;
    22682431
    2269         if ( $.support.transition ) {
    2270                 if ( ! (selector instanceof $) ) {
    2271                         selector = $( selector );
     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;
    22722436                }
    22732437
    2274                 // Resolve the deferred when the first element finishes animating.
    2275                 selector.first().one( $.support.transition.end, deferred.resolve );
     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});
    22762447
    2277                 // Just in case the event doesn't trigger, fire a callback.
    2278                 _.delay( deferred.resolve, sensitivity );
     2448module.exports = CustomizeImageCropper;
    22792449
    2280         // Otherwise, execute on the spot.
    2281         } else {
    2282                 deferred.resolve();
    2283         }
    22842450
    2285         return deferred.promise();
    2286 };
     2451/***/ }),
     2452/* 44 */
     2453/***/ (function(module, exports) {
    22872454
    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;
     2455var Controller = wp.media.controller,
     2456        SiteIconCropper;
    23682457
    23692458/**
    2370  * wp.media.view.AttachmentCompat
     2459 * wp.media.controller.SiteIconCropper
    23712460 *
    2372  * A view to display fields added via the `attachment_fields_to_edit` filter.
     2461 * A state for cropping a Site Icon.
    23732462 *
    2374  * @memberOf wp.media.view
     2463 * @memberOf wp.media.controller
    23752464 *
    23762465 * @class
    2377  * @augments wp.media.View
    2378  * @augments wp.Backbone.View
    2379  * @augments Backbone.View
     2466 * @augments wp.media.controller.Cropper
     2467 * @augments wp.media.controller.State
     2468 * @augments Backbone.Model
    23802469 */
    2381 AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{
    2382         tagName:   'form',
    2383         className: 'compat-item',
     2470SiteIconCropper = Controller.Cropper.extend(/** @lends wp.media.controller.SiteIconCropper.prototype */{
     2471        activate: function() {
     2472                this.frame.on( 'content:create:crop', this.createCropContent, this );
     2473                this.frame.on( 'close', this.removeCropper, this );
     2474                this.set('selection', new Backbone.Collection(this.frame._selection.single));
     2475        },
     2476
     2477        createCropContent: function() {
     2478                this.cropperView = new wp.media.view.SiteIconCropper({
     2479                        controller: this,
     2480                        attachment: this.get('selection').first()
     2481                });
     2482                this.cropperView.on('image-loaded', this.createCropToolbar, this);
     2483                this.frame.content.set(this.cropperView);
    23842484
    2385         events: {
    2386                 'submit':          'preventDefault',
    2387                 'change input':    'save',
    2388                 'change select':   'save',
    2389                 'change textarea': 'save'
    23902485        },
    23912486
    2392         initialize: function() {
    2393                 this.listenTo( this.model, 'change:compat', this.render );
     2487        doCrop: function( attachment ) {
     2488                var cropDetails = attachment.get( 'cropDetails' ),
     2489                        control = this.get( 'control' );
     2490
     2491                cropDetails.dst_width  = control.params.width;
     2492                cropDetails.dst_height = control.params.height;
     2493
     2494                return wp.ajax.post( 'crop-image', {
     2495                        nonce: attachment.get( 'nonces' ).edit,
     2496                        id: attachment.get( 'id' ),
     2497                        context: 'site-icon',
     2498                        cropDetails: cropDetails
     2499                } );
     2500        }
     2501});
     2502
     2503module.exports = SiteIconCropper;
     2504
     2505
     2506/***/ }),
     2507/* 45 */
     2508/***/ (function(module, exports) {
     2509
     2510/**
     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
     2523 *
     2524 * @class
     2525 * @augments wp.Backbone.View
     2526 * @augments Backbone.View
     2527 */
     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 );
    23942534        },
    23952535        /**
    2396          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     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.
     2539         *
     2540         * @returns {wp.media.View} Returns itself to allow chaining
    23972541         */
    23982542        dispose: function() {
    2399                 if ( this.$(':focus').length ) {
    2400                         this.save();
     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 );
    24012550                }
    2402                 /**
    2403                  * call 'dispose' directly on the parent class
    2404                  */
    2405                 return View.prototype.dispose.apply( this, arguments );
    2406         },
    2407         /**
    2408          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    2409          */
    2410         render: function() {
    2411                 var compat = this.model.get('compat');
    2412                 if ( ! compat || ! compat.item ) {
    2413                         return;
     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 );
    24142559                }
    24152560
    2416                 this.views.detach();
    2417                 this.$el.html( compat.item );
    2418                 this.views.render();
    24192561                return this;
    24202562        },
    24212563        /**
    2422          * @param {Object} event
    2423          */
    2424         preventDefault: function( event ) {
    2425                 event.preventDefault();
    2426         },
    2427         /**
    2428          * @param {Object} event
     2564         * @returns {wp.media.View} Returns itself to allow chaining
    24292565         */
    2430         save: function( event ) {
    2431                 var data = {};
    2432 
    2433                 if ( event ) {
    2434                         event.preventDefault();
    2435                 }
    2436 
    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'] );
     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 );
    24472572        }
    24482573});
    24492574
    2450 module.exports = AttachmentCompat;
     2575module.exports = View;
    24512576
    2452 },{}],21:[function(require,module,exports){
    2453 var $ = jQuery,
    2454         AttachmentFilters;
     2577
     2578/***/ }),
     2579/* 46 */
     2580/***/ (function(module, exports) {
    24552581
    24562582/**
    2457  * wp.media.view.AttachmentFilters
     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.
    24582587 *
    24592588 * @memberOf wp.media.view
    24602589 *
     2590 * @see wp.media.controller.State
     2591 * @see wp.media.controller.Region
     2592 *
    24612593 * @class
    24622594 * @augments wp.media.View
    24632595 * @augments wp.Backbone.View
    24642596 * @augments Backbone.View
     2597 * @mixes wp.media.controller.StateMachine
    24652598 */
    2466 AttachmentFilters = wp.media.View.extend(/** @lends wp.media.view.AttachmentFilters.prototype */{
    2467         tagName:   'select',
    2468         className: 'attachment-filters',
    2469         id:        'media-attachment-filters',
    2470 
    2471         events: {
    2472                 change: 'change'
     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();
    24732607        },
    24742608
    2475         keys: [],
     2609        _createRegions: function() {
     2610                // Clone the regions array.
     2611                this.regions = this.regions ? this.regions.slice() : [];
    24762612
    2477         initialize: function() {
    2478                 this.createFilters();
    2479                 _.extend( this.filters, this.options.filters );
     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                });
    24802635
    2481                 // Build `<option>` elements.
    2482                 this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
    2483                         return {
    2484                                 el: $( '<option></option>' ).val( value ).html( filter.text )[0],
    2485                                 priority: filter.priority || 50
    2486                         };
    2487                 }, this ).sortBy('priority').pluck('el').value() );
     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 );
    24882641
    2489                 this.listenTo( this.model, 'change', this.select );
    2490                 this.select();
     2642                if ( this.options.states ) {
     2643                        this.states.add( this.options.states );
     2644                }
    24912645        },
    24922646
    24932647        /**
    2494          * @abstract
     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.
    24952651         */
    2496         createFilters: function() {
    2497                 this.filters = {};
    2498         },
     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 ) );
    24992656
     2657                _.each( this.options.mode, function( mode ) {
     2658                        this.activateMode( mode );
     2659                }, this );
     2660        },
    25002661        /**
    2501          * When the selected filter changes, update the Attachment Query properties to match.
     2662         * Reset all states on the frame to their defaults.
     2663         *
     2664         * @returns {wp.media.view.Frame} Returns itself to allow chaining
    25022665         */
    2503         change: function() {
    2504                 var filter = this.filters[ this.el.value ];
    2505                 if ( filter ) {
    2506                         this.model.set( filter.props );
    2507                 }
     2666        reset: function() {
     2667                this.states.invoke( 'trigger', 'reset' );
     2668                return this;
    25082669        },
    2509 
    2510         select: function() {
    2511                 var model = this.model,
    2512                         value = 'all',
    2513                         props = model.toJSON();
    2514 
    2515                 _.find( this.filters, function( filter, id ) {
    2516                         var equal = _.all( filter.props, function( prop, key ) {
    2517                                 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
    2518                         });
    2519 
    2520                         if ( equal ) {
    2521                                 return value = id;
     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;
    25222684                        }
    2523                 });
    2524 
    2525                 this.$el.val( value );
    2526         }
    2527 });
     2685                } );
    25282686
    2529 module.exports = AttachmentFilters;
     2687                if ( ! _.has( modeEventMap, collectionEvent ) ) {
     2688                        return;
     2689                }
    25302690
    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                         };
     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;
    25872704                }
     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 );
    25882708
    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                         };
     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;
    26152721                }
     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' );
    26162730
    2617                 this.filters = filters;
     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 );
    26182741        }
    26192742});
    26202743
    2621 module.exports = All;
    2622 
    2623 },{}],23:[function(require,module,exports){
    2624 var l10n = wp.media.view.l10n,
    2625         DateFilter;
     2744// Make the `Frame` a `StateMachine`.
     2745_.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
    26262746
    2627 /**
    2628  * A filter dropdown for month/dates.
    2629  *
    2630  * @memberOf wp.media.view.AttachmentFilters
    2631  *
    2632  * @class
    2633  * @augments wp.media.view.AttachmentFilters
    2634  * @augments wp.media.View
    2635  * @augments wp.Backbone.View
    2636  * @augments Backbone.View
    2637  */
    2638 DateFilter = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Date.prototype */{
    2639         id: 'media-attachment-date-filters',
     2747module.exports = Frame;
    26402748
    2641         createFilters: function() {
    2642                 var filters = {};
    2643                 _.each( wp.media.view.settings.months || {}, function( value, index ) {
    2644                         filters[ index ] = {
    2645                                 text: value.text,
    2646                                 props: {
    2647                                         year: value.year,
    2648                                         monthnum: value.month
    2649                                 }
    2650                         };
    2651                 });
    2652                 filters.all = {
    2653                         text:  l10n.allDates,
    2654                         props: {
    2655                                 monthnum: false,
    2656                                 year:  false
    2657                         },
    2658                         priority: 10
    2659                 };
    2660                 this.filters = filters;
    2661         }
    2662 });
    26632749
    2664 module.exports = DateFilter;
     2750/***/ }),
     2751/* 47 */
     2752/***/ (function(module, exports) {
    26652753
    2666 },{}],24:[function(require,module,exports){
    2667 var l10n = wp.media.view.l10n,
    2668         Uploaded;
     2754var Frame = wp.media.view.Frame,
     2755        $ = jQuery,
     2756        MediaFrame;
    26692757
    26702758/**
    2671  * wp.media.view.AttachmentFilters.Uploaded
     2759 * wp.media.view.MediaFrame
    26722760 *
    2673  * @memberOf wp.media.view.AttachmentFilters
     2761 * The frame used to create the media modal.
     2762 *
     2763 * @memberOf wp.media.view
    26742764 *
    26752765 * @class
    2676  * @augments wp.media.view.AttachmentFilters
     2766 * @augments wp.media.view.Frame
    26772767 * @augments wp.media.View
    26782768 * @augments wp.Backbone.View
    26792769 * @augments Backbone.View
     2770 * @mixes wp.media.controller.StateMachine
    26802771 */
    2681 Uploaded = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Uploaded.prototype */{
    2682         createFilters: function() {
    2683                 var type = this.model.get('type'),
    2684                         types = wp.media.view.settings.mimeTypes,
    2685                         text;
     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'],
    26862776
    2687                 if ( types && type ) {
    2688                         text = types[ type ];
    2689                 }
     2777        events: {
     2778                'click div.media-frame-title h1': 'toggleMenu'
     2779        },
    26902780
    2691                 this.filters = {
    2692                         all: {
    2693                                 text:  text || l10n.allMediaItems,
    2694                                 props: {
    2695                                         uploadedTo: null,
    2696                                         orderby: 'date',
    2697                                         order:   'DESC'
    2698                                 },
    2699                                 priority: 10
    2700                         },
     2781        /**
     2782         * @constructs
     2783         */
     2784        initialize: function() {
     2785                Frame.prototype.initialize.apply( this, arguments );
    27012786
    2702                         uploaded: {
    2703                                 text:  l10n.uploadedToThisPost,
    2704                                 props: {
    2705                                         uploadedTo: wp.media.view.settings.post.id,
    2706                                         orderby: 'menuOrder',
    2707                                         order:   'ASC'
    2708                                 },
    2709                                 priority: 20
    2710                         },
     2787                _.defaults( this.options, {
     2788                        title:    '',
     2789                        modal:    true,
     2790                        uploader: true
     2791                });
    27112792
    2712                         unattached: {
    2713                                 text:  l10n.unattached,
    2714                                 props: {
    2715                                         uploadedTo: 0,
    2716                                         orderby: 'menuOrder',
    2717                                         order:   'ASC'
    2718                                 },
    2719                                 priority: 50
    2720                         }
    2721                 };
    2722         }
    2723 });
     2793                // Ensure core UI is enabled.
     2794                this.$el.addClass('wp-core-ui');
    27242795
    2725 module.exports = Uploaded;
     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                        });
    27262802
    2727 },{}],25:[function(require,module,exports){
    2728 var View = wp.media.View,
    2729         $ = jQuery,
    2730         Attachment;
     2803                        this.modal.content( this );
     2804                }
    27312805
    2732 /**
    2733  * wp.media.view.Attachment
    2734  *
    2735  * @memberOf wp.media.view
    2736  *
    2737  * @class
    2738  * @augments wp.media.View
    2739  * @augments wp.Backbone.View
    2740  * @augments Backbone.View
    2741  */
    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' )
    2754                 };
    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         },
     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                }
    27672811
    2768         buttons: {},
     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                }
    27692823
    2770         initialize: function() {
    2771                 var selection = this.options.selection,
    2772                         options = _.defaults( this.options, {
    2773                                 rerenderOnModelChange: true
    2774                         } );
     2824                this.on( 'attach', _.bind( this.views.ready, this.views ), this );
    27752825
    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 );
     2826                // Bind default title creation.
     2827                this.on( 'title:create:default', this.createTitle, this );
     2828                this.title.mode('default');
    27852829
    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                 }
     2830                this.on( 'title:render', function( view ) {
     2831                        view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
     2832                });
    27952833
    2796                 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
     2834                // Bind default menu.
     2835                this.on( 'menu:create:default', this.createMenu, this );
    27972836        },
    27982837        /**
    2799          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     2838         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    28002839         */
    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 );
     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 );
    28092844                }
    28102845                /**
    2811                  * call 'dispose' directly on the parent class
     2846                 * call 'render' directly on the parent class
    28122847                 */
    2813                 View.prototype.dispose.apply( this, arguments );
    2814                 return this;
     2848                return Frame.prototype.render.apply( this, arguments );
    28152849        },
    28162850        /**
    2817          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     2851         * @param {Object} title
     2852         * @this wp.media.controller.Region
    28182853         */
    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');
     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        },
    28392869
    2840                 if ( 'image' === options.type ) {
    2841                         options.size = this.imageSize();
    2842                 }
     2870        toggleMenu: function() {
     2871                this.$el.find( '.media-menu' ).toggleClass( 'visible' );
     2872        },
    28432873
    2844                 options.can = {};
    2845                 if ( options.nonces ) {
    2846                         options.can.remove = !! options.nonces['delete'];
    2847                         options.can.save = !! options.nonces.update;
    2848                 }
     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;
    28492900
    2850                 if ( this.controller.state().get('allowLocalEdits') ) {
    2851                         options.allowLocalEdits = true;
     2901                if ( ! tabs || ! tabUrl ) {
     2902                        return;
    28522903                }
    28532904
    2854                 if ( options.uploading && ! options.percent ) {
    2855                         options.percent = 0;
     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();
    28562909                }
    28572910
    2858                 this.views.detach();
    2859                 this.$el.html( this.template( options ) );
     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 );
    28602921
    2861                 this.$el.toggleClass( 'uploading', options.uploading );
     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        },
    28622928
    2863                 if ( options.uploading ) {
    2864                         this.$bar = this.$('.media-progress-bar div');
    2865                 } else {
    2866                         delete this.$bar;
    2867                 }
     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        },
    28682939
    2869                 // Check if the model is selected.
    2870                 this.updateSelect();
     2940        iframeContentCleanup: function() {
     2941                this.$el.removeClass('hide-toolbar');
     2942        },
    28712943
    2872                 // Update the save status.
    2873                 this.updateSave();
     2944        iframeMenu: function( view ) {
     2945                var views = {};
    28742946
    2875                 this.views.render();
     2947                if ( ! view ) {
     2948                        return;
     2949                }
    28762950
    2877                 return this;
    2878         },
     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 );
    28792957
    2880         progress: function() {
    2881                 if ( this.$bar && this.$bar.length ) {
    2882                         this.$bar.width( this.model.get('percent') + '%' );
    2883                 }
     2958                view.set( views );
    28842959        },
    28852960
    2886         /**
    2887          * @param {Object} event
    2888          */
    2889         toggleSelectionHandler: function( event ) {
    2890                 var method;
     2961        hijackThickbox: function() {
     2962                var frame = this;
    28912963
    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 ) {
     2964                if ( ! window.tb_remove || this._tb_remove ) {
    28942965                        return;
    28952966                }
    28962967
    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                 }
     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        },
    29022976
    2903                 // Catch enter and space events
    2904                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     2977        restoreThickbox: function() {
     2978                if ( ! this._tb_remove ) {
    29052979                        return;
    29062980                }
    29072981
    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                 });
     2982                window.tb_remove = this._tb_remove;
     2983                delete this._tb_remove;
     2984        }
     2985});
    29322986
    2933                 this.controller.trigger( 'selection:toggle' );
    2934         },
     2987// Map some of the modal's methods to the frame.
     2988_.each(['open','close','attach','detach','escape'], function( method ) {
    29352989        /**
    2936          * @param {Object} options
     2990         * @function open
     2991         * @memberOf wp.media.view.MediaFrame
     2992         * @instance
     2993         *
     2994         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    29372995         */
    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;
     2996        /**
     2997         * @function close
     2998         * @memberOf wp.media.view.MediaFrame
     2999         * @instance
     3000         *
     3001         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     3002         */
     3003        /**
     3004         * @function attach
     3005         * @memberOf wp.media.view.MediaFrame
     3006         * @instance
     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 );
    29473027                }
     3028                return this;
     3029        };
     3030});
    29483031
    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 );
     3032module.exports = MediaFrame;
    29623033
    2963                         if ( singleIndex < modelIndex ) {
    2964                                 models = collection.models.slice( singleIndex, modelIndex + 1 );
    2965                         } else {
    2966                                 models = collection.models.slice( modelIndex, singleIndex + 1 );
    2967                         }
    29683034
    2969                         selection.add( models );
    2970                         selection.single( model );
    2971                         return;
     3035/***/ }),
     3036/* 48 */
     3037/***/ (function(module, exports) {
    29723038
    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                 }
     3039var MediaFrame = wp.media.view.MediaFrame,
     3040        l10n = wp.media.view.l10n,
     3041        Select;
    29843042
    2985                 // Fixes bug that loses focus when selecting a featured image
    2986                 if ( ! method ) {
    2987                         method = 'add';
    2988                 }
     3043/**
     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
     3049 *
     3050 * @class
     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
     3057 */
     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 );
    29893062
    2990                 if ( method !== 'add' ) {
    2991                         method = 'reset';
    2992                 }
     3063                _.defaults( this.options, {
     3064                        selection: [],
     3065                        library:   {},
     3066                        multiple:  false,
     3067                        state:    'library'
     3068                });
    29933069
    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                 }
     3070                this.createSelection();
     3071                this.createStates();
     3072                this.bindHandlers();
    30063073        },
    30073074
    3008         updateSelect: function() {
    3009                 this[ this.selected() ? 'select' : 'deselect' ]();
    3010         },
    30113075        /**
    3012          * @returns {unresolved|Boolean}
     3076         * Attach a selection collection to the frame.
     3077         *
     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.
     3081         *
     3082         * @see media.model.Selection
    30133083         */
    3014         selected: function() {
     3084        createSelection: function() {
    30153085                var selection = this.options.selection;
    3016                 if ( selection ) {
    3017                         return !! selection.get( this.model.cid );
     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                        });
    30183091                }
     3092
     3093                this._selection = {
     3094                        attachments: new wp.media.model.Attachments(),
     3095                        difference: []
     3096                };
    30193097        },
     3098
    30203099        /**
    3021          * @param {Backbone.Model} model
    3022          * @param {Backbone.Collection} collection
     3100         * Create the default states on the frame.
    30233101         */
    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                 }
     3102        createStates: function() {
     3103                var options = this.options;
    30343104
    3035                 // Bail if the model is already selected.
    3036                 if ( this.$el.hasClass( 'selected' ) ) {
     3105                if ( this.options.states ) {
    30373106                        return;
    30383107                }
    30393108
    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                 }
     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                ]);
    30463119        },
     3120
    30473121        /**
    3048          * @param {Backbone.Model} model
    3049          * @param {Backbone.Collection} collection
     3122         * Bind region mode event callbacks.
     3123         *
     3124         * @see media.controller.Region.render
    30503125         */
    3051         deselect: function( model, collection ) {
    3052                 var selection = this.options.selection;
     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        },
    30533133
    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         },
    30633134        /**
    3064          * @param {Backbone.Model} model
    3065          * @param {Backbone.Collection} collection
     3135         * Render callback for the router region in the `browse` mode.
     3136         *
     3137         * @param {wp.media.view.Router} routerView
    30663138         */
    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 );
     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                });
    30773150        },
     3151
    30783152        /**
    3079          * @param {string} size
    3080          * @returns {Object}
     3153         * Render callback for the content region in the `browse` mode.
     3154         *
     3155         * @param {wp.media.controller.Region} contentRegion
    30813156         */
    3082         imageSize: function( size ) {
    3083                 var sizes = this.model.get('sizes'), matched = false;
     3157        browseContent: function( contentRegion ) {
     3158                var state = this.state();
    30843159
    3085                 size = size || 'medium';
     3160                this.$el.removeClass('hide-toolbar');
    30863161
    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                         }
     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'),
    30983174
    3099                         if ( matched ) {
    3100                                 return _.clone( matched );
    3101                         }
    3102                 }
     3175                        idealColumnWidth: state.get('idealColumnWidth'),
     3176                        suggestedWidth:   state.get('suggestedWidth'),
     3177                        suggestedHeight:  state.get('suggestedHeight'),
    31033178
    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')
    3109                 };
     3179                        AttachmentView: state.get('AttachmentView')
     3180                });
    31103181        },
     3182
    31113183        /**
    3112          * @param {Object} event
     3184         * Render callback for the content region in the `upload` mode.
    31133185         */
    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                 }
     3186        uploadContent: function() {
     3187                this.$el.removeClass( 'hide-toolbar' );
     3188                this.content.set( new wp.media.view.UploaderInline({
     3189                        controller: this
     3190                }) );
    31283191        },
    31293192
    31303193        /**
    3131          * Pass all the arguments to the model's save method.
     3194         * Toolbars
    31323195         *
    3133          * Records the aggregate status of all save requests and updates the
    3134          * view's classes accordingly.
     3196         * @param {Object} toolbar
     3197         * @param {Object} [options={}]
     3198         * @this wp.media.controller.Region
    31353199         */
    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                         }
     3200        createSelectToolbar: function( toolbar, options ) {
     3201                options = options || this.options.button || {};
     3202                options.controller = this;
    31543203
    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' };
     3204                toolbar.view = new wp.media.view.Toolbar.Select( options );
     3205        }
     3206});
    31683207
    3169                 if ( status && status !== save.status ) {
    3170                         this.$el.removeClass( 'save-' + save.status );
    3171                         save.status = status;
    3172                 }
     3208module.exports = Select;
    31733209
    3174                 this.$el.addClass( 'save-' + save.status );
    3175                 return this;
    3176         },
    31773210
    3178         updateAll: function() {
    3179                 var $settings = this.$('[data-setting]'),
    3180                         model = this.model,
    3181                         changed;
     3211/***/ }),
     3212/* 49 */
     3213/***/ (function(module, exports) {
    31823214
    3183                 changed = _.chain( $settings ).map( function( el ) {
    3184                         var $input = $('input, textarea, select, [value]', el ),
    3185                                 setting, value;
     3215var Select = wp.media.view.MediaFrame.Select,
     3216        Library = wp.media.controller.Library,
     3217        l10n = wp.media.view.l10n,
     3218        Post;
    31863219
    3187                         if ( ! $input.length ) {
    3188                                 return;
     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'
    31893246                        }
     3247                };
    31903248
    3191                         setting = $(el).data('setting');
    3192                         value = $input.val();
     3249                _.defaults( this.options, {
     3250                        multiple:  true,
     3251                        editing:   false,
     3252                        state:    'insert',
     3253                        metadata:  {}
     3254                });
    31933255
    3194                         // Record the value if it changed.
    3195                         if ( model.get( setting ) !== value ) {
    3196                                 return [ setting, value ];
    3197                         }
    3198                 }).compact().object().value();
     3256                // Call 'initialize' directly on the parent class.
     3257                Select.prototype.initialize.apply( this, arguments );
     3258                this.createIframeStates();
    31993259
    3200                 if ( ! _.isEmpty( changed ) ) {
    3201                         model.save( changed );
    3202                 }
    32033260        },
     3261
    32043262        /**
    3205          * @param {Object} event
     3263         * Create the default states.
    32063264         */
    3207         removeFromLibrary: function( event ) {
    3208                 // Catch enter and space events
    3209                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    3210                         return;
    3211                 }
     3265        createStates: function() {
     3266                var options = this.options;
    32123267
    3213                 // Stop propagation so the model isn't selected.
    3214                 event.stopPropagation();
     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,
    32153279
    3216                 this.collection.remove( this.model );
    3217         },
     3280                                // If the user isn't allowed to edit fields,
     3281                                // can they still edit it locally?
     3282                                allowLocalEdits: true,
    32183283
    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                 }
    3239         }
    3240 });
    3241 
    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                 }
     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                        }),
    32913290
    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                 }
     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,
    32993299
    3300                 return this.render();
    3301         };
    3302 });
     3300                                library:  wp.media.query( _.defaults({
     3301                                        type: 'image'
     3302                                }, options.library ) )
     3303                        }),
    33033304
    3304 module.exports = Attachment;
     3305                        // Embed states.
     3306                        new wp.media.controller.Embed( { metadata: options.metadata } ),
    33053307
    3306 },{}],26:[function(require,module,exports){
    3307 var Attachment = wp.media.view.Attachment,
    3308         l10n = wp.media.view.l10n,
    3309         Details;
     3308                        new wp.media.controller.EditImage( { model: options.editImage } ),
    33103309
    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'),
     3310                        // Gallery states.
     3311                        new wp.media.controller.GalleryEdit({
     3312                                library: options.selection,
     3313                                editing: options.editing,
     3314                                menu:    'gallery'
     3315                        }),
    33263316
    3327         attributes: function() {
    3328                 return {
    3329                         'tabIndex':     0,
    3330                         'data-id':      this.model.get( 'id' )
    3331                 };
    3332         },
     3317                        new wp.media.controller.GalleryAdd(),
    33333318
    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         },
     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,
    33453327
    3346         initialize: function() {
    3347                 this.options = _.defaults( this.options, {
    3348                         rerenderOnModelChange: false
    3349                 });
     3328                                library:  wp.media.query( _.defaults({
     3329                                        type: 'audio'
     3330                                }, options.library ) )
     3331                        }),
    33503332
    3351                 this.on( 'ready', this.initialFocus );
    3352                 // Call 'initialize' directly on the parent class.
    3353                 Attachment.prototype.initialize.apply( this, arguments );
    3354         },
     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                        }),
    33553345
    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();
     3346                        new wp.media.controller.CollectionAdd({
     3347                                type: 'audio',
     3348                                collectionType: 'playlist',
     3349                                title: l10n.addToPlaylistTitle
     3350                        }),
    33733351
    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();
     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,
    33873360
    3388                 if ( wp.media.view.settings.mediaTrash &&
    3389                         'edit-metadata' === this.controller.content.mode() ) {
     3361                                library:  wp.media.query( _.defaults({
     3362                                        type: 'video'
     3363                                }, options.library ) )
     3364                        }),
    33903365
    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();
     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                        }),
    34053377
    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();
     3378                        new wp.media.controller.CollectionAdd({
     3379                                type: 'video',
     3380                                collectionType: 'playlist',
     3381                                title: l10n.addToVideoPlaylistTitle
     3382                        })
     3383                ]);
    34183384
    3419                         editState.set( 'image', this.model );
    3420                         this.controller.setState( 'edit-image' );
    3421                 } else {
    3422                         this.$el.addClass('needs-refresh');
     3385                if ( wp.media.view.settings.post.featuredImageId ) {
     3386                        this.states.add( new wp.media.controller.FeaturedImage() );
    34233387                }
    34243388        },
    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                 }
    34363389
    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 });
     3390        bindHandlers: function() {
     3391                var handlers, checkCounts;
    34433392
    3444 module.exports = Details;
     3393                Select.prototype.bindHandlers.apply( this, arguments );
    34453394
    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 });
     3395                this.on( 'activate', this.activate, this );
    34633396
    3464 module.exports = EditLibrary;
     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                } );
    34653401
    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 });
     3402                if ( typeof checkCounts !== 'undefined' ) {
     3403                        this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
     3404                }
    34843405
    3485 module.exports = EditSelection;
     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 );
    34863415
    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 });
     3416                handlers = {
     3417                        menu: {
     3418                                'default': 'mainMenu',
     3419                                'gallery': 'galleryMenu',
     3420                                'playlist': 'playlistMenu',
     3421                                'video-playlist': 'videoPlaylistMenu'
     3422                        },
    35043423
    3505 module.exports = Library;
     3424                        content: {
     3425                                'embed':          'embedContent',
     3426                                'edit-image':     'editImageContent',
     3427                                'edit-selection': 'editSelectionContent'
     3428                        },
    35063429
    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',
     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                };
    35213443
    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 });
     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        },
    35283450
    3529 module.exports = Selection;
     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        },
    35303459
    3531 },{}],31:[function(require,module,exports){
    3532 var View = wp.media.View,
    3533         $ = jQuery,
    3534         Attachments;
     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        },
    35353466
    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',
     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        },
    35493479
    3550         attributes: {
    3551                 tabIndex: -1
     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                }
    35523487        },
     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;
    35533495
    3554         initialize: function() {
    3555                 this.el.id = _.uniqueId('__attachments-view-');
     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                                        }
    35563506
    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
     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                        })
    35643516                });
     3517        },
    35653518
    3566                 this._viewsByCid = {};
    3567                 this.$window = $( window );
    3568                 this.resizeEvent = 'resize.media-modal-columns';
     3519        playlistMenu: function( view ) {
     3520                var lastState = this.lastState(),
     3521                        previous = lastState && lastState.id,
     3522                        frame = this;
    35693523
    3570                 this.collection.on( 'add', function( attachment ) {
    3571                         this.views.add( this.createAttachmentView( attachment ), {
    3572                                 at: this.collection.indexOf( attachment )
    3573                         });
    3574                 }, this );
     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        },
    35753542
    3576                 this.collection.on( 'remove', function( attachment ) {
    3577                         var view = this._viewsByCid[ attachment.cid ];
    3578                         delete this._viewsByCid[ attachment.cid ];
     3543        videoPlaylistMenu: function( view ) {
     3544                var lastState = this.lastState(),
     3545                        previous = lastState && lastState.id,
     3546                        frame = this;
    35793547
    3580                         if ( view ) {
    3581                                 view.remove();
    3582                         }
    3583                 }, this );
     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        },
    35843566
    3585                 this.collection.on( 'reset', this.render, this );
     3567        // Content
     3568        embedContent: function() {
     3569                var view = new wp.media.view.Embed({
     3570                        controller: this,
     3571                        model:      this.state()
     3572                }).render();
    35863573
    3587                 this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
     3574                this.content.set( view );
    35883575
    3589                 // Throttle the scroll handler and bind this.
    3590                 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
     3576                if ( ! wp.media.isTouchDevice ) {
     3577                        view.url.focus();
     3578                }
     3579        },
    35913580
    3592                 this.options.scrollElement = this.options.scrollElement || this.el;
    3593                 $( this.options.scrollElement ).on( 'scroll', this.scroll );
     3581        editSelectionContent: function() {
     3582                var state = this.state(),
     3583                        selection = state.get('selection'),
     3584                        view;
    35943585
    3595                 this.initSortable();
     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();
    35963598
    3597                 _.bindAll( this, 'setColumns' );
     3599                view.toolbar.set( 'backToLibrary', {
     3600                        text:     l10n.returnToLibrary,
     3601                        priority: -100,
    35983602
    3599                 if ( this.options.resize ) {
    3600                         this.on( 'ready', this.bindEvents );
    3601                         this.controller.on( 'open', this.setColumns );
     3603                        click: function() {
     3604                                this.controller.content.mode('browse');
     3605                        }
     3606                });
    36023607
    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                // Browse our library of attachments.
     3609                this.content.set( view );
    36083610
    3609         bindEvents: function() {
    3610                 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
     3611                // Trigger the controller to set focus
     3612                this.trigger( 'edit:selection', this );
    36113613        },
    36123614
    3613         attachmentFocus: function() {
    3614                 this.$( 'li:first' ).focus();
    3615         },
     3615        editImageContent: function() {
     3616                var image = this.state().get('image'),
     3617                        view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
    36163618
    3617         restoreFocus: function() {
    3618                 this.$( 'li.selected:first' ).focus();
    3619         },
     3619                this.content.set( view );
    36203620
    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 );
     3621                // after creating the wrapper view, load the actual editor via an ajax call
     3622                view.loadEditor();
    36263623
    3627                 if ( index === -1 ) {
    3628                         return;
    3629                 }
     3624        },
    36303625
    3631                 // Left arrow
    3632                 if ( 37 === event.keyCode ) {
    3633                         if ( 0 === index ) {
    3634                                 return;
    3635                         }
    3636                         attachments.eq( index - 1 ).focus();
    3637                 }
     3626        // Toolbars
    36383627
    3639                 // Up arrow
    3640                 if ( 38 === event.keyCode ) {
    3641                         if ( 1 === row ) {
    3642                                 return;
    3643                         }
    3644                         attachments.eq( index - perRow ).focus();
    3645                 }
     3628        /**
     3629         * @param {wp.Backbone.View} view
     3630         */
     3631        selectionStatusToolbar: function( view ) {
     3632                var editable = this.state().get('editable');
    36463633
    3647                 // Right arrow
    3648                 if ( 39 === event.keyCode ) {
    3649                         if ( attachments.length === index ) {
    3650                                 return;
    3651                         }
    3652                         attachments.eq( index + 1 ).focus();
    3653                 }
     3634                view.set( 'selection', new wp.media.view.Selection({
     3635                        controller: this,
     3636                        collection: this.state().get('selection'),
     3637                        priority:   -40,
    36543638
    3655                 // Down arrow
    3656                 if ( 40 === event.keyCode ) {
    3657                         if ( Math.ceil( attachments.length / perRow ) === row ) {
    3658                                 return;
     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');
    36593643                        }
    3660                         attachments.eq( index + perRow ).focus();
    3661                 }
     3644                }).render() );
    36623645        },
    36633646
    3664         dispose: function() {
    3665                 this.collection.props.off( null, null, this );
    3666                 if ( this.options.resize ) {
    3667                         this.$window.off( this.resizeEvent );
    3668                 }
     3647        /**
     3648         * @param {wp.Backbone.View} view
     3649         */
     3650        mainInsertToolbar: function( view ) {
     3651                var controller = this;
    36693652
    3670                 /**
    3671                  * call 'dispose' directly on the parent class
    3672                  */
    3673                 View.prototype.dispose.apply( this, arguments );
    3674         },
     3653                this.selectionStatusToolbar( view );
    36753654
    3676         setColumns: function() {
    3677                 var prev = this.columns,
    3678                         width = this.$el.width();
     3655                view.set( 'insert', {
     3656                        style:    'primary',
     3657                        priority: 80,
     3658                        text:     l10n.insertIntoPost,
     3659                        requires: { selection: true },
    36793660
    3680                 if ( width ) {
    3681                         this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
     3661                        /**
     3662                         * @callback
     3663                         * @fires wp.media.controller.State#insert
     3664                         */
     3665                        click: function() {
     3666                                var state = controller.state(),
     3667                                        selection = state.get('selection');
    36823668
    3683                         if ( ! prev || prev !== this.columns ) {
    3684                                 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
     3669                                controller.close();
     3670                                state.trigger( 'insert', selection ).reset();
    36853671                        }
    3686                 }
     3672                });
    36873673        },
    36883674
    3689         initSortable: function() {
    3690                 var collection = this.collection;
     3675        /**
     3676         * @param {wp.Backbone.View} view
     3677         */
     3678        mainGalleryToolbar: function( view ) {
     3679                var controller = this;
    36913680
    3692                 if ( ! this.options.sortable || ! $.fn.sortable ) {
    3693                         return;
    3694                 }
     3681                this.selectionStatusToolbar( view );
    36953682
    3696                 this.$el.sortable( _.extend({
    3697                         // If the `collection` has a `comparator`, disable sorting.
    3698                         disabled: !! collection.comparator,
     3683                view.set( 'gallery', {
     3684                        style:    'primary',
     3685                        text:     l10n.createNewGallery,
     3686                        priority: 60,
     3687                        requires: { selection: true },
    36993688
    3700                         // Change the position of the attachment as soon as the
    3701                         // mouse pointer overlaps a thumbnail.
    3702                         tolerance: 'pointer',
     3689                        click: function() {
     3690                                var selection = controller.state().get('selection'),
     3691                                        edit = controller.state('gallery-edit'),
     3692                                        models = selection.where({ type: 'image' });
    37033693
    3704                         // Record the initial `index` of the dragged model.
    3705                         start: function( event, ui ) {
    3706                                 ui.item.data('sortableIndexStart', ui.item.index());
    3707                         },
     3694                                edit.set( 'library', new wp.media.model.Selection( models, {
     3695                                        props:    selection.props.toJSON(),
     3696                                        multiple: true
     3697                                }) );
    37083698
    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;
     3699                                this.controller.setState('gallery-edit');
    37143700
    3715                                 // Temporarily disable the comparator to prevent `add`
    3716                                 // from re-sorting.
    3717                                 delete collection.comparator;
     3701                                // Keep focus inside media modal
     3702                                // after jumping to gallery view
     3703                                this.controller.modal.focusManager.focus();
     3704                        }
     3705                });
     3706        },
    37183707
    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                                 });
     3708        mainPlaylistToolbar: function( view ) {
     3709                var controller = this;
    37273710
    3728                                 // Restore the comparator.
    3729                                 collection.comparator = comparator;
     3711                this.selectionStatusToolbar( view );
    37303712
    3731                                 // Fire the `reset` event to ensure other collections sync.
    3732                                 collection.trigger( 'reset', collection );
     3713                view.set( 'playlist', {
     3714                        style:    'primary',
     3715                        text:     l10n.createNewPlaylist,
     3716                        priority: 100,
     3717                        requires: { selection: true },
    37333718
    3734                                 // If the collection is sorted by menu order,
    3735                                 // update the menu order.
    3736                                 collection.saveMenuOrder();
    3737                         }
    3738                 }, this.options.sortable ) );
     3719                        click: function() {
     3720                                var selection = controller.state().get('selection'),
     3721                                        edit = controller.state('playlist-edit'),
     3722                                        models = selection.where({ type: 'audio' });
    37393723
    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 );
     3724                                edit.set( 'library', new wp.media.model.Selection( models, {
     3725                                        props:    selection.props.toJSON(),
     3726                                        multiple: true
     3727                                }) );
    37453728
    3746                 this.collection.props.on( 'change:orderby', this.refreshSortable, this );
    3747                 this.refreshSortable();
    3748         },
     3729                                this.controller.setState('playlist-edit');
    37493730
    3750         refreshSortable: function() {
    3751                 if ( ! this.options.sortable || ! $.fn.sortable ) {
    3752                         return;
    3753                 }
     3731                                // Keep focus inside media modal
     3732                                // after jumping to playlist view
     3733                                this.controller.modal.focusManager.focus();
     3734                        }
     3735                });
     3736        },
    37543737
    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;
     3738        mainVideoPlaylistToolbar: function( view ) {
     3739                var controller = this;
    37593740
    3760                 this.$el.sortable( 'option', 'disabled', ! enabled );
    3761         },
     3741                this.selectionStatusToolbar( view );
    37623742
    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                 });
     3743                view.set( 'video-playlist', {
     3744                        style:    'primary',
     3745                        text:     l10n.createNewVideoPlaylist,
     3746                        priority: 100,
     3747                        requires: { selection: true },
    37743748
    3775                 return this._viewsByCid[ attachment.cid ] = view;
    3776         },
     3749                        click: function() {
     3750                                var selection = controller.state().get('selection'),
     3751                                        edit = controller.state('video-playlist-edit'),
     3752                                        models = selection.where({ type: 'video' });
    37773753
    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 ) );
     3754                                edit.set( 'library', new wp.media.model.Selection( models, {
     3755                                        props:    selection.props.toJSON(),
     3756                                        multiple: true
     3757                                }) );
    37833758
    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         },
     3759                                this.controller.setState('video-playlist-edit');
    37903760
    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();
     3761                                // Keep focus inside media modal
     3762                                // after jumping to video playlist view
     3763                                this.controller.modal.focusManager.focus();
     3764                        }
     3765                });
    37953766        },
    37963767
    3797         scroll: function() {
    3798                 var view = this,
    3799                         el = this.options.scrollElement,
    3800                         scrollTop = el.scrollTop,
    3801                         toolbar;
     3768        featuredImageToolbar: function( toolbar ) {
     3769                this.createSelectToolbar( toolbar, {
     3770                        text:  l10n.setFeaturedImage,
     3771                        state: this.options.state
     3772                });
     3773        },
    38023774
    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                 }
     3775        mainEmbedToolbar: function( toolbar ) {
     3776                toolbar.view = new wp.media.view.Toolbar.Embed({
     3777                        controller: this
     3778                });
     3779        },
    38093780
    3810                 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
    3811                         return;
    3812                 }
     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 },
    38133791
    3814                 toolbar = this.views.parent.toolbar;
     3792                                        /**
     3793                                         * @fires wp.media.controller.State#update
     3794                                         */
     3795                                        click: function() {
     3796                                                var controller = this.controller,
     3797                                                        state = controller.state();
    38153798
    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                 }
     3799                                                controller.close();
     3800                                                state.trigger( 'update', state.get('library') );
    38203801
    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 });
     3802                                                // Restore and reset the default state.
     3803                                                controller.setState( controller.options.state );
     3804                                                controller.reset();
     3805                                        }
     3806                                }
     3807                        }
     3808                }) );
     3809        },
    38293810
    3830 module.exports = Attachments;
     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 },
    38313820
    3832 },{}],32:[function(require,module,exports){
    3833 var View = wp.media.View,
    3834         mediaTrash = wp.media.view.settings.mediaTrash,
    3835         l10n = wp.media.view.l10n,
    3836         $ = jQuery,
    3837         AttachmentsBrowser;
     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');
    38383828
    3839 /**
    3840  * wp.media.view.AttachmentsBrowser
    3841  *
    3842  * @memberOf wp.media.view
    3843  *
    3844  * @class
    3845  * @augments wp.media.View
    3846  * @augments wp.Backbone.View
    3847  * @augments Backbone.View
    3848  *
    3849  * @param {object}         [options]               The options hash passed to the view.
    3850  * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar.
    3851  *                                                 Accepts 'uploaded' and 'all'.
    3852  * @param {boolean}        [options.search=true]   Whether to show the search interface in the
    3853  *                                                 browser's toolbar.
    3854  * @param {boolean}        [options.date=true]     Whether to show the date filter in the
    3855  *                                                 browser's toolbar.
    3856  * @param {boolean}        [options.display=false] Whether to show the attachments display settings
    3857  *                                                 view in the sidebar.
    3858  * @param {boolean|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
    3859  *                                                 Accepts true, false, and 'errors'.
    3860  */
    3861 AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{
    3862         tagName:   'div',
    3863         className: 'attachments-browser',
     3829                                                edit.get('library').add( state.get('selection').models );
     3830                                                state.trigger('reset');
     3831                                                controller.setState('gallery-edit');
     3832                                        }
     3833                                }
     3834                        }
     3835                }) );
     3836        },
    38643837
    3865         initialize: function() {
    3866                 _.defaults( this.options, {
    3867                         filters: false,
    3868                         search:  true,
    3869                         date:    true,
    3870                         display: false,
    3871                         sidebar: true,
    3872                         AttachmentView: wp.media.view.Attachment.Library
    3873                 });
     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 },
    38743848
    3875                 this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this );
    3876                 this.controller.on( 'edit:selection', this.editSelection );
     3849                                        /**
     3850                                         * @fires wp.media.controller.State#update
     3851                                         */
     3852                                        click: function() {
     3853                                                var controller = this.controller,
     3854                                                        state = controller.state();
    38773855
    3878                 // In the Media Library, the sidebar is used to display errors before the attachments grid.
    3879                 if ( this.options.sidebar && 'errors' === this.options.sidebar ) {
    3880                         this.createSidebar();
    3881                 }
     3856                                                controller.close();
     3857                                                state.trigger( 'update', state.get('library') );
    38823858
    3883                 /*
    3884                  * For accessibility reasons, place the Inline Uploader before other sections.
    3885                  * This way, in the Media Library, it's right after the Add New button, see ticket #37188.
    3886                  */
    3887                 this.createUploader();
     3859                                                // Restore and reset the default state.
     3860                                                controller.setState( controller.options.state );
     3861                                                controller.reset();
     3862                                        }
     3863                                }
     3864                        }
     3865                }) );
     3866        },
    38883867
    3889                 /*
    3890                  * Create a multi-purpose toolbar. Used as main toolbar in the Media Library
    3891                  * and also for other things, for example the "Drag and drop to reorder" and
    3892                  * "Suggested dimensions" info in the media modal.
    3893                  */
    3894                 this.createToolbar();
     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 },
    38953877
    3896                 // Create the list of attachments.
    3897                 this.createAttachments();
     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');
    38983885
    3899                 // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909.
    3900                 if ( this.options.sidebar && 'errors' !== this.options.sidebar ) {
    3901                         this.createSidebar();
    3902                 }
     3886                                                edit.get('library').add( state.get('selection').models );
     3887                                                state.trigger('reset');
     3888                                                controller.setState('playlist-edit');
     3889                                        }
     3890                                }
     3891                        }
     3892                }) );
     3893        },
    39033894
    3904                 this.updateContent();
     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 },
    39053905
    3906                 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
    3907                         this.$el.addClass( 'hide-sidebar' );
     3906                                        click: function() {
     3907                                                var controller = this.controller,
     3908                                                        state = controller.state(),
     3909                                                        library = state.get('library');
    39083910
    3909                         if ( 'errors' === this.options.sidebar ) {
    3910                                 this.$el.addClass( 'sidebar-for-errors' );
    3911                         }
    3912                 }
     3911                                                library.type = 'video';
    39133912
    3914                 this.collection.on( 'add remove reset', this.updateContent, this );
    3915         },
     3913                                                controller.close();
     3914                                                state.trigger( 'update', library );
    39163915
    3917         editSelection: function( modal ) {
    3918                 modal.$( '.media-button-backToLibrary' ).focus();
     3916                                                // Restore and reset the default state.
     3917                                                controller.setState( controller.options.state );
     3918                                                controller.reset();
     3919                                        }
     3920                                }
     3921                        }
     3922                }) );
    39193923        },
    39203924
    3921         /**
    3922          * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
    3923          */
    3924         dispose: function() {
    3925                 this.options.selection.off( null, null, this );
    3926                 View.prototype.dispose.apply( this, arguments );
    3927                 return this;
    3928         },
     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 },
    39293934
    3930         createToolbar: function() {
    3931                 var LibraryViewSwitcher, Filters, toolbarOptions;
     3935                                        click: function() {
     3936                                                var controller = this.controller,
     3937                                                        state = controller.state(),
     3938                                                        edit = controller.state('video-playlist-edit');
    39323939
    3933                 toolbarOptions = {
    3934                         controller: this.controller
    3935                 };
     3940                                                edit.get('library').add( state.get('selection').models );
     3941                                                state.trigger('reset');
     3942                                                controller.setState('video-playlist-edit');
     3943                                        }
     3944                                }
     3945                        }
     3946                }) );
     3947        }
     3948});
    39363949
    3937                 if ( this.controller.isModeActive( 'grid' ) ) {
    3938                         toolbarOptions.className = 'media-toolbar wp-filter';
    3939                 }
     3950module.exports = Post;
    39403951
    3941                 /**
    3942                 * @member {wp.media.view.Toolbar}
    3943                 */
    3944                 this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
    39453952
    3946                 this.views.add( this.toolbar );
     3953/***/ }),
     3954/* 50 */
     3955/***/ (function(module, exports) {
    39473956
    3948                 this.toolbar.set( 'spinner', new wp.media.view.Spinner({
    3949                         priority: -60
    3950                 }) );
     3957var Select = wp.media.view.MediaFrame.Select,
     3958        l10n = wp.media.view.l10n,
     3959        ImageDetails;
    39513960
    3952                 if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
    3953                         // "Filters" will return a <select>, need to render
    3954                         // screen reader text before
    3955                         this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
    3956                                 value: l10n.filterByType,
    3957                                 attributes: {
    3958                                         'for':  'media-attachment-filters'
    3959                                 },
    3960                                 priority:   -80
    3961                         }).render() );
     3961/**
     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
     3977 */
     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        },
    39623989
    3963                         if ( 'uploaded' === this.options.filters ) {
    3964                                 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({
    3965                                         controller: this.controller,
    3966                                         model:      this.collection.props,
    3967                                         priority:   -80
    3968                                 }).render() );
    3969                         } else {
    3970                                 Filters = new wp.media.view.AttachmentFilters.All({
    3971                                         controller: this.controller,
    3972                                         model:      this.collection.props,
    3973                                         priority:   -80
    3974                                 });
     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        },
    39753995
    3976                                 this.toolbar.set( 'filters', Filters.render() );
    3977                         }
    3978                 }
     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        },
    39794005
    3980                 // Feels odd to bring the global media library switcher into the Attachment
    3981                 // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
    3982                 // which the controller can tap into and add this view?
    3983                 if ( this.controller.isModeActive( 'grid' ) ) {
    3984                         LibraryViewSwitcher = View.extend({
    3985                                 className: 'view-switch media-grid-view-switch',
    3986                                 template: wp.template( 'media-library-view-switcher')
    3987                         });
     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        },
    39884028
    3989                         this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
    3990                                 controller: this.controller,
    3991                                 priority: -90
    3992                         }).render() );
     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        },
    39934036
    3994                         // DateFilter is a <select>, screen reader text needs to be rendered before
    3995                         this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
    3996                                 value: l10n.filterByDate,
    3997                                 attributes: {
    3998                                         'for': 'media-attachment-date-filters'
    3999                                 },
    4000                                 priority: -75
    4001                         }).render() );
    4002                         this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
    4003                                 controller: this.controller,
    4004                                 model:      this.collection.props,
    4005                                 priority: -75
    4006                         }).render() );
     4037        editImageContent: function() {
     4038                var state = this.state(),
     4039                        model = state.get('image'),
     4040                        view;
    40074041
    4008                         // BulkSelection is a <div> with subviews, including screen reader text
    4009                         this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
    4010                                 text: l10n.bulkSelect,
    4011                                 controller: this.controller,
    4012                                 priority: -70
    4013                         }).render() );
     4042                if ( ! model ) {
     4043                        return;
     4044                }
    40144045
    4015                         this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
    4016                                 filters: Filters,
    4017                                 style: 'primary',
    4018                                 disabled: true,
    4019                                 text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
    4020                                 controller: this.controller,
    4021                                 priority: -60,
    4022                                 click: function() {
    4023                                         var changed = [], removed = [],
    4024                                                 selection = this.controller.state().get( 'selection' ),
    4025                                                 library = this.controller.state().get( 'library' );
     4046                view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
    40264047
    4027                                         if ( ! selection.length ) {
    4028                                                 return;
    4029                                         }
     4048                this.content.set( view );
    40304049
    4031                                         if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
    4032                                                 return;
    4033                                         }
     4050                // after bringing in the frame, load the actual editor via an ajax call
     4051                view.loadEditor();
    40344052
    4035                                         if ( mediaTrash &&
    4036                                                 'trash' !== selection.at( 0 ).get( 'status' ) &&
    4037                                                 ! window.confirm( l10n.warnBulkTrash ) ) {
     4053        },
    40384054
    4039                                                 return;
    4040                                         }
     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,
    40414063
    4042                                         selection.each( function( model ) {
    4043                                                 if ( ! model.get( 'nonces' )['delete'] ) {
    4044                                                         removed.push( model );
    4045                                                         return;
    4046                                                 }
     4064                                        click: function() {
     4065                                                var controller = this.controller,
     4066                                                        state = controller.state();
    40474067
    4048                                                 if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
    4049                                                         model.set( 'status', 'inherit' );
    4050                                                         changed.push( model.save() );
    4051                                                         removed.push( model );
    4052                                                 } else if ( mediaTrash ) {
    4053                                                         model.set( 'status', 'trash' );
    4054                                                         changed.push( model.save() );
    4055                                                         removed.push( model );
    4056                                                 } else {
    4057                                                         model.destroy({wait: true});
    4058                                                 }
    4059                                         } );
     4068                                                controller.close();
    40604069
    4061                                         if ( changed.length ) {
    4062                                                 selection.remove( removed );
     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() );
    40634073
    4064                                                 $.when.apply( null, changed ).then( _.bind( function() {
    4065                                                         library._requery( true );
    4066                                                         this.controller.trigger( 'selection:action:done' );
    4067                                                 }, this ) );
    4068                                         } else {
    4069                                                 this.controller.trigger( 'selection:action:done' );
     4074                                                // Restore and reset the default state.
     4075                                                controller.setState( controller.options.state );
     4076                                                controller.reset();
    40704077                                        }
    40714078                                }
    4072                         }).render() );
     4079                        }
     4080                }) );
     4081        },
    40734082
    4074                         if ( mediaTrash ) {
    4075                                 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
    4076                                         filters: Filters,
    4077                                         style: 'primary',
    4078                                         disabled: true,
    4079                                         text: l10n.deleteSelected,
    4080                                         controller: this.controller,
    4081                                         priority: -55,
    4082                                         click: function() {
    4083                                                 var removed = [],
    4084                                                         destroy = [],
    4085                                                         selection = this.controller.state().get( 'selection' );
     4083        renderReplaceImageToolbar: function() {
     4084                var frame = this,
     4085                        lastState = frame.lastState(),
     4086                        previous = lastState && lastState.id;
    40864087
    4087                                                 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
    4088                                                         return;
     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();
    40894099                                                }
     4100                                        }
     4101                                },
    40904102
    4091                                                 selection.each( function( model ) {
    4092                                                         if ( ! model.get( 'nonces' )['delete'] ) {
    4093                                                                 removed.push( model );
    4094                                                                 return;
    4095                                                         }
     4103                                replace: {
     4104                                        style:    'primary',
     4105                                        text:     l10n.replace,
     4106                                        priority: 80,
     4107                                        requires: { selection: true },
    40964108
    4097                                                         destroy.push( model );
    4098                                                 } );
     4109                                        click: function() {
     4110                                                var controller = this.controller,
     4111                                                        state = controller.state(),
     4112                                                        selection = state.get( 'selection' ),
     4113                                                        attachment = selection.single();
    40994114
    4100                                                 if ( removed.length ) {
    4101                                                         selection.remove( removed );
    4102                                                 }
     4115                                                controller.close();
    41034116
    4104                                                 if ( destroy.length ) {
    4105                                                         $.when.apply( null, destroy.map( function (item) {
    4106                                                                 return item.destroy();
    4107                                                         } ) ).then( _.bind( function() {
    4108                                                                 this.controller.trigger( 'selection:action:done' );
    4109                                                         }, this ) );
    4110                                                 }
     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();
    41114126                                        }
    4112                                 }).render() );
     4127                                }
    41134128                        }
     4129                }) );
     4130        }
    41144131
    4115                 } else if ( this.options.date ) {
    4116                         // DateFilter is a <select>, screen reader text needs to be rendered before
    4117                         this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
    4118                                 value: l10n.filterByDate,
    4119                                 attributes: {
    4120                                         'for': 'media-attachment-date-filters'
    4121                                 },
    4122                                 priority: -75
    4123                         }).render() );
    4124                         this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
    4125                                 controller: this.controller,
    4126                                 model:      this.collection.props,
    4127                                 priority: -75
    4128                         }).render() );
    4129                 }
     4132});
    41304133
    4131                 if ( this.options.search ) {
    4132                         // Search is an input, screen reader text needs to be rendered before
    4133                         this.toolbar.set( 'searchLabel', new wp.media.view.Label({
    4134                                 value: l10n.searchMediaLabel,
    4135                                 attributes: {
    4136                                         'for': 'media-search-input'
    4137                                 },
    4138                                 priority:   60
    4139                         }).render() );
    4140                         this.toolbar.set( 'search', new wp.media.view.Search({
    4141                                 controller: this.controller,
    4142                                 model:      this.collection.props,
    4143                                 priority:   60
    4144                         }).render() );
    4145                 }
     4134module.exports = ImageDetails;
    41464135
    4147                 if ( this.options.dragInfo ) {
    4148                         this.toolbar.set( 'dragInfo', new View({
    4149                                 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
    4150                                 priority: -40
    4151                         }) );
    4152                 }
    41534136
    4154                 if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
    4155                         this.toolbar.set( 'suggestedDimensions', new View({
    4156                                 el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0],
    4157                                 priority: -40
    4158                         }) );
    4159                 }
    4160         },
     4137/***/ }),
     4138/* 51 */
     4139/***/ (function(module, exports) {
    41614140
    4162         updateContent: function() {
    4163                 var view = this,
    4164                         noItemsView;
     4141var $ = jQuery,
     4142        Modal;
    41654143
    4166                 if ( this.controller.isModeActive( 'grid' ) ) {
    4167                         noItemsView = view.attachmentsNoResults;
    4168                 } else {
    4169                         noItemsView = view.uploader;
    4170                 }
     4144/**
     4145 * wp.media.view.Modal
     4146 *
     4147 * A modal view, which the media modal uses as its default container.
     4148 *
     4149 * @memberOf wp.media.view
     4150 *
     4151 * @class
     4152 * @augments wp.media.View
     4153 * @augments wp.Backbone.View
     4154 * @augments Backbone.View
     4155 */
     4156Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{
     4157        tagName:  'div',
     4158        template: wp.template('media-modal'),
    41714159
    4172                 if ( ! this.collection.length ) {
    4173                         this.toolbar.get( 'spinner' ).show();
    4174                         this.dfd = this.collection.more().done( function() {
    4175                                 if ( ! view.collection.length ) {
    4176                                         noItemsView.$el.removeClass( 'hidden' );
    4177                                 } else {
    4178                                         noItemsView.$el.addClass( 'hidden' );
    4179                                 }
    4180                                 view.toolbar.get( 'spinner' ).hide();
    4181                         } );
    4182                 } else {
    4183                         noItemsView.$el.addClass( 'hidden' );
    4184                         view.toolbar.get( 'spinner' ).hide();
    4185                 }
     4160        attributes: {
     4161                tabindex: 0
    41864162        },
    41874163
    4188         createUploader: function() {
    4189                 this.uploader = new wp.media.view.UploaderInline({
    4190                         controller: this.controller,
    4191                         status:     false,
    4192                         message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
    4193                         canClose:   this.controller.isModeActive( 'grid' )
     4164        events: {
     4165                'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
     4166                'keydown': 'keydown'
     4167        },
     4168
     4169        clickedOpenerEl: null,
     4170
     4171        initialize: function() {
     4172                _.defaults( this.options, {
     4173                        container: document.body,
     4174                        title:     '',
     4175                        propagate: true,
     4176                        freeze:    true
    41944177                });
    41954178
    4196                 this.uploader.$el.addClass( 'hidden' );
    4197                 this.views.add( this.uploader );
     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                };
    41984190        },
    41994191
    4200         toggleUploader: function() {
    4201                 if ( this.uploader.$el.hasClass( 'hidden' ) ) {
    4202                         this.uploader.show();
    4203                 } else {
    4204                         this.uploader.hide();
     4192        /**
     4193         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4194         */
     4195        attach: function() {
     4196                if ( this.views.attached ) {
     4197                        return this;
    42054198                }
    4206         },
    42074199
    4208         createAttachments: function() {
    4209                 this.attachments = new wp.media.view.Attachments({
    4210                         controller:           this.controller,
    4211                         collection:           this.collection,
    4212                         selection:            this.options.selection,
    4213                         model:                this.model,
    4214                         sortable:             this.options.sortable,
    4215                         scrollElement:        this.options.scrollElement,
    4216                         idealColumnWidth:     this.options.idealColumnWidth,
     4200                if ( ! this.views.rendered ) {
     4201                        this.render();
     4202                }
    42174203
    4218                         // The single `Attachment` view to be used in the `Attachments` view.
    4219                         AttachmentView: this.options.AttachmentView
    4220                 });
     4204                this.$el.appendTo( this.options.container );
    42214205
    4222                 // Add keydown listener to the instance of the Attachments view
    4223                 this.controller.on( 'attachment:keydown:arrow',     _.bind( this.attachments.arrowEvent, this.attachments ) );
    4224                 this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) );
     4206                // Manually mark the view as attached and trigger ready.
     4207                this.views.attached = true;
     4208                this.views.ready();
    42254209
    4226                 this.views.add( this.attachments );
     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;
    42274233
     4234                if ( $el.is(':visible') ) {
     4235                        return this;
     4236                }
    42284237
    4229                 if ( this.controller.isModeActive( 'grid' ) ) {
    4230                         this.attachmentsNoResults = new View({
    4231                                 controller: this.controller,
    4232                                 tagName: 'p'
    4233                         });
     4238                this.clickedOpenerEl = document.activeElement;
    42344239
    4235                         this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
    4236                         this.attachmentsNoResults.$el.html( l10n.noMedia );
     4240                if ( ! this.views.attached ) {
     4241                        this.attach();
     4242                }
    42374243
    4238                         this.views.add( this.attachmentsNoResults );
     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                        };
    42394249                }
    4240         },
    42414250
    4242         createSidebar: function() {
    4243                 var options = this.options,
    4244                         selection = options.selection,
    4245                         sidebar = this.sidebar = new wp.media.view.Sidebar({
    4246                                 controller: this.controller
    4247                         });
     4251                // Disable page scrolling.
     4252                $( 'body' ).addClass( 'modal-open' );
    42484253
    4249                 this.views.add( sidebar );
     4254                $el.show();
    42504255
    4251                 if ( this.controller.uploader ) {
    4252                         sidebar.set( 'uploads', new wp.media.view.UploaderStatus({
    4253                                 controller: this.controller,
    4254                                 priority:   40
    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                        }
    42564266                }
    42574267
    4258                 selection.on( 'selection:single', this.createSingle, this );
    4259                 selection.on( 'selection:unsingle', this.disposeSingle, this );
     4268                this.$el.focus();
    42604269
    4261                 if ( selection.single() ) {
    4262                         this.createSingle();
    4263                 }
     4270                return this.propagate('open');
    42644271        },
    42654272
    4266         createSingle: function() {
    4267                 var sidebar = this.sidebar,
    4268                         single = this.options.selection.single();
     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;
    42694279
    4270                 sidebar.set( 'details', new wp.media.view.Attachment.Details({
    4271                         controller: this.controller,
    4272                         model:      single,
    4273                         priority:   80
    4274                 }) );
     4280                if ( ! this.views.attached || ! this.$el.is(':visible') ) {
     4281                        return this;
     4282                }
    42754283
    4276                 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({
    4277                         controller: this.controller,
    4278                         model:      single,
    4279                         priority:   120
    4280                 }) );
     4284                // Enable page scrolling.
     4285                $( 'body' ).removeClass( 'modal-open' );
    42814286
    4282                 if ( this.options.display ) {
    4283                         sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({
    4284                                 controller:   this.controller,
    4285                                 model:        this.model.display( single ),
    4286                                 attachment:   single,
    4287                                 priority:     160,
    4288                                 userSettings: this.model.get('displayUserSettings')
    4289                         }) );
     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();
    42904295                }
    42914296
    4292                 // Show the sidebar on mobile
    4293                 if ( this.model.id === 'insert' ) {
    4294                         sidebar.$el.addClass( 'visible' );
     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 );
    42954302                }
    4296         },
    42974303
    4298         disposeSingle: function() {
    4299                 var sidebar = this.sidebar;
    4300                 sidebar.unset('details');
    4301                 sidebar.unset('compat');
    4302                 sidebar.unset('display');
    4303                 // Hide the sidebar on mobile
    4304                 sidebar.$el.removeClass( 'visible' );
    4305         }
    4306 });
     4304                if ( options && options.escape ) {
     4305                        this.propagate('escape');
     4306                }
    43074307
    4308 module.exports = AttachmentsBrowser;
     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        },
    43094323
    4310 },{}],33:[function(require,module,exports){
    4311 var Attachments = wp.media.view.Attachments,
    4312         Selection;
     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        },
    43134332
    4314 /**
    4315  * wp.media.view.Attachments.Selection
    4316  *
    4317  * @memberOf wp.media.view.Attachments
    4318  *
    4319  * @class
    4320  * @augments wp.media.view.Attachments
    4321  * @augments wp.media.View
    4322  * @augments wp.Backbone.View
    4323  * @augments Backbone.View
    4324  */
    4325 Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{
    4326         events: {},
    4327         initialize: function() {
    4328                 _.defaults( this.options, {
    4329                         sortable:   false,
    4330                         resize:     false,
     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 );
    43314342
    4332                         // The single `Attachment` view to be used in the `Attachments` view.
    4333                         AttachmentView: wp.media.view.Attachment.Selection
    4334                 });
    4335                 // Call 'initialize' directly on the parent class.
    4336                 return Attachments.prototype.initialize.apply( this, arguments );
     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                }
    43374358        }
    43384359});
    43394360
    4340 module.exports = Selection;
     4361module.exports = Modal;
    43414362
    4342 },{}],34:[function(require,module,exports){
    4343 var $ = Backbone.$,
    4344         ButtonGroup;
     4363
     4364/***/ }),
     4365/* 52 */
     4366/***/ (function(module, exports) {
    43454367
    43464368/**
    4347  * wp.media.view.ButtonGroup
     4369 * wp.media.view.FocusManager
    43484370 *
    43494371 * @memberOf wp.media.view
    43504372 *
    var $ = Backbone.$, 
    43534375 * @augments wp.Backbone.View
    43544376 * @augments Backbone.View
    43554377 */
    4356 ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{
    4357         tagName:   'div',
    4358         className: 'button-group button-large media-button-group',
    4359 
    4360         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;
    4367                         } else {
    4368                                 return new wp.media.view.Button( button ).render();
    4369                         }
    4370                 });
    4371 
    4372                 delete this.options.buttons;
     4378var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{
    43734379
    4374                 if ( this.options.classes ) {
    4375                         this.$el.addClass( this.options.classes );
    4376                 }
     4380        events: {
     4381                'keydown': 'constrainTabbing'
    43774382        },
    43784383
     4384        focus: function() { // Reset focus on first left menu item
     4385                this.$('.media-menu-item').first().focus();
     4386        },
    43794387        /**
    4380          * @returns {wp.media.view.ButtonGroup}
     4388         * @param {Object} event
    43814389         */
    4382         render: function() {
    4383                 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
    4384                 return this;
     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                }
    43854409        }
     4410
    43864411});
    43874412
    4388 module.exports = ButtonGroup;
     4413module.exports = FocusManager;
     4414
     4415
     4416/***/ }),
     4417/* 53 */
     4418/***/ (function(module, exports) {
     4419
     4420var $ = jQuery,
     4421        UploaderWindow;
    43894422
    4390 },{}],35:[function(require,module,exports){
    43914423/**
    4392  * wp.media.view.Button
     4424 * wp.media.view.UploaderWindow
     4425 *
     4426 * An uploader window that allows for dragging and dropping media.
    43934427 *
    43944428 * @memberOf wp.media.view
    43954429 *
    module.exports = ButtonGroup; 
    43974431 * @augments wp.media.View
    43984432 * @augments wp.Backbone.View
    43994433 * @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]
    44004440 */
    4401 var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{
    4402         tagName:    'button',
    4403         className:  'media-button',
    4404         attributes: { type: 'button' },
     4441UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{
     4442        tagName:   'div',
     4443        className: 'uploader-window',
     4444        template:  wp.template('uploader-window'),
    44054445
    4406         events: {
    4407                 'click': 'click'
    4408         },
     4446        initialize: function() {
     4447                var uploader;
    44094448
    4410         defaults: {
    4411                 text:     '',
    4412                 style:    '',
    4413                 size:     'large',
    4414                 disabled: false
    4415         },
     4449                this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' );
    44164450
    4417         initialize: function() {
    4418                 /**
    4419                  * Create a model with the provided `defaults`.
    4420                  *
    4421                  * @member {Backbone.Model}
    4422                  */
    4423                 this.model = new Backbone.Model( this.defaults );
     4451                uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
     4452                        dropzone:  this.$el,
     4453                        browser:   this.$browser,
     4454                        params:    {}
     4455                });
    44244456
    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                         }
     4457                // Ensure the dropzone is a jQuery collection.
     4458                if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
     4459                        uploader.dropzone = $( uploader.dropzone );
     4460                }
    44324461
    4433                         this.model.set( key, value );
    4434                         delete this.options[ key ];
     4462                this.controller.on( 'activate', this.refresh, this );
     4463
     4464                this.controller.on( 'detach', function() {
     4465                        this.$browser.remove();
    44354466                }, this );
     4467        },
    44364468
    4437                 this.listenTo( this.model, 'change', this.render );
     4469        refresh: function() {
     4470                if ( this.uploader ) {
     4471                        this.uploader.refresh();
     4472                }
    44384473        },
    4439         /**
    4440          * @returns {wp.media.view.Button} Returns itself to allow chaining
    4441          */
    4442         render: function() {
    4443                 var classes = [ 'button', this.className ],
    4444                         model = this.model.toJSON();
    44454474
    4446                 if ( model.style ) {
    4447                         classes.push( 'button-' + model.style );
     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;
    44484482                }
    44494483
    4450                 if ( model.size ) {
    4451                         classes.push( 'button-' + model.size );
     4484                if ( postId ) {
     4485                        this.options.uploader.params.post_id = postId;
    44524486                }
     4487                this.uploader = new wp.Uploader( this.options.uploader );
    44534488
    4454                 classes = _.uniq( classes.concat( this.options.classes ) );
    4455                 this.el.className = classes.join(' ');
     4489                dropzone = this.uploader.dropzone;
     4490                dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
     4491                dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
    44564492
    4457                 this.$el.attr( 'disabled', model.disabled );
    4458                 this.$el.text( this.model.get('text') );
     4493                $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
     4494        },
    44594495
    4460                 return this;
     4496        _ready: function() {
     4497                this.controller.trigger( 'uploader:ready' );
    44614498        },
    4462         /**
    4463          * @param {Object} event
    4464          */
    4465         click: function( event ) {
    4466                 if ( '#' === this.attributes.href ) {
    4467                         event.preventDefault();
    4468                 }
    44694499
    4470                 if ( this.options.click && ! this.model.get('disabled') ) {
    4471                         this.options.click.apply( this, arguments );
    4472                 }
     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 );
    44734527        }
    44744528});
    44754529
    4476 module.exports = Button;
     4530module.exports = UploaderWindow;
     4531
     4532
     4533/***/ }),
     4534/* 54 */
     4535/***/ (function(module, exports) {
    44774536
    4478 },{}],36:[function(require,module,exports){
    44794537var View = wp.media.View,
    4480         UploaderStatus = wp.media.view.UploaderStatus,
    44814538        l10n = wp.media.view.l10n,
    44824539        $ = jQuery,
    4483         Cropper;
     4540        EditorUploader;
    44844541
    44854542/**
    4486  * wp.media.view.Cropper
    4487  *
    4488  * Uses the imgAreaSelect plugin to allow a user to crop an image.
     4543 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
     4544 * and relays drag'n'dropped files to a media workflow.
    44894545 *
    4490  * Takes imgAreaSelect options from
    4491  * wp.customize.HeaderControl.calculateImageSelectOptions via
    4492  * wp.customize.HeaderControl.openMM.
     4546 * wp.media.view.EditorUploader
    44934547 *
    44944548 * @memberOf wp.media.view
    44954549 *
    var View = wp.media.View, 
    44984552 * @augments wp.Backbone.View
    44994553 * @augments Backbone.View
    45004554 */
    4501 Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{
    4502         className: 'crop-content',
    4503         template: wp.template('crop-content'),
     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         */
    45044568        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;
     4569                this.initialized = false;
    45284570
    4529                 if (typeof imgOptions === 'function') {
    4530                         imgOptions = imgOptions(this.options.attachment, this.controller);
     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;
    45314574                }
    45324575
    4533                 imgOptions = _.extend(imgOptions, {
    4534                         parent: this.$el,
    4535                         onInit: function() {
    4536                                 this.parent.children().on( 'mousedown touchstart', function( e ){
     4576                this.$document = $(document);
     4577                this.dropzones = [];
     4578                this.files = [];
    45374579
    4538                                         if ( e.shiftKey ) {
    4539                                                 imgSelect.setOptions( {
    4540                                                         aspectRatio: '1:1'
    4541                                                 } );
    4542                                         } else {
    4543                                                 imgSelect.setOptions( {
    4544                                                         aspectRatio: false
    4545                                                 } );
    4546                                         }
    4547                                 } );
     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();
    45484593                        }
    4549                 } );
    4550                 this.trigger('image-loaded');
    4551                 imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
     4594                }, this ) );
     4595
     4596                this.initialized = true;
     4597                return this;
    45524598        },
    4553         onError: function() {
    4554                 var filename = this.options.attachment.get('filename');
    45554599
    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 });
    4560         }
    4561 });
     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;
    45624624
    4563 module.exports = Cropper;
     4625                return this.draggingFile;
     4626        },
    45644627
    4565 },{}],37:[function(require,module,exports){
    4566 var View = wp.media.View,
    4567         EditImage;
     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                }
    45684634
    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'),
     4635                if ( ! _.isUndefined( e ) ) {
     4636                        $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
     4637                }
    45824638
    4583         initialize: function( options ) {
    4584                 this.editor = window.imageEdit;
    4585                 this.controller = options.controller;
    4586                 View.prototype.initialize.apply( this, arguments );
    4587         },
     4639                if ( ! this.overContainer && ! this.overDropzone ) {
     4640                        this.draggingFile = null;
     4641                }
    45884642
    4589         prepare: function() {
    4590                 return this.model.toJSON();
     4643                return this;
    45914644        },
    45924645
    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         },
     4646        render: function() {
     4647                if ( ! this.initialized ) {
     4648                        return this;
     4649                }
    45974650
    4598         focus: function() {
    4599                 this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
     4651                View.prototype.render.apply( this, arguments );
     4652                $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
     4653                return this;
    46004654        },
    46014655
    4602         back: function() {
    4603                 var lastState = this.controller.lastState();
    4604                 this.controller.setState( lastState );
     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;
    46054662        },
    46064663
    4607         refresh: function() {
    4608                 this.model.fetch();
    4609         },
     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;
    46104672
    4611         save: function() {
    4612                 var lastState = this.controller.lastState();
     4673                this.containerDragleave( event );
     4674                this.dropzoneDragleave( event );
    46134675
    4614                 this.model.fetch().done( _.bind( function() {
    4615                         this.controller.setState( lastState );
    4616                 }, this ) );
    4617         }
     4676                this.files = event.originalEvent.dataTransfer.files;
     4677                if ( this.files.length < 1 ) {
     4678                        return;
     4679                }
    46184680
    4619 });
     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                }
    46204686
    4621 module.exports = EditImage;
     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                        });
    46224694
    4623 },{}],38:[function(require,module,exports){
    4624 /**
    4625  * wp.media.view.Embed
    4626  *
    4627  * @memberOf wp.media.view
    4628  *
    4629  * @class
    4630  * @augments wp.media.View
    4631  * @augments wp.Backbone.View
    4632  * @augments Backbone.View
    4633  */
    4634 var Embed = wp.media.View.extend(/** @lends wp.media.view.Ember.prototype */{
    4635         className: 'media-embed',
     4695                        uploadView = this.workflow.uploader;
    46364696
    4637         initialize: function() {
    4638                 /**
    4639                  * @member {wp.media.view.EmbedUrl}
    4640                  */
    4641                 this.url = new wp.media.view.EmbedUrl({
    4642                         controller: this.controller,
    4643                         model:      this.model.props
    4644                 }).render();
     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                }
    46454707
    4646                 this.views.set([ this.url ]);
    4647                 this.refresh();
    4648                 this.listenTo( this.model, 'change:type', this.refresh );
    4649                 this.listenTo( this.model, 'change:loading', this.loading );
     4708                return false;
    46504709        },
    46514710
    46524711        /**
    4653          * @param {Object} view
     4712         * Add the files to the uploader.
    46544713         */
    4655         settings: function( view ) {
    4656                 if ( this._settings ) {
    4657                         this._settings.remove();
     4714        addFiles: function() {
     4715                if ( this.files.length ) {
     4716                        this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
     4717                        this.files = [];
    46584718                }
    4659                 this._settings = view;
    4660                 this.views.add( view );
     4719                return this;
    46614720        },
    46624721
    4663         refresh: function() {
    4664                 var type = this.model.get('type'),
    4665                         constructor;
    4666 
    4667                 if ( 'image' === type ) {
    4668                         constructor = wp.media.view.EmbedImage;
    4669                 } else if ( 'link' === type ) {
    4670                         constructor = wp.media.view.EmbedLink;
    4671                 } else {
     4722        containerDragover: function( event ) {
     4723                if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    46724724                        return;
    46734725                }
    46744726
    4675                 this.settings( new constructor({
    4676                         controller: this.controller,
    4677                         model:      this.model.props,
    4678                         priority:   40
    4679                 }) );
     4727                this.overContainer = true;
     4728                this.refresh();
    46804729        },
    46814730
    4682         loading: function() {
    4683                 this.$el.toggleClass( 'embed-loading', this.model.get('loading') );
    4684         }
    4685 });
     4731        containerDragleave: function() {
     4732                this.overContainer = false;
    46864733
    4687 module.exports = Embed;
     4734                // Throttle dragleave because it's called when bouncing from some elements to others.
     4735                _.delay( _.bind( this.refresh, this ), 50 );
     4736        },
    46884737
    4689 },{}],39:[function(require,module,exports){
    4690 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    4691         EmbedImage;
     4738        dropzoneDragover: function( event ) {
     4739                if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     4740                        return;
     4741                }
    46924742
    4693 /**
    4694  * wp.media.view.EmbedImage
    4695  *
    4696  * @memberOf wp.media.view
    4697  *
    4698  * @class
    4699  * @augments wp.media.view.Settings.AttachmentDisplay
    4700  * @augments wp.media.view.Settings
    4701  * @augments wp.media.View
    4702  * @augments wp.Backbone.View
    4703  * @augments Backbone.View
    4704  */
    4705 EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{
    4706         className: 'embed-media-settings',
    4707         template:  wp.template('embed-image-settings'),
     4743                this.overDropzone = true;
     4744                this.refresh( event );
     4745                return false;
     4746        },
    47084747
    4709         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 );
     4748        dropzoneDragleave: function( e ) {
     4749                this.overDropzone = false;
     4750                _.delay( _.bind( this.refresh, this, e ), 50 );
    47154751        },
    47164752
    4717         updateImage: function() {
    4718                 this.$('img').attr( 'src', this.model.get('url') );
     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;
    47194758        }
    47204759});
    47214760
    4722 module.exports = EmbedImage;
     4761module.exports = EditorUploader;
    47234762
    4724 },{}],40:[function(require,module,exports){
    4725 var $ = jQuery,
    4726         EmbedLink;
     4763
     4764/***/ }),
     4765/* 55 */
     4766/***/ (function(module, exports) {
     4767
     4768var View = wp.media.View,
     4769        UploaderInline;
    47274770
    47284771/**
    4729  * wp.media.view.EmbedLink
     4772 * wp.media.view.UploaderInline
     4773 *
     4774 * The inline uploader that shows up in the 'Upload Files' tab.
    47304775 *
    47314776 * @memberOf wp.media.view
    47324777 *
    47334778 * @class
    4734  * @augments wp.media.view.Settings
    47354779 * @augments wp.media.View
    47364780 * @augments wp.Backbone.View
    47374781 * @augments Backbone.View
    47384782 */
    4739 EmbedLink = wp.media.view.Settings.extend(/** @lends wp.media.view.EmbedLink.prototype */{
    4740         className: 'embed-link-settings',
    4741         template:  wp.template('embed-link-settings'),
     4783UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{
     4784        tagName:   'div',
     4785        className: 'uploader-inline',
     4786        template:  wp.template('uploader-inline'),
    47424787
    4743         initialize: function() {
    4744                 this.listenTo( this.model, 'change:url', this.updateoEmbed );
     4788        events: {
     4789                'click .close': 'hide'
    47454790        },
    47464791
    4747         updateoEmbed: _.debounce( function() {
    4748                 var url = this.model.get( 'url' );
     4792        initialize: function() {
     4793                _.defaults( this.options, {
     4794                        message: '',
     4795                        status:  true,
     4796                        canClose: false
     4797                });
    47494798
    4750                 // clear out previous results
    4751                 this.$('.embed-container').hide().find('.embed-preview').empty();
    4752                 this.$( '.setting' ).hide();
     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                }
    47534806
    4754                 // only proceed with embed if the field contains more than 11 characters
    4755                 // Example: http://a.io is 11 chars
    4756                 if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {
    4757                         return;
     4807                if ( this.options.status ) {
     4808                        this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
     4809                                controller: this.controller
     4810                        }) );
    47584811                }
     4812        },
    47594813
    4760                 this.fetch();
    4761         }, wp.media.controller.Embed.sensitivity ),
     4814        prepare: function() {
     4815                var suggestedWidth = this.controller.state().get('suggestedWidth'),
     4816                        suggestedHeight = this.controller.state().get('suggestedHeight'),
     4817                        data = {};
    47624818
    4763         fetch: function() {
    4764                 var url = this.model.get( 'url' ), re, youTubeEmbedMatch;
     4819                data.message = this.options.message;
     4820                data.canClose = this.options.canClose;
    47654821
    4766                 // check if they haven't typed in 500 ms
    4767                 if ( $('#embed-url-field').val() !== url ) {
    4768                         return;
     4822                if ( suggestedWidth && suggestedHeight ) {
     4823                        data.suggestedWidth = suggestedWidth;
     4824                        data.suggestedHeight = suggestedHeight;
    47694825                }
    47704826
    4771                 if ( this.dfd && 'pending' === this.dfd.state() ) {
    4772                         this.dfd.abort();
     4827                return data;
     4828        },
     4829        /**
     4830         * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
     4831         */
     4832        dispose: function() {
     4833                if ( this.disposing ) {
     4834                        /**
     4835                         * call 'dispose' directly on the parent class
     4836                         */
     4837                        return View.prototype.dispose.apply( this, arguments );
    47734838                }
    47744839
    4775                 // Support YouTube embed urls, since they work once in the editor.
    4776                 re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/;
    4777                 youTubeEmbedMatch = re.exec( url );
    4778                 if ( youTubeEmbedMatch ) {
    4779                         url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ];
    4780                 }
     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 );
    47814854
    4782                 this.dfd = wp.apiRequest({
    4783                         url: wp.media.view.settings.oEmbedProxyUrl,
    4784                         data: {
    4785                                 url: url,
    4786                                 maxwidth: this.model.get( 'width' ),
    4787                                 maxheight: this.model.get( 'height' )
    4788                         },
    4789                         type: 'GET',
    4790                         dataType: 'json',
    4791                         context: this
    4792                 })
    4793                         .done( function( response ) {
    4794                                 this.renderoEmbed( {
    4795                                         data: {
    4796                                                 body: response.html || ''
    4797                                         }
    4798                                 } );
    4799                         } )
    4800                         .fail( this.renderFail );
     4855                _.defer( _.bind( this.refresh, this ) );
     4856                return result;
    48014857        },
    48024858
    4803         renderFail: function ( response, status ) {
    4804                 if ( 'abort' === status ) {
    4805                         return;
     4859        refresh: function() {
     4860                var uploader = this.controller.uploader;
     4861
     4862                if ( uploader ) {
     4863                        uploader.refresh();
    48064864                }
    4807                 this.$( '.link-text' ).show();
    48084865        },
     4866        /**
     4867         * @returns {wp.media.view.UploaderInline}
     4868         */
     4869        ready: function() {
     4870                var $browser = this.options.$browser,
     4871                        $placeholder;
    48094872
    4810         renderoEmbed: function( response ) {
    4811                 var html = ( response && response.data && response.data.body ) || '';
     4873                if ( this.controller.uploader ) {
     4874                        $placeholder = this.$('.browser');
    48124875
    4813                 if ( html ) {
    4814                         this.$('.embed-container').show().find('.embed-preview').html( html );
    4815                 } else {
    4816                         this.renderFail();
     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();
    48174902                }
    48184903        }
     4904
    48194905});
    48204906
    4821 module.exports = EmbedLink;
     4907module.exports = UploaderInline;
     4908
     4909
     4910/***/ }),
     4911/* 56 */
     4912/***/ (function(module, exports) {
    48224913
    4823 },{}],41:[function(require,module,exports){
    48244914var View = wp.media.View,
    4825         $ = jQuery,
    4826         EmbedUrl;
     4915        UploaderStatus;
    48274916
    48284917/**
    4829  * wp.media.view.EmbedUrl
     4918 * wp.media.view.UploaderStatus
     4919 *
     4920 * An uploader status for on-going uploads.
    48304921 *
    48314922 * @memberOf wp.media.view
    48324923 *
    var View = wp.media.View, 
    48354926 * @augments wp.Backbone.View
    48364927 * @augments Backbone.View
    48374928 */
    4838 EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{
    4839         tagName:   'label',
    4840         className: 'embed-url',
     4929UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{
     4930        className: 'media-uploader-status',
     4931        template:  wp.template('uploader-status'),
    48414932
    48424933        events: {
    4843                 'input':  'url',
    4844                 'keyup':  'url',
    4845                 'change': 'url'
     4934                'click .upload-dismiss-errors': 'dismiss'
    48464935        },
    48474936
    48484937        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 );
     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 );
    48564942
    4857                 if ( this.model.get( 'url' ) ) {
    4858                         _.delay( _.bind( function () {
    4859                                 this.model.trigger( 'change:url' );
    4860                         }, this ), 500 );
    4861                 }
     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 );
    48624947        },
    48634948        /**
    4864          * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
     4949         * @returns {wp.media.view.UploaderStatus}
    48654950         */
    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://';
     4951        dispose: function() {
     4952                wp.Uploader.queue.off( null, null, this );
    48744953                /**
    4875                  * Call `render` directly on parent class with passed arguments
     4954                 * call 'dispose' directly on the parent class
    48764955                 */
    4877                 View.prototype.render.apply( this, arguments );
     4956                View.prototype.dispose.apply( this, arguments );
    48784957                return this;
    48794958        },
    48804959
     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
    48814966        ready: function() {
    4882                 if ( ! wp.media.isTouchDevice ) {
    4883                         this.focus();
     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;
    48844987                }
     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 ) + '%' );
    48854997        },
    48864998
    4887         url: function( event ) {
    4888                 this.model.set( 'url', $.trim( event.target.value ) );
     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 });
    48895031        },
    48905032
    48915033        /**
    4892          * If the input is visible, focus and select its contents.
     5034         * @param {Object} event
    48935035         */
    4894         focus: function() {
    4895                 var $input = this.$input;
    4896                 if ( $input.is(':visible') ) {
    4897                         $input.focus()[0].select();
     5036        dismiss: function( event ) {
     5037                var errors = this.views.get('.upload-errors');
     5038
     5039                event.preventDefault();
     5040
     5041                if ( errors ) {
     5042                        _.invoke( errors, 'remove' );
    48985043                }
     5044                wp.Uploader.errors.reset();
    48995045        }
    49005046});
    49015047
    4902 module.exports = EmbedUrl;
     5048module.exports = UploaderStatus;
     5049
     5050
     5051/***/ }),
     5052/* 57 */
     5053/***/ (function(module, exports) {
    49035054
    4904 },{}],42:[function(require,module,exports){
    49055055/**
    4906  * wp.media.view.FocusManager
     5056 * wp.media.view.UploaderStatusError
    49075057 *
    49085058 * @memberOf wp.media.view
    49095059 *
    module.exports = EmbedUrl; 
    49125062 * @augments wp.Backbone.View
    49135063 * @augments Backbone.View
    49145064 */
    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                 }
     5065var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{
     5066        className: 'upload-error',
     5067        template:  wp.template('uploader-status-error')
     5068});
    49345069
    4935                 // Skip the file input added by Plupload.
    4936                 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
     5070module.exports = UploaderStatusError;
    49375071
    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         }
    49475072
    4948 });
     5073/***/ }),
     5074/* 58 */
     5075/***/ (function(module, exports) {
    49495076
    4950 module.exports = FocusManager;
     5077var View = wp.media.View,
     5078        Toolbar;
    49515079
    4952 },{}],43:[function(require,module,exports){
    49535080/**
    4954  * wp.media.view.Frame
     5081 * wp.media.view.Toolbar
    49555082 *
    4956  * A frame is a composite view consisting of one or more regions and one or more
    4957  * states.
     5083 * A toolbar which consists of a primary and a secondary section. Each sections
     5084 * can be filled with views.
    49585085 *
    49595086 * @memberOf wp.media.view
    49605087 *
    4961  * @see wp.media.controller.State
    4962  * @see wp.media.controller.Region
    4963  *
    49645088 * @class
    49655089 * @augments wp.media.View
    49665090 * @augments wp.Backbone.View
    49675091 * @augments Backbone.View
    4968  * @mixes wp.media.controller.StateMachine
    49695092 */
    4970 var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{
     5093Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{
     5094        tagName:   'div',
     5095        className: 'media-toolbar',
     5096
    49715097        initialize: function() {
    4972                 _.defaults( this.options, {
    4973                         mode: [ 'select' ]
    4974                 });
    4975                 this._createRegions();
    4976                 this._createStates();
    4977                 this._createModes();
    4978         },
     5098                var state = this.controller.state(),
     5099                        selection = this.selection = state.get('selection'),
     5100                        library = this.library = state.get('library');
    49795101
    4980         _createRegions: function() {
    4981                 // Clone the regions array.
    4982                 this.regions = this.regions ? this.regions.slice() : [];
     5102                this._views = {};
    49835103
    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                 });
     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');
    50065109
    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 );
     5110                this.views.set([ this.secondary, this.primary ]);
    50125111
    5013                 if ( this.options.states ) {
    5014                         this.states.add( this.options.states );
     5112                if ( this.options.items ) {
     5113                        this.set( this.options.items, { silent: true });
    50155114                }
    5016         },
    50175115
    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 ) );
     5116                if ( ! this.options.silent ) {
     5117                        this.render();
     5118                }
    50275119
    5028                 _.each( this.options.mode, function( mode ) {
    5029                         this.activateMode( mode );
    5030                 }, this );
     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                }
    50315127        },
    50325128        /**
    5033          * Reset all states on the frame to their defaults.
    5034          *
    5035          * @returns {wp.media.view.Frame} Returns itself to allow chaining
     5129         * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
    50365130         */
    5037         reset: function() {
    5038                 this.states.invoke( 'trigger', 'reset' );
    5039                 return this;
     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 );
     5138                }
     5139                /**
     5140                 * call 'dispose' directly on the parent class
     5141                 */
     5142                return View.prototype.dispose.apply( this, arguments );
     5143        },
     5144
     5145        ready: function() {
     5146                this.refresh();
    50405147        },
     5148
    50415149        /**
    5042          * Map activeMode collection events to the frame.
     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
    50435154         */
    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;
     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();
    50555169                        }
    5056                 } );
    50575170
    5058                 if ( ! _.has( modeEventMap, collectionEvent ) ) {
    5059                         return;
     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 );
    50605177                }
    50615178
    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;
     5179                if ( ! options.silent ) {
     5180                        this.refresh();
    50755181                }
    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 );
    50795182
    50805183                return this;
    50815184        },
    50825185        /**
    5083          * Deactivate a mode on the frame.
    5084          *
    5085          * @param string mode Mode ID.
    5086          * @returns {this} Returns itself to allow chaining.
     5186         * @param {string} id
     5187         * @returns {wp.media.view.Button}
    50875188         */
    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' );
     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 );
    51015201
     5202                if ( ! options || ! options.silent ) {
     5203                        this.refresh();
     5204                }
    51025205                return this;
    51035206        },
    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 );
     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                });
    51125235        }
    51135236});
    51145237
    5115 // Make the `Frame` a `StateMachine`.
    5116 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
     5238module.exports = Toolbar;
    51175239
    5118 module.exports = Frame;
    51195240
    5120 },{}],44:[function(require,module,exports){
    5121 var Select = wp.media.view.MediaFrame.Select,
     5241/***/ }),
     5242/* 59 */
     5243/***/ (function(module, exports) {
     5244
     5245var Toolbar = wp.media.view.Toolbar,
    51225246        l10n = wp.media.view.l10n,
    5123         ImageDetails;
     5247        Select;
    51245248
    51255249/**
    5126  * wp.media.view.MediaFrame.ImageDetails
    5127  *
    5128  * A media frame for manipulating an image that's already been inserted
    5129  * into a post.
     5250 * wp.media.view.Toolbar.Select
    51305251 *
    5131  * @memberOf wp.media.view.MediaFrame
     5252 * @memberOf wp.media.view.Toolbar
    51325253 *
    51335254 * @class
    5134  * @augments wp.media.view.MediaFrame.Select
    5135  * @augments wp.media.view.MediaFrame
    5136  * @augments wp.media.view.Frame
     5255 * @augments wp.media.view.Toolbar
    51375256 * @augments wp.media.View
    51385257 * @augments wp.Backbone.View
    51395258 * @augments Backbone.View
    5140  * @mixes wp.media.controller.StateMachine
    51415259 */
    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         },
     5260Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{
     5261        initialize: function() {
     5262                var options = this.options;
    51595263
    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         },
     5264                _.bindAll( this, 'clickSelect' );
    51695265
    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         },
     5266                _.defaults( options, {
     5267                        event: 'select',
     5268                        state: false,
     5269                        reset: true,
     5270                        close: true,
     5271                        text:  l10n.select,
    51925272
    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
     5273                        // Does the button rely on the selection?
     5274                        requires: {
     5275                                selection: true
     5276                        }
    51985277                });
    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();
    52335278
    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                                 }
     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
    52435286                        }
    5244                 }) );
     5287                });
     5288                // Call 'initialize' directly on the parent class.
     5289                Toolbar.prototype.initialize.apply( this, arguments );
    52455290        },
    52465291
    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();
     5292        clickSelect: function() {
     5293                var options = this.options,
     5294                        controller = this.controller;
    52785295
    5279                                                 controller.close();
     5296                if ( options.close ) {
     5297                        controller.close();
     5298                }
    52805299
    5281                                                 controller.image.changeAttachment( attachment, state.display( attachment ) );
     5300                if ( options.event ) {
     5301                        controller.state().trigger( options.event );
     5302                }
    52825303
    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() );
     5304                if ( options.state ) {
     5305                        controller.setState( options.state );
     5306                }
    52865307
    5287                                                 // Restore and reset the default state.
    5288                                                 controller.setState( controller.options.state );
    5289                                                 controller.reset();
    5290                                         }
    5291                                 }
    5292                         }
    5293                 }) );
     5308                if ( options.reset ) {
     5309                        controller.reset();
     5310                }
    52945311        }
    5295 
    52965312});
    52975313
    5298 module.exports = ImageDetails;
     5314module.exports = Select;
    52995315
    5300 },{}],45:[function(require,module,exports){
    5301 var Select = wp.media.view.MediaFrame.Select,
    5302         Library = wp.media.controller.Library,
     5316
     5317/***/ }),
     5318/* 60 */
     5319/***/ (function(module, exports) {
     5320
     5321var Select = wp.media.view.Toolbar.Select,
    53035322        l10n = wp.media.view.l10n,
    5304         Post;
     5323        Embed;
    53055324
    53065325/**
    5307  * wp.media.view.MediaFrame.Post
    5308  *
    5309  * The frame for manipulating media on the Edit Post page.
     5326 * wp.media.view.Toolbar.Embed
    53105327 *
    5311  * @memberOf wp.media.view.MediaFrame
     5328 * @memberOf wp.media.view.Toolbar
    53125329 *
    53135330 * @class
    5314  * @augments wp.media.view.MediaFrame.Select
    5315  * @augments wp.media.view.MediaFrame
    5316  * @augments wp.media.view.Frame
     5331 * @augments wp.media.view.Toolbar.Select
     5332 * @augments wp.media.view.Toolbar
    53175333 * @augments wp.media.View
    53185334 * @augments wp.Backbone.View
    53195335 * @augments Backbone.View
    5320  * @mixes wp.media.controller.StateMachine
    53215336 */
    5322 Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{
     5337Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{
    53235338        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 
    53355339                _.defaults( this.options, {
    5336                         multiple:  true,
    5337                         editing:   false,
    5338                         state:    'insert',
    5339                         metadata:  {}
     5340                        text: l10n.insertIntoPost,
     5341                        requires: false
    53405342                });
    5341 
    53425343                // Call 'initialize' directly on the parent class.
    53435344                Select.prototype.initialize.apply( this, arguments );
    5344                 this.createIframeStates();
     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) {
    53455363
     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'
    53465381        },
    53475382
    5348         /**
    5349          * Create the default states.
    5350          */
    5351         createStates: function() {
    5352                 var options = this.options;
     5383        defaults: {
     5384                text:     '',
     5385                style:    '',
     5386                size:     'large',
     5387                disabled: false
     5388        },
    53535389
    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,
     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 );
    53655397
    5366                                 // If the user isn't allowed to edit fields,
    5367                                 // can they still edit it locally?
    5368                                 allowLocalEdits: true,
     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                        }
    53695405
    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                         }),
     5406                        this.model.set( key, value );
     5407                        delete this.options[ key ];
     5408                }, this );
    53765409
    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,
     5410                this.listenTo( this.model, 'change', this.render );
     5411        },
     5412        /**
     5413         * @returns {wp.media.view.Button} Returns itself to allow chaining
     5414         */
     5415        render: function() {
     5416                var classes = [ 'button', this.className ],
     5417                        model = this.model.toJSON();
    53855418
    5386                                 library:  wp.media.query( _.defaults({
    5387                                         type: 'image'
    5388                                 }, options.library ) )
    5389                         }),
     5419                if ( model.style ) {
     5420                        classes.push( 'button-' + model.style );
     5421                }
    53905422
    5391                         // Embed states.
    5392                         new wp.media.controller.Embed( { metadata: options.metadata } ),
     5423                if ( model.size ) {
     5424                        classes.push( 'button-' + model.size );
     5425                }
    53935426
    5394                         new wp.media.controller.EditImage( { model: options.editImage } ),
     5427                classes = _.uniq( classes.concat( this.options.classes ) );
     5428                this.el.className = classes.join(' ');
    53955429
    5396                         // Gallery states.
    5397                         new wp.media.controller.GalleryEdit({
    5398                                 library: options.selection,
    5399                                 editing: options.editing,
    5400                                 menu:    'gallery'
    5401                         }),
     5430                this.$el.attr( 'disabled', model.disabled );
     5431                this.$el.text( this.model.get('text') );
    54025432
    5403                         new wp.media.controller.GalleryAdd(),
     5433                return this;
     5434        },
     5435        /**
     5436         * @param {Object} event
     5437         */
     5438        click: function( event ) {
     5439                if ( '#' === this.attributes.href ) {
     5440                        event.preventDefault();
     5441                }
    54045442
    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,
     5443                if ( this.options.click && ! this.model.get('disabled') ) {
     5444                        this.options.click.apply( this, arguments );
     5445                }
     5446        }
     5447});
    54135448
    5414                                 library:  wp.media.query( _.defaults({
    5415                                         type: 'audio'
    5416                                 }, options.library ) )
    5417                         }),
     5449module.exports = Button;
    54185450
    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                         }),
    54315451
    5432                         new wp.media.controller.CollectionAdd({
    5433                                 type: 'audio',
    5434                                 collectionType: 'playlist',
    5435                                 title: l10n.addToPlaylistTitle
    5436                         }),
     5452/***/ }),
     5453/* 62 */
     5454/***/ (function(module, exports) {
    54375455
    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,
     5456var $ = Backbone.$,
     5457        ButtonGroup;
    54465458
    5447                                 library:  wp.media.query( _.defaults({
    5448                                         type: 'video'
    5449                                 }, options.library ) )
    5450                         }),
     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',
    54515472
    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                         }),
     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                });
    54635484
    5464                         new wp.media.controller.CollectionAdd({
    5465                                 type: 'video',
    5466                                 collectionType: 'playlist',
    5467                                 title: l10n.addToVideoPlaylistTitle
    5468                         })
    5469                 ]);
     5485                delete this.options.buttons;
    54705486
    5471                 if ( wp.media.view.settings.post.featuredImageId ) {
    5472                         this.states.add( new wp.media.controller.FeaturedImage() );
     5487                if ( this.options.classes ) {
     5488                        this.$el.addClass( this.options.classes );
    54735489                }
    54745490        },
    54755491
    5476         bindHandlers: function() {
    5477                 var handlers, checkCounts;
     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});
    54785500
    5479                 Select.prototype.bindHandlers.apply( this, arguments );
     5501module.exports = ButtonGroup;
    54805502
    5481                 this.on( 'activate', this.activate, this );
    54825503
    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                 } );
     5504/***/ }),
     5505/* 63 */
     5506/***/ (function(module, exports) {
    54875507
    5488                 if ( typeof checkCounts !== 'undefined' ) {
    5489                         this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
    5490                 }
     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',
    54915520
    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 );
     5521        initialize: function() {
     5522                this._views = {};
    55015523
    5502                 handlers = {
    5503                         menu: {
    5504                                 'default': 'mainMenu',
    5505                                 'gallery': 'galleryMenu',
    5506                                 'playlist': 'playlistMenu',
    5507                                 'video-playlist': 'videoPlaylistMenu'
    5508                         },
     5524                this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
     5525                delete this.options.views;
    55095526
    5510                         content: {
    5511                                 'embed':          'embedContent',
    5512                                 'edit-image':     'editImageContent',
    5513                                 'edit-selection': 'editSelectionContent'
    5514                         },
     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;
    55155539
    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                 };
     5540                options = options || {};
    55295541
    5530                 _.each( handlers, function( regionHandlers, region ) {
    5531                         _.each( regionHandlers, function( callback, handler ) {
    5532                                 this.on( region + ':render:' + handler, this[ callback ], this );
     5542                // Accept an object with an `id` : `view` mapping.
     5543                if ( _.isObject( id ) ) {
     5544                        _.each( id, function( view, id ) {
     5545                                this.set( id, view );
    55335546                        }, this );
    5534                 }, this );
    5535         },
     5547                        return this;
     5548                }
    55365549
    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' );
     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;
    55425564                        }
    5543                 }, this );
    5544         },
     5565                });
    55455566
    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         },
     5567                this._views[ id ] = view;
     5568                this.views.add( view, {
     5569                        at: _.isNumber( index ) ? index : views.length || 0
     5570                });
    55525571
    5553         // Menus
     5572                return this;
     5573        },
    55545574        /**
    5555          * @param {wp.Backbone.View} view
     5575         * @param {string} id
     5576         * @returns {wp.media.View}
    55565577         */
    5557         mainMenu: function( view ) {
    5558                 view.set({
    5559                         'library-separator': new wp.media.View({
    5560                                 className: 'separator',
    5561                                 priority: 100
    5562                         })
    5563                 });
     5578        get: function( id ) {
     5579                return this._views[ id ];
    55645580        },
     5581        /**
     5582         * @param {string} id
     5583         * @returns {wp.media.view.PriorityList}
     5584         */
     5585        unset: function( id ) {
     5586                var view = this.get( id );
    55655587
    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 );
     5588                if ( view ) {
     5589                        view.remove();
    55725590                }
     5591
     5592                delete this._views[ id ];
     5593                return this;
    55735594        },
    55745595        /**
    5575          * @param {wp.Backbone.View} view
     5596         * @param {Object} options
     5597         * @returns {wp.media.View}
    55765598         */
    5577         galleryMenu: function( view ) {
    5578                 var lastState = this.lastState(),
    5579                         previous = lastState && lastState.id,
    5580                         frame = this;
     5599        toView: function( options ) {
     5600                return new wp.media.View( options );
     5601        }
     5602});
    55815603
    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                                         }
     5604module.exports = PriorityList;
    55925605
    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         },
    56045606
    5605         playlistMenu: function( view ) {
    5606                 var lastState = this.lastState(),
    5607                         previous = lastState && lastState.id,
    5608                         frame = this;
     5607/***/ }),
     5608/* 64 */
     5609/***/ (function(module, exports) {
    56095610
    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         },
     5611var $ = jQuery,
     5612        MenuItem;
    56285613
    5629         videoPlaylistMenu: function( view ) {
    5630                 var lastState = this.lastState(),
    5631                         previous = lastState && lastState.id,
    5632                         frame = this;
     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',
    56335627
    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                 });
     5628        attributes: {
     5629                href: '#'
     5630        },
     5631
     5632        events: {
     5633                'click': '_click'
    56515634        },
     5635        /**
     5636         * @param {Object} event
     5637         */
     5638        _click: function( event ) {
     5639                var clickOverride = this.options.click;
    56525640
    5653         // Content
    5654         embedContent: function() {
    5655                 var view = new wp.media.view.Embed({
    5656                         controller: this,
    5657                         model:      this.state()
    5658                 }).render();
     5641                if ( event ) {
     5642                        event.preventDefault();
     5643                }
    56595644
    5660                 this.content.set( view );
     5645                if ( clickOverride ) {
     5646                        clickOverride.call( this );
     5647                } else {
     5648                        this.click();
     5649                }
    56615650
     5651                // When selecting a tab along the left side,
     5652                // focus should be transferred into the main panel
    56625653                if ( ! wp.media.isTouchDevice ) {
    5663                         view.url.focus();
     5654                        $('.media-frame-content input').first().focus();
    56645655                }
    56655656        },
    56665657
    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,
     5658        click: function() {
     5659                var state = this.options.state;
    56815660
    5682                         AttachmentView: wp.media.view.Attachments.EditSelection
    5683                 }).render();
     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;
    56845671
    5685                 view.toolbar.set( 'backToLibrary', {
    5686                         text:     l10n.returnToLibrary,
    5687                         priority: -100,
     5672                if ( options.text ) {
     5673                        this.$el.text( options.text );
     5674                } else if ( options.html ) {
     5675                        this.$el.html( options.html );
     5676                }
    56885677
    5689                         click: function() {
    5690                                 this.controller.content.mode('browse');
    5691                         }
    5692                 });
     5678                return this;
     5679        }
     5680});
    56935681
    5694                 // Browse our library of attachments.
    5695                 this.content.set( view );
     5682module.exports = MenuItem;
    56965683
    5697                 // Trigger the controller to set focus
    5698                 this.trigger( 'edit:selection', this );
    5699         },
    57005684
    5701         editImageContent: function() {
    5702                 var image = this.state().get('image'),
    5703                         view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
     5685/***/ }),
     5686/* 65 */
     5687/***/ (function(module, exports) {
    57045688
    5705                 this.content.set( view );
     5689var MenuItem = wp.media.view.MenuItem,
     5690        PriorityList = wp.media.view.PriorityList,
     5691        Menu;
    57065692
    5707                 // after creating the wrapper view, load the actual editor via an ajax call
    5708                 view.loadEditor();
     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',
    57095710
     5711        /* TODO: alternatively hide on any click anywhere
     5712        events: {
     5713                'click': 'click'
    57105714        },
    57115715
    5712         // Toolbars
     5716        click: function() {
     5717                this.$el.removeClass( 'visible' );
     5718        },
     5719        */
    57135720
    57145721        /**
    5715          * @param {wp.Backbone.View} view
     5722         * @param {Object} options
     5723         * @param {string} id
     5724         * @returns {wp.media.View}
    57165725         */
    5717         selectionStatusToolbar: function( view ) {
    5718                 var editable = this.state().get('editable');
     5726        toView: function( options, id ) {
     5727                options = options || {};
     5728                options[ this.property ] = options[ this.property ] || id;
     5729                return new this.ItemView( options ).render();
     5730        },
    57195731
    5720                 view.set( 'selection', new wp.media.view.Selection({
    5721                         controller: this,
    5722                         collection: this.state().get('selection'),
    5723                         priority:   -40,
     5732        ready: function() {
     5733                /**
     5734                 * call 'ready' directly on the parent class
     5735                 */
     5736                PriorityList.prototype.ready.apply( this, arguments );
     5737                this.visibility();
     5738        },
    57245739
    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() );
     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();
    57315754        },
    57325755
     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        },
    57335766        /**
    5734          * @param {wp.Backbone.View} view
     5767         * @param {string} id
    57355768         */
    5736         mainInsertToolbar: function( view ) {
    5737                 var controller = this;
     5769        select: function( id ) {
     5770                var view = this.get( id );
    57385771
    5739                 this.selectionStatusToolbar( view );
     5772                if ( ! view ) {
     5773                        return;
     5774                }
    57405775
    5741                 view.set( 'insert', {
    5742                         style:    'primary',
    5743                         priority: 80,
    5744                         text:     l10n.insertIntoPost,
    5745                         requires: { selection: true },
     5776                this.deselect();
     5777                view.$el.addClass('active');
     5778        },
    57465779
    5747                         /**
    5748                          * @callback
    5749                          * @fires wp.media.controller.State#insert
    5750                          */
    5751                         click: function() {
    5752                                 var state = controller.state(),
    5753                                         selection = state.get('selection');
     5780        deselect: function() {
     5781                this.$el.children().removeClass('active');
     5782        },
    57545783
    5755                                 controller.close();
    5756                                 state.trigger( 'insert', selection ).reset();
    5757                         }
    5758                 });
     5784        hide: function( id ) {
     5785                var view = this.get( id );
     5786
     5787                if ( ! view ) {
     5788                        return;
     5789                }
     5790
     5791                view.$el.addClass('hidden');
    57595792        },
    57605793
     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 */{
    57615824        /**
    5762          * @param {wp.Backbone.View} view
     5825         * On click handler to activate the content region's corresponding mode.
    57635826         */
    5764         mainGalleryToolbar: function( view ) {
    5765                 var controller = this;
     5827        click: function() {
     5828                var contentMode = this.options.contentMode;
     5829                if ( contentMode ) {
     5830                        this.controller.content.mode( contentMode );
     5831                }
     5832        }
     5833});
    57665834
    5767                 this.selectionStatusToolbar( view );
     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',
    57685863
    5769                 view.set( 'gallery', {
    5770                         style:    'primary',
    5771                         text:     l10n.createNewGallery,
    5772                         priority: 60,
    5773                         requires: { selection: true },
     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        },
    57745869
    5775                         click: function() {
    5776                                 var selection = controller.state().get('selection'),
    5777                                         edit = controller.state('gallery-edit'),
    5778                                         models = selection.where({ type: 'image' });
     5870        update: function() {
     5871                var mode = this.controller.content.mode();
     5872                if ( mode ) {
     5873                        this.select( mode );
     5874                }
     5875        }
     5876});
    57795877
    5780                                 edit.set( 'library', new wp.media.model.Selection( models, {
    5781                                         props:    selection.props.toJSON(),
    5782                                         multiple: true
    5783                                 }) );
     5878module.exports = Router;
    57845879
    5785                                 this.controller.setState('gallery-edit');
    57865880
    5787                                 // Keep focus inside media modal
    5788                                 // after jumping to gallery view
    5789                                 this.controller.modal.focusManager.focus();
    5790                         }
    5791                 });
    5792         },
     5881/***/ }),
     5882/* 68 */
     5883/***/ (function(module, exports) {
    57935884
    5794         mainPlaylistToolbar: function( view ) {
    5795                 var controller = this;
     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});
    57965899
    5797                 this.selectionStatusToolbar( view );
     5900module.exports = Sidebar;
    57985901
    5799                 view.set( 'playlist', {
    5800                         style:    'primary',
    5801                         text:     l10n.createNewPlaylist,
    5802                         priority: 100,
    5803                         requires: { selection: true },
    58045902
    5805                         click: function() {
    5806                                 var selection = controller.state().get('selection'),
    5807                                         edit = controller.state('playlist-edit'),
    5808                                         models = selection.where({ type: 'audio' });
     5903/***/ }),
     5904/* 69 */
     5905/***/ (function(module, exports) {
    58095906
    5810                                 edit.set( 'library', new wp.media.model.Selection( models, {
    5811                                         props:    selection.props.toJSON(),
    5812                                         multiple: true
    5813                                 }) );
     5907var View = wp.media.View,
     5908        $ = jQuery,
     5909        Attachment;
    58145910
    5815                                 this.controller.setState('playlist-edit');
     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'),
    58165925
    5817                                 // Keep focus inside media modal
    5818                                 // after jumping to playlist view
    5819                                 this.controller.modal.focusManager.focus();
    5820                         }
    5821                 });
     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                };
    58225934        },
    58235935
    5824         mainVideoPlaylistToolbar: function( view ) {
    5825                 var controller = this;
    5826 
    5827                 this.selectionStatusToolbar( view );
     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        },
    58285946
    5829                 view.set( 'video-playlist', {
    5830                         style:    'primary',
    5831                         text:     l10n.createNewVideoPlaylist,
    5832                         priority: 100,
    5833                         requires: { selection: true },
     5947        buttons: {},
    58345948
    5835                         click: function() {
    5836                                 var selection = controller.state().get('selection'),
    5837                                         edit = controller.state('video-playlist-edit'),
    5838                                         models = selection.where({ type: 'video' });
     5949        initialize: function() {
     5950                var selection = this.options.selection,
     5951                        options = _.defaults( this.options, {
     5952                                rerenderOnModelChange: true
     5953                        } );
    58395954
    5840                                 edit.set( 'library', new wp.media.model.Selection( models, {
    5841                                         props:    selection.props.toJSON(),
    5842                                         multiple: true
    5843                                 }) );
     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 );
    58445964
    5845                                 this.controller.setState('video-playlist-edit');
     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                }
    58465974
    5847                                 // Keep focus inside media modal
    5848                                 // after jumping to video playlist view
    5849                                 this.controller.modal.focusManager.focus();
    5850                         }
    5851                 });
     5975                this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
    58525976        },
     5977        /**
     5978         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     5979         */
     5980        dispose: function() {
     5981                var selection = this.options.selection;
    58535982
    5854         featuredImageToolbar: function( toolbar ) {
    5855                 this.createSelectToolbar( toolbar, {
    5856                         text:  l10n.setFeaturedImage,
    5857                         state: this.options.state
    5858                 });
    5859         },
     5983                // Make sure all settings are saved before removing the view.
     5984                this.updateAll();
    58605985
    5861         mainEmbedToolbar: function( toolbar ) {
    5862                 toolbar.view = new wp.media.view.Toolbar.Embed({
    5863                         controller: this
    5864                 });
     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;
    58655994        },
     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 );
    58666015
    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 },
     6016                options.buttons  = this.buttons;
     6017                options.describe = this.controller.state().get('describe');
    58776018
    5878                                         /**
    5879                                          * @fires wp.media.controller.State#update
    5880                                          */
    5881                                         click: function() {
    5882                                                 var controller = this.controller,
    5883                                                         state = controller.state();
     6019                if ( 'image' === options.type ) {
     6020                        options.size = this.imageSize();
     6021                }
    58846022
    5885                                                 controller.close();
    5886                                                 state.trigger( 'update', state.get('library') );
     6023                options.can = {};
     6024                if ( options.nonces ) {
     6025                        options.can.remove = !! options.nonces['delete'];
     6026                        options.can.save = !! options.nonces.update;
     6027                }
    58876028
    5888                                                 // Restore and reset the default state.
    5889                                                 controller.setState( controller.options.state );
    5890                                                 controller.reset();
    5891                                         }
    5892                                 }
    5893                         }
    5894                 }) );
    5895         },
     6029                if ( this.controller.state().get('allowLocalEdits') ) {
     6030                        options.allowLocalEdits = true;
     6031                }
    58966032
    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 },
     6033                if ( options.uploading && ! options.percent ) {
     6034                        options.percent = 0;
     6035                }
    59066036
    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');
     6037                this.views.detach();
     6038                this.$el.html( this.template( options ) );
    59146039
    5915                                                 edit.get('library').add( state.get('selection').models );
    5916                                                 state.trigger('reset');
    5917                                                 controller.setState('gallery-edit');
    5918                                         }
    5919                                 }
    5920                         }
    5921                 }) );
    5922         },
     6040                this.$el.toggleClass( 'uploading', options.uploading );
    59236041
    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 },
     6042                if ( options.uploading ) {
     6043                        this.$bar = this.$('.media-progress-bar div');
     6044                } else {
     6045                        delete this.$bar;
     6046                }
    59346047
    5935                                         /**
    5936                                          * @fires wp.media.controller.State#update
    5937                                          */
    5938                                         click: function() {
    5939                                                 var controller = this.controller,
    5940                                                         state = controller.state();
     6048                // Check if the model is selected.
     6049                this.updateSelect();
    59416050
    5942                                                 controller.close();
    5943                                                 state.trigger( 'update', state.get('library') );
     6051                // Update the save status.
     6052                this.updateSave();
    59446053
    5945                                                 // Restore and reset the default state.
    5946                                                 controller.setState( controller.options.state );
    5947                                                 controller.reset();
    5948                                         }
    5949                                 }
    5950                         }
    5951                 }) );
     6054                this.views.render();
     6055
     6056                return this;
    59526057        },
    59536058
    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 },
     6059        progress: function() {
     6060                if ( this.$bar && this.$bar.length ) {
     6061                        this.$bar.width( this.model.get('percent') + '%' );
     6062                }
     6063        },
    59636064
    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');
     6065        /**
     6066         * @param {Object} event
     6067         */
     6068        toggleSelectionHandler: function( event ) {
     6069                var method;
    59716070
    5972                                                 edit.get('library').add( state.get('selection').models );
    5973                                                 state.trigger('reset');
    5974                                                 controller.setState('playlist-edit');
    5975                                         }
    5976                                 }
    5977                         }
    5978                 }) );
    5979         },
     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                }
    59806075
    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 },
     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                }
    59916081
    5992                                         click: function() {
    5993                                                 var controller = this.controller,
    5994                                                         state = controller.state(),
    5995                                                         library = state.get('library');
     6082                // Catch enter and space events
     6083                if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     6084                        return;
     6085                }
    59966086
    5997                                                 library.type = 'video';
     6087                event.preventDefault();
    59986088
    5999                                                 controller.close();
    6000                                                 state.trigger( 'update', library );
     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                        }
    60016096
    6002                                                 // Restore and reset the default state.
    6003                                                 controller.setState( controller.options.state );
    6004                                                 controller.reset();
    6005                                         }
    6006                                 }
     6097                        if ( this.controller.isModeActive( 'select' ) ) {
     6098                                method = 'toggle';
    60076099                        }
    6008                 }) );
     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' );
    60096113        },
     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;
    60106123
    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 },
     6124                if ( ! selection ) {
     6125                        return;
     6126                }
    60206127
    6021                                         click: function() {
    6022                                                 var controller = this.controller,
    6023                                                         state = controller.state(),
    6024                                                         edit = controller.state('video-playlist-edit');
     6128                single = selection.single();
     6129                method = _.isUndefined( method ) ? selection.multiple : method;
    60256130
    6026                                                 edit.get('library').add( state.get('selection').models );
    6027                                                 state.trigger('reset');
    6028                                                 controller.setState('video-playlist-edit');
    6029                                         }
    6030                                 }
     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;
    60316137                        }
    6032                 }) );
    6033         }
    6034 });
    60356138
    6036 module.exports = Post;
     6139                        singleIndex = collection.indexOf( single );
     6140                        modelIndex  = collection.indexOf( this.model );
    60376141
    6038 },{}],46:[function(require,module,exports){
    6039 var MediaFrame = wp.media.view.MediaFrame,
    6040         l10n = wp.media.view.l10n,
    6041         Select;
     6142                        if ( singleIndex < modelIndex ) {
     6143                                models = collection.models.slice( singleIndex, modelIndex + 1 );
     6144                        } else {
     6145                                models = collection.models.slice( modelIndex, singleIndex + 1 );
     6146                        }
    60426147
    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 );
     6148                        selection.add( models );
     6149                        selection.single( model );
     6150                        return;
    60626151
    6063                 _.defaults( this.options, {
    6064                         selection: [],
    6065                         library:   {},
    6066                         multiple:  false,
    6067                         state:    'library'
    6068                 });
     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                }
    60696163
    6070                 this.createSelection();
    6071                 this.createStates();
    6072                 this.bindHandlers();
     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                }
    60736185        },
    60746186
     6187        updateSelect: function() {
     6188                this[ this.selected() ? 'select' : 'deselect' ]();
     6189        },
    60756190        /**
    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
     6191         * @returns {unresolved|Boolean}
    60836192         */
    6084         createSelection: function() {
     6193        selected: function() {
    60856194                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                         });
     6195                if ( selection ) {
     6196                        return !! selection.get( this.model.cid );
    60916197                }
    6092 
    6093                 this._selection = {
    6094                         attachments: new wp.media.model.Attachments(),
    6095                         difference: []
    6096                 };
    60976198        },
    6098 
    60996199        /**
    6100          * Create the default states on the frame.
     6200         * @param {Backbone.Model} model
     6201         * @param {Backbone.Collection} collection
    61016202         */
    6102         createStates: function() {
    6103                 var options = this.options;
     6203        select: function( model, collection ) {
     6204                var selection = this.options.selection,
     6205                        controller = this.controller;
    61046206
    6105                 if ( this.options.states ) {
     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 ) ) {
    61066211                        return;
    61076212                }
    61086213
    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         },
     6214                // Bail if the model is already selected.
     6215                if ( this.$el.hasClass( 'selected' ) ) {
     6216                        return;
     6217                }
    61206218
    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 );
     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                }
    61326225        },
    6133 
    61346226        /**
    6135          * Render callback for the router region in the `browse` mode.
    6136          *
    6137          * @param {wp.media.view.Router} routerView
     6227         * @param {Backbone.Model} model
     6228         * @param {Backbone.Collection} collection
    61386229         */
    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         },
     6230        deselect: function( model, collection ) {
     6231                var selection = this.options.selection;
    61516232
     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        },
    61526242        /**
    6153          * Render callback for the content region in the `browse` mode.
    6154          *
    6155          * @param {wp.media.controller.Region} contentRegion
     6243         * @param {Backbone.Model} model
     6244         * @param {Backbone.Collection} collection
    61566245         */
    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'),
     6246        details: function( model, collection ) {
     6247                var selection = this.options.selection,
     6248                        details;
    61746249
    6175                         idealColumnWidth: state.get('idealColumnWidth'),
    6176                         suggestedWidth:   state.get('suggestedWidth'),
    6177                         suggestedHeight:  state.get('suggestedHeight'),
     6250                if ( selection !== collection ) {
     6251                        return;
     6252                }
    61786253
    6179                         AttachmentView: state.get('AttachmentView')
    6180                 });
     6254                details = selection.single();
     6255                this.$el.toggleClass( 'details', details === this.model );
    61816256        },
    6182 
    61836257        /**
    6184          * Render callback for the content region in the `upload` mode.
     6258         * @param {string} size
     6259         * @returns {Object}
    61856260         */
    6186         uploadContent: function() {
    6187                 this.$el.removeClass( 'hide-toolbar' );
    6188                 this.content.set( new wp.media.view.UploaderInline({
    6189                         controller: this
    6190                 }) );
    6191         },
     6261        imageSize: function( size ) {
     6262                var sizes = this.model.get('sizes'), matched = false;
    61926263
    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;
     6264                size = size || 'medium';
    62036265
    6204                 toolbar.view = new wp.media.view.Toolbar.Select( options );
    6205         }
    6206 });
     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                        }
    62076277
    6208 module.exports = Select;
     6278                        if ( matched ) {
     6279                                return _.clone( matched );
     6280                        }
     6281                }
    62096282
    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',
     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        },
    62236290        /**
    6224          * @returns {wp.media.view.Iframe} Returns itself to allow chaining
     6291         * @param {Object} event
    62256292         */
    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){
    6237 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    6238         $ = jQuery,
    6239         ImageDetails;
    6240 
    6241 /**
    6242  * wp.media.view.ImageDetails
    6243  *
    6244  * @memberOf wp.media.view
    6245  *
    6246  * @class
    6247  * @augments wp.media.view.Settings.AttachmentDisplay
    6248  * @augments wp.media.view.Settings
    6249  * @augments wp.media.View
    6250  * @augments wp.Backbone.View
    6251  * @augments Backbone.View
    6252  */
    6253 ImageDetails = AttachmentDisplay.extend(/** @lends wp.media.view.ImageDetails.prototype */{
    6254         className: 'image-details',
    6255         template:  wp.template('image-details'),
    6256         events: _.defaults( AttachmentDisplay.prototype.events, {
    6257                 'click .edit-attachment': 'editAttachment',
    6258                 'click .replace-attachment': 'replaceAttachment',
    6259                 'click .advanced-toggle': 'onToggleAdvanced',
    6260                 'change [data-setting="customWidth"]': 'onCustomSize',
    6261                 'change [data-setting="customHeight"]': 'onCustomSize',
    6262                 'keyup [data-setting="customWidth"]': 'onCustomSize',
    6263                 'keyup [data-setting="customHeight"]': 'onCustomSize'
    6264         } ),
    6265         initialize: function() {
    6266                 // used in AttachmentDisplay.prototype.updateLinkTo
    6267                 this.options.attachment = this.model.attachment;
    6268                 this.listenTo( this.model, 'change:url', this.updateUrl );
    6269                 this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
    6270                 this.listenTo( this.model, 'change:size', this.toggleCustomSize );
     6293        updateSetting: function( event ) {
     6294                var $setting = $( event.target ).closest('[data-setting]'),
     6295                        setting, value;
    62716296
    6272                 AttachmentDisplay.prototype.initialize.apply( this, arguments );
    6273         },
     6297                if ( ! $setting.length ) {
     6298                        return;
     6299                }
    62746300
    6275         prepare: function() {
    6276                 var attachment = false;
     6301                setting = $setting.data('setting');
     6302                value   = event.target.value;
    62776303
    6278                 if ( this.model.attachment ) {
    6279                         attachment = this.model.attachment.toJSON();
     6304                if ( this.model.get( setting ) !== value ) {
     6305                        this.save( setting, value );
    62806306                }
    6281                 return _.defaults({
    6282                         model: this.model.toJSON(),
    6283                         attachment: attachment
    6284                 }, this.options );
    62856307        },
    62866308
    6287         render: function() {
    6288                 var args = arguments;
     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;
    62896320
    6290                 if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
    6291                         this.model.dfd
    6292                                 .done( _.bind( function() {
    6293                                         AttachmentDisplay.prototype.render.apply( this, args );
    6294                                         this.postRender();
    6295                                 }, this ) )
    6296                                 .fail( _.bind( function() {
    6297                                         this.model.attachment = false;
    6298                                         AttachmentDisplay.prototype.render.apply( this, args );
    6299                                         this.postRender();
    6300                                 }, this ) );
    6301                 } else {
    6302                         AttachmentDisplay.prototype.render.apply( this, arguments );
    6303                         this.postRender();
     6321                // If we're waiting to remove 'Saved.', stop.
     6322                if ( save.savedTimer ) {
     6323                        clearTimeout( save.savedTimer );
    63046324                }
    63056325
    6306                 return this;
     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                });
    63076340        },
     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' };
    63086347
    6309         postRender: function() {
    6310                 setTimeout( _.bind( this.resetFocus, this ), 10 );
    6311                 this.toggleLinkSettings();
    6312                 if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
    6313                         this.toggleAdvanced( true );
     6348                if ( status && status !== save.status ) {
     6349                        this.$el.removeClass( 'save-' + save.status );
     6350                        save.status = status;
    63146351                }
    6315                 this.trigger( 'post-render' );
    6316         },
    63176352
    6318         resetFocus: function() {
    6319                 this.$( '.link-to-custom' ).blur();
    6320                 this.$( '.embed-media-settings' ).scrollTop( 0 );
     6353                this.$el.addClass( 'save-' + save.status );
     6354                return this;
    63216355        },
    63226356
    6323         updateUrl: function() {
    6324                 this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
    6325                 this.$( '.url' ).val( this.model.get( 'url' ) );
    6326         },
     6357        updateAll: function() {
     6358                var $settings = this.$('[data-setting]'),
     6359                        model = this.model,
     6360                        changed;
    63276361
    6328         toggleLinkSettings: function() {
    6329                 if ( this.model.get( 'link' ) === 'none' ) {
    6330                         this.$( '.link-settings' ).addClass('hidden');
    6331                 } else {
    6332                         this.$( '.link-settings' ).removeClass('hidden');
    6333                 }
    6334         },
     6362                changed = _.chain( $settings ).map( function( el ) {
     6363                        var $input = $('input, textarea, select, [value]', el ),
     6364                                setting, value;
    63356365
    6336         toggleCustomSize: function() {
    6337                 if ( this.model.get( 'size' ) !== 'custom' ) {
    6338                         this.$( '.custom-size' ).addClass('hidden');
    6339                 } else {
    6340                         this.$( '.custom-size' ).removeClass('hidden');
     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 );
    63416381                }
    63426382        },
     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                }
    63436391
    6344         onCustomSize: function( event ) {
    6345                 var dimension = $( event.target ).data('setting'),
    6346                         num = $( event.target ).val(),
    6347                         value;
     6392                // Stop propagation so the model isn't selected.
     6393                event.stopPropagation();
    63486394
    6349                 // Ignore bogus input
    6350                 if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
    6351                         event.preventDefault();
     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 ) {
    63526408                        return;
    63536409                }
    6354 
    6355                 if ( dimension === 'customWidth' ) {
    6356                         value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );
    6357                         this.model.set( 'customHeight', value, { silent: true } );
    6358                         this.$( '[data-setting="customHeight"]' ).val( value );
     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();
    63596415                } else {
    6360                         value = Math.round( this.model.get( 'aspectRatio' ) * num );
    6361                         this.model.set( 'customWidth', value, { silent: true  } );
    6362                         this.$( '[data-setting="customWidth"]' ).val( value );
     6416                        selection.add( this.model );
    63636417                }
    6364         },
     6418        }
     6419});
    63656420
    6366         onToggleAdvanced: function( event ) {
    6367                 event.preventDefault();
    6368                 this.toggleAdvanced();
    6369         },
     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 + '"]');
    63706466
    6371         toggleAdvanced: function( show ) {
    6372                 var $advanced = this.$el.find( '.advanced-section' ),
    6373                         mode;
     6467                if ( ! $setting.length ) {
     6468                        return this;
     6469                }
    63746470
    6375                 if ( $advanced.hasClass('advanced-visible') || show === false ) {
    6376                         $advanced.removeClass('advanced-visible');
    6377                         $advanced.find('.advanced-settings').addClass('hidden');
    6378                         mode = 'hide';
    6379                 } else {
    6380                         $advanced.addClass('advanced-visible');
    6381                         $advanced.find('.advanced-settings').removeClass('hidden');
    6382                         mode = 'show';
     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;
    63836477                }
    63846478
    6385                 window.setUserSetting( 'advImgDetails', mode );
    6386         },
     6479                return this.render();
     6480        };
     6481});
    63876482
    6388         editAttachment: function( event ) {
    6389                 var editState = this.controller.states.get( 'edit-image' );
     6483module.exports = Attachment;
    63906484
    6391                 if ( window.imageEdit && editState ) {
    6392                         event.preventDefault();
    6393                         editState.set( 'image', this.model.attachment );
    6394                         this.controller.setState( 'edit-image' );
    6395                 }
    6396         },
    63976485
    6398         replaceAttachment: function( event ) {
    6399                 event.preventDefault();
    6400                 this.controller.setState( 'replace-image' );
     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
    64016504        }
    64026505});
    64036506
    6404 module.exports = ImageDetails;
     6507module.exports = Library;
     6508
     6509
     6510/***/ }),
     6511/* 71 */
     6512/***/ (function(module, exports) {
    64056513
    6406 },{}],49:[function(require,module,exports){
    64076514/**
    6408  * wp.media.view.Label
     6515 * wp.media.view.Attachment.EditLibrary
    64096516 *
    6410  * @memberOf wp.media.view
     6517 * @memberOf wp.media.view.Attachment
    64116518 *
    64126519 * @class
     6520 * @augments wp.media.view.Attachment
    64136521 * @augments wp.media.View
    64146522 * @augments wp.Backbone.View
    64156523 * @augments Backbone.View
    64166524 */
    6417 var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{
    6418         tagName: 'label',
    6419         className: 'screen-reader-text',
    6420 
    6421         initialize: function() {
    6422                 this.value = this.options.value;
    6423         },
    6424 
    6425         render: function() {
    6426                 this.$el.html( this.value );
    6427 
    6428                 return this;
     6525var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{
     6526        buttons: {
     6527                close: true
    64296528        }
    64306529});
    64316530
    6432 module.exports = Label;
     6531module.exports = EditLibrary;
    64336532
    6434 },{}],50:[function(require,module,exports){
    6435 var Frame = wp.media.view.Frame,
     6533
     6534/***/ }),
     6535/* 72 */
     6536/***/ (function(module, exports) {
     6537
     6538var View = wp.media.View,
    64366539        $ = jQuery,
    6437         MediaFrame;
     6540        Attachments;
    64386541
    64396542/**
    6440  * wp.media.view.MediaFrame
    6441  *
    6442  * The frame used to create the media modal.
     6543 * wp.media.view.Attachments
    64436544 *
    64446545 * @memberOf wp.media.view
    64456546 *
    64466547 * @class
    6447  * @augments wp.media.view.Frame
    64486548 * @augments wp.media.View
    64496549 * @augments wp.Backbone.View
    64506550 * @augments Backbone.View
    6451  * @mixes wp.media.controller.StateMachine
    64526551 */
    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'],
     6552Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
     6553        tagName:   'ul',
     6554        className: 'attachments',
    64576555
    6458         events: {
    6459                 'click div.media-frame-title h1': 'toggleMenu'
     6556        attributes: {
     6557                tabIndex: -1
    64606558        },
    64616559
    6462         /**
    6463          * @constructs
    6464          */
    64656560        initialize: function() {
    6466                 Frame.prototype.initialize.apply( this, arguments );
     6561                this.el.id = _.uniqueId('__attachments-view-');
    64676562
    64686563                _.defaults( this.options, {
    6469                         title:    '',
    6470                         modal:    true,
    6471                         uploader: true
     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
    64726570                });
    64736571
    6474                 // Ensure core UI is enabled.
    6475                 this.$el.addClass('wp-core-ui');
     6572                this._viewsByCid = {};
     6573                this.$window = $( window );
     6574                this.resizeEvent = 'resize.media-modal-columns';
    64766575
    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
     6576                this.collection.on( 'add', function( attachment ) {
     6577                        this.views.add( this.createAttachmentView( attachment ), {
     6578                                at: this.collection.indexOf( attachment )
    64826579                        });
     6580                }, this );
    64836581
    6484                         this.modal.content( this );
     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 );
    64856612                }
     6613        },
    64866614
    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;
     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;
    64916635                }
    64926636
    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 );
     6637                // Left arrow
     6638                if ( 37 === event.keyCode ) {
     6639                        if ( 0 === index ) {
     6640                                return;
     6641                        }
     6642                        attachments.eq( index - 1 ).focus();
    65036643                }
    65046644
    6505                 this.on( 'attach', _.bind( this.views.ready, this.views ), this );
     6645                // Up arrow
     6646                if ( 38 === event.keyCode ) {
     6647                        if ( 1 === row ) {
     6648                                return;
     6649                        }
     6650                        attachments.eq( index - perRow ).focus();
     6651                }
    65066652
    6507                 // Bind default title creation.
    6508                 this.on( 'title:create:default', this.createTitle, this );
    6509                 this.title.mode('default');
     6653                // Right arrow
     6654                if ( 39 === event.keyCode ) {
     6655                        if ( attachments.length === index ) {
     6656                                return;
     6657                        }
     6658                        attachments.eq( index + 1 ).focus();
     6659                }
    65106660
    6511                 this.on( 'title:render', function( view ) {
    6512                         view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
    6513                 });
     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 );
    65146739
    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         },
     6740                                // If the collection is sorted by menu order,
     6741                                // update the menu order.
     6742                                collection.saveMenuOrder();
     6743                        }
     6744                }, this.options.sortable ) );
    65506745
    6551         toggleMenu: function() {
    6552                 this.$el.find( '.media-menu' ).toggleClass( 'visible' );
    6553         },
     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 );
    65546751
    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                 });
     6752                this.collection.props.on( 'change:orderby', this.refreshSortable, this );
     6753                this.refreshSortable();
    65726754        },
    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;
    65816755
    6582                 if ( ! tabs || ! tabUrl ) {
     6756        refreshSortable: function() {
     6757                if ( ! this.options.sortable || ! $.fn.sortable ) {
    65836758                        return;
    65846759                }
    65856760
    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 );
     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;
    66026765
    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 );
     6766                this.$el.sortable( 'option', 'disabled', ! enabled );
    66086767        },
    66096768
    66106769        /**
    6611          * @param {Object} content
    6612          * @this wp.media.controller.Region
     6770         * @param {wp.media.model.Attachment} attachment
     6771         * @returns {wp.media.View}
    66136772         */
    6614         iframeContent: function( content ) {
    6615                 this.$el.addClass('hide-toolbar');
    6616                 content.view = new wp.media.view.Iframe({
    6617                         controller: this
     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
    66186779                });
    6619         },
    66206780
    6621         iframeContentCleanup: function() {
    6622                 this.$el.removeClass('hide-toolbar');
     6781                return this._viewsByCid[ attachment.cid ] = view;
    66236782        },
    66246783
    6625         iframeMenu: function( view ) {
    6626                 var views = {};
     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 ) );
    66276789
    6628                 if ( ! view ) {
    6629                         return;
     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 );
    66306794                }
     6795        },
    66316796
    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 );
     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();
    66406801        },
    66416802
    6642         hijackThickbox: function() {
    6643                 var frame = this;
     6803        scroll: function() {
     6804                var view = this,
     6805                        el = this.options.scrollElement,
     6806                        scrollTop = el.scrollTop,
     6807                        toolbar;
    66446808
    6645                 if ( ! window.tb_remove || this._tb_remove ) {
     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() ) {
    66466817                        return;
    66476818                }
    66486819
    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         },
     6820                toolbar = this.views.parent.toolbar;
    66576821
    6658         restoreThickbox: function() {
    6659                 if ( ! this._tb_remove ) {
    6660                         return;
     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();
    66616825                }
    66626826
    6663                 window.tb_remove = this._tb_remove;
    6664                 delete this._tb_remove;
     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                }
    66656833        }
    66666834});
    66676835
    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
     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
    67046873         */
    6705         MediaFrame.prototype[ method ] = function() {
    6706                 if ( this.modal ) {
    6707                         this.modal[ method ].apply( this.modal, arguments );
    6708                 }
     6874        render: function() {
     6875                this.el.value = this.model.escape('search');
    67096876                return this;
    6710         };
     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 )
    67116886});
    67126887
    6713 module.exports = MediaFrame;
     6888module.exports = Search;
     6889
     6890
     6891/***/ }),
     6892/* 74 */
     6893/***/ (function(module, exports) {
    67146894
    6715 },{}],51:[function(require,module,exports){
    67166895var $ = jQuery,
    6717         MenuItem;
     6896        AttachmentFilters;
    67186897
    67196898/**
    6720  * wp.media.view.MenuItem
     6899 * wp.media.view.AttachmentFilters
    67216900 *
    67226901 * @memberOf wp.media.view
    67236902 *
    var $ = jQuery, 
    67266905 * @augments wp.Backbone.View
    67276906 * @augments Backbone.View
    67286907 */
    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         },
     6908AttachmentFilters = wp.media.View.extend(/** @lends wp.media.view.AttachmentFilters.prototype */{
     6909        tagName:   'select',
     6910        className: 'attachment-filters',
     6911        id:        'media-attachment-filters',
    67366912
    67376913        events: {
    6738                 'click': '_click'
     6914                change: 'change'
    67396915        },
    6740         /**
    6741          * @param {Object} event
    6742          */
    6743         _click: function( event ) {
    6744                 var clickOverride = this.options.click;
    6745 
    6746                 if ( event ) {
    6747                         event.preventDefault();
    6748                 }
    67496916
    6750                 if ( clickOverride ) {
    6751                         clickOverride.call( this );
    6752                 } else {
    6753                         this.click();
    6754                 }
     6917        keys: [],
    67556918
    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         },
     6919        initialize: function() {
     6920                this.createFilters();
     6921                _.extend( this.filters, this.options.filters );
    67626922
    6763         click: function() {
    6764                 var state = this.options.state;
     6923                // Build `<option>` elements.
     6924                this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
     6925                        return {
     6926                                el: $( '<option></option>' ).val( value ).html( filter.text )[0],
     6927                                priority: filter.priority || 50
     6928                        };
     6929                }, this ).sortBy('priority').pluck('el').value() );
    67656930
    6766                 if ( state ) {
    6767                         this.controller.setState( state );
    6768                         this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
    6769                 }
     6931                this.listenTo( this.model, 'change', this.select );
     6932                this.select();
    67706933        },
     6934
    67716935        /**
    6772          * @returns {wp.media.view.MenuItem} returns itself to allow chaining
     6936         * @abstract
    67736937         */
    6774         render: function() {
    6775                 var options = this.options;
     6938        createFilters: function() {
     6939                this.filters = {};
     6940        },
    67766941
    6777                 if ( options.text ) {
    6778                         this.$el.text( options.text );
    6779                 } else if ( options.html ) {
    6780                         this.$el.html( options.html );
     6942        /**
     6943         * When the selected filter changes, update the Attachment Query properties to match.
     6944         */
     6945        change: function() {
     6946                var filter = this.filters[ this.el.value ];
     6947                if ( filter ) {
     6948                        this.model.set( filter.props );
    67816949                }
     6950        },
    67826951
    6783                 return this;
     6952        select: function() {
     6953                var model = this.model,
     6954                        value = 'all',
     6955                        props = model.toJSON();
     6956
     6957                _.find( this.filters, function( filter, id ) {
     6958                        var equal = _.all( filter.props, function( prop, key ) {
     6959                                return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
     6960                        });
     6961
     6962                        if ( equal ) {
     6963                                return value = id;
     6964                        }
     6965                });
     6966
     6967                this.$el.val( value );
    67846968        }
    67856969});
    67866970
    6787 module.exports = MenuItem;
     6971module.exports = AttachmentFilters;
    67886972
    6789 },{}],52:[function(require,module,exports){
    6790 var MenuItem = wp.media.view.MenuItem,
    6791         PriorityList = wp.media.view.PriorityList,
    6792         Menu;
     6973
     6974/***/ }),
     6975/* 75 */
     6976/***/ (function(module, exports) {
     6977
     6978var l10n = wp.media.view.l10n,
     6979        DateFilter;
    67936980
    67946981/**
    6795  * wp.media.view.Menu
     6982 * A filter dropdown for month/dates.
    67966983 *
    6797  * @memberOf wp.media.view
     6984 * @memberOf wp.media.view.AttachmentFilters
    67986985 *
    67996986 * @class
    6800  * @augments wp.media.view.PriorityList
     6987 * @augments wp.media.view.AttachmentFilters
    68016988 * @augments wp.media.View
    68026989 * @augments wp.Backbone.View
    68036990 * @augments Backbone.View
    68046991 */
    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',
     6992DateFilter = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Date.prototype */{
     6993        id: 'media-attachment-date-filters',
    68116994
    6812         /* TODO: alternatively hide on any click anywhere
    6813         events: {
    6814                 'click': 'click'
    6815         },
     6995        createFilters: function() {
     6996                var filters = {};
     6997                _.each( wp.media.view.settings.months || {}, function( value, index ) {
     6998                        filters[ index ] = {
     6999                                text: value.text,
     7000                                props: {
     7001                                        year: value.year,
     7002                                        monthnum: value.month
     7003                                }
     7004                        };
     7005                });
     7006                filters.all = {
     7007                        text:  l10n.allDates,
     7008                        props: {
     7009                                monthnum: false,
     7010                                year:  false
     7011                        },
     7012                        priority: 10
     7013                };
     7014                this.filters = filters;
     7015        }
     7016});
    68167017
    6817         click: function() {
    6818                 this.$el.removeClass( 'visible' );
    6819         },
    6820         */
     7018module.exports = DateFilter;
    68217019
    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         },
    68327020
    6833         ready: function() {
    6834                 /**
    6835                  * call 'ready' directly on the parent class
    6836                  */
    6837                 PriorityList.prototype.ready.apply( this, arguments );
    6838                 this.visibility();
    6839         },
     7021/***/ }),
     7022/* 76 */
     7023/***/ (function(module, exports) {
    68407024
    6841         set: function() {
    6842                 /**
    6843                  * call 'set' directly on the parent class
    6844                  */
    6845                 PriorityList.prototype.set.apply( this, arguments );
    6846                 this.visibility();
    6847         },
     7025var l10n = wp.media.view.l10n,
     7026        Uploaded;
    68487027
    6849         unset: function() {
    6850                 /**
    6851                  * call 'unset' directly on the parent class
    6852                  */
    6853                 PriorityList.prototype.unset.apply( this, arguments );
    6854                 this.visibility();
    6855         },
     7028/**
     7029 * wp.media.view.AttachmentFilters.Uploaded
     7030 *
     7031 * @memberOf wp.media.view.AttachmentFilters
     7032 *
     7033 * @class
     7034 * @augments wp.media.view.AttachmentFilters
     7035 * @augments wp.media.View
     7036 * @augments wp.Backbone.View
     7037 * @augments Backbone.View
     7038 */
     7039Uploaded = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Uploaded.prototype */{
     7040        createFilters: function() {
     7041                var type = this.model.get('type'),
     7042                        types = wp.media.view.settings.mimeTypes,
     7043                        text;
     7044
     7045                if ( types && type ) {
     7046                        text = types[ type ];
     7047                }
     7048
     7049                this.filters = {
     7050                        all: {
     7051                                text:  text || l10n.allMediaItems,
     7052                                props: {
     7053                                        uploadedTo: null,
     7054                                        orderby: 'date',
     7055                                        order:   'DESC'
     7056                                },
     7057                                priority: 10
     7058                        },
     7059
     7060                        uploaded: {
     7061                                text:  l10n.uploadedToThisPost,
     7062                                props: {
     7063                                        uploadedTo: wp.media.view.settings.post.id,
     7064                                        orderby: 'menuOrder',
     7065                                        order:   'ASC'
     7066                                },
     7067                                priority: 20
     7068                        },
     7069
     7070                        unattached: {
     7071                                text:  l10n.unattached,
     7072                                props: {
     7073                                        uploadedTo: 0,
     7074                                        orderby: 'menuOrder',
     7075                                        order:   'ASC'
     7076                                },
     7077                                priority: 50
     7078                        }
     7079                };
     7080        }
     7081});
    68567082
    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;
     7083module.exports = Uploaded;
    68627084
    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 );
    68727085
    6873                 if ( ! view ) {
    6874                         return;
    6875                 }
     7086/***/ }),
     7087/* 77 */
     7088/***/ (function(module, exports) {
    68767089
    6877                 this.deselect();
    6878                 view.$el.addClass('active');
    6879         },
     7090var l10n = wp.media.view.l10n,
     7091        All;
    68807092
    6881         deselect: function() {
    6882                 this.$el.children().removeClass('active');
    6883         },
     7093/**
     7094 * wp.media.view.AttachmentFilters.All
     7095 *
     7096 * @memberOf wp.media.view.AttachmentFilters
     7097 *
     7098 * @class
     7099 * @augments wp.media.view.AttachmentFilters
     7100 * @augments wp.media.View
     7101 * @augments wp.Backbone.View
     7102 * @augments Backbone.View
     7103 */
     7104All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{
     7105        createFilters: function() {
     7106                var filters = {};
    68847107
    6885         hide: function( id ) {
    6886                 var view = this.get( id );
     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                });
    68877120
    6888                 if ( ! view ) {
    6889                         return;
     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
     7131                };
     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                        };
    68907145                }
    68917146
    6892                 view.$el.addClass('hidden');
    6893         },
     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
     7157                };
    68947158
    6895         show: function( id ) {
    6896                 var view = this.get( id );
     7159                if ( wp.media.view.settings.mediaTrash &&
     7160                        this.controller.isModeActive( 'grid' ) ) {
    68977161
    6898                 if ( ! view ) {
    6899                         return;
     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                        };
    69007173                }
    69017174
    6902                 view.$el.removeClass('hidden');
     7175                this.filters = filters;
    69037176        }
    69047177});
    69057178
    6906 module.exports = Menu;
     7179module.exports = All;
    69077180
    6908 },{}],53:[function(require,module,exports){
    6909 var $ = jQuery,
    6910         Modal;
     7181
     7182/***/ }),
     7183/* 78 */
     7184/***/ (function(module, exports) {
     7185
     7186var View = wp.media.View,
     7187        mediaTrash = wp.media.view.settings.mediaTrash,
     7188        l10n = wp.media.view.l10n,
     7189        $ = jQuery,
     7190        AttachmentsBrowser;
    69117191
    69127192/**
    6913  * wp.media.view.Modal
    6914  *
    6915  * A modal view, which the media modal uses as its default container.
     7193 * wp.media.view.AttachmentsBrowser
    69167194 *
    69177195 * @memberOf wp.media.view
    69187196 *
    var $ = jQuery, 
    69207198 * @augments wp.media.View
    69217199 * @augments wp.Backbone.View
    69227200 * @augments Backbone.View
     7201 *
     7202 * @param {object}         [options]               The options hash passed to the view.
     7203 * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar.
     7204 *                                                 Accepts 'uploaded' and 'all'.
     7205 * @param {boolean}        [options.search=true]   Whether to show the search interface in the
     7206 *                                                 browser's toolbar.
     7207 * @param {boolean}        [options.date=true]     Whether to show the date filter in the
     7208 *                                                 browser's toolbar.
     7209 * @param {boolean}        [options.display=false] Whether to show the attachments display settings
     7210 *                                                 view in the sidebar.
     7211 * @param {boolean|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
     7212 *                                                 Accepts true, false, and 'errors'.
    69237213 */
    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,
     7214AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{
     7215        tagName:   'div',
     7216        className: 'attachments-browser',
    69387217
    69397218        initialize: function() {
    69407219                _.defaults( this.options, {
    6941                         container: document.body,
    6942                         title:     '',
    6943                         propagate: true,
    6944                         freeze:    true
     7220                        filters: false,
     7221                        search:  true,
     7222                        date:    true,
     7223                        display: false,
     7224                        sidebar: true,
     7225                        AttachmentView: wp.media.view.Attachment.Library
    69457226                });
    69467227
    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         },
     7228                this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this );
     7229                this.controller.on( 'edit:selection', this.editSelection );
    69597230
    6960         /**
    6961          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6962          */
    6963         attach: function() {
    6964                 if ( this.views.attached ) {
    6965                         return this;
     7231                // In the Media Library, the sidebar is used to display errors before the attachments grid.
     7232                if ( this.options.sidebar && 'errors' === this.options.sidebar ) {
     7233                        this.createSidebar();
    69667234                }
    69677235
    6968                 if ( ! this.views.rendered ) {
    6969                         this.render();
     7236                /*
     7237                 * For accessibility reasons, place the Inline Uploader before other sections.
     7238                 * This way, in the Media Library, it's right after the Add New button, see ticket #37188.
     7239                 */
     7240                this.createUploader();
     7241
     7242                /*
     7243                 * Create a multi-purpose toolbar. Used as main toolbar in the Media Library
     7244                 * and also for other things, for example the "Drag and drop to reorder" and
     7245                 * "Suggested dimensions" info in the media modal.
     7246                 */
     7247                this.createToolbar();
     7248
     7249                // Create the list of attachments.
     7250                this.createAttachments();
     7251
     7252                // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909.
     7253                if ( this.options.sidebar && 'errors' !== this.options.sidebar ) {
     7254                        this.createSidebar();
    69707255                }
    69717256
    6972                 this.$el.appendTo( this.options.container );
     7257                this.updateContent();
    69737258
    6974                 // Manually mark the view as attached and trigger ready.
    6975                 this.views.attached = true;
    6976                 this.views.ready();
     7259                if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
     7260                        this.$el.addClass( 'hide-sidebar' );
    69777261
    6978                 return this.propagate('attach');
     7262                        if ( 'errors' === this.options.sidebar ) {
     7263                                this.$el.addClass( 'sidebar-for-errors' );
     7264                        }
     7265                }
     7266
     7267                this.collection.on( 'add remove reset', this.updateContent, this );
     7268        },
     7269
     7270        editSelection: function( modal ) {
     7271                modal.$( '.media-button-backToLibrary' ).focus();
    69797272        },
    69807273
    69817274        /**
    6982          * @returns {wp.media.view.Modal} Returns itself to allow chaining
     7275         * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
    69837276         */
    6984         detach: function() {
    6985                 if ( this.$el.is(':visible') ) {
    6986                         this.close();
     7277        dispose: function() {
     7278                this.options.selection.off( null, null, this );
     7279                View.prototype.dispose.apply( this, arguments );
     7280                return this;
     7281        },
     7282
     7283        createToolbar: function() {
     7284                var LibraryViewSwitcher, Filters, toolbarOptions;
     7285
     7286                toolbarOptions = {
     7287                        controller: this.controller
     7288                };
     7289
     7290                if ( this.controller.isModeActive( 'grid' ) ) {
     7291                        toolbarOptions.className = 'media-toolbar wp-filter';
    69877292                }
    69887293
    6989                 this.$el.detach();
    6990                 this.views.attached = false;
    6991                 return this.propagate('detach');
    6992         },
     7294                /**
     7295                * @member {wp.media.view.Toolbar}
     7296                */
     7297                this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
    69937298
    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;
     7299                this.views.add( this.toolbar );
    70017300
    7002                 if ( $el.is(':visible') ) {
    7003                         return this;
    7004                 }
     7301                this.toolbar.set( 'spinner', new wp.media.view.Spinner({
     7302                        priority: -60
     7303                }) );
    70057304
    7006                 this.clickedOpenerEl = document.activeElement;
     7305                if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
     7306                        // "Filters" will return a <select>, need to render
     7307                        // screen reader text before
     7308                        this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
     7309                                value: l10n.filterByType,
     7310                                attributes: {
     7311                                        'for':  'media-attachment-filters'
     7312                                },
     7313                                priority:   -80
     7314                        }).render() );
    70077315
    7008                 if ( ! this.views.attached ) {
    7009                         this.attach();
    7010                 }
     7316                        if ( 'uploaded' === this.options.filters ) {
     7317                                this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({
     7318                                        controller: this.controller,
     7319                                        model:      this.collection.props,
     7320                                        priority:   -80
     7321                                }).render() );
     7322                        } else {
     7323                                Filters = new wp.media.view.AttachmentFilters.All({
     7324                                        controller: this.controller,
     7325                                        model:      this.collection.props,
     7326                                        priority:   -80
     7327                                });
    70117328
    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                         };
     7329                                this.toolbar.set( 'filters', Filters.render() );
     7330                        }
    70177331                }
    70187332
    7019                 // Disable page scrolling.
    7020                 $( 'body' ).addClass( 'modal-open' );
     7333                // Feels odd to bring the global media library switcher into the Attachment
     7334                // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
     7335                // which the controller can tap into and add this view?
     7336                if ( this.controller.isModeActive( 'grid' ) ) {
     7337                        LibraryViewSwitcher = View.extend({
     7338                                className: 'view-switch media-grid-view-switch',
     7339                                template: wp.template( 'media-library-view-switcher')
     7340                        });
    70217341
    7022                 $el.show();
     7342                        this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
     7343                                controller: this.controller,
     7344                                priority: -90
     7345                        }).render() );
    70237346
    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();
     7347                        // DateFilter is a <select>, screen reader text needs to be rendered before
     7348                        this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
     7349                                value: l10n.filterByDate,
     7350                                attributes: {
     7351                                        'for': 'media-attachment-date-filters'
     7352                                },
     7353                                priority: -75
     7354                        }).render() );
     7355                        this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
     7356                                controller: this.controller,
     7357                                model:      this.collection.props,
     7358                                priority: -75
     7359                        }).render() );
    70297360
    7030                                 setTimeout( function() {
    7031                                         mceEditor.iframeElement.blur();
    7032                                 }, 100 );
    7033                         }
    7034                 }
     7361                        // BulkSelection is a <div> with subviews, including screen reader text
     7362                        this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
     7363                                text: l10n.bulkSelect,
     7364                                controller: this.controller,
     7365                                priority: -70
     7366                        }).render() );
    70357367
    7036                 this.$el.focus();
     7368                        this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
     7369                                filters: Filters,
     7370                                style: 'primary',
     7371                                disabled: true,
     7372                                text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
     7373                                controller: this.controller,
     7374                                priority: -60,
     7375                                click: function() {
     7376                                        var changed = [], removed = [],
     7377                                                selection = this.controller.state().get( 'selection' ),
     7378                                                library = this.controller.state().get( 'library' );
    70377379
    7038                 return this.propagate('open');
    7039         },
     7380                                        if ( ! selection.length ) {
     7381                                                return;
     7382                                        }
    70407383
    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;
     7384                                        if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
     7385                                                return;
     7386                                        }
    70477387
    7048                 if ( ! this.views.attached || ! this.$el.is(':visible') ) {
    7049                         return this;
    7050                 }
     7388                                        if ( mediaTrash &&
     7389                                                'trash' !== selection.at( 0 ).get( 'status' ) &&
     7390                                                ! window.confirm( l10n.warnBulkTrash ) ) {
    70517391
    7052                 // Enable page scrolling.
    7053                 $( 'body' ).removeClass( 'modal-open' );
     7392                                                return;
     7393                                        }
    70547394
    7055                 // Hide modal and remove restricted media modal tab focus once it's closed
    7056                 this.$el.hide().undelegate( 'keydown' );
     7395                                        selection.each( function( model ) {
     7396                                                if ( ! model.get( 'nonces' )['delete'] ) {
     7397                                                        removed.push( model );
     7398                                                        return;
     7399                                                }
    70577400
    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                 }
     7401                                                if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
     7402                                                        model.set( 'status', 'inherit' );
     7403                                                        changed.push( model.save() );
     7404                                                        removed.push( model );
     7405                                                } else if ( mediaTrash ) {
     7406                                                        model.set( 'status', 'trash' );
     7407                                                        changed.push( model.save() );
     7408                                                        removed.push( model );
     7409                                                } else {
     7410                                                        model.destroy({wait: true});
     7411                                                }
     7412                                        } );
    70647413
    7065                 this.propagate('close');
     7414                                        if ( changed.length ) {
     7415                                                selection.remove( removed );
    70667416
    7067                 // If the `freeze` option is set, restore the container's scroll position.
    7068                 if ( freeze ) {
    7069                         $( window ).scrollTop( freeze.scrollTop );
    7070                 }
     7417                                                $.when.apply( null, changed ).then( _.bind( function() {
     7418                                                        library._requery( true );
     7419                                                        this.controller.trigger( 'selection:action:done' );
     7420                                                }, this ) );
     7421                                        } else {
     7422                                                this.controller.trigger( 'selection:action:done' );
     7423                                        }
     7424                                }
     7425                        }).render() );
    70717426
    7072                 if ( options && options.escape ) {
    7073                         this.propagate('escape');
    7074                 }
     7427                        if ( mediaTrash ) {
     7428                                this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
     7429                                        filters: Filters,
     7430                                        style: 'primary',
     7431                                        disabled: true,
     7432                                        text: l10n.deleteSelected,
     7433                                        controller: this.controller,
     7434                                        priority: -55,
     7435                                        click: function() {
     7436                                                var removed = [],
     7437                                                        destroy = [],
     7438                                                        selection = this.controller.state().get( 'selection' );
    70757439
    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         },
     7440                                                if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
     7441                                                        return;
     7442                                                }
    70917443
    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         },
     7444                                                selection.each( function( model ) {
     7445                                                        if ( ! model.get( 'nonces' )['delete'] ) {
     7446                                                                removed.push( model );
     7447                                                                return;
     7448                                                        }
    71007449
    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 );
     7450                                                        destroy.push( model );
     7451                                                } );
    71107452
    7111                 if ( this.options.propagate ) {
    7112                         this.controller.trigger( id );
     7453                                                if ( removed.length ) {
     7454                                                        selection.remove( removed );
     7455                                                }
     7456
     7457                                                if ( destroy.length ) {
     7458                                                        $.when.apply( null, destroy.map( function (item) {
     7459                                                                return item.destroy();
     7460                                                        } ) ).then( _.bind( function() {
     7461                                                                this.controller.trigger( 'selection:action:done' );
     7462                                                        }, this ) );
     7463                                                }
     7464                                        }
     7465                                }).render() );
     7466                        }
     7467
     7468                } else if ( this.options.date ) {
     7469                        // DateFilter is a <select>, screen reader text needs to be rendered before
     7470                        this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
     7471                                value: l10n.filterByDate,
     7472                                attributes: {
     7473                                        'for': 'media-attachment-date-filters'
     7474                                },
     7475                                priority: -75
     7476                        }).render() );
     7477                        this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
     7478                                controller: this.controller,
     7479                                model:      this.collection.props,
     7480                                priority: -75
     7481                        }).render() );
    71137482                }
    71147483
    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();
     7484                if ( this.options.search ) {
     7485                        // Search is an input, screen reader text needs to be rendered before
     7486                        this.toolbar.set( 'searchLabel', new wp.media.view.Label({
     7487                                value: l10n.searchMediaLabel,
     7488                                attributes: {
     7489                                        'for': 'media-search-input'
     7490                                },
     7491                                priority:   60
     7492                        }).render() );
     7493                        this.toolbar.set( 'search', new wp.media.view.Search({
     7494                                controller: this.controller,
     7495                                model:      this.collection.props,
     7496                                priority:   60
     7497                        }).render() );
    71257498                }
    7126         }
    7127 });
    71287499
    7129 module.exports = Modal;
     7500                if ( this.options.dragInfo ) {
     7501                        this.toolbar.set( 'dragInfo', new View({
     7502                                el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
     7503                                priority: -40
     7504                        }) );
     7505                }
    71307506
    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',
     7507                if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
     7508                        this.toolbar.set( 'suggestedDimensions', new View({
     7509                                el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0],
     7510                                priority: -40
     7511                        }) );
     7512                }
     7513        },
    71447514
    7145         initialize: function() {
    7146                 this._views = {};
     7515        updateContent: function() {
     7516                var view = this,
     7517                        noItemsView;
    71477518
    7148                 this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
    7149                 delete this.options.views;
     7519                if ( this.controller.isModeActive( 'grid' ) ) {
     7520                        noItemsView = view.attachmentsNoResults;
     7521                } else {
     7522                        noItemsView = view.uploader;
     7523                }
    71507524
    7151                 if ( ! this.options.silent ) {
    7152                         this.render();
     7525                if ( ! this.collection.length ) {
     7526                        this.toolbar.get( 'spinner' ).show();
     7527                        this.dfd = this.collection.more().done( function() {
     7528                                if ( ! view.collection.length ) {
     7529                                        noItemsView.$el.removeClass( 'hidden' );
     7530                                } else {
     7531                                        noItemsView.$el.addClass( 'hidden' );
     7532                                }
     7533                                view.toolbar.get( 'spinner' ).hide();
     7534                        } );
     7535                } else {
     7536                        noItemsView.$el.addClass( 'hidden' );
     7537                        view.toolbar.get( 'spinner' ).hide();
    71537538                }
    71547539        },
    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;
    71637540
    7164                 options = options || {};
     7541        createUploader: function() {
     7542                this.uploader = new wp.media.view.UploaderInline({
     7543                        controller: this.controller,
     7544                        status:     false,
     7545                        message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
     7546                        canClose:   this.controller.isModeActive( 'grid' )
     7547                });
    71657548
    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                 }
     7549                this.uploader.$el.addClass( 'hidden' );
     7550                this.views.add( this.uploader );
     7551        },
    71737552
    7174                 if ( ! (view instanceof Backbone.View) ) {
    7175                         view = this.toView( view, id, options );
     7553        toggleUploader: function() {
     7554                if ( this.uploader.$el.hasClass( 'hidden' ) ) {
     7555                        this.uploader.show();
     7556                } else {
     7557                        this.uploader.hide();
    71767558                }
    7177                 view.controller = view.controller || this.controller;
    7178 
    7179                 this.unset( id );
     7559        },
    71807560
    7181                 priority = view.options.priority || 10;
    7182                 views = this.views.get() || [];
     7561        createAttachments: function() {
     7562                this.attachments = new wp.media.view.Attachments({
     7563                        controller:           this.controller,
     7564                        collection:           this.collection,
     7565                        selection:            this.options.selection,
     7566                        model:                this.model,
     7567                        sortable:             this.options.sortable,
     7568                        scrollElement:        this.options.scrollElement,
     7569                        idealColumnWidth:     this.options.idealColumnWidth,
    71837570
    7184                 _.find( views, function( existing, i ) {
    7185                         if ( existing.options.priority > priority ) {
    7186                                 index = i;
    7187                                 return true;
    7188                         }
     7571                        // The single `Attachment` view to be used in the `Attachments` view.
     7572                        AttachmentView: this.options.AttachmentView
    71897573                });
    71907574
    7191                 this._views[ id ] = view;
    7192                 this.views.add( view, {
    7193                         at: _.isNumber( index ) ? index : views.length || 0
    7194                 });
     7575                // Add keydown listener to the instance of the Attachments view
     7576                this.controller.on( 'attachment:keydown:arrow',     _.bind( this.attachments.arrowEvent, this.attachments ) );
     7577                this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) );
    71957578
    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 );
     7579                this.views.add( this.attachments );
    72117580
    7212                 if ( view ) {
    7213                         view.remove();
    7214                 }
    72157581
    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 });
     7582                if ( this.controller.isModeActive( 'grid' ) ) {
     7583                        this.attachmentsNoResults = new View({
     7584                                controller: this.controller,
     7585                                tagName: 'p'
     7586                        });
    72277587
    7228 module.exports = PriorityList;
     7588                        this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
     7589                        this.attachmentsNoResults.$el.html( l10n.noMedia );
    72297590
    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 );
     7591                        this.views.add( this.attachmentsNoResults );
    72507592                }
    7251         }
    7252 });
     7593        },
    72537594
    7254 module.exports = RouterItem;
     7595        createSidebar: function() {
     7596                var options = this.options,
     7597                        selection = options.selection,
     7598                        sidebar = this.sidebar = new wp.media.view.Sidebar({
     7599                                controller: this.controller
     7600                        });
    72557601
    7256 },{}],56:[function(require,module,exports){
    7257 var Menu = wp.media.view.Menu,
    7258         Router;
     7602                this.views.add( sidebar );
    72597603
    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',
     7604                if ( this.controller.uploader ) {
     7605                        sidebar.set( 'uploads', new wp.media.view.UploaderStatus({
     7606                                controller: this.controller,
     7607                                priority:   40
     7608                        }) );
     7609                }
    72787610
    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         },
     7611                selection.on( 'selection:single', this.createSingle, this );
     7612                selection.on( 'selection:unsingle', this.disposeSingle, this );
    72847613
    7285         update: function() {
    7286                 var mode = this.controller.content.mode();
    7287                 if ( mode ) {
    7288                         this.select( mode );
     7614                if ( selection.single() ) {
     7615                        this.createSingle();
    72897616                }
    7290         }
    7291 });
     7617        },
     7618
     7619        createSingle: function() {
     7620                var sidebar = this.sidebar,
     7621                        single = this.options.selection.single();
    72927622
    7293 module.exports = Router;
     7623                sidebar.set( 'details', new wp.media.view.Attachment.Details({
     7624                        controller: this.controller,
     7625                        model:      single,
     7626                        priority:   80
     7627                }) );
    72947628
    7295 },{}],57:[function(require,module,exports){
    7296 var l10n = wp.media.view.l10n,
    7297         Search;
     7629                sidebar.set( 'compat', new wp.media.view.AttachmentCompat({
     7630                        controller: this.controller,
     7631                        model:      single,
     7632                        priority:   120
     7633                }) );
    72987634
    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',
     7635                if ( this.options.display ) {
     7636                        sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({
     7637                                controller:   this.controller,
     7638                                model:        this.model.display( single ),
     7639                                attachment:   single,
     7640                                priority:     160,
     7641                                userSettings: this.model.get('displayUserSettings')
     7642                        }) );
     7643                }
    73137644
    7314         attributes: {
    7315                 type:        'search',
    7316                 placeholder: l10n.searchMediaPlaceholder
     7645                // Show the sidebar on mobile
     7646                if ( this.model.id === 'insert' ) {
     7647                        sidebar.$el.addClass( 'visible' );
     7648                }
    73177649        },
    73187650
    7319         events: {
    7320                 'input':  'search',
    7321                 'keyup':  'search'
    7322         },
     7651        disposeSingle: function() {
     7652                var sidebar = this.sidebar;
     7653                sidebar.unset('details');
     7654                sidebar.unset('compat');
     7655                sidebar.unset('display');
     7656                // Hide the sidebar on mobile
     7657                sidebar.$el.removeClass( 'visible' );
     7658        }
     7659});
    73237660
    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         },
     7661module.exports = AttachmentsBrowser;
    73317662
    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 });
    73407663
    7341 module.exports = Search;
     7664/***/ }),
     7665/* 79 */
     7666/***/ (function(module, exports) {
    73427667
    7343 },{}],58:[function(require,module,exports){
    73447668var l10n = wp.media.view.l10n,
    73457669        Selection;
    73467670
    Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */ 
    74257749
    74267750module.exports = Selection;
    74277751
    7428 },{}],59:[function(require,module,exports){
     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
     7785var Attachments = wp.media.view.Attachments,
     7786        Selection;
     7787
     7788/**
     7789 * wp.media.view.Attachments.Selection
     7790 *
     7791 * @memberOf wp.media.view.Attachments
     7792 *
     7793 * @class
     7794 * @augments wp.media.view.Attachments
     7795 * @augments wp.media.View
     7796 * @augments wp.Backbone.View
     7797 * @augments Backbone.View
     7798 */
     7799Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{
     7800        events: {},
     7801        initialize: function() {
     7802                _.defaults( this.options, {
     7803                        sortable:   false,
     7804                        resize:     false,
     7805
     7806                        // The single `Attachment` view to be used in the `Attachments` view.
     7807                        AttachmentView: wp.media.view.Attachment.Selection
     7808                });
     7809                // Call 'initialize' directly on the parent class.
     7810                return Attachments.prototype.initialize.apply( this, arguments );
     7811        }
     7812});
     7813
     7814module.exports = Selection;
     7815
     7816
     7817/***/ }),
     7818/* 82 */
     7819/***/ (function(module, exports) {
     7820
     7821/**
     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
    74297846var View = wp.media.View,
    74307847        $ = Backbone.$,
    74317848        Settings;
    Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{ 
    75487965
    75497966module.exports = Settings;
    75507967
    7551 },{}],60:[function(require,module,exports){
     7968
     7969/***/ }),
     7970/* 84 */
     7971/***/ (function(module, exports) {
     7972
    75527973var Settings = wp.media.view.Settings,
    75537974        AttachmentDisplay;
    75547975
    AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.Attachment 
    76258046                        if ( 'post' === linkTo ) {
    76268047                                $input.val( attachment.get('link') );
    76278048                        } 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){
    7704 var View = wp.media.view,
    7705         SiteIconCropper;
    7706 
    7707 /**
    7708  * wp.media.view.SiteIconCropper
    7709  *
    7710  * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon.
    7711  *
    7712  * Takes imgAreaSelect options from
    7713  * wp.customize.SiteIconControl.calculateImageSelectOptions.
    7714  *
    7715  * @memberOf wp.media.view
    7716  *
    7717  * @class
    7718  * @augments wp.media.view.Cropper
    7719  * @augments wp.media.View
    7720  * @augments wp.Backbone.View
    7721  * @augments Backbone.View
    7722  */
    7723 SiteIconCropper = View.Cropper.extend(/** @lends wp.media.view.SiteIconCropper.prototype */{
    7724         className: 'crop-content site-icon',
    7725 
    7726         ready: function () {
    7727                 View.Cropper.prototype.ready.apply( this, arguments );
    7728 
    7729                 this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) );
    7730         },
     8049                                $input.val( attachment.get('url') );
     8050                        } else if ( ! this.model.get('linkUrl') ) {
     8051                                $input.val('http://');
     8052                        }
    77318053
    7732         addSidebar: function() {
    7733                 this.sidebar = new wp.media.view.Sidebar({
    7734                         controller: this.controller
    7735                 });
     8054                        $input.prop( 'readonly', 'custom' !== linkTo );
     8055                }
    77368056
    7737                 this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({
    7738                         controller: this.controller,
    7739                         attachment: this.options.attachment
    7740                 }) );
     8057                $input.removeClass( 'hidden' );
    77418058
    7742                 this.controller.cropperView.views.add( this.sidebar );
     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                }
    77438063        }
    77448064});
    77458065
    7746 module.exports = SiteIconCropper;
     8066module.exports = AttachmentDisplay;
    77478067
    7748 },{}],65:[function(require,module,exports){
    7749 var View = wp.media.View,
    7750         $ = jQuery,
    7751         SiteIconPreview;
     8068
     8069/***/ }),
     8070/* 85 */
     8071/***/ (function(module, exports) {
    77528072
    77538073/**
    7754  * wp.media.view.SiteIconPreview
    7755  *
    7756  * Shows a preview of the Site Icon as a favicon and app icon while cropping.
     8074 * wp.media.view.Settings.Gallery
    77578075 *
    7758  * @memberOf wp.media.view
     8076 * @memberOf wp.media.view.Settings
    77598077 *
    77608078 * @class
     8079 * @augments wp.media.view.Settings
    77618080 * @augments wp.media.View
    77628081 * @augments wp.Backbone.View
    77638082 * @augments Backbone.View
    77648083 */
    7765 SiteIconPreview = View.extend(/** @lends wp.media.view.SiteIconPreview.prototype */{
    7766         className: 'site-icon-preview',
    7767         template: wp.template( 'site-icon-preview' ),
    7768 
    7769         ready: function() {
    7770                 this.controller.imgSelect.setOptions({
    7771                         onInit: this.updatePreview,
    7772                         onSelectChange: this.updatePreview
    7773                 });
    7774         },
    7775 
    7776         prepare: function() {
    7777                 return {
    7778                         url: this.options.attachment.get( 'url' )
    7779                 };
    7780         },
    7781 
    7782         updatePreview: function( img, coords ) {
    7783                 var rx = 64 / coords.width,
    7784                         ry = 64 / coords.height,
    7785                         preview_rx = 16 / coords.width,
    7786                         preview_ry = 16 / coords.height;
     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});
    77878088
    7788                 $( '#preview-app-icon' ).css({
    7789                         width: Math.round(rx * this.imageWidth ) + 'px',
    7790                         height: Math.round(ry * this.imageHeight ) + 'px',
    7791                         marginLeft: '-' + Math.round(rx * coords.x1) + 'px',
    7792                         marginTop: '-' + Math.round(ry * coords.y1) + 'px'
    7793                 });
     8089module.exports = Gallery;
    77948090
    7795                 $( '#preview-favicon' ).css({
    7796                         width: Math.round( preview_rx * this.imageWidth ) + 'px',
    7797                         height: Math.round( preview_ry * this.imageHeight ) + 'px',
    7798                         marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px',
    7799                         marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px'
    7800                 });
    7801         }
    7802 });
    78038091
    7804 module.exports = SiteIconPreview;
     8092/***/ }),
     8093/* 86 */
     8094/***/ (function(module, exports) {
    78058095
    7806 },{}],66:[function(require,module,exports){
    78078096/**
    7808  * wp.media.view.Spinner
     8097 * wp.media.view.Settings.Playlist
    78098098 *
    7810  * @memberOf wp.media.view
     8099 * @memberOf wp.media.view.Settings
    78118100 *
    78128101 * @class
     8102 * @augments wp.media.view.Settings
    78138103 * @augments wp.media.View
    78148104 * @augments wp.Backbone.View
    78158105 * @augments Backbone.View
    78168106 */
    7817 var Spinner = wp.media.View.extend(/** @lends wp.media.view.Spinner.prototype */{
    7818         tagName:   'span',
    7819         className: 'spinner',
    7820         spinnerTimeout: false,
    7821         delay: 400,
    7822 
    7823         show: function() {
    7824                 if ( ! this.spinnerTimeout ) {
    7825                         this.spinnerTimeout = _.delay(function( $el ) {
    7826                                 $el.addClass( 'is-active' );
    7827                         }, this.delay, this.$el );
    7828                 }
    7829 
    7830                 return this;
    7831         },
     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});
    78328111
    7833         hide: function() {
    7834                 this.$el.removeClass( 'is-active' );
    7835                 this.spinnerTimeout = clearTimeout( this.spinnerTimeout );
     8112module.exports = Playlist;
    78368113
    7837                 return this;
    7838         }
    7839 });
    78408114
    7841 module.exports = Spinner;
     8115/***/ }),
     8116/* 87 */
     8117/***/ (function(module, exports) {
    78428118
    7843 },{}],67:[function(require,module,exports){
    7844 var View = wp.media.View,
    7845         Toolbar;
     8119var Attachment = wp.media.view.Attachment,
     8120        l10n = wp.media.view.l10n,
     8121        Details;
    78468122
    78478123/**
    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.
     8124 * wp.media.view.Attachment.Details
    78528125 *
    7853  * @memberOf wp.media.view
     8126 * @memberOf wp.media.view.Attachment
    78548127 *
    78558128 * @class
     8129 * @augments wp.media.view.Attachment
    78568130 * @augments wp.media.View
    78578131 * @augments wp.Backbone.View
    78588132 * @augments Backbone.View
    78598133 */
    7860 Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{
     8134Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{
    78618135        tagName:   'div',
    7862         className: 'media-toolbar',
     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        },
    78638157
    78648158        initialize: function() {
    7865                 var state = this.controller.state(),
    7866                         selection = this.selection = state.get('selection'),
    7867                         library = this.library = state.get('library');
     8159                this.options = _.defaults( this.options, {
     8160                        rerenderOnModelChange: false
     8161                });
    78688162
    7869                 this._views = {};
     8163                this.on( 'ready', this.initialFocus );
     8164                // Call 'initialize' directly on the parent class.
     8165                Attachment.prototype.initialize.apply( this, arguments );
     8166        },
    78708167
    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');
     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();
    78768185
    7877                 this.views.set([ this.secondary, this.primary ]);
     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();
    78788199
    7879                 if ( this.options.items ) {
    7880                         this.set( this.options.items, { silent: true });
     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();
    78818209                }
     8210        },
     8211        /**
     8212         * @param {Object} event
     8213         */
     8214        untrashAttachment: function( event ) {
     8215                var library = this.controller.library;
     8216                event.preventDefault();
    78828217
    7883                 if ( ! this.options.silent ) {
    7884                         this.render();
     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;
    78858247                }
    78868248
    7887                 if ( selection ) {
    7888                         selection.on( 'add remove reset', this.refresh, this );
    7889                 }
     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.
     8270 *
     8271 * @memberOf wp.media.view
     8272 *
     8273 * @class
     8274 * @augments wp.media.View
     8275 * @augments wp.Backbone.View
     8276 * @augments Backbone.View
     8277 */
     8278AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{
     8279        tagName:   'form',
     8280        className: 'compat-item',
     8281
     8282        events: {
     8283                'submit':          'preventDefault',
     8284                'change input':    'save',
     8285                'change select':   'save',
     8286                'change textarea': 'save'
     8287        },
    78908288
    7891                 if ( library ) {
    7892                         library.on( 'add remove reset', this.refresh, this );
    7893                 }
     8289        initialize: function() {
     8290                this.listenTo( this.model, 'change:compat', this.render );
    78948291        },
    78958292        /**
    7896          * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
     8293         * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    78978294         */
    78988295        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 );
     8296                if ( this.$(':focus').length ) {
     8297                        this.save();
    79058298                }
    79068299                /**
    79078300                 * call 'dispose' directly on the parent class
    79088301                 */
    79098302                return View.prototype.dispose.apply( this, arguments );
    79108303        },
    7911 
    7912         ready: function() {
    7913                 this.refresh();
    7914         },
    7915 
    79168304        /**
    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
     8305         * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    79218306         */
    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();
     8307        render: function() {
     8308                var compat = this.model.get('compat');
     8309                if ( ! compat || ! compat.item ) {
     8310                        return;
    79488311                }
    79498312
     8313                this.views.detach();
     8314                this.$el.html( compat.item );
     8315                this.views.render();
    79508316                return this;
    79518317        },
    79528318        /**
    7953          * @param {string} id
    7954          * @returns {wp.media.view.Button}
     8319         * @param {Object} event
    79558320         */
    7956         get: function( id ) {
    7957                 return this._views[ id ];
     8321        preventDefault: function( event ) {
     8322                event.preventDefault();
    79588323        },
    79598324        /**
    7960          * @param {string} id
    7961          * @param {Object} options
    7962          * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     8325         * @param {Object} event
    79638326         */
    7964         unset: function( id, options ) {
    7965                 delete this._views[ id ];
    7966                 this.primary.unset( id, options );
    7967                 this.secondary.unset( id, options );
     8327        save: function( event ) {
     8328                var data = {};
    79688329
    7969                 if ( ! options || ! options.silent ) {
    7970                         this.refresh();
     8330                if ( event ) {
     8331                        event.preventDefault();
    79718332                }
    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                         }
    79848333
    7985                         var requires = button.options.requires,
    7986                                 disabled = false;
     8334                _.each( this.$el.serializeArray(), function( pair ) {
     8335                        data[ pair.name ] = pair.value;
     8336                });
    79878337
    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                         }
     8338                this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
     8339                this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
     8340        },
    79948341
    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                 });
     8342        postSave: function() {
     8343                this.controller.trigger( 'attachment:compat:ready', ['ready'] );
    80028344        }
    80038345});
    80048346
    8005 module.exports = Toolbar;
     8347module.exports = AttachmentCompat;
    80068348
    8007 },{}],68:[function(require,module,exports){
    8008 var Select = wp.media.view.Toolbar.Select,
    8009         l10n = wp.media.view.l10n,
    8010         Embed;
     8349
     8350/***/ }),
     8351/* 89 */
     8352/***/ (function(module, exports) {
    80118353
    80128354/**
    8013  * wp.media.view.Toolbar.Embed
     8355 * wp.media.view.Iframe
    80148356 *
    8015  * @memberOf wp.media.view.Toolbar
     8357 * @memberOf wp.media.view
    80168358 *
    80178359 * @class
    8018  * @augments wp.media.view.Toolbar.Select
    8019  * @augments wp.media.view.Toolbar
    80208360 * @augments wp.media.View
    80218361 * @augments wp.Backbone.View
    80228362 * @augments Backbone.View
    80238363 */
    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 );
     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;
    80418374        }
    80428375});
    80438376
    8044 module.exports = Embed;
     8377module.exports = Iframe;
    80458378
    8046 },{}],69:[function(require,module,exports){
    8047 var Toolbar = wp.media.view.Toolbar,
    8048         l10n = wp.media.view.l10n,
    8049         Select;
     8379
     8380/***/ }),
     8381/* 90 */
     8382/***/ (function(module, exports) {
    80508383
    80518384/**
    8052  * wp.media.view.Toolbar.Select
     8385 * wp.media.view.Embed
    80538386 *
    8054  * @memberOf wp.media.view.Toolbar
     8387 * @memberOf wp.media.view
    80558388 *
    80568389 * @class
    8057  * @augments wp.media.view.Toolbar
    80588390 * @augments wp.media.View
    80598391 * @augments wp.Backbone.View
    80608392 * @augments Backbone.View
    80618393 */
    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,
     8394var Embed = wp.media.View.extend(/** @lends wp.media.view.Ember.prototype */{
     8395        className: 'media-embed',
    80748396
    8075                         // Does the button rely on the selection?
    8076                         requires: {
    8077                                 selection: true
    8078                         }
    8079                 });
     8397        initialize: function() {
     8398                /**
     8399                 * @member {wp.media.view.EmbedUrl}
     8400                 */
     8401                this.url = new wp.media.view.EmbedUrl({
     8402                        controller: this.controller,
     8403                        model:      this.model.props
     8404                }).render();
    80808405
    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 );
     8406                this.views.set([ this.url ]);
     8407                this.refresh();
     8408                this.listenTo( this.model, 'change:type', this.refresh );
     8409                this.listenTo( this.model, 'change:loading', this.loading );
    80928410        },
    80938411
    8094         clickSelect: function() {
    8095                 var options = this.options,
    8096                         controller = this.controller;
    8097 
    8098                 if ( options.close ) {
    8099                         controller.close();
     8412        /**
     8413         * @param {Object} view
     8414         */
     8415        settings: function( view ) {
     8416                if ( this._settings ) {
     8417                        this._settings.remove();
    81008418                }
     8419                this._settings = view;
     8420                this.views.add( view );
     8421        },
    81018422
    8102                 if ( options.event ) {
    8103                         controller.state().trigger( options.event );
     8423        refresh: function() {
     8424                var type = this.model.get('type'),
     8425                        constructor;
     8426
     8427                if ( 'image' === type ) {
     8428                        constructor = wp.media.view.EmbedImage;
     8429                } else if ( 'link' === type ) {
     8430                        constructor = wp.media.view.EmbedLink;
     8431                } else {
     8432                        return;
    81048433                }
    81058434
    8106                 if ( options.state ) {
    8107                         controller.setState( options.state );
    8108                 }
     8435                this.settings( new constructor({
     8436                        controller: this.controller,
     8437                        model:      this.model.props,
     8438                        priority:   40
     8439                }) );
     8440        },
    81098441
    8110                 if ( options.reset ) {
    8111                         controller.reset();
    8112                 }
     8442        loading: function() {
     8443                this.$el.toggleClass( 'embed-loading', this.model.get('loading') );
    81138444        }
    81148445});
    81158446
    8116 module.exports = Select;
     8447module.exports = Embed;
    81178448
    8118 },{}],70:[function(require,module,exports){
    8119 var View = wp.media.View,
    8120         l10n = wp.media.view.l10n,
    8121         $ = jQuery,
    8122         EditorUploader;
     8449
     8450/***/ }),
     8451/* 91 */
     8452/***/ (function(module, exports) {
    81238453
    81248454/**
    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
     8455 * wp.media.view.Label
    81298456 *
    81308457 * @memberOf wp.media.view
    81318458 *
    var View = wp.media.View, 
    81348461 * @augments wp.Backbone.View
    81358462 * @augments Backbone.View
    81368463 */
    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,
     8464var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{
     8465        tagName: 'label',
     8466        className: 'screen-reader-text',
    81468467
    8147         /**
    8148          * Bind drag'n'drop events to callbacks.
    8149          */
    81508468        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                 }
     8469                this.value = this.options.value;
     8470        },
    81578471
    8158                 this.$document = $(document);
    8159                 this.dropzones = [];
    8160                 this.files = [];
     8472        render: function() {
     8473                this.$el.html( this.value );
    81618474
    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 ) );
     8475                return this;
     8476        }
     8477});
    81668478
    8167                 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
    8168                 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
     8479module.exports = Label;
    81698480
    8170                 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
    8171                         this.localDrag = event.type === 'dragstart';
    81728481
    8173                         if ( event.type === 'drop' ) {
    8174                                 this.containerDragleave();
    8175                         }
    8176                 }, this ) );
     8482/***/ }),
     8483/* 92 */
     8484/***/ (function(module, exports) {
    81778485
    8178                 this.initialized = true;
    8179                 return this;
    8180         },
     8486var View = wp.media.View,
     8487        $ = jQuery,
     8488        EmbedUrl;
    81818489
    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');
     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',
    81898503
    8190                 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
    8191                 supports = supports && !! ( window.File && window.FileList && window.FileReader );
    8192                 return supports;
     8504        events: {
     8505                'input':  'url',
     8506                'keyup':  'url',
     8507                'change': 'url'
    81938508        },
    81948509
    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;
     8510        initialize: function() {
     8511                this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
     8512                this.input = this.$input[0];
    82068513
    8207                 return this.draggingFile;
    8208         },
     8514                this.spinner = $('<span class="spinner" />')[0];
     8515                this.$el.append([ this.input, this.spinner ]);
    82098516
    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                 }
     8517                this.listenTo( this.model, 'change:url', this.render );
    82168518
    8217                 if ( ! _.isUndefined( e ) ) {
    8218                         $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
     8519                if ( this.model.get( 'url' ) ) {
     8520                        _.delay( _.bind( function () {
     8521                                this.model.trigger( 'change:url' );
     8522                        }, this ), 500 );
    82198523                }
     8524        },
     8525        /**
     8526         * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
     8527         */
     8528        render: function() {
     8529                var $input = this.$input;
    82208530
    8221                 if ( ! this.overContainer && ! this.overDropzone ) {
    8222                         this.draggingFile = null;
     8531                if ( $input.is(':focus') ) {
     8532                        return;
    82238533                }
    82248534
     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 );
    82258540                return this;
    82268541        },
    82278542
    8228         render: function() {
    8229                 if ( ! this.initialized ) {
    8230                         return this;
     8543        ready: function() {
     8544                if ( ! wp.media.isTouchDevice ) {
     8545                        this.focus();
    82318546                }
    8232 
    8233                 View.prototype.render.apply( this, arguments );
    8234                 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
    8235                 return this;
    82368547        },
    82378548
    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;
     8549        url: function( event ) {
     8550                this.model.set( 'url', $.trim( event.target.value ) );
    82448551        },
    82458552
    82468553        /**
    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.
     8554         * If the input is visible, focus and select its contents.
    82518555         */
    8252         drop: function( event ) {
    8253                 var $wrap, uploadView;
     8556        focus: function() {
     8557                var $input = this.$input;
     8558                if ( $input.is(':visible') ) {
     8559                        $input.focus()[0].select();
     8560                }
     8561        }
     8562});
    82548563
    8255                 this.containerDragleave( event );
    8256                 this.dropzoneDragleave( event );
     8564module.exports = EmbedUrl;
    82578565
    8258                 this.files = event.originalEvent.dataTransfer.files;
    8259                 if ( this.files.length < 1 ) {
     8566
     8567/***/ }),
     8568/* 93 */
     8569/***/ (function(module, exports) {
     8570
     8571var $ = jQuery,
     8572        EmbedLink;
     8573
     8574/**
     8575 * wp.media.view.EmbedLink
     8576 *
     8577 * @memberOf wp.media.view
     8578 *
     8579 * @class
     8580 * @augments wp.media.view.Settings
     8581 * @augments wp.media.View
     8582 * @augments wp.Backbone.View
     8583 * @augments Backbone.View
     8584 */
     8585EmbedLink = wp.media.view.Settings.extend(/** @lends wp.media.view.EmbedLink.prototype */{
     8586        className: 'embed-link-settings',
     8587        template:  wp.template('embed-link-settings'),
     8588
     8589        initialize: function() {
     8590                this.listenTo( this.model, 'change:url', this.updateoEmbed );
     8591        },
     8592
     8593        updateoEmbed: _.debounce( function() {
     8594                var url = this.model.get( 'url' );
     8595
     8596                // clear out previous results
     8597                this.$('.embed-container').hide().find('.embed-preview').empty();
     8598                this.$( '.setting' ).hide();
     8599
     8600                // only proceed with embed if the field contains more than 11 characters
     8601                // Example: http://a.io is 11 chars
     8602                if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {
    82608603                        return;
    82618604                }
    82628605
    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                 }
     8606                this.fetch();
     8607        }, wp.media.controller.Embed.sensitivity ),
    82688608
    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                         });
     8609        fetch: function() {
     8610                var url = this.model.get( 'url' ), re, youTubeEmbedMatch;
    82768611
    8277                         uploadView = this.workflow.uploader;
     8612                // check if they haven't typed in 500 ms
     8613                if ( $('#embed-url-field').val() !== url ) {
     8614                        return;
     8615                }
    82788616
    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();
     8617                if ( this.dfd && 'pending' === this.dfd.state() ) {
     8618                        this.dfd.abort();
     8619                }
     8620
     8621                // Support YouTube embed urls, since they work once in the editor.
     8622                re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/;
     8623                youTubeEmbedMatch = re.exec( url );
     8624                if ( youTubeEmbedMatch ) {
     8625                        url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ];
    82888626                }
    82898627
    8290                 return false;
     8628                this.dfd = wp.apiRequest({
     8629                        url: wp.media.view.settings.oEmbedProxyUrl,
     8630                        data: {
     8631                                url: url,
     8632                                maxwidth: this.model.get( 'width' ),
     8633                                maxheight: this.model.get( 'height' )
     8634                        },
     8635                        type: 'GET',
     8636                        dataType: 'json',
     8637                        context: this
     8638                })
     8639                        .done( function( response ) {
     8640                                this.renderoEmbed( {
     8641                                        data: {
     8642                                                body: response.html || ''
     8643                                        }
     8644                                } );
     8645                        } )
     8646                        .fail( this.renderFail );
    82918647        },
    82928648
    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 = [];
     8649        renderFail: function ( response, status ) {
     8650                if ( 'abort' === status ) {
     8651                        return;
    83008652                }
    8301                 return this;
     8653                this.$( '.link-text' ).show();
    83028654        },
    83038655
    8304         containerDragover: function( event ) {
    8305                 if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    8306                         return;
     8656        renderoEmbed: function( response ) {
     8657                var html = ( response && response.data && response.data.body ) || '';
     8658
     8659                if ( html ) {
     8660                        this.$('.embed-container').show().find('.embed-preview').html( html );
     8661                } else {
     8662                        this.renderFail();
    83078663                }
     8664        }
     8665});
    83088666
    8309                 this.overContainer = true;
    8310                 this.refresh();
    8311         },
     8667module.exports = EmbedLink;
    83128668
    8313         containerDragleave: function() {
    8314                 this.overContainer = false;
    83158669
    8316                 // Throttle dragleave because it's called when bouncing from some elements to others.
    8317                 _.delay( _.bind( this.refresh, this ), 50 );
    8318         },
     8670/***/ }),
     8671/* 94 */
     8672/***/ (function(module, exports) {
    83198673
    8320         dropzoneDragover: function( event ) {
    8321                 if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    8322                         return;
    8323                 }
     8674var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     8675        EmbedImage;
    83248676
    8325                 this.overDropzone = true;
    8326                 this.refresh( event );
    8327                 return false;
    8328         },
     8677/**
     8678 * wp.media.view.EmbedImage
     8679 *
     8680 * @memberOf wp.media.view
     8681 *
     8682 * @class
     8683 * @augments wp.media.view.Settings.AttachmentDisplay
     8684 * @augments wp.media.view.Settings
     8685 * @augments wp.media.View
     8686 * @augments wp.Backbone.View
     8687 * @augments Backbone.View
     8688 */
     8689EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{
     8690        className: 'embed-media-settings',
     8691        template:  wp.template('embed-image-settings'),
    83298692
    8330         dropzoneDragleave: function( e ) {
    8331                 this.overDropzone = false;
    8332                 _.delay( _.bind( this.refresh, this, e ), 50 );
     8693        initialize: function() {
     8694                /**
     8695                 * Call `initialize` directly on parent class with passed arguments
     8696                 */
     8697                AttachmentDisplay.prototype.initialize.apply( this, arguments );
     8698                this.listenTo( this.model, 'change:url', this.updateImage );
    83338699        },
    83348700
    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;
     8701        updateImage: function() {
     8702                this.$('img').attr( 'src', this.model.get('url') );
    83408703        }
    83418704});
    83428705
    8343 module.exports = EditorUploader;
     8706module.exports = EmbedImage;
    83448707
    8345 },{}],71:[function(require,module,exports){
    8346 var View = wp.media.View,
    8347         UploaderInline;
     8708
     8709/***/ }),
     8710/* 95 */
     8711/***/ (function(module, exports) {
     8712
     8713var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     8714        $ = jQuery,
     8715        ImageDetails;
    83488716
    83498717/**
    8350  * wp.media.view.UploaderInline
    8351  *
    8352  * The inline uploader that shows up in the 'Upload Files' tab.
     8718 * wp.media.view.ImageDetails
    83538719 *
    83548720 * @memberOf wp.media.view
    83558721 *
    83568722 * @class
     8723 * @augments wp.media.view.Settings.AttachmentDisplay
     8724 * @augments wp.media.view.Settings
    83578725 * @augments wp.media.View
    83588726 * @augments wp.Backbone.View
    83598727 * @augments Backbone.View
    83608728 */
    8361 UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{
    8362         tagName:   'div',
    8363         className: 'uploader-inline',
    8364         template:  wp.template('uploader-inline'),
     8729ImageDetails = AttachmentDisplay.extend(/** @lends wp.media.view.ImageDetails.prototype */{
     8730        className: 'image-details',
     8731        template:  wp.template('image-details'),
     8732        events: _.defaults( AttachmentDisplay.prototype.events, {
     8733                'click .edit-attachment': 'editAttachment',
     8734                'click .replace-attachment': 'replaceAttachment',
     8735                'click .advanced-toggle': 'onToggleAdvanced',
     8736                'change [data-setting="customWidth"]': 'onCustomSize',
     8737                'change [data-setting="customHeight"]': 'onCustomSize',
     8738                'keyup [data-setting="customWidth"]': 'onCustomSize',
     8739                'keyup [data-setting="customHeight"]': 'onCustomSize'
     8740        } ),
     8741        initialize: function() {
     8742                // used in AttachmentDisplay.prototype.updateLinkTo
     8743                this.options.attachment = this.model.attachment;
     8744                this.listenTo( this.model, 'change:url', this.updateUrl );
     8745                this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
     8746                this.listenTo( this.model, 'change:size', this.toggleCustomSize );
    83658747
    8366         events: {
    8367                 'click .close': 'hide'
     8748                AttachmentDisplay.prototype.initialize.apply( this, arguments );
    83688749        },
    83698750
    8370         initialize: function() {
    8371                 _.defaults( this.options, {
    8372                         message: '',
    8373                         status:  true,
    8374                         canClose: false
    8375                 });
     8751        prepare: function() {
     8752                var attachment = false;
    83768753
    8377                 if ( ! this.options.$browser && this.controller.uploader ) {
    8378                         this.options.$browser = this.controller.uploader.$browser;
     8754                if ( this.model.attachment ) {
     8755                        attachment = this.model.attachment.toJSON();
    83798756                }
     8757                return _.defaults({
     8758                        model: this.model.toJSON(),
     8759                        attachment: attachment
     8760                }, this.options );
     8761        },
    83808762
    8381                 if ( _.isUndefined( this.options.postId ) ) {
    8382                         this.options.postId = wp.media.view.settings.post.id;
     8763        render: function() {
     8764                var args = arguments;
     8765
     8766                if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
     8767                        this.model.dfd
     8768                                .done( _.bind( function() {
     8769                                        AttachmentDisplay.prototype.render.apply( this, args );
     8770                                        this.postRender();
     8771                                }, this ) )
     8772                                .fail( _.bind( function() {
     8773                                        this.model.attachment = false;
     8774                                        AttachmentDisplay.prototype.render.apply( this, args );
     8775                                        this.postRender();
     8776                                }, this ) );
     8777                } else {
     8778                        AttachmentDisplay.prototype.render.apply( this, arguments );
     8779                        this.postRender();
    83838780                }
    83848781
    8385                 if ( this.options.status ) {
    8386                         this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
    8387                                 controller: this.controller
    8388                         }) );
     8782                return this;
     8783        },
     8784
     8785        postRender: function() {
     8786                setTimeout( _.bind( this.resetFocus, this ), 10 );
     8787                this.toggleLinkSettings();
     8788                if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
     8789                        this.toggleAdvanced( true );
    83898790                }
     8791                this.trigger( 'post-render' );
    83908792        },
    83918793
    8392         prepare: function() {
    8393                 var suggestedWidth = this.controller.state().get('suggestedWidth'),
    8394                         suggestedHeight = this.controller.state().get('suggestedHeight'),
    8395                         data = {};
     8794        resetFocus: function() {
     8795                this.$( '.link-to-custom' ).blur();
     8796                this.$( '.embed-media-settings' ).scrollTop( 0 );
     8797        },
    83968798
    8397                 data.message = this.options.message;
    8398                 data.canClose = this.options.canClose;
     8799        updateUrl: function() {
     8800                this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
     8801                this.$( '.url' ).val( this.model.get( 'url' ) );
     8802        },
    83998803
    8400                 if ( suggestedWidth && suggestedHeight ) {
    8401                         data.suggestedWidth = suggestedWidth;
    8402                         data.suggestedHeight = suggestedHeight;
     8804        toggleLinkSettings: function() {
     8805                if ( this.model.get( 'link' ) === 'none' ) {
     8806                        this.$( '.link-settings' ).addClass('hidden');
     8807                } else {
     8808                        this.$( '.link-settings' ).removeClass('hidden');
    84038809                }
    8404 
    8405                 return data;
    84068810        },
    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                 }
    84178811
    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();
     8812        toggleCustomSize: function() {
     8813                if ( this.model.get( 'size' ) !== 'custom' ) {
     8814                        this.$( '.custom-size' ).addClass('hidden');
     8815                } else {
     8816                        this.$( '.custom-size' ).removeClass('hidden');
     8817                }
    84238818        },
    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 );
    84328819
    8433                 _.defer( _.bind( this.refresh, this ) );
    8434                 return result;
    8435         },
     8820        onCustomSize: function( event ) {
     8821                var dimension = $( event.target ).data('setting'),
     8822                        num = $( event.target ).val(),
     8823                        value;
    84368824
    8437         refresh: function() {
    8438                 var uploader = this.controller.uploader;
     8825                // Ignore bogus input
     8826                if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
     8827                        event.preventDefault();
     8828                        return;
     8829                }
    84398830
    8440                 if ( uploader ) {
    8441                         uploader.refresh();
     8831                if ( dimension === 'customWidth' ) {
     8832                        value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );
     8833                        this.model.set( 'customHeight', value, { silent: true } );
     8834                        this.$( '[data-setting="customHeight"]' ).val( value );
     8835                } else {
     8836                        value = Math.round( this.model.get( 'aspectRatio' ) * num );
     8837                        this.model.set( 'customWidth', value, { silent: true  } );
     8838                        this.$( '[data-setting="customWidth"]' ).val( value );
    84428839                }
    84438840        },
    8444         /**
    8445          * @returns {wp.media.view.UploaderInline}
    8446          */
    8447         ready: function() {
    8448                 var $browser = this.options.$browser,
    8449                         $placeholder;
    84508841
    8451                 if ( this.controller.uploader ) {
    8452                         $placeholder = this.$('.browser');
     8842        onToggleAdvanced: function( event ) {
     8843                event.preventDefault();
     8844                this.toggleAdvanced();
     8845        },
    84538846
    8454                         // Check if we've already replaced the placeholder.
    8455                         if ( $placeholder[0] === $browser[0] ) {
    8456                                 return;
    8457                         }
     8847        toggleAdvanced: function( show ) {
     8848                var $advanced = this.$el.find( '.advanced-section' ),
     8849                        mode;
    84588850
    8459                         $browser.detach().text( $placeholder.text() );
    8460                         $browser[0].className = $placeholder[0].className;
    8461                         $placeholder.replaceWith( $browser.show() );
     8851                if ( $advanced.hasClass('advanced-visible') || show === false ) {
     8852                        $advanced.removeClass('advanced-visible');
     8853                        $advanced.find('.advanced-settings').addClass('hidden');
     8854                        mode = 'hide';
     8855                } else {
     8856                        $advanced.addClass('advanced-visible');
     8857                        $advanced.find('.advanced-settings').removeClass('hidden');
     8858                        mode = 'show';
    84628859                }
    84638860
    8464                 this.refresh();
    8465                 return this;
     8861                window.setUserSetting( 'advImgDetails', mode );
    84668862        },
    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' );
     8863
     8864        editAttachment: function( event ) {
     8865                var editState = this.controller.states.get( 'edit-image' );
     8866
     8867                if ( window.imageEdit && editState ) {
     8868                        event.preventDefault();
     8869                        editState.set( 'image', this.model.attachment );
     8870                        this.controller.setState( 'edit-image' );
    84718871                }
    84728872        },
    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         }
    84828873
     8874        replaceAttachment: function( event ) {
     8875                event.preventDefault();
     8876                this.controller.setState( 'replace-image' );
     8877        }
    84838878});
    84848879
    8485 module.exports = UploaderInline;
     8880module.exports = ImageDetails;
    84868881
    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 });
    85028882
    8503 module.exports = UploaderStatusError;
     8883/***/ }),
     8884/* 96 */
     8885/***/ (function(module, exports) {
    85048886
    8505 },{}],73:[function(require,module,exports){
    85068887var View = wp.media.View,
    8507         UploaderStatus;
     8888        UploaderStatus = wp.media.view.UploaderStatus,
     8889        l10n = wp.media.view.l10n,
     8890        $ = jQuery,
     8891        Cropper;
    85088892
    85098893/**
    8510  * wp.media.view.UploaderStatus
     8894 * wp.media.view.Cropper
    85118895 *
    8512  * An uploader status for on-going uploads.
     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.
    85138901 *
    85148902 * @memberOf wp.media.view
    85158903 *
    var View = wp.media.View, 
    85188906 * @augments wp.Backbone.View
    85198907 * @augments Backbone.View
    85208908 */
    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 
     8909Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{
     8910        className: 'crop-content',
     8911        template: wp.template('crop-content'),
    85298912        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 );
     8913                _.bindAll(this, 'onImageLoad');
    85398914        },
    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;
     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));
    85508920        },
    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 );
     8921        remove: function() {
     8922                $(window).off('resize.cropper');
     8923                this.$el.remove();
     8924                this.$el.off();
     8925                View.prototype.remove.apply(this, arguments);
    85568926        },
    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();
     8927        prepare: function() {
     8928                return {
     8929                        title: l10n.cropYourImage,
     8930                        url: this.options.attachment.get('url')
     8931                };
    85718932        },
     8933        onImageLoad: function() {
     8934                var imgOptions = this.controller.get('imgSelectOptions'),
     8935                        imgSelect;
    85728936
    8573         progress: function() {
    8574                 var queue = this.queue,
    8575                         $bar = this.$bar;
    8576 
    8577                 if ( ! $bar || ! queue.length ) {
    8578                         return;
     8937                if (typeof imgOptions === 'function') {
     8938                        imgOptions = imgOptions(this.options.attachment, this.controller);
    85798939                }
    85808940
    8581                 $bar.width( ( queue.reduce( function( memo, attachment ) {
    8582                         if ( ! attachment.get('uploading') ) {
    8583                                 return memo + 100;
    8584                         }
     8941                imgOptions = _.extend(imgOptions, {
     8942                        parent: this.$el,
     8943                        onInit: function() {
     8944                                this.parent.children().on( 'mousedown touchstart', function( e ){
    85858945
    8586                         var percent = attachment.get('percent');
    8587                         return memo + ( _.isNumber( percent ) ? percent : 100 );
    8588                 }, 0 ) / queue.length ) + '%' );
     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);
    85898960        },
     8961        onError: function() {
     8962                var filename = this.options.attachment.get('filename');
    85908963
    8591         info: function() {
    8592                 var queue = this.queue,
    8593                         index = 0, active;
     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 });
     8968        }
     8969});
    85948970
    8595                 if ( ! queue.length ) {
    8596                         return;
    8597                 }
     8971module.exports = Cropper;
    85988972
    8599                 active = this.queue.find( function( attachment, i ) {
    8600                         index = i;
    8601                         return attachment.get('uploading');
    8602                 });
    86038973
    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 });
     8974/***/ }),
     8975/* 97 */
     8976/***/ (function(module, exports) {
     8977
     8978var View = wp.media.view,
     8979        SiteIconCropper;
     8980
     8981/**
     8982 * wp.media.view.SiteIconCropper
     8983 *
     8984 * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon.
     8985 *
     8986 * Takes imgAreaSelect options from
     8987 * wp.customize.SiteIconControl.calculateImageSelectOptions.
     8988 *
     8989 * @memberOf wp.media.view
     8990 *
     8991 * @class
     8992 * @augments wp.media.view.Cropper
     8993 * @augments wp.media.View
     8994 * @augments wp.Backbone.View
     8995 * @augments Backbone.View
     8996 */
     8997SiteIconCropper = View.Cropper.extend(/** @lends wp.media.view.SiteIconCropper.prototype */{
     8998        className: 'crop-content site-icon',
     8999
     9000        ready: function () {
     9001                View.Cropper.prototype.ready.apply( this, arguments );
     9002
     9003                this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) );
    86239004        },
    86249005
    8625         /**
    8626          * @param {Object} event
    8627          */
    8628         dismiss: function( event ) {
    8629                 var errors = this.views.get('.upload-errors');
     9006        addSidebar: function() {
     9007                this.sidebar = new wp.media.view.Sidebar({
     9008                        controller: this.controller
     9009                });
    86309010
    8631                 event.preventDefault();
     9011                this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({
     9012                        controller: this.controller,
     9013                        attachment: this.options.attachment
     9014                }) );
    86329015
    8633                 if ( errors ) {
    8634                         _.invoke( errors, 'remove' );
    8635                 }
    8636                 wp.Uploader.errors.reset();
     9016                this.controller.cropperView.views.add( this.sidebar );
    86379017        }
    86389018});
    86399019
    8640 module.exports = UploaderStatus;
     9020module.exports = SiteIconCropper;
    86419021
    8642 },{}],74:[function(require,module,exports){
    8643 var $ = jQuery,
    8644         UploaderWindow;
     9022
     9023/***/ }),
     9024/* 98 */
     9025/***/ (function(module, exports) {
     9026
     9027var View = wp.media.View,
     9028        $ = jQuery,
     9029        SiteIconPreview;
    86459030
    86469031/**
    8647  * wp.media.view.UploaderWindow
     9032 * wp.media.view.SiteIconPreview
    86489033 *
    8649  * An uploader window that allows for dragging and dropping media.
     9034 * Shows a preview of the Site Icon as a favicon and app icon while cropping.
    86509035 *
    86519036 * @memberOf wp.media.view
    86529037 *
    var $ = jQuery, 
    86549039 * @augments wp.media.View
    86559040 * @augments wp.Backbone.View
    86569041 * @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]
    86639042 */
    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'),
     9043SiteIconPreview = View.extend(/** @lends wp.media.view.SiteIconPreview.prototype */{
     9044        className: 'site-icon-preview',
     9045        template: wp.template( 'site-icon-preview' ),
    86689046
    8669         initialize: function() {
    8670                 var uploader;
     9047        ready: function() {
     9048                this.controller.imgSelect.setOptions({
     9049                        onInit: this.updatePreview,
     9050                        onSelectChange: this.updatePreview
     9051                });
     9052        },
    86719053
    8672                 this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' );
     9054        prepare: function() {
     9055                return {
     9056                        url: this.options.attachment.get( 'url' )
     9057                };
     9058        },
    86739059
    8674                 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
    8675                         dropzone:  this.$el,
    8676                         browser:   this.$browser,
    8677                         params:    {}
    8678                 });
     9060        updatePreview: function( img, coords ) {
     9061                var rx = 64 / coords.width,
     9062                        ry = 64 / coords.height,
     9063                        preview_rx = 16 / coords.width,
     9064                        preview_ry = 16 / coords.height;
    86799065
    8680                 // Ensure the dropzone is a jQuery collection.
    8681                 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
    8682                         uploader.dropzone = $( uploader.dropzone );
    8683                 }
     9066                $( '#preview-app-icon' ).css({
     9067                        width: Math.round(rx * this.imageWidth ) + 'px',
     9068                        height: Math.round(ry * this.imageHeight ) + 'px',
     9069                        marginLeft: '-' + Math.round(rx * coords.x1) + 'px',
     9070                        marginTop: '-' + Math.round(ry * coords.y1) + 'px'
     9071                });
    86849072
    8685                 this.controller.on( 'activate', this.refresh, this );
     9073                $( '#preview-favicon' ).css({
     9074                        width: Math.round( preview_rx * this.imageWidth ) + 'px',
     9075                        height: Math.round( preview_ry * this.imageHeight ) + 'px',
     9076                        marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px',
     9077                        marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px'
     9078                });
     9079        }
     9080});
    86869081
    8687                 this.controller.on( 'detach', function() {
    8688                         this.$browser.remove();
    8689                 }, this );
    8690         },
     9082module.exports = SiteIconPreview;
    86919083
    8692         refresh: function() {
    8693                 if ( this.uploader ) {
    8694                         this.uploader.refresh();
    8695                 }
    8696         },
    86979084
    8698         ready: function() {
    8699                 var postId = wp.media.view.settings.post.id,
    8700                         dropzone;
     9085/***/ }),
     9086/* 99 */
     9087/***/ (function(module, exports) {
    87019088
    8702                 // If the uploader already exists, bail.
    8703                 if ( this.uploader ) {
    8704                         return;
    8705                 }
     9089var View = wp.media.View,
     9090        EditImage;
    87069091
    8707                 if ( postId ) {
    8708                         this.options.uploader.params.post_id = postId;
    8709                 }
    8710                 this.uploader = new wp.Uploader( this.options.uploader );
     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'),
    87119105
    8712                 dropzone = this.uploader.dropzone;
    8713                 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
    8714                 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
     9106        initialize: function( options ) {
     9107                this.editor = window.imageEdit;
     9108                this.controller = options.controller;
     9109                View.prototype.initialize.apply( this, arguments );
     9110        },
    87159111
    8716                 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
     9112        prepare: function() {
     9113                return this.model.toJSON();
    87179114        },
    87189115
    8719         _ready: function() {
    8720                 this.controller.trigger( 'uploader:ready' );
     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 ) );
    87219119        },
    87229120
    8723         show: function() {
    8724                 var $el = this.$el.show();
     9121        focus: function() {
     9122                this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
     9123        },
    87259124
    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                 });
     9125        back: function() {
     9126                var lastState = this.controller.lastState();
     9127                this.controller.setState( lastState );
    87319128        },
    87329129
    8733         hide: function() {
    8734                 var $el = this.$el.css({ opacity: 0 });
     9130        refresh: function() {
     9131                this.model.fetch();
     9132        },
    87359133
    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                 });
     9134        save: function() {
     9135                var lastState = this.controller.lastState();
    87439136
    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 );
     9137                this.model.fetch().done( _.bind( function() {
     9138                        this.controller.setState( lastState );
     9139                }, this ) );
    87509140        }
     9141
    87519142});
    87529143
    8753 module.exports = UploaderWindow;
     9144module.exports = EditImage;
     9145
     9146
     9147/***/ }),
     9148/* 100 */
     9149/***/ (function(module, exports) {
    87549150
    8755 },{}],75:[function(require,module,exports){
    87569151/**
    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.
     9152 * wp.media.view.Spinner
    87679153 *
    8768  * @memberOf wp.media
     9154 * @memberOf wp.media.view
    87699155 *
    87709156 * @class
     9157 * @augments wp.media.View
    87719158 * @augments wp.Backbone.View
    87729159 * @augments Backbone.View
    87739160 */
    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();
     9161var Spinner = wp.media.View.extend(/** @lends wp.media.view.Spinner.prototype */{
     9162        tagName:   'span',
     9163        className: 'spinner',
     9164        spinnerTimeout: false,
     9165        delay: 400,
    87939166
    8794                 if ( this.model && this.model.off ) {
    8795                         this.model.off( null, null, this );
     9167        show: function() {
     9168                if ( ! this.spinnerTimeout ) {
     9169                        this.spinnerTimeout = _.delay(function( $el ) {
     9170                                $el.addClass( 'is-active' );
     9171                        }, this.delay, this.$el );
    87969172                }
    87979173
    8798                 if ( this.collection && this.collection.off ) {
    8799                         this.collection.off( null, null, this );
    8800                 }
     9174                return this;
     9175        },
    88019176
    8802                 // Unbind controller events.
    8803                 if ( this.controller && this.controller.off ) {
    8804                         this.controller.off( null, null, this );
    8805                 }
     9177        hide: function() {
     9178                this.$el.removeClass( 'is-active' );
     9179                this.spinnerTimeout = clearTimeout( this.spinnerTimeout );
    88069180
    88079181                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 );
    88189182        }
    88199183});
    88209184
    8821 module.exports = View;
     9185module.exports = Spinner;
     9186
    88229187
    8823 },{}]},{},[19]);
     9188/***/ })
     9189/******/ ]));
     9190 No newline at end of file
  • new file webpack.config.dev.js

    diff --git webpack.config.dev.js webpack.config.dev.js
    new file mode 100644
    index 0000000000..27c8e74b23
    - +  
     1var path         = require( 'path' ),
     2        SOURCE_DIR   = 'src/',
     3        mediaConfig  = {},
     4        mediaBuilds  = [ 'audiovideo', 'grid', 'models', 'views' ],
     5        webpack      = require( 'webpack' );
     6
     7
     8mediaBuilds.forEach( function ( build ) {
     9        var path = SOURCE_DIR + 'wp-includes/js/media';
     10        mediaConfig[ build ] = './' + path + '/' + build + '.manifest.js';
     11} );
     12
     13module.exports = {
     14        cache: true,
     15        watch: true,
     16        entry: mediaConfig,
     17        output: {
     18                path: path.join( __dirname, 'src/wp-includes/js' ),
     19                filename: 'media-[name].js'
     20        }
     21};
  • new file webpack.config.prod.js

    diff --git webpack.config.prod.js webpack.config.prod.js
    new file mode 100644
    index 0000000000..8a0b1e4c5c
    - +  
     1var path         = require( 'path' ),
     2        SOURCE_DIR   = 'src/',
     3        mediaConfig  = {},
     4        mediaBuilds  = [ 'audiovideo', 'grid', 'models', 'views' ],
     5        webpack      = require( 'webpack' );
     6
     7
     8mediaBuilds.forEach( function ( build ) {
     9        var path = SOURCE_DIR + 'wp-includes/js/media';
     10        mediaConfig[ build ] = './' + path + '/' + build + '.manifest.js';
     11} );
     12
     13module.exports = {
     14        cache: true,
     15        entry: mediaConfig,
     16        output: {
     17                path: path.join( __dirname, 'src/wp-includes/js' ),
     18                filename: 'media-[name].js'
     19        },
     20        plugins: [
     21                new webpack.optimize.ModuleConcatenationPlugin()
     22        ]
     23};