Make WordPress Core

Ticket #40894: 40894.8.diff

File 40894.8.diff, 528.9 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 2d82878322..aa8d7bd2b7 100644
     
    11/* jshint node:true */
    22/* globals Set */
     3var webpackConfig = require( './webpack.config' );
     4var webpackDevConfig = require( './webpack-dev.config' );
     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) { 
    554545                                dest: SOURCE_DIR + 'wp-includes/js/jquery/jquery.masonry.min.js'
    555546                        }
    556547                },
    557 
     548                webpack: {
     549                        prod: webpackConfig,
     550                        dev: webpackDevConfig
     551                },
    558552                concat: {
    559553                        tinymce: {
    560554                                options: {
    module.exports = function(grunt) { 
    720714                                }
    721715                        },
    722716                        config: {
    723                                 files: 'Gruntfile.js'
     717                                files: [
     718                                        'Gruntfile.js',
     719                                        'webpack-dev.config.js',
     720                                        'webpack.config.js'
     721                                ]
    724722                        },
    725723                        colors: {
    726724                                files: [SOURCE_DIR + 'wp-admin/css/colors/**'],
    module.exports = function(grunt) { 
    758756
    759757        // Register tasks.
    760758
     759        // Webpack task.
     760        grunt.loadNpmTasks( 'grunt-webpack' );
     761
    761762        // RTL task.
    762763        grunt.registerTask('rtl', ['rtlcss:core', 'rtlcss:colors']);
    763764
    module.exports = function(grunt) { 
    781782        grunt.renameTask( 'watch', '_watch' );
    782783
    783784        grunt.registerTask( 'watch', function() {
    784                 if ( ! this.args.length || this.args.indexOf( 'browserify' ) > -1 ) {
    785                         grunt.config( 'browserify.options', {
    786                                 browserifyOptions: {
    787                                         debug: true
    788                                 },
    789                                 watch: true
    790                         } );
     785                if ( ! this.args.length || this.args.indexOf( 'webpack' ) > -1 ) {
    791786
    792                         grunt.task.run( 'browserify' );
     787                        grunt.task.run( 'webpack:dev' );
    793788                }
    794789
    795790                grunt.task.run( '_' + this.nameArgs );
    module.exports = function(grunt) { 
    800795        ] );
    801796
    802797        grunt.registerTask( 'precommit:js', [
    803                 'browserify',
     798                'webpack:prod',
    804799                'jshint:corejs',
    805800                'uglify:masonry',
    806801                'qunit:compiled'
    module.exports = function(grunt) { 
    879874                                }
    880875
    881876                                if ( code === 0 ) {
    882                                         if ( [ 'package.json', 'Gruntfile.js' ].some( testPath ) ) {
    883                                                 grunt.log.writeln( 'Configuration files modified. Running `prerelease`.' );
    884                                                 taskList.push( 'prerelease' );
    885                                         } else {
    886                                                 if ( [ 'png', 'jpg', 'gif', 'jpeg' ].some( testExtension ) ) {
    887                                                         grunt.log.writeln( 'Image files modified. Minifying.' );
    888                                                         taskList.push( 'precommit:image' );
    889                                                 }
    890 
    891                                                 [ 'js', 'css', 'php' ].forEach( function( extension ) {
    892                                                         if ( testExtension( extension ) ) {
    893                                                                 grunt.log.writeln( extension.toUpperCase() + ' files modified. ' + extension.toUpperCase() + ' tests will be run.' );
    894                                                                 taskList.push( 'precommit:' + extension );
    895                                                         }
    896                                                 } );
     877                                if ( [ 'package.json', 'Gruntfile.js', 'webpack.config.js' ].some( testPath ) ) {
     878                                        grunt.log.writeln( 'Configuration files modified. Running `prerelease`.' );
     879                                        taskList.push( 'prerelease' );
     880                                } else {
     881                                        if ( [ 'png', 'jpg', 'gif', 'jpeg' ].some( testExtension ) ) {
     882                                                grunt.log.writeln( 'Image files modified. Minifying.' );
     883                                                taskList.push( 'precommit:image' );
     884                                        }
    897885
    898                                                 if ( [ 'twemoji.js' ].some( testPath ) ) {
    899                                                         grunt.log.writeln( 'twemoji.js has updated. Running `precommit:emoji.' );
    900                                                         taskList.push( 'precommit:emoji' );
     886                                        [ 'js', 'css', 'php' ].forEach( function( extension ) {
     887                                                if ( testExtension( extension ) ) {
     888                                                        grunt.log.writeln( extension.toUpperCase() + ' files modified. ' + extension.toUpperCase() + ' tests will be run.' );
     889                                                        taskList.push( 'precommit:' + extension );
    901890                                                }
     891                                        } );
     892
     893                                        if ( [ 'twemoji.js' ].some( testPath ) ) {
     894                                                grunt.log.writeln( 'twemoji.js has updated. Running `precommit:emoji.' );
     895                                                taskList.push( 'precommit:emoji' );
    902896                                        }
     897                                }
    903898
    904                                         grunt.task.run( taskList );
    905                                         done();
     899                                grunt.task.run( taskList );
     900                                done();
    906901                                } else {
    907902                                        runAllTasks();
    908903                                }
    module.exports = function(grunt) { 
    979974        grunt.event.on('watch', function( action, filepath, target ) {
    980975                var src;
    981976
    982                 if ( [ 'all', 'rtl', 'browserify' ].indexOf( target ) === -1 ) {
     977                if ( [ 'all', 'rtl', 'webpack' ].indexOf( target ) === -1 ) {
    983978                        return;
    984979                }
    985980
  • package.json

    diff --git package.json package.json
    index 71695dfb77..4e1655c099 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.5.4",
     41    "webpack-dev-server": "^2.7.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