Make WordPress Core

Ticket #40894: 40894.6.diff

File 40894.6.diff, 530.0 KB (added by adamsilverstein, 6 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 199f9faa2c..3cf55b2c93 100644
     
    11/* jshint node:true */
     2var webpackConfig = require( './webpack.config' );
     3var webpackDevConfig = require( './webpack-dev.config' );
     4
    25module.exports = function(grunt) {
    36        var path = require('path'),
    47                fs = require( 'fs' ),
    58                SOURCE_DIR = 'src/',
    69                BUILD_DIR = 'build/',
    710                BANNER_TEXT = '/*! This file is auto-generated */',
    8                 autoprefixer = require('autoprefixer'),
    9                 mediaConfig = {},
    10                 mediaBuilds = ['audiovideo', 'grid', 'models', 'views'];
     11                autoprefixer = require( 'autoprefixer' );
    1112
    1213        // Load tasks.
    1314        require('matchdep').filterDev(['grunt-*', '!grunt-legacy-util']).forEach( grunt.loadNpmTasks );
    1415        // Load legacy utils
    1516        grunt.util = require('grunt-legacy-util');
    1617
    17         mediaBuilds.forEach( function ( build ) {
    18                 var path = SOURCE_DIR + 'wp-includes/js/media';
    19                 mediaConfig[ build ] = { files : {} };
    20                 mediaConfig[ build ].files[ path + '-' + build + '.js' ] = [ path + '/' + build + '.manifest.js' ];
    21         } );
    22 
    2318        // Project configuration.
    2419        grunt.initConfig({
    2520                postcss: {
    module.exports = function(grunt) { 
    175170                                }
    176171                        }
    177172                },
    178                 browserify: mediaConfig,
    179173                sass: {
    180174                        colors: {
    181175                                expand: true,
    module.exports = function(grunt) { 
    336330                                ]
    337331                        },
    338332                        media: {
    339                                 options: {
    340                                         browserify: true
    341                                 },
    342333                                src: [
    343334                                        SOURCE_DIR + 'wp-includes/js/media/**/*.js'
    344335                                ]
    module.exports = function(grunt) { 
    548539                                dest: SOURCE_DIR + 'wp-includes/js/jquery/jquery.masonry.min.js'
    549540                        }
    550541                },
    551 
     542                webpack: {
     543                        prod: webpackConfig,
     544                        dev: webpackDevConfig
     545                },
    552546                concat: {
    553547                        tinymce: {
    554548                                options: {
    module.exports = function(grunt) { 
    731725                                }
    732726                        },
    733727                        config: {
    734                                 files: 'Gruntfile.js'
     728                                files: [
     729                                        'Gruntfile.js',
     730                                        'webpack-dev.config.js',
     731                                        'webpack.config.js'
     732                                ]
    735733                        },
    736734                        colors: {
    737735                                files: [SOURCE_DIR + 'wp-admin/css/colors/**'],
    module.exports = function(grunt) { 
    769767
    770768        // Register tasks.
    771769
     770        // Webpack task.
     771        grunt.loadNpmTasks( 'grunt-webpack' );
     772
    772773        // RTL task.
    773774        grunt.registerTask('rtl', ['rtlcss:core', 'rtlcss:colors']);
    774775
    module.exports = function(grunt) { 
    792793        grunt.renameTask( 'watch', '_watch' );
    793794
    794795        grunt.registerTask( 'watch', function() {
    795                 if ( ! this.args.length || this.args.indexOf( 'browserify' ) > -1 ) {
    796                         grunt.config( 'browserify.options', {
    797                                 browserifyOptions: {
    798                                         debug: true
    799                                 },
    800                                 watch: true
    801                         } );
     796                if ( ! this.args.length || this.args.indexOf( 'webpack' ) > -1 ) {
    802797
    803                         grunt.task.run( 'browserify' );
     798                        grunt.task.run( 'webpack:dev' );
    804799                }
    805800
    806801                grunt.task.run( '_' + this.nameArgs );
    module.exports = function(grunt) { 
    811806        ] );
    812807
    813808        grunt.registerTask( 'precommit:js', [
    814                 'browserify',
     809                'webpack:prod',
    815810                'jshint:corejs',
    816811                'uglify:masonry',
    817812                'qunit:compiled'
    module.exports = function(grunt) { 
    879874                                        return regex.test( result.stdout );
    880875                                }
    881876
    882                                 if ( [ 'package.json', 'Gruntfile.js' ].some( testPath ) ) {
     877                                if ( [ 'package.json', 'Gruntfile.js', 'webpack.config.js' ].some( testPath ) ) {
    883878                                        grunt.log.writeln( 'Configuration files modified. Running `prerelease`.' );
    884879                                        taskList.push( 'prerelease' );
    885880                                } else {
    module.exports = function(grunt) { 
    977972        grunt.event.on('watch', function( action, filepath, target ) {
    978973                var src;
    979974
    980                 if ( [ 'all', 'rtl', 'browserify' ].indexOf( target ) === -1 ) {
     975                if ( [ 'all', 'rtl', 'webpack' ].indexOf( target ) === -1 ) {
    981976                        return;
    982977                }
    983978
  • 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}
  • deleted file src/wp-content/plugins/hello.php

    diff --git src/wp-content/plugins/hello.php src/wp-content/plugins/hello.php
    deleted file mode 100644
    index 2b1e07b59e..0000000000
    + -  
    1 <?php
    2 /**
    3  * @package Hello_Dolly
    4  * @version 1.6
    5  */
    6 /*
    7 Plugin Name: Hello Dolly
    8 Plugin URI: http://wordpress.org/plugins/hello-dolly/
    9 Description: This is not just a plugin, it symbolizes the hope and enthusiasm of an entire generation summed up in two words sung most famously by Louis Armstrong: Hello, Dolly. When activated you will randomly see a lyric from <cite>Hello, Dolly</cite> in the upper right of your admin screen on every page.
    10 Author: Matt Mullenweg
    11 Version: 1.6
    12 Author URI: http://ma.tt/
    13 */
    14 
    15 function hello_dolly_get_lyric() {
    16         /** These are the lyrics to Hello Dolly */
    17         $lyrics = "Hello, Dolly
    18 Well, hello, Dolly
    19 It's so nice to have you back where you belong
    20 You're lookin' swell, Dolly
    21 I can tell, Dolly
    22 You're still glowin', you're still crowin'
    23 You're still goin' strong
    24 We feel the room swayin'
    25 While the band's playin'
    26 One of your old favourite songs from way back when
    27 So, take her wrap, fellas
    28 Find her an empty lap, fellas
    29 Dolly'll never go away again
    30 Hello, Dolly
    31 Well, hello, Dolly
    32 It's so nice to have you back where you belong
    33 You're lookin' swell, Dolly
    34 I can tell, Dolly
    35 You're still glowin', you're still crowin'
    36 You're still goin' strong
    37 We feel the room swayin'
    38 While the band's playin'
    39 One of your old favourite songs from way back when
    40 Golly, gee, fellas
    41 Find her a vacant knee, fellas
    42 Dolly'll never go away
    43 Dolly'll never go away
    44 Dolly'll never go away again";
    45 
    46         // Here we split it into lines
    47         $lyrics = explode( "\n", $lyrics );
    48 
    49         // And then randomly choose a line
    50         return wptexturize( $lyrics[ mt_rand( 0, count( $lyrics ) - 1 ) ] );
    51 }
    52 
    53 // This just echoes the chosen line, we'll position it later
    54 function hello_dolly() {
    55         $chosen = hello_dolly_get_lyric();
    56         echo "<p id='dolly'>$chosen</p>";
    57 }
    58 
    59 // Now we set that function up to execute when the admin_notices action is called
    60 add_action( 'admin_notices', 'hello_dolly' );
    61 
    62 // We need some CSS to position the paragraph
    63 function dolly_css() {
    64         // This makes sure that the positioning is also good for right-to-left languages
    65         $x = is_rtl() ? 'left' : 'right';
    66 
    67         echo "
    68         <style type='text/css'>
    69         #dolly {
    70                 float: $x;
    71                 padding-$x: 15px;
    72                 padding-top: 5px;               
    73                 margin: 0;
    74                 font-size: 11px;
    75         }
    76         </style>
    77         ";
    78 }
    79 
    80 add_action( 'admin_head', 'dolly_css' );
    81 
    82 ?>
  • deleted file src/wp-content/plugins/index.php

    diff --git src/wp-content/plugins/index.php src/wp-content/plugins/index.php
    deleted file mode 100644
    index 62200328fd..0000000000
    + -  
    1 <?php
    2 // Silence is golden.
  • src/wp-includes/js/media-audiovideo.js

    diff --git src/wp-includes/js/media-audiovideo.js src/wp-includes/js/media-audiovideo.js
    index 0a819747d2..cb2abd57ae 100644
     
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     1/******/ (function(modules) { // webpackBootstrap
     2/******/        // The module cache
     3/******/        var installedModules = {};
     4/******/
     5/******/        // The require function
     6/******/        function __webpack_require__(moduleId) {
     7/******/
     8/******/                // Check if module is in cache
     9/******/                if(installedModules[moduleId]) {
     10/******/                        return installedModules[moduleId].exports;
     11/******/                }
     12/******/                // Create a new module (and put it into the cache)
     13/******/                var module = installedModules[moduleId] = {
     14/******/                        i: moduleId,
     15/******/                        l: false,
     16/******/                        exports: {}
     17/******/                };
     18/******/
     19/******/                // Execute the module function
     20/******/                modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/                // Flag the module as loaded
     23/******/                module.l = true;
     24/******/
     25/******/                // Return the exports of the module
     26/******/                return module.exports;
     27/******/        }
     28/******/
     29/******/
     30/******/        // expose the modules object (__webpack_modules__)
     31/******/        __webpack_require__.m = modules;
     32/******/
     33/******/        // expose the module cache
     34/******/        __webpack_require__.c = installedModules;
     35/******/
     36/******/        // define getter function for harmony exports
     37/******/        __webpack_require__.d = function(exports, name, getter) {
     38/******/                if(!__webpack_require__.o(exports, name)) {
     39/******/                        Object.defineProperty(exports, name, {
     40/******/                                configurable: false,
     41/******/                                enumerable: true,
     42/******/                                get: getter
     43/******/                        });
     44/******/                }
     45/******/        };
     46/******/
     47/******/        // getDefaultExport function for compatibility with non-harmony modules
     48/******/        __webpack_require__.n = function(module) {
     49/******/                var getter = module && module.__esModule ?
     50/******/                        function getDefault() { return module['default']; } :
     51/******/                        function getModuleExports() { return module; };
     52/******/                __webpack_require__.d(getter, 'a', getter);
     53/******/                return getter;
     54/******/        };
     55/******/
     56/******/        // Object.prototype.hasOwnProperty.call
     57/******/        __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/        // __webpack_public_path__
     60/******/        __webpack_require__.p = "";
     61/******/
     62/******/        // Load entry module and return exports
     63/******/        return __webpack_require__(__webpack_require__.s = 0);
     64/******/ })
     65/************************************************************************/
     66/******/ ([
     67/* 0 */
     68/***/ (function(module, exports, __webpack_require__) {
     69
    270var media = wp.media,
    371        baseSettings = window._wpmejsSettings || {},
    472        l10n = window._wpMediaViewsL10n || {};
    wp.media.video = { 
    268336        }
    269337};
    270338
    271 media.model.PostMedia = require( './models/post-media.js' );
    272 media.controller.AudioDetails = require( './controllers/audio-details.js' );
    273 media.controller.VideoDetails = require( './controllers/video-details.js' );
    274 media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' );
    275 media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' );
    276 media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' );
    277 media.view.MediaDetails = require( './views/media-details.js' );
    278 media.view.AudioDetails = require( './views/audio-details.js' );
    279 media.view.VideoDetails = require( './views/video-details.js' );
    280 
    281 },{"./controllers/audio-details.js":2,"./controllers/video-details.js":3,"./models/post-media.js":4,"./views/audio-details.js":5,"./views/frame/audio-details.js":6,"./views/frame/media-details.js":7,"./views/frame/video-details.js":8,"./views/media-details.js":9,"./views/video-details.js":10}],2:[function(require,module,exports){
    282 var State = wp.media.controller.State,
    283         l10n = wp.media.view.l10n,
    284         AudioDetails;
    285 
    286 /**
    287  * wp.media.controller.AudioDetails
    288  *
    289  * The controller for the Audio Details state
    290  *
    291  * @memberOf wp.media.controller
    292  *
    293  * @class
    294  * @augments wp.media.controller.State
    295  * @augments Backbone.Model
    296  */
    297 AudioDetails = State.extend(/** @lends wp.media.controller.AudioDetails.prototype */{
    298         defaults: {
    299                 id: 'audio-details',
    300                 toolbar: 'audio-details',
    301                 title: l10n.audioDetailsTitle,
    302                 content: 'audio-details',
    303                 menu: 'audio-details',
    304                 router: false,
    305                 priority: 60
    306         },
    307 
    308         initialize: function( options ) {
    309                 this.media = options.media;
    310                 State.prototype.initialize.apply( this, arguments );
    311         }
    312 });
    313 
    314 module.exports = AudioDetails;
    315 
    316 },{}],3:[function(require,module,exports){
    317 /**
    318  * wp.media.controller.VideoDetails
    319  *
    320  * The controller for the Video Details state
    321  *
    322  * @memberOf wp.media.controller
    323  *
    324  * @class
    325  * @augments wp.media.controller.State
    326  * @augments Backbone.Model
    327  */
    328 var State = wp.media.controller.State,
    329         l10n = wp.media.view.l10n,
    330         VideoDetails;
    331 
    332 VideoDetails = State.extend(/** @lends wp.media.controller.VideoDetails.prototype */{
    333         defaults: {
    334                 id: 'video-details',
    335                 toolbar: 'video-details',
    336                 title: l10n.videoDetailsTitle,
    337                 content: 'video-details',
    338                 menu: 'video-details',
    339                 router: false,
    340                 priority: 60
    341         },
     339media.model.PostMedia = __webpack_require__( 1 );
     340media.controller.AudioDetails = __webpack_require__( 2 );
     341media.controller.VideoDetails = __webpack_require__( 3 );
     342media.view.MediaFrame.MediaDetails = __webpack_require__( 4 );
     343media.view.MediaFrame.AudioDetails = __webpack_require__( 5 );
     344media.view.MediaFrame.VideoDetails = __webpack_require__( 6 );
     345media.view.MediaDetails = __webpack_require__( 7 );
     346media.view.AudioDetails = __webpack_require__( 8 );
     347media.view.VideoDetails = __webpack_require__( 9 );
    342348
    343         initialize: function( options ) {
    344                 this.media = options.media;
    345                 State.prototype.initialize.apply( this, arguments );
    346         }
    347 });
    348349
    349 module.exports = VideoDetails;
     350/***/ }),
     351/* 1 */
     352/***/ (function(module, exports) {
    350353
    351 },{}],4:[function(require,module,exports){
    352354/**
    353355 * wp.media.model.PostMedia
    354356 *
    var PostMedia = Backbone.Model.extend(/** @lends wp.media.model.PostMedia.protot 
    392394
    393395module.exports = PostMedia;
    394396
    395 },{}],5:[function(require,module,exports){
    396 var MediaDetails = wp.media.view.MediaDetails,
     397
     398/***/ }),
     399/* 2 */
     400/***/ (function(module, exports) {
     401
     402var State = wp.media.controller.State,
     403        l10n = wp.media.view.l10n,
    397404        AudioDetails;
    398405
    399406/**
    400  * wp.media.view.AudioDetails
     407 * wp.media.controller.AudioDetails
    401408 *
    402  * @memberOf wp.media.view
     409 * The controller for the Audio Details state
     410 *
     411 * @memberOf wp.media.controller
    403412 *
    404413 * @class
    405  * @augments wp.media.view.MediaDetails
    406  * @augments wp.media.view.Settings.AttachmentDisplay
    407  * @augments wp.media.view.Settings
    408  * @augments wp.media.View
    409  * @augments wp.Backbone.View
    410  * @augments Backbone.View
     414 * @augments wp.media.controller.State
     415 * @augments Backbone.Model
    411416 */
    412 AudioDetails = MediaDetails.extend(/** @lends wp.media.view.AudioDetails.prototype */{
    413         className: 'audio-details',
    414         template:  wp.template('audio-details'),
    415 
    416         setMedia: function() {
    417                 var audio = this.$('.wp-audio-shortcode');
    418 
    419                 if ( audio.find( 'source' ).length ) {
    420                         if ( audio.is(':hidden') ) {
    421                                 audio.show();
    422                         }
    423                         this.media = MediaDetails.prepareSrc( audio.get(0) );
    424                 } else {
    425                         audio.hide();
    426                         this.media = false;
    427                 }
     417AudioDetails = State.extend(/** @lends wp.media.controller.AudioDetails.prototype */{
     418        defaults: {
     419                id: 'audio-details',
     420                toolbar: 'audio-details',
     421                title: l10n.audioDetailsTitle,
     422                content: 'audio-details',
     423                menu: 'audio-details',
     424                router: false,
     425                priority: 60
     426        },
    428427
    429                 return this;
     428        initialize: function( options ) {
     429                this.media = options.media;
     430                State.prototype.initialize.apply( this, arguments );
    430431        }
    431432});
    432433
    433434module.exports = AudioDetails;
    434435
    435 },{}],6:[function(require,module,exports){
    436 var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
    437         MediaLibrary = wp.media.controller.MediaLibrary,
    438436
    439         l10n = wp.media.view.l10n,
    440         AudioDetails;
     437/***/ }),
     438/* 3 */
     439/***/ (function(module, exports) {
    441440
    442441/**
    443  * wp.media.view.MediaFrame.AudioDetails
     442 * wp.media.controller.VideoDetails
    444443 *
    445  * @memberOf wp.media.view.MediaFrame
     444 * The controller for the Video Details state
     445 *
     446 * @memberOf wp.media.controller
    446447 *
    447448 * @class
    448  * @augments wp.media.view.MediaFrame.MediaDetails
    449  * @augments wp.media.view.MediaFrame.Select
    450  * @augments wp.media.view.MediaFrame
    451  * @augments wp.media.view.Frame
    452  * @augments wp.media.View
    453  * @augments wp.Backbone.View
    454  * @augments Backbone.View
    455  * @mixes wp.media.controller.StateMachine
     449 * @augments wp.media.controller.State
     450 * @augments Backbone.Model
    456451 */
    457 AudioDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.AudioDetails.prototype */{
     452var State = wp.media.controller.State,
     453        l10n = wp.media.view.l10n,
     454        VideoDetails;
     455
     456VideoDetails = State.extend(/** @lends wp.media.controller.VideoDetails.prototype */{
    458457        defaults: {
    459                 id:      'audio',
    460                 url:     '',
    461                 menu:    'audio-details',
    462                 content: 'audio-details',
    463                 toolbar: 'audio-details',
    464                 type:    'link',
    465                 title:    l10n.audioDetailsTitle,
    466                 priority: 120
     458                id: 'video-details',
     459                toolbar: 'video-details',
     460                title: l10n.videoDetailsTitle,
     461                content: 'video-details',
     462                menu: 'video-details',
     463                router: false,
     464                priority: 60
    467465        },
    468466
    469467        initialize: function( options ) {
    470                 options.DetailsView = wp.media.view.AudioDetails;
    471                 options.cancelText = l10n.audioDetailsCancel;
    472                 options.addText = l10n.audioAddSourceTitle;
    473 
    474                 MediaDetails.prototype.initialize.call( this, options );
    475         },
    476 
    477         bindHandlers: function() {
    478                 MediaDetails.prototype.bindHandlers.apply( this, arguments );
    479 
    480                 this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
    481                 this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
    482         },
    483 
    484         createStates: function() {
    485                 this.states.add([
    486                         new wp.media.controller.AudioDetails( {
    487                                 media: this.media
    488                         } ),
    489 
    490                         new MediaLibrary( {
    491                                 type: 'audio',
    492                                 id: 'replace-audio',
    493                                 title: l10n.audioReplaceTitle,
    494                                 toolbar: 'replace-audio',
    495                                 media: this.media,
    496                                 menu: 'audio-details'
    497                         } ),
    498 
    499                         new MediaLibrary( {
    500                                 type: 'audio',
    501                                 id: 'add-audio-source',
    502                                 title: l10n.audioAddSourceTitle,
    503                                 toolbar: 'add-audio-source',
    504                                 media: this.media,
    505                                 menu: false
    506                         } )
    507                 ]);
     468                this.media = options.media;
     469                State.prototype.initialize.apply( this, arguments );
    508470        }
    509471});
    510472
    511 module.exports = AudioDetails;
     473module.exports = VideoDetails;
     474
     475
     476/***/ }),
     477/* 4 */
     478/***/ (function(module, exports) {
    512479
    513 },{}],7:[function(require,module,exports){
    514480var Select = wp.media.view.MediaFrame.Select,
    515481        l10n = wp.media.view.l10n,
    516482        MediaDetails;
    MediaDetails = Select.extend(/** @lends wp.media.view.MediaFrame.MediaDetails.pr 
    642608
    643609module.exports = MediaDetails;
    644610
    645 },{}],8:[function(require,module,exports){
     611
     612/***/ }),
     613/* 5 */
     614/***/ (function(module, exports) {
     615
     616var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
     617        MediaLibrary = wp.media.controller.MediaLibrary,
     618
     619        l10n = wp.media.view.l10n,
     620        AudioDetails;
     621
     622/**
     623 * wp.media.view.MediaFrame.AudioDetails
     624 *
     625 * @memberOf wp.media.view.MediaFrame
     626 *
     627 * @class
     628 * @augments wp.media.view.MediaFrame.MediaDetails
     629 * @augments wp.media.view.MediaFrame.Select
     630 * @augments wp.media.view.MediaFrame
     631 * @augments wp.media.view.Frame
     632 * @augments wp.media.View
     633 * @augments wp.Backbone.View
     634 * @augments Backbone.View
     635 * @mixes wp.media.controller.StateMachine
     636 */
     637AudioDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.AudioDetails.prototype */{
     638        defaults: {
     639                id:      'audio',
     640                url:     '',
     641                menu:    'audio-details',
     642                content: 'audio-details',
     643                toolbar: 'audio-details',
     644                type:    'link',
     645                title:    l10n.audioDetailsTitle,
     646                priority: 120
     647        },
     648
     649        initialize: function( options ) {
     650                options.DetailsView = wp.media.view.AudioDetails;
     651                options.cancelText = l10n.audioDetailsCancel;
     652                options.addText = l10n.audioAddSourceTitle;
     653
     654                MediaDetails.prototype.initialize.call( this, options );
     655        },
     656
     657        bindHandlers: function() {
     658                MediaDetails.prototype.bindHandlers.apply( this, arguments );
     659
     660                this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
     661                this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
     662        },
     663
     664        createStates: function() {
     665                this.states.add([
     666                        new wp.media.controller.AudioDetails( {
     667                                media: this.media
     668                        } ),
     669
     670                        new MediaLibrary( {
     671                                type: 'audio',
     672                                id: 'replace-audio',
     673                                title: l10n.audioReplaceTitle,
     674                                toolbar: 'replace-audio',
     675                                media: this.media,
     676                                menu: 'audio-details'
     677                        } ),
     678
     679                        new MediaLibrary( {
     680                                type: 'audio',
     681                                id: 'add-audio-source',
     682                                title: l10n.audioAddSourceTitle,
     683                                toolbar: 'add-audio-source',
     684                                media: this.media,
     685                                menu: false
     686                        } )
     687                ]);
     688        }
     689});
     690
     691module.exports = AudioDetails;
     692
     693
     694/***/ }),
     695/* 6 */
     696/***/ (function(module, exports) {
     697
    646698var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
    647699        MediaLibrary = wp.media.controller.MediaLibrary,
    648700        l10n = wp.media.view.l10n,
    VideoDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.VideoDeta 
    779831
    780832module.exports = VideoDetails;
    781833
    782 },{}],9:[function(require,module,exports){
     834
     835/***/ }),
     836/* 7 */
     837/***/ (function(module, exports) {
     838
    783839/* global MediaElementPlayer */
    784840var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    785841        $ = jQuery,
    MediaDetails = AttachmentDisplay.extend(/** @lends wp.media.view.MediaDetails.pr 
    9491005
    9501006module.exports = MediaDetails;
    9511007
    952 },{}],10:[function(require,module,exports){
     1008
     1009/***/ }),
     1010/* 8 */
     1011/***/ (function(module, exports) {
     1012
     1013var MediaDetails = wp.media.view.MediaDetails,
     1014        AudioDetails;
     1015
     1016/**
     1017 * wp.media.view.AudioDetails
     1018 *
     1019 * @memberOf wp.media.view
     1020 *
     1021 * @class
     1022 * @augments wp.media.view.MediaDetails
     1023 * @augments wp.media.view.Settings.AttachmentDisplay
     1024 * @augments wp.media.view.Settings
     1025 * @augments wp.media.View
     1026 * @augments wp.Backbone.View
     1027 * @augments Backbone.View
     1028 */
     1029AudioDetails = MediaDetails.extend(/** @lends wp.media.view.AudioDetails.prototype */{
     1030        className: 'audio-details',
     1031        template:  wp.template('audio-details'),
     1032
     1033        setMedia: function() {
     1034                var audio = this.$('.wp-audio-shortcode');
     1035
     1036                if ( audio.find( 'source' ).length ) {
     1037                        if ( audio.is(':hidden') ) {
     1038                                audio.show();
     1039                        }
     1040                        this.media = MediaDetails.prepareSrc( audio.get(0) );
     1041                } else {
     1042                        audio.hide();
     1043                        this.media = false;
     1044                }
     1045
     1046                return this;
     1047        }
     1048});
     1049
     1050module.exports = AudioDetails;
     1051
     1052
     1053/***/ }),
     1054/* 9 */
     1055/***/ (function(module, exports) {
     1056
    9531057var MediaDetails = wp.media.view.MediaDetails,
    9541058        VideoDetails;
    9551059
    VideoDetails = MediaDetails.extend(/** @lends wp.media.view.VideoDetails.prototy 
    9941098
    9951099module.exports = VideoDetails;
    9961100
    997 },{}]},{},[1]);
     1101
     1102/***/ })
     1103/******/ ]);
     1104 No newline at end of file
  • src/wp-includes/js/media-grid.js

    diff --git src/wp-includes/js/media-grid.js src/wp-includes/js/media-grid.js
    index 6e35fb52a6..c9babd6d50 100644
     
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     1/******/ (function(modules) { // webpackBootstrap
     2/******/        // The module cache
     3/******/        var installedModules = {};
     4/******/
     5/******/        // The require function
     6/******/        function __webpack_require__(moduleId) {
     7/******/
     8/******/                // Check if module is in cache
     9/******/                if(installedModules[moduleId]) {
     10/******/                        return installedModules[moduleId].exports;
     11/******/                }
     12/******/                // Create a new module (and put it into the cache)
     13/******/                var module = installedModules[moduleId] = {
     14/******/                        i: moduleId,
     15/******/                        l: false,
     16/******/                        exports: {}
     17/******/                };
     18/******/
     19/******/                // Execute the module function
     20/******/                modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/                // Flag the module as loaded
     23/******/                module.l = true;
     24/******/
     25/******/                // Return the exports of the module
     26/******/                return module.exports;
     27/******/        }
     28/******/
     29/******/
     30/******/        // expose the modules object (__webpack_modules__)
     31/******/        __webpack_require__.m = modules;
     32/******/
     33/******/        // expose the module cache
     34/******/        __webpack_require__.c = installedModules;
     35/******/
     36/******/        // define getter function for harmony exports
     37/******/        __webpack_require__.d = function(exports, name, getter) {
     38/******/                if(!__webpack_require__.o(exports, name)) {
     39/******/                        Object.defineProperty(exports, name, {
     40/******/                                configurable: false,
     41/******/                                enumerable: true,
     42/******/                                get: getter
     43/******/                        });
     44/******/                }
     45/******/        };
     46/******/
     47/******/        // getDefaultExport function for compatibility with non-harmony modules
     48/******/        __webpack_require__.n = function(module) {
     49/******/                var getter = module && module.__esModule ?
     50/******/                        function getDefault() { return module['default']; } :
     51/******/                        function getModuleExports() { return module; };
     52/******/                __webpack_require__.d(getter, 'a', getter);
     53/******/                return getter;
     54/******/        };
     55/******/
     56/******/        // Object.prototype.hasOwnProperty.call
     57/******/        __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/        // __webpack_public_path__
     60/******/        __webpack_require__.p = "";
     61/******/
     62/******/        // Load entry module and return exports
     63/******/        return __webpack_require__(__webpack_require__.s = 10);
     64/******/ })
     65/************************************************************************/
     66/******/ ([
     67/* 0 */,
     68/* 1 */,
     69/* 2 */,
     70/* 3 */,
     71/* 4 */,
     72/* 5 */,
     73/* 6 */,
     74/* 7 */,
     75/* 8 */,
     76/* 9 */,
     77/* 10 */
     78/***/ (function(module, exports, __webpack_require__) {
     79
     80var media = wp.media;
     81
     82media.controller.EditAttachmentMetadata = __webpack_require__( 11 );
     83media.view.MediaFrame.Manage = __webpack_require__( 12 );
     84media.view.Attachment.Details.TwoColumn = __webpack_require__( 13 );
     85media.view.MediaFrame.Manage.Router = __webpack_require__( 14 );
     86media.view.EditImage.Details = __webpack_require__( 15 );
     87media.view.MediaFrame.EditAttachments = __webpack_require__( 16 );
     88media.view.SelectModeToggleButton = __webpack_require__( 17 );
     89media.view.DeleteSelectedButton = __webpack_require__( 18 );
     90media.view.DeleteSelectedPermanentlyButton = __webpack_require__( 19 );
     91
     92
     93/***/ }),
     94/* 11 */
     95/***/ (function(module, exports) {
     96
    297var l10n = wp.media.view.l10n,
    398        EditAttachmentMetadata;
    499
    EditAttachmentMetadata = wp.media.controller.State.extend(/** @lends wp.media.co 
    28123
    29124module.exports = EditAttachmentMetadata;
    30125
    31 },{}],2:[function(require,module,exports){
    32 var media = wp.media;
    33126
    34 media.controller.EditAttachmentMetadata = require( './controllers/edit-attachment-metadata.js' );
    35 media.view.MediaFrame.Manage = require( './views/frame/manage.js' );
    36 media.view.Attachment.Details.TwoColumn = require( './views/attachment/details-two-column.js' );
    37 media.view.MediaFrame.Manage.Router = require( './routers/manage.js' );
    38 media.view.EditImage.Details = require( './views/edit-image-details.js' );
    39 media.view.MediaFrame.EditAttachments = require( './views/frame/edit-attachments.js' );
    40 media.view.SelectModeToggleButton = require( './views/button/select-mode-toggle.js' );
    41 media.view.DeleteSelectedButton = require( './views/button/delete-selected.js' );
    42 media.view.DeleteSelectedPermanentlyButton = require( './views/button/delete-selected-permanently.js' );
    43 
    44 },{"./controllers/edit-attachment-metadata.js":1,"./routers/manage.js":3,"./views/attachment/details-two-column.js":4,"./views/button/delete-selected-permanently.js":5,"./views/button/delete-selected.js":6,"./views/button/select-mode-toggle.js":7,"./views/edit-image-details.js":8,"./views/frame/edit-attachments.js":9,"./views/frame/manage.js":10}],3:[function(require,module,exports){
     127/***/ }),
     128/* 12 */
     129/***/ (function(module, exports) {
     130
     131var MediaFrame = wp.media.view.MediaFrame,
     132        Library = wp.media.controller.Library,
     133
     134        $ = Backbone.$,
     135        Manage;
     136
    45137/**
    46  * wp.media.view.MediaFrame.Manage.Router
     138 * wp.media.view.MediaFrame.Manage
    47139 *
    48  * A router for handling the browser history and application state.
     140 * A generic management frame workflow.
    49141 *
    50  * @memberOf wp.media.view.MediaFrame.Manage
     142 * Used in the media grid view.
     143 *
     144 * @memberOf wp.media.view.MediaFrame
    51145 *
    52146 * @class
    53  * @augments Backbone.Router
     147 * @augments wp.media.view.MediaFrame
     148 * @augments wp.media.view.Frame
     149 * @augments wp.media.View
     150 * @augments wp.Backbone.View
     151 * @augments Backbone.View
     152 * @mixes wp.media.controller.StateMachine
    54153 */
    55 var Router = Backbone.Router.extend(/** @lends wp.media.view.MediaFrame.Manage.Router.prototype */{
    56         routes: {
    57                 'upload.php?item=:slug&mode=edit': 'editItem',
    58                 'upload.php?item=:slug':           'showItem',
    59                 'upload.php?search=:query':        'search',
    60                 'upload.php':                      'reset'
    61         },
     154Manage = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Manage.prototype */{
     155        /**
     156         * @constructs
     157         */
     158        initialize: function() {
     159                _.defaults( this.options, {
     160                        title:     '',
     161                        modal:     false,
     162                        selection: [],
     163                        library:   {}, // Options hash for the query to the media library.
     164                        multiple:  'add',
     165                        state:     'library',
     166                        uploader:  true,
     167                        mode:      [ 'grid', 'edit' ]
     168                });
    62169
    63         // Map routes against the page URL
    64         baseUrl: function( url ) {
    65                 return 'upload.php' + url;
    66         },
     170                this.$body = $( document.body );
     171                this.$window = $( window );
     172                this.$adminBar = $( '#wpadminbar' );
     173                // Store the Add New button for later reuse in wp.media.view.UploaderInline.
     174                this.$uploaderToggler = $( '.page-title-action' )
     175                        .attr( 'aria-expanded', 'false' )
     176                        .on( 'click', _.bind( this.addNewClickHandler, this ) );
    67177
    68         reset: function() {
    69                 var frame = wp.media.frames.edit;
     178                this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) );
    70179
    71                 if ( frame ) {
    72                         frame.close();
     180                // Ensure core and media grid view UI is enabled.
     181                this.$el.addClass('wp-core-ui');
     182
     183                // Force the uploader off if the upload limit has been exceeded or
     184                // if the browser isn't supported.
     185                if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     186                        this.options.uploader = false;
    73187                }
    74         },
    75188
    76         // Respond to the search route by filling the search field and trigggering the input event
    77         search: function( query ) {
    78                 jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
    79         },
     189                // Initialize a window-wide uploader.
     190                if ( this.options.uploader ) {
     191                        this.uploader = new wp.media.view.UploaderWindow({
     192                                controller: this,
     193                                uploader: {
     194                                        dropzone:  document.body,
     195                                        container: document.body
     196                                }
     197                        }).render();
     198                        this.uploader.ready();
     199                        $('body').append( this.uploader.el );
    80200
    81         // Show the modal with a specific item
    82         showItem: function( query ) {
    83                 var media = wp.media,
    84                         frame = media.frames.browse,
    85                         library = frame.state().get('library'),
    86                         item;
     201                        this.options.uploader = false;
     202                }
    87203
    88                 // Trigger the media frame to open the correct item
    89                 item = library.findWhere( { id: parseInt( query, 10 ) } );
    90                 item.set( 'skipHistory', true );
     204                this.gridRouter = new wp.media.view.MediaFrame.Manage.Router();
    91205
    92                 if ( item ) {
    93                         frame.trigger( 'edit:attachment', item );
    94                 } else {
    95                         item = media.attachment( query );
    96                         frame.listenTo( item, 'change', function( model ) {
    97                                 frame.stopListening( item );
    98                                 frame.trigger( 'edit:attachment', model );
    99                         } );
    100                         item.fetch();
    101                 }
    102         },
     206                // Call 'initialize' directly on the parent class.
     207                MediaFrame.prototype.initialize.apply( this, arguments );
    103208
    104         // Show the modal in edit mode with a specific item.
    105         editItem: function( query ) {
    106                 this.showItem( query );
    107                 wp.media.frames.edit.content.mode( 'edit-details' );
    108         }
    109 });
     209                // Append the frame view directly the supplied container.
     210                this.$el.appendTo( this.options.container );
    110211
    111 module.exports = Router;
     212                this.createStates();
     213                this.bindRegionModeHandlers();
     214                this.render();
     215                this.bindSearchHandler();
    112216
    113 },{}],4:[function(require,module,exports){
    114 var Details = wp.media.view.Attachment.Details,
    115         TwoColumn;
     217                wp.media.frames.browse = this;
     218        },
    116219
    117 /**
    118  * wp.media.view.Attachment.Details.TwoColumn
    119  *
    120  * A similar view to media.view.Attachment.Details
    121  * for use in the Edit Attachment modal.
    122  *
    123  * @memberOf wp.media.view.Attachment.Details
    124  *
    125  * @class
    126  * @augments wp.media.view.Attachment.Details
    127  * @augments wp.media.view.Attachment
    128  * @augments wp.media.View
    129  * @augments wp.Backbone.View
    130  * @augments Backbone.View
    131  */
    132 TwoColumn = Details.extend(/** @lends wp.media.view.Attachment.Details.TowColumn.prototype */{
    133         template: wp.template( 'attachment-details-two-column' ),
     220        bindSearchHandler: function() {
     221                var search = this.$( '#media-search-input' ),
     222                        searchView = this.browserView.toolbar.get( 'search' ).$el,
     223                        listMode = this.$( '.view-list' ),
    134224
    135         initialize: function() {
    136                 this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) );
     225                        input  = _.throttle( function (e) {
     226                                var val = $( e.currentTarget ).val(),
     227                                        url = '';
    137228
    138                 Details.prototype.initialize.apply( this, arguments );
    139         },
     229                                if ( val ) {
     230                                        url += '?search=' + val;
     231                                        this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
     232                                }
     233                        }, 1000 );
    140234
    141         editAttachment: function( event ) {
    142                 if ( event ) {
    143                         event.preventDefault();
    144                 }
    145                 this.controller.content.mode( 'edit-image' );
     235                // Update the URL when entering search string (at most once per second)
     236                search.on( 'input', _.bind( input, this ) );
     237
     238                this.gridRouter
     239                        .on( 'route:search', function () {
     240                                var href = window.location.href;
     241                                if ( href.indexOf( 'mode=' ) > -1 ) {
     242                                        href = href.replace( /mode=[^&]+/g, 'mode=list' );
     243                                } else {
     244                                        href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
     245                                }
     246                                href = href.replace( 'search=', 's=' );
     247                                listMode.prop( 'href', href );
     248                        })
     249                        .on( 'route:reset', function() {
     250                                searchView.val( '' ).trigger( 'input' );
     251                        });
    146252        },
    147253
    148254        /**
    149          * Noop this from parent class, doesn't apply here.
     255         * Create the default states for the frame.
    150256         */
    151         toggleSelectionHandler: function() {},
    152 
    153         render: function() {
    154                 Details.prototype.render.apply( this, arguments );
     257        createStates: function() {
     258                var options = this.options;
    155259
    156                 wp.media.mixin.removeAllPlayers();
    157                 this.$( 'audio, video' ).each( function (i, elem) {
    158                         var el = wp.media.view.MediaDetails.prepareSrc( elem );
    159                         new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
    160                 } );
    161         }
    162 });
     260                if ( this.options.states ) {
     261                        return;
     262                }
    163263
    164 module.exports = TwoColumn;
     264                // Add the default states.
     265                this.states.add([
     266                        new Library({
     267                                library:            wp.media.query( options.library ),
     268                                multiple:           options.multiple,
     269                                title:              options.title,
     270                                content:            'browse',
     271                                toolbar:            'select',
     272                                contentUserSetting: false,
     273                                filterable:         'all',
     274                                autoSelect:         false
     275                        })
     276                ]);
     277        },
    165278
    166 },{}],5:[function(require,module,exports){
    167 var Button = wp.media.view.Button,
    168         DeleteSelected = wp.media.view.DeleteSelectedButton,
    169         DeleteSelectedPermanently;
     279        /**
     280         * Bind region mode activation events to proper handlers.
     281         */
     282        bindRegionModeHandlers: function() {
     283                this.on( 'content:create:browse', this.browseContent, this );
    170284
    171 /**
    172  * wp.media.view.DeleteSelectedPermanentlyButton
    173  *
    174  * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
    175  *
    176  * @memberOf wp.media.view
    177  *
    178  * @class
    179  * @augments wp.media.view.DeleteSelectedButton
    180  * @augments wp.media.view.Button
    181  * @augments wp.media.View
    182  * @augments wp.Backbone.View
    183  * @augments Backbone.View
    184  */
    185 DeleteSelectedPermanently = DeleteSelected.extend(/** @lends wp.media.view.DeleteSelectedPermanentlyButton.prototype */{
    186         initialize: function() {
    187                 DeleteSelected.prototype.initialize.apply( this, arguments );
    188                 this.controller.on( 'select:activate', this.selectActivate, this );
    189                 this.controller.on( 'select:deactivate', this.selectDeactivate, this );
    190         },
     285                // Handle a frame-level event for editing an attachment.
     286                this.on( 'edit:attachment', this.openEditAttachmentModal, this );
    191287
    192         filterChange: function( model ) {
    193                 this.canShow = ( 'trash' === model.get( 'status' ) );
     288                this.on( 'select:activate', this.bindKeydown, this );
     289                this.on( 'select:deactivate', this.unbindKeydown, this );
    194290        },
    195291
    196         selectActivate: function() {
    197                 this.toggleDisabled();
    198                 this.$el.toggleClass( 'hidden', ! this.canShow );
     292        handleKeydown: function( e ) {
     293                if ( 27 === e.which ) {
     294                        e.preventDefault();
     295                        this.deactivateMode( 'select' ).activateMode( 'edit' );
     296                }
    199297        },
    200298
    201         selectDeactivate: function() {
    202                 this.toggleDisabled();
    203                 this.$el.addClass( 'hidden' );
     299        bindKeydown: function() {
     300                this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) );
    204301        },
    205302
    206         render: function() {
    207                 Button.prototype.render.apply( this, arguments );
    208                 this.selectActivate();
    209                 return this;
    210         }
    211 });
     303        unbindKeydown: function() {
     304                this.$body.off( 'keydown.select' );
     305        },
    212306
    213 module.exports = DeleteSelectedPermanently;
     307        fixPosition: function() {
     308                var $browser, $toolbar;
     309                if ( ! this.isModeActive( 'select' ) ) {
     310                        return;
     311                }
    214312
    215 },{}],6:[function(require,module,exports){
    216 var Button = wp.media.view.Button,
    217         l10n = wp.media.view.l10n,
    218         DeleteSelected;
     313                $browser = this.$('.attachments-browser');
     314                $toolbar = $browser.find('.media-toolbar');
    219315
    220 /**
    221  * wp.media.view.DeleteSelectedButton
    222  *
    223  * A button that handles bulk Delete/Trash logic
    224  *
    225  * @memberOf wp.media.view
    226  *
    227  * @class
    228  * @augments wp.media.view.Button
    229  * @augments wp.media.View
    230  * @augments wp.Backbone.View
    231  * @augments Backbone.View
    232  */
    233 DeleteSelected = Button.extend(/** @lends wp.media.view.DeleteSelectedButton.prototype */{
    234         initialize: function() {
    235                 Button.prototype.initialize.apply( this, arguments );
    236                 if ( this.options.filters ) {
    237                         this.options.filters.model.on( 'change', this.filterChange, this );
     316                // Offset doesn't appear to take top margin into account, hence +16
     317                if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) {
     318                        $browser.addClass( 'fixed' );
     319                        $toolbar.css('width', $browser.width() + 'px');
     320                } else {
     321                        $browser.removeClass( 'fixed' );
     322                        $toolbar.css('width', '');
    238323                }
    239                 this.controller.on( 'selection:toggle', this.toggleDisabled, this );
    240324        },
    241325
    242         filterChange: function( model ) {
    243                 if ( 'trash' === model.get( 'status' ) ) {
    244                         this.model.set( 'text', l10n.untrashSelected );
    245                 } else if ( wp.media.view.settings.mediaTrash ) {
    246                         this.model.set( 'text', l10n.trashSelected );
     326        /**
     327         * Click handler for the `Add New` button.
     328         */
     329        addNewClickHandler: function( event ) {
     330                event.preventDefault();
     331                this.trigger( 'toggle:upload:attachment' );
     332
     333                if ( this.uploader ) {
     334                        this.uploader.refresh();
     335                }
     336        },
     337
     338        /**
     339         * Open the Edit Attachment modal.
     340         */
     341        openEditAttachmentModal: function( model ) {
     342                // Create a new EditAttachment frame, passing along the library and the attachment model.
     343                if ( wp.media.frames.edit ) {
     344                        wp.media.frames.edit.open().trigger( 'refresh', model );
    247345                } else {
    248                         this.model.set( 'text', l10n.deleteSelected );
     346                        wp.media.frames.edit = wp.media( {
     347                                frame:       'edit-attachments',
     348                                controller:  this,
     349                                library:     this.state().get('library'),
     350                                model:       model
     351                        } );
    249352                }
    250353        },
    251354
    252         toggleDisabled: function() {
    253                 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
     355        /**
     356         * Create an attachments browser view within the content region.
     357         *
     358         * @param {Object} contentRegion Basic object with a `view` property, which
     359         *                               should be set with the proper region view.
     360         * @this wp.media.controller.Region
     361         */
     362        browseContent: function( contentRegion ) {
     363                var state = this.state();
     364
     365                // Browse our library of attachments.
     366                this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({
     367                        controller: this,
     368                        collection: state.get('library'),
     369                        selection:  state.get('selection'),
     370                        model:      state,
     371                        sortable:   state.get('sortable'),
     372                        search:     state.get('searchable'),
     373                        filters:    state.get('filterable'),
     374                        date:       state.get('date'),
     375                        display:    state.get('displaySettings'),
     376                        dragInfo:   state.get('dragInfo'),
     377                        sidebar:    'errors',
     378
     379                        suggestedWidth:  state.get('suggestedWidth'),
     380                        suggestedHeight: state.get('suggestedHeight'),
     381
     382                        AttachmentView: state.get('AttachmentView'),
     383
     384                        scrollElement: document
     385                });
     386                this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) );
     387
     388                this.errors = wp.Uploader.errors;
     389                this.errors.on( 'add remove reset', this.sidebarVisibility, this );
    254390        },
    255391
    256         render: function() {
    257                 Button.prototype.render.apply( this, arguments );
    258                 if ( this.controller.isModeActive( 'select' ) ) {
    259                         this.$el.addClass( 'delete-selected-button' );
    260                 } else {
    261                         this.$el.addClass( 'delete-selected-button hidden' );
     392        sidebarVisibility: function() {
     393                this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length );
     394        },
     395
     396        bindDeferred: function() {
     397                if ( ! this.browserView.dfd ) {
     398                        return;
     399                }
     400                this.browserView.dfd.done( _.bind( this.startHistory, this ) );
     401        },
     402
     403        startHistory: function() {
     404                // Verify pushState support and activate
     405                if ( window.history && window.history.pushState ) {
     406                        if ( Backbone.History.started ) {
     407                                Backbone.history.stop();
     408                        }
     409                        Backbone.history.start( {
     410                                root: window._wpMediaGridSettings.adminUrl,
     411                                pushState: true
     412                        } );
    262413                }
    263                 this.toggleDisabled();
    264                 return this;
    265414        }
    266415});
    267416
    268 module.exports = DeleteSelected;
     417module.exports = Manage;
    269418
    270 },{}],7:[function(require,module,exports){
    271419
    272 var Button = wp.media.view.Button,
    273         l10n = wp.media.view.l10n,
    274         SelectModeToggle;
     420/***/ }),
     421/* 13 */
     422/***/ (function(module, exports) {
     423
     424var Details = wp.media.view.Attachment.Details,
     425        TwoColumn;
    275426
    276427/**
    277  * wp.media.view.SelectModeToggleButton
     428 * wp.media.view.Attachment.Details.TwoColumn
    278429 *
    279  * @memberOf wp.media.view
     430 * A similar view to media.view.Attachment.Details
     431 * for use in the Edit Attachment modal.
     432 *
     433 * @memberOf wp.media.view.Attachment.Details
    280434 *
    281435 * @class
    282  * @augments wp.media.view.Button
     436 * @augments wp.media.view.Attachment.Details
     437 * @augments wp.media.view.Attachment
    283438 * @augments wp.media.View
    284439 * @augments wp.Backbone.View
    285440 * @augments Backbone.View
    286441 */
    287 SelectModeToggle = Button.extend(/** @lends wp.media.view.SelectModeToggle.prototype */{
    288         initialize: function() {
    289                 _.defaults( this.options, {
    290                         size : ''
    291                 } );
     442TwoColumn = Details.extend(/** @lends wp.media.view.Attachment.Details.TowColumn.prototype */{
     443        template: wp.template( 'attachment-details-two-column' ),
    292444
    293                 Button.prototype.initialize.apply( this, arguments );
    294                 this.controller.on( 'select:activate select:deactivate', this.toggleBulkEditHandler, this );
    295                 this.controller.on( 'selection:action:done', this.back, this );
    296         },
     445        initialize: function() {
     446                this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) );
    297447
    298         back: function () {
    299                 this.controller.deactivateMode( 'select' ).activateMode( 'edit' );
     448                Details.prototype.initialize.apply( this, arguments );
    300449        },
    301450
    302         click: function() {
    303                 Button.prototype.click.apply( this, arguments );
    304                 if ( this.controller.isModeActive( 'select' ) ) {
    305                         this.back();
    306                 } else {
    307                         this.controller.deactivateMode( 'edit' ).activateMode( 'select' );
     451        editAttachment: function( event ) {
     452                if ( event ) {
     453                        event.preventDefault();
    308454                }
     455                this.controller.content.mode( 'edit-image' );
    309456        },
    310457
    311         render: function() {
    312                 Button.prototype.render.apply( this, arguments );
    313                 this.$el.addClass( 'select-mode-toggle-button' );
    314                 return this;
    315         },
    316 
    317         toggleBulkEditHandler: function() {
    318                 var toolbar = this.controller.content.get().toolbar, children;
     458        /**
     459         * Noop this from parent class, doesn't apply here.
     460         */
     461        toggleSelectionHandler: function() {},
    319462
    320                 children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' );
     463        render: function() {
     464                Details.prototype.render.apply( this, arguments );
    321465
    322                 // TODO: the Frame should be doing all of this.
    323                 if ( this.controller.isModeActive( 'select' ) ) {
    324                         this.model.set( {
    325                                 size: 'large',
    326                                 text: l10n.cancelSelection
    327                         } );
    328                         children.not( '.spinner, .media-button' ).hide();
    329                         this.$el.show();
    330                         toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' );
    331                 } else {
    332                         this.model.set( {
    333                                 size: '',
    334                                 text: l10n.bulkSelect
    335                         } );
    336                         this.controller.content.get().$el.removeClass( 'fixed' );
    337                         toolbar.$el.css( 'width', '' );
    338                         toolbar.$( '.delete-selected-button' ).addClass( 'hidden' );
    339                         children.not( '.media-button' ).show();
    340                         this.controller.state().get( 'selection' ).reset();
    341                 }
     466                wp.media.mixin.removeAllPlayers();
     467                this.$( 'audio, video' ).each( function (i, elem) {
     468                        var el = wp.media.view.MediaDetails.prepareSrc( elem );
     469                        new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
     470                } );
    342471        }
    343472});
    344473
    345 module.exports = SelectModeToggle;
     474module.exports = TwoColumn;
    346475
    347 },{}],8:[function(require,module,exports){
    348 var View = wp.media.View,
    349         EditImage = wp.media.view.EditImage,
    350         Details;
     476
     477/***/ }),
     478/* 14 */
     479/***/ (function(module, exports) {
    351480
    352481/**
    353  * wp.media.view.EditImage.Details
     482 * wp.media.view.MediaFrame.Manage.Router
    354483 *
    355  * @memberOf wp.media.view.EditImage
     484 * A router for handling the browser history and application state.
     485 *
     486 * @memberOf wp.media.view.MediaFrame.Manage
    356487 *
    357488 * @class
    358  * @augments wp.media.view.EditImage
    359  * @augments wp.media.View
    360  * @augments wp.Backbone.View
    361  * @augments Backbone.View
     489 * @augments Backbone.Router
    362490 */
    363 Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype */{
     491var Router = Backbone.Router.extend(/** @lends wp.media.view.MediaFrame.Manage.Router.prototype */{
     492        routes: {
     493                'upload.php?item=:slug&mode=edit': 'editItem',
     494                'upload.php?item=:slug':           'showItem',
     495                'upload.php?search=:query':        'search',
     496                'upload.php':                      'reset'
     497        },
     498
     499        // Map routes against the page URL
     500        baseUrl: function( url ) {
     501                return 'upload.php' + url;
     502        },
     503
     504        reset: function() {
     505                var frame = wp.media.frames.edit;
     506
     507                if ( frame ) {
     508                        frame.close();
     509                }
     510        },
     511
     512        // Respond to the search route by filling the search field and trigggering the input event
     513        search: function( query ) {
     514                jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
     515        },
     516
     517        // Show the modal with a specific item
     518        showItem: function( query ) {
     519                var media = wp.media,
     520                        frame = media.frames.browse,
     521                        library = frame.state().get('library'),
     522                        item;
     523
     524                // Trigger the media frame to open the correct item
     525                item = library.findWhere( { id: parseInt( query, 10 ) } );
     526                item.set( 'skipHistory', true );
     527
     528                if ( item ) {
     529                        frame.trigger( 'edit:attachment', item );
     530                } else {
     531                        item = media.attachment( query );
     532                        frame.listenTo( item, 'change', function( model ) {
     533                                frame.stopListening( item );
     534                                frame.trigger( 'edit:attachment', model );
     535                        } );
     536                        item.fetch();
     537                }
     538        },
     539
     540        // Show the modal in edit mode with a specific item.
     541        editItem: function( query ) {
     542                this.showItem( query );
     543                wp.media.frames.edit.content.mode( 'edit-details' );
     544        }
     545});
     546
     547module.exports = Router;
     548
     549
     550/***/ }),
     551/* 15 */
     552/***/ (function(module, exports) {
     553
     554var View = wp.media.View,
     555        EditImage = wp.media.view.EditImage,
     556        Details;
     557
     558/**
     559 * wp.media.view.EditImage.Details
     560 *
     561 * @memberOf wp.media.view.EditImage
     562 *
     563 * @class
     564 * @augments wp.media.view.EditImage
     565 * @augments wp.media.View
     566 * @augments wp.Backbone.View
     567 * @augments Backbone.View
     568 */
     569Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype */{
    364570        initialize: function( options ) {
    365571                this.editor = window.imageEdit;
    366572                this.frame = options.frame;
    Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype 
    381587
    382588module.exports = Details;
    383589
    384 },{}],9:[function(require,module,exports){
     590
     591/***/ }),
     592/* 16 */
     593/***/ (function(module, exports) {
     594
    385595var Frame = wp.media.view.Frame,
    386596        MediaFrame = wp.media.view.MediaFrame,
    387597
    EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta 
    643853
    644854module.exports = EditAttachments;
    645855
    646 },{}],10:[function(require,module,exports){
    647 var MediaFrame = wp.media.view.MediaFrame,
    648         Library = wp.media.controller.Library,
    649856
    650         $ = Backbone.$,
    651         Manage;
     857/***/ }),
     858/* 17 */
     859/***/ (function(module, exports) {
     860
     861
     862var Button = wp.media.view.Button,
     863        l10n = wp.media.view.l10n,
     864        SelectModeToggle;
    652865
    653866/**
    654  * wp.media.view.MediaFrame.Manage
    655  *
    656  * A generic management frame workflow.
    657  *
    658  * Used in the media grid view.
     867 * wp.media.view.SelectModeToggleButton
    659868 *
    660  * @memberOf wp.media.view.MediaFrame
     869 * @memberOf wp.media.view
    661870 *
    662871 * @class
    663  * @augments wp.media.view.MediaFrame
    664  * @augments wp.media.view.Frame
     872 * @augments wp.media.view.Button
    665873 * @augments wp.media.View
    666874 * @augments wp.Backbone.View
    667875 * @augments Backbone.View
    668  * @mixes wp.media.controller.StateMachine
    669876 */
    670 Manage = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Manage.prototype */{
    671         /**
    672          * @constructs
    673          */
     877SelectModeToggle = Button.extend(/** @lends wp.media.view.SelectModeToggle.prototype */{
    674878        initialize: function() {
    675879                _.defaults( this.options, {
    676                         title:     '',
    677                         modal:     false,
    678                         selection: [],
    679                         library:   {}, // Options hash for the query to the media library.
    680                         multiple:  'add',
    681                         state:     'library',
    682                         uploader:  true,
    683                         mode:      [ 'grid', 'edit' ]
    684                 });
    685 
    686                 this.$body = $( document.body );
    687                 this.$window = $( window );
    688                 this.$adminBar = $( '#wpadminbar' );
    689                 // Store the Add New button for later reuse in wp.media.view.UploaderInline.
    690                 this.$uploaderToggler = $( '.page-title-action' )
    691                         .attr( 'aria-expanded', 'false' )
    692                         .on( 'click', _.bind( this.addNewClickHandler, this ) );
    693 
    694                 this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) );
    695 
    696                 // Ensure core and media grid view UI is enabled.
    697                 this.$el.addClass('wp-core-ui');
     880                        size : ''
     881                } );
    698882
    699                 // Force the uploader off if the upload limit has been exceeded or
    700                 // if the browser isn't supported.
    701                 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
    702                         this.options.uploader = false;
    703                 }
     883                Button.prototype.initialize.apply( this, arguments );
     884                this.controller.on( 'select:activate select:deactivate', this.toggleBulkEditHandler, this );
     885                this.controller.on( 'selection:action:done', this.back, this );
     886        },
    704887
    705                 // Initialize a window-wide uploader.
    706                 if ( this.options.uploader ) {
    707                         this.uploader = new wp.media.view.UploaderWindow({
    708                                 controller: this,
    709                                 uploader: {
    710                                         dropzone:  document.body,
    711                                         container: document.body
    712                                 }
    713                         }).render();
    714                         this.uploader.ready();
    715                         $('body').append( this.uploader.el );
     888        back: function () {
     889                this.controller.deactivateMode( 'select' ).activateMode( 'edit' );
     890        },
    716891
    717                         this.options.uploader = false;
     892        click: function() {
     893                Button.prototype.click.apply( this, arguments );
     894                if ( this.controller.isModeActive( 'select' ) ) {
     895                        this.back();
     896                } else {
     897                        this.controller.deactivateMode( 'edit' ).activateMode( 'select' );
    718898                }
    719 
    720                 this.gridRouter = new wp.media.view.MediaFrame.Manage.Router();
    721 
    722                 // Call 'initialize' directly on the parent class.
    723                 MediaFrame.prototype.initialize.apply( this, arguments );
    724 
    725                 // Append the frame view directly the supplied container.
    726                 this.$el.appendTo( this.options.container );
    727 
    728                 this.createStates();
    729                 this.bindRegionModeHandlers();
    730                 this.render();
    731                 this.bindSearchHandler();
    732 
    733                 wp.media.frames.browse = this;
    734899        },
    735900
    736         bindSearchHandler: function() {
    737                 var search = this.$( '#media-search-input' ),
    738                         searchView = this.browserView.toolbar.get( 'search' ).$el,
    739                         listMode = this.$( '.view-list' ),
    740 
    741                         input  = _.throttle( function (e) {
    742                                 var val = $( e.currentTarget ).val(),
    743                                         url = '';
    744 
    745                                 if ( val ) {
    746                                         url += '?search=' + val;
    747                                         this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
    748                                 }
    749                         }, 1000 );
    750 
    751                 // Update the URL when entering search string (at most once per second)
    752                 search.on( 'input', _.bind( input, this ) );
    753 
    754                 this.gridRouter
    755                         .on( 'route:search', function () {
    756                                 var href = window.location.href;
    757                                 if ( href.indexOf( 'mode=' ) > -1 ) {
    758                                         href = href.replace( /mode=[^&]+/g, 'mode=list' );
    759                                 } else {
    760                                         href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
    761                                 }
    762                                 href = href.replace( 'search=', 's=' );
    763                                 listMode.prop( 'href', href );
    764                         })
    765                         .on( 'route:reset', function() {
    766                                 searchView.val( '' ).trigger( 'input' );
    767                         });
     901        render: function() {
     902                Button.prototype.render.apply( this, arguments );
     903                this.$el.addClass( 'select-mode-toggle-button' );
     904                return this;
    768905        },
    769906
    770         /**
    771          * Create the default states for the frame.
    772          */
    773         createStates: function() {
    774                 var options = this.options;
     907        toggleBulkEditHandler: function() {
     908                var toolbar = this.controller.content.get().toolbar, children;
    775909
    776                 if ( this.options.states ) {
    777                         return;
     910                children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' );
     911
     912                // TODO: the Frame should be doing all of this.
     913                if ( this.controller.isModeActive( 'select' ) ) {
     914                        this.model.set( {
     915                                size: 'large',
     916                                text: l10n.cancelSelection
     917                        } );
     918                        children.not( '.spinner, .media-button' ).hide();
     919                        this.$el.show();
     920                        toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' );
     921                } else {
     922                        this.model.set( {
     923                                size: '',
     924                                text: l10n.bulkSelect
     925                        } );
     926                        this.controller.content.get().$el.removeClass( 'fixed' );
     927                        toolbar.$el.css( 'width', '' );
     928                        toolbar.$( '.delete-selected-button' ).addClass( 'hidden' );
     929                        children.not( '.media-button' ).show();
     930                        this.controller.state().get( 'selection' ).reset();
    778931                }
     932        }
     933});
    779934
    780                 // Add the default states.
    781                 this.states.add([
    782                         new Library({
    783                                 library:            wp.media.query( options.library ),
    784                                 multiple:           options.multiple,
    785                                 title:              options.title,
    786                                 content:            'browse',
    787                                 toolbar:            'select',
    788                                 contentUserSetting: false,
    789                                 filterable:         'all',
    790                                 autoSelect:         false
    791                         })
    792                 ]);
    793         },
     935module.exports = SelectModeToggle;
    794936
    795         /**
    796          * Bind region mode activation events to proper handlers.
    797          */
    798         bindRegionModeHandlers: function() {
    799                 this.on( 'content:create:browse', this.browseContent, this );
    800937
    801                 // Handle a frame-level event for editing an attachment.
    802                 this.on( 'edit:attachment', this.openEditAttachmentModal, this );
     938/***/ }),
     939/* 18 */
     940/***/ (function(module, exports) {
    803941
    804                 this.on( 'select:activate', this.bindKeydown, this );
    805                 this.on( 'select:deactivate', this.unbindKeydown, this );
    806         },
     942var Button = wp.media.view.Button,
     943        l10n = wp.media.view.l10n,
     944        DeleteSelected;
    807945
    808         handleKeydown: function( e ) {
    809                 if ( 27 === e.which ) {
    810                         e.preventDefault();
    811                         this.deactivateMode( 'select' ).activateMode( 'edit' );
     946/**
     947 * wp.media.view.DeleteSelectedButton
     948 *
     949 * A button that handles bulk Delete/Trash logic
     950 *
     951 * @memberOf wp.media.view
     952 *
     953 * @class
     954 * @augments wp.media.view.Button
     955 * @augments wp.media.View
     956 * @augments wp.Backbone.View
     957 * @augments Backbone.View
     958 */
     959DeleteSelected = Button.extend(/** @lends wp.media.view.DeleteSelectedButton.prototype */{
     960        initialize: function() {
     961                Button.prototype.initialize.apply( this, arguments );
     962                if ( this.options.filters ) {
     963                        this.options.filters.model.on( 'change', this.filterChange, this );
    812964                }
     965                this.controller.on( 'selection:toggle', this.toggleDisabled, this );
    813966        },
    814967
    815         bindKeydown: function() {
    816                 this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) );
    817         },
    818 
    819         unbindKeydown: function() {
    820                 this.$body.off( 'keydown.select' );
    821         },
    822 
    823         fixPosition: function() {
    824                 var $browser, $toolbar;
    825                 if ( ! this.isModeActive( 'select' ) ) {
    826                         return;
    827                 }
    828 
    829                 $browser = this.$('.attachments-browser');
    830                 $toolbar = $browser.find('.media-toolbar');
    831 
    832                 // Offset doesn't appear to take top margin into account, hence +16
    833                 if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) {
    834                         $browser.addClass( 'fixed' );
    835                         $toolbar.css('width', $browser.width() + 'px');
     968        filterChange: function( model ) {
     969                if ( 'trash' === model.get( 'status' ) ) {
     970                        this.model.set( 'text', l10n.untrashSelected );
     971                } else if ( wp.media.view.settings.mediaTrash ) {
     972                        this.model.set( 'text', l10n.trashSelected );
    836973                } else {
    837                         $browser.removeClass( 'fixed' );
    838                         $toolbar.css('width', '');
     974                        this.model.set( 'text', l10n.deleteSelected );
    839975                }
    840976        },
    841977
    842         /**
    843          * Click handler for the `Add New` button.
    844          */
    845         addNewClickHandler: function( event ) {
    846                 event.preventDefault();
    847                 this.trigger( 'toggle:upload:attachment' );
    848 
    849                 if ( this.uploader ) {
    850                         this.uploader.refresh();
    851                 }
     978        toggleDisabled: function() {
     979                this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
    852980        },
    853981
    854         /**
    855          * Open the Edit Attachment modal.
    856          */
    857         openEditAttachmentModal: function( model ) {
    858                 // Create a new EditAttachment frame, passing along the library and the attachment model.
    859                 if ( wp.media.frames.edit ) {
    860                         wp.media.frames.edit.open().trigger( 'refresh', model );
     982        render: function() {
     983                Button.prototype.render.apply( this, arguments );
     984                if ( this.controller.isModeActive( 'select' ) ) {
     985                        this.$el.addClass( 'delete-selected-button' );
    861986                } else {
    862                         wp.media.frames.edit = wp.media( {
    863                                 frame:       'edit-attachments',
    864                                 controller:  this,
    865                                 library:     this.state().get('library'),
    866                                 model:       model
    867                         } );
     987                        this.$el.addClass( 'delete-selected-button hidden' );
    868988                }
    869         },
     989                this.toggleDisabled();
     990                return this;
     991        }
     992});
    870993
    871         /**
    872          * Create an attachments browser view within the content region.
    873          *
    874          * @param {Object} contentRegion Basic object with a `view` property, which
    875          *                               should be set with the proper region view.
    876          * @this wp.media.controller.Region
    877          */
    878         browseContent: function( contentRegion ) {
    879                 var state = this.state();
     994module.exports = DeleteSelected;
    880995
    881                 // Browse our library of attachments.
    882                 this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({
    883                         controller: this,
    884                         collection: state.get('library'),
    885                         selection:  state.get('selection'),
    886                         model:      state,
    887                         sortable:   state.get('sortable'),
    888                         search:     state.get('searchable'),
    889                         filters:    state.get('filterable'),
    890                         date:       state.get('date'),
    891                         display:    state.get('displaySettings'),
    892                         dragInfo:   state.get('dragInfo'),
    893                         sidebar:    'errors',
    894996
    895                         suggestedWidth:  state.get('suggestedWidth'),
    896                         suggestedHeight: state.get('suggestedHeight'),
     997/***/ }),
     998/* 19 */
     999/***/ (function(module, exports) {
    8971000
    898                         AttachmentView: state.get('AttachmentView'),
     1001var Button = wp.media.view.Button,
     1002        DeleteSelected = wp.media.view.DeleteSelectedButton,
     1003        DeleteSelectedPermanently;
    8991004
    900                         scrollElement: document
    901                 });
    902                 this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) );
     1005/**
     1006 * wp.media.view.DeleteSelectedPermanentlyButton
     1007 *
     1008 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
     1009 *
     1010 * @memberOf wp.media.view
     1011 *
     1012 * @class
     1013 * @augments wp.media.view.DeleteSelectedButton
     1014 * @augments wp.media.view.Button
     1015 * @augments wp.media.View
     1016 * @augments wp.Backbone.View
     1017 * @augments Backbone.View
     1018 */
     1019DeleteSelectedPermanently = DeleteSelected.extend(/** @lends wp.media.view.DeleteSelectedPermanentlyButton.prototype */{
     1020        initialize: function() {
     1021                DeleteSelected.prototype.initialize.apply( this, arguments );
     1022                this.controller.on( 'select:activate', this.selectActivate, this );
     1023                this.controller.on( 'select:deactivate', this.selectDeactivate, this );
     1024        },
    9031025
    904                 this.errors = wp.Uploader.errors;
    905                 this.errors.on( 'add remove reset', this.sidebarVisibility, this );
     1026        filterChange: function( model ) {
     1027                this.canShow = ( 'trash' === model.get( 'status' ) );
    9061028        },
    9071029
    908         sidebarVisibility: function() {
    909                 this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length );
     1030        selectActivate: function() {
     1031                this.toggleDisabled();
     1032                this.$el.toggleClass( 'hidden', ! this.canShow );
    9101033        },
    9111034
    912         bindDeferred: function() {
    913                 if ( ! this.browserView.dfd ) {
    914                         return;
    915                 }
    916                 this.browserView.dfd.done( _.bind( this.startHistory, this ) );
     1035        selectDeactivate: function() {
     1036                this.toggleDisabled();
     1037                this.$el.addClass( 'hidden' );
    9171038        },
    9181039
    919         startHistory: function() {
    920                 // Verify pushState support and activate
    921                 if ( window.history && window.history.pushState ) {
    922                         if ( Backbone.History.started ) {
    923                                 Backbone.history.stop();
    924                         }
    925                         Backbone.history.start( {
    926                                 root: window._wpMediaGridSettings.adminUrl,
    927                                 pushState: true
    928                         } );
    929                 }
     1040        render: function() {
     1041                Button.prototype.render.apply( this, arguments );
     1042                this.selectActivate();
     1043                return this;
    9301044        }
    9311045});
    9321046
    933 module.exports = Manage;
     1047module.exports = DeleteSelectedPermanently;
     1048
    9341049
    935 },{}]},{},[2]);
     1050/***/ })
     1051/******/ ]);
     1052 No newline at end of file
  • src/wp-includes/js/media-models.js

    diff --git src/wp-includes/js/media-models.js src/wp-includes/js/media-models.js
    index caa7fcb590..af64387257 100644
     
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     1/******/ (function(modules) { // webpackBootstrap
     2/******/        // The module cache
     3/******/        var installedModules = {};
     4/******/
     5/******/        // The require function
     6/******/        function __webpack_require__(moduleId) {
     7/******/
     8/******/                // Check if module is in cache
     9/******/                if(installedModules[moduleId]) {
     10/******/                        return installedModules[moduleId].exports;
     11/******/                }
     12/******/                // Create a new module (and put it into the cache)
     13/******/                var module = installedModules[moduleId] = {
     14/******/                        i: moduleId,
     15/******/                        l: false,
     16/******/                        exports: {}
     17/******/                };
     18/******/
     19/******/                // Execute the module function
     20/******/                modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/                // Flag the module as loaded
     23/******/                module.l = true;
     24/******/
     25/******/                // Return the exports of the module
     26/******/                return module.exports;
     27/******/        }
     28/******/
     29/******/
     30/******/        // expose the modules object (__webpack_modules__)
     31/******/        __webpack_require__.m = modules;
     32/******/
     33/******/        // expose the module cache
     34/******/        __webpack_require__.c = installedModules;
     35/******/
     36/******/        // define getter function for harmony exports
     37/******/        __webpack_require__.d = function(exports, name, getter) {
     38/******/                if(!__webpack_require__.o(exports, name)) {
     39/******/                        Object.defineProperty(exports, name, {
     40/******/                                configurable: false,
     41/******/                                enumerable: true,
     42/******/                                get: getter
     43/******/                        });
     44/******/                }
     45/******/        };
     46/******/
     47/******/        // getDefaultExport function for compatibility with non-harmony modules
     48/******/        __webpack_require__.n = function(module) {
     49/******/                var getter = module && module.__esModule ?
     50/******/                        function getDefault() { return module['default']; } :
     51/******/                        function getModuleExports() { return module; };
     52/******/                __webpack_require__.d(getter, 'a', getter);
     53/******/                return getter;
     54/******/        };
     55/******/
     56/******/        // Object.prototype.hasOwnProperty.call
     57/******/        __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/        // __webpack_public_path__
     60/******/        __webpack_require__.p = "";
     61/******/
     62/******/        // Load entry module and return exports
     63/******/        return __webpack_require__(__webpack_require__.s = 20);
     64/******/ })
     65/************************************************************************/
     66/******/ ({
     67
     68/***/ 20:
     69/***/ (function(module, exports, __webpack_require__) {
     70
    271var $ = jQuery,
    372        Attachment, Attachments, l10n, media;
    473
    l10n = media.model.l10n = window._wpMediaModelsL10n || {}; 
    65134media.model.settings = l10n.settings || {};
    66135delete l10n.settings;
    67136
    68 Attachment = media.model.Attachment = require( './models/attachment.js' );
    69 Attachments = media.model.Attachments = require( './models/attachments.js' );
     137Attachment = media.model.Attachment = __webpack_require__( 21 );
     138Attachments = media.model.Attachments = __webpack_require__( 22 );
    70139
    71 media.model.Query = require( './models/query.js' );
    72 media.model.PostImage = require( './models/post-image.js' );
    73 media.model.Selection = require( './models/selection.js' );
     140media.model.Query = __webpack_require__( 23 );
     141media.model.PostImage = __webpack_require__( 24 );
     142media.model.Selection = __webpack_require__( 25 );
    74143
    75144/**
    76145 * ========================================================================
    $(window).on('unload', function(){ 
    238307        window.wp = null;
    239308});
    240309
    241 },{"./models/attachment.js":2,"./models/attachments.js":3,"./models/post-image.js":4,"./models/query.js":5,"./models/selection.js":6}],2:[function(require,module,exports){
     310
     311/***/ }),
     312
     313/***/ 21:
     314/***/ (function(module, exports) {
     315
    242316var $ = Backbone.$,
    243317        Attachment;
    244318
    Attachment = Backbone.Model.extend(/** @lends wp.media.model.Attachment.prototyp 
    409483
    410484module.exports = Attachment;
    411485
    412 },{}],3:[function(require,module,exports){
     486
     487/***/ }),
     488
     489/***/ 22:
     490/***/ (function(module, exports) {
     491
    413492/**
    414493 * wp.media.model.Attachments
    415494 *
    var Attachments = Backbone.Collection.extend(/** @lends wp.media.model.Attachmen 
    9541033
    9551034module.exports = Attachments;
    9561035
    957 },{}],4:[function(require,module,exports){
    958 /**
    959  * wp.media.model.PostImage
    960  *
    961  * An instance of an image that's been embedded into a post.
    962  *
    963  * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails.
    964  *
    965  * @memberOf wp.media.model
    966  *
    967  * @class
    968  * @augments Backbone.Model
    969  *
    970  * @param {int} [attributes]               Initial model attributes.
    971  * @param {int} [attributes.attachment_id] ID of the attachment.
    972  **/
    973 var PostImage = Backbone.Model.extend(/** @lends wp.media.model.PostImage.prototype */{
    974 
    975         initialize: function( attributes ) {
    976                 var Attachment = wp.media.model.Attachment;
    977                 this.attachment = false;
    9781036
    979                 if ( attributes.attachment_id ) {
    980                         this.attachment = Attachment.get( attributes.attachment_id );
    981                         if ( this.attachment.get( 'url' ) ) {
    982                                 this.dfd = jQuery.Deferred();
    983                                 this.dfd.resolve();
    984                         } else {
    985                                 this.dfd = this.attachment.fetch();
    986                         }
    987                         this.bindAttachmentListeners();
    988                 }
     1037/***/ }),
    9891038
    990                 // keep url in sync with changes to the type of link
    991                 this.on( 'change:link', this.updateLinkUrl, this );
    992                 this.on( 'change:size', this.updateSize, this );
     1039/***/ 23:
     1040/***/ (function(module, exports) {
    9931041
    994                 this.setLinkTypeFromUrl();
    995                 this.setAspectRatio();
    996 
    997                 this.set( 'originalUrl', attributes.url );
    998         },
    999 
    1000         bindAttachmentListeners: function() {
    1001                 this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );
    1002                 this.listenTo( this.attachment, 'sync', this.setAspectRatio );
    1003                 this.listenTo( this.attachment, 'change', this.updateSize );
    1004         },
    1005 
    1006         changeAttachment: function( attachment, props ) {
    1007                 this.stopListening( this.attachment );
    1008                 this.attachment = attachment;
    1009                 this.bindAttachmentListeners();
    1010 
    1011                 this.set( 'attachment_id', this.attachment.get( 'id' ) );
    1012                 this.set( 'caption', this.attachment.get( 'caption' ) );
    1013                 this.set( 'alt', this.attachment.get( 'alt' ) );
    1014                 this.set( 'size', props.get( 'size' ) );
    1015                 this.set( 'align', props.get( 'align' ) );
    1016                 this.set( 'link', props.get( 'link' ) );
    1017                 this.updateLinkUrl();
    1018                 this.updateSize();
    1019         },
    1020 
    1021         setLinkTypeFromUrl: function() {
    1022                 var linkUrl = this.get( 'linkUrl' ),
    1023                         type;
    1024 
    1025                 if ( ! linkUrl ) {
    1026                         this.set( 'link', 'none' );
    1027                         return;
    1028                 }
    1029 
    1030                 // default to custom if there is a linkUrl
    1031                 type = 'custom';
    1032 
    1033                 if ( this.attachment ) {
    1034                         if ( this.attachment.get( 'url' ) === linkUrl ) {
    1035                                 type = 'file';
    1036                         } else if ( this.attachment.get( 'link' ) === linkUrl ) {
    1037                                 type = 'post';
    1038                         }
    1039                 } else {
    1040                         if ( this.get( 'url' ) === linkUrl ) {
    1041                                 type = 'file';
    1042                         }
    1043                 }
    1044 
    1045                 this.set( 'link', type );
    1046         },
    1047 
    1048         updateLinkUrl: function() {
    1049                 var link = this.get( 'link' ),
    1050                         url;
    1051 
    1052                 switch( link ) {
    1053                         case 'file':
    1054                                 if ( this.attachment ) {
    1055                                         url = this.attachment.get( 'url' );
    1056                                 } else {
    1057                                         url = this.get( 'url' );
    1058                                 }
    1059                                 this.set( 'linkUrl', url );
    1060                                 break;
    1061                         case 'post':
    1062                                 this.set( 'linkUrl', this.attachment.get( 'link' ) );
    1063                                 break;
    1064                         case 'none':
    1065                                 this.set( 'linkUrl', '' );
    1066                                 break;
    1067                 }
    1068         },
    1069 
    1070         updateSize: function() {
    1071                 var size;
    1072 
    1073                 if ( ! this.attachment ) {
    1074                         return;
    1075                 }
    1076 
    1077                 if ( this.get( 'size' ) === 'custom' ) {
    1078                         this.set( 'width', this.get( 'customWidth' ) );
    1079                         this.set( 'height', this.get( 'customHeight' ) );
    1080                         this.set( 'url', this.get( 'originalUrl' ) );
    1081                         return;
    1082                 }
    1083 
    1084                 size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ];
    1085 
    1086                 if ( ! size ) {
    1087                         return;
    1088                 }
    1089 
    1090                 this.set( 'url', size.url );
    1091                 this.set( 'width', size.width );
    1092                 this.set( 'height', size.height );
    1093         },
    1094 
    1095         setAspectRatio: function() {
    1096                 var full;
    1097 
    1098                 if ( this.attachment && this.attachment.get( 'sizes' ) ) {
    1099                         full = this.attachment.get( 'sizes' ).full;
    1100 
    1101                         if ( full ) {
    1102                                 this.set( 'aspectRatio', full.width / full.height );
    1103                                 return;
    1104                         }
    1105                 }
    1106 
    1107                 this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) );
    1108         }
    1109 });
    1110 
    1111 module.exports = PostImage;
    1112 
    1113 },{}],5:[function(require,module,exports){
    11141042var Attachments = wp.media.model.Attachments,
    11151043        Query;
    11161044
    Query = Attachments.extend(/** @lends wp.media.model.Query.prototype */{ 
    14181346
    14191347module.exports = Query;
    14201348
    1421 },{}],6:[function(require,module,exports){
     1349
     1350/***/ }),
     1351
     1352/***/ 24:
     1353/***/ (function(module, exports) {
     1354
     1355/**
     1356 * wp.media.model.PostImage
     1357 *
     1358 * An instance of an image that's been embedded into a post.
     1359 *
     1360 * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails.
     1361 *
     1362 * @memberOf wp.media.model
     1363 *
     1364 * @class
     1365 * @augments Backbone.Model
     1366 *
     1367 * @param {int} [attributes]               Initial model attributes.
     1368 * @param {int} [attributes.attachment_id] ID of the attachment.
     1369 **/
     1370var PostImage = Backbone.Model.extend(/** @lends wp.media.model.PostImage.prototype */{
     1371
     1372        initialize: function( attributes ) {
     1373                var Attachment = wp.media.model.Attachment;
     1374                this.attachment = false;
     1375
     1376                if ( attributes.attachment_id ) {
     1377                        this.attachment = Attachment.get( attributes.attachment_id );
     1378                        if ( this.attachment.get( 'url' ) ) {
     1379                                this.dfd = jQuery.Deferred();
     1380                                this.dfd.resolve();
     1381                        } else {
     1382                                this.dfd = this.attachment.fetch();
     1383                        }
     1384                        this.bindAttachmentListeners();
     1385                }
     1386
     1387                // keep url in sync with changes to the type of link
     1388                this.on( 'change:link', this.updateLinkUrl, this );
     1389                this.on( 'change:size', this.updateSize, this );
     1390
     1391                this.setLinkTypeFromUrl();
     1392                this.setAspectRatio();
     1393
     1394                this.set( 'originalUrl', attributes.url );
     1395        },
     1396
     1397        bindAttachmentListeners: function() {
     1398                this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );
     1399                this.listenTo( this.attachment, 'sync', this.setAspectRatio );
     1400                this.listenTo( this.attachment, 'change', this.updateSize );
     1401        },
     1402
     1403        changeAttachment: function( attachment, props ) {
     1404                this.stopListening( this.attachment );
     1405                this.attachment = attachment;
     1406                this.bindAttachmentListeners();
     1407
     1408                this.set( 'attachment_id', this.attachment.get( 'id' ) );
     1409                this.set( 'caption', this.attachment.get( 'caption' ) );
     1410                this.set( 'alt', this.attachment.get( 'alt' ) );
     1411                this.set( 'size', props.get( 'size' ) );
     1412                this.set( 'align', props.get( 'align' ) );
     1413                this.set( 'link', props.get( 'link' ) );
     1414                this.updateLinkUrl();
     1415                this.updateSize();
     1416        },
     1417
     1418        setLinkTypeFromUrl: function() {
     1419                var linkUrl = this.get( 'linkUrl' ),
     1420                        type;
     1421
     1422                if ( ! linkUrl ) {
     1423                        this.set( 'link', 'none' );
     1424                        return;
     1425                }
     1426
     1427                // default to custom if there is a linkUrl
     1428                type = 'custom';
     1429
     1430                if ( this.attachment ) {
     1431                        if ( this.attachment.get( 'url' ) === linkUrl ) {
     1432                                type = 'file';
     1433                        } else if ( this.attachment.get( 'link' ) === linkUrl ) {
     1434                                type = 'post';
     1435                        }
     1436                } else {
     1437                        if ( this.get( 'url' ) === linkUrl ) {
     1438                                type = 'file';
     1439                        }
     1440                }
     1441
     1442                this.set( 'link', type );
     1443        },
     1444
     1445        updateLinkUrl: function() {
     1446                var link = this.get( 'link' ),
     1447                        url;
     1448
     1449                switch( link ) {
     1450                        case 'file':
     1451                                if ( this.attachment ) {
     1452                                        url = this.attachment.get( 'url' );
     1453                                } else {
     1454                                        url = this.get( 'url' );
     1455                                }
     1456                                this.set( 'linkUrl', url );
     1457                                break;
     1458                        case 'post':
     1459                                this.set( 'linkUrl', this.attachment.get( 'link' ) );
     1460                                break;
     1461                        case 'none':
     1462                                this.set( 'linkUrl', '' );
     1463                                break;
     1464                }
     1465        },
     1466
     1467        updateSize: function() {
     1468                var size;
     1469
     1470                if ( ! this.attachment ) {
     1471                        return;
     1472                }
     1473
     1474                if ( this.get( 'size' ) === 'custom' ) {
     1475                        this.set( 'width', this.get( 'customWidth' ) );
     1476                        this.set( 'height', this.get( 'customHeight' ) );
     1477                        this.set( 'url', this.get( 'originalUrl' ) );
     1478                        return;
     1479                }
     1480
     1481                size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ];
     1482
     1483                if ( ! size ) {
     1484                        return;
     1485                }
     1486
     1487                this.set( 'url', size.url );
     1488                this.set( 'width', size.width );
     1489                this.set( 'height', size.height );
     1490        },
     1491
     1492        setAspectRatio: function() {
     1493                var full;
     1494
     1495                if ( this.attachment && this.attachment.get( 'sizes' ) ) {
     1496                        full = this.attachment.get( 'sizes' ).full;
     1497
     1498                        if ( full ) {
     1499                                this.set( 'aspectRatio', full.width / full.height );
     1500                                return;
     1501                        }
     1502                }
     1503
     1504                this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) );
     1505        }
     1506});
     1507
     1508module.exports = PostImage;
     1509
     1510
     1511/***/ }),
     1512
     1513/***/ 25:
     1514/***/ (function(module, exports) {
     1515
    14221516var Attachments = wp.media.model.Attachments,
    14231517        Selection;
    14241518
    Selection = Attachments.extend(/** @lends wp.media.model.Selection.prototype */{ 
    15171611
    15181612module.exports = Selection;
    15191613
    1520 },{}]},{},[1]);
     1614
     1615/***/ })
     1616
     1617/******/ });
     1618 No newline at end of file
  • src/wp-includes/js/media-views.js

    diff --git src/wp-includes/js/media-views.js src/wp-includes/js/media-views.js
    index 18e6444691..2e462971e2 100644
     
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
    2 var Selection = wp.media.model.Selection,
    3         Library = wp.media.controller.Library,
    4         CollectionAdd;
     1/******/ (function(modules) { // webpackBootstrap
     2/******/        // The module cache
     3/******/        var installedModules = {};
     4/******/
     5/******/        // The require function
     6/******/        function __webpack_require__(moduleId) {
     7/******/
     8/******/                // Check if module is in cache
     9/******/                if(installedModules[moduleId]) {
     10/******/                        return installedModules[moduleId].exports;
     11/******/                }
     12/******/                // Create a new module (and put it into the cache)
     13/******/                var module = installedModules[moduleId] = {
     14/******/                        i: moduleId,
     15/******/                        l: false,
     16/******/                        exports: {}
     17/******/                };
     18/******/
     19/******/                // Execute the module function
     20/******/                modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/                // Flag the module as loaded
     23/******/                module.l = true;
     24/******/
     25/******/                // Return the exports of the module
     26/******/                return module.exports;
     27/******/        }
     28/******/
     29/******/
     30/******/        // expose the modules object (__webpack_modules__)
     31/******/        __webpack_require__.m = modules;
     32/******/
     33/******/        // expose the module cache
     34/******/        __webpack_require__.c = installedModules;
     35/******/
     36/******/        // define getter function for harmony exports
     37/******/        __webpack_require__.d = function(exports, name, getter) {
     38/******/                if(!__webpack_require__.o(exports, name)) {
     39/******/                        Object.defineProperty(exports, name, {
     40/******/                                configurable: false,
     41/******/                                enumerable: true,
     42/******/                                get: getter
     43/******/                        });
     44/******/                }
     45/******/        };
     46/******/
     47/******/        // getDefaultExport function for compatibility with non-harmony modules
     48/******/        __webpack_require__.n = function(module) {
     49/******/                var getter = module && module.__esModule ?
     50/******/                        function getDefault() { return module['default']; } :
     51/******/                        function getModuleExports() { return module; };
     52/******/                __webpack_require__.d(getter, 'a', getter);
     53/******/                return getter;
     54/******/        };
     55/******/
     56/******/        // Object.prototype.hasOwnProperty.call
     57/******/        __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/        // __webpack_public_path__
     60/******/        __webpack_require__.p = "";
     61/******/
     62/******/        // Load entry module and return exports
     63/******/        return __webpack_require__(__webpack_require__.s = 26);
     64/******/ })
     65/************************************************************************/
     66/******/ (Array(26).concat([
     67/* 26 */
     68/***/ (function(module, exports, __webpack_require__) {
    569
    6 /**
    7  * wp.media.controller.CollectionAdd
    8  *
    9  * A state for adding attachments to a collection (e.g. video playlist).
    10  *
    11  * @memberOf wp.media.controller
    12  *
    13  * @class
    14  * @augments wp.media.controller.Library
    15  * @augments wp.media.controller.State
    16  * @augments Backbone.Model
    17  *
    18  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    19  * @param {string}                     [attributes.id=library]      Unique identifier.
    20  * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
    21  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    22  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    23  *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
    24  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    25  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    26  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    27  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    28  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    29  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    30  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    31  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    32  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    33  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    34  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    35  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    36  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    37  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    38  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    39  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
    40  */
    41 CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{
    42         defaults: _.defaults( {
    43                 // Selection defaults. @see media.model.Selection
    44                 multiple:      'add',
    45                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
    46                 filterable:    'uploaded',
     70var media = wp.media,
     71        $ = jQuery,
     72        l10n;
    4773
    48                 priority:      100,
    49                 syncSelection: false
    50         }, Library.prototype.defaults ),
     74media.isTouchDevice = ( 'ontouchend' in document );
    5175
    52         /**
    53          * @since 3.9.0
    54          */
    55         initialize: function() {
    56                 var collectionType = this.get('collectionType');
     76// Link any localized strings.
     77l10n = media.view.l10n = window._wpMediaViewsL10n || {};
    5778
    58                 if ( 'video' === this.get( 'type' ) ) {
    59                         collectionType = 'video-' + collectionType;
    60                 }
     79// Link any settings.
     80media.view.settings = l10n.settings || {};
     81delete l10n.settings;
    6182
    62                 this.set( 'id', collectionType + '-library' );
    63                 this.set( 'toolbar', collectionType + '-add' );
    64                 this.set( 'menu', collectionType );
     83// Copy the `post` setting over to the model settings.
     84media.model.settings.post = media.view.settings.post;
    6585
    66                 // If we haven't been provided a `library`, create a `Selection`.
    67                 if ( ! this.get('library') ) {
    68                         this.set( 'library', wp.media.query({ type: this.get('type') }) );
    69                 }
    70                 Library.prototype.initialize.apply( this, arguments );
    71         },
     86// Check if the browser supports CSS 3.0 transitions
     87$.support.transition = (function(){
     88        var style = document.documentElement.style,
     89                transitions = {
     90                        WebkitTransition: 'webkitTransitionEnd',
     91                        MozTransition:    'transitionend',
     92                        OTransition:      'oTransitionEnd otransitionend',
     93                        transition:       'transitionend'
     94                }, transition;
    7295
    73         /**
    74          * @since 3.9.0
    75          */
    76         activate: function() {
    77                 var library = this.get('library'),
    78                         editLibrary = this.get('editLibrary'),
    79                         edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
     96        transition = _.find( _.keys( transitions ), function( transition ) {
     97                return ! _.isUndefined( style[ transition ] );
     98        });
    8099
    81                 if ( editLibrary && editLibrary !== edit ) {
    82                         library.unobserve( editLibrary );
     100        return transition && {
     101                end: transitions[ transition ]
     102        };
     103}());
     104
     105/**
     106 * A shared event bus used to provide events into
     107 * the media workflows that 3rd-party devs can use to hook
     108 * in.
     109 */
     110media.events = _.extend( {}, Backbone.Events );
     111
     112/**
     113 * Makes it easier to bind events using transitions.
     114 *
     115 * @param {string} selector
     116 * @param {Number} sensitivity
     117 * @returns {Promise}
     118 */
     119media.transition = function( selector, sensitivity ) {
     120        var deferred = $.Deferred();
     121
     122        sensitivity = sensitivity || 2000;
     123
     124        if ( $.support.transition ) {
     125                if ( ! (selector instanceof $) ) {
     126                        selector = $( selector );
    83127                }
    84128
    85                 // Accepts attachments that exist in the original library and
    86                 // that do not exist in gallery's library.
    87                 library.validator = function( attachment ) {
    88                         return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    89                 };
     129                // Resolve the deferred when the first element finishes animating.
     130                selector.first().one( $.support.transition.end, deferred.resolve );
    90131
    91                 // Reset the library to ensure that all attachments are re-added
    92                 // to the collection. Do so silently, as calling `observe` will
    93                 // trigger the `reset` event.
    94                 library.reset( library.mirroring.models, { silent: true });
    95                 library.observe( edit );
    96                 this.set('editLibrary', edit);
     132                // Just in case the event doesn't trigger, fire a callback.
     133                _.delay( deferred.resolve, sensitivity );
    97134
    98                 Library.prototype.activate.apply( this, arguments );
     135        // Otherwise, execute on the spot.
     136        } else {
     137                deferred.resolve();
    99138        }
    100 });
    101139
    102 module.exports = CollectionAdd;
     140        return deferred.promise();
     141};
    103142
    104 },{}],2:[function(require,module,exports){
    105 var Library = wp.media.controller.Library,
    106         l10n = wp.media.view.l10n,
    107         $ = jQuery,
    108         CollectionEdit;
     143media.controller.Region = __webpack_require__( 27 );
     144media.controller.StateMachine = __webpack_require__( 28 );
     145media.controller.State = __webpack_require__( 29 );
     146
     147media.selectionSync = __webpack_require__( 30 );
     148media.controller.Library = __webpack_require__( 31 );
     149media.controller.ImageDetails = __webpack_require__( 32 );
     150media.controller.GalleryEdit = __webpack_require__( 33 );
     151media.controller.GalleryAdd = __webpack_require__( 34 );
     152media.controller.CollectionEdit = __webpack_require__( 35 );
     153media.controller.CollectionAdd = __webpack_require__( 36 );
     154media.controller.FeaturedImage = __webpack_require__( 37 );
     155media.controller.ReplaceImage = __webpack_require__( 38 );
     156media.controller.EditImage = __webpack_require__( 39 );
     157media.controller.MediaLibrary = __webpack_require__( 40 );
     158media.controller.Embed = __webpack_require__( 41 );
     159media.controller.Cropper = __webpack_require__( 42 );
     160media.controller.CustomizeImageCropper = __webpack_require__( 43 );
     161media.controller.SiteIconCropper = __webpack_require__( 44 );
     162
     163media.View = __webpack_require__( 45 );
     164media.view.Frame = __webpack_require__( 46 );
     165media.view.MediaFrame = __webpack_require__( 47 );
     166media.view.MediaFrame.Select = __webpack_require__( 48 );
     167media.view.MediaFrame.Post = __webpack_require__( 49 );
     168media.view.MediaFrame.ImageDetails = __webpack_require__( 50 );
     169media.view.Modal = __webpack_require__( 51 );
     170media.view.FocusManager = __webpack_require__( 52 );
     171media.view.UploaderWindow = __webpack_require__( 53 );
     172media.view.EditorUploader = __webpack_require__( 54 );
     173media.view.UploaderInline = __webpack_require__( 55 );
     174media.view.UploaderStatus = __webpack_require__( 56 );
     175media.view.UploaderStatusError = __webpack_require__( 57 );
     176media.view.Toolbar = __webpack_require__( 58 );
     177media.view.Toolbar.Select = __webpack_require__( 59 );
     178media.view.Toolbar.Embed = __webpack_require__( 60 );
     179media.view.Button = __webpack_require__( 61 );
     180media.view.ButtonGroup = __webpack_require__( 62 );
     181media.view.PriorityList = __webpack_require__( 63 );
     182media.view.MenuItem = __webpack_require__( 64 );
     183media.view.Menu = __webpack_require__( 65 );
     184media.view.RouterItem = __webpack_require__( 66 );
     185media.view.Router = __webpack_require__( 67 );
     186media.view.Sidebar = __webpack_require__( 68 );
     187media.view.Attachment = __webpack_require__( 69 );
     188media.view.Attachment.Library = __webpack_require__( 70 );
     189media.view.Attachment.EditLibrary = __webpack_require__( 71 );
     190media.view.Attachments = __webpack_require__( 72 );
     191media.view.Search = __webpack_require__( 73 );
     192media.view.AttachmentFilters = __webpack_require__( 74 );
     193media.view.DateFilter = __webpack_require__( 75 );
     194media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 );
     195media.view.AttachmentFilters.All = __webpack_require__( 77 );
     196media.view.AttachmentsBrowser = __webpack_require__( 78 );
     197media.view.Selection = __webpack_require__( 79 );
     198media.view.Attachment.Selection = __webpack_require__( 80 );
     199media.view.Attachments.Selection = __webpack_require__( 81 );
     200media.view.Attachment.EditSelection = __webpack_require__( 82 );
     201media.view.Settings = __webpack_require__( 83 );
     202media.view.Settings.AttachmentDisplay = __webpack_require__( 84 );
     203media.view.Settings.Gallery = __webpack_require__( 85 );
     204media.view.Settings.Playlist = __webpack_require__( 86 );
     205media.view.Attachment.Details = __webpack_require__( 87 );
     206media.view.AttachmentCompat = __webpack_require__( 88 );
     207media.view.Iframe = __webpack_require__( 89 );
     208media.view.Embed = __webpack_require__( 90 );
     209media.view.Label = __webpack_require__( 91 );
     210media.view.EmbedUrl = __webpack_require__( 92 );
     211media.view.EmbedLink = __webpack_require__( 93 );
     212media.view.EmbedImage = __webpack_require__( 94 );
     213media.view.ImageDetails = __webpack_require__( 95 );
     214media.view.Cropper = __webpack_require__( 96 );
     215media.view.SiteIconCropper = __webpack_require__( 97 );
     216media.view.SiteIconPreview = __webpack_require__( 98 );
     217media.view.EditImage = __webpack_require__( 99 );
     218media.view.Spinner = __webpack_require__( 100 );
     219
     220
     221/***/ }),
     222/* 27 */
     223/***/ (function(module, exports) {
    109224
    110225/**
    111  * wp.media.controller.CollectionEdit
     226 * wp.media.controller.Region
    112227 *
    113  * A state for editing a collection, which is used by audio and video playlists,
    114  * and can be used for other collections.
     228 * A region is a persistent application layout area.
     229 *
     230 * A region assumes one mode at any time, and can be switched to another.
     231 *
     232 * When mode changes, events are triggered on the region's parent view.
     233 * The parent view will listen to specific events and fill the region with an
     234 * appropriate view depending on mode. For example, a frame listens for the
     235 * 'browse' mode t be activated on the 'content' view and then fills the region
     236 * with an AttachmentsBrowser view.
    115237 *
    116238 * @memberOf wp.media.controller
    117239 *
    118240 * @class
    119  * @augments wp.media.controller.Library
    120  * @augments wp.media.controller.State
    121  * @augments Backbone.Model
    122241 *
    123  * @param {object}                     [attributes]                      The attributes hash passed to the state.
    124  * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
    125  * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
    126  *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
    127  * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
    128  * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
    129  * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
    130  * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
    131  * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    132  * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
    133  * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
    134  * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
    135  * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
    136  * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
    137  * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
    138  * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
    139  * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
    140  *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
    141  * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
    142  * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
    143  *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    144  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    145  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     242 * @param {object}        options          Options hash for the region.
     243 * @param {string}        options.id       Unique identifier for the region.
     244 * @param {Backbone.View} options.view     A parent view the region exists within.
     245 * @param {string}        options.selector jQuery selector for the region within the parent view.
    146246 */
    147 CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{
    148         defaults: {
    149                 multiple:         false,
    150                 sortable:         true,
    151                 date:             false,
    152                 searchable:       false,
    153                 content:          'browse',
    154                 describe:         true,
    155                 dragInfo:         true,
    156                 idealColumnWidth: 170,
    157                 editing:          false,
    158                 priority:         60,
    159                 SettingsView:     false,
    160                 syncSelection:    false
    161         },
     247var Region = function( options ) {
     248        _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
     249};
     250
     251// Use Backbone's self-propagating `extend` inheritance method.
     252Region.extend = Backbone.Model.extend;
    162253
     254_.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{
    163255        /**
    164          * @since 3.9.0
     256         * Activate a mode.
     257         *
     258         * @since 3.5.0
     259         *
     260         * @param {string} mode
     261         *
     262         * @fires Region#activate
     263         * @fires Region#deactivate
     264         *
     265         * @returns {wp.media.controller.Region} Returns itself to allow chaining.
    165266         */
    166         initialize: function() {
    167                 var collectionType = this.get('collectionType');
    168 
    169                 if ( 'video' === this.get( 'type' ) ) {
    170                         collectionType = 'video-' + collectionType;
    171                 }
    172 
    173                 this.set( 'id', collectionType + '-edit' );
    174                 this.set( 'toolbar', collectionType + '-edit' );
    175 
    176                 // If we haven't been provided a `library`, create a `Selection`.
    177                 if ( ! this.get('library') ) {
    178                         this.set( 'library', new wp.media.model.Selection() );
     267        mode: function( mode ) {
     268                if ( ! mode ) {
     269                        return this._mode;
    179270                }
    180                 // The single `Attachment` view to be used in the `Attachments` view.
    181                 if ( ! this.get('AttachmentView') ) {
    182                         this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     271                // Bail if we're trying to change to the current mode.
     272                if ( mode === this._mode ) {
     273                        return this;
    183274                }
    184                 Library.prototype.initialize.apply( this, arguments );
    185         },
    186 
    187         /**
    188          * @since 3.9.0
    189          */
    190         activate: function() {
    191                 var library = this.get('library');
    192275
    193                 // Limit the library to images only.
    194                 library.props.set( 'type', this.get( 'type' ) );
    195 
    196                 // Watch for uploaded attachments.
    197                 this.get('library').observe( wp.Uploader.queue );
     276                /**
     277                 * Region mode deactivation event.
     278                 *
     279                 * @event wp.media.controller.Region#deactivate
     280                 */
     281                this.trigger('deactivate');
    198282
    199                 this.frame.on( 'content:render:browse', this.renderSettings, this );
     283                this._mode = mode;
     284                this.render( mode );
    200285
    201                 Library.prototype.activate.apply( this, arguments );
     286                /**
     287                 * Region mode activation event.
     288                 *
     289                 * @event wp.media.controller.Region#activate
     290                 */
     291                this.trigger('activate');
     292                return this;
    202293        },
    203 
    204294        /**
    205          * @since 3.9.0
     295         * Render a mode.
     296         *
     297         * @since 3.5.0
     298         *
     299         * @param {string} mode
     300         *
     301         * @fires Region#create
     302         * @fires Region#render
     303         *
     304         * @returns {wp.media.controller.Region} Returns itself to allow chaining
    206305         */
    207         deactivate: function() {
    208                 // Stop watching for uploaded attachments.
    209                 this.get('library').unobserve( wp.Uploader.queue );
     306        render: function( mode ) {
     307                // If the mode isn't active, activate it.
     308                if ( mode && mode !== this._mode ) {
     309                        return this.mode( mode );
     310                }
    210311
    211                 this.frame.off( 'content:render:browse', this.renderSettings, this );
     312                var set = { view: null },
     313                        view;
    212314
    213                 Library.prototype.deactivate.apply( this, arguments );
     315                /**
     316                 * Create region view event.
     317                 *
     318                 * Region view creation takes place in an event callback on the frame.
     319                 *
     320                 * @event wp.media.controller.Region#create
     321                 * @type {object}
     322                 * @property {object} view
     323                 */
     324                this.trigger( 'create', set );
     325                view = set.view;
     326
     327                /**
     328                 * Render region view event.
     329                 *
     330                 * Region view creation takes place in an event callback on the frame.
     331                 *
     332                 * @event wp.media.controller.Region#render
     333                 * @type {object}
     334                 */
     335                this.trigger( 'render', view );
     336                if ( view ) {
     337                        this.set( view );
     338                }
     339                return this;
    214340        },
    215341
    216342        /**
    217          * Render the collection embed settings view in the browser sidebar.
    218          *
    219          * @todo This is against the pattern elsewhere in media. Typically the frame
    220          *       is responsible for adding region mode callbacks. Explain.
     343         * Get the region's view.
    221344         *
    222          * @since 3.9.0
     345         * @since 3.5.0
    223346         *
    224          * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
     347         * @returns {wp.media.View}
    225348         */
    226         renderSettings: function( attachmentsBrowserView ) {
    227                 var library = this.get('library'),
    228                         collectionType = this.get('collectionType'),
    229                         dragInfoText = this.get('dragInfoText'),
    230                         SettingsView = this.get('SettingsView'),
    231                         obj = {};
     349        get: function() {
     350                return this.view.views.first( this.selector );
     351        },
    232352
    233                 if ( ! library || ! attachmentsBrowserView ) {
    234                         return;
     353        /**
     354         * Set the region's view as a subview of the frame.
     355         *
     356         * @since 3.5.0
     357         *
     358         * @param {Array|Object} views
     359         * @param {Object} [options={}]
     360         * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
     361         */
     362        set: function( views, options ) {
     363                if ( options ) {
     364                        options.add = false;
    235365                }
     366                return this.view.views.set( this.selector, views, options );
     367        },
    236368
    237                 library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
    238 
    239                 obj[ collectionType ] = new SettingsView({
    240                         controller: this,
    241                         model:      library[ collectionType ],
    242                         priority:   40
    243                 });
    244 
    245                 attachmentsBrowserView.sidebar.set( obj );
     369        /**
     370         * Trigger regional view events on the frame.
     371         *
     372         * @since 3.5.0
     373         *
     374         * @param {string} event
     375         * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
     376         */
     377        trigger: function( event ) {
     378                var base, args;
    246379
    247                 if ( dragInfoText ) {
    248                         attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
    249                                 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
    250                                 priority: -40
    251                         }) );
     380                if ( ! this._mode ) {
     381                        return;
    252382                }
    253383
    254                 // Add the 'Reverse order' button to the toolbar.
    255                 attachmentsBrowserView.toolbar.set( 'reverse', {
    256                         text:     l10n.reverseOrder,
    257                         priority: 80,
     384                args = _.toArray( arguments );
     385                base = this.id + ':' + event;
    258386
    259                         click: function() {
    260                                 library.reset( library.toArray().reverse() );
    261                         }
    262                 });
     387                // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
     388                args[0] = base + ':' + this._mode;
     389                this.view.trigger.apply( this.view, args );
     390
     391                // Trigger `{this.id}:{event}` event on the frame.
     392                args[0] = base;
     393                this.view.trigger.apply( this.view, args );
     394                return this;
    263395        }
    264396});
    265397
    266 module.exports = CollectionEdit;
     398module.exports = Region;
    267399
    268 },{}],3:[function(require,module,exports){
    269 var l10n = wp.media.view.l10n,
    270         Cropper;
     400
     401/***/ }),
     402/* 28 */
     403/***/ (function(module, exports) {
    271404
    272405/**
    273  * wp.media.controller.Cropper
     406 * wp.media.controller.StateMachine
    274407 *
    275  * A state for cropping an image.
     408 * A state machine keeps track of state. It is in one state at a time,
     409 * and can change from one state to another.
     410 *
     411 * States are stored as models in a Backbone collection.
    276412 *
    277413 * @memberOf wp.media.controller
    278414 *
     415 * @since 3.5.0
     416 *
    279417 * @class
    280  * @augments wp.media.controller.State
    281418 * @augments Backbone.Model
     419 * @mixin
     420 * @mixes Backbone.Events
     421 *
     422 * @param {Array} states
    282423 */
    283 Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{
    284         defaults: {
    285                 id:          'cropper',
    286                 title:       l10n.cropImage,
    287                 // Region mode defaults.
    288                 toolbar:     'crop',
    289                 content:     'crop',
    290                 router:      false,
    291                 canSkipCrop: false,
    292 
    293                 // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state.
    294                 doCropArgs: {}
    295         },
     424var StateMachine = function( states ) {
     425        // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
     426        this.states = new Backbone.Collection( states );
     427};
    296428
    297         activate: function() {
    298                 this.frame.on( 'content:create:crop', this.createCropContent, this );
    299                 this.frame.on( 'close', this.removeCropper, this );
    300                 this.set('selection', new Backbone.Collection(this.frame._selection.single));
    301         },
     429// Use Backbone's self-propagating `extend` inheritance method.
     430StateMachine.extend = Backbone.Model.extend;
    302431
    303         deactivate: function() {
    304                 this.frame.toolbar.mode('browse');
    305         },
     432_.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{
     433        /**
     434         * Fetch a state.
     435         *
     436         * If no `id` is provided, returns the active state.
     437         *
     438         * Implicitly creates states.
     439         *
     440         * Ensure that the `states` collection exists so the `StateMachine`
     441         *   can be used as a mixin.
     442         *
     443         * @since 3.5.0
     444         *
     445         * @param {string} id
     446         * @returns {wp.media.controller.State} Returns a State model
     447         *   from the StateMachine collection
     448         */
     449        state: function( id ) {
     450                this.states = this.states || new Backbone.Collection();
    306451
    307         createCropContent: function() {
    308                 this.cropperView = new wp.media.view.Cropper({
    309                         controller: this,
    310                         attachment: this.get('selection').first()
    311                 });
    312                 this.cropperView.on('image-loaded', this.createCropToolbar, this);
    313                 this.frame.content.set(this.cropperView);
     452                // Default to the active state.
     453                id = id || this._state;
    314454
     455                if ( id && ! this.states.get( id ) ) {
     456                        this.states.add({ id: id });
     457                }
     458                return this.states.get( id );
    315459        },
    316         removeCropper: function() {
    317                 this.imgSelect.cancelSelection();
    318                 this.imgSelect.setOptions({remove: true});
    319                 this.imgSelect.update();
    320                 this.cropperView.remove();
    321         },
    322         createCropToolbar: function() {
    323                 var canSkipCrop, toolbarOptions;
    324460
    325                 canSkipCrop = this.get('canSkipCrop') || false;
     461        /**
     462         * Sets the active state.
     463         *
     464         * Bail if we're trying to select the current state, if we haven't
     465         * created the `states` collection, or are trying to select a state
     466         * that does not exist.
     467         *
     468         * @since 3.5.0
     469         *
     470         * @param {string} id
     471         *
     472         * @fires wp.media.controller.State#deactivate
     473         * @fires wp.media.controller.State#activate
     474         *
     475         * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
     476         */
     477        setState: function( id ) {
     478                var previous = this.state();
    326479
    327                 toolbarOptions = {
    328                         controller: this.frame,
    329                         items: {
    330                                 insert: {
    331                                         style:    'primary',
    332                                         text:     l10n.cropImage,
    333                                         priority: 80,
    334                                         requires: { library: false, selection: false },
    335 
    336                                         click: function() {
    337                                                 var controller = this.controller,
    338                                                         selection;
    339 
    340                                                 selection = controller.state().get('selection').first();
    341                                                 selection.set({cropDetails: controller.state().imgSelect.getSelection()});
    342 
    343                                                 this.$el.text(l10n.cropping);
    344                                                 this.$el.attr('disabled', true);
    345 
    346                                                 controller.state().doCrop( selection ).done( function( croppedImage ) {
    347                                                         controller.trigger('cropped', croppedImage );
    348                                                         controller.close();
    349                                                 }).fail( function() {
    350                                                         controller.trigger('content:error:crop');
    351                                                 });
    352                                         }
    353                                 }
    354                         }
    355                 };
     480                if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
     481                        return this;
     482                }
    356483
    357                 if ( canSkipCrop ) {
    358                         _.extend( toolbarOptions.items, {
    359                                 skip: {
    360                                         style:      'secondary',
    361                                         text:       l10n.skipCropping,
    362                                         priority:   70,
    363                                         requires:   { library: false, selection: false },
    364                                         click:      function() {
    365                                                 var selection = this.controller.state().get('selection').first();
    366                                                 this.controller.state().cropperView.remove();
    367                                                 this.controller.trigger('skippedcrop', selection);
    368                                                 this.controller.close();
    369                                         }
    370                                 }
    371                         });
     484                if ( previous ) {
     485                        previous.trigger('deactivate');
     486                        this._lastState = previous.id;
    372487                }
    373488
    374                 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
     489                this._state = id;
     490                this.state().trigger('activate');
     491
     492                return this;
    375493        },
    376494
    377         doCrop: function( attachment ) {
    378                 return wp.ajax.post( 'custom-header-crop', _.extend(
    379                         {},
    380                         this.defaults.doCropArgs,
    381                         {
    382                                 nonce: attachment.get( 'nonces' ).edit,
    383                                 id: attachment.get( 'id' ),
    384                                 cropDetails: attachment.get( 'cropDetails' )
    385                         }
    386                 ) );
     495        /**
     496         * Returns the previous active state.
     497         *
     498         * Call the `state()` method with no parameters to retrieve the current
     499         * active state.
     500         *
     501         * @since 3.5.0
     502         *
     503         * @returns {wp.media.controller.State} Returns a State model
     504         *    from the StateMachine collection
     505         */
     506        lastState: function() {
     507                if ( this._lastState ) {
     508                        return this.state( this._lastState );
     509                }
    387510        }
    388511});
    389512
    390 module.exports = Cropper;
    391 
    392 },{}],4:[function(require,module,exports){
    393 var Controller = wp.media.controller,
    394         CustomizeImageCropper;
    395 
    396 /**
    397  * wp.media.controller.CustomizeImageCropper
    398  *
    399  * @memberOf wp.media.controller
    400  *
    401  * A state for cropping an image.
    402  *
    403  * @class
    404  * @augments wp.media.controller.Cropper
    405  * @augments wp.media.controller.State
    406  * @augments Backbone.Model
    407  */
    408 CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{
    409         doCrop: function( attachment ) {
    410                 var cropDetails = attachment.get( 'cropDetails' ),
    411                         control = this.get( 'control' ),
    412                         ratio = cropDetails.width / cropDetails.height;
    413 
    414                 // Use crop measurements when flexible in both directions.
    415                 if ( control.params.flex_width && control.params.flex_height ) {
    416                         cropDetails.dst_width  = cropDetails.width;
    417                         cropDetails.dst_height = cropDetails.height;
    418 
    419                 // Constrain flexible side based on image ratio and size of the fixed side.
    420                 } else {
    421                         cropDetails.dst_width  = control.params.flex_width  ? control.params.height * ratio : control.params.width;
    422                         cropDetails.dst_height = control.params.flex_height ? control.params.width  / ratio : control.params.height;
    423                 }
    424 
    425                 return wp.ajax.post( 'crop-image', {
    426                         wp_customize: 'on',
    427                         nonce: attachment.get( 'nonces' ).edit,
    428                         id: attachment.get( 'id' ),
    429                         context: control.id,
    430                         cropDetails: cropDetails
    431                 } );
    432         }
     513// Map all event binding and triggering on a StateMachine to its `states` collection.
     514_.each([ 'on', 'off', 'trigger' ], function( method ) {
     515        /**
     516         * @function on
     517         * @memberOf wp.media.controller.StateMachine
     518         * @instance
     519         * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     520         */
     521        /**
     522         * @function off
     523         * @memberOf wp.media.controller.StateMachine
     524         * @instance
     525         * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     526         */
     527        /**
     528         * @function trigger
     529         * @memberOf wp.media.controller.StateMachine
     530         * @instance
     531         * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     532         */
     533        StateMachine.prototype[ method ] = function() {
     534                // Ensure that the `states` collection exists so the `StateMachine`
     535                // can be used as a mixin.
     536                this.states = this.states || new Backbone.Collection();
     537                // Forward the method to the `states` collection.
     538                this.states[ method ].apply( this.states, arguments );
     539                return this;
     540        };
    433541});
    434542
    435 module.exports = CustomizeImageCropper;
     543module.exports = StateMachine;
    436544
    437 },{}],5:[function(require,module,exports){
    438 var l10n = wp.media.view.l10n,
    439         EditImage;
     545
     546/***/ }),
     547/* 29 */
     548/***/ (function(module, exports) {
    440549
    441550/**
    442  * wp.media.controller.EditImage
     551 * wp.media.controller.State
    443552 *
    444  * A state for editing (cropping, etc.) an image.
     553 * A state is a step in a workflow that when set will trigger the controllers
     554 * for the regions to be updated as specified in the frame.
     555 *
     556 * A state has an event-driven lifecycle:
     557 *
     558 *     'ready'      triggers when a state is added to a state machine's collection.
     559 *     'activate'   triggers when a state is activated by a state machine.
     560 *     'deactivate' triggers when a state is deactivated by a state machine.
     561 *     'reset'      is not triggered automatically. It should be invoked by the
     562 *                  proper controller to reset the state to its default.
    445563 *
    446564 * @memberOf wp.media.controller
    447565 *
    448566 * @class
    449  * @augments wp.media.controller.State
    450567 * @augments Backbone.Model
    451  *
    452  * @param {object}                    attributes                      The attributes hash passed to the state.
    453  * @param {wp.media.model.Attachment} attributes.model                The attachment.
    454  * @param {string}                    [attributes.id=edit-image]      Unique identifier.
    455  * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
    456  * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
    457  * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
    458  * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
    459  * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
    460568 */
    461 EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{
    462         defaults: {
    463                 id:      'edit-image',
    464                 title:   l10n.editImage,
    465                 menu:    false,
    466                 toolbar: 'edit-image',
    467                 content: 'edit-image',
    468                 url:     ''
     569var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{
     570        /**
     571         * Constructor.
     572         *
     573         * @since 3.5.0
     574         */
     575        constructor: function() {
     576                this.on( 'activate', this._preActivate, this );
     577                this.on( 'activate', this.activate, this );
     578                this.on( 'activate', this._postActivate, this );
     579                this.on( 'deactivate', this._deactivate, this );
     580                this.on( 'deactivate', this.deactivate, this );
     581                this.on( 'reset', this.reset, this );
     582                this.on( 'ready', this._ready, this );
     583                this.on( 'ready', this.ready, this );
     584                /**
     585                 * Call parent constructor with passed arguments
     586                 */
     587                Backbone.Model.apply( this, arguments );
     588                this.on( 'change:menu', this._updateMenu, this );
    469589        },
     590        /**
     591         * Ready event callback.
     592         *
     593         * @abstract
     594         * @since 3.5.0
     595         */
     596        ready: function() {},
    470597
    471598        /**
    472          * @since 3.9.0
     599         * Activate event callback.
     600         *
     601         * @abstract
     602         * @since 3.5.0
    473603         */
    474         activate: function() {
    475                 this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
    476         },
     604        activate: function() {},
    477605
    478606        /**
    479          * @since 3.9.0
     607         * Deactivate event callback.
     608         *
     609         * @abstract
     610         * @since 3.5.0
    480611         */
    481         deactivate: function() {
    482                 this.frame.off( 'toolbar:render:edit-image' );
    483         },
     612        deactivate: function() {},
    484613
    485614        /**
    486          * @since 3.9.0
     615         * Reset event callback.
     616         *
     617         * @abstract
     618         * @since 3.5.0
    487619         */
    488         toolbar: function() {
    489                 var frame = this.frame,
    490                         lastState = frame.lastState(),
    491                         previous = lastState && lastState.id;
     620        reset: function() {},
    492621
    493                 frame.toolbar.set( new wp.media.view.Toolbar({
    494                         controller: frame,
    495                         items: {
    496                                 back: {
    497                                         style: 'primary',
    498                                         text:     l10n.back,
    499                                         priority: 20,
    500                                         click:    function() {
    501                                                 if ( previous ) {
    502                                                         frame.setState( previous );
    503                                                 } else {
    504                                                         frame.close();
    505                                                 }
    506                                         }
    507                                 }
    508                         }
    509                 }) );
    510         }
    511 });
     622        /**
     623         * @access private
     624         * @since 3.5.0
     625         */
     626        _ready: function() {
     627                this._updateMenu();
     628        },
    512629
    513 module.exports = EditImage;
     630        /**
     631         * @access private
     632         * @since 3.5.0
     633        */
     634        _preActivate: function() {
     635                this.active = true;
     636        },
    514637
    515 },{}],6:[function(require,module,exports){
    516 var l10n = wp.media.view.l10n,
    517         $ = Backbone.$,
    518         Embed;
     638        /**
     639         * @access private
     640         * @since 3.5.0
     641         */
     642        _postActivate: function() {
     643                this.on( 'change:menu', this._menu, this );
     644                this.on( 'change:titleMode', this._title, this );
     645                this.on( 'change:content', this._content, this );
     646                this.on( 'change:toolbar', this._toolbar, this );
    519647
    520 /**
    521  * wp.media.controller.Embed
    522  *
    523  * A state for embedding media from a URL.
    524  *
    525  * @memberOf wp.media.controller
    526  *
    527  * @class
    528  * @augments wp.media.controller.State
    529  * @augments Backbone.Model
    530  *
    531  * @param {object} attributes                         The attributes hash passed to the state.
    532  * @param {string} [attributes.id=embed]              Unique identifier.
    533  * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
    534  * @param {string} [attributes.content=embed]         Initial mode for the content region.
    535  * @param {string} [attributes.menu=default]          Initial mode for the menu region.
    536  * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
    537  * @param {string} [attributes.menu=false]            Initial mode for the menu region.
    538  * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
    539  * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
    540  * @param {string} [attributes.url]                   The embed URL.
    541  * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
    542  */
    543 Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{
    544         defaults: {
    545                 id:       'embed',
    546                 title:    l10n.insertFromUrlTitle,
    547                 content:  'embed',
    548                 menu:     'default',
    549                 toolbar:  'main-embed',
    550                 priority: 120,
    551                 type:     'link',
    552                 url:      '',
    553                 metadata: {}
     648                this.frame.on( 'title:render:default', this._renderTitle, this );
     649
     650                this._title();
     651                this._menu();
     652                this._toolbar();
     653                this._content();
     654                this._router();
    554655        },
    555656
    556         // The amount of time used when debouncing the scan.
    557         sensitivity: 400,
     657        /**
     658         * @access private
     659         * @since 3.5.0
     660         */
     661        _deactivate: function() {
     662                this.active = false;
    558663
    559         initialize: function(options) {
    560                 this.metadata = options.metadata;
    561                 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
    562                 this.props = new Backbone.Model( this.metadata || { url: '' });
    563                 this.props.on( 'change:url', this.debouncedScan, this );
    564                 this.props.on( 'change:url', this.refresh, this );
    565                 this.on( 'scan', this.scanImage, this );
     664                this.frame.off( 'title:render:default', this._renderTitle, this );
     665
     666                this.off( 'change:menu', this._menu, this );
     667                this.off( 'change:titleMode', this._title, this );
     668                this.off( 'change:content', this._content, this );
     669                this.off( 'change:toolbar', this._toolbar, this );
    566670        },
    567671
    568672        /**
    569          * Trigger a scan of the embedded URL's content for metadata required to embed.
    570          *
    571          * @fires wp.media.controller.Embed#scan
     673         * @access private
     674         * @since 3.5.0
    572675         */
    573         scan: function() {
    574                 var scanners,
    575                         embed = this,
    576                         attributes = {
    577                                 type: 'link',
    578                                 scanners: []
    579                         };
     676        _title: function() {
     677                this.frame.title.render( this.get('titleMode') || 'default' );
     678        },
    580679
    581                 // Scan is triggered with the list of `attributes` to set on the
    582                 // state, useful for the 'type' attribute and 'scanners' attribute,
    583                 // an array of promise objects for asynchronous scan operations.
    584                 if ( this.props.get('url') ) {
    585                         this.trigger( 'scan', attributes );
    586                 }
     680        /**
     681         * @access private
     682         * @since 3.5.0
     683         */
     684        _renderTitle: function( view ) {
     685                view.$el.text( this.get('title') || '' );
     686        },
    587687
    588                 if ( attributes.scanners.length ) {
    589                         scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
    590                         scanners.always( function() {
    591                                 if ( embed.get('scanners') === scanners ) {
    592                                         embed.set( 'loading', false );
    593                                 }
    594                         });
    595                 } else {
    596                         attributes.scanners = null;
     688        /**
     689         * @access private
     690         * @since 3.5.0
     691         */
     692        _router: function() {
     693                var router = this.frame.router,
     694                        mode = this.get('router'),
     695                        view;
     696
     697                this.frame.$el.toggleClass( 'hide-router', ! mode );
     698                if ( ! mode ) {
     699                        return;
    597700                }
    598701
    599                 attributes.loading = !! attributes.scanners;
    600                 this.set( attributes );
     702                this.frame.router.render( mode );
     703
     704                view = router.get();
     705                if ( view && view.select ) {
     706                        view.select( this.frame.content.mode() );
     707                }
    601708        },
     709
    602710        /**
    603          * Try scanning the embed as an image to discover its dimensions.
    604          *
    605          * @param {Object} attributes
     711         * @access private
     712         * @since 3.5.0
    606713         */
    607         scanImage: function( attributes ) {
    608                 var frame = this.frame,
    609                         state = this,
    610                         url = this.props.get('url'),
    611                         image = new Image(),
    612                         deferred = $.Deferred();
     714        _menu: function() {
     715                var menu = this.frame.menu,
     716                        mode = this.get('menu'),
     717                        view;
    613718
    614                 attributes.scanners.push( deferred.promise() );
     719                this.frame.$el.toggleClass( 'hide-menu', ! mode );
     720                if ( ! mode ) {
     721                        return;
     722                }
    615723
    616                 // Try to load the image and find its width/height.
    617                 image.onload = function() {
    618                         deferred.resolve();
     724                menu.mode( mode );
    619725
    620                         if ( state !== frame.state() || url !== state.props.get('url') ) {
    621                                 return;
    622                         }
     726                view = menu.get();
     727                if ( view && view.select ) {
     728                        view.select( this.id );
     729                }
     730        },
    623731
    624                         state.set({
    625                                 type: 'image'
    626                         });
     732        /**
     733         * @access private
     734         * @since 3.5.0
     735         */
     736        _updateMenu: function() {
     737                var previous = this.previous('menu'),
     738                        menu = this.get('menu');
    627739
    628                         state.props.set({
    629                                 width:  image.width,
    630                                 height: image.height
    631                         });
    632                 };
     740                if ( previous ) {
     741                        this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
     742                }
    633743
    634                 image.onerror = deferred.reject;
    635                 image.src = url;
     744                if ( menu ) {
     745                        this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
     746                }
    636747        },
    637748
    638         refresh: function() {
    639                 this.frame.toolbar.get().refresh();
    640         },
     749        /**
     750         * Create a view in the media menu for the state.
     751         *
     752         * @access private
     753         * @since 3.5.0
     754         *
     755         * @param {media.view.Menu} view The menu view.
     756         */
     757        _renderMenu: function( view ) {
     758                var menuItem = this.get('menuItem'),
     759                        title = this.get('title'),
     760                        priority = this.get('priority');
    641761
    642         reset: function() {
    643                 this.props.clear().set({ url: '' });
     762                if ( ! menuItem && title ) {
     763                        menuItem = { text: title };
    644764
    645                 if ( this.active ) {
    646                         this.refresh();
     765                        if ( priority ) {
     766                                menuItem.priority = priority;
     767                        }
     768                }
     769
     770                if ( ! menuItem ) {
     771                        return;
    647772                }
     773
     774                view.set( this.id, menuItem );
    648775        }
    649776});
    650777
    651 module.exports = Embed;
     778_.each(['toolbar','content'], function( region ) {
     779        /**
     780         * @access private
     781         */
     782        State.prototype[ '_' + region ] = function() {
     783                var mode = this.get( region );
     784                if ( mode ) {
     785                        this.frame[ region ].render( mode );
     786                }
     787        };
     788});
     789
     790module.exports = State;
    652791
    653 },{}],7:[function(require,module,exports){
    654 var Attachment = wp.media.model.Attachment,
    655         Library = wp.media.controller.Library,
    656         l10n = wp.media.view.l10n,
    657         FeaturedImage;
     792
     793/***/ }),
     794/* 30 */
     795/***/ (function(module, exports) {
    658796
    659797/**
    660  * wp.media.controller.FeaturedImage
     798 * wp.media.selectionSync
    661799 *
    662  * A state for selecting a featured image for a post.
     800 * Sync an attachments selection in a state with another state.
    663801 *
    664  * @memberOf wp.media.controller
     802 * Allows for selecting multiple images in the Add Media workflow, and then
     803 * switching to the Insert Gallery workflow while preserving the attachments selection.
    665804 *
    666  * @class
    667  * @augments wp.media.controller.Library
    668  * @augments wp.media.controller.State
    669  * @augments Backbone.Model
     805 * @memberOf wp.media
    670806 *
    671  * @param {object}                     [attributes]                          The attributes hash passed to the state.
    672  * @param {string}                     [attributes.id=featured-image]        Unique identifier.
    673  * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
    674  * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
    675  *                                                                           If one is not supplied, a collection of all images will be created.
    676  * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
    677  * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
    678  *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
    679  * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
    680  * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
    681  * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
    682  * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
    683  * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
    684  * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
    685  *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
    686  * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    687  * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
    688  * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    689  * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
    690  * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
     807 * @mixin
    691808 */
    692 FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{
    693         defaults: _.defaults({
    694                 id:            'featured-image',
    695                 title:         l10n.setFeaturedImageTitle,
    696                 multiple:      false,
    697                 filterable:    'uploaded',
    698                 toolbar:       'featured-image',
    699                 priority:      60,
    700                 syncSelection: true
    701         }, Library.prototype.defaults ),
    702 
     809var selectionSync = {
    703810        /**
    704811         * @since 3.5.0
    705812         */
    706         initialize: function() {
    707                 var library, comparator;
     813        syncSelection: function() {
     814                var selection = this.get('selection'),
     815                        manager = this.frame._selection;
    708816
    709                 // If we haven't been provided a `library`, create a `Selection`.
    710                 if ( ! this.get('library') ) {
    711                         this.set( 'library', wp.media.query({ type: 'image' }) );
     817                if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     818                        return;
    712819                }
    713820
    714                 Library.prototype.initialize.apply( this, arguments );
    715 
    716                 library    = this.get('library');
    717                 comparator = library.comparator;
    718 
    719                 // Overload the library's comparator to push items that are not in
    720                 // the mirrored query to the front of the aggregate collection.
    721                 library.comparator = function( a, b ) {
    722                         var aInQuery = !! this.mirroring.get( a.cid ),
    723                                 bInQuery = !! this.mirroring.get( b.cid );
    724 
    725                         if ( ! aInQuery && bInQuery ) {
    726                                 return -1;
    727                         } else if ( aInQuery && ! bInQuery ) {
    728                                 return 1;
    729                         } else {
    730                                 return comparator.apply( this, arguments );
    731                         }
    732                 };
    733 
    734                 // Add all items in the selection to the library, so any featured
    735                 // images that are not initially loaded still appear.
    736                 library.observe( this.get('selection') );
    737         },
    738 
    739         /**
    740          * @since 3.5.0
    741          */
    742         activate: function() {
    743                 this.updateSelection();
    744                 this.frame.on( 'open', this.updateSelection, this );
    745 
    746                 Library.prototype.activate.apply( this, arguments );
    747         },
    748 
    749         /**
    750          * @since 3.5.0
    751          */
    752         deactivate: function() {
    753                 this.frame.off( 'open', this.updateSelection, this );
     821                // If the selection supports multiple items, validate the stored
     822                // attachments based on the new selection's conditions. Record
     823                // the attachments that are not included; we'll maintain a
     824                // reference to those. Other attachments are considered in flux.
     825                if ( selection.multiple ) {
     826                        selection.reset( [], { silent: true });
     827                        selection.validateAll( manager.attachments );
     828                        manager.difference = _.difference( manager.attachments.models, selection.models );
     829                }
    754830
    755                 Library.prototype.deactivate.apply( this, arguments );
     831                // Sync the selection's single item with the master.
     832                selection.single( manager.single );
    756833        },
    757834
    758835        /**
     836         * Record the currently active attachments, which is a combination
     837         * of the selection's attachments and the set of selected
     838         * attachments that this specific selection considered invalid.
     839         * Reset the difference and record the single attachment.
     840         *
    759841         * @since 3.5.0
    760842         */
    761         updateSelection: function() {
     843        recordSelection: function() {
    762844                var selection = this.get('selection'),
    763                         id = wp.media.view.settings.post.featuredImageId,
    764                         attachment;
     845                        manager = this.frame._selection;
    765846
    766                 if ( '' !== id && -1 !== id ) {
    767                         attachment = Attachment.get( id );
    768                         attachment.fetch();
     847                if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     848                        return;
    769849                }
    770850
    771                 selection.reset( attachment ? [ attachment ] : [] );
     851                if ( selection.multiple ) {
     852                        manager.attachments.reset( selection.toArray().concat( manager.difference ) );
     853                        manager.difference = [];
     854                } else {
     855                        manager.attachments.add( selection.toArray() );
     856                }
     857
     858                manager.single = selection._single;
    772859        }
    773 });
     860};
    774861
    775 module.exports = FeaturedImage;
     862module.exports = selectionSync;
    776863
    777 },{}],8:[function(require,module,exports){
    778 var Selection = wp.media.model.Selection,
    779         Library = wp.media.controller.Library,
    780         l10n = wp.media.view.l10n,
    781         GalleryAdd;
     864
     865/***/ }),
     866/* 31 */
     867/***/ (function(module, exports) {
     868
     869var l10n = wp.media.view.l10n,
     870        getUserSetting = window.getUserSetting,
     871        setUserSetting = window.setUserSetting,
     872        Library;
    782873
    783874/**
    784  * wp.media.controller.GalleryAdd
     875 * wp.media.controller.Library
    785876 *
    786  * A state for selecting more images to add to a gallery.
     877 * A state for choosing an attachment or group of attachments from the media library.
    787878 *
    788879 * @memberOf wp.media.controller
    789880 *
    790881 * @class
    791  * @augments wp.media.controller.Library
    792882 * @augments wp.media.controller.State
    793883 * @augments Backbone.Model
     884 * @mixes media.selectionSync
    794885 *
    795  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    796  * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
    797  * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
    798  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    799  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    800  *                                                                          If one is not supplied, a collection of all images will be created.
    801  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    802  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    803  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    804  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    805  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    806  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    807  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    808  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    809  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    810  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    811  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    812  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    813  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    814  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     886 * @param {object}                          [attributes]                         The attributes hash passed to the state.
     887 * @param {string}                          [attributes.id=library]              Unique identifier.
     888 * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
     889 * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
     890 *                                                                               If one is not supplied, a collection of all attachments will be created.
     891 * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
     892 *                                                                               If the 'selection' attribute is a plain JS object,
     893 *                                                                               a Selection will be created using its values as the selection instance's `props` model.
     894 *                                                                               Otherwise, it will copy the library's `props` model.
     895 * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
     896 * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
     897 *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
     898 * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
     899 * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
     900 * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
     901 * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
     902 * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
     903 *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
     904 * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     905 * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     906 * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     907 * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     908 * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    815909 */
    816 GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{
    817         defaults: _.defaults({
    818                 id:            'gallery-library',
    819                 title:         l10n.addToGalleryTitle,
    820                 multiple:      'add',
    821                 filterable:    'uploaded',
    822                 menu:          'gallery',
    823                 toolbar:       'gallery-add',
    824                 priority:      100,
    825                 syncSelection: false
    826         }, Library.prototype.defaults ),
     910Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{
     911        defaults: {
     912                id:                 'library',
     913                title:              l10n.mediaLibraryTitle,
     914                multiple:           false,
     915                content:            'upload',
     916                menu:               'default',
     917                router:             'browse',
     918                toolbar:            'select',
     919                searchable:         true,
     920                filterable:         false,
     921                sortable:           true,
     922                autoSelect:         true,
     923                describe:           false,
     924                contentUserSetting: true,
     925                syncSelection:      true
     926        },
    827927
    828928        /**
     929         * If a library isn't provided, query all media items.
     930         * If a selection instance isn't provided, create one.
     931         *
    829932         * @since 3.5.0
    830933         */
    831934        initialize: function() {
    832                 // If a library wasn't supplied, create a library of images.
     935                var selection = this.get('selection'),
     936                        props;
     937
    833938                if ( ! this.get('library') ) {
    834                         this.set( 'library', wp.media.query({ type: 'image' }) );
     939                        this.set( 'library', wp.media.query() );
    835940                }
    836941
    837                 Library.prototype.initialize.apply( this, arguments );
     942                if ( ! ( selection instanceof wp.media.model.Selection ) ) {
     943                        props = selection;
     944
     945                        if ( ! props ) {
     946                                props = this.get('library').props.toJSON();
     947                                props = _.omit( props, 'orderby', 'query' );
     948                        }
     949
     950                        this.set( 'selection', new wp.media.model.Selection( null, {
     951                                multiple: this.get('multiple'),
     952                                props: props
     953                        }) );
     954                }
     955
     956                this.resetDisplays();
    838957        },
    839958
    840959        /**
    841960         * @since 3.5.0
    842961         */
    843962        activate: function() {
    844                 var library = this.get('library'),
    845                         edit    = this.frame.state('gallery-edit').get('library');
     963                this.syncSelection();
    846964
    847                 if ( this.editLibrary && this.editLibrary !== edit ) {
    848                         library.unobserve( this.editLibrary );
    849                 }
     965                wp.Uploader.queue.on( 'add', this.uploading, this );
    850966
    851                 // Accepts attachments that exist in the original library and
    852                 // that do not exist in gallery's library.
    853                 library.validator = function( attachment ) {
    854                         return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    855                 };
     967                this.get('selection').on( 'add remove reset', this.refreshContent, this );
    856968
    857                 // Reset the library to ensure that all attachments are re-added
    858                 // to the collection. Do so silently, as calling `observe` will
    859                 // trigger the `reset` event.
    860                 library.reset( library.mirroring.models, { silent: true });
    861                 library.observe( edit );
    862                 this.editLibrary = edit;
     969                if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
     970                        this.frame.on( 'content:activate', this.saveContentMode, this );
     971                        this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
     972                }
     973        },
    863974
    864                 Library.prototype.activate.apply( this, arguments );
    865         }
    866 });
     975        /**
     976         * @since 3.5.0
     977         */
     978        deactivate: function() {
     979                this.recordSelection();
    867980
    868 module.exports = GalleryAdd;
     981                this.frame.off( 'content:activate', this.saveContentMode, this );
    869982
    870 },{}],9:[function(require,module,exports){
    871 var Library = wp.media.controller.Library,
    872         l10n = wp.media.view.l10n,
    873         GalleryEdit;
     983                // Unbind all event handlers that use this state as the context
     984                // from the selection.
     985                this.get('selection').off( null, null, this );
    874986
    875 /**
    876  * wp.media.controller.GalleryEdit
    877  *
    878  * A state for editing a gallery's images and settings.
    879  *
    880  * @memberOf wp.media.controller
    881  *
    882  * @class
    883  * @augments wp.media.controller.Library
    884  * @augments wp.media.controller.State
    885  * @augments Backbone.Model
    886  *
    887  * @param {object}                     [attributes]                       The attributes hash passed to the state.
    888  * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
    889  * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
    890  * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
    891  *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
    892  * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
    893  * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
    894  * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    895  * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
    896  * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
    897  * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
    898  * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    899  * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
    900  * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
    901  * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
    902  * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
    903  * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
    904  * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
    905  *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
    906  * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
    907  *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    908  */
    909 GalleryEdit = Library.extend(/** @lends wp.media.controller.GalleryEdit.prototype */{
    910         defaults: {
    911                 id:               'gallery-edit',
    912                 title:            l10n.editGalleryTitle,
    913                 multiple:         false,
    914                 searchable:       false,
    915                 sortable:         true,
    916                 date:             false,
    917                 display:          false,
    918                 content:          'browse',
    919                 toolbar:          'gallery-edit',
    920                 describe:         true,
    921                 displaySettings:  true,
    922                 dragInfo:         true,
    923                 idealColumnWidth: 170,
    924                 editing:          false,
    925                 priority:         60,
    926                 syncSelection:    false
     987                wp.Uploader.queue.off( null, null, this );
    927988        },
    928989
    929990        /**
     991         * Reset the library to its initial state.
     992         *
    930993         * @since 3.5.0
    931994         */
    932         initialize: function() {
    933                 // If we haven't been provided a `library`, create a `Selection`.
    934                 if ( ! this.get('library') ) {
    935                         this.set( 'library', new wp.media.model.Selection() );
     995        reset: function() {
     996                this.get('selection').reset();
     997                this.resetDisplays();
     998                this.refreshContent();
     999        },
     1000
     1001        /**
     1002         * Reset the attachment display settings defaults to the site options.
     1003         *
     1004         * If site options don't define them, fall back to a persistent user setting.
     1005         *
     1006         * @since 3.5.0
     1007         */
     1008        resetDisplays: function() {
     1009                var defaultProps = wp.media.view.settings.defaultProps;
     1010                this._displays = [];
     1011                this._defaultDisplaySettings = {
     1012                        align: getUserSetting( 'align', defaultProps.align ) || 'none',
     1013                        size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
     1014                        link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
     1015                };
     1016        },
     1017
     1018        /**
     1019         * Create a model to represent display settings (alignment, etc.) for an attachment.
     1020         *
     1021         * @since 3.5.0
     1022         *
     1023         * @param {wp.media.model.Attachment} attachment
     1024         * @returns {Backbone.Model}
     1025         */
     1026        display: function( attachment ) {
     1027                var displays = this._displays;
     1028
     1029                if ( ! displays[ attachment.cid ] ) {
     1030                        displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
    9361031                }
     1032                return displays[ attachment.cid ];
     1033        },
    9371034
    938                 // The single `Attachment` view to be used in the `Attachments` view.
    939                 if ( ! this.get('AttachmentView') ) {
    940                         this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     1035        /**
     1036         * Given an attachment, create attachment display settings properties.
     1037         *
     1038         * @since 3.6.0
     1039         *
     1040         * @param {wp.media.model.Attachment} attachment
     1041         * @returns {Object}
     1042         */
     1043        defaultDisplaySettings: function( attachment ) {
     1044                var settings = _.clone( this._defaultDisplaySettings );
     1045
     1046                if ( settings.canEmbed = this.canEmbed( attachment ) ) {
     1047                        settings.link = 'embed';
     1048                } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
     1049                        settings.link = 'file';
    9411050                }
    9421051
    943                 Library.prototype.initialize.apply( this, arguments );
     1052                return settings;
    9441053        },
    9451054
    9461055        /**
    947          * @since 3.5.0
     1056         * Whether an attachment is image.
     1057         *
     1058         * @since 4.4.1
     1059         *
     1060         * @param {wp.media.model.Attachment} attachment
     1061         * @returns {Boolean}
    9481062         */
    949         activate: function() {
    950                 var library = this.get('library');
     1063        isImageAttachment: function( attachment ) {
     1064                // If uploading, we know the filename but not the mime type.
     1065                if ( attachment.get('uploading') ) {
     1066                        return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
     1067                }
    9511068
    952                 // Limit the library to images only.
    953                 library.props.set( 'type', 'image' );
     1069                return attachment.get('type') === 'image';
     1070        },
    9541071
    955                 // Watch for uploaded attachments.
    956                 this.get('library').observe( wp.Uploader.queue );
     1072        /**
     1073         * Whether an attachment can be embedded (audio or video).
     1074         *
     1075         * @since 3.6.0
     1076         *
     1077         * @param {wp.media.model.Attachment} attachment
     1078         * @returns {Boolean}
     1079         */
     1080        canEmbed: function( attachment ) {
     1081                // If uploading, we know the filename but not the mime type.
     1082                if ( ! attachment.get('uploading') ) {
     1083                        var type = attachment.get('type');
     1084                        if ( type !== 'audio' && type !== 'video' ) {
     1085                                return false;
     1086                        }
     1087                }
    9571088
    958                 this.frame.on( 'content:render:browse', this.gallerySettings, this );
     1089                return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
     1090        },
    9591091
    960                 Library.prototype.activate.apply( this, arguments );
     1092
     1093        /**
     1094         * If the state is active, no items are selected, and the current
     1095         * content mode is not an option in the state's router (provided
     1096         * the state has a router), reset the content mode to the default.
     1097         *
     1098         * @since 3.5.0
     1099         */
     1100        refreshContent: function() {
     1101                var selection = this.get('selection'),
     1102                        frame = this.frame,
     1103                        router = frame.router.get(),
     1104                        mode = frame.content.mode();
     1105
     1106                if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
     1107                        this.frame.content.render( this.get('content') );
     1108                }
    9611109        },
    9621110
    9631111        /**
     1112         * Callback handler when an attachment is uploaded.
     1113         *
     1114         * Switch to the Media Library if uploaded from the 'Upload Files' tab.
     1115         *
     1116         * Adds any uploading attachments to the selection.
     1117         *
     1118         * If the state only supports one attachment to be selected and multiple
     1119         * attachments are uploaded, the last attachment in the upload queue will
     1120         * be selected.
     1121         *
    9641122         * @since 3.5.0
     1123         *
     1124         * @param {wp.media.model.Attachment} attachment
    9651125         */
    966         deactivate: function() {
    967                 // Stop watching for uploaded attachments.
    968                 this.get('library').unobserve( wp.Uploader.queue );
     1126        uploading: function( attachment ) {
     1127                var content = this.frame.content;
    9691128
    970                 this.frame.off( 'content:render:browse', this.gallerySettings, this );
     1129                if ( 'upload' === content.mode() ) {
     1130                        this.frame.content.mode('browse');
     1131                }
    9711132
    972                 Library.prototype.deactivate.apply( this, arguments );
     1133                if ( this.get( 'autoSelect' ) ) {
     1134                        this.get('selection').add( attachment );
     1135                        this.frame.trigger( 'library:selection:add' );
     1136                }
    9731137        },
    9741138
    9751139        /**
    976          * @since 3.5.0
     1140         * Persist the mode of the content region as a user setting.
    9771141         *
    978          * @param browser
     1142         * @since 3.5.0
    9791143         */
    980         gallerySettings: function( browser ) {
    981                 if ( ! this.get('displaySettings') ) {
     1144        saveContentMode: function() {
     1145                if ( 'browse' !== this.get('router') ) {
    9821146                        return;
    9831147                }
    9841148
    985                 var library = this.get('library');
     1149                var mode = this.frame.content.mode(),
     1150                        view = this.frame.router.get();
    9861151
    987                 if ( ! library || ! browser ) {
    988                         return;
     1152                if ( view && view.get( mode ) ) {
     1153                        setUserSetting( 'libraryContent', mode );
    9891154                }
     1155        }
     1156});
    9901157
    991                 library.gallery = library.gallery || new Backbone.Model();
     1158// Make selectionSync available on any Media Library state.
     1159_.extend( Library.prototype, wp.media.selectionSync );
    9921160
    993                 browser.sidebar.set({
    994                         gallery: new wp.media.view.Settings.Gallery({
    995                                 controller: this,
    996                                 model:      library.gallery,
    997                                 priority:   40
    998                         })
    999                 });
     1161module.exports = Library;
    10001162
    1001                 browser.toolbar.set( 'reverse', {
    1002                         text:     l10n.reverseOrder,
    1003                         priority: 80,
    1004 
    1005                         click: function() {
    1006                                 library.reset( library.toArray().reverse() );
    1007                         }
    1008                 });
    1009         }
    1010 });
    10111163
    1012 module.exports = GalleryEdit;
     1164/***/ }),
     1165/* 32 */
     1166/***/ (function(module, exports) {
    10131167
    1014 },{}],10:[function(require,module,exports){
    10151168var State = wp.media.controller.State,
    10161169        Library = wp.media.controller.Library,
    10171170        l10n = wp.media.view.l10n,
    ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototyp 
    10751228
    10761229module.exports = ImageDetails;
    10771230
    1078 },{}],11:[function(require,module,exports){
    1079 var l10n = wp.media.view.l10n,
    1080         getUserSetting = window.getUserSetting,
    1081         setUserSetting = window.setUserSetting,
    1082         Library;
     1231
     1232/***/ }),
     1233/* 33 */
     1234/***/ (function(module, exports) {
     1235
     1236var Library = wp.media.controller.Library,
     1237        l10n = wp.media.view.l10n,
     1238        GalleryEdit;
    10831239
    10841240/**
    1085  * wp.media.controller.Library
     1241 * wp.media.controller.GalleryEdit
    10861242 *
    1087  * A state for choosing an attachment or group of attachments from the media library.
     1243 * A state for editing a gallery's images and settings.
    10881244 *
    10891245 * @memberOf wp.media.controller
    10901246 *
    10911247 * @class
     1248 * @augments wp.media.controller.Library
    10921249 * @augments wp.media.controller.State
    10931250 * @augments Backbone.Model
    1094  * @mixes media.selectionSync
    10951251 *
    1096  * @param {object}                          [attributes]                         The attributes hash passed to the state.
    1097  * @param {string}                          [attributes.id=library]              Unique identifier.
    1098  * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
    1099  * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
    1100  *                                                                               If one is not supplied, a collection of all attachments will be created.
    1101  * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
    1102  *                                                                               If the 'selection' attribute is a plain JS object,
    1103  *                                                                               a Selection will be created using its values as the selection instance's `props` model.
    1104  *                                                                               Otherwise, it will copy the library's `props` model.
    1105  * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
    1106  * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
    1107  *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
    1108  * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
    1109  * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
    1110  * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
    1111  * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
    1112  * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
    1113  *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
    1114  * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1115  * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1116  * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1117  * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1118  * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
     1252 * @param {object}                     [attributes]                       The attributes hash passed to the state.
     1253 * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
     1254 * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
     1255 * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
     1256 *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
     1257 * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
     1258 * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
     1259 * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1260 * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
     1261 * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
     1262 * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
     1263 * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     1264 * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
     1265 * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
     1266 * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
     1267 * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
     1268 * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
     1269 * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
     1270 *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
     1271 * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
     1272 *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    11191273 */
    1120 Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{
     1274GalleryEdit = Library.extend(/** @lends wp.media.controller.GalleryEdit.prototype */{
    11211275        defaults: {
    1122                 id:                 'library',
    1123                 title:              l10n.mediaLibraryTitle,
    1124                 multiple:           false,
    1125                 content:            'upload',
    1126                 menu:               'default',
    1127                 router:             'browse',
    1128                 toolbar:            'select',
    1129                 searchable:         true,
    1130                 filterable:         false,
    1131                 sortable:           true,
    1132                 autoSelect:         true,
    1133                 describe:           false,
    1134                 contentUserSetting: true,
    1135                 syncSelection:      true
     1276                id:               'gallery-edit',
     1277                title:            l10n.editGalleryTitle,
     1278                multiple:         false,
     1279                searchable:       false,
     1280                sortable:         true,
     1281                date:             false,
     1282                display:          false,
     1283                content:          'browse',
     1284                toolbar:          'gallery-edit',
     1285                describe:         true,
     1286                displaySettings:  true,
     1287                dragInfo:         true,
     1288                idealColumnWidth: 170,
     1289                editing:          false,
     1290                priority:         60,
     1291                syncSelection:    false
    11361292        },
    11371293
    11381294        /**
    1139          * If a library isn't provided, query all media items.
    1140          * If a selection instance isn't provided, create one.
    1141          *
    11421295         * @since 3.5.0
    11431296         */
    11441297        initialize: function() {
    1145                 var selection = this.get('selection'),
    1146                         props;
    1147 
     1298                // If we haven't been provided a `library`, create a `Selection`.
    11481299                if ( ! this.get('library') ) {
    1149                         this.set( 'library', wp.media.query() );
     1300                        this.set( 'library', new wp.media.model.Selection() );
    11501301                }
    11511302
    1152                 if ( ! ( selection instanceof wp.media.model.Selection ) ) {
    1153                         props = selection;
    1154 
    1155                         if ( ! props ) {
    1156                                 props = this.get('library').props.toJSON();
    1157                                 props = _.omit( props, 'orderby', 'query' );
    1158                         }
    1159 
    1160                         this.set( 'selection', new wp.media.model.Selection( null, {
    1161                                 multiple: this.get('multiple'),
    1162                                 props: props
    1163                         }) );
     1303                // The single `Attachment` view to be used in the `Attachments` view.
     1304                if ( ! this.get('AttachmentView') ) {
     1305                        this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    11641306                }
    11651307
    1166                 this.resetDisplays();
     1308                Library.prototype.initialize.apply( this, arguments );
    11671309        },
    11681310
    11691311        /**
    11701312         * @since 3.5.0
    11711313         */
    11721314        activate: function() {
    1173                 this.syncSelection();
     1315                var library = this.get('library');
    11741316
    1175                 wp.Uploader.queue.on( 'add', this.uploading, this );
     1317                // Limit the library to images only.
     1318                library.props.set( 'type', 'image' );
    11761319
    1177                 this.get('selection').on( 'add remove reset', this.refreshContent, this );
     1320                // Watch for uploaded attachments.
     1321                this.get('library').observe( wp.Uploader.queue );
    11781322
    1179                 if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
    1180                         this.frame.on( 'content:activate', this.saveContentMode, this );
    1181                         this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
    1182                 }
     1323                this.frame.on( 'content:render:browse', this.gallerySettings, this );
     1324
     1325                Library.prototype.activate.apply( this, arguments );
    11831326        },
    11841327
    11851328        /**
    11861329         * @since 3.5.0
    11871330         */
    11881331        deactivate: function() {
    1189                 this.recordSelection();
    1190 
    1191                 this.frame.off( 'content:activate', this.saveContentMode, this );
     1332                // Stop watching for uploaded attachments.
     1333                this.get('library').unobserve( wp.Uploader.queue );
    11921334
    1193                 // Unbind all event handlers that use this state as the context
    1194                 // from the selection.
    1195                 this.get('selection').off( null, null, this );
     1335                this.frame.off( 'content:render:browse', this.gallerySettings, this );
    11961336
    1197                 wp.Uploader.queue.off( null, null, this );
     1337                Library.prototype.deactivate.apply( this, arguments );
    11981338        },
    11991339
    12001340        /**
    1201          * Reset the library to its initial state.
    1202          *
    12031341         * @since 3.5.0
    1204          */
    1205         reset: function() {
    1206                 this.get('selection').reset();
    1207                 this.resetDisplays();
    1208                 this.refreshContent();
    1209         },
    1210 
    1211         /**
    1212          * Reset the attachment display settings defaults to the site options.
    1213          *
    1214          * If site options don't define them, fall back to a persistent user setting.
    12151342         *
    1216          * @since 3.5.0
     1343         * @param browser
    12171344         */
    1218         resetDisplays: function() {
    1219                 var defaultProps = wp.media.view.settings.defaultProps;
    1220                 this._displays = [];
    1221                 this._defaultDisplaySettings = {
    1222                         align: getUserSetting( 'align', defaultProps.align ) || 'none',
    1223                         size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
    1224                         link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
    1225                 };
    1226         },
     1345        gallerySettings: function( browser ) {
     1346                if ( ! this.get('displaySettings') ) {
     1347                        return;
     1348                }
    12271349
    1228         /**
    1229          * Create a model to represent display settings (alignment, etc.) for an attachment.
    1230          *
    1231          * @since 3.5.0
    1232          *
    1233          * @param {wp.media.model.Attachment} attachment
    1234          * @returns {Backbone.Model}
    1235          */
    1236         display: function( attachment ) {
    1237                 var displays = this._displays;
     1350                var library = this.get('library');
    12381351
    1239                 if ( ! displays[ attachment.cid ] ) {
    1240                         displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
     1352                if ( ! library || ! browser ) {
     1353                        return;
    12411354                }
    1242                 return displays[ attachment.cid ];
    1243         },
    12441355
    1245         /**
    1246          * Given an attachment, create attachment display settings properties.
    1247          *
    1248          * @since 3.6.0
    1249          *
    1250          * @param {wp.media.model.Attachment} attachment
    1251          * @returns {Object}
    1252          */
    1253         defaultDisplaySettings: function( attachment ) {
    1254                 var settings = _.clone( this._defaultDisplaySettings );
     1356                library.gallery = library.gallery || new Backbone.Model();
    12551357
    1256                 if ( settings.canEmbed = this.canEmbed( attachment ) ) {
    1257                         settings.link = 'embed';
    1258                 } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
    1259                         settings.link = 'file';
    1260                 }
     1358                browser.sidebar.set({
     1359                        gallery: new wp.media.view.Settings.Gallery({
     1360                                controller: this,
     1361                                model:      library.gallery,
     1362                                priority:   40
     1363                        })
     1364                });
    12611365
    1262                 return settings;
    1263         },
     1366                browser.toolbar.set( 'reverse', {
     1367                        text:     l10n.reverseOrder,
     1368                        priority: 80,
    12641369
    1265         /**
    1266          * Whether an attachment is image.
    1267          *
    1268          * @since 4.4.1
    1269          *
    1270          * @param {wp.media.model.Attachment} attachment
    1271          * @returns {Boolean}
    1272          */
    1273         isImageAttachment: function( attachment ) {
    1274                 // If uploading, we know the filename but not the mime type.
    1275                 if ( attachment.get('uploading') ) {
    1276                         return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
    1277                 }
     1370                        click: function() {
     1371                                library.reset( library.toArray().reverse() );
     1372                        }
     1373                });
     1374        }
     1375});
    12781376
    1279                 return attachment.get('type') === 'image';
    1280         },
     1377module.exports = GalleryEdit;
    12811378
    1282         /**
    1283          * Whether an attachment can be embedded (audio or video).
    1284          *
    1285          * @since 3.6.0
    1286          *
    1287          * @param {wp.media.model.Attachment} attachment
    1288          * @returns {Boolean}
    1289          */
    1290         canEmbed: function( attachment ) {
    1291                 // If uploading, we know the filename but not the mime type.
    1292                 if ( ! attachment.get('uploading') ) {
    1293                         var type = attachment.get('type');
    1294                         if ( type !== 'audio' && type !== 'video' ) {
    1295                                 return false;
    1296                         }
    1297                 }
    1298 
    1299                 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
    1300         },
    13011379
     1380/***/ }),
     1381/* 34 */
     1382/***/ (function(module, exports) {
    13021383
    1303         /**
    1304          * If the state is active, no items are selected, and the current
    1305          * content mode is not an option in the state's router (provided
    1306          * the state has a router), reset the content mode to the default.
    1307          *
    1308          * @since 3.5.0
    1309          */
    1310         refreshContent: function() {
    1311                 var selection = this.get('selection'),
    1312                         frame = this.frame,
    1313                         router = frame.router.get(),
    1314                         mode = frame.content.mode();
     1384var Selection = wp.media.model.Selection,
     1385        Library = wp.media.controller.Library,
     1386        l10n = wp.media.view.l10n,
     1387        GalleryAdd;
    13151388
    1316                 if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
    1317                         this.frame.content.render( this.get('content') );
    1318                 }
    1319         },
     1389/**
     1390 * wp.media.controller.GalleryAdd
     1391 *
     1392 * A state for selecting more images to add to a gallery.
     1393 *
     1394 * @memberOf wp.media.controller
     1395 *
     1396 * @class
     1397 * @augments wp.media.controller.Library
     1398 * @augments wp.media.controller.State
     1399 * @augments Backbone.Model
     1400 *
     1401 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     1402 * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
     1403 * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
     1404 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     1405 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     1406 *                                                                          If one is not supplied, a collection of all images will be created.
     1407 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     1408 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     1409 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     1410 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     1411 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     1412 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     1413 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     1414 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     1415 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1416 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     1417 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     1418 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     1419 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     1420 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     1421 */
     1422GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{
     1423        defaults: _.defaults({
     1424                id:            'gallery-library',
     1425                title:         l10n.addToGalleryTitle,
     1426                multiple:      'add',
     1427                filterable:    'uploaded',
     1428                menu:          'gallery',
     1429                toolbar:       'gallery-add',
     1430                priority:      100,
     1431                syncSelection: false
     1432        }, Library.prototype.defaults ),
    13201433
    13211434        /**
    1322          * Callback handler when an attachment is uploaded.
    1323          *
    1324          * Switch to the Media Library if uploaded from the 'Upload Files' tab.
    1325          *
    1326          * Adds any uploading attachments to the selection.
    1327          *
    1328          * If the state only supports one attachment to be selected and multiple
    1329          * attachments are uploaded, the last attachment in the upload queue will
    1330          * be selected.
    1331          *
    13321435         * @since 3.5.0
    1333          *
    1334          * @param {wp.media.model.Attachment} attachment
    13351436         */
    1336         uploading: function( attachment ) {
    1337                 var content = this.frame.content;
    1338 
    1339                 if ( 'upload' === content.mode() ) {
    1340                         this.frame.content.mode('browse');
     1437        initialize: function() {
     1438                // If a library wasn't supplied, create a library of images.
     1439                if ( ! this.get('library') ) {
     1440                        this.set( 'library', wp.media.query({ type: 'image' }) );
    13411441                }
    13421442
    1343                 if ( this.get( 'autoSelect' ) ) {
    1344                         this.get('selection').add( attachment );
    1345                         this.frame.trigger( 'library:selection:add' );
    1346                 }
     1443                Library.prototype.initialize.apply( this, arguments );
    13471444        },
    13481445
    13491446        /**
    1350          * Persist the mode of the content region as a user setting.
    1351          *
    13521447         * @since 3.5.0
    13531448         */
    1354         saveContentMode: function() {
    1355                 if ( 'browse' !== this.get('router') ) {
    1356                         return;
     1449        activate: function() {
     1450                var library = this.get('library'),
     1451                        edit    = this.frame.state('gallery-edit').get('library');
     1452
     1453                if ( this.editLibrary && this.editLibrary !== edit ) {
     1454                        library.unobserve( this.editLibrary );
    13571455                }
    13581456
    1359                 var mode = this.frame.content.mode(),
    1360                         view = this.frame.router.get();
     1457                // Accepts attachments that exist in the original library and
     1458                // that do not exist in gallery's library.
     1459                library.validator = function( attachment ) {
     1460                        return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     1461                };
    13611462
    1362                 if ( view && view.get( mode ) ) {
    1363                         setUserSetting( 'libraryContent', mode );
    1364                 }
     1463                // Reset the library to ensure that all attachments are re-added
     1464                // to the collection. Do so silently, as calling `observe` will
     1465                // trigger the `reset` event.
     1466                library.reset( library.mirroring.models, { silent: true });
     1467                library.observe( edit );
     1468                this.editLibrary = edit;
     1469
     1470                Library.prototype.activate.apply( this, arguments );
    13651471        }
    13661472});
    13671473
    1368 // Make selectionSync available on any Media Library state.
    1369 _.extend( Library.prototype, wp.media.selectionSync );
     1474module.exports = GalleryAdd;
    13701475
    1371 module.exports = Library;
    13721476
    1373 },{}],12:[function(require,module,exports){
     1477/***/ }),
     1478/* 35 */
     1479/***/ (function(module, exports) {
     1480
     1481var Library = wp.media.controller.Library,
     1482        l10n = wp.media.view.l10n,
     1483        $ = jQuery,
     1484        CollectionEdit;
     1485
    13741486/**
    1375  * wp.media.controller.MediaLibrary
     1487 * wp.media.controller.CollectionEdit
     1488 *
     1489 * A state for editing a collection, which is used by audio and video playlists,
     1490 * and can be used for other collections.
    13761491 *
    13771492 * @memberOf wp.media.controller
    13781493 *
    module.exports = Library; 
    13801495 * @augments wp.media.controller.Library
    13811496 * @augments wp.media.controller.State
    13821497 * @augments Backbone.Model
     1498 *
     1499 * @param {object}                     [attributes]                      The attributes hash passed to the state.
     1500 * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
     1501 * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
     1502 *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
     1503 * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
     1504 * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
     1505 * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
     1506 * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
     1507 * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1508 * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
     1509 * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
     1510 * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
     1511 * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
     1512 * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
     1513 * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
     1514 * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
     1515 * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
     1516 *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
     1517 * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
     1518 * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
     1519 *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     1520 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     1521 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
    13831522 */
    1384 var Library = wp.media.controller.Library,
    1385         MediaLibrary;
    1386 
    1387 MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{
    1388         defaults: _.defaults({
    1389                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
    1390                 filterable:      'uploaded',
    1391 
    1392                 displaySettings: false,
    1393                 priority:        80,
    1394                 syncSelection:   false
    1395         }, Library.prototype.defaults ),
     1523CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{
     1524        defaults: {
     1525                multiple:         false,
     1526                sortable:         true,
     1527                date:             false,
     1528                searchable:       false,
     1529                content:          'browse',
     1530                describe:         true,
     1531                dragInfo:         true,
     1532                idealColumnWidth: 170,
     1533                editing:          false,
     1534