Make WordPress Core

Ticket #40894: 40894.6.diff

File 40894.6.diff, 530.0 KB (added by adamsilverstein, 7 years ago)
  • .jshintrc

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

    diff --git Gruntfile.js Gruntfile.js
    index 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                priority:         60,
     1535                SettingsView:     false,
     1536                syncSelection:    false
     1537        },
    13961538
    13971539        /**
    13981540         * @since 3.9.0
    1399          *
    1400          * @param options
    14011541         */
    1402         initialize: function( options ) {
    1403                 this.media = options.media;
    1404                 this.type = options.type;
    1405                 this.set( 'library', wp.media.query({ type: this.type }) );
     1542        initialize: function() {
     1543                var collectionType = this.get('collectionType');
     1544
     1545                if ( 'video' === this.get( 'type' ) ) {
     1546                        collectionType = 'video-' + collectionType;
     1547                }
     1548
     1549                this.set( 'id', collectionType + '-edit' );
     1550                this.set( 'toolbar', collectionType + '-edit' );
    14061551
     1552                // If we haven't been provided a `library`, create a `Selection`.
     1553                if ( ! this.get('library') ) {
     1554                        this.set( 'library', new wp.media.model.Selection() );
     1555                }
     1556                // The single `Attachment` view to be used in the `Attachments` view.
     1557                if ( ! this.get('AttachmentView') ) {
     1558                        this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     1559                }
    14071560                Library.prototype.initialize.apply( this, arguments );
    14081561        },
    14091562
    MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.protot 
    14111564         * @since 3.9.0
    14121565         */
    14131566        activate: function() {
    1414                 // @todo this should use this.frame.
    1415                 if ( wp.media.frame.lastMime ) {
    1416                         this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
    1417                         delete wp.media.frame.lastMime;
    1418                 }
     1567                var library = this.get('library');
     1568
     1569                // Limit the library to images only.
     1570                library.props.set( 'type', this.get( 'type' ) );
     1571
     1572                // Watch for uploaded attachments.
     1573                this.get('library').observe( wp.Uploader.queue );
     1574
     1575                this.frame.on( 'content:render:browse', this.renderSettings, this );
     1576
    14191577                Library.prototype.activate.apply( this, arguments );
    1420         }
    1421 });
     1578        },
    14221579
    1423 module.exports = MediaLibrary;
     1580        /**
     1581         * @since 3.9.0
     1582         */
     1583        deactivate: function() {
     1584                // Stop watching for uploaded attachments.
     1585                this.get('library').unobserve( wp.Uploader.queue );
    14241586
    1425 },{}],13:[function(require,module,exports){
    1426 /**
    1427  * wp.media.controller.Region
    1428  *
    1429  * A region is a persistent application layout area.
    1430  *
    1431  * A region assumes one mode at any time, and can be switched to another.
    1432  *
    1433  * When mode changes, events are triggered on the region's parent view.
    1434  * The parent view will listen to specific events and fill the region with an
    1435  * appropriate view depending on mode. For example, a frame listens for the
    1436  * 'browse' mode t be activated on the 'content' view and then fills the region
    1437  * with an AttachmentsBrowser view.
    1438  *
    1439  * @memberOf wp.media.controller
    1440  *
    1441  * @class
    1442  *
    1443  * @param {object}        options          Options hash for the region.
    1444  * @param {string}        options.id       Unique identifier for the region.
    1445  * @param {Backbone.View} options.view     A parent view the region exists within.
    1446  * @param {string}        options.selector jQuery selector for the region within the parent view.
    1447  */
    1448 var Region = function( options ) {
    1449         _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
    1450 };
    1451 
    1452 // Use Backbone's self-propagating `extend` inheritance method.
    1453 Region.extend = Backbone.Model.extend;
    1454 
    1455 _.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{
    1456         /**
    1457          * Activate a mode.
    1458          *
    1459          * @since 3.5.0
    1460          *
    1461          * @param {string} mode
    1462          *
    1463          * @fires Region#activate
    1464          * @fires Region#deactivate
    1465          *
    1466          * @returns {wp.media.controller.Region} Returns itself to allow chaining.
    1467          */
    1468         mode: function( mode ) {
    1469                 if ( ! mode ) {
    1470                         return this._mode;
    1471                 }
    1472                 // Bail if we're trying to change to the current mode.
    1473                 if ( mode === this._mode ) {
    1474                         return this;
    1475                 }
    1476 
    1477                 /**
    1478                  * Region mode deactivation event.
    1479                  *
    1480                  * @event wp.media.controller.Region#deactivate
    1481                  */
    1482                 this.trigger('deactivate');
    1483 
    1484                 this._mode = mode;
    1485                 this.render( mode );
     1587                this.frame.off( 'content:render:browse', this.renderSettings, this );
    14861588
    1487                 /**
    1488                  * Region mode activation event.
    1489                  *
    1490                  * @event wp.media.controller.Region#activate
    1491                  */
    1492                 this.trigger('activate');
    1493                 return this;
     1589                Library.prototype.deactivate.apply( this, arguments );
    14941590        },
     1591
    14951592        /**
    1496          * Render a mode.
    1497          *
    1498          * @since 3.5.0
     1593         * Render the collection embed settings view in the browser sidebar.
    14991594         *
    1500          * @param {string} mode
     1595         * @todo This is against the pattern elsewhere in media. Typically the frame
     1596         *       is responsible for adding region mode callbacks. Explain.
    15011597         *
    1502          * @fires Region#create
    1503          * @fires Region#render
     1598         * @since 3.9.0
    15041599         *
    1505          * @returns {wp.media.controller.Region} Returns itself to allow chaining
     1600         * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
    15061601         */
    1507         render: function( mode ) {
    1508                 // If the mode isn't active, activate it.
    1509                 if ( mode && mode !== this._mode ) {
    1510                         return this.mode( mode );
    1511                 }
    1512 
    1513                 var set = { view: null },
    1514                         view;
    1515 
    1516                 /**
    1517                  * Create region view event.
    1518                  *
    1519                  * Region view creation takes place in an event callback on the frame.
    1520                  *
    1521                  * @event wp.media.controller.Region#create
    1522                  * @type {object}
    1523                  * @property {object} view
    1524                  */
    1525                 this.trigger( 'create', set );
    1526                 view = set.view;
     1602        renderSettings: function( attachmentsBrowserView ) {
     1603                var library = this.get('library'),
     1604                        collectionType = this.get('collectionType'),
     1605                        dragInfoText = this.get('dragInfoText'),
     1606                        SettingsView = this.get('SettingsView'),
     1607                        obj = {};
    15271608
    1528                 /**
    1529                  * Render region view event.
    1530                  *
    1531                  * Region view creation takes place in an event callback on the frame.
    1532                  *
    1533                  * @event wp.media.controller.Region#render
    1534                  * @type {object}
    1535                  */
    1536                 this.trigger( 'render', view );
    1537                 if ( view ) {
    1538                         this.set( view );
     1609                if ( ! library || ! attachmentsBrowserView ) {
     1610                        return;
    15391611                }
    1540                 return this;
    1541         },
    15421612
    1543         /**
    1544          * Get the region's view.
    1545          *
    1546          * @since 3.5.0
    1547          *
    1548          * @returns {wp.media.View}
    1549          */
    1550         get: function() {
    1551                 return this.view.views.first( this.selector );
    1552         },
     1613                library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
    15531614
    1554         /**
    1555          * Set the region's view as a subview of the frame.
    1556          *
    1557          * @since 3.5.0
    1558          *
    1559          * @param {Array|Object} views
    1560          * @param {Object} [options={}]
    1561          * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
    1562          */
    1563         set: function( views, options ) {
    1564                 if ( options ) {
    1565                         options.add = false;
    1566                 }
    1567                 return this.view.views.set( this.selector, views, options );
    1568         },
     1615                obj[ collectionType ] = new SettingsView({
     1616                        controller: this,
     1617                        model:      library[ collectionType ],
     1618                        priority:   40
     1619                });
    15691620
    1570         /**
    1571          * Trigger regional view events on the frame.
    1572          *
    1573          * @since 3.5.0
    1574          *
    1575          * @param {string} event
    1576          * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
    1577          */
    1578         trigger: function( event ) {
    1579                 var base, args;
     1621                attachmentsBrowserView.sidebar.set( obj );
    15801622
    1581                 if ( ! this._mode ) {
    1582                         return;
     1623                if ( dragInfoText ) {
     1624                        attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
     1625                                el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
     1626                                priority: -40
     1627                        }) );
    15831628                }
    15841629
    1585                 args = _.toArray( arguments );
    1586                 base = this.id + ':' + event;
    1587 
    1588                 // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
    1589                 args[0] = base + ':' + this._mode;
    1590                 this.view.trigger.apply( this.view, args );
     1630                // Add the 'Reverse order' button to the toolbar.
     1631                attachmentsBrowserView.toolbar.set( 'reverse', {
     1632                        text:     l10n.reverseOrder,
     1633                        priority: 80,
    15911634
    1592                 // Trigger `{this.id}:{event}` event on the frame.
    1593                 args[0] = base;
    1594                 this.view.trigger.apply( this.view, args );
    1595                 return this;
     1635                        click: function() {
     1636                                library.reset( library.toArray().reverse() );
     1637                        }
     1638                });
    15961639        }
    15971640});
    15981641
    1599 module.exports = Region;
     1642module.exports = CollectionEdit;
    16001643
    1601 },{}],14:[function(require,module,exports){
    1602 var Library = wp.media.controller.Library,
    1603         l10n = wp.media.view.l10n,
    1604         ReplaceImage;
     1644
     1645/***/ }),
     1646/* 36 */
     1647/***/ (function(module, exports) {
     1648
     1649var Selection = wp.media.model.Selection,
     1650        Library = wp.media.controller.Library,
     1651        CollectionAdd;
    16051652
    16061653/**
    1607  * wp.media.controller.ReplaceImage
     1654 * wp.media.controller.CollectionAdd
    16081655 *
    1609  * A state for replacing an image.
     1656 * A state for adding attachments to a collection (e.g. video playlist).
    16101657 *
    16111658 * @memberOf wp.media.controller
    16121659 *
    var Library = wp.media.controller.Library, 
    16161663 * @augments Backbone.Model
    16171664 *
    16181665 * @param {object}                     [attributes]                         The attributes hash passed to the state.
    1619  * @param {string}                     [attributes.id=replace-image]        Unique identifier.
    1620  * @param {string}                     [attributes.title=Replace Image]     Title for the state. Displays in the media menu and the frame's title region.
     1666 * @param {string}                     [attributes.id=library]      Unique identifier.
     1667 * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
     1668 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    16211669 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    1622  *                                                                          If one is not supplied, a collection of all images will be created.
    1623  * @param {boolean}                    [attributes.multiple=false]          Whether multi-select is enabled.
     1670 *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
     1671 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     1672 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     1673 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    16241674 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    16251675 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    1626  * @param {string}                     [attributes.menu=default]            Initial mode for the menu region.
    16271676 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    1628  * @param {string}                     [attributes.toolbar=replace]         Initial mode for the toolbar region.
    1629  * @param {int}                        [attributes.priority=60]             The priority for the state link in the media menu.
     1677 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    16301678 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    1631  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    1632  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    16331679 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    16341680 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1635  * @param {boolean}                    [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    16361681 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1637  * @param {boolean}                    [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
     1682 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     1683 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     1684 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     1685 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     1686 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
    16381687 */
    1639 ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.prototype */{
    1640         defaults: _.defaults({
    1641                 id:            'replace-image',
    1642                 title:         l10n.replaceImageTitle,
    1643                 multiple:      false,
     1688CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{
     1689        defaults: _.defaults( {
     1690                // Selection defaults. @see media.model.Selection
     1691                multiple:      'add',
     1692                // Attachments browser defaults. @see media.view.AttachmentsBrowser
    16441693                filterable:    'uploaded',
    1645                 toolbar:       'replace',
    1646                 menu:          false,
    1647                 priority:      60,
    1648                 syncSelection: true
     1694
     1695                priority:      100,
     1696                syncSelection: false
    16491697        }, Library.prototype.defaults ),
    16501698
    16511699        /**
    16521700         * @since 3.9.0
    1653          *
    1654          * @param options
    16551701         */
    1656         initialize: function( options ) {
    1657                 var library, comparator;
     1702        initialize: function() {
     1703                var collectionType = this.get('collectionType');
     1704
     1705                if ( 'video' === this.get( 'type' ) ) {
     1706                        collectionType = 'video-' + collectionType;
     1707                }
     1708
     1709                this.set( 'id', collectionType + '-library' );
     1710                this.set( 'toolbar', collectionType + '-add' );
     1711                this.set( 'menu', collectionType );
    16581712
    1659                 this.image = options.image;
    16601713                // If we haven't been provided a `library`, create a `Selection`.
    16611714                if ( ! this.get('library') ) {
    1662                         this.set( 'library', wp.media.query({ type: 'image' }) );
     1715                        this.set( 'library', wp.media.query({ type: this.get('type') }) );
    16631716                }
    1664 
    16651717                Library.prototype.initialize.apply( this, arguments );
     1718        },
     1719
     1720        /**
     1721         * @since 3.9.0
     1722         */
     1723        activate: function() {
     1724                var library = this.get('library'),
     1725                        editLibrary = this.get('editLibrary'),
     1726                        edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
     1727
     1728                if ( editLibrary && editLibrary !== edit ) {
     1729                        library.unobserve( editLibrary );
     1730                }
     1731
     1732                // Accepts attachments that exist in the original library and
     1733                // that do not exist in gallery's library.
     1734                library.validator = function( attachment ) {
     1735                        return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     1736                };
     1737
     1738                // Reset the library to ensure that all attachments are re-added
     1739                // to the collection. Do so silently, as calling `observe` will
     1740                // trigger the `reset` event.
     1741                library.reset( library.mirroring.models, { silent: true });
     1742                library.observe( edit );
     1743                this.set('editLibrary', edit);
     1744
     1745                Library.prototype.activate.apply( this, arguments );
     1746        }
     1747});
     1748
     1749module.exports = CollectionAdd;
     1750
     1751
     1752/***/ }),
     1753/* 37 */
     1754/***/ (function(module, exports) {
     1755
     1756var Attachment = wp.media.model.Attachment,
     1757        Library = wp.media.controller.Library,
     1758        l10n = wp.media.view.l10n,
     1759        FeaturedImage;
     1760
     1761/**
     1762 * wp.media.controller.FeaturedImage
     1763 *
     1764 * A state for selecting a featured image for a post.
     1765 *
     1766 * @memberOf wp.media.controller
     1767 *
     1768 * @class
     1769 * @augments wp.media.controller.Library
     1770 * @augments wp.media.controller.State
     1771 * @augments Backbone.Model
     1772 *
     1773 * @param {object}                     [attributes]                          The attributes hash passed to the state.
     1774 * @param {string}                     [attributes.id=featured-image]        Unique identifier.
     1775 * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
     1776 * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
     1777 *                                                                           If one is not supplied, a collection of all images will be created.
     1778 * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
     1779 * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
     1780 *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
     1781 * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
     1782 * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
     1783 * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
     1784 * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
     1785 * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
     1786 * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
     1787 *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
     1788 * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1789 * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
     1790 * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     1791 * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
     1792 * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
     1793 */
     1794FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{
     1795        defaults: _.defaults({
     1796                id:            'featured-image',
     1797                title:         l10n.setFeaturedImageTitle,
     1798                multiple:      false,
     1799                filterable:    'uploaded',
     1800                toolbar:       'featured-image',
     1801                priority:      60,
     1802                syncSelection: true
     1803        }, Library.prototype.defaults ),
     1804
     1805        /**
     1806         * @since 3.5.0
     1807         */
     1808        initialize: function() {
     1809                var library, comparator;
     1810
     1811                // If we haven't been provided a `library`, create a `Selection`.
     1812                if ( ! this.get('library') ) {
     1813                        this.set( 'library', wp.media.query({ type: 'image' }) );
     1814                }
     1815
     1816                Library.prototype.initialize.apply( this, arguments );
     1817
     1818                library    = this.get('library');
     1819                comparator = library.comparator;
    16661820
    1667                 library    = this.get('library');
    1668                 comparator = library.comparator;
    1669 
    16701821                // Overload the library's comparator to push items that are not in
    16711822                // the mirrored query to the front of the aggregate collection.
    16721823                library.comparator = function( a, b ) {
    ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.protot 
    16881839        },
    16891840
    16901841        /**
    1691          * @since 3.9.0
     1842         * @since 3.5.0
    16921843         */
    16931844        activate: function() {
    16941845                this.updateSelection();
     1846                this.frame.on( 'open', this.updateSelection, this );
     1847
    16951848                Library.prototype.activate.apply( this, arguments );
    16961849        },
    16971850
    16981851        /**
    1699          * @since 3.9.0
     1852         * @since 3.5.0
     1853         */
     1854        deactivate: function() {
     1855                this.frame.off( 'open', this.updateSelection, this );
     1856
     1857                Library.prototype.deactivate.apply( this, arguments );
     1858        },
     1859
     1860        /**
     1861         * @since 3.5.0
    17001862         */
    17011863        updateSelection: function() {
    17021864                var selection = this.get('selection'),
    1703                         attachment = this.image.attachment;
     1865                        id = wp.media.view.settings.post.featuredImageId,
     1866                        attachment;
     1867
     1868                if ( '' !== id && -1 !== id ) {
     1869                        attachment = Attachment.get( id );
     1870                        attachment.fetch();
     1871                }
    17041872
    17051873                selection.reset( attachment ? [ attachment ] : [] );
    17061874        }
    17071875});
    17081876
    1709 module.exports = ReplaceImage;
     1877module.exports = FeaturedImage;
    17101878
    1711 },{}],15:[function(require,module,exports){
    1712 var Controller = wp.media.controller,
    1713         SiteIconCropper;
     1879
     1880/***/ }),
     1881/* 38 */
     1882/***/ (function(module, exports) {
     1883
     1884var Library = wp.media.controller.Library,
     1885        l10n = wp.media.view.l10n,
     1886        ReplaceImage;
    17141887
    17151888/**
    1716  * wp.media.controller.SiteIconCropper
     1889 * wp.media.controller.ReplaceImage
    17171890 *
    1718  * A state for cropping a Site Icon.
     1891 * A state for replacing an image.
    17191892 *
    17201893 * @memberOf wp.media.controller
    17211894 *
    17221895 * @class
    1723  * @augments wp.media.controller.Cropper
     1896 * @augments wp.media.controller.Library
    17241897 * @augments wp.media.controller.State
    17251898 * @augments Backbone.Model
     1899 *
     1900 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     1901 * @param {string}                     [attributes.id=replace-image]        Unique identifier.
     1902 * @param {string}                     [attributes.title=Replace Image]     Title for the state. Displays in the media menu and the frame's title region.
     1903 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     1904 *                                                                          If one is not supplied, a collection of all images will be created.
     1905 * @param {boolean}                    [attributes.multiple=false]          Whether multi-select is enabled.
     1906 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     1907 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     1908 * @param {string}                     [attributes.menu=default]            Initial mode for the menu region.
     1909 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     1910 * @param {string}                     [attributes.toolbar=replace]         Initial mode for the toolbar region.
     1911 * @param {int}                        [attributes.priority=60]             The priority for the state link in the media menu.
     1912 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     1913 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     1914 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     1915 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1916 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     1917 * @param {boolean}                    [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     1918 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     1919 * @param {boolean}                    [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    17261920 */
    1727 SiteIconCropper = Controller.Cropper.extend(/** @lends wp.media.controller.SiteIconCropper.prototype */{
    1728         activate: function() {
    1729                 this.frame.on( 'content:create:crop', this.createCropContent, this );
    1730                 this.frame.on( 'close', this.removeCropper, this );
    1731                 this.set('selection', new Backbone.Collection(this.frame._selection.single));
    1732         },
     1921ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.prototype */{
     1922        defaults: _.defaults({
     1923                id:            'replace-image',
     1924                title:         l10n.replaceImageTitle,
     1925                multiple:      false,
     1926                filterable:    'uploaded',
     1927                toolbar:       'replace',
     1928                menu:          false,
     1929                priority:      60,
     1930                syncSelection: true
     1931        }, Library.prototype.defaults ),
    17331932
    1734         createCropContent: function() {
    1735                 this.cropperView = new wp.media.view.SiteIconCropper({
    1736                         controller: this,
    1737                         attachment: this.get('selection').first()
    1738                 });
    1739                 this.cropperView.on('image-loaded', this.createCropToolbar, this);
    1740                 this.frame.content.set(this.cropperView);
     1933        /**
     1934         * @since 3.9.0
     1935         *
     1936         * @param options
     1937         */
     1938        initialize: function( options ) {
     1939                var library, comparator;
     1940
     1941                this.image = options.image;
     1942                // If we haven't been provided a `library`, create a `Selection`.
     1943                if ( ! this.get('library') ) {
     1944                        this.set( 'library', wp.media.query({ type: 'image' }) );
     1945                }
     1946
     1947                Library.prototype.initialize.apply( this, arguments );
     1948
     1949                library    = this.get('library');
     1950                comparator = library.comparator;
     1951
     1952                // Overload the library's comparator to push items that are not in
     1953                // the mirrored query to the front of the aggregate collection.
     1954                library.comparator = function( a, b ) {
     1955                        var aInQuery = !! this.mirroring.get( a.cid ),
     1956                                bInQuery = !! this.mirroring.get( b.cid );
     1957
     1958                        if ( ! aInQuery && bInQuery ) {
     1959                                return -1;
     1960                        } else if ( aInQuery && ! bInQuery ) {
     1961                                return 1;
     1962                        } else {
     1963                                return comparator.apply( this, arguments );
     1964                        }
     1965                };
    17411966
     1967                // Add all items in the selection to the library, so any featured
     1968                // images that are not initially loaded still appear.
     1969                library.observe( this.get('selection') );
    17421970        },
    17431971
    1744         doCrop: function( attachment ) {
    1745                 var cropDetails = attachment.get( 'cropDetails' ),
    1746                         control = this.get( 'control' );
     1972        /**
     1973         * @since 3.9.0
     1974         */
     1975        activate: function() {
     1976                this.updateSelection();
     1977                Library.prototype.activate.apply( this, arguments );
     1978        },
    17471979
    1748                 cropDetails.dst_width  = control.params.width;
    1749                 cropDetails.dst_height = control.params.height;
     1980        /**
     1981         * @since 3.9.0
     1982         */
     1983        updateSelection: function() {
     1984                var selection = this.get('selection'),
     1985                        attachment = this.image.attachment;
    17501986
    1751                 return wp.ajax.post( 'crop-image', {
    1752                         nonce: attachment.get( 'nonces' ).edit,
    1753                         id: attachment.get( 'id' ),
    1754                         context: 'site-icon',
    1755                         cropDetails: cropDetails
    1756                 } );
     1987                selection.reset( attachment ? [ attachment ] : [] );
    17571988        }
    17581989});
    17591990
    1760 module.exports = SiteIconCropper;
     1991module.exports = ReplaceImage;
     1992
     1993
     1994/***/ }),
     1995/* 39 */
     1996/***/ (function(module, exports) {
     1997
     1998var l10n = wp.media.view.l10n,
     1999        EditImage;
    17612000
    1762 },{}],16:[function(require,module,exports){
    17632001/**
    1764  * wp.media.controller.StateMachine
    1765  *
    1766  * A state machine keeps track of state. It is in one state at a time,
    1767  * and can change from one state to another.
     2002 * wp.media.controller.EditImage
    17682003 *
    1769  * States are stored as models in a Backbone collection.
     2004 * A state for editing (cropping, etc.) an image.
    17702005 *
    17712006 * @memberOf wp.media.controller
    17722007 *
    1773  * @since 3.5.0
    1774  *
    17752008 * @class
     2009 * @augments wp.media.controller.State
    17762010 * @augments Backbone.Model
    1777  * @mixin
    1778  * @mixes Backbone.Events
    17792011 *
    1780  * @param {Array} states
     2012 * @param {object}                    attributes                      The attributes hash passed to the state.
     2013 * @param {wp.media.model.Attachment} attributes.model                The attachment.
     2014 * @param {string}                    [attributes.id=edit-image]      Unique identifier.
     2015 * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
     2016 * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
     2017 * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
     2018 * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
     2019 * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
    17812020 */
    1782 var StateMachine = function( states ) {
    1783         // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
    1784         this.states = new Backbone.Collection( states );
    1785 };
    1786 
    1787 // Use Backbone's self-propagating `extend` inheritance method.
    1788 StateMachine.extend = Backbone.Model.extend;
     2021EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{
     2022        defaults: {
     2023                id:      'edit-image',
     2024                title:   l10n.editImage,
     2025                menu:    false,
     2026                toolbar: 'edit-image',
     2027                content: 'edit-image',
     2028                url:     ''
     2029        },
    17892030
    1790 _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{
    17912031        /**
    1792          * Fetch a state.
    1793          *
    1794          * If no `id` is provided, returns the active state.
    1795          *
    1796          * Implicitly creates states.
    1797          *
    1798          * Ensure that the `states` collection exists so the `StateMachine`
    1799          *   can be used as a mixin.
    1800          *
    1801          * @since 3.5.0
    1802          *
    1803          * @param {string} id
    1804          * @returns {wp.media.controller.State} Returns a State model
    1805          *   from the StateMachine collection
     2032         * @since 3.9.0
    18062033         */
    1807         state: function( id ) {
    1808                 this.states = this.states || new Backbone.Collection();
    1809 
    1810                 // Default to the active state.
    1811                 id = id || this._state;
    1812 
    1813                 if ( id && ! this.states.get( id ) ) {
    1814                         this.states.add({ id: id });
    1815                 }
    1816                 return this.states.get( id );
     2034        activate: function() {
     2035                this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
    18172036        },
    18182037
    18192038        /**
    1820          * Sets the active state.
    1821          *
    1822          * Bail if we're trying to select the current state, if we haven't
    1823          * created the `states` collection, or are trying to select a state
    1824          * that does not exist.
    1825          *
    1826          * @since 3.5.0
    1827          *
    1828          * @param {string} id
    1829          *
    1830          * @fires wp.media.controller.State#deactivate
    1831          * @fires wp.media.controller.State#activate
    1832          *
    1833          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
     2039         * @since 3.9.0
    18342040         */
    1835         setState: function( id ) {
    1836                 var previous = this.state();
    1837 
    1838                 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
    1839                         return this;
    1840                 }
    1841 
    1842                 if ( previous ) {
    1843                         previous.trigger('deactivate');
    1844                         this._lastState = previous.id;
    1845                 }
    1846 
    1847                 this._state = id;
    1848                 this.state().trigger('activate');
    1849 
    1850                 return this;
     2041        deactivate: function() {
     2042                this.frame.off( 'toolbar:render:edit-image' );
    18512043        },
    18522044
    18532045        /**
    1854          * Returns the previous active state.
    1855          *
    1856          * Call the `state()` method with no parameters to retrieve the current
    1857          * active state.
    1858          *
    1859          * @since 3.5.0
    1860          *
    1861          * @returns {wp.media.controller.State} Returns a State model
    1862          *    from the StateMachine collection
     2046         * @since 3.9.0
    18632047         */
    1864         lastState: function() {
    1865                 if ( this._lastState ) {
    1866                         return this.state( this._lastState );
    1867                 }
     2048        toolbar: function() {
     2049                var frame = this.frame,
     2050                        lastState = frame.lastState(),
     2051                        previous = lastState && lastState.id;
     2052
     2053                frame.toolbar.set( new wp.media.view.Toolbar({
     2054                        controller: frame,
     2055                        items: {
     2056                                back: {
     2057                                        style: 'primary',
     2058                                        text:     l10n.back,
     2059                                        priority: 20,
     2060                                        click:    function() {
     2061                                                if ( previous ) {
     2062                                                        frame.setState( previous );
     2063                                                } else {
     2064                                                        frame.close();
     2065                                                }
     2066                                        }
     2067                                }
     2068                        }
     2069                }) );
    18682070        }
    18692071});
    18702072
    1871 // Map all event binding and triggering on a StateMachine to its `states` collection.
    1872 _.each([ 'on', 'off', 'trigger' ], function( method ) {
    1873         /**
    1874          * @function on
    1875          * @memberOf wp.media.controller.StateMachine
    1876          * @instance
    1877          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    1878          */
    1879         /**
    1880          * @function off
    1881          * @memberOf wp.media.controller.StateMachine
    1882          * @instance
    1883          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    1884          */
    1885         /**
    1886          * @function trigger
    1887          * @memberOf wp.media.controller.StateMachine
    1888          * @instance
    1889          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    1890          */
    1891         StateMachine.prototype[ method ] = function() {
    1892                 // Ensure that the `states` collection exists so the `StateMachine`
    1893                 // can be used as a mixin.
    1894                 this.states = this.states || new Backbone.Collection();
    1895                 // Forward the method to the `states` collection.
    1896                 this.states[ method ].apply( this.states, arguments );
    1897                 return this;
    1898         };
    1899 });
     2073module.exports = EditImage;
    19002074
    1901 module.exports = StateMachine;
    19022075
    1903 },{}],17:[function(require,module,exports){
     2076/***/ }),
     2077/* 40 */
     2078/***/ (function(module, exports) {
     2079
    19042080/**
    1905  * wp.media.controller.State
    1906  *
    1907  * A state is a step in a workflow that when set will trigger the controllers
    1908  * for the regions to be updated as specified in the frame.
    1909  *
    1910  * A state has an event-driven lifecycle:
    1911  *
    1912  *     'ready'      triggers when a state is added to a state machine's collection.
    1913  *     'activate'   triggers when a state is activated by a state machine.
    1914  *     'deactivate' triggers when a state is deactivated by a state machine.
    1915  *     'reset'      is not triggered automatically. It should be invoked by the
    1916  *                  proper controller to reset the state to its default.
     2081 * wp.media.controller.MediaLibrary
    19172082 *
    19182083 * @memberOf wp.media.controller
    19192084 *
    19202085 * @class
     2086 * @augments wp.media.controller.Library
     2087 * @augments wp.media.controller.State
    19212088 * @augments Backbone.Model
    19222089 */
    1923 var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{
    1924         /**
    1925          * Constructor.
    1926          *
    1927          * @since 3.5.0
    1928          */
    1929         constructor: function() {
    1930                 this.on( 'activate', this._preActivate, this );
    1931                 this.on( 'activate', this.activate, this );
    1932                 this.on( 'activate', this._postActivate, this );
    1933                 this.on( 'deactivate', this._deactivate, this );
    1934                 this.on( 'deactivate', this.deactivate, this );
    1935                 this.on( 'reset', this.reset, this );
    1936                 this.on( 'ready', this._ready, this );
    1937                 this.on( 'ready', this.ready, this );
    1938                 /**
    1939                  * Call parent constructor with passed arguments
    1940                  */
    1941                 Backbone.Model.apply( this, arguments );
    1942                 this.on( 'change:menu', this._updateMenu, this );
    1943         },
    1944         /**
    1945          * Ready event callback.
    1946          *
    1947          * @abstract
    1948          * @since 3.5.0
    1949          */
    1950         ready: function() {},
     2090var Library = wp.media.controller.Library,
     2091        MediaLibrary;
    19512092
    1952         /**
    1953          * Activate event callback.
    1954          *
    1955          * @abstract
    1956          * @since 3.5.0
    1957          */
    1958         activate: function() {},
     2093MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{
     2094        defaults: _.defaults({
     2095                // Attachments browser defaults. @see media.view.AttachmentsBrowser
     2096                filterable:      'uploaded',
    19592097
    1960         /**
    1961          * Deactivate event callback.
    1962          *
    1963          * @abstract
    1964          * @since 3.5.0
    1965          */
    1966         deactivate: function() {},
     2098                displaySettings: false,
     2099                priority:        80,
     2100                syncSelection:   false
     2101        }, Library.prototype.defaults ),
    19672102
    19682103        /**
    1969          * Reset event callback.
     2104         * @since 3.9.0
    19702105         *
    1971          * @abstract
    1972          * @since 3.5.0
    1973          */
    1974         reset: function() {},
    1975 
    1976         /**
    1977          * @access private
    1978          * @since 3.5.0
     2106         * @param options
    19792107         */
    1980         _ready: function() {
    1981                 this._updateMenu();
    1982         },
     2108        initialize: function( options ) {
     2109                this.media = options.media;
     2110                this.type = options.type;
     2111                this.set( 'library', wp.media.query({ type: this.type }) );
    19832112
    1984         /**
    1985          * @access private
    1986          * @since 3.5.0
    1987         */
    1988         _preActivate: function() {
    1989                 this.active = true;
     2113                Library.prototype.initialize.apply( this, arguments );
    19902114        },
    19912115
    19922116        /**
    1993          * @access private
    1994          * @since 3.5.0
     2117         * @since 3.9.0
    19952118         */
    1996         _postActivate: function() {
    1997                 this.on( 'change:menu', this._menu, this );
    1998                 this.on( 'change:titleMode', this._title, this );
    1999                 this.on( 'change:content', this._content, this );
    2000                 this.on( 'change:toolbar', this._toolbar, this );
     2119        activate: function() {
     2120                // @todo this should use this.frame.
     2121                if ( wp.media.frame.lastMime ) {
     2122                        this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
     2123                        delete wp.media.frame.lastMime;
     2124                }
     2125                Library.prototype.activate.apply( this, arguments );
     2126        }
     2127});
    20012128
    2002                 this.frame.on( 'title:render:default', this._renderTitle, this );
     2129module.exports = MediaLibrary;
    20032130
    2004                 this._title();
    2005                 this._menu();
    2006                 this._toolbar();
    2007                 this._content();
    2008                 this._router();
    2009         },
    20102131
    2011         /**
    2012          * @access private
    2013          * @since 3.5.0
    2014          */
    2015         _deactivate: function() {
    2016                 this.active = false;
     2132/***/ }),
     2133/* 41 */
     2134/***/ (function(module, exports) {
    20172135
    2018                 this.frame.off( 'title:render:default', this._renderTitle, this );
     2136var l10n = wp.media.view.l10n,
     2137        $ = Backbone.$,
     2138        Embed;
    20192139
    2020                 this.off( 'change:menu', this._menu, this );
    2021                 this.off( 'change:titleMode', this._title, this );
    2022                 this.off( 'change:content', this._content, this );
    2023                 this.off( 'change:toolbar', this._toolbar, this );
     2140/**
     2141 * wp.media.controller.Embed
     2142 *
     2143 * A state for embedding media from a URL.
     2144 *
     2145 * @memberOf wp.media.controller
     2146 *
     2147 * @class
     2148 * @augments wp.media.controller.State
     2149 * @augments Backbone.Model
     2150 *
     2151 * @param {object} attributes                         The attributes hash passed to the state.
     2152 * @param {string} [attributes.id=embed]              Unique identifier.
     2153 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
     2154 * @param {string} [attributes.content=embed]         Initial mode for the content region.
     2155 * @param {string} [attributes.menu=default]          Initial mode for the menu region.
     2156 * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
     2157 * @param {string} [attributes.menu=false]            Initial mode for the menu region.
     2158 * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
     2159 * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
     2160 * @param {string} [attributes.url]                   The embed URL.
     2161 * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
     2162 */
     2163Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{
     2164        defaults: {
     2165                id:       'embed',
     2166                title:    l10n.insertFromUrlTitle,
     2167                content:  'embed',
     2168                menu:     'default',
     2169                toolbar:  'main-embed',
     2170                priority: 120,
     2171                type:     'link',
     2172                url:      '',
     2173                metadata: {}
    20242174        },
    20252175
    2026         /**
    2027          * @access private
    2028          * @since 3.5.0
    2029          */
    2030         _title: function() {
    2031                 this.frame.title.render( this.get('titleMode') || 'default' );
    2032         },
     2176        // The amount of time used when debouncing the scan.
     2177        sensitivity: 400,
    20332178
    2034         /**
    2035          * @access private
    2036          * @since 3.5.0
    2037          */
    2038         _renderTitle: function( view ) {
    2039                 view.$el.text( this.get('title') || '' );
     2179        initialize: function(options) {
     2180                this.metadata = options.metadata;
     2181                this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
     2182                this.props = new Backbone.Model( this.metadata || { url: '' });
     2183                this.props.on( 'change:url', this.debouncedScan, this );
     2184                this.props.on( 'change:url', this.refresh, this );
     2185                this.on( 'scan', this.scanImage, this );
    20402186        },
    20412187
    20422188        /**
    2043          * @access private
    2044          * @since 3.5.0
     2189         * Trigger a scan of the embedded URL's content for metadata required to embed.
     2190         *
     2191         * @fires wp.media.controller.Embed#scan
    20452192         */
    2046         _router: function() {
    2047                 var router = this.frame.router,
    2048                         mode = this.get('router'),
    2049                         view;
     2193        scan: function() {
     2194                var scanners,
     2195                        embed = this,
     2196                        attributes = {
     2197                                type: 'link',
     2198                                scanners: []
     2199                        };
    20502200
    2051                 this.frame.$el.toggleClass( 'hide-router', ! mode );
    2052                 if ( ! mode ) {
    2053                         return;
     2201                // Scan is triggered with the list of `attributes` to set on the
     2202                // state, useful for the 'type' attribute and 'scanners' attribute,
     2203                // an array of promise objects for asynchronous scan operations.
     2204                if ( this.props.get('url') ) {
     2205                        this.trigger( 'scan', attributes );
    20542206                }
    20552207
    2056                 this.frame.router.render( mode );
    2057 
    2058                 view = router.get();
    2059                 if ( view && view.select ) {
    2060                         view.select( this.frame.content.mode() );
     2208                if ( attributes.scanners.length ) {
     2209                        scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
     2210                        scanners.always( function() {
     2211                                if ( embed.get('scanners') === scanners ) {
     2212                                        embed.set( 'loading', false );
     2213                                }
     2214                        });
     2215                } else {
     2216                        attributes.scanners = null;
    20612217                }
    2062         },
    20632218
     2219                attributes.loading = !! attributes.scanners;
     2220                this.set( attributes );
     2221        },
    20642222        /**
    2065          * @access private
    2066          * @since 3.5.0
     2223         * Try scanning the embed as an image to discover its dimensions.
     2224         *
     2225         * @param {Object} attributes
    20672226         */
    2068         _menu: function() {
    2069                 var menu = this.frame.menu,
    2070                         mode = this.get('menu'),
    2071                         view;
     2227        scanImage: function( attributes ) {
     2228                var frame = this.frame,
     2229                        state = this,
     2230                        url = this.props.get('url'),
     2231                        image = new Image(),
     2232                        deferred = $.Deferred();
    20722233
    2073                 this.frame.$el.toggleClass( 'hide-menu', ! mode );
    2074                 if ( ! mode ) {
    2075                         return;
    2076                 }
     2234                attributes.scanners.push( deferred.promise() );
    20772235
    2078                 menu.mode( mode );
     2236                // Try to load the image and find its width/height.
     2237                image.onload = function() {
     2238                        deferred.resolve();
    20792239
    2080                 view = menu.get();
    2081                 if ( view && view.select ) {
    2082                         view.select( this.id );
    2083                 }
    2084         },
     2240                        if ( state !== frame.state() || url !== state.props.get('url') ) {
     2241                                return;
     2242                        }
    20852243
    2086         /**
    2087          * @access private
    2088          * @since 3.5.0
    2089          */
    2090         _updateMenu: function() {
    2091                 var previous = this.previous('menu'),
    2092                         menu = this.get('menu');
     2244                        state.set({
     2245                                type: 'image'
     2246                        });
    20932247
    2094                 if ( previous ) {
    2095                         this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
    2096                 }
     2248                        state.props.set({
     2249                                width:  image.width,
     2250                                height: image.height
     2251                        });
     2252                };
    20972253
    2098                 if ( menu ) {
    2099                         this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
    2100                 }
     2254                image.onerror = deferred.reject;
     2255                image.src = url;
    21012256        },
    21022257
    2103         /**
    2104          * Create a view in the media menu for the state.
    2105          *
    2106          * @access private
    2107          * @since 3.5.0
    2108          *
    2109          * @param {media.view.Menu} view The menu view.
    2110          */
    2111         _renderMenu: function( view ) {
    2112                 var menuItem = this.get('menuItem'),
    2113                         title = this.get('title'),
    2114                         priority = this.get('priority');
    2115 
    2116                 if ( ! menuItem && title ) {
    2117                         menuItem = { text: title };
     2258        refresh: function() {
     2259                this.frame.toolbar.get().refresh();
     2260        },
    21182261
    2119                         if ( priority ) {
    2120                                 menuItem.priority = priority;
    2121                         }
    2122                 }
     2262        reset: function() {
     2263                this.props.clear().set({ url: '' });
    21232264
    2124                 if ( ! menuItem ) {
    2125                         return;
     2265                if ( this.active ) {
     2266                        this.refresh();
    21262267                }
    2127 
    2128                 view.set( this.id, menuItem );
    21292268        }
    21302269});
    21312270
    2132 _.each(['toolbar','content'], function( region ) {
    2133         /**
    2134          * @access private
    2135          */
    2136         State.prototype[ '_' + region ] = function() {
    2137                 var mode = this.get( region );
    2138                 if ( mode ) {
    2139                         this.frame[ region ].render( mode );
    2140                 }
    2141         };
    2142 });
     2271module.exports = Embed;
    21432272
    2144 module.exports = State;
    21452273
    2146 },{}],18:[function(require,module,exports){
     2274/***/ }),
     2275/* 42 */
     2276/***/ (function(module, exports) {
     2277
     2278var l10n = wp.media.view.l10n,
     2279        Cropper;
     2280
    21472281/**
    2148  * wp.media.selectionSync
    2149  *
    2150  * Sync an attachments selection in a state with another state.
     2282 * wp.media.controller.Cropper
    21512283 *
    2152  * Allows for selecting multiple images in the Add Media workflow, and then
    2153  * switching to the Insert Gallery workflow while preserving the attachments selection.
     2284 * A state for cropping an image.
    21542285 *
    2155  * @memberOf wp.media
     2286 * @memberOf wp.media.controller
    21562287 *
    2157  * @mixin
     2288 * @class
     2289 * @augments wp.media.controller.State
     2290 * @augments Backbone.Model
    21582291 */
    2159 var selectionSync = {
    2160         /**
    2161          * @since 3.5.0
    2162          */
    2163         syncSelection: function() {
    2164                 var selection = this.get('selection'),
    2165                         manager = this.frame._selection;
    2166 
    2167                 if ( ! this.get('syncSelection') || ! manager || ! selection ) {
    2168                         return;
    2169                 }
     2292Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{
     2293        defaults: {
     2294                id:          'cropper',
     2295                title:       l10n.cropImage,
     2296                // Region mode defaults.
     2297                toolbar:     'crop',
     2298                content:     'crop',
     2299                router:      false,
     2300                canSkipCrop: false,
    21702301
    2171                 // If the selection supports multiple items, validate the stored
    2172                 // attachments based on the new selection's conditions. Record
    2173                 // the attachments that are not included; we'll maintain a
    2174                 // reference to those. Other attachments are considered in flux.
    2175                 if ( selection.multiple ) {
    2176                         selection.reset( [], { silent: true });
    2177                         selection.validateAll( manager.attachments );
    2178                         manager.difference = _.difference( manager.attachments.models, selection.models );
    2179                 }
     2302                // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state.
     2303                doCropArgs: {}
     2304        },
    21802305
    2181                 // Sync the selection's single item with the master.
    2182                 selection.single( manager.single );
     2306        activate: function() {
     2307                this.frame.on( 'content:create:crop', this.createCropContent, this );
     2308                this.frame.on( 'close', this.removeCropper, this );
     2309                this.set('selection', new Backbone.Collection(this.frame._selection.single));
    21832310        },
    21842311
    2185         /**
    2186          * Record the currently active attachments, which is a combination
    2187          * of the selection's attachments and the set of selected
    2188          * attachments that this specific selection considered invalid.
    2189          * Reset the difference and record the single attachment.
    2190          *
    2191          * @since 3.5.0
    2192          */
    2193         recordSelection: function() {
    2194                 var selection = this.get('selection'),
    2195                         manager = this.frame._selection;
     2312        deactivate: function() {
     2313                this.frame.toolbar.mode('browse');
     2314        },
    21962315
    2197                 if ( ! this.get('syncSelection') || ! manager || ! selection ) {
    2198                         return;
    2199                 }
     2316        createCropContent: function() {
     2317                this.cropperView = new wp.media.view.Cropper({
     2318                        controller: this,
     2319                        attachment: this.get('selection').first()
     2320                });
     2321                this.cropperView.on('image-loaded', this.createCropToolbar, this);
     2322                this.frame.content.set(this.cropperView);
    22002323
    2201                 if ( selection.multiple ) {
    2202                         manager.attachments.reset( selection.toArray().concat( manager.difference ) );
    2203                         manager.difference = [];
    2204                 } else {
    2205                         manager.attachments.add( selection.toArray() );
    2206                 }
     2324        },
     2325        removeCropper: function() {
     2326                this.imgSelect.cancelSelection();
     2327                this.imgSelect.setOptions({remove: true});
     2328                this.imgSelect.update();
     2329                this.cropperView.remove();
     2330        },
     2331        createCropToolbar: function() {
     2332                var canSkipCrop, toolbarOptions;
    22072333
    2208                 manager.single = selection._single;
    2209         }
    2210 };
     2334                canSkipCrop = this.get('canSkipCrop') || false;
    22112335
    2212 module.exports = selectionSync;
     2336                toolbarOptions = {
     2337                        controller: this.frame,
     2338                        items: {
     2339                                insert: {
     2340                                        style:    'primary',
     2341                                        text:     l10n.cropImage,
     2342                                        priority: 80,
     2343                                        requires: { library: false, selection: false },
    22132344
    2214 },{}],19:[function(require,module,exports){
    2215 var media = wp.media,
    2216         $ = jQuery,
    2217         l10n;
     2345                                        click: function() {
     2346                                                var controller = this.controller,
     2347                                                        selection;
    22182348
    2219 media.isTouchDevice = ( 'ontouchend' in document );
     2349                                                selection = controller.state().get('selection').first();
     2350                                                selection.set({cropDetails: controller.state().imgSelect.getSelection()});
    22202351
    2221 // Link any localized strings.
    2222 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
     2352                                                this.$el.text(l10n.cropping);
     2353                                                this.$el.attr('disabled', true);
    22232354
    2224 // Link any settings.
    2225 media.view.settings = l10n.settings || {};
    2226 delete l10n.settings;
     2355                                                controller.state().doCrop( selection ).done( function( croppedImage ) {
     2356                                                        controller.trigger('cropped', croppedImage );
     2357                                                        controller.close();
     2358                                                }).fail( function() {
     2359                                                        controller.trigger('content:error:crop');
     2360                                                });
     2361                                        }
     2362                                }
     2363                        }
     2364                };
    22272365
    2228 // Copy the `post` setting over to the model settings.
    2229 media.model.settings.post = media.view.settings.post;
     2366                if ( canSkipCrop ) {
     2367                        _.extend( toolbarOptions.items, {
     2368                                skip: {
     2369                                        style:      'secondary',
     2370                                        text:       l10n.skipCropping,
     2371                                        priority:   70,
     2372                                        requires:   { library: false, selection: false },
     2373                                        click:      function() {
     2374                                                var selection = this.controller.state().get('selection').first();
     2375                                                this.controller.state().cropperView.remove();
     2376                                                this.controller.trigger('skippedcrop', selection);
     2377                                                this.controller.close();
     2378                                        }
     2379                                }
     2380                        });
     2381                }
    22302382
    2231 // Check if the browser supports CSS 3.0 transitions
    2232 $.support.transition = (function(){
    2233         var style = document.documentElement.style,
    2234                 transitions = {
    2235                         WebkitTransition: 'webkitTransitionEnd',
    2236                         MozTransition:    'transitionend',
    2237                         OTransition:      'oTransitionEnd otransitionend',
    2238                         transition:       'transitionend'
    2239                 }, transition;
     2383                this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
     2384        },
    22402385
    2241         transition = _.find( _.keys( transitions ), function( transition ) {
    2242                 return ! _.isUndefined( style[ transition ] );
    2243         });
     2386        doCrop: function( attachment ) {
     2387                return wp.ajax.post( 'custom-header-crop', _.extend(
     2388                        {},
     2389                        this.defaults.doCropArgs,
     2390                        {
     2391                                nonce: attachment.get( 'nonces' ).edit,
     2392                                id: attachment.get( 'id' ),
     2393                                cropDetails: attachment.get( 'cropDetails' )
     2394                        }
     2395                ) );
     2396        }
     2397});
    22442398
    2245         return transition && {
    2246                 end: transitions[ transition ]
    2247         };
    2248 }());
     2399module.exports = Cropper;
    22492400
    2250 /**
    2251  * A shared event bus used to provide events into
    2252  * the media workflows that 3rd-party devs can use to hook
    2253  * in.
    2254  */
    2255 media.events = _.extend( {}, Backbone.Events );
     2401
     2402/***/ }),
     2403/* 43 */
     2404/***/ (function(module, exports) {
     2405
     2406var Controller = wp.media.controller,
     2407        CustomizeImageCropper;
    22562408
    22572409/**
    2258  * Makes it easier to bind events using transitions.
     2410 * wp.media.controller.CustomizeImageCropper
    22592411 *
    2260  * @param {string} selector
    2261  * @param {Number} sensitivity
    2262  * @returns {Promise}
     2412 * @memberOf wp.media.controller
     2413 *
     2414 * A state for cropping an image.
     2415 *
     2416 * @class
     2417 * @augments wp.media.controller.Cropper
     2418 * @augments wp.media.controller.State
     2419 * @augments Backbone.Model
    22632420 */
    2264 media.transition = function( selector, sensitivity ) {
    2265         var deferred = $.Deferred();
     2421CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{
     2422        doCrop: function( attachment ) {
     2423                var cropDetails = attachment.get( 'cropDetails' ),
     2424                        control = this.get( 'control' ),
     2425                        ratio = cropDetails.width / cropDetails.height;
    22662426
    2267         sensitivity = sensitivity || 2000;
     2427                // Use crop measurements when flexible in both directions.
     2428                if ( control.params.flex_width && control.params.flex_height ) {
     2429                        cropDetails.dst_width  = cropDetails.width;
     2430                        cropDetails.dst_height = cropDetails.height;
    22682431
    2269         if ( $.support.transition ) {
    2270                 if ( ! (selector instanceof $) ) {
    2271                         selector = $( selector );
     2432                // Constrain flexible side based on image ratio and size of the fixed side.
     2433                } else {
     2434                        cropDetails.dst_width  = control.params.flex_width  ? control.params.height * ratio : control.params.width;
     2435                        cropDetails.dst_height = control.params.flex_height ? control.params.width  / ratio : control.params.height;
    22722436                }
    22732437
    2274                 // Resolve the deferred when the first element finishes animating.
    2275                 selector.first().one( $.support.transition.end, deferred.resolve );
     2438                return wp.ajax.post( 'crop-image', {
     2439                        wp_customize: 'on',
     2440                        nonce: attachment.get( 'nonces' ).edit,
     2441                        id: attachment.get( 'id' ),
     2442                        context: control.id,
     2443                        cropDetails: cropDetails
     2444                } );
     2445        }
     2446});
    22762447
    2277                 // Just in case the event doesn't trigger, fire a callback.
    2278                 _.delay( deferred.resolve, sensitivity );
     2448module.exports = CustomizeImageCropper;
    22792449
    2280         // Otherwise, execute on the spot.
    2281         } else {
    2282                 deferred.resolve();
    2283         }
    22842450
    2285         return deferred.promise();
    2286 };
     2451/***/ }),
     2452/* 44 */
     2453/***/ (function(module, exports) {
    22872454
    2288 media.controller.Region = require( './controllers/region.js' );
    2289 media.controller.StateMachine = require( './controllers/state-machine.js' );
    2290 media.controller.State = require( './controllers/state.js' );
    2291 
    2292 media.selectionSync = require( './utils/selection-sync.js' );
    2293 media.controller.Library = require( './controllers/library.js' );
    2294 media.controller.ImageDetails = require( './controllers/image-details.js' );
    2295 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
    2296 media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
    2297 media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
    2298 media.controller.CollectionAdd = require( './controllers/collection-add.js' );
    2299 media.controller.FeaturedImage = require( './controllers/featured-image.js' );
    2300 media.controller.ReplaceImage = require( './controllers/replace-image.js' );
    2301 media.controller.EditImage = require( './controllers/edit-image.js' );
    2302 media.controller.MediaLibrary = require( './controllers/media-library.js' );
    2303 media.controller.Embed = require( './controllers/embed.js' );
    2304 media.controller.Cropper = require( './controllers/cropper.js' );
    2305 media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' );
    2306 media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' );
    2307 
    2308 media.View = require( './views/view.js' );
    2309 media.view.Frame = require( './views/frame.js' );
    2310 media.view.MediaFrame = require( './views/media-frame.js' );
    2311 media.view.MediaFrame.Select = require( './views/frame/select.js' );
    2312 media.view.MediaFrame.Post = require( './views/frame/post.js' );
    2313 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
    2314 media.view.Modal = require( './views/modal.js' );
    2315 media.view.FocusManager = require( './views/focus-manager.js' );
    2316 media.view.UploaderWindow = require( './views/uploader/window.js' );
    2317 media.view.EditorUploader = require( './views/uploader/editor.js' );
    2318 media.view.UploaderInline = require( './views/uploader/inline.js' );
    2319 media.view.UploaderStatus = require( './views/uploader/status.js' );
    2320 media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
    2321 media.view.Toolbar = require( './views/toolbar.js' );
    2322 media.view.Toolbar.Select = require( './views/toolbar/select.js' );
    2323 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
    2324 media.view.Button = require( './views/button.js' );
    2325 media.view.ButtonGroup = require( './views/button-group.js' );
    2326 media.view.PriorityList = require( './views/priority-list.js' );
    2327 media.view.MenuItem = require( './views/menu-item.js' );
    2328 media.view.Menu = require( './views/menu.js' );
    2329 media.view.RouterItem = require( './views/router-item.js' );
    2330 media.view.Router = require( './views/router.js' );
    2331 media.view.Sidebar = require( './views/sidebar.js' );
    2332 media.view.Attachment = require( './views/attachment.js' );
    2333 media.view.Attachment.Library = require( './views/attachment/library.js' );
    2334 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
    2335 media.view.Attachments = require( './views/attachments.js' );
    2336 media.view.Search = require( './views/search.js' );
    2337 media.view.AttachmentFilters = require( './views/attachment-filters.js' );
    2338 media.view.DateFilter = require( './views/attachment-filters/date.js' );
    2339 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
    2340 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
    2341 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
    2342 media.view.Selection = require( './views/selection.js' );
    2343 media.view.Attachment.Selection = require( './views/attachment/selection.js' );
    2344 media.view.Attachments.Selection = require( './views/attachments/selection.js' );
    2345 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
    2346 media.view.Settings = require( './views/settings.js' );
    2347 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
    2348 media.view.Settings.Gallery = require( './views/settings/gallery.js' );
    2349 media.view.Settings.Playlist = require( './views/settings/playlist.js' );
    2350 media.view.Attachment.Details = require( './views/attachment/details.js' );
    2351 media.view.AttachmentCompat = require( './views/attachment-compat.js' );
    2352 media.view.Iframe = require( './views/iframe.js' );
    2353 media.view.Embed = require( './views/embed.js' );
    2354 media.view.Label = require( './views/label.js' );
    2355 media.view.EmbedUrl = require( './views/embed/url.js' );
    2356 media.view.EmbedLink = require( './views/embed/link.js' );
    2357 media.view.EmbedImage = require( './views/embed/image.js' );
    2358 media.view.ImageDetails = require( './views/image-details.js' );
    2359 media.view.Cropper = require( './views/cropper.js' );
    2360 media.view.SiteIconCropper = require( './views/site-icon-cropper.js' );
    2361 media.view.SiteIconPreview = require( './views/site-icon-preview.js' );
    2362 media.view.EditImage = require( './views/edit-image.js' );
    2363 media.view.Spinner = require( './views/spinner.js' );
    2364 
    2365 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/customize-image-cropper.js":4,"./controllers/edit-image.js":5,"./controllers/embed.js":6,"./controllers/featured-image.js":7,"./controllers/gallery-add.js":8,"./controllers/gallery-edit.js":9,"./controllers/image-details.js":10,"./controllers/library.js":11,"./controllers/media-library.js":12,"./controllers/region.js":13,"./controllers/replace-image.js":14,"./controllers/site-icon-cropper.js":15,"./controllers/state-machine.js":16,"./controllers/state.js":17,"./utils/selection-sync.js":18,"./views/attachment-compat.js":20,"./views/attachment-filters.js":21,"./views/attachment-filters/all.js":22,"./views/attachment-filters/date.js":23,"./views/attachment-filters/uploaded.js":24,"./views/attachment.js":25,"./views/attachment/details.js":26,"./views/attachment/edit-library.js":27,"./views/attachment/edit-selection.js":28,"./views/attachment/library.js":29,"./views/attachment/selection.js":30,"./views/attachments.js":31,"./views/attachments/browser.js":32,"./views/attachments/selection.js":33,"./views/button-group.js":34,"./views/button.js":35,"./views/cropper.js":36,"./views/edit-image.js":37,"./views/embed.js":38,"./views/embed/image.js":39,"./views/embed/link.js":40,"./views/embed/url.js":41,"./views/focus-manager.js":42,"./views/frame.js":43,"./views/frame/image-details.js":44,"./views/frame/post.js":45,"./views/frame/select.js":46,"./views/iframe.js":47,"./views/image-details.js":48,"./views/label.js":49,"./views/media-frame.js":50,"./views/menu-item.js":51,"./views/menu.js":52,"./views/modal.js":53,"./views/priority-list.js":54,"./views/router-item.js":55,"./views/router.js":56,"./views/search.js":57,"./views/selection.js":58,"./views/settings.js":59,"./views/settings/attachment-display.js":60,"./views/settings/gallery.js":61,"./views/settings/playlist.js":62,"./views/sidebar.js":63,"./views/site-icon-cropper.js":64,"./views/site-icon-preview.js":65,"./views/spinner.js":66,"./views/toolbar.js":67,"./views/toolbar/embed.js":68,"./views/toolbar/select.js":69,"./views/uploader/editor.js":70,"./views/uploader/inline.js":71,"./views/uploader/status-error.js":72,"./views/uploader/status.js":73,"./views/uploader/window.js":74,"./views/view.js":75}],20:[function(require,module,exports){
    2366 var View = wp.media.View,
    2367         AttachmentCompat;
     2455var Controller = wp.media.controller,
     2456        SiteIconCropper;
    23682457
    23692458/**
    2370  * wp.media.view.AttachmentCompat
     2459 * wp.media.controller.SiteIconCropper
    23712460 *
    2372  * A view to display fields added via the `attachment_fields_to_edit` filter.
     2461 * A state for cropping a Site Icon.
    23732462 *
    2374  * @memberOf wp.media.view
     2463 * @memberOf wp.media.controller
    23752464 *
    23762465 * @class
    2377  * @augments wp.media.View
    2378  * @augments wp.Backbone.View
    2379  * @augments Backbone.View
     2466 * @augments wp.media.controller.Cropper
     2467 * @augments wp.media.controller.State
     2468 * @augments Backbone.Model
    23802469 */
    2381 AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{
    2382         tagName:   'form',
    2383         className: 'compat-item',
     2470SiteIconCropper = Controller.Cropper.extend(/** @lends wp.media.controller.SiteIconCropper.prototype */{
     2471        activate: function() {
     2472                this.frame.on( 'content:create:crop', this.createCropContent, this );
     2473                this.frame.on( 'close', this.removeCropper, this );
     2474                this.set('selection', new Backbone.Collection(this.frame._selection.single));
     2475        },
     2476
     2477        createCropContent: function() {
     2478                this.cropperView = new wp.media.view.SiteIconCropper({
     2479                        controller: this,
     2480                        attachment: this.get('selection').first()
     2481                });
     2482                this.cropperView.on('image-loaded', this.createCropToolbar, this);
     2483                this.frame.content.set(this.cropperView);
    23842484
    2385         events: {
    2386                 'submit':          'preventDefault',
    2387                 'change input':    'save',
    2388                 'change select':   'save',
    2389                 'change textarea': 'save'
    23902485        },
    23912486
    2392         initialize: function() {
    2393                 this.listenTo( this.model, 'change:compat', this.render );
     2487        doCrop: function( attachment ) {
     2488                var cropDetails = attachment.get( 'cropDetails' ),
     2489                        control = this.get( 'control' );
     2490
     2491                cropDetails.dst_width  = control.params.width;
     2492                cropDetails.dst_height = control.params.height;
     2493
     2494                return wp.ajax.post( 'crop-image', {
     2495                        nonce: attachment.get( 'nonces' ).edit,
     2496                        id: attachment.get( 'id' ),
     2497                        context: 'site-icon',
     2498                        cropDetails: cropDetails
     2499                } );
     2500        }
     2501});
     2502
     2503module.exports = SiteIconCropper;
     2504
     2505
     2506/***/ }),
     2507/* 45 */
     2508/***/ (function(module, exports) {
     2509
     2510/**
     2511 * wp.media.View
     2512 *
     2513 * The base view class for media.
     2514 *
     2515 * Undelegating events, removing events from the model, and
     2516 * removing events from the controller mirror the code for
     2517 * `Backbone.View.dispose` in Backbone 0.9.8 development.
     2518 *
     2519 * This behavior has since been removed, and should not be used
     2520 * outside of the media manager.
     2521 *
     2522 * @memberOf wp.media
     2523 *
     2524 * @class
     2525 * @augments wp.Backbone.View
     2526 * @augments Backbone.View
     2527 */
     2528var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{
     2529        constructor: function( options ) {
     2530                if ( options && options.controller ) {
     2531                        this.controller = options.controller;
     2532                }
     2533                wp.Backbone.View.apply( this, arguments );
    23942534        },
    23952535        /**
    2396          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     2536         * @todo The internal comment mentions this might have been a stop-gap
     2537         *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
     2538         *       care of this in Backbone.View now.
     2539         *
     2540         * @returns {wp.media.View} Returns itself to allow chaining
    23972541         */
    23982542        dispose: function() {
    2399                 if ( this.$(':focus').length ) {
    2400                         this.save();
     2543                // Undelegating events, removing events from the model, and
     2544                // removing events from the controller mirror the code for
     2545                // `Backbone.View.dispose` in Backbone 0.9.8 development.
     2546                this.undelegateEvents();
     2547
     2548                if ( this.model && this.model.off ) {
     2549                        this.model.off( null, null, this );
    24012550                }
    2402                 /**
    2403                  * call 'dispose' directly on the parent class
    2404                  */
    2405                 return View.prototype.dispose.apply( this, arguments );
    2406         },
    2407         /**
    2408          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    2409          */
    2410         render: function() {
    2411                 var compat = this.model.get('compat');
    2412                 if ( ! compat || ! compat.item ) {
    2413                         return;
     2551
     2552                if ( this.collection && this.collection.off ) {
     2553                        this.collection.off( null, null, this );
     2554                }
     2555
     2556                // Unbind controller events.
     2557                if ( this.controller && this.controller.off ) {
     2558                        this.controller.off( null, null, this );
    24142559                }
    24152560
    2416                 this.views.detach();
    2417                 this.$el.html( compat.item );
    2418                 this.views.render();
    24192561                return this;
    24202562        },
    24212563        /**
    2422          * @param {Object} event
    2423          */
    2424         preventDefault: function( event ) {
    2425                 event.preventDefault();
    2426         },
    2427         /**
    2428          * @param {Object} event
     2564         * @returns {wp.media.View} Returns itself to allow chaining
    24292565         */
    2430         save: function( event ) {
    2431                 var data = {};
    2432 
    2433                 if ( event ) {
    2434                         event.preventDefault();
    2435                 }
    2436 
    2437                 _.each( this.$el.serializeArray(), function( pair ) {
    2438                         data[ pair.name ] = pair.value;
    2439                 });
    2440 
    2441                 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
    2442                 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
    2443         },
    2444 
    2445         postSave: function() {
    2446                 this.controller.trigger( 'attachment:compat:ready', ['ready'] );
     2566        remove: function() {
     2567                this.dispose();
     2568                /**
     2569                 * call 'remove' directly on the parent class
     2570                 */
     2571                return wp.Backbone.View.prototype.remove.apply( this, arguments );
    24472572        }
    24482573});
    24492574
    2450 module.exports = AttachmentCompat;
     2575module.exports = View;
    24512576
    2452 },{}],21:[function(require,module,exports){
    2453 var $ = jQuery,
    2454         AttachmentFilters;
     2577
     2578/***/ }),
     2579/* 46 */
     2580/***/ (function(module, exports) {
    24552581
    24562582/**
    2457  * wp.media.view.AttachmentFilters
     2583 * wp.media.view.Frame
     2584 *
     2585 * A frame is a composite view consisting of one or more regions and one or more
     2586 * states.
    24582587 *
    24592588 * @memberOf wp.media.view
    24602589 *
     2590 * @see wp.media.controller.State
     2591 * @see wp.media.controller.Region
     2592 *
    24612593 * @class
    24622594 * @augments wp.media.View
    24632595 * @augments wp.Backbone.View
    24642596 * @augments Backbone.View
     2597 * @mixes wp.media.controller.StateMachine
    24652598 */
    2466 AttachmentFilters = wp.media.View.extend(/** @lends wp.media.view.AttachmentFilters.prototype */{
    2467         tagName:   'select',
    2468         className: 'attachment-filters',
    2469         id:        'media-attachment-filters',
    2470 
    2471         events: {
    2472                 change: 'change'
     2599var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{
     2600        initialize: function() {
     2601                _.defaults( this.options, {
     2602                        mode: [ 'select' ]
     2603                });
     2604                this._createRegions();
     2605                this._createStates();
     2606                this._createModes();
    24732607        },
    24742608
    2475         keys: [],
     2609        _createRegions: function() {
     2610                // Clone the regions array.
     2611                this.regions = this.regions ? this.regions.slice() : [];
    24762612
    2477         initialize: function() {
    2478                 this.createFilters();
    2479                 _.extend( this.filters, this.options.filters );
     2613                // Initialize regions.
     2614                _.each( this.regions, function( region ) {
     2615                        this[ region ] = new wp.media.controller.Region({
     2616                                view:     this,
     2617                                id:       region,
     2618                                selector: '.media-frame-' + region
     2619                        });
     2620                }, this );
     2621        },
     2622        /**
     2623         * Create the frame's states.
     2624         *
     2625         * @see wp.media.controller.State
     2626         * @see wp.media.controller.StateMachine
     2627         *
     2628         * @fires wp.media.controller.State#ready
     2629         */
     2630        _createStates: function() {
     2631                // Create the default `states` collection.
     2632                this.states = new Backbone.Collection( null, {
     2633                        model: wp.media.controller.State
     2634                });
    24802635
    2481                 // Build `<option>` elements.
    2482                 this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
    2483                         return {
    2484                                 el: $( '<option></option>' ).val( value ).html( filter.text )[0],
    2485                                 priority: filter.priority || 50
    2486                         };
    2487                 }, this ).sortBy('priority').pluck('el').value() );
     2636                // Ensure states have a reference to the frame.
     2637                this.states.on( 'add', function( model ) {
     2638                        model.frame = this;
     2639                        model.trigger('ready');
     2640                }, this );
    24882641
    2489                 this.listenTo( this.model, 'change', this.select );
    2490                 this.select();
     2642                if ( this.options.states ) {
     2643                        this.states.add( this.options.states );
     2644                }
    24912645        },
    24922646
    24932647        /**
    2494          * @abstract
     2648         * A frame can be in a mode or multiple modes at one time.
     2649         *
     2650         * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
    24952651         */
    2496         createFilters: function() {
    2497                 this.filters = {};
    2498         },
     2652        _createModes: function() {
     2653                // Store active "modes" that the frame is in. Unrelated to region modes.
     2654                this.activeModes = new Backbone.Collection();
     2655                this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
    24992656
     2657                _.each( this.options.mode, function( mode ) {
     2658                        this.activateMode( mode );
     2659                }, this );
     2660        },
    25002661        /**
    2501          * When the selected filter changes, update the Attachment Query properties to match.
     2662         * Reset all states on the frame to their defaults.
     2663         *
     2664         * @returns {wp.media.view.Frame} Returns itself to allow chaining
    25022665         */
    2503         change: function() {
    2504                 var filter = this.filters[ this.el.value ];
    2505                 if ( filter ) {
    2506                         this.model.set( filter.props );
    2507                 }
     2666        reset: function() {
     2667                this.states.invoke( 'trigger', 'reset' );
     2668                return this;
    25082669        },
    2509 
    2510         select: function() {
    2511                 var model = this.model,
    2512                         value = 'all',
    2513                         props = model.toJSON();
    2514 
    2515                 _.find( this.filters, function( filter, id ) {
    2516                         var equal = _.all( filter.props, function( prop, key ) {
    2517                                 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
    2518                         });
    2519 
    2520                         if ( equal ) {
    2521                                 return value = id;
     2670        /**
     2671         * Map activeMode collection events to the frame.
     2672         */
     2673        triggerModeEvents: function( model, collection, options ) {
     2674                var collectionEvent,
     2675                        modeEventMap = {
     2676                                add: 'activate',
     2677                                remove: 'deactivate'
     2678                        },
     2679                        eventToTrigger;
     2680                // Probably a better way to do this.
     2681                _.each( options, function( value, key ) {
     2682                        if ( value ) {
     2683                                collectionEvent = key;
    25222684                        }
    2523                 });
    2524 
    2525                 this.$el.val( value );
    2526         }
    2527 });
     2685                } );
    25282686
    2529 module.exports = AttachmentFilters;
     2687                if ( ! _.has( modeEventMap, collectionEvent ) ) {
     2688                        return;
     2689                }
    25302690
    2531 },{}],22:[function(require,module,exports){
    2532 var l10n = wp.media.view.l10n,
    2533         All;
    2534 
    2535 /**
    2536  * wp.media.view.AttachmentFilters.All
    2537  *
    2538  * @memberOf wp.media.view.AttachmentFilters
    2539  *
    2540  * @class
    2541  * @augments wp.media.view.AttachmentFilters
    2542  * @augments wp.media.View
    2543  * @augments wp.Backbone.View
    2544  * @augments Backbone.View
    2545  */
    2546 All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{
    2547         createFilters: function() {
    2548                 var filters = {};
    2549 
    2550                 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
    2551                         filters[ key ] = {
    2552                                 text: text,
    2553                                 props: {
    2554                                         status:  null,
    2555                                         type:    key,
    2556                                         uploadedTo: null,
    2557                                         orderby: 'date',
    2558                                         order:   'DESC'
    2559                                 }
    2560                         };
    2561                 });
    2562 
    2563                 filters.all = {
    2564                         text:  l10n.allMediaItems,
    2565                         props: {
    2566                                 status:  null,
    2567                                 type:    null,
    2568                                 uploadedTo: null,
    2569                                 orderby: 'date',
    2570                                 order:   'DESC'
    2571                         },
    2572                         priority: 10
    2573                 };
    2574 
    2575                 if ( wp.media.view.settings.post.id ) {
    2576                         filters.uploaded = {
    2577                                 text:  l10n.uploadedToThisPost,
    2578                                 props: {
    2579                                         status:  null,
    2580                                         type:    null,
    2581                                         uploadedTo: wp.media.view.settings.post.id,
    2582                                         orderby: 'menuOrder',
    2583                                         order:   'ASC'
    2584                                 },
    2585                                 priority: 20
    2586                         };
     2691                eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
     2692                this.trigger( eventToTrigger );
     2693        },
     2694        /**
     2695         * Activate a mode on the frame.
     2696         *
     2697         * @param string mode Mode ID.
     2698         * @returns {this} Returns itself to allow chaining.
     2699         */
     2700        activateMode: function( mode ) {
     2701                // Bail if the mode is already active.
     2702                if ( this.isModeActive( mode ) ) {
     2703                        return;
    25872704                }
     2705                this.activeModes.add( [ { id: mode } ] );
     2706                // Add a CSS class to the frame so elements can be styled for the mode.
     2707                this.$el.addClass( 'mode-' + mode );
    25882708
    2589                 filters.unattached = {
    2590                         text:  l10n.unattached,
    2591                         props: {
    2592                                 status:     null,
    2593                                 uploadedTo: 0,
    2594                                 type:       null,
    2595                                 orderby:    'menuOrder',
    2596                                 order:      'ASC'
    2597                         },
    2598                         priority: 50
    2599                 };
    2600 
    2601                 if ( wp.media.view.settings.mediaTrash &&
    2602                         this.controller.isModeActive( 'grid' ) ) {
    2603 
    2604                         filters.trash = {
    2605                                 text:  l10n.trash,
    2606                                 props: {
    2607                                         uploadedTo: null,
    2608                                         status:     'trash',
    2609                                         type:       null,
    2610                                         orderby:    'date',
    2611                                         order:      'DESC'
    2612                                 },
    2613                                 priority: 50
    2614                         };
     2709                return this;
     2710        },
     2711        /**
     2712         * Deactivate a mode on the frame.
     2713         *
     2714         * @param string mode Mode ID.
     2715         * @returns {this} Returns itself to allow chaining.
     2716         */
     2717        deactivateMode: function( mode ) {
     2718                // Bail if the mode isn't active.
     2719                if ( ! this.isModeActive( mode ) ) {
     2720                        return this;
    26152721                }
     2722                this.activeModes.remove( this.activeModes.where( { id: mode } ) );
     2723                this.$el.removeClass( 'mode-' + mode );
     2724                /**
     2725                 * Frame mode deactivation event.
     2726                 *
     2727                 * @event wp.media.view.Frame#{mode}:deactivate
     2728                 */
     2729                this.trigger( mode + ':deactivate' );
    26162730
    2617                 this.filters = filters;
     2731                return this;
     2732        },
     2733        /**
     2734         * Check if a mode is enabled on the frame.
     2735         *
     2736         * @param  string mode Mode ID.
     2737         * @return bool
     2738         */
     2739        isModeActive: function( mode ) {
     2740                return Boolean( this.activeModes.where( { id: mode } ).length );
    26182741        }
    26192742});
    26202743
    2621 module.exports = All;
    2622 
    2623 },{}],23:[function(require,module,exports){
    2624 var l10n = wp.media.view.l10n,
    2625         DateFilter;
     2744// Make the `Frame` a `StateMachine`.
     2745_.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
    26262746
    2627 /**
    2628  * A filter dropdown for month/dates.
    2629  *
    2630  * @memberOf wp.media.view.AttachmentFilters
    2631  *
    2632  * @class
    2633  * @augments wp.media.view.AttachmentFilters
    2634  * @augments wp.media.View
    2635  * @augments wp.Backbone.View
    2636  * @augments Backbone.View
    2637  */
    2638 DateFilter = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Date.prototype */{
    2639         id: 'media-attachment-date-filters',
     2747module.exports = Frame;
    26402748
    2641         createFilters: function() {
    2642                 var filters = {};
    2643                 _.each( wp.media.view.settings.months || {}, function( value, index ) {
    2644                         filters[ index ] = {
    2645                                 text: value.text,
    2646                                 props: {
    2647                                         year: value.year,
    2648                                         monthnum: value.month
    2649                                 }
    2650                         };
    2651                 });
    2652                 filters.all = {
    2653                         text:  l10n.allDates,
    2654                         props: {
    2655                                 monthnum: false,
    2656                                 year:  false
    2657                         },
    2658                         priority: 10
    2659                 };
    2660                 this.filters = filters;
    2661         }
    2662 });
    26632749
    2664 module.exports = DateFilter;
     2750/***/ }),
     2751/* 47 */
     2752/***/ (function(module, exports) {
    26652753
    2666 },{}],24:[function(require,module,exports){
    2667 var l10n = wp.media.view.l10n,
    2668         Uploaded;
     2754var Frame = wp.media.view.Frame,
     2755        $ = jQuery,
     2756        MediaFrame;
    26692757
    26702758/**
    2671  * wp.media.view.AttachmentFilters.Uploaded
     2759 * wp.media.view.MediaFrame
    26722760 *
    2673  * @memberOf wp.media.view.AttachmentFilters
     2761 * The frame used to create the media modal.
     2762 *
     2763 * @memberOf wp.media.view
    26742764 *
    26752765 * @class
    2676  * @augments wp.media.view.AttachmentFilters
     2766 * @augments wp.media.view.Frame
    26772767 * @augments wp.media.View
    26782768 * @augments wp.Backbone.View
    26792769 * @augments Backbone.View
     2770 * @mixes wp.media.controller.StateMachine
    26802771 */
    2681 Uploaded = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Uploaded.prototype */{
    2682         createFilters: function() {
    2683                 var type = this.model.get('type'),
    2684                         types = wp.media.view.settings.mimeTypes,
    2685                         text;
     2772MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{
     2773        className: 'media-frame',
     2774        template:  wp.template('media-frame'),
     2775        regions:   ['menu','title','content','toolbar','router'],
    26862776
    2687                 if ( types && type ) {
    2688                         text = types[ type ];
    2689                 }
     2777        events: {
     2778                'click div.media-frame-title h1': 'toggleMenu'
     2779        },
    26902780
    2691                 this.filters = {
    2692                         all: {
    2693                                 text:  text || l10n.allMediaItems,
    2694                                 props: {
    2695                                         uploadedTo: null,
    2696                                         orderby: 'date',
    2697                                         order:   'DESC'
    2698                                 },
    2699                                 priority: 10
    2700                         },
     2781        /**
     2782         * @constructs
     2783         */
     2784        initialize: function() {
     2785                Frame.prototype.initialize.apply( this, arguments );
    27012786
    2702                         uploaded: {
    2703                                 text:  l10n.uploadedToThisPost,
    2704                                 props: {
    2705                                         uploadedTo: wp.media.view.settings.post.id,
    2706                                         orderby: 'menuOrder',
    2707                                         order:   'ASC'
    2708                                 },
    2709                                 priority: 20
    2710                         },
     2787                _.defaults( this.options, {
     2788                        title:    '',
     2789                        modal:    true,
     2790                        uploader: true
     2791                });
    27112792
    2712                         unattached: {
    2713                                 text:  l10n.unattached,
    2714                                 props: {
    2715                                         uploadedTo: 0,
    2716                                         orderby: 'menuOrder',
    2717                                         order:   'ASC'
    2718                                 },
    2719                                 priority: 50
    2720                         }
    2721                 };
    2722         }
    2723 });
     2793                // Ensure core UI is enabled.
     2794                this.$el.addClass('wp-core-ui');
    27242795
    2725 module.exports = Uploaded;
     2796                // Initialize modal container view.
     2797                if ( this.options.modal ) {
     2798                        this.modal = new wp.media.view.Modal({
     2799                                controller: this,
     2800                                title:      this.options.title
     2801                        });
    27262802
    2727 },{}],25:[function(require,module,exports){
    2728 var View = wp.media.View,
    2729         $ = jQuery,
    2730         Attachment;
     2803                        this.modal.content( this );
     2804                }
    27312805
    2732 /**
    2733  * wp.media.view.Attachment
    2734  *
    2735  * @memberOf wp.media.view
    2736  *
    2737  * @class
    2738  * @augments wp.media.View
    2739  * @augments wp.Backbone.View
    2740  * @augments Backbone.View
    2741  */
    2742 Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{
    2743         tagName:   'li',
    2744         className: 'attachment',
    2745         template:  wp.template('attachment'),
    2746 
    2747         attributes: function() {
    2748                 return {
    2749                         'tabIndex':     0,
    2750                         'role':         'checkbox',
    2751                         'aria-label':   this.model.get( 'title' ),
    2752                         'aria-checked': false,
    2753                         'data-id':      this.model.get( 'id' )
    2754                 };
    2755         },
    2756 
    2757         events: {
    2758                 'click':                          'toggleSelectionHandler',
    2759                 'change [data-setting]':          'updateSetting',
    2760                 'change [data-setting] input':    'updateSetting',
    2761                 'change [data-setting] select':   'updateSetting',
    2762                 'change [data-setting] textarea': 'updateSetting',
    2763                 'click .attachment-close':        'removeFromLibrary',
    2764                 'click .check':                   'checkClickHandler',
    2765                 'keydown':                        'toggleSelectionHandler'
    2766         },
     2806                // Force the uploader off if the upload limit has been exceeded or
     2807                // if the browser isn't supported.
     2808                if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     2809                        this.options.uploader = false;
     2810                }
    27672811
    2768         buttons: {},
     2812                // Initialize window-wide uploader.
     2813                if ( this.options.uploader ) {
     2814                        this.uploader = new wp.media.view.UploaderWindow({
     2815                                controller: this,
     2816                                uploader: {
     2817                                        dropzone:  this.modal ? this.modal.$el : this.$el,
     2818                                        container: this.$el
     2819                                }
     2820                        });
     2821                        this.views.set( '.media-frame-uploader', this.uploader );
     2822                }
    27692823
    2770         initialize: function() {
    2771                 var selection = this.options.selection,
    2772                         options = _.defaults( this.options, {
    2773                                 rerenderOnModelChange: true
    2774                         } );
     2824                this.on( 'attach', _.bind( this.views.ready, this.views ), this );
    27752825
    2776                 if ( options.rerenderOnModelChange ) {
    2777                         this.listenTo( this.model, 'change', this.render );
    2778                 } else {
    2779                         this.listenTo( this.model, 'change:percent', this.progress );
    2780                 }
    2781                 this.listenTo( this.model, 'change:title', this._syncTitle );
    2782                 this.listenTo( this.model, 'change:caption', this._syncCaption );
    2783                 this.listenTo( this.model, 'change:artist', this._syncArtist );
    2784                 this.listenTo( this.model, 'change:album', this._syncAlbum );
     2826                // Bind default title creation.
     2827                this.on( 'title:create:default', this.createTitle, this );
     2828                this.title.mode('default');
    27852829
    2786                 // Update the selection.
    2787                 this.listenTo( this.model, 'add', this.select );
    2788                 this.listenTo( this.model, 'remove', this.deselect );
    2789                 if ( selection ) {
    2790                         selection.on( 'reset', this.updateSelect, this );
    2791                         // Update the model's details view.
    2792                         this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
    2793                         this.details( this.model, this.controller.state().get('selection') );
    2794                 }
     2830                this.on( 'title:render', function( view ) {
     2831                        view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
     2832                });
    27952833
    2796                 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
     2834                // Bind default menu.
     2835                this.on( 'menu:create:default', this.createMenu, this );
    27972836        },
    27982837        /**
    2799          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     2838         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    28002839         */
    2801         dispose: function() {
    2802                 var selection = this.options.selection;
    2803 
    2804                 // Make sure all settings are saved before removing the view.
    2805                 this.updateAll();
    2806 
    2807                 if ( selection ) {
    2808                         selection.off( null, null, this );
     2840        render: function() {
     2841                // Activate the default state if no active state exists.
     2842                if ( ! this.state() && this.options.state ) {
     2843                        this.setState( this.options.state );
    28092844                }
    28102845                /**
    2811                  * call 'dispose' directly on the parent class
     2846                 * call 'render' directly on the parent class
    28122847                 */
    2813                 View.prototype.dispose.apply( this, arguments );
    2814                 return this;
     2848                return Frame.prototype.render.apply( this, arguments );
    28152849        },
    28162850        /**
    2817          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     2851         * @param {Object} title
     2852         * @this wp.media.controller.Region
    28182853         */
    2819         render: function() {
    2820                 var options = _.defaults( this.model.toJSON(), {
    2821                                 orientation:   'landscape',
    2822                                 uploading:     false,
    2823                                 type:          '',
    2824                                 subtype:       '',
    2825                                 icon:          '',
    2826                                 filename:      '',
    2827                                 caption:       '',
    2828                                 title:         '',
    2829                                 dateFormatted: '',
    2830                                 width:         '',
    2831                                 height:        '',
    2832                                 compat:        false,
    2833                                 alt:           '',
    2834                                 description:   ''
    2835                         }, this.options );
    2836 
    2837                 options.buttons  = this.buttons;
    2838                 options.describe = this.controller.state().get('describe');
     2854        createTitle: function( title ) {
     2855                title.view = new wp.media.View({
     2856                        controller: this,
     2857                        tagName: 'h1'
     2858                });
     2859        },
     2860        /**
     2861         * @param {Object} menu
     2862         * @this wp.media.controller.Region
     2863         */
     2864        createMenu: function( menu ) {
     2865                menu.view = new wp.media.view.Menu({
     2866                        controller: this
     2867                });
     2868        },
    28392869
    2840                 if ( 'image' === options.type ) {
    2841                         options.size = this.imageSize();
    2842                 }
     2870        toggleMenu: function() {
     2871                this.$el.find( '.media-menu' ).toggleClass( 'visible' );
     2872        },
    28432873
    2844                 options.can = {};
    2845                 if ( options.nonces ) {
    2846                         options.can.remove = !! options.nonces['delete'];
    2847                         options.can.save = !! options.nonces.update;
    2848                 }
     2874        /**
     2875         * @param {Object} toolbar
     2876         * @this wp.media.controller.Region
     2877         */
     2878        createToolbar: function( toolbar ) {
     2879                toolbar.view = new wp.media.view.Toolbar({
     2880                        controller: this
     2881                });
     2882        },
     2883        /**
     2884         * @param {Object} router
     2885         * @this wp.media.controller.Region
     2886         */
     2887        createRouter: function( router ) {
     2888                router.view = new wp.media.view.Router({
     2889                        controller: this
     2890                });
     2891        },
     2892        /**
     2893         * @param {Object} options
     2894         */
     2895        createIframeStates: function( options ) {
     2896                var settings = wp.media.view.settings,
     2897                        tabs = settings.tabs,
     2898                        tabUrl = settings.tabUrl,
     2899                        $postId;
    28492900
    2850                 if ( this.controller.state().get('allowLocalEdits') ) {
    2851                         options.allowLocalEdits = true;
     2901                if ( ! tabs || ! tabUrl ) {
     2902                        return;
    28522903                }
    28532904
    2854                 if ( options.uploading && ! options.percent ) {
    2855                         options.percent = 0;
     2905                // Add the post ID to the tab URL if it exists.
     2906                $postId = $('#post_ID');
     2907                if ( $postId.length ) {
     2908                        tabUrl += '&post_id=' + $postId.val();
    28562909                }
    28572910
    2858                 this.views.detach();
    2859                 this.$el.html( this.template( options ) );
     2911                // Generate the tab states.
     2912                _.each( tabs, function( title, id ) {
     2913                        this.state( 'iframe:' + id ).set( _.defaults({
     2914                                tab:     id,
     2915                                src:     tabUrl + '&tab=' + id,
     2916                                title:   title,
     2917                                content: 'iframe',
     2918                                menu:    'default'
     2919                        }, options ) );
     2920                }, this );
    28602921
    2861                 this.$el.toggleClass( 'uploading', options.uploading );
     2922                this.on( 'content:create:iframe', this.iframeContent, this );
     2923                this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
     2924                this.on( 'menu:render:default', this.iframeMenu, this );
     2925                this.on( 'open', this.hijackThickbox, this );
     2926                this.on( 'close', this.restoreThickbox, this );
     2927        },
    28622928
    2863                 if ( options.uploading ) {
    2864                         this.$bar = this.$('.media-progress-bar div');
    2865                 } else {
    2866                         delete this.$bar;
    2867                 }
     2929        /**
     2930         * @param {Object} content
     2931         * @this wp.media.controller.Region
     2932         */
     2933        iframeContent: function( content ) {
     2934                this.$el.addClass('hide-toolbar');
     2935                content.view = new wp.media.view.Iframe({
     2936                        controller: this
     2937                });
     2938        },
    28682939
    2869                 // Check if the model is selected.
    2870                 this.updateSelect();
     2940        iframeContentCleanup: function() {
     2941                this.$el.removeClass('hide-toolbar');
     2942        },
    28712943
    2872                 // Update the save status.
    2873                 this.updateSave();
     2944        iframeMenu: function( view ) {
     2945                var views = {};
    28742946
    2875                 this.views.render();
     2947                if ( ! view ) {
     2948                        return;
     2949                }
    28762950
    2877                 return this;
    2878         },
     2951                _.each( wp.media.view.settings.tabs, function( title, id ) {
     2952                        views[ 'iframe:' + id ] = {
     2953                                text: this.state( 'iframe:' + id ).get('title'),
     2954                                priority: 200
     2955                        };
     2956                }, this );
    28792957
    2880         progress: function() {
    2881                 if ( this.$bar && this.$bar.length ) {
    2882                         this.$bar.width( this.model.get('percent') + '%' );
    2883                 }
     2958                view.set( views );
    28842959        },
    28852960
    2886         /**
    2887          * @param {Object} event
    2888          */
    2889         toggleSelectionHandler: function( event ) {
    2890                 var method;
     2961        hijackThickbox: function() {
     2962                var frame = this;
    28912963
    2892                 // Don't do anything inside inputs and on the attachment check and remove buttons.
    2893                 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
     2964                if ( ! window.tb_remove || this._tb_remove ) {
    28942965                        return;
    28952966                }
    28962967
    2897                 // Catch arrow events
    2898                 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    2899                         this.controller.trigger( 'attachment:keydown:arrow', event );
    2900                         return;
    2901                 }
     2968                this._tb_remove = window.tb_remove;
     2969                window.tb_remove = function() {
     2970                        frame.close();
     2971                        frame.reset();
     2972                        frame.setState( frame.options.state );
     2973                        frame._tb_remove.call( window );
     2974                };
     2975        },
    29022976
    2903                 // Catch enter and space events
    2904                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     2977        restoreThickbox: function() {
     2978                if ( ! this._tb_remove ) {
    29052979                        return;
    29062980                }
    29072981
    2908                 event.preventDefault();
    2909 
    2910                 // In the grid view, bubble up an edit:attachment event to the controller.
    2911                 if ( this.controller.isModeActive( 'grid' ) ) {
    2912                         if ( this.controller.isModeActive( 'edit' ) ) {
    2913                                 // Pass the current target to restore focus when closing
    2914                                 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
    2915                                 return;
    2916                         }
    2917 
    2918                         if ( this.controller.isModeActive( 'select' ) ) {
    2919                                 method = 'toggle';
    2920                         }
    2921                 }
    2922 
    2923                 if ( event.shiftKey ) {
    2924                         method = 'between';
    2925                 } else if ( event.ctrlKey || event.metaKey ) {
    2926                         method = 'toggle';
    2927                 }
    2928 
    2929                 this.toggleSelection({
    2930                         method: method
    2931                 });
     2982                window.tb_remove = this._tb_remove;
     2983                delete this._tb_remove;
     2984        }
     2985});
    29322986
    2933                 this.controller.trigger( 'selection:toggle' );
    2934         },
     2987// Map some of the modal's methods to the frame.
     2988_.each(['open','close','attach','detach','escape'], function( method ) {
    29352989        /**
    2936          * @param {Object} options
     2990         * @function open
     2991         * @memberOf wp.media.view.MediaFrame
     2992         * @instance
     2993         *
     2994         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    29372995         */
    2938         toggleSelection: function( options ) {
    2939                 var collection = this.collection,
    2940                         selection = this.options.selection,
    2941                         model = this.model,
    2942                         method = options && options.method,
    2943                         single, models, singleIndex, modelIndex;
    2944 
    2945                 if ( ! selection ) {
    2946                         return;
     2996        /**
     2997         * @function close
     2998         * @memberOf wp.media.view.MediaFrame
     2999         * @instance
     3000         *
     3001         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     3002         */
     3003        /**
     3004         * @function attach
     3005         * @memberOf wp.media.view.MediaFrame
     3006         * @instance
     3007         *
     3008         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     3009         */
     3010        /**
     3011         * @function detach
     3012         * @memberOf wp.media.view.MediaFrame
     3013         * @instance
     3014         *
     3015         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     3016         */
     3017        /**
     3018         * @function escape
     3019         * @memberOf wp.media.view.MediaFrame
     3020         * @instance
     3021         *
     3022         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     3023         */
     3024        MediaFrame.prototype[ method ] = function() {
     3025                if ( this.modal ) {
     3026                        this.modal[ method ].apply( this.modal, arguments );
    29473027                }
     3028                return this;
     3029        };
     3030});
    29483031
    2949                 single = selection.single();
    2950                 method = _.isUndefined( method ) ? selection.multiple : method;
    2951 
    2952                 // If the `method` is set to `between`, select all models that
    2953                 // exist between the current and the selected model.
    2954                 if ( 'between' === method && single && selection.multiple ) {
    2955                         // If the models are the same, short-circuit.
    2956                         if ( single === model ) {
    2957                                 return;
    2958                         }
    2959 
    2960                         singleIndex = collection.indexOf( single );
    2961                         modelIndex  = collection.indexOf( this.model );
     3032module.exports = MediaFrame;
    29623033
    2963                         if ( singleIndex < modelIndex ) {
    2964                                 models = collection.models.slice( singleIndex, modelIndex + 1 );
    2965                         } else {
    2966                                 models = collection.models.slice( modelIndex, singleIndex + 1 );
    2967                         }
    29683034
    2969                         selection.add( models );
    2970                         selection.single( model );
    2971                         return;
     3035/***/ }),
     3036/* 48 */
     3037/***/ (function(module, exports) {
    29723038
    2973                 // If the `method` is set to `toggle`, just flip the selection
    2974                 // status, regardless of whether the model is the single model.
    2975                 } else if ( 'toggle' === method ) {
    2976                         selection[ this.selected() ? 'remove' : 'add' ]( model );
    2977                         selection.single( model );
    2978                         return;
    2979                 } else if ( 'add' === method ) {
    2980                         selection.add( model );
    2981                         selection.single( model );
    2982                         return;
    2983                 }
     3039var MediaFrame = wp.media.view.MediaFrame,
     3040        l10n = wp.media.view.l10n,
     3041        Select;
    29843042
    2985                 // Fixes bug that loses focus when selecting a featured image
    2986                 if ( ! method ) {
    2987                         method = 'add';
    2988                 }
     3043/**
     3044 * wp.media.view.MediaFrame.Select
     3045 *
     3046 * A frame for selecting an item or items from the media library.
     3047 *
     3048 * @memberOf wp.media.view.MediaFrame
     3049 *
     3050 * @class
     3051 * @augments wp.media.view.MediaFrame
     3052 * @augments wp.media.view.Frame
     3053 * @augments wp.media.View
     3054 * @augments wp.Backbone.View
     3055 * @augments Backbone.View
     3056 * @mixes wp.media.controller.StateMachine
     3057 */
     3058Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{
     3059        initialize: function() {
     3060                // Call 'initialize' directly on the parent class.
     3061                MediaFrame.prototype.initialize.apply( this, arguments );
    29893062
    2990                 if ( method !== 'add' ) {
    2991                         method = 'reset';
    2992                 }
     3063                _.defaults( this.options, {
     3064                        selection: [],
     3065                        library:   {},
     3066                        multiple:  false,
     3067                        state:    'library'
     3068                });
    29933069
    2994                 if ( this.selected() ) {
    2995                         // If the model is the single model, remove it.
    2996                         // If it is not the same as the single model,
    2997                         // it now becomes the single model.
    2998                         selection[ single === model ? 'remove' : 'single' ]( model );
    2999                 } else {
    3000                         // If the model is not selected, run the `method` on the
    3001                         // selection. By default, we `reset` the selection, but the
    3002                         // `method` can be set to `add` the model to the selection.
    3003                         selection[ method ]( model );
    3004                         selection.single( model );
    3005                 }
     3070                this.createSelection();
     3071                this.createStates();
     3072                this.bindHandlers();
    30063073        },
    30073074
    3008         updateSelect: function() {
    3009                 this[ this.selected() ? 'select' : 'deselect' ]();
    3010         },
    30113075        /**
    3012          * @returns {unresolved|Boolean}
     3076         * Attach a selection collection to the frame.
     3077         *
     3078         * A selection is a collection of attachments used for a specific purpose
     3079         * by a media frame. e.g. Selecting an attachment (or many) to insert into
     3080         * post content.
     3081         *
     3082         * @see media.model.Selection
    30133083         */
    3014         selected: function() {
     3084        createSelection: function() {
    30153085                var selection = this.options.selection;
    3016                 if ( selection ) {
    3017                         return !! selection.get( this.model.cid );
     3086
     3087                if ( ! (selection instanceof wp.media.model.Selection) ) {
     3088                        this.options.selection = new wp.media.model.Selection( selection, {
     3089                                multiple: this.options.multiple
     3090                        });
    30183091                }
     3092
     3093                this._selection = {
     3094                        attachments: new wp.media.model.Attachments(),
     3095                        difference: []
     3096                };
    30193097        },
     3098
    30203099        /**
    3021          * @param {Backbone.Model} model
    3022          * @param {Backbone.Collection} collection
     3100         * Create the default states on the frame.
    30233101         */
    3024         select: function( model, collection ) {
    3025                 var selection = this.options.selection,
    3026                         controller = this.controller;
    3027 
    3028                 // Check if a selection exists and if it's the collection provided.
    3029                 // If they're not the same collection, bail; we're in another
    3030                 // selection's event loop.
    3031                 if ( ! selection || ( collection && collection !== selection ) ) {
    3032                         return;
    3033                 }
     3102        createStates: function() {
     3103                var options = this.options;
    30343104
    3035                 // Bail if the model is already selected.
    3036                 if ( this.$el.hasClass( 'selected' ) ) {
     3105                if ( this.options.states ) {
    30373106                        return;
    30383107                }
    30393108
    3040                 // Add 'selected' class to model, set aria-checked to true.
    3041                 this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
    3042                 //  Make the checkbox tabable, except in media grid (bulk select mode).
    3043                 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
    3044                         this.$( '.check' ).attr( 'tabindex', '0' );
    3045                 }
     3109                // Add the default states.
     3110                this.states.add([
     3111                        // Main states.
     3112                        new wp.media.controller.Library({
     3113                                library:   wp.media.query( options.library ),
     3114                                multiple:  options.multiple,
     3115                                title:     options.title,
     3116                                priority:  20
     3117                        })
     3118                ]);
    30463119        },
     3120
    30473121        /**
    3048          * @param {Backbone.Model} model
    3049          * @param {Backbone.Collection} collection
     3122         * Bind region mode event callbacks.
     3123         *
     3124         * @see media.controller.Region.render
    30503125         */
    3051         deselect: function( model, collection ) {
    3052                 var selection = this.options.selection;
     3126        bindHandlers: function() {
     3127                this.on( 'router:create:browse', this.createRouter, this );
     3128                this.on( 'router:render:browse', this.browseRouter, this );
     3129                this.on( 'content:create:browse', this.browseContent, this );
     3130                this.on( 'content:render:upload', this.uploadContent, this );
     3131                this.on( 'toolbar:create:select', this.createSelectToolbar, this );
     3132        },
    30533133
    3054                 // Check if a selection exists and if it's the collection provided.
    3055                 // If they're not the same collection, bail; we're in another
    3056                 // selection's event loop.
    3057                 if ( ! selection || ( collection && collection !== selection ) ) {
    3058                         return;
    3059                 }
    3060                 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
    3061                         .find( '.check' ).attr( 'tabindex', '-1' );
    3062         },
    30633134        /**
    3064          * @param {Backbone.Model} model
    3065          * @param {Backbone.Collection} collection
     3135         * Render callback for the router region in the `browse` mode.
     3136         *
     3137         * @param {wp.media.view.Router} routerView
    30663138         */
    3067         details: function( model, collection ) {
    3068                 var selection = this.options.selection,
    3069                         details;
    3070 
    3071                 if ( selection !== collection ) {
    3072                         return;
    3073                 }
    3074 
    3075                 details = selection.single();
    3076                 this.$el.toggleClass( 'details', details === this.model );
     3139        browseRouter: function( routerView ) {
     3140                routerView.set({
     3141                        upload: {
     3142                                text:     l10n.uploadFilesTitle,
     3143                                priority: 20
     3144                        },
     3145                        browse: {
     3146                                text:     l10n.mediaLibraryTitle,
     3147                                priority: 40
     3148                        }
     3149                });
    30773150        },
     3151
    30783152        /**
    3079          * @param {string} size
    3080          * @returns {Object}
     3153         * Render callback for the content region in the `browse` mode.
     3154         *
     3155         * @param {wp.media.controller.Region} contentRegion
    30813156         */
    3082         imageSize: function( size ) {
    3083                 var sizes = this.model.get('sizes'), matched = false;
     3157        browseContent: function( contentRegion ) {
     3158                var state = this.state();
    30843159
    3085                 size = size || 'medium';
     3160                this.$el.removeClass('hide-toolbar');
    30863161
    3087                 // Use the provided image size if possible.
    3088                 if ( sizes ) {
    3089                         if ( sizes[ size ] ) {
    3090                                 matched = sizes[ size ];
    3091                         } else if ( sizes.large ) {
    3092                                 matched = sizes.large;
    3093                         } else if ( sizes.thumbnail ) {
    3094                                 matched = sizes.thumbnail;
    3095                         } else if ( sizes.full ) {
    3096                                 matched = sizes.full;
    3097                         }
     3162                // Browse our library of attachments.
     3163                contentRegion.view = new wp.media.view.AttachmentsBrowser({
     3164                        controller: this,
     3165                        collection: state.get('library'),
     3166                        selection:  state.get('selection'),
     3167                        model:      state,
     3168                        sortable:   state.get('sortable'),
     3169                        search:     state.get('searchable'),
     3170                        filters:    state.get('filterable'),
     3171                        date:       state.get('date'),
     3172                        display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
     3173                        dragInfo:   state.get('dragInfo'),
    30983174
    3099                         if ( matched ) {
    3100                                 return _.clone( matched );
    3101                         }
    3102                 }
     3175                        idealColumnWidth: state.get('idealColumnWidth'),
     3176                        suggestedWidth:   state.get('suggestedWidth'),
     3177                        suggestedHeight:  state.get('suggestedHeight'),
    31033178
    3104                 return {
    3105                         url:         this.model.get('url'),
    3106                         width:       this.model.get('width'),
    3107                         height:      this.model.get('height'),
    3108                         orientation: this.model.get('orientation')
    3109                 };
     3179                        AttachmentView: state.get('AttachmentView')
     3180                });
    31103181        },
     3182
    31113183        /**
    3112          * @param {Object} event
     3184         * Render callback for the content region in the `upload` mode.
    31133185         */
    3114         updateSetting: function( event ) {
    3115                 var $setting = $( event.target ).closest('[data-setting]'),
    3116                         setting, value;
    3117 
    3118                 if ( ! $setting.length ) {
    3119                         return;
    3120                 }
    3121 
    3122                 setting = $setting.data('setting');
    3123                 value   = event.target.value;
    3124 
    3125                 if ( this.model.get( setting ) !== value ) {
    3126                         this.save( setting, value );
    3127                 }
     3186        uploadContent: function() {
     3187                this.$el.removeClass( 'hide-toolbar' );
     3188                this.content.set( new wp.media.view.UploaderInline({
     3189                        controller: this
     3190                }) );
    31283191        },
    31293192
    31303193        /**
    3131          * Pass all the arguments to the model's save method.
     3194         * Toolbars
    31323195         *
    3133          * Records the aggregate status of all save requests and updates the
    3134          * view's classes accordingly.
     3196         * @param {Object} toolbar
     3197         * @param {Object} [options={}]
     3198         * @this wp.media.controller.Region
    31353199         */
    3136         save: function() {
    3137                 var view = this,
    3138                         save = this._save = this._save || { status: 'ready' },
    3139                         request = this.model.save.apply( this.model, arguments ),
    3140                         requests = save.requests ? $.when( request, save.requests ) : request;
    3141 
    3142                 // If we're waiting to remove 'Saved.', stop.
    3143                 if ( save.savedTimer ) {
    3144                         clearTimeout( save.savedTimer );
    3145                 }
    3146 
    3147                 this.updateSave('waiting');
    3148                 save.requests = requests;
    3149                 requests.always( function() {
    3150                         // If we've performed another request since this one, bail.
    3151                         if ( save.requests !== requests ) {
    3152                                 return;
    3153                         }
     3200        createSelectToolbar: function( toolbar, options ) {
     3201                options = options || this.options.button || {};
     3202                options.controller = this;
    31543203
    3155                         view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
    3156                         save.savedTimer = setTimeout( function() {
    3157                                 view.updateSave('ready');
    3158                                 delete save.savedTimer;
    3159                         }, 2000 );
    3160                 });
    3161         },
    3162         /**
    3163          * @param {string} status
    3164          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3165          */
    3166         updateSave: function( status ) {
    3167                 var save = this._save = this._save || { status: 'ready' };
     3204                toolbar.view = new wp.media.view.Toolbar.Select( options );
     3205        }
     3206});
    31683207
    3169                 if ( status && status !== save.status ) {
    3170                         this.$el.removeClass( 'save-' + save.status );
    3171                         save.status = status;
    3172                 }
     3208module.exports = Select;
    31733209
    3174                 this.$el.addClass( 'save-' + save.status );
    3175                 return this;
    3176         },
    31773210
    3178         updateAll: function() {
    3179                 var $settings = this.$('[data-setting]'),
    3180                         model = this.model,
    3181                         changed;
     3211/***/ }),
     3212/* 49 */
     3213/***/ (function(module, exports) {
    31823214
    3183                 changed = _.chain( $settings ).map( function( el ) {
    3184                         var $input = $('input, textarea, select, [value]', el ),
    3185                                 setting, value;
     3215var Select = wp.media.view.MediaFrame.Select,
     3216        Library = wp.media.controller.Library,
     3217        l10n = wp.media.view.l10n,
     3218        Post;
    31863219
    3187                         if ( ! $input.length ) {
    3188                                 return;
     3220/**
     3221 * wp.media.view.MediaFrame.Post
     3222 *
     3223 * The frame for manipulating media on the Edit Post page.
     3224 *
     3225 * @memberOf wp.media.view.MediaFrame
     3226 *
     3227 * @class
     3228 * @augments wp.media.view.MediaFrame.Select
     3229 * @augments wp.media.view.MediaFrame
     3230 * @augments wp.media.view.Frame
     3231 * @augments wp.media.View
     3232 * @augments wp.Backbone.View
     3233 * @augments Backbone.View
     3234 * @mixes wp.media.controller.StateMachine
     3235 */
     3236Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{
     3237        initialize: function() {
     3238                this.counts = {
     3239                        audio: {
     3240                                count: wp.media.view.settings.attachmentCounts.audio,
     3241                                state: 'playlist'
     3242                        },
     3243                        video: {
     3244                                count: wp.media.view.settings.attachmentCounts.video,
     3245                                state: 'video-playlist'
    31893246                        }
     3247                };
    31903248
    3191                         setting = $(el).data('setting');
    3192                         value = $input.val();
     3249                _.defaults( this.options, {
     3250                        multiple:  true,
     3251                        editing:   false,
     3252                        state:    'insert',
     3253                        metadata:  {}
     3254                });
    31933255
    3194                         // Record the value if it changed.
    3195                         if ( model.get( setting ) !== value ) {
    3196                                 return [ setting, value ];
    3197                         }
    3198                 }).compact().object().value();
     3256                // Call 'initialize' directly on the parent class.
     3257                Select.prototype.initialize.apply( this, arguments );
     3258                this.createIframeStates();
    31993259
    3200                 if ( ! _.isEmpty( changed ) ) {
    3201                         model.save( changed );
    3202                 }
    32033260        },
     3261
    32043262        /**
    3205          * @param {Object} event
     3263         * Create the default states.
    32063264         */
    3207         removeFromLibrary: function( event ) {
    3208                 // Catch enter and space events
    3209                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    3210                         return;
    3211                 }
     3265        createStates: function() {
     3266                var options = this.options;
    32123267
    3213                 // Stop propagation so the model isn't selected.
    3214                 event.stopPropagation();
     3268                this.states.add([
     3269                        // Main states.
     3270                        new Library({
     3271                                id:         'insert',
     3272                                title:      l10n.insertMediaTitle,
     3273                                priority:   20,
     3274                                toolbar:    'main-insert',
     3275                                filterable: 'all',
     3276                                library:    wp.media.query( options.library ),
     3277                                multiple:   options.multiple ? 'reset' : false,
     3278                                editable:   true,
    32153279
    3216                 this.collection.remove( this.model );
    3217         },
     3280                                // If the user isn't allowed to edit fields,
     3281                                // can they still edit it locally?
     3282                                allowLocalEdits: true,
    32183283
    3219         /**
    3220          * Add the model if it isn't in the selection, if it is in the selection,
    3221          * remove it.
    3222          *
    3223          * @param  {[type]} event [description]
    3224          * @return {[type]}       [description]
    3225          */
    3226         checkClickHandler: function ( event ) {
    3227                 var selection = this.options.selection;
    3228                 if ( ! selection ) {
    3229                         return;
    3230                 }
    3231                 event.stopPropagation();
    3232                 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
    3233                         selection.remove( this.model );
    3234                         // Move focus back to the attachment tile (from the check).
    3235                         this.$el.focus();
    3236                 } else {
    3237                         selection.add( this.model );
    3238                 }
    3239         }
    3240 });
    3241 
    3242 // Ensure settings remain in sync between attachment views.
    3243 _.each({
    3244         caption: '_syncCaption',
    3245         title:   '_syncTitle',
    3246         artist:  '_syncArtist',
    3247         album:   '_syncAlbum'
    3248 }, function( method, setting ) {
    3249         /**
    3250          * @function _syncCaption
    3251          * @memberOf wp.media.view.Attachment
    3252          * @instance
    3253          *
    3254          * @param {Backbone.Model} model
    3255          * @param {string} value
    3256          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3257          */
    3258         /**
    3259          * @function _syncTitle
    3260          * @memberOf wp.media.view.Attachment
    3261          * @instance
    3262          *
    3263          * @param {Backbone.Model} model
    3264          * @param {string} value
    3265          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3266          */
    3267         /**
    3268          * @function _syncArtist
    3269          * @memberOf wp.media.view.Attachment
    3270          * @instance
    3271          *
    3272          * @param {Backbone.Model} model
    3273          * @param {string} value
    3274          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3275          */
    3276         /**
    3277          * @function _syncAlbum
    3278          * @memberOf wp.media.view.Attachment
    3279          * @instance
    3280          *
    3281          * @param {Backbone.Model} model
    3282          * @param {string} value
    3283          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3284          */
    3285         Attachment.prototype[ method ] = function( model, value ) {
    3286                 var $setting = this.$('[data-setting="' + setting + '"]');
    3287 
    3288                 if ( ! $setting.length ) {
    3289                         return this;
    3290                 }
     3284                                // Show the attachment display settings.
     3285                                displaySettings: true,
     3286                                // Update user settings when users adjust the
     3287                                // attachment display settings.
     3288                                displayUserSettings: true
     3289                        }),
    32913290
    3292                 // If the updated value is in sync with the value in the DOM, there
    3293                 // is no need to re-render. If we're currently editing the value,
    3294                 // it will automatically be in sync, suppressing the re-render for
    3295                 // the view we're editing, while updating any others.
    3296                 if ( value === $setting.find('input, textarea, select, [value]').val() ) {
    3297                         return this;
    3298                 }
     3291                        new Library({
     3292                                id:         'gallery',
     3293                                title:      l10n.createGalleryTitle,
     3294                                priority:   40,
     3295                                toolbar:    'main-gallery',
     3296                                filterable: 'uploaded',
     3297                                multiple:   'add',
     3298                                editable:   false,
    32993299
    3300                 return this.render();
    3301         };
    3302 });
     3300                                library:  wp.media.query( _.defaults({
     3301                                        type: 'image'
     3302                                }, options.library ) )
     3303                        }),
    33033304
    3304 module.exports = Attachment;
     3305                        // Embed states.
     3306                        new wp.media.controller.Embed( { metadata: options.metadata } ),
    33053307
    3306 },{}],26:[function(require,module,exports){
    3307 var Attachment = wp.media.view.Attachment,
    3308         l10n = wp.media.view.l10n,
    3309         Details;
     3308                        new wp.media.controller.EditImage( { model: options.editImage } ),
    33103309
    3311 /**
    3312  * wp.media.view.Attachment.Details
    3313  *
    3314  * @memberOf wp.media.view.Attachment
    3315  *
    3316  * @class
    3317  * @augments wp.media.view.Attachment
    3318  * @augments wp.media.View
    3319  * @augments wp.Backbone.View
    3320  * @augments Backbone.View
    3321  */
    3322 Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{
    3323         tagName:   'div',
    3324         className: 'attachment-details',
    3325         template:  wp.template('attachment-details'),
     3310                        // Gallery states.
     3311                        new wp.media.controller.GalleryEdit({
     3312                                library: options.selection,
     3313                                editing: options.editing,
     3314                                menu:    'gallery'
     3315                        }),
    33263316
    3327         attributes: function() {
    3328                 return {
    3329                         'tabIndex':     0,
    3330                         'data-id':      this.model.get( 'id' )
    3331                 };
    3332         },
     3317                        new wp.media.controller.GalleryAdd(),
    33333318
    3334         events: {
    3335                 'change [data-setting]':          'updateSetting',
    3336                 'change [data-setting] input':    'updateSetting',
    3337                 'change [data-setting] select':   'updateSetting',
    3338                 'change [data-setting] textarea': 'updateSetting',
    3339                 'click .delete-attachment':       'deleteAttachment',
    3340                 'click .trash-attachment':        'trashAttachment',
    3341                 'click .untrash-attachment':      'untrashAttachment',
    3342                 'click .edit-attachment':         'editAttachment',
    3343                 'keydown':                        'toggleSelectionHandler'
    3344         },
     3319                        new Library({
     3320                                id:         'playlist',
     3321                                title:      l10n.createPlaylistTitle,
     3322                                priority:   60,
     3323                                toolbar:    'main-playlist',
     3324                                filterable: 'uploaded',
     3325                                multiple:   'add',
     3326                                editable:   false,
    33453327
    3346         initialize: function() {
    3347                 this.options = _.defaults( this.options, {
    3348                         rerenderOnModelChange: false
    3349                 });
     3328                                library:  wp.media.query( _.defaults({
     3329                                        type: 'audio'
     3330                                }, options.library ) )
     3331                        }),
    33503332
    3351                 this.on( 'ready', this.initialFocus );
    3352                 // Call 'initialize' directly on the parent class.
    3353                 Attachment.prototype.initialize.apply( this, arguments );
    3354         },
     3333                        // Playlist states.
     3334                        new wp.media.controller.CollectionEdit({
     3335                                type: 'audio',
     3336                                collectionType: 'playlist',
     3337                                title:          l10n.editPlaylistTitle,
     3338                                SettingsView:   wp.media.view.Settings.Playlist,
     3339                                library:        options.selection,
     3340                                editing:        options.editing,
     3341                                menu:           'playlist',
     3342                                dragInfoText:   l10n.playlistDragInfo,
     3343                                dragInfo:       false
     3344                        }),
    33553345
    3356         initialFocus: function() {
    3357                 if ( ! wp.media.isTouchDevice ) {
    3358                         /*
    3359                         Previously focused the first ':input' (the readonly URL text field).
    3360                         Since the first ':input' is now a button (delete/trash): when pressing
    3361                         spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
    3362                         as soon as focus is moved. Explicitly target the first text field for now.
    3363                         @todo change initial focus logic, also for accessibility.
    3364                         */
    3365                         this.$( 'input[type="text"]' ).eq( 0 ).focus();
    3366                 }
    3367         },
    3368         /**
    3369          * @param {Object} event
    3370          */
    3371         deleteAttachment: function( event ) {
    3372                 event.preventDefault();
     3346                        new wp.media.controller.CollectionAdd({
     3347                                type: 'audio',
     3348                                collectionType: 'playlist',
     3349                                title: l10n.addToPlaylistTitle
     3350                        }),
    33733351
    3374                 if ( window.confirm( l10n.warnDelete ) ) {
    3375                         this.model.destroy();
    3376                         // Keep focus inside media modal
    3377                         // after image is deleted
    3378                         this.controller.modal.focusManager.focus();
    3379                 }
    3380         },
    3381         /**
    3382          * @param {Object} event
    3383          */
    3384         trashAttachment: function( event ) {
    3385                 var library = this.controller.library;
    3386                 event.preventDefault();
     3352                        new Library({
     3353                                id:         'video-playlist',
     3354                                title:      l10n.createVideoPlaylistTitle,
     3355                                priority:   60,
     3356                                toolbar:    'main-video-playlist',
     3357                                filterable: 'uploaded',
     3358                                multiple:   'add',
     3359                                editable:   false,
    33873360
    3388                 if ( wp.media.view.settings.mediaTrash &&
    3389                         'edit-metadata' === this.controller.content.mode() ) {
     3361                                library:  wp.media.query( _.defaults({
     3362                                        type: 'video'
     3363                                }, options.library ) )
     3364                        }),
    33903365
    3391                         this.model.set( 'status', 'trash' );
    3392                         this.model.save().done( function() {
    3393                                 library._requery( true );
    3394                         } );
    3395                 }  else {
    3396                         this.model.destroy();
    3397                 }
    3398         },
    3399         /**
    3400          * @param {Object} event
    3401          */
    3402         untrashAttachment: function( event ) {
    3403                 var library = this.controller.library;
    3404                 event.preventDefault();
     3366                        new wp.media.controller.CollectionEdit({
     3367                                type: 'video',
     3368                                collectionType: 'playlist',
     3369                                title:          l10n.editVideoPlaylistTitle,
     3370                                SettingsView:   wp.media.view.Settings.Playlist,
     3371                                library:        options.selection,
     3372                                editing:        options.editing,
     3373                                menu:           'video-playlist',
     3374                                dragInfoText:   l10n.videoPlaylistDragInfo,
     3375                                dragInfo:       false
     3376                        }),
    34053377
    3406                 this.model.set( 'status', 'inherit' );
    3407                 this.model.save().done( function() {
    3408                         library._requery( true );
    3409                 } );
    3410         },
    3411         /**
    3412          * @param {Object} event
    3413          */
    3414         editAttachment: function( event ) {
    3415                 var editState = this.controller.states.get( 'edit-image' );
    3416                 if ( window.imageEdit && editState ) {
    3417                         event.preventDefault();
     3378                        new wp.media.controller.CollectionAdd({
     3379                                type: 'video',
     3380                                collectionType: 'playlist',
     3381                                title: l10n.addToVideoPlaylistTitle
     3382                        })
     3383                ]);
    34183384
    3419                         editState.set( 'image', this.model );
    3420                         this.controller.setState( 'edit-image' );
    3421                 } else {
    3422                         this.$el.addClass('needs-refresh');
     3385                if ( wp.media.view.settings.post.featuredImageId ) {
     3386                        this.states.add( new wp.media.controller.FeaturedImage() );
    34233387                }
    34243388        },
    3425         /**
    3426          * When reverse tabbing(shift+tab) out of the right details panel, deliver
    3427          * the focus to the item in the list that was being edited.
    3428          *
    3429          * @param {Object} event
    3430          */
    3431         toggleSelectionHandler: function( event ) {
    3432                 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
    3433                         this.controller.trigger( 'attachment:details:shift-tab', event );
    3434                         return false;
    3435                 }
    34363389
    3437                 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    3438                         this.controller.trigger( 'attachment:keydown:arrow', event );
    3439                         return;
    3440                 }
    3441         }
    3442 });
     3390        bindHandlers: function() {
     3391                var handlers, checkCounts;
    34433392
    3444 module.exports = Details;
     3393                Select.prototype.bindHandlers.apply( this, arguments );
    34453394
    3446 },{}],27:[function(require,module,exports){
    3447 /**
    3448  * wp.media.view.Attachment.EditLibrary
    3449  *
    3450  * @memberOf wp.media.view.Attachment
    3451  *
    3452  * @class
    3453  * @augments wp.media.view.Attachment
    3454  * @augments wp.media.View
    3455  * @augments wp.Backbone.View
    3456  * @augments Backbone.View
    3457  */
    3458 var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{
    3459         buttons: {
    3460                 close: true
    3461         }
    3462 });
     3395                this.on( 'activate', this.activate, this );
    34633396
    3464 module.exports = EditLibrary;
     3397                // Only bother checking media type counts if one of the counts is zero
     3398                checkCounts = _.find( this.counts, function( type ) {
     3399                        return type.count === 0;
     3400                } );
    34653401
    3466 },{}],28:[function(require,module,exports){
    3467 /**
    3468  * wp.media.view.Attachment.EditSelection
    3469  *
    3470  * @memberOf wp.media.view.Attachment
    3471  *
    3472  * @class
    3473  * @augments wp.media.view.Attachment.Selection
    3474  * @augments wp.media.view.Attachment
    3475  * @augments wp.media.View
    3476  * @augments wp.Backbone.View
    3477  * @augments Backbone.View
    3478  */
    3479 var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{
    3480         buttons: {
    3481                 close: true
    3482         }
    3483 });
     3402                if ( typeof checkCounts !== 'undefined' ) {
     3403                        this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
     3404                }
    34843405
    3485 module.exports = EditSelection;
     3406                this.on( 'menu:create:gallery', this.createMenu, this );
     3407                this.on( 'menu:create:playlist', this.createMenu, this );
     3408                this.on( 'menu:create:video-playlist', this.createMenu, this );
     3409                this.on( 'toolbar:create:main-insert', this.createToolbar, this );
     3410                this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
     3411                this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
     3412                this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
     3413                this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
     3414                this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
    34863415
    3487 },{}],29:[function(require,module,exports){
    3488 /**
    3489  * wp.media.view.Attachment.Library
    3490  *
    3491  * @memberOf wp.media.view.Attachment
    3492  *
    3493  * @class
    3494  * @augments wp.media.view.Attachment
    3495  * @augments wp.media.View
    3496  * @augments wp.Backbone.View
    3497  * @augments Backbone.View
    3498  */
    3499 var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{
    3500         buttons: {
    3501                 check: true
    3502         }
    3503 });
     3416                handlers = {
     3417                        menu: {
     3418                                'default': 'mainMenu',
     3419                                'gallery': 'galleryMenu',
     3420                                'playlist': 'playlistMenu',
     3421                                'video-playlist': 'videoPlaylistMenu'
     3422                        },
    35043423
    3505 module.exports = Library;
     3424                        content: {
     3425                                'embed':          'embedContent',
     3426                                'edit-image':     'editImageContent',
     3427                                'edit-selection': 'editSelectionContent'
     3428                        },
    35063429
    3507 },{}],30:[function(require,module,exports){
    3508 /**
    3509  * wp.media.view.Attachment.Selection
    3510  *
    3511  * @memberOf wp.media.view.Attachment
    3512  *
    3513  * @class
    3514  * @augments wp.media.view.Attachment
    3515  * @augments wp.media.View
    3516  * @augments wp.Backbone.View
    3517  * @augments Backbone.View
    3518  */
    3519 var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{
    3520         className: 'attachment selection',
     3430                        toolbar: {
     3431                                'main-insert':      'mainInsertToolbar',
     3432                                'main-gallery':     'mainGalleryToolbar',
     3433                                'gallery-edit':     'galleryEditToolbar',
     3434                                'gallery-add':      'galleryAddToolbar',
     3435                                'main-playlist':        'mainPlaylistToolbar',
     3436                                'playlist-edit':        'playlistEditToolbar',
     3437                                'playlist-add':         'playlistAddToolbar',
     3438                                'main-video-playlist': 'mainVideoPlaylistToolbar',
     3439                                'video-playlist-edit': 'videoPlaylistEditToolbar',
     3440                                'video-playlist-add': 'videoPlaylistAddToolbar'
     3441                        }
     3442                };
    35213443
    3522         // On click, just select the model, instead of removing the model from
    3523         // the selection.
    3524         toggleSelection: function() {
    3525                 this.options.selection.single( this.model );
    3526         }
    3527 });
     3444                _.each( handlers, function( regionHandlers, region ) {
     3445                        _.each( regionHandlers, function( callback, handler ) {
     3446                                this.on( region + ':render:' + handler, this[ callback ], this );
     3447                        }, this );
     3448                }, this );
     3449        },
    35283450
    3529 module.exports = Selection;
     3451        activate: function() {
     3452                // Hide menu items for states tied to particular media types if there are no items
     3453                _.each( this.counts, function( type ) {
     3454                        if ( type.count < 1 ) {
     3455                                this.menuItemVisibility( type.state, 'hide' );
     3456                        }
     3457                }, this );
     3458        },
    35303459
    3531 },{}],31:[function(require,module,exports){
    3532 var View = wp.media.View,
    3533         $ = jQuery,
    3534         Attachments;
     3460        mediaTypeCounts: function( model, attr ) {
     3461                if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
     3462                        this.counts[ attr ].count++;
     3463                        this.menuItemVisibility( this.counts[ attr ].state, 'show' );
     3464                }
     3465        },
    35353466
    3536 /**
    3537  * wp.media.view.Attachments
    3538  *
    3539  * @memberOf wp.media.view
    3540  *
    3541  * @class
    3542  * @augments wp.media.View
    3543  * @augments wp.Backbone.View
    3544  * @augments Backbone.View
    3545  */
    3546 Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
    3547         tagName:   'ul',
    3548         className: 'attachments',
     3467        // Menus
     3468        /**
     3469         * @param {wp.Backbone.View} view
     3470         */
     3471        mainMenu: function( view ) {
     3472                view.set({
     3473                        'library-separator': new wp.media.View({
     3474                                className: 'separator',
     3475                                priority: 100
     3476                        })
     3477                });
     3478        },
    35493479
    3550         attributes: {
    3551                 tabIndex: -1
     3480        menuItemVisibility: function( state, visibility ) {
     3481                var menu = this.menu.get();
     3482                if ( visibility === 'hide' ) {
     3483                        menu.hide( state );
     3484                } else if ( visibility === 'show' ) {
     3485                        menu.show( state );
     3486                }
    35523487        },
     3488        /**
     3489         * @param {wp.Backbone.View} view
     3490         */
     3491        galleryMenu: function( view ) {
     3492                var lastState = this.lastState(),
     3493                        previous = lastState && lastState.id,
     3494                        frame = this;
    35533495
    3554         initialize: function() {
    3555                 this.el.id = _.uniqueId('__attachments-view-');
     3496                view.set({
     3497                        cancel: {
     3498                                text:     l10n.cancelGalleryTitle,
     3499                                priority: 20,
     3500                                click:    function() {
     3501                                        if ( previous ) {
     3502                                                frame.setState( previous );
     3503                                        } else {
     3504                                                frame.close();
     3505                                        }
    35563506
    3557                 _.defaults( this.options, {
    3558                         refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
    3559                         refreshThreshold:   3,
    3560                         AttachmentView:     wp.media.view.Attachment,
    3561                         sortable:           false,
    3562                         resize:             true,
    3563                         idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
     3507                                        // Keep focus inside media modal
     3508                                        // after canceling a gallery
     3509                                        this.controller.modal.focusManager.focus();
     3510                                }
     3511                        },
     3512                        separateCancel: new wp.media.View({
     3513                                className: 'separator',
     3514                                priority: 40
     3515                        })
    35643516                });
     3517        },
    35653518
    3566                 this._viewsByCid = {};
    3567                 this.$window = $( window );
    3568                 this.resizeEvent = 'resize.media-modal-columns';
     3519        playlistMenu: function( view ) {
     3520                var lastState = this.lastState(),
     3521                        previous = lastState && lastState.id,
     3522                        frame = this;
    35693523
    3570                 this.collection.on( 'add', function( attachment ) {
    3571                         this.views.add( this.createAttachmentView( attachment ), {
    3572                                 at: this.collection.indexOf( attachment )
    3573                         });
    3574                 }, this );
     3524                view.set({
     3525                        cancel: {
     3526                                text:     l10n.cancelPlaylistTitle,
     3527                                priority: 20,
     3528                                click:    function() {
     3529                                        if ( previous ) {
     3530                                                frame.setState( previous );
     3531                                        } else {
     3532                                                frame.close();
     3533                                        }
     3534                                }
     3535                        },
     3536                        separateCancel: new wp.media.View({
     3537                                className: 'separator',
     3538                                priority: 40
     3539                        })
     3540                });
     3541        },
    35753542
    3576                 this.collection.on( 'remove', function( attachment ) {
    3577                         var view = this._viewsByCid[ attachment.cid ];
    3578                         delete this._viewsByCid[ attachment.cid ];
     3543        videoPlaylistMenu: function( view ) {
     3544                var lastState = this.lastState(),
     3545                        previous = lastState && lastState.id,
     3546                        frame = this;
    35793547
    3580                         if ( view ) {
    3581                                 view.remove();
    3582                         }
    3583                 }, this );
     3548                view.set({
     3549                        cancel: {
     3550                                text:     l10n.cancelVideoPlaylistTitle,
     3551                                priority: 20,
     3552                                click:    function() {
     3553                                        if ( previous ) {
     3554                                                frame.setState( previous );
     3555                                        } else {
     3556                                                frame.close();
     3557                                        }
     3558                                }
     3559                        },
     3560                        separateCancel: new wp.media.View({
     3561                                className: 'separator',
     3562                                priority: 40
     3563                        })
     3564                });
     3565        },
    35843566
    3585                 this.collection.on( 'reset', this.render, this );
     3567        // Content
     3568        embedContent: function() {
     3569                var view = new wp.media.view.Embed({
     3570                        controller: this,
     3571                        model:      this.state()
     3572                }).render();
    35863573
    3587                 this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
     3574                this.content.set( view );
    35883575
    3589                 // Throttle the scroll handler and bind this.
    3590                 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
     3576                if ( ! wp.media.isTouchDevice ) {
     3577                        view.url.focus();
     3578                }
     3579        },
    35913580
    3592                 this.options.scrollElement = this.options.scrollElement || this.el;
    3593                 $( this.options.scrollElement ).on( 'scroll', this.scroll );
     3581        editSelectionContent: function() {
     3582                var state = this.state(),
     3583                        selection = state.get('selection'),
     3584                        view;
    35943585
    3595                 this.initSortable();
     3586                view = new wp.media.view.AttachmentsBrowser({
     3587                        controller: this,
     3588                        collection: selection,
     3589                        selection:  selection,
     3590                        model:      state,
     3591                        sortable:   true,
     3592                        search:     false,
     3593                        date:       false,
     3594                        dragInfo:   true,
     3595
     3596                        AttachmentView: wp.media.view.Attachments.EditSelection
     3597                }).render();
    35963598
    3597                 _.bindAll( this, 'setColumns' );
     3599                view.toolbar.set( 'backToLibrary', {
     3600                        text:     l10n.returnToLibrary,
     3601                        priority: -100,
    35983602
    3599                 if ( this.options.resize ) {
    3600                         this.on( 'ready', this.bindEvents );
    3601                         this.controller.on( 'open', this.setColumns );
     3603                        click: function() {
     3604                                this.controller.content.mode('browse');
     3605                        }
     3606                });
    36023607
    3603                         // Call this.setColumns() after this view has been rendered in the DOM so
    3604                         // attachments get proper width applied.
    3605                         _.defer( this.setColumns, this );
    3606                 }
    3607         },
     3608                // Browse our library of attachments.
     3609                this.content.set( view );
    36083610
    3609         bindEvents: function() {
    3610                 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
     3611                // Trigger the controller to set focus
     3612                this.trigger( 'edit:selection', this );
    36113613        },
    36123614
    3613         attachmentFocus: function() {
    3614                 this.$( 'li:first' ).focus();
    3615         },
     3615        editImageContent: function() {
     3616                var image = this.state().get('image'),
     3617                        view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
    36163618
    3617         restoreFocus: function() {
    3618                 this.$( 'li.selected:first' ).focus();
    3619         },
     3619                this.content.set( view );
    36203620
    3621         arrowEvent: function( event ) {
    3622                 var attachments = this.$el.children( 'li' ),
    3623                         perRow = this.columns,
    3624                         index = attachments.filter( ':focus' ).index(),
    3625                         row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
     3621                // after creating the wrapper view, load the actual editor via an ajax call
     3622                view.loadEditor();
    36263623
    3627                 if ( index === -1 ) {
    3628                         return;
    3629                 }
     3624        },
    36303625
    3631                 // Left arrow
    3632                 if ( 37 === event.keyCode ) {
    3633                         if ( 0 === index ) {
    3634                                 return;
    3635                         }
    3636                         attachments.eq( index - 1 ).focus();
    3637                 }
     3626        // Toolbars
    36383627
    3639                 // Up arrow
    3640                 if ( 38 === event.keyCode ) {
    3641                         if ( 1 === row ) {
    3642                                 return;
    3643                         }
    3644                         attachments.eq( index - perRow ).focus();
    3645                 }
     3628        /**
     3629         * @param {wp.Backbone.View} view
     3630         */
     3631        selectionStatusToolbar: function( view ) {
     3632                var editable = this.state().get('editable');
    36463633
    3647                 // Right arrow
    3648                 if ( 39 === event.keyCode ) {
    3649                         if ( attachments.length === index ) {
    3650                                 return;
    3651                         }
    3652                         attachments.eq( index + 1 ).focus();
    3653                 }
     3634                view.set( 'selection', new wp.media.view.Selection({
     3635                        controller: this,
     3636                        collection: this.state().get('selection'),
     3637                        priority:   -40,
    36543638
    3655                 // Down arrow
    3656                 if ( 40 === event.keyCode ) {
    3657                         if ( Math.ceil( attachments.length / perRow ) === row ) {
    3658                                 return;
     3639                        // If the selection is editable, pass the callback to
     3640                        // switch the content mode.
     3641                        editable: editable && function() {
     3642                                this.controller.content.mode('edit-selection');
    36593643                        }
    3660                         attachments.eq( index + perRow ).focus();
    3661                 }
     3644                }).render() );
    36623645        },
    36633646
    3664         dispose: function() {
    3665                 this.collection.props.off( null, null, this );
    3666                 if ( this.options.resize ) {
    3667                         this.$window.off( this.resizeEvent );
    3668                 }
     3647        /**
     3648         * @param {wp.Backbone.View} view
     3649         */
     3650        mainInsertToolbar: function( view ) {
     3651                var controller = this;
    36693652
    3670                 /**
    3671                  * call 'dispose' directly on the parent class
    3672                  */
    3673                 View.prototype.dispose.apply( this, arguments );
    3674         },
     3653                this.selectionStatusToolbar( view );
    36753654
    3676         setColumns: function() {
    3677                 var prev = this.columns,
    3678                         width = this.$el.width();
     3655                view.set( 'insert', {
     3656                        style:    'primary',
     3657                        priority: 80,
     3658                        text:     l10n.insertIntoPost,
     3659                        requires: { selection: true },
    36793660
    3680                 if ( width ) {
    3681                         this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
     3661                        /**
     3662                         * @callback
     3663                         * @fires wp.media.controller.State#insert
     3664                         */
     3665                        click: function() {
     3666                                var state = controller.state(),
     3667                                        selection = state.get('selection');
    36823668
    3683                         if ( ! prev || prev !== this.columns ) {
    3684                                 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
     3669                                controller.close();
     3670                                state.trigger( 'insert', selection ).reset();
    36853671                        }
    3686                 }
     3672                });
    36873673        },
    36883674
    3689         initSortable: function() {
    3690                 var collection = this.collection;
     3675        /**
     3676         * @param {wp.Backbone.View} view
     3677         */
     3678        mainGalleryToolbar: function( view ) {
     3679                var controller = this;
    36913680
    3692                 if ( ! this.options.sortable || ! $.fn.sortable ) {
    3693                         return;
    3694                 }
     3681                this.selectionStatusToolbar( view );
    36953682
    3696                 this.$el.sortable( _.extend({
    3697                         // If the `collection` has a `comparator`, disable sorting.
    3698                         disabled: !! collection.comparator,
     3683                view.set( 'gallery', {
     3684                        style:    'primary',
     3685                        text:     l10n.createNewGallery,
     3686                        priority: 60,
     3687                        requires: { selection: true },
    36993688
    3700                         // Change the position of the attachment as soon as the
    3701                         // mouse pointer overlaps a thumbnail.
    3702                         tolerance: 'pointer',
     3689                        click: function() {
     3690                                var selection = controller.state().get('selection'),
     3691                                        edit = controller.state('gallery-edit'),
     3692                                        models = selection.where({ type: 'image' });
    37033693
    3704                         // Record the initial `index` of the dragged model.
    3705                         start: function( event, ui ) {
    3706                                 ui.item.data('sortableIndexStart', ui.item.index());
    3707                         },
     3694                                edit.set( 'library', new wp.media.model.Selection( models, {
     3695                                        props:    selection.props.toJSON(),
     3696                                        multiple: true
     3697                                }) );
    37083698
    3709                         // Update the model's index in the collection.
    3710                         // Do so silently, as the view is already accurate.
    3711                         update: function( event, ui ) {
    3712                                 var model = collection.at( ui.item.data('sortableIndexStart') ),
    3713                                         comparator = collection.comparator;
     3699                                this.controller.setState('gallery-edit');
    37143700
    3715                                 // Temporarily disable the comparator to prevent `add`
    3716                                 // from re-sorting.
    3717                                 delete collection.comparator;
     3701                                // Keep focus inside media modal
     3702                                // after jumping to gallery view
     3703                                this.controller.modal.focusManager.focus();
     3704                        }
     3705                });
     3706        },
    37183707
    3719                                 // Silently shift the model to its new index.
    3720                                 collection.remove( model, {
    3721                                         silent: true
    3722                                 });
    3723                                 collection.add( model, {
    3724                                         silent: true,
    3725                                         at:     ui.item.index()
    3726                                 });
     3708        mainPlaylistToolbar: function( view ) {
     3709                var controller = this;
    37273710
    3728                                 // Restore the comparator.
    3729                                 collection.comparator = comparator;
     3711                this.selectionStatusToolbar( view );
    37303712
    3731                                 // Fire the `reset` event to ensure other collections sync.
    3732                                 collection.trigger( 'reset', collection );
     3713                view.set( 'playlist', {
     3714                        style:    'primary',
     3715                        text:     l10n.createNewPlaylist,
     3716                        priority: 100,
     3717                        requires: { selection: true },
    37333718
    3734                                 // If the collection is sorted by menu order,
    3735                                 // update the menu order.
    3736                                 collection.saveMenuOrder();
    3737                         }
    3738                 }, this.options.sortable ) );
     3719                        click: function() {
     3720                                var selection = controller.state().get('selection'),
     3721                                        edit = controller.state('playlist-edit'),
     3722                                        models = selection.where({ type: 'audio' });
    37393723
    3740                 // If the `orderby` property is changed on the `collection`,
    3741                 // check to see if we have a `comparator`. If so, disable sorting.
    3742                 collection.props.on( 'change:orderby', function() {
    3743                         this.$el.sortable( 'option', 'disabled', !! collection.comparator );
    3744                 }, this );
     3724                                edit.set( 'library', new wp.media.model.Selection( models, {
     3725                                        props:    selection.props.toJSON(),
     3726                                        multiple: true
     3727                                }) );
    37453728
    3746                 this.collection.props.on( 'change:orderby', this.refreshSortable, this );
    3747                 this.refreshSortable();
    3748         },
     3729                                this.controller.setState('playlist-edit');
    37493730
    3750         refreshSortable: function() {
    3751                 if ( ! this.options.sortable || ! $.fn.sortable ) {
    3752                         return;
    3753                 }
     3731                                // Keep focus inside media modal
     3732                                // after jumping to playlist view
     3733                                this.controller.modal.focusManager.focus();
     3734                        }
     3735                });
     3736        },
    37543737
    3755                 // If the `collection` has a `comparator`, disable sorting.
    3756                 var collection = this.collection,
    3757                         orderby = collection.props.get('orderby'),
    3758                         enabled = 'menuOrder' === orderby || ! collection.comparator;
     3738        mainVideoPlaylistToolbar: function( view ) {
     3739                var controller = this;
    37593740
    3760                 this.$el.sortable( 'option', 'disabled', ! enabled );
    3761         },
     3741                this.selectionStatusToolbar( view );
    37623742
    3763         /**
    3764          * @param {wp.media.model.Attachment} attachment
    3765          * @returns {wp.media.View}
    3766          */
    3767         createAttachmentView: function( attachment ) {
    3768                 var view = new this.options.AttachmentView({
    3769                         controller:           this.controller,
    3770                         model:                attachment,
    3771                         collection:           this.collection,
    3772                         selection:            this.options.selection
    3773                 });
     3743                view.set( 'video-playlist', {
     3744                        style:    'primary',
     3745                        text:     l10n.createNewVideoPlaylist,
     3746                        priority: 100,
     3747                        requires: { selection: true },
    37743748
    3775                 return this._viewsByCid[ attachment.cid ] = view;
    3776         },
     3749                        click: function() {
     3750                                var selection = controller.state().get('selection'),
     3751                                        edit = controller.state('video-playlist-edit'),
     3752                                        models = selection.where({ type: 'video' });
    37773753
    3778         prepare: function() {
    3779                 // Create all of the Attachment views, and replace
    3780                 // the list in a single DOM operation.
    3781                 if ( this.collection.length ) {
    3782                         this.views.set( this.collection.map( this.createAttachmentView, this ) );
     3754                                edit.set( 'library', new wp.media.model.Selection( models, {
     3755                                        props:    selection.props.toJSON(),
     3756                                        multiple: true
     3757                                }) );
    37833758
    3784                 // If there are no elements, clear the views and load some.
    3785                 } else {
    3786                         this.views.unset();
    3787                         this.collection.more().done( this.scroll );
    3788                 }
    3789         },
     3759                                this.controller.setState('video-playlist-edit');
    37903760
    3791         ready: function() {
    3792                 // Trigger the scroll event to check if we're within the
    3793                 // threshold to query for additional attachments.
    3794                 this.scroll();
     3761                                // Keep focus inside media modal
     3762                                // after jumping to video playlist view
     3763                                this.controller.modal.focusManager.focus();
     3764                        }
     3765                });
    37953766        },
    37963767
    3797         scroll: function() {
    3798                 var view = this,
    3799                         el = this.options.scrollElement,
    3800                         scrollTop = el.scrollTop,
    3801                         toolbar;
     3768        featuredImageToolbar: function( toolbar ) {
     3769                this.createSelectToolbar( toolbar, {
     3770                        text:  l10n.setFeaturedImage,
     3771                        state: this.options.state
     3772                });
     3773        },
    38023774
    3803                 // The scroll event occurs on the document, but the element
    3804                 // that should be checked is the document body.
    3805                 if ( el === document ) {
    3806                         el = document.body;
    3807                         scrollTop = $(document).scrollTop();
    3808                 }
     3775        mainEmbedToolbar: function( toolbar ) {
     3776                toolbar.view = new wp.media.view.Toolbar.Embed({
     3777                        controller: this
     3778                });
     3779        },
    38093780
    3810                 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
    3811                         return;
    3812                 }
     3781        galleryEditToolbar: function() {
     3782                var editing = this.state().get('editing');
     3783                this.toolbar.set( new wp.media.view.Toolbar({
     3784                        controller: this,
     3785                        items: {
     3786                                insert: {
     3787                                        style:    'primary',
     3788                                        text:     editing ? l10n.updateGallery : l10n.insertGallery,
     3789                                        priority: 80,
     3790                                        requires: { library: true },
    38133791
    3814                 toolbar = this.views.parent.toolbar;
     3792                                        /**
     3793                                         * @fires wp.media.controller.State#update
     3794                                         */
     3795                                        click: function() {
     3796                                                var controller = this.controller,
     3797                                                        state = controller.state();
    38153798
    3816                 // Show the spinner only if we are close to the bottom.
    3817                 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
    3818                         toolbar.get('spinner').show();
    3819                 }
     3799                                                controller.close();
     3800                                                state.trigger( 'update', state.get('library') );
    38203801
    3821                 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
    3822                         this.collection.more().done(function() {
    3823                                 view.scroll();
    3824                                 toolbar.get('spinner').hide();
    3825                         });
    3826                 }
    3827         }
    3828 });
     3802                                                // Restore and reset the default state.
     3803                                                controller.setState( controller.options.state );
     3804                                                controller.reset();
     3805                                        }
     3806                                }
     3807                        }
     3808                }) );
     3809        },
    38293810
    3830 module.exports = Attachments;
     3811        galleryAddToolbar: function() {
     3812                this.toolbar.set( new wp.media.view.Toolbar({
     3813                        controller: this,
     3814                        items: {
     3815                                insert: {
     3816                                        style:    'primary',
     3817                                        text:     l10n.addToGallery,
     3818                                        priority: 80,
     3819                                        requires: { selection: true },
    38313820
    3832 },{}],32:[function(require,module,exports){
    3833 var View = wp.media.View,
    3834         mediaTrash = wp.media.view.settings.mediaTrash,
    3835         l10n = wp.media.view.l10n,
    3836         $ = jQuery,
    3837         AttachmentsBrowser;
     3821                                        /**
     3822                                         * @fires wp.media.controller.State#reset
     3823                                         */
     3824                                        click: function() {
     3825                                                var controller = this.controller,
     3826                                                        state = controller.state(),
     3827                                                        edit = controller.state('gallery-edit');
    38383828
    3839 /**
    3840  * wp.media.view.AttachmentsBrowser
    3841  *
    3842  * @memberOf wp.media.view
    3843  *
    3844  * @class
    3845  * @augments wp.media.View
    3846  * @augments wp.Backbone.View
    3847  * @augments Backbone.View
    3848  *
    3849  * @param {object}         [options]               The options hash passed to the view.
    3850  * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar.
    3851  *                                                 Accepts 'uploaded' and 'all'.
    3852  * @param {boolean}        [options.search=true]   Whether to show the search interface in the
    3853  *                                                 browser's toolbar.
    3854  * @param {boolean}        [options.date=true]     Whether to show the date filter in the
    3855  *                                                 browser's toolbar.
    3856  * @param {boolean}        [options.display=false] Whether to show the attachments display settings
    3857  *                                                 view in the sidebar.
    3858  * @param {boolean|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
    3859  *                                                 Accepts true, false, and 'errors'.
    3860  */
    3861 AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{
    3862         tagName:   'div',
    3863         className: 'attachments-browser',
     3829                                                edit.get('library').add( state.get('selection').models );
     3830                                                state.trigger('reset');
     3831                                                controller.setState('gallery-edit');
     3832                                        }
     3833                                }
     3834                        }
     3835                }) );
     3836        },
    38643837
    3865         initialize: function() {
    3866                 _.defaults( this.options, {
    3867                         filters: false,
    3868                         search:  true,
    3869                         date:    true,
    3870                         display: false,
    3871                         sidebar: true,
    3872                         AttachmentView: wp.media.view.Attachment.Library
    3873                 });
     3838        playlistEditToolbar: function() {
     3839                var editing = this.state().get('editing');
     3840                this.toolbar.set( new wp.media.view.Toolbar({
     3841                        controller: this,
     3842                        items: {
     3843                                insert: {
     3844                                        style:    'primary',
     3845                                        text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
     3846                                        priority: 80,
     3847                                        requires: { library: true },
    38743848
    3875                 this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this );
    3876                 this.controller.on( 'edit:selection', this.editSelection );
     3849                                        /**
     3850                                         * @fires wp.media.controller.State#update
     3851                                         */
     3852                                        click: function() {
     3853                                                var controller = this.controller,
     3854                                                        state = controller.state();
    38773855
    3878                 // In the Media Library, the sidebar is used to display errors before the attachments grid.
    3879                 if ( this.options.sidebar && 'errors' === this.options.sidebar ) {
    3880                         this.createSidebar();
    3881                 }
     3856                                                controller.close();
     3857                                                state.trigger( 'update', state.get('library') );
    38823858
    3883                 /*
    3884                  * For accessibility reasons, place the Inline Uploader before other sections.
    3885                  * This way, in the Media Library, it's right after the Add New button, see ticket #37188.
    3886                  */
    3887                 this.createUploader();
     3859                                                // Restore and reset the default state.
     3860                                                controller.setState( controller.options.state );
     3861                                                controller.reset();
     3862                                        }
     3863                                }
     3864                        }
     3865                }) );
     3866        },
    38883867
    3889                 /*
    3890                  * Create a multi-purpose toolbar. Used as main toolbar in the Media Library
    3891                  * and also for other things, for example the "Drag and drop to reorder" and
    3892                  * "Suggested dimensions" info in the media modal.
    3893                  */
    3894                 this.createToolbar();
     3868        playlistAddToolbar: function() {
     3869                this.toolbar.set( new wp.media.view.Toolbar({
     3870                        controller: this,
     3871                        items: {
     3872                                insert: {
     3873                                        style:    'primary',
     3874                                        text:     l10n.addToPlaylist,
     3875                                        priority: 80,
     3876                                        requires: { selection: true },
    38953877
    3896                 // Create the list of attachments.
    3897                 this.createAttachments();
     3878                                        /**
     3879                                         * @fires wp.media.controller.State#reset
     3880                                         */
     3881                                        click: function() {
     3882                                                var controller = this.controller,
     3883                                                        state = controller.state(),
     3884                                                        edit = controller.state('playlist-edit');
    38983885
    3899                 // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909.
    3900                 if ( this.options.sidebar && 'errors' !== this.options.sidebar ) {
    3901                         this.createSidebar();
    3902                 }
     3886                                                edit.get('library').add( state.get('selection').models );
     3887                                                state.trigger('reset');
     3888                                                controller.setState('playlist-edit');
     3889                                        }
     3890                                }
     3891                        }
     3892                }) );
     3893        },
    39033894
    3904                 this.updateContent();
     3895        videoPlaylistEditToolbar: function() {
     3896                var editing = this.state().get('editing');
     3897                this.toolbar.set( new wp.media.view.Toolbar({
     3898                        controller: this,
     3899                        items: {
     3900                                insert: {
     3901                                        style:    'primary',
     3902                                        text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
     3903                                        priority: 140,
     3904                                        requires: { library: true },
    39053905
    3906                 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
    3907                         this.$el.addClass( 'hide-sidebar' );
     3906                                        click: function() {
     3907                                                var controller = this.controller,
     3908                                                        state = controller.state(),
     3909                                                        library = state.get('library');
    39083910
    3909                         if ( 'errors' === this.options.sidebar ) {
    3910                                 this.$el.addClass( 'sidebar-for-errors' );
    3911                         }
    3912                 }
     3911                                                library.type = 'video';
    39133912
    3914                 this.collection.on( 'add remove reset', this.updateContent, this );
    3915         },
     3913                                                controller.close();
     3914                                                state.trigger( 'update', library );
    39163915
    3917         editSelection: function( modal ) {
    3918                 modal.$( '.media-button-backToLibrary' ).focus();
     3916                                                // Restore and reset the default state.
     3917                                                controller.setState( controller.options.state );
     3918                                                controller.reset();
     3919                                        }
     3920                                }
     3921                        }
     3922                }) );
    39193923        },
    39203924
    3921         /**
    3922          * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
    3923          */
    3924         dispose: function() {
    3925                 this.options.selection.off( null, null, this );
    3926                 View.prototype.dispose.apply( this, arguments );
    3927                 return this;
    3928         },
     3925        videoPlaylistAddToolbar: function() {
     3926                this.toolbar.set( new wp.media.view.Toolbar({
     3927                        controller: this,
     3928                        items: {
     3929                                insert: {
     3930                                        style:    'primary',
     3931                                        text:     l10n.addToVideoPlaylist,
     3932                                        priority: 140,
     3933                                        requires: { selection: true },
    39293934
    3930         createToolbar: function() {
    3931                 var LibraryViewSwitcher, Filters, toolbarOptions;
     3935                                        click: function() {
     3936                                                var controller = this.controller,
     3937                                                        state = controller.state(),
     3938                                                        edit = controller.state('video-playlist-edit');
    39323939
    3933                 toolbarOptions = {
    3934                         controller: this.controller
    3935                 };
     3940                                                edit.get('library').add( state.get('selection').models );
     3941                                                state.trigger('reset');
     3942                                                controller.setState('video-playlist-edit');
     3943                                        }
     3944                                }
     3945                        }
     3946                }) );
     3947        }
     3948});
    39363949
    3937                 if ( this.controller.isModeActive( 'grid' ) ) {
    3938                         toolbarOptions.className = 'media-toolbar wp-filter';
    3939                 }
     3950module.exports = Post;
    39403951
    3941                 /**
    3942                 * @member {wp.media.view.Toolbar}
    3943                 */
    3944                 this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
    39453952
    3946                 this.views.add( this.toolbar );
     3953/***/ }),
     3954/* 50 */
     3955/***/ (function(module, exports) {
    39473956
    3948                 this.toolbar.set( 'spinner', new wp.media.view.Spinner({
    3949                         priority: -60
    3950                 }) );
     3957var Select = wp.media.view.MediaFrame.Select,
     3958        l10n = wp.media.view.l10n,
     3959        ImageDetails;
    39513960
    3952                 if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
    3953                         // "Filters" will return a <select>, need to render
    3954                         // screen reader text before
    3955                         this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
    3956                                 value: l10n.filterByType,
    3957                                 attributes: {
    3958                                         'for':  'media-attachment-filters'
    3959                                 },
    3960                                 priority:   -80
    3961                         }).render() );
     3961/**
     3962 * wp.media.view.MediaFrame.ImageDetails
     3963 *
     3964 * A media frame for manipulating an image that's already been inserted
     3965 * into a post.
     3966 *
     3967 * @memberOf wp.media.view.MediaFrame
     3968 *
     3969 * @class
     3970 * @augments wp.media.view.MediaFrame.Select
     3971 * @augments wp.media.view.MediaFrame
     3972 * @augments wp.media.view.Frame
     3973 * @augments wp.media.View
     3974 * @augments wp.Backbone.View
     3975 * @augments Backbone.View
     3976 * @mixes wp.media.controller.StateMachine
     3977 */
     3978ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{
     3979        defaults: {
     3980                id:      'image',
     3981                url:     '',
     3982                menu:    'image-details',
     3983                content: 'image-details',
     3984                toolbar: 'image-details',
     3985                type:    'link',
     3986                title:    l10n.imageDetailsTitle,
     3987                priority: 120
     3988        },
    39623989
    3963                         if ( 'uploaded' === this.options.filters ) {
    3964                                 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({
    3965                                         controller: this.controller,
    3966                                         model:      this.collection.props,
    3967                                         priority:   -80
    3968                                 }).render() );
    3969                         } else {
    3970                                 Filters = new wp.media.view.AttachmentFilters.All({
    3971                                         controller: this.controller,
    3972                                         model:      this.collection.props,
    3973                                         priority:   -80
    3974                                 });
     3990        initialize: function( options ) {
     3991                this.image = new wp.media.model.PostImage( options.metadata );
     3992                this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
     3993                Select.prototype.initialize.apply( this, arguments );
     3994        },
    39753995
    3976                                 this.toolbar.set( 'filters', Filters.render() );
    3977                         }
    3978                 }
     3996        bindHandlers: function() {
     3997                Select.prototype.bindHandlers.apply( this, arguments );
     3998                this.on( 'menu:create:image-details', this.createMenu, this );
     3999                this.on( 'content:create:image-details', this.imageDetailsContent, this );
     4000                this.on( 'content:render:edit-image', this.editImageContent, this );
     4001                this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
     4002                // override the select toolbar
     4003                this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
     4004        },
    39794005
    3980                 // Feels odd to bring the global media library switcher into the Attachment
    3981                 // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
    3982                 // which the controller can tap into and add this view?
    3983                 if ( this.controller.isModeActive( 'grid' ) ) {
    3984                         LibraryViewSwitcher = View.extend({
    3985                                 className: 'view-switch media-grid-view-switch',
    3986                                 template: wp.template( 'media-library-view-switcher')
    3987                         });
     4006        createStates: function() {
     4007                this.states.add([
     4008                        new wp.media.controller.ImageDetails({
     4009                                image: this.image,
     4010                                editable: false
     4011                        }),
     4012                        new wp.media.controller.ReplaceImage({
     4013                                id: 'replace-image',
     4014                                library: wp.media.query( { type: 'image' } ),
     4015                                image: this.image,
     4016                                multiple:  false,
     4017                                title:     l10n.imageReplaceTitle,
     4018                                toolbar: 'replace',
     4019                                priority:  80,
     4020                                displaySettings: true
     4021                        }),
     4022                        new wp.media.controller.EditImage( {
     4023                                image: this.image,
     4024                                selection: this.options.selection
     4025                        } )
     4026                ]);
     4027        },
    39884028
    3989                         this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
    3990                                 controller: this.controller,
    3991                                 priority: -90
    3992                         }).render() );
     4029        imageDetailsContent: function( options ) {
     4030                options.view = new wp.media.view.ImageDetails({
     4031                        controller: this,
     4032                        model: this.state().image,
     4033                        attachment: this.state().image.attachment
     4034                });
     4035        },
    39934036
    3994                         // DateFilter is a <select>, screen reader text needs to be rendered before
    3995                         this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
    3996                                 value: l10n.filterByDate,
    3997                                 attributes: {
    3998                                         'for': 'media-attachment-date-filters'
    3999                                 },
    4000                                 priority: -75
    4001                         }).render() );
    4002                         this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
    4003                                 controller: this.controller,
    4004                                 model:      this.collection.props,
    4005                                 priority: -75
    4006                         }).render() );
     4037        editImageContent: function() {
     4038                var state = this.state(),
     4039                        model = state.get('image'),
     4040                        view;
    40074041
    4008                         // BulkSelection is a <div> with subviews, including screen reader text
    4009                         this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
    4010                                 text: l10n.bulkSelect,
    4011                                 controller: this.controller,
    4012                                 priority: -70
    4013                         }).render() );
     4042                if ( ! model ) {
     4043                        return;
     4044                }
    40144045
    4015                         this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
    4016                                 filters: Filters,
    4017                                 style: 'primary',
    4018                                 disabled: true,
    4019                                 text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
    4020                                 controller: this.controller,
    4021                                 priority: -60,
    4022                                 click: function() {
    4023                                         var changed = [], removed = [],
    4024                                                 selection = this.controller.state().get( 'selection' ),
    4025                                                 library = this.controller.state().get( 'library' );
     4046                view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
    40264047
    4027                                         if ( ! selection.length ) {
    4028                                                 return;
    4029                                         }
     4048                this.content.set( view );
    40304049
    4031                                         if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
    4032                                                 return;
    4033                                         }
     4050                // after bringing in the frame, load the actual editor via an ajax call
     4051                view.loadEditor();
    40344052
    4035                                         if ( mediaTrash &&
    4036                                                 'trash' !== selection.at( 0 ).get( 'status' ) &&
    4037                                                 ! window.confirm( l10n.warnBulkTrash ) ) {
     4053        },
    40384054
    4039                                                 return;
    4040                                         }
     4055        renderImageDetailsToolbar: function() {
     4056                this.toolbar.set( new wp.media.view.Toolbar({
     4057                        controller: this,
     4058                        items: {
     4059                                select: {
     4060                                        style:    'primary',
     4061                                        text:     l10n.update,
     4062                                        priority: 80,
    40414063
    4042                                         selection.each( function( model ) {
    4043                                                 if ( ! model.get( 'nonces' )['delete'] ) {
    4044                                                         removed.push( model );
    4045                                                         return;
    4046                                                 }
     4064                                        click: function() {
     4065                                                var controller = this.controller,
     4066                                                        state = controller.state();
    40474067
    4048                                                 if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
    4049                                                         model.set( 'status', 'inherit' );
    4050                                                         changed.push( model.save() );
    4051                                                         removed.push( model );
    4052                                                 } else if ( mediaTrash ) {
    4053                                                         model.set( 'status', 'trash' );
    4054                                                         changed.push( model.save() );
    4055                                                         removed.push( model );
    4056                                                 } else {
    4057                                                         model.destroy({wait: true});
    4058                                                 }
    4059                                         } );
     4068                                                controller.close();
    40604069
    4061                                         if ( changed.length ) {
    4062                                                 selection.remove( removed );
     4070                                                // not sure if we want to use wp.media.string.image which will create a shortcode or
     4071                                                // perhaps wp.html.string to at least to build the <img />
     4072                                                state.trigger( 'update', controller.image.toJSON() );
    40634073
    4064                                                 $.when.apply( null, changed ).then( _.bind( function() {
    4065                                                         library._requery( true );
    4066                                                         this.controller.trigger( 'selection:action:done' );
    4067                                                 }, this ) );
    4068                                         } else {
    4069                                                 this.controller.trigger( 'selection:action:done' );
     4074                                                // Restore and reset the default state.
     4075                                                controller.setState( controller.options.state );
     4076                                                controller.reset();
    40704077                                        }
    40714078                                }
    4072                         }).render() );
     4079                        }
     4080                }) );
     4081        },
    40734082
    4074                         if ( mediaTrash ) {
    4075                                 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
    4076                                         filters: Filters,
    4077                                         style: 'primary',
    4078                                         disabled: true,
    4079                                         text: l10n.deleteSelected,
    4080                                         controller: this.controller,
    4081                                         priority: -55,
    4082                                         click: function() {
    4083                                                 var removed = [],
    4084                                                         destroy = [],
    4085                                                         selection = this.controller.state().get( 'selection' );
     4083        renderReplaceImageToolbar: function() {
     4084                var frame = this,
     4085                        lastState = frame.lastState(),
     4086                        previous = lastState && lastState.id;
    40864087
    4087                                                 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
    4088                                                         return;
     4088                this.toolbar.set( new wp.media.view.Toolbar({
     4089                        controller: this,
     4090                        items: {
     4091                                back: {
     4092                                        text:     l10n.back,
     4093                                        priority: 20,
     4094                                        click:    function() {
     4095                                                if ( previous ) {
     4096                                                        frame.setState( previous );
     4097                                                } else {
     4098                                                        frame.close();
    40894099                                                }
     4100                                        }
     4101                                },
    40904102
    4091                                                 selection.each( function( model ) {
    4092                                                         if ( ! model.get( 'nonces' )['delete'] ) {
    4093                                                                 removed.push( model );
    4094                                                                 return;
    4095                                                         }
     4103                                replace: {
     4104                                        style:    'primary',
     4105                                        text:     l10n.replace,
     4106                                        priority: 80,
     4107                                        requires: { selection: true },
    40964108
    4097                                                         destroy.push( model );
    4098                                                 } );
     4109                                        click: function() {
     4110                                                var controller = this.controller,
     4111                                                        state = controller.state(),
     4112                                                        selection = state.get( 'selection' ),
     4113                                                        attachment = selection.single();
    40994114
    4100                                                 if ( removed.length ) {
    4101                                                         selection.remove( removed );
    4102                                                 }
     4115                                                controller.close();
    41034116
    4104                                                 if ( destroy.length ) {
    4105                                                         $.when.apply( null, destroy.map( function (item) {
    4106                                                                 return item.destroy();
    4107                                                         } ) ).then( _.bind( function() {
    4108                                                                 this.controller.trigger( 'selection:action:done' );
    4109                                                         }, this ) );
    4110                                                 }
     4117                                                controller.image.changeAttachment( attachment, state.display( attachment ) );
     4118
     4119                                                // not sure if we want to use wp.media.string.image which will create a shortcode or
     4120                                                // perhaps wp.html.string to at least to build the <img />
     4121                                                state.trigger( 'replace', controller.image.toJSON() );
     4122
     4123                                                // Restore and reset the default state.
     4124                                                controller.setState( controller.options.state );
     4125                                                controller.reset();
    41114126                                        }
    4112                                 }).render() );
     4127                                }
    41134128                        }
     4129                }) );
     4130        }
    41144131
    4115                 } else if ( this.options.date ) {
    4116                         // DateFilter is a <select>, screen reader text needs to be rendered before
    4117                         this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
    4118                                 value: l10n.filterByDate,
    4119                                 attributes: {
    4120                                         'for': 'media-attachment-date-filters'
    4121                                 },
    4122                                 priority: -75
    4123                         }).render() );
    4124                         this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
    4125                                 controller: this.controller,
    4126                                 model:      this.collection.props,
    4127                                 priority: -75
    4128                         }).render() );
    4129                 }
     4132});
    41304133
    4131                 if ( this.options.search ) {
    4132                         // Search is an input, screen reader text needs to be rendered before
    4133                         this.toolbar.set( 'searchLabel', new wp.media.view.Label({
    4134                                 value: l10n.searchMediaLabel,
    4135                                 attributes: {
    4136                                         'for': 'media-search-input'
    4137                                 },
    4138                                 priority:   60
    4139                         }).render() );
    4140                         this.toolbar.set( 'search', new wp.media.view.Search({
    4141                                 controller: this.controller,
    4142                                 model:      this.collection.props,
    4143                                 priority:   60
    4144                         }).render() );
    4145                 }
     4134module.exports = ImageDetails;
    41464135
    4147                 if ( this.options.dragInfo ) {
    4148                         this.toolbar.set( 'dragInfo', new View({
    4149                                 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
    4150                                 priority: -40
    4151                         }) );
    4152                 }
    41534136
    4154                 if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
    4155                         this.toolbar.set( 'suggestedDimensions', new View({
    4156                                 el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0],
    4157                                 priority: -40
    4158                         }) );
    4159                 }
    4160         },
     4137/***/ }),
     4138/* 51 */
     4139/***/ (function(module, exports) {
    41614140
    4162         updateContent: function() {
    4163                 var view = this,
    4164                         noItemsView;
     4141var $ = jQuery,
     4142        Modal;
    41654143
    4166                 if ( this.controller.isModeActive( 'grid' ) ) {
    4167                         noItemsView = view.attachmentsNoResults;
    4168                 } else {
    4169                         noItemsView = view.uploader;
    4170                 }
     4144/**
     4145 * wp.media.view.Modal
     4146 *
     4147 * A modal view, which the media modal uses as its default container.
     4148 *
     4149 * @memberOf wp.media.view
     4150 *
     4151 * @class
     4152 * @augments wp.media.View
     4153 * @augments wp.Backbone.View
     4154 * @augments Backbone.View
     4155 */
     4156Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{
     4157        tagName:  'div',
     4158        template: wp.template('media-modal'),
    41714159
    4172                 if ( ! this.collection.length ) {
    4173                         this.toolbar.get( 'spinner' ).show();
    4174                         this.dfd = this.collection.more().done( function() {
    4175                                 if ( ! view.collection.length ) {
    4176                                         noItemsView.$el.removeClass( 'hidden' );
    4177                                 } else {
    4178                                         noItemsView.$el.addClass( 'hidden' );
    4179                                 }
    4180                                 view.toolbar.get( 'spinner' ).hide();
    4181                         } );
    4182                 } else {
    4183                         noItemsView.$el.addClass( 'hidden' );
    4184                         view.toolbar.get( 'spinner' ).hide();
    4185                 }
     4160        attributes: {
     4161                tabindex: 0
    41864162        },
    41874163
    4188         createUploader: function() {
    4189                 this.uploader = new wp.media.view.UploaderInline({
    4190                         controller: this.controller,
    4191                         status:     false,
    4192                         message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
    4193                         canClose:   this.controller.isModeActive( 'grid' )
     4164        events: {
     4165                'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
     4166                'keydown': 'keydown'
     4167        },
     4168
     4169        clickedOpenerEl: null,
     4170
     4171        initialize: function() {
     4172                _.defaults( this.options, {
     4173                        container: document.body,
     4174                        title:     '',
     4175                        propagate: true,
     4176                        freeze:    true
    41944177                });
    41954178
    4196                 this.uploader.$el.addClass( 'hidden' );
    4197                 this.views.add( this.uploader );
     4179                this.focusManager = new wp.media.view.FocusManager({
     4180                        el: this.el
     4181                });
     4182        },
     4183        /**
     4184         * @returns {Object}
     4185         */
     4186        prepare: function() {
     4187                return {
     4188                        title: this.options.title
     4189                };
    41984190        },
    41994191
    4200         toggleUploader: function() {
    4201                 if ( this.uploader.$el.hasClass( 'hidden' ) ) {
    4202                         this.uploader.show();
    4203                 } else {
    4204                         this.uploader.hide();
     4192        /**
     4193         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4194         */
     4195        attach: function() {
     4196                if ( this.views.attached ) {
     4197                        return this;
    42054198                }
    4206         },
    42074199
    4208         createAttachments: function() {
    4209                 this.attachments = new wp.media.view.Attachments({
    4210                         controller:           this.controller,
    4211                         collection:           this.collection,
    4212                         selection:            this.options.selection,
    4213                         model:                this.model,
    4214                         sortable:             this.options.sortable,
    4215                         scrollElement:        this.options.scrollElement,
    4216                         idealColumnWidth:     this.options.idealColumnWidth,
     4200                if ( ! this.views.rendered ) {
     4201                        this.render();
     4202                }
    42174203
    4218                         // The single `Attachment` view to be used in the `Attachments` view.
    4219                         AttachmentView: this.options.AttachmentView
    4220                 });
     4204                this.$el.appendTo( this.options.container );
    42214205
    4222                 // Add keydown listener to the instance of the Attachments view
    4223                 this.controller.on( 'attachment:keydown:arrow',     _.bind( this.attachments.arrowEvent, this.attachments ) );
    4224                 this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) );
     4206                // Manually mark the view as attached and trigger ready.
     4207                this.views.attached = true;
     4208                this.views.ready();
    42254209
    4226                 this.views.add( this.attachments );
     4210                return this.propagate('attach');
     4211        },
     4212
     4213        /**
     4214         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4215         */
     4216        detach: function() {
     4217                if ( this.$el.is(':visible') ) {
     4218                        this.close();
     4219                }
     4220
     4221                this.$el.detach();
     4222                this.views.attached = false;
     4223                return this.propagate('detach');
     4224        },
     4225
     4226        /**
     4227         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4228         */
     4229        open: function() {
     4230                var $el = this.$el,
     4231                        options = this.options,
     4232                        mceEditor;
    42274233
     4234                if ( $el.is(':visible') ) {
     4235                        return this;
     4236                }
    42284237
    4229                 if ( this.controller.isModeActive( 'grid' ) ) {
    4230                         this.attachmentsNoResults = new View({
    4231                                 controller: this.controller,
    4232                                 tagName: 'p'
    4233                         });
     4238                this.clickedOpenerEl = document.activeElement;
    42344239
    4235                         this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
    4236                         this.attachmentsNoResults.$el.html( l10n.noMedia );
     4240                if ( ! this.views.attached ) {
     4241                        this.attach();
     4242                }
    42374243
    4238                         this.views.add( this.attachmentsNoResults );
     4244                // If the `freeze` option is set, record the window's scroll position.
     4245                if ( options.freeze ) {
     4246                        this._freeze = {
     4247                                scrollTop: $( window ).scrollTop()
     4248                        };
    42394249                }
    4240         },
    42414250
    4242         createSidebar: function() {
    4243                 var options = this.options,
    4244                         selection = options.selection,
    4245                         sidebar = this.sidebar = new wp.media.view.Sidebar({
    4246                                 controller: this.controller
    4247                         });
     4251                // Disable page scrolling.
     4252                $( 'body' ).addClass( 'modal-open' );
    42484253
    4249                 this.views.add( sidebar );
     4254                $el.show();
    42504255
    4251                 if ( this.controller.uploader ) {
    4252                         sidebar.set( 'uploads', new wp.media.view.UploaderStatus({
    4253                                 controller: this.controller,
    4254                                 priority:   40
    4255                         }) );
     4256                // Try to close the onscreen keyboard
     4257                if ( 'ontouchend' in document ) {
     4258                        if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
     4259                                mceEditor.iframeElement.focus();
     4260                                mceEditor.iframeElement.blur();
     4261
     4262                                setTimeout( function() {
     4263                                        mceEditor.iframeElement.blur();
     4264                                }, 100 );
     4265                        }
    42564266                }
    42574267
    4258                 selection.on( 'selection:single', this.createSingle, this );
    4259                 selection.on( 'selection:unsingle', this.disposeSingle, this );
     4268                this.$el.focus();
    42604269
    4261                 if ( selection.single() ) {
    4262                         this.createSingle();
    4263                 }
     4270                return this.propagate('open');
    42644271        },
    42654272
    4266         createSingle: function() {
    4267                 var sidebar = this.sidebar,
    4268                         single = this.options.selection.single();
     4273        /**
     4274         * @param {Object} options
     4275         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4276         */
     4277        close: function( options ) {
     4278                var freeze = this._freeze;
    42694279
    4270                 sidebar.set( 'details', new wp.media.view.Attachment.Details({
    4271                         controller: this.controller,
    4272                         model:      single,
    4273                         priority:   80
    4274                 }) );
     4280                if ( ! this.views.attached || ! this.$el.is(':visible') ) {
     4281                        return this;
     4282                }
    42754283
    4276                 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({
    4277                         controller: this.controller,
    4278                         model:      single,
    4279                         priority:   120
    4280                 }) );
     4284                // Enable page scrolling.
     4285                $( 'body' ).removeClass( 'modal-open' );
    42814286
    4282                 if ( this.options.display ) {
    4283                         sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({
    4284                                 controller:   this.controller,
    4285                                 model:        this.model.display( single ),
    4286                                 attachment:   single,
    4287                                 priority:     160,
    4288                                 userSettings: this.model.get('displayUserSettings')
    4289                         }) );
     4287                // Hide modal and remove restricted media modal tab focus once it's closed
     4288                this.$el.hide().undelegate( 'keydown' );
     4289
     4290                // Put focus back in useful location once modal is closed.
     4291                if ( null !== this.clickedOpenerEl ) {
     4292                        this.clickedOpenerEl.focus();
     4293                } else {
     4294                        $( '#wpbody-content' ).focus();
    42904295                }
    42914296
    4292                 // Show the sidebar on mobile
    4293                 if ( this.model.id === 'insert' ) {
    4294                         sidebar.$el.addClass( 'visible' );
     4297                this.propagate('close');
     4298
     4299                // If the `freeze` option is set, restore the container's scroll position.
     4300                if ( freeze ) {
     4301                        $( window ).scrollTop( freeze.scrollTop );
    42954302                }
    4296         },
    42974303
    4298         disposeSingle: function() {
    4299                 var sidebar = this.sidebar;
    4300                 sidebar.unset('details');
    4301                 sidebar.unset('compat');
    4302                 sidebar.unset('display');
    4303                 // Hide the sidebar on mobile
    4304                 sidebar.$el.removeClass( 'visible' );
    4305         }
    4306 });
     4304                if ( options && options.escape ) {
     4305                        this.propagate('escape');
     4306                }
    43074307
    4308 module.exports = AttachmentsBrowser;
     4308                return this;
     4309        },
     4310        /**
     4311         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4312         */
     4313        escape: function() {
     4314                return this.close({ escape: true });
     4315        },
     4316        /**
     4317         * @param {Object} event
     4318         */
     4319        escapeHandler: function( event ) {
     4320                event.preventDefault();
     4321                this.escape();
     4322        },
    43094323
    4310 },{}],33:[function(require,module,exports){
    4311 var Attachments = wp.media.view.Attachments,
    4312         Selection;
     4324        /**
     4325         * @param {Array|Object} content Views to register to '.media-modal-content'
     4326         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4327         */
     4328        content: function( content ) {
     4329                this.views.set( '.media-modal-content', content );
     4330                return this;
     4331        },
    43134332
    4314 /**
    4315  * wp.media.view.Attachments.Selection
    4316  *
    4317  * @memberOf wp.media.view.Attachments
    4318  *
    4319  * @class
    4320  * @augments wp.media.view.Attachments
    4321  * @augments wp.media.View
    4322  * @augments wp.Backbone.View
    4323  * @augments Backbone.View
    4324  */
    4325 Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{
    4326         events: {},
    4327         initialize: function() {
    4328                 _.defaults( this.options, {
    4329                         sortable:   false,
    4330                         resize:     false,
     4333        /**
     4334         * Triggers a modal event and if the `propagate` option is set,
     4335         * forwards events to the modal's controller.
     4336         *
     4337         * @param {string} id
     4338         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     4339         */
     4340        propagate: function( id ) {
     4341                this.trigger( id );
    43314342
    4332                         // The single `Attachment` view to be used in the `Attachments` view.
    4333                         AttachmentView: wp.media.view.Attachment.Selection
    4334                 });
    4335                 // Call 'initialize' directly on the parent class.
    4336                 return Attachments.prototype.initialize.apply( this, arguments );
     4343                if ( this.options.propagate ) {
     4344                        this.controller.trigger( id );
     4345                }
     4346
     4347                return this;
     4348        },
     4349        /**
     4350         * @param {Object} event
     4351         */
     4352        keydown: function( event ) {
     4353                // Close the modal when escape is pressed.
     4354                if ( 27 === event.which && this.$el.is(':visible') ) {
     4355                        this.escape();
     4356                        event.stopImmediatePropagation();
     4357                }
    43374358        }
    43384359});
    43394360
    4340 module.exports = Selection;
     4361module.exports = Modal;
    43414362
    4342 },{}],34:[function(require,module,exports){
    4343 var $ = Backbone.$,
    4344         ButtonGroup;
     4363
     4364/***/ }),
     4365/* 52 */
     4366/***/ (function(module, exports) {
    43454367
    43464368/**
    4347  * wp.media.view.ButtonGroup
     4369 * wp.media.view.FocusManager
    43484370 *
    43494371 * @memberOf wp.media.view
    43504372 *
    var $ = Backbone.$, 
    43534375 * @augments wp.Backbone.View
    43544376 * @augments Backbone.View
    43554377 */
    4356 ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{
    4357         tagName:   'div',
    4358         className: 'button-group button-large media-button-group',
    4359 
    4360         initialize: function() {
    4361                 /**
    4362                  * @member {wp.media.view.Button[]}
    4363                  */
    4364                 this.buttons = _.map( this.options.buttons || [], function( button ) {
    4365                         if ( button instanceof Backbone.View ) {
    4366                                 return button;
    4367                         } else {
    4368                                 return new wp.media.view.Button( button ).render();
    4369                         }
    4370                 });
    4371 
    4372                 delete this.options.buttons;
     4378var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{
    43734379
    4374                 if ( this.options.classes ) {
    4375                         this.$el.addClass( this.options.classes );
    4376                 }
     4380        events: {
     4381                'keydown': 'constrainTabbing'
    43774382        },
    43784383
     4384        focus: function() { // Reset focus on first left menu item
     4385                this.$('.media-menu-item').first().focus();
     4386        },
    43794387        /**
    4380          * @returns {wp.media.view.ButtonGroup}
     4388         * @param {Object} event
    43814389         */
    4382         render: function() {
    4383                 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
    4384                 return this;
     4390        constrainTabbing: function( event ) {
     4391                var tabbables;
     4392
     4393                // Look for the tab key.
     4394                if ( 9 !== event.keyCode ) {
     4395                        return;
     4396                }
     4397
     4398                // Skip the file input added by Plupload.
     4399                tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
     4400
     4401                // Keep tab focus within media modal while it's open
     4402                if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
     4403                        tabbables.first().focus();
     4404                        return false;
     4405                } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
     4406                        tabbables.last().focus();
     4407                        return false;
     4408                }
    43854409        }
     4410
    43864411});
    43874412
    4388 module.exports = ButtonGroup;
     4413module.exports = FocusManager;
     4414
     4415
     4416/***/ }),
     4417/* 53 */
     4418/***/ (function(module, exports) {
     4419
     4420var $ = jQuery,
     4421        UploaderWindow;
    43894422
    4390 },{}],35:[function(require,module,exports){
    43914423/**
    4392  * wp.media.view.Button
     4424 * wp.media.view.UploaderWindow
     4425 *
     4426 * An uploader window that allows for dragging and dropping media.
    43934427 *
    43944428 * @memberOf wp.media.view
    43954429 *
    module.exports = ButtonGroup; 
    43974431 * @augments wp.media.View
    43984432 * @augments wp.Backbone.View
    43994433 * @augments Backbone.View
     4434 *
     4435 * @param {object} [options]                   Options hash passed to the view.
     4436 * @param {object} [options.uploader]          Uploader properties.
     4437 * @param {jQuery} [options.uploader.browser]
     4438 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
     4439 * @param {object} [options.uploader.params]
    44004440 */
    4401 var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{
    4402         tagName:    'button',
    4403         className:  'media-button',
    4404         attributes: { type: 'button' },
     4441UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{
     4442        tagName:   'div',
     4443        className: 'uploader-window',
     4444        template:  wp.template('uploader-window'),
    44054445
    4406         events: {
    4407                 'click': 'click'
    4408         },
     4446        initialize: function() {
     4447                var uploader;
    44094448
    4410         defaults: {
    4411                 text:     '',
    4412                 style:    '',
    4413                 size:     'large',
    4414                 disabled: false
    4415         },
     4449                this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' );
    44164450
    4417         initialize: function() {
    4418                 /**
    4419                  * Create a model with the provided `defaults`.
    4420                  *
    4421                  * @member {Backbone.Model}
    4422                  */
    4423                 this.model = new Backbone.Model( this.defaults );
     4451                uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
     4452                        dropzone:  this.$el,
     4453                        browser:   this.$browser,
     4454                        params:    {}
     4455                });
    44244456
    4425                 // If any of the `options` have a key from `defaults`, apply its
    4426                 // value to the `model` and remove it from the `options object.
    4427                 _.each( this.defaults, function( def, key ) {
    4428                         var value = this.options[ key ];
    4429                         if ( _.isUndefined( value ) ) {
    4430                                 return;
    4431                         }
     4457                // Ensure the dropzone is a jQuery collection.
     4458                if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
     4459                        uploader.dropzone = $( uploader.dropzone );
     4460                }
    44324461
    4433                         this.model.set( key, value );
    4434                         delete this.options[ key ];
     4462                this.controller.on( 'activate', this.refresh, this );
     4463
     4464                this.controller.on( 'detach', function() {
     4465                        this.$browser.remove();
    44354466                }, this );
     4467        },
    44364468
    4437                 this.listenTo( this.model, 'change', this.render );
     4469        refresh: function() {
     4470                if ( this.uploader ) {
     4471                        this.uploader.refresh();
     4472                }
    44384473        },
    4439         /**
    4440          * @returns {wp.media.view.Button} Returns itself to allow chaining
    4441          */
    4442         render: function() {
    4443                 var classes = [ 'button', this.className ],
    4444                         model = this.model.toJSON();
    44454474
    4446                 if ( model.style ) {
    4447                         classes.push( 'button-' + model.style );
     4475        ready: function() {
     4476                var postId = wp.media.view.settings.post.id,
     4477                        dropzone;
     4478
     4479                // If the uploader already exists, bail.
     4480                if ( this.uploader ) {
     4481                        return;
    44484482                }
    44494483
    4450                 if ( model.size ) {
    4451                         classes.push( 'button-' + model.size );
     4484                if ( postId ) {
     4485                        this.options.uploader.params.post_id = postId;
    44524486                }
     4487                this.uploader = new wp.Uploader( this.options.uploader );
    44534488
    4454                 classes = _.uniq( classes.concat( this.options.classes ) );
    4455                 this.el.className = classes.join(' ');
     4489                dropzone = this.uploader.dropzone;
     4490                dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
     4491                dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
    44564492
    4457                 this.$el.attr( 'disabled', model.disabled );
    4458                 this.$el.text( this.model.get('text') );
     4493                $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
     4494        },
    44594495
    4460                 return this;
     4496        _ready: function() {
     4497                this.controller.trigger( 'uploader:ready' );
    44614498        },
    4462         /**
    4463          * @param {Object} event
    4464          */
    4465         click: function( event ) {
    4466                 if ( '#' === this.attributes.href ) {
    4467                         event.preventDefault();
    4468                 }
    44694499
    4470                 if ( this.options.click && ! this.model.get('disabled') ) {
    4471                         this.options.click.apply( this, arguments );
    4472                 }
     4500        show: function() {
     4501                var $el = this.$el.show();
     4502
     4503                // Ensure that the animation is triggered by waiting until
     4504                // the transparent element is painted into the DOM.
     4505                _.defer( function() {
     4506                        $el.css({ opacity: 1 });
     4507                });
     4508        },
     4509
     4510        hide: function() {
     4511                var $el = this.$el.css({ opacity: 0 });
     4512
     4513                wp.media.transition( $el ).done( function() {
     4514                        // Transition end events are subject to race conditions.
     4515                        // Make sure that the value is set as intended.
     4516                        if ( '0' === $el.css('opacity') ) {
     4517                                $el.hide();
     4518                        }
     4519                });
     4520
     4521                // https://core.trac.wordpress.org/ticket/27341
     4522                _.delay( function() {
     4523                        if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
     4524                                $el.hide();
     4525                        }
     4526                }, 500 );
    44734527        }
    44744528});
    44754529
    4476 module.exports = Button;
     4530module.exports = UploaderWindow;
     4531
     4532
     4533/***/ }),
     4534/* 54 */
     4535/***/ (function(module, exports) {
    44774536
    4478 },{}],36:[function(require,module,exports){
    44794537var View = wp.media.View,
    4480         UploaderStatus = wp.media.view.UploaderStatus,
    44814538        l10n = wp.media.view.l10n,
    44824539        $ = jQuery,
    4483         Cropper;
     4540        EditorUploader;
    44844541
    44854542/**
    4486  * wp.media.view.Cropper
    4487  *
    4488  * Uses the imgAreaSelect plugin to allow a user to crop an image.
     4543 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
     4544 * and relays drag'n'dropped files to a media workflow.
    44894545 *
    4490  * Takes imgAreaSelect options from
    4491  * wp.customize.HeaderControl.calculateImageSelectOptions via
    4492  * wp.customize.HeaderControl.openMM.
     4546 * wp.media.view.EditorUploader
    44934547 *
    44944548 * @memberOf wp.media.view
    44954549 *
    var View = wp.media.View, 
    44984552 * @augments wp.Backbone.View
    44994553 * @augments Backbone.View
    45004554 */
    4501 Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{
    4502         className: 'crop-content',
    4503         template: wp.template('crop-content'),
     4555EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{
     4556        tagName:   'div',
     4557        className: 'uploader-editor',
     4558        template:  wp.template( 'uploader-editor' ),
     4559
     4560        localDrag: false,
     4561        overContainer: false,
     4562        overDropzone: false,
     4563        draggingFile: null,
     4564
     4565        /**
     4566         * Bind drag'n'drop events to callbacks.
     4567         */
    45044568        initialize: function() {
    4505                 _.bindAll(this, 'onImageLoad');
    4506         },
    4507         ready: function() {
    4508                 this.controller.frame.on('content:error:crop', this.onError, this);
    4509                 this.$image = this.$el.find('.crop-image');
    4510                 this.$image.on('load', this.onImageLoad);
    4511                 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
    4512         },
    4513         remove: function() {
    4514                 $(window).off('resize.cropper');
    4515                 this.$el.remove();
    4516                 this.$el.off();
    4517                 View.prototype.remove.apply(this, arguments);
    4518         },
    4519         prepare: function() {
    4520                 return {
    4521                         title: l10n.cropYourImage,
    4522                         url: this.options.attachment.get('url')
    4523                 };
    4524         },
    4525         onImageLoad: function() {
    4526                 var imgOptions = this.controller.get('imgSelectOptions'),
    4527                         imgSelect;
     4569                this.initialized = false;
    45284570
    4529                 if (typeof imgOptions === 'function') {
    4530                         imgOptions = imgOptions(this.options.attachment, this.controller);
     4571                // Bail if not enabled or UA does not support drag'n'drop or File API.
     4572                if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
     4573                        return this;
    45314574                }
    45324575
    4533                 imgOptions = _.extend(imgOptions, {
    4534                         parent: this.$el,
    4535                         onInit: function() {
    4536                                 this.parent.children().on( 'mousedown touchstart', function( e ){
     4576                this.$document = $(document);
     4577                this.dropzones = [];
     4578                this.files = [];
    45374579
    4538                                         if ( e.shiftKey ) {
    4539                                                 imgSelect.setOptions( {
    4540                                                         aspectRatio: '1:1'
    4541                                                 } );
    4542                                         } else {
    4543                                                 imgSelect.setOptions( {
    4544                                                         aspectRatio: false
    4545                                                 } );
    4546                                         }
    4547                                 } );
     4580                this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
     4581                this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
     4582                this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
     4583                this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
     4584
     4585                this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
     4586                this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
     4587
     4588                this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
     4589                        this.localDrag = event.type === 'dragstart';
     4590
     4591                        if ( event.type === 'drop' ) {
     4592                                this.containerDragleave();
    45484593                        }
    4549                 } );
    4550                 this.trigger('image-loaded');
    4551                 imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
     4594                }, this ) );
     4595
     4596                this.initialized = true;
     4597                return this;
    45524598        },
    4553         onError: function() {
    4554                 var filename = this.options.attachment.get('filename');
    45554599
    4556                 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    4557                         filename: UploaderStatus.prototype.filename(filename),
    4558                         message: window._wpMediaViewsL10n.cropError
    4559                 }), { at: 0 });
    4560         }
    4561 });
     4600        /**
     4601         * Check browser support for drag'n'drop.
     4602         *
     4603         * @return Boolean
     4604         */
     4605        browserSupport: function() {
     4606                var supports = false, div = document.createElement('div');
     4607
     4608                supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
     4609                supports = supports && !! ( window.File && window.FileList && window.FileReader );
     4610                return supports;
     4611        },
     4612
     4613        isDraggingFile: function( event ) {
     4614                if ( this.draggingFile !== null ) {
     4615                        return this.draggingFile;
     4616                }
     4617
     4618                if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
     4619                        return false;
     4620                }
     4621
     4622                this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
     4623                        _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
    45624624
    4563 module.exports = Cropper;
     4625                return this.draggingFile;
     4626        },
    45644627
    4565 },{}],37:[function(require,module,exports){
    4566 var View = wp.media.View,
    4567         EditImage;
     4628        refresh: function( e ) {
     4629                var dropzone_id;
     4630                for ( dropzone_id in this.dropzones ) {
     4631                        // Hide the dropzones only if dragging has left the screen.
     4632                        this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
     4633                }
    45684634
    4569 /**
    4570  * wp.media.view.EditImage
    4571  *
    4572  * @memberOf wp.media.view
    4573  *
    4574  * @class
    4575  * @augments wp.media.View
    4576  * @augments wp.Backbone.View
    4577  * @augments Backbone.View
    4578  */
    4579 EditImage = View.extend(/** @lends wp.media.view.EditImage.prototype */{
    4580         className: 'image-editor',
    4581         template: wp.template('image-editor'),
     4635                if ( ! _.isUndefined( e ) ) {
     4636                        $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
     4637                }
    45824638
    4583         initialize: function( options ) {
    4584                 this.editor = window.imageEdit;
    4585                 this.controller = options.controller;
    4586                 View.prototype.initialize.apply( this, arguments );
    4587         },
     4639                if ( ! this.overContainer && ! this.overDropzone ) {
     4640                        this.draggingFile = null;
     4641                }
    45884642
    4589         prepare: function() {
    4590                 return this.model.toJSON();
     4643                return this;
    45914644        },
    45924645
    4593         loadEditor: function() {
    4594                 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
    4595                 dfd.done( _.bind( this.focus, this ) );
    4596         },
     4646        render: function() {
     4647                if ( ! this.initialized ) {
     4648                        return this;
     4649                }
    45974650
    4598         focus: function() {
    4599                 this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
     4651                View.prototype.render.apply( this, arguments );
     4652                $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
     4653                return this;
    46004654        },
    46014655
    4602         back: function() {
    4603                 var lastState = this.controller.lastState();
    4604                 this.controller.setState( lastState );
     4656        attach: function( index, editor ) {
     4657                // Attach a dropzone to an editor.
     4658                var dropzone = this.$el.clone();
     4659                this.dropzones.push( dropzone );
     4660                $( editor ).append( dropzone );
     4661                return this;
    46054662        },
    46064663
    4607         refresh: function() {
    4608                 this.model.fetch();
    4609         },
     4664        /**
     4665         * When a file is dropped on the editor uploader, open up an editor media workflow
     4666         * and upload the file immediately.
     4667         *
     4668         * @param  {jQuery.Event} event The 'drop' event.
     4669         */
     4670        drop: function( event ) {
     4671                var $wrap, uploadView;
    46104672
    4611         save: function() {
    4612                 var lastState = this.controller.lastState();
     4673                this.containerDragleave( event );
     4674                this.dropzoneDragleave( event );
    46134675
    4614                 this.model.fetch().done( _.bind( function() {
    4615                         this.controller.setState( lastState );
    4616                 }, this ) );
    4617         }
     4676                this.files = event.originalEvent.dataTransfer.files;
     4677                if ( this.files.length < 1 ) {
     4678                        return;
     4679                }
    46184680
    4619 });
     4681                // Set the active editor to the drop target.
     4682                $wrap = $( event.target ).parents( '.wp-editor-wrap' );
     4683                if ( $wrap.length > 0 && $wrap[0].id ) {
     4684                        window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
     4685                }
    46204686
    4621 module.exports = EditImage;
     4687                if ( ! this.workflow ) {
     4688                        this.workflow = wp.media.editor.open( window.wpActiveEditor, {
     4689                                frame:    'post',
     4690                                state:    'insert',
     4691                                title:    l10n.addMedia,
     4692                                multiple: true
     4693                        });
    46224694
    4623 },{}],38:[function(require,module,exports){
    4624 /**
    4625  * wp.media.view.Embed
    4626  *
    4627  * @memberOf wp.media.view
    4628  *
    4629  * @class
    4630  * @augments wp.media.View
    4631  * @augments wp.Backbone.View
    4632  * @augments Backbone.View
    4633  */
    4634 var Embed = wp.media.View.extend(/** @lends wp.media.view.Ember.prototype */{
    4635         className: 'media-embed',
     4695                        uploadView = this.workflow.uploader;
    46364696
    4637         initialize: function() {
    4638                 /**
    4639                  * @member {wp.media.view.EmbedUrl}
    4640                  */
    4641                 this.url = new wp.media.view.EmbedUrl({
    4642                         controller: this.controller,
    4643                         model:      this.model.props
    4644                 }).render();
     4697                        if ( uploadView.uploader && uploadView.uploader.ready ) {
     4698                                this.addFiles.apply( this );
     4699                        } else {
     4700                                this.workflow.on( 'uploader:ready', this.addFiles, this );
     4701                        }
     4702                } else {
     4703                        this.workflow.state().reset();
     4704                        this.addFiles.apply( this );
     4705                        this.workflow.open();
     4706                }
    46454707
    4646                 this.views.set([ this.url ]);
    4647                 this.refresh();
    4648                 this.listenTo( this.model, 'change:type', this.refresh );
    4649                 this.listenTo( this.model, 'change:loading', this.loading );
     4708                return false;
    46504709        },
    46514710
    46524711        /**
    4653          * @param {Object} view
     4712         * Add the files to the uploader.
    46544713         */
    4655         settings: function( view ) {
    4656                 if ( this._settings ) {
    4657                         this._settings.remove();
     4714        addFiles: function() {
     4715                if ( this.files.length ) {
     4716                        this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
     4717                        this.files = [];
    46584718                }
    4659                 this._settings = view;
    4660                 this.views.add( view );
     4719                return this;
    46614720        },
    46624721
    4663         refresh: function() {
    4664                 var type = this.model.get('type'),
    4665                         constructor;
    4666 
    4667                 if ( 'image' === type ) {
    4668                         constructor = wp.media.view.EmbedImage;
    4669                 } else if ( 'link' === type ) {
    4670                         constructor = wp.media.view.EmbedLink;
    4671                 } else {
     4722        containerDragover: function( event ) {
     4723                if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    46724724                        return;
    46734725                }
    46744726
    4675                 this.settings( new constructor({
    4676                         controller: this.controller,
    4677                         model:      this.model.props,
    4678                         priority:   40
    4679                 }) );
     4727                this.overContainer = true;
     4728                this.refresh();
    46804729        },
    46814730
    4682         loading: function() {
    4683                 this.$el.toggleClass( 'embed-loading', this.model.get('loading') );
    4684         }
    4685 });
     4731        containerDragleave: function() {
     4732                this.overContainer = false;
    46864733
    4687 module.exports = Embed;
     4734                // Throttle dragleave because it's called when bouncing from some elements to others.
     4735                _.delay( _.bind( this.refresh, this ), 50 );
     4736        },
    46884737
    4689 },{}],39:[function(require,module,exports){
    4690 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    4691         EmbedImage;
     4738        dropzoneDragover: function( event ) {
     4739                if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     4740                        return;
     4741                }
    46924742
    4693 /**
    4694  * wp.media.view.EmbedImage
    4695  *
    4696  * @memberOf wp.media.view
    4697  *
    4698  * @class
    4699  * @augments wp.media.view.Settings.AttachmentDisplay
    4700  * @augments wp.media.view.Settings
    4701  * @augments wp.media.View
    4702  * @augments wp.Backbone.View
    4703  * @augments Backbone.View
    4704  */
    4705 EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{
    4706         className: 'embed-media-settings',
    4707         template:  wp.template('embed-image-settings'),
     4743                this.overDropzone = true;
     4744                this.refresh( event );
     4745                return false;
     4746        },
    47084747
    4709         initialize: function() {
    4710                 /**
    4711                  * Call `initialize` directly on parent class with passed arguments
    4712                  */
    4713                 AttachmentDisplay.prototype.initialize.apply( this, arguments );
    4714                 this.listenTo( this.model, 'change:url', this.updateImage );
     4748        dropzoneDragleave: function( e ) {
     4749                this.overDropzone = false;
     4750                _.delay( _.bind( this.refresh, this, e ), 50 );
    47154751        },
    47164752
    4717         updateImage: function() {
    4718                 this.$('img').attr( 'src', this.model.get('url') );
     4753        click: function( e ) {
     4754                // In the rare case where the dropzone gets stuck, hide it on click.
     4755                this.containerDragleave( e );
     4756                this.dropzoneDragleave( e );
     4757                this.localDrag = false;
    47194758        }
    47204759});
    47214760
    4722 module.exports = EmbedImage;
     4761module.exports = EditorUploader;
    47234762
    4724 },{}],40:[function(require,module,exports){
    4725 var $ = jQuery,
    4726         EmbedLink;
     4763
     4764/***/ }),
     4765/* 55 */
     4766/***/ (function(module, exports) {
     4767
     4768var View = wp.media.View,
     4769        UploaderInline;
    47274770
    47284771/**
    4729  * wp.media.view.EmbedLink
     4772 * wp.media.view.UploaderInline
     4773 *
     4774 * The inline uploader that shows up in the 'Upload Files' tab.
    47304775 *
    47314776 * @memberOf wp.media.view
    47324777 *
    47334778 * @class
    4734  * @augments wp.media.view.Settings
    47354779 * @augments wp.media.View
    47364780 * @augments wp.Backbone.View
    47374781 * @augments Backbone.View
    47384782 */
    4739 EmbedLink = wp.media.view.Settings.extend(/** @lends wp.media.view.EmbedLink.prototype */{
    4740         className: 'embed-link-settings',
    4741         template:  wp.template('embed-link-settings'),
     4783UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{
     4784        tagName:   'div',
     4785        className: 'uploader-inline',
     4786        template:  wp.template('uploader-inline'),
    47424787
    4743         initialize: function() {
    4744                 this.listenTo( this.model, 'change:url', this.updateoEmbed );
     4788        events: {
     4789                'click .close': 'hide'
    47454790        },
    47464791
    4747         updateoEmbed: _.debounce( function() {
    4748                 var url = this.model.get( 'url' );
     4792        initialize: function() {
     4793                _.defaults( this.options, {
     4794                        message: '',
     4795                        status:  true,
     4796                        canClose: false
     4797                });
    47494798
    4750                 // clear out previous results
    4751                 this.$('.embed-container').hide().find('.embed-preview').empty();
    4752                 this.$( '.setting' ).hide();
     4799                if ( ! this.options.$browser && this.controller.uploader ) {
     4800                        this.options.$browser = this.controller.uploader.$browser;
     4801                }
     4802
     4803                if ( _.isUndefined( this.options.postId ) ) {
     4804                        this.options.postId = wp.media.view.settings.post.id;
     4805                }
    47534806
    4754                 // only proceed with embed if the field contains more than 11 characters
    4755                 // Example: http://a.io is 11 chars
    4756                 if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {
    4757                         return;
     4807                if ( this.options.status ) {
     4808                        this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
     4809                                controller: this.controller
     4810                        }) );
    47584811                }
     4812        },
    47594813
    4760                 this.fetch();
    4761         }, wp.media.controller.Embed.sensitivity ),
     4814        prepare: function() {
     4815                var suggestedWidth = this.controller.state().get('suggestedWidth'),
     4816                        suggestedHeight = this.controller.state().get('suggestedHeight'),
     4817                        data = {};
    47624818
    4763         fetch: function() {
    4764                 var url = this.model.get( 'url' ), re, youTubeEmbedMatch;
     4819                data.message = this.options.message;
     4820                data.canClose = this.options.canClose;
    47654821
    4766                 // check if they haven't typed in 500 ms
    4767                 if ( $('#embed-url-field').val() !== url ) {
    4768                         return;
     4822                if ( suggestedWidth && suggestedHeight ) {
     4823                        data.suggestedWidth = suggestedWidth;
     4824                        data.suggestedHeight = suggestedHeight;
    47694825                }
    47704826
    4771                 if ( this.dfd && 'pending' === this.dfd.state() ) {
    4772                         this.dfd.abort();
     4827                return data;
     4828        },
     4829        /**
     4830         * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
     4831         */
     4832        dispose: function() {
     4833                if ( this.disposing ) {
     4834                        /**
     4835                         * call 'dispose' directly on the parent class
     4836                         */
     4837                        return View.prototype.dispose.apply( this, arguments );
    47734838                }
    47744839
    4775                 // Support YouTube embed urls, since they work once in the editor.
    4776                 re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/;
    4777                 youTubeEmbedMatch = re.exec( url );
    4778                 if ( youTubeEmbedMatch ) {
    4779                         url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ];
    4780                 }
     4840                // Run remove on `dispose`, so we can be sure to refresh the
     4841                // uploader with a view-less DOM. Track whether we're disposing
     4842                // so we don't trigger an infinite loop.
     4843                this.disposing = true;
     4844                return this.remove();
     4845        },
     4846        /**
     4847         * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
     4848         */
     4849        remove: function() {
     4850                /**
     4851                 * call 'remove' directly on the parent class
     4852                 */
     4853                var result = View.prototype.remove.apply( this, arguments );
    47814854
    4782                 this.dfd = wp.apiRequest({
    4783                         url: wp.media.view.settings.oEmbedProxyUrl,
    4784                         data: {
    4785                                 url: url,
    4786                                 maxwidth: this.model.get( 'width' ),
    4787                                 maxheight: this.model.get( 'height' )
    4788                         },
    4789                         type: 'GET',
    4790                         dataType: 'json',
    4791                         context: this
    4792                 })
    4793                         .done( function( response ) {
    4794                                 this.renderoEmbed( {
    4795                                         data: {
    4796                                                 body: response.html || ''
    4797                                         }
    4798                                 } );
    4799                         } )
    4800                         .fail( this.renderFail );
     4855                _.defer( _.bind( this.refresh, this ) );
     4856                return result;
    48014857        },
    48024858
    4803         renderFail: function ( response, status ) {
    4804                 if ( 'abort' === status ) {
    4805                         return;
     4859        refresh: function() {
     4860                var uploader = this.controller.uploader;
     4861
     4862                if ( uploader ) {
     4863                        uploader.refresh();
    48064864                }
    4807                 this.$( '.link-text' ).show();
    48084865        },
     4866        /**
     4867         * @returns {wp.media.view.UploaderInline}
     4868         */
     4869        ready: function() {
     4870                var $browser = this.options.$browser,
     4871                        $placeholder;
    48094872
    4810         renderoEmbed: function( response ) {
    4811                 var html = ( response && response.data && response.data.body ) || '';
     4873                if ( this.controller.uploader ) {
     4874                        $placeholder = this.$('.browser');
    48124875
    4813                 if ( html ) {
    4814                         this.$('.embed-container').show().find('.embed-preview').html( html );
    4815                 } else {
    4816                         this.renderFail();
     4876                        // Check if we've already replaced the placeholder.
     4877                        if ( $placeholder[0] === $browser[0] ) {
     4878                                return;
     4879                        }
     4880
     4881                        $browser.detach().text( $placeholder.text() );
     4882                        $browser[0].className = $placeholder[0].className;
     4883                        $placeholder.replaceWith( $browser.show() );
     4884                }
     4885
     4886                this.refresh();
     4887                return this;
     4888        },
     4889        show: function() {
     4890                this.$el.removeClass( 'hidden' );
     4891                if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
     4892                        this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' );
     4893                }
     4894        },
     4895        hide: function() {
     4896                this.$el.addClass( 'hidden' );
     4897                if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
     4898                        this.controller.$uploaderToggler
     4899                                .attr( 'aria-expanded', 'false' )
     4900                                // Move focus back to the toggle button when closing the uploader.
     4901                                .focus();
    48174902                }
    48184903        }
     4904
    48194905});
    48204906
    4821 module.exports = EmbedLink;
     4907module.exports = UploaderInline;
     4908
     4909
     4910/***/ }),
     4911/* 56 */
     4912/***/ (function(module, exports) {
    48224913
    4823 },{}],41:[function(require,module,exports){
    48244914var View = wp.media.View,
    4825         $ = jQuery,
    4826         EmbedUrl;
     4915        UploaderStatus;
    48274916
    48284917/**
    4829  * wp.media.view.EmbedUrl
     4918 * wp.media.view.UploaderStatus
     4919 *
     4920 * An uploader status for on-going uploads.
    48304921 *
    48314922 * @memberOf wp.media.view
    48324923 *
    var View = wp.media.View, 
    48354926 * @augments wp.Backbone.View
    48364927 * @augments Backbone.View
    48374928 */
    4838 EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{
    4839         tagName:   'label',
    4840         className: 'embed-url',
     4929UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{
     4930        className: 'media-uploader-status',
     4931        template:  wp.template('uploader-status'),
    48414932
    48424933        events: {
    4843                 'input':  'url',
    4844                 'keyup':  'url',
    4845                 'change': 'url'
     4934                'click .upload-dismiss-errors': 'dismiss'
    48464935        },
    48474936
    48484937        initialize: function() {
    4849                 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
    4850                 this.input = this.$input[0];
    4851 
    4852                 this.spinner = $('<span class="spinner" />')[0];
    4853                 this.$el.append([ this.input, this.spinner ]);
    4854 
    4855                 this.listenTo( this.model, 'change:url', this.render );
     4938                this.queue = wp.Uploader.queue;
     4939                this.queue.on( 'add remove reset', this.visibility, this );
     4940                this.queue.on( 'add remove reset change:percent', this.progress, this );
     4941                this.queue.on( 'add remove reset change:uploading', this.info, this );
    48564942
    4857                 if ( this.model.get( 'url' ) ) {
    4858                         _.delay( _.bind( function () {
    4859                                 this.model.trigger( 'change:url' );
    4860                         }, this ), 500 );
    4861                 }
     4943                this.errors = wp.Uploader.errors;
     4944                this.errors.reset();
     4945                this.errors.on( 'add remove reset', this.visibility, this );
     4946                this.errors.on( 'add', this.error, this );
    48624947        },
    48634948        /**
    4864          * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
     4949         * @returns {wp.media.view.UploaderStatus}
    48654950         */
    4866         render: function() {
    4867                 var $input = this.$input;
    4868 
    4869                 if ( $input.is(':focus') ) {
    4870                         return;
    4871                 }
    4872 
    4873                 this.input.value = this.model.get('url') || 'http://';
     4951        dispose: function() {
     4952                wp.Uploader.queue.off( null, null, this );
    48744953                /**
    4875                  * Call `render` directly on parent class with passed arguments
     4954                 * call 'dispose' directly on the parent class
    48764955                 */
    4877                 View.prototype.render.apply( this, arguments );
     4956                View.prototype.dispose.apply( this, arguments );
    48784957                return this;
    48794958        },
    48804959
     4960        visibility: function() {
     4961                this.$el.toggleClass( 'uploading', !! this.queue.length );
     4962                this.$el.toggleClass( 'errors', !! this.errors.length );
     4963                this.$el.toggle( !! this.queue.length || !! this.errors.length );
     4964        },
     4965
    48814966        ready: function() {
    4882                 if ( ! wp.media.isTouchDevice ) {
    4883                         this.focus();
     4967                _.each({
     4968                        '$bar':      '.media-progress-bar div',
     4969                        '$index':    '.upload-index',
     4970                        '$total':    '.upload-total',
     4971                        '$filename': '.upload-filename'
     4972                }, function( selector, key ) {
     4973                        this[ key ] = this.$( selector );
     4974                }, this );
     4975
     4976                this.visibility();
     4977                this.progress();
     4978                this.info();
     4979        },
     4980
     4981        progress: function() {
     4982                var queue = this.queue,
     4983                        $bar = this.$bar;
     4984
     4985                if ( ! $bar || ! queue.length ) {
     4986                        return;
    48844987                }
     4988
     4989                $bar.width( ( queue.reduce( function( memo, attachment ) {
     4990                        if ( ! attachment.get('uploading') ) {
     4991                                return memo + 100;
     4992                        }
     4993
     4994                        var percent = attachment.get('percent');
     4995                        return memo + ( _.isNumber( percent ) ? percent : 100 );
     4996                }, 0 ) / queue.length ) + '%' );
    48854997        },
    48864998
    4887         url: function( event ) {
    4888                 this.model.set( 'url', $.trim( event.target.value ) );
     4999        info: function() {
     5000                var queue = this.queue,
     5001                        index = 0, active;
     5002
     5003                if ( ! queue.length ) {
     5004                        return;
     5005                }
     5006
     5007                active = this.queue.find( function( attachment, i ) {
     5008                        index = i;
     5009                        return attachment.get('uploading');
     5010                });
     5011
     5012                this.$index.text( index + 1 );
     5013                this.$total.text( queue.length );
     5014                this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
     5015        },
     5016        /**
     5017         * @param {string} filename
     5018         * @returns {string}
     5019         */
     5020        filename: function( filename ) {
     5021                return _.escape( filename );
     5022        },
     5023        /**
     5024         * @param {Backbone.Model} error
     5025         */
     5026        error: function( error ) {
     5027                this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     5028                        filename: this.filename( error.get('file').name ),
     5029                        message:  error.get('message')
     5030                }), { at: 0 });
    48895031        },
    48905032
    48915033        /**
    4892          * If the input is visible, focus and select its contents.
     5034         * @param {Object} event
    48935035         */
    4894         focus: function() {
    4895                 var $input = this.$input;
    4896                 if ( $input.is(':visible') ) {
    4897                         $input.focus()[0].select();
     5036        dismiss: function( event ) {
     5037                var errors = this.views.get('.upload-errors');
     5038
     5039                event.preventDefault();
     5040
     5041                if ( errors ) {
     5042                        _.invoke( errors, 'remove' );
    48985043                }
     5044                wp.Uploader.errors.reset();
    48995045        }
    49005046});
    49015047
    4902 module.exports = EmbedUrl;
     5048module.exports = UploaderStatus;
     5049
     5050
     5051/***/ }),
     5052/* 57 */
     5053/***/ (function(module, exports) {
    49035054
    4904 },{}],42:[function(require,module,exports){
    49055055/**
    4906  * wp.media.view.FocusManager
     5056 * wp.media.view.UploaderStatusError
    49075057 *
    49085058 * @memberOf wp.media.view
    49095059 *
    module.exports = EmbedUrl; 
    49125062 * @augments wp.Backbone.View
    49135063 * @augments Backbone.View
    49145064 */
    4915 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{
    4916 
    4917         events: {
    4918                 'keydown': 'constrainTabbing'
    4919         },
    4920 
    4921         focus: function() { // Reset focus on first left menu item
    4922                 this.$('.media-menu-item').first().focus();
    4923         },
    4924         /**
    4925          * @param {Object} event
    4926          */
    4927         constrainTabbing: function( event ) {
    4928                 var tabbables;
    4929 
    4930                 // Look for the tab key.
    4931                 if ( 9 !== event.keyCode ) {
    4932                         return;
    4933                 }
     5065var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{
     5066        className: 'upload-error',
     5067        template:  wp.template('uploader-status-error')
     5068});
    49345069
    4935                 // Skip the file input added by Plupload.
    4936                 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
     5070module.exports = UploaderStatusError;
    49375071
    4938                 // Keep tab focus within media modal while it's open
    4939                 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
    4940                         tabbables.first().focus();
    4941                         return false;
    4942                 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
    4943                         tabbables.last().focus();
    4944                         return false;
    4945                 }
    4946         }
    49475072
    4948 });
     5073/***/ }),
     5074/* 58 */
     5075/***/ (function(module, exports) {
    49495076
    4950 module.exports = FocusManager;
     5077var View = wp.media.View,
     5078        Toolbar;
    49515079
    4952 },{}],43:[function(require,module,exports){
    49535080/**
    4954  * wp.media.view.Frame
     5081 * wp.media.view.Toolbar
    49555082 *
    4956  * A frame is a composite view consisting of one or more regions and one or more
    4957  * states.
     5083 * A toolbar which consists of a primary and a secondary section. Each sections
     5084 * can be filled with views.
    49585085 *
    49595086 * @memberOf wp.media.view
    49605087 *
    4961  * @see wp.media.controller.State
    4962  * @see wp.media.controller.Region
    4963  *
    49645088 * @class
    49655089 * @augments wp.media.View
    49665090 * @augments wp.Backbone.View
    49675091 * @augments Backbone.View
    4968  * @mixes wp.media.controller.StateMachine
    49695092 */
    4970 var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{
     5093Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{
     5094        tagName:   'div',
     5095        className: 'media-toolbar',
     5096
    49715097        initialize: function() {
    4972                 _.defaults( this.options, {
    4973                         mode: [ 'select' ]
    4974                 });
    4975                 this._createRegions();
    4976                 this._createStates();
    4977                 this._createModes();
    4978         },
     5098                var state = this.controller.state(),
     5099                        selection = this.selection = state.get('selection'),
     5100                        library = this.library = state.get('library');
    49795101
    4980         _createRegions: function() {
    4981                 // Clone the regions array.
    4982                 this.regions = this.regions ? this.regions.slice() : [];
     5102                this._views = {};
    49835103
    4984                 // Initialize regions.
    4985                 _.each( this.regions, function( region ) {
    4986                         this[ region ] = new wp.media.controller.Region({
    4987                                 view:     this,
    4988                                 id:       region,
    4989                                 selector: '.media-frame-' + region
    4990                         });
    4991                 }, this );
    4992         },
    4993         /**
    4994          * Create the frame's states.
    4995          *
    4996          * @see wp.media.controller.State
    4997          * @see wp.media.controller.StateMachine
    4998          *
    4999          * @fires wp.media.controller.State#ready
    5000          */
    5001         _createStates: function() {
    5002                 // Create the default `states` collection.
    5003                 this.states = new Backbone.Collection( null, {
    5004                         model: wp.media.controller.State
    5005                 });
     5104                // The toolbar is composed of two `PriorityList` views.
     5105                this.primary   = new wp.media.view.PriorityList();
     5106                this.secondary = new wp.media.view.PriorityList();
     5107                this.primary.$el.addClass('media-toolbar-primary search-form');
     5108                this.secondary.$el.addClass('media-toolbar-secondary');
    50065109
    5007                 // Ensure states have a reference to the frame.
    5008                 this.states.on( 'add', function( model ) {
    5009                         model.frame = this;
    5010                         model.trigger('ready');
    5011                 }, this );
     5110                this.views.set([ this.secondary, this.primary ]);
    50125111
    5013                 if ( this.options.states ) {
    5014                         this.states.add( this.options.states );
     5112                if ( this.options.items ) {
     5113                        this.set( this.options.items, { silent: true });
    50155114                }
    5016         },
    50175115
    5018         /**
    5019          * A frame can be in a mode or multiple modes at one time.
    5020          *
    5021          * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
    5022          */
    5023         _createModes: function() {
    5024                 // Store active "modes" that the frame is in. Unrelated to region modes.
    5025                 this.activeModes = new Backbone.Collection();
    5026                 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
     5116                if ( ! this.options.silent ) {
     5117                        this.render();
     5118                }
    50275119
    5028                 _.each( this.options.mode, function( mode ) {
    5029                         this.activateMode( mode );
    5030                 }, this );
     5120                if ( selection ) {
     5121                        selection.on( 'add remove reset', this.refresh, this );
     5122                }
     5123
     5124                if ( library ) {
     5125                        library.on( 'add remove reset', this.refresh, this );
     5126                }
    50315127        },
    50325128        /**
    5033          * Reset all states on the frame to their defaults.
    5034          *
    5035          * @returns {wp.media.view.Frame} Returns itself to allow chaining
     5129         * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
    50365130         */
    5037         reset: function() {
    5038                 this.states.invoke( 'trigger', 'reset' );
    5039                 return this;
     5131        dispose: function() {
     5132                if ( this.selection ) {
     5133                        this.selection.off( null, null, this );
     5134                }
     5135
     5136                if ( this.library ) {
     5137                        this.library.off( null, null, this );
     5138                }
     5139                /**
     5140                 * call 'dispose' directly on the parent class
     5141                 */
     5142                return View.prototype.dispose.apply( this, arguments );
     5143        },
     5144
     5145        ready: function() {
     5146                this.refresh();
    50405147        },
     5148
    50415149        /**
    5042          * Map activeMode collection events to the frame.
     5150         * @param {string} id
     5151         * @param {Backbone.View|Object} view
     5152         * @param {Object} [options={}]
     5153         * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
    50435154         */
    5044         triggerModeEvents: function( model, collection, options ) {
    5045                 var collectionEvent,
    5046                         modeEventMap = {
    5047                                 add: 'activate',
    5048                                 remove: 'deactivate'
    5049                         },
    5050                         eventToTrigger;
    5051                 // Probably a better way to do this.
    5052                 _.each( options, function( value, key ) {
    5053                         if ( value ) {
    5054                                 collectionEvent = key;
     5155        set: function( id, view, options ) {
     5156                var list;
     5157                options = options || {};
     5158
     5159                // Accept an object with an `id` : `view` mapping.
     5160                if ( _.isObject( id ) ) {
     5161                        _.each( id, function( view, id ) {
     5162                                this.set( id, view, { silent: true });
     5163                        }, this );
     5164
     5165                } else {
     5166                        if ( ! ( view instanceof Backbone.View ) ) {
     5167                                view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
     5168                                view = new wp.media.view.Button( view ).render();
    50555169                        }
    5056                 } );
    50575170
    5058                 if ( ! _.has( modeEventMap, collectionEvent ) ) {
    5059                         return;
     5171                        view.controller = view.controller || this.controller;
     5172
     5173                        this._views[ id ] = view;
     5174
     5175                        list = view.options.priority < 0 ? 'secondary' : 'primary';
     5176                        this[ list ].set( id, view, options );
    50605177                }
    50615178
    5062                 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
    5063                 this.trigger( eventToTrigger );
    5064         },
    5065         /**
    5066          * Activate a mode on the frame.
    5067          *
    5068          * @param string mode Mode ID.
    5069          * @returns {this} Returns itself to allow chaining.
    5070          */
    5071         activateMode: function( mode ) {
    5072                 // Bail if the mode is already active.
    5073                 if ( this.isModeActive( mode ) ) {
    5074                         return;
     5179                if ( ! options.silent ) {
     5180                        this.refresh();
    50755181                }
    5076                 this.activeModes.add( [ { id: mode } ] );
    5077                 // Add a CSS class to the frame so elements can be styled for the mode.
    5078                 this.$el.addClass( 'mode-' + mode );
    50795182
    50805183                return this;
    50815184        },
    50825185        /**
    5083          * Deactivate a mode on the frame.
    5084          *
    5085          * @param string mode Mode ID.
    5086          * @returns {this} Returns itself to allow chaining.
     5186         * @param {string} id
     5187         * @returns {wp.media.view.Button}
    50875188         */
    5088         deactivateMode: function( mode ) {
    5089                 // Bail if the mode isn't active.
    5090                 if ( ! this.isModeActive( mode ) ) {
    5091                         return this;
    5092                 }
    5093                 this.activeModes.remove( this.activeModes.where( { id: mode } ) );
    5094                 this.$el.removeClass( 'mode-' + mode );
    5095                 /**
    5096                  * Frame mode deactivation event.
    5097                  *
    5098                  * @event wp.media.view.Frame#{mode}:deactivate
    5099                  */
    5100                 this.trigger( mode + ':deactivate' );
     5189        get: function( id ) {
     5190                return this._views[ id ];
     5191        },
     5192        /**
     5193         * @param {string} id
     5194         * @param {Object} options
     5195         * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     5196         */
     5197        unset: function( id, options ) {
     5198                delete this._views[ id ];
     5199                this.primary.unset( id, options );
     5200                this.secondary.unset( id, options );
    51015201
     5202                if ( ! options || ! options.silent ) {
     5203                        this.refresh();
     5204                }
    51025205                return this;
    51035206        },
    5104         /**
    5105          * Check if a mode is enabled on the frame.
    5106          *
    5107          * @param  string mode Mode ID.
    5108          * @return bool
    5109          */
    5110         isModeActive: function( mode ) {
    5111                 return Boolean( this.activeModes.where( { id: mode } ).length );
     5207
     5208        refresh: function() {
     5209                var state = this.controller.state(),
     5210                        library = state.get('library'),
     5211                        selection = state.get('selection');
     5212
     5213                _.each( this._views, function( button ) {
     5214                        if ( ! button.model || ! button.options || ! button.options.requires ) {
     5215                                return;
     5216                        }
     5217
     5218                        var requires = button.options.requires,
     5219                                disabled = false;
     5220
     5221                        // Prevent insertion of attachments if any of them are still uploading
     5222                        if ( selection && selection.models ) {
     5223                                disabled = _.some( selection.models, function( attachment ) {
     5224                                        return attachment.get('uploading') === true;
     5225                                });
     5226                        }
     5227
     5228                        if ( requires.selection && selection && ! selection.length ) {
     5229                                disabled = true;
     5230                        } else if ( requires.library && library && ! library.length ) {
     5231                                disabled = true;
     5232                        }
     5233                        button.model.set( 'disabled', disabled );
     5234                });
    51125235        }
    51135236});
    51145237
    5115 // Make the `Frame` a `StateMachine`.
    5116 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
     5238module.exports = Toolbar;
    51175239
    5118 module.exports = Frame;
    51195240
    5120 },{}],44:[function(require,module,exports){
    5121 var Select = wp.media.view.MediaFrame.Select,
     5241/***/ }),
     5242/* 59 */
     5243/***/ (function(module, exports) {
     5244
     5245var Toolbar = wp.media.view.Toolbar,
    51225246        l10n = wp.media.view.l10n,
    5123         ImageDetails;
     5247        Select;
    51245248
    51255249/**
    5126  * wp.media.view.MediaFrame.ImageDetails
    5127  *
    5128  * A media frame for manipulating an image that's already been inserted
    5129  * into a post.
     5250 * wp.media.view.Toolbar.Select
    51305251 *
    5131  * @memberOf wp.media.view.MediaFrame
     5252 * @memberOf wp.media.view.Toolbar
    51325253 *
    51335254 * @class
    5134  * @augments wp.media.view.MediaFrame.Select
    5135  * @augments wp.media.view.MediaFrame
    5136  * @augments wp.media.view.Frame
     5255 * @augments wp.media.view.Toolbar
    51375256 * @augments wp.media.View
    51385257 * @augments wp.Backbone.View
    51395258 * @augments Backbone.View
    5140  * @mixes wp.media.controller.StateMachine
    51415259 */
    5142 ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{
    5143         defaults: {
    5144                 id:      'image',
    5145                 url:     '',
    5146                 menu:    'image-details',
    5147                 content: 'image-details',
    5148                 toolbar: 'image-details',
    5149                 type:    'link',
    5150                 title:    l10n.imageDetailsTitle,
    5151                 priority: 120
    5152         },
    5153 
    5154         initialize: function( options ) {
    5155                 this.image = new wp.media.model.PostImage( options.metadata );
    5156                 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
    5157                 Select.prototype.initialize.apply( this, arguments );
    5158         },
     5260Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{
     5261        initialize: function() {
     5262                var options = this.options;
    51595263
    5160         bindHandlers: function() {
    5161                 Select.prototype.bindHandlers.apply( this, arguments );
    5162                 this.on( 'menu:create:image-details', this.createMenu, this );
    5163                 this.on( 'content:create:image-details', this.imageDetailsContent, this );
    5164                 this.on( 'content:render:edit-image', this.editImageContent, this );
    5165                 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
    5166                 // override the select toolbar
    5167                 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
    5168         },
     5264                _.bindAll( this, 'clickSelect' );
    51695265
    5170         createStates: function() {
    5171                 this.states.add([
    5172                         new wp.media.controller.ImageDetails({
    5173                                 image: this.image,
    5174                                 editable: false
    5175                         }),
    5176                         new wp.media.controller.ReplaceImage({
    5177                                 id: 'replace-image',
    5178                                 library: wp.media.query( { type: 'image' } ),
    5179                                 image: this.image,
    5180                                 multiple:  false,
    5181                                 title:     l10n.imageReplaceTitle,
    5182                                 toolbar: 'replace',
    5183                                 priority:  80,
    5184                                 displaySettings: true
    5185                         }),
    5186                         new wp.media.controller.EditImage( {
    5187                                 image: this.image,
    5188                                 selection: this.options.selection
    5189                         } )
    5190                 ]);
    5191         },
     5266                _.defaults( options, {
     5267                        event: 'select',
     5268                        state: false,
     5269                        reset: true,
     5270                        close: true,
     5271                        text:  l10n.select,
    51925272
    5193         imageDetailsContent: function( options ) {
    5194                 options.view = new wp.media.view.ImageDetails({
    5195                         controller: this,
    5196                         model: this.state().image,
    5197                         attachment: this.state().image.attachment
     5273                        // Does the button rely on the selection?
     5274                        requires: {
     5275                                selection: true
     5276                        }
    51985277                });
    5199         },
    5200 
    5201         editImageContent: function() {
    5202                 var state = this.state(),
    5203                         model = state.get('image'),
    5204                         view;
    5205 
    5206                 if ( ! model ) {
    5207                         return;
    5208                 }
    5209 
    5210                 view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
    5211 
    5212                 this.content.set( view );
    5213 
    5214                 // after bringing in the frame, load the actual editor via an ajax call
    5215                 view.loadEditor();
    5216 
    5217         },
    5218 
    5219         renderImageDetailsToolbar: function() {
    5220                 this.toolbar.set( new wp.media.view.Toolbar({
    5221                         controller: this,
    5222                         items: {
    5223                                 select: {
    5224                                         style:    'primary',
    5225                                         text:     l10n.update,
    5226                                         priority: 80,
    5227 
    5228                                         click: function() {
    5229                                                 var controller = this.controller,
    5230                                                         state = controller.state();
    5231 
    5232                                                 controller.close();
    52335278
    5234                                                 // not sure if we want to use wp.media.string.image which will create a shortcode or
    5235                                                 // perhaps wp.html.string to at least to build the <img />
    5236                                                 state.trigger( 'update', controller.image.toJSON() );
    5237 
    5238                                                 // Restore and reset the default state.
    5239                                                 controller.setState( controller.options.state );
    5240                                                 controller.reset();
    5241                                         }
    5242                                 }
     5279                options.items = _.defaults( options.items || {}, {
     5280                        select: {
     5281                                style:    'primary',
     5282                                text:     options.text,
     5283                                priority: 80,
     5284                                click:    this.clickSelect,
     5285                                requires: options.requires
    52435286                        }
    5244                 }) );
     5287                });
     5288                // Call 'initialize' directly on the parent class.
     5289                Toolbar.prototype.initialize.apply( this, arguments );
    52455290        },
    52465291
    5247         renderReplaceImageToolbar: function() {
    5248                 var frame = this,
    5249                         lastState = frame.lastState(),
    5250                         previous = lastState && lastState.id;
    5251 
    5252                 this.toolbar.set( new wp.media.view.Toolbar({
    5253                         controller: this,
    5254                         items: {
    5255                                 back: {
    5256                                         text:     l10n.back,
    5257                                         priority: 20,
    5258                                         click:    function() {
    5259                                                 if ( previous ) {
    5260                                                         frame.setState( previous );
    5261                                                 } else {
    5262                                                         frame.close();
    5263                                                 }
    5264                                         }
    5265                                 },
    5266 
    5267                                 replace: {
    5268                                         style:    'primary',
    5269                                         text:     l10n.replace,
    5270                                         priority: 80,
    5271                                         requires: { selection: true },
    5272 
    5273                                         click: function() {
    5274                                                 var controller = this.controller,
    5275                                                         state = controller.state(),
    5276                                                         selection = state.get( 'selection' ),
    5277                                                         attachment = selection.single();
     5292        clickSelect: function() {
     5293                var options = this.options,
     5294                        controller = this.controller;
    52785295
    5279                                                 controller.close();
     5296                if ( options.close ) {
     5297                        controller.close();
     5298                }
    52805299
    5281                                                 controller.image.changeAttachment( attachment, state.display( attachment ) );
     5300                if ( options.event ) {
     5301                        controller.state().trigger( options.event );
     5302                }
    52825303
    5283                                                 // not sure if we want to use wp.media.string.image which will create a shortcode or
    5284                                                 // perhaps wp.html.string to at least to build the <img />
    5285                                                 state.trigger( 'replace', controller.image.toJSON() );
     5304                if ( options.state ) {
     5305                        controller.setState( options.state );
     5306                }
    52865307
    5287                                                 // Restore and reset the default state.
    5288                                                 controller.setState( controller.options.state );
    5289                                                 controller.reset();
    5290                                         }
    5291                                 }
    5292                         }
    5293                 }) );
     5308                if ( options.reset ) {
     5309                        controller.reset();
     5310                }
    52945311        }
    5295 
    52965312});
    52975313
    5298 module.exports = ImageDetails;
     5314module.exports = Select;
    52995315
    5300 },{}],45:[function(require,module,exports){
    5301 var Select = wp.media.view.MediaFrame.Select,
    5302         Library = wp.media.controller.Library,
     5316
     5317/***/ }),
     5318/* 60 */
     5319/***/ (function(module, exports) {
     5320
     5321var Select = wp.media.view.Toolbar.Select,
    53035322        l10n = wp.media.view.l10n,
    5304         Post;
     5323        Embed;
    53055324
    53065325/**
    5307  * wp.media.view.MediaFrame.Post
    5308  *
    5309  * The frame for manipulating media on the Edit Post page.
     5326 * wp.media.view.Toolbar.Embed
    53105327 *
    5311  * @memberOf wp.media.view.MediaFrame
     5328 * @memberOf wp.media.view.Toolbar
    53125329 *
    53135330 * @class
    5314  * @augments wp.media.view.MediaFrame.Select
    5315  * @augments wp.media.view.MediaFrame
    5316  * @augments wp.media.view.Frame
     5331 * @augments wp.media.view.Toolbar.Select
     5332 * @augments wp.media.view.Toolbar
    53175333 * @augments wp.media.View
    53185334 * @augments wp.Backbone.View
    53195335 * @augments Backbone.View
    5320  * @mixes wp.media.controller.StateMachine
    53215336 */
    5322 Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{
     5337Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{
    53235338        initialize: function() {
    5324                 this.counts = {
    5325                         audio: {
    5326                                 count: wp.media.view.settings.attachmentCounts.audio,
    5327                                 state: 'playlist'
    5328                         },
    5329                         video: {
    5330                                 count: wp.media.view.settings.attachmentCounts.video,
    5331                                 state: 'video-playlist'
    5332                         }
    5333                 };
    5334 
    53355339                _.defaults( this.options, {
    5336                         multiple:  true,
    5337                         editing:   false,
    5338                         state:    'insert',
    5339                         metadata:  {}
     5340                        text: l10n.insertIntoPost,
     5341                        requires: false
    53405342                });
    5341 
    53425343                // Call 'initialize' directly on the parent class.
    53435344                Select.prototype.initialize.apply( this, arguments );
    5344                 this.createIframeStates();
     5345        },
     5346
     5347        refresh: function() {
     5348                var url = this.controller.state().props.get('url');
     5349                this.get('select').model.set( 'disabled', ! url || url === 'http://' );
     5350                /**
     5351                 * call 'refresh' directly on the parent class
     5352                 */
     5353                Select.prototype.refresh.apply( this, arguments );
     5354        }
     5355});
     5356
     5357module.exports = Embed;
     5358
     5359
     5360/***/ }),
     5361/* 61 */
     5362/***/ (function(module, exports) {
    53455363
     5364/**
     5365 * wp.media.view.Button
     5366 *
     5367 * @memberOf wp.media.view
     5368 *
     5369 * @class
     5370 * @augments wp.media.View
     5371 * @augments wp.Backbone.View
     5372 * @augments Backbone.View
     5373 */
     5374var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{
     5375        tagName:    'button',
     5376        className:  'media-button',
     5377        attributes: { type: 'button' },
     5378
     5379        events: {
     5380                'click': 'click'
    53465381        },
    53475382
    5348         /**
    5349          * Create the default states.
    5350          */
    5351         createStates: function() {
    5352                 var options = this.options;
     5383        defaults: {
     5384                text:     '',
     5385                style:    '',
     5386                size:     'large',
     5387                disabled: false
     5388        },
    53535389
    5354                 this.states.add([
    5355                         // Main states.
    5356                         new Library({
    5357                                 id:         'insert',
    5358                                 title:      l10n.insertMediaTitle,
    5359                                 priority:   20,
    5360                                 toolbar:    'main-insert',
    5361                                 filterable: 'all',
    5362                                 library:    wp.media.query( options.library ),
    5363                                 multiple:   options.multiple ? 'reset' : false,
    5364                                 editable:   true,
     5390        initialize: function() {
     5391                /**
     5392                 * Create a model with the provided `defaults`.
     5393                 *
     5394                 * @member {Backbone.Model}
     5395                 */
     5396                this.model = new Backbone.Model( this.defaults );
    53655397
    5366                                 // If the user isn't allowed to edit fields,
    5367                                 // can they still edit it locally?
    5368                                 allowLocalEdits: true,
     5398                // If any of the `options` have a key from `defaults`, apply its
     5399                // value to the `model` and remove it from the `options object.
     5400                _.each( this.defaults, function( def, key ) {
     5401                        var value = this.options[ key ];
     5402                        if ( _.isUndefined( value ) ) {
     5403                                return;
     5404                        }
    53695405
    5370                                 // Show the attachment display settings.
    5371                                 displaySettings: true,
    5372                                 // Update user settings when users adjust the
    5373                                 // attachment display settings.
    5374                                 displayUserSettings: true
    5375                         }),
     5406                        this.model.set( key, value );
     5407                        delete this.options[ key ];
     5408                }, this );
    53765409
    5377                         new Library({
    5378                                 id:         'gallery',
    5379                                 title:      l10n.createGalleryTitle,
    5380                                 priority:   40,
    5381                                 toolbar:    'main-gallery',
    5382                                 filterable: 'uploaded',
    5383                                 multiple:   'add',
    5384                                 editable:   false,
     5410                this.listenTo( this.model, 'change', this.render );
     5411        },
     5412        /**
     5413         * @returns {wp.media.view.Button} Returns itself to allow chaining
     5414         */
     5415        render: function() {
     5416                var classes = [ 'button', this.className ],
     5417                        model = this.model.toJSON();
    53855418
    5386                                 library:  wp.media.query( _.defaults({
    5387                                         type: 'image'
    5388                                 }, options.library ) )
    5389                         }),
     5419                if ( model.style ) {
     5420                        classes.push( 'button-' + model.style );
     5421                }
    53905422
    5391                         // Embed states.
    5392                         new wp.media.controller.Embed( { metadata: options.metadata } ),
     5423                if ( model.size ) {
     5424                        classes.push( 'button-' + model.size );
     5425                }
    53935426
    5394                         new wp.media.controller.EditImage( { model: options.editImage } ),
     5427                classes = _.uniq( classes.concat( this.options.classes ) );
     5428                this.el.className = classes.join(' ');
    53955429
    5396                         // Gallery states.
    5397                         new wp.media.controller.GalleryEdit({
    5398                                 library: options.selection,
    5399                                 editing: options.editing,
    5400                                 menu:    'gallery'
    5401                         }),
     5430                this.$el.attr( 'disabled', model.disabled );
     5431                this.$el.text( this.model.get('text') );
    54025432
    5403                         new wp.media.controller.GalleryAdd(),
     5433                return this;
     5434        },
     5435        /**
     5436         * @param {Object} event
     5437         */
     5438        click: function( event ) {
     5439                if ( '#' === this.attributes.href ) {
     5440                        event.preventDefault();
     5441                }
    54045442
    5405                         new Library({
    5406                                 id:         'playlist',
    5407                                 title:      l10n.createPlaylistTitle,
    5408                                 priority:   60,
    5409                                 toolbar:    'main-playlist',
    5410                                 filterable: 'uploaded',
    5411                                 multiple:   'add',
    5412                                 editable:   false,
     5443                if ( this.options.click && ! this.model.get('disabled') ) {
     5444                        this.options.click.apply( this, arguments );
     5445                }
     5446        }
     5447});
    54135448
    5414                                 library:  wp.media.query( _.defaults({
    5415                                         type: 'audio'
    5416                                 }, options.library ) )
    5417                         }),
     5449module.exports = Button;
    54185450
    5419                         // Playlist states.
    5420                         new wp.media.controller.CollectionEdit({
    5421                                 type: 'audio',
    5422                                 collectionType: 'playlist',
    5423                                 title:          l10n.editPlaylistTitle,
    5424                                 SettingsView:   wp.media.view.Settings.Playlist,
    5425                                 library:        options.selection,
    5426                                 editing:        options.editing,
    5427                                 menu:           'playlist',
    5428                                 dragInfoText:   l10n.playlistDragInfo,
    5429                                 dragInfo:       false
    5430                         }),
    54315451
    5432                         new wp.media.controller.CollectionAdd({
    5433                                 type: 'audio',
    5434                                 collectionType: 'playlist',
    5435                                 title: l10n.addToPlaylistTitle
    5436                         }),
     5452/***/ }),
     5453/* 62 */
     5454/***/ (function(module, exports) {
    54375455
    5438                         new Library({
    5439                                 id:         'video-playlist',
    5440                                 title:      l10n.createVideoPlaylistTitle,
    5441                                 priority:   60,
    5442                                 toolbar:    'main-video-playlist',
    5443                                 filterable: 'uploaded',
    5444                                 multiple:   'add',
    5445                                 editable:   false,
     5456var $ = Backbone.$,
     5457        ButtonGroup;
    54465458
    5447                                 library:  wp.media.query( _.defaults({
    5448                                         type: 'video'
    5449                                 }, options.library ) )
    5450                         }),
     5459/**
     5460 * wp.media.view.ButtonGroup
     5461 *
     5462 * @memberOf wp.media.view
     5463 *
     5464 * @class
     5465 * @augments wp.media.View
     5466 * @augments wp.Backbone.View
     5467 * @augments Backbone.View
     5468 */
     5469ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{
     5470        tagName:   'div',
     5471        className: 'button-group button-large media-button-group',
    54515472
    5452                         new wp.media.controller.CollectionEdit({
    5453                                 type: 'video',
    5454                                 collectionType: 'playlist',
    5455                                 title:          l10n.editVideoPlaylistTitle,
    5456                                 SettingsView:   wp.media.view.Settings.Playlist,
    5457                                 library:        options.selection,
    5458                                 editing:        options.editing,
    5459                                 menu:           'video-playlist',
    5460                                 dragInfoText:   l10n.videoPlaylistDragInfo,
    5461                                 dragInfo:       false
    5462                         }),
     5473        initialize: function() {
     5474                /**
     5475                 * @member {wp.media.view.Button[]}
     5476                 */
     5477                this.buttons = _.map( this.options.buttons || [], function( button ) {
     5478                        if ( button instanceof Backbone.View ) {
     5479                                return button;
     5480                        } else {
     5481                                return new wp.media.view.Button( button ).render();
     5482                        }
     5483                });
    54635484
    5464                         new wp.media.controller.CollectionAdd({
    5465                                 type: 'video',
    5466                                 collectionType: 'playlist',
    5467                                 title: l10n.addToVideoPlaylistTitle
    5468                         })
    5469                 ]);
     5485                delete this.options.buttons;
    54705486
    5471                 if ( wp.media.view.settings.post.featuredImageId ) {
    5472                         this.states.add( new wp.media.controller.FeaturedImage() );
     5487                if ( this.options.classes ) {
     5488                        this.$el.addClass( this.options.classes );
    54735489                }
    54745490        },
    54755491
    5476         bindHandlers: function() {
    5477                 var handlers, checkCounts;
     5492        /**
     5493         * @returns {wp.media.view.ButtonGroup}
     5494         */
     5495        render: function() {
     5496                this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
     5497                return this;
     5498        }
     5499});
    54785500
    5479                 Select.prototype.bindHandlers.apply( this, arguments );
     5501module.exports = ButtonGroup;
    54805502
    5481                 this.on( 'activate', this.activate, this );
    54825503
    5483                 // Only bother checking media type counts if one of the counts is zero
    5484                 checkCounts = _.find( this.counts, function( type ) {
    5485                         return type.count === 0;
    5486                 } );
     5504/***/ }),
     5505/* 63 */
     5506/***/ (function(module, exports) {
    54875507
    5488                 if ( typeof checkCounts !== 'undefined' ) {
    5489                         this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
    5490                 }
     5508/**
     5509 * wp.media.view.PriorityList
     5510 *
     5511 * @memberOf wp.media.view
     5512 *
     5513 * @class
     5514 * @augments wp.media.View
     5515 * @augments wp.Backbone.View
     5516 * @augments Backbone.View
     5517 */
     5518var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{
     5519        tagName:   'div',
    54915520
    5492                 this.on( 'menu:create:gallery', this.createMenu, this );
    5493                 this.on( 'menu:create:playlist', this.createMenu, this );
    5494                 this.on( 'menu:create:video-playlist', this.createMenu, this );
    5495                 this.on( 'toolbar:create:main-insert', this.createToolbar, this );
    5496                 this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
    5497                 this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
    5498                 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
    5499                 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
    5500                 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
     5521        initialize: function() {
     5522                this._views = {};
    55015523
    5502                 handlers = {
    5503                         menu: {
    5504                                 'default': 'mainMenu',
    5505                                 'gallery': 'galleryMenu',
    5506                                 'playlist': 'playlistMenu',
    5507                                 'video-playlist': 'videoPlaylistMenu'
    5508                         },
     5524                this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
     5525                delete this.options.views;
    55095526
    5510                         content: {
    5511                                 'embed':          'embedContent',
    5512                                 'edit-image':     'editImageContent',
    5513                                 'edit-selection': 'editSelectionContent'
    5514                         },
     5527                if ( ! this.options.silent ) {
     5528                        this.render();
     5529                }
     5530        },
     5531        /**
     5532         * @param {string} id
     5533         * @param {wp.media.View|Object} view
     5534         * @param {Object} options
     5535         * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
     5536         */
     5537        set: function( id, view, options ) {
     5538                var priority, views, index;
    55155539
    5516                         toolbar: {
    5517                                 'main-insert':      'mainInsertToolbar',
    5518                                 'main-gallery':     'mainGalleryToolbar',
    5519                                 'gallery-edit':     'galleryEditToolbar',
    5520                                 'gallery-add':      'galleryAddToolbar',
    5521                                 'main-playlist':        'mainPlaylistToolbar',
    5522                                 'playlist-edit':        'playlistEditToolbar',
    5523                                 'playlist-add':         'playlistAddToolbar',
    5524                                 'main-video-playlist': 'mainVideoPlaylistToolbar',
    5525                                 'video-playlist-edit': 'videoPlaylistEditToolbar',
    5526                                 'video-playlist-add': 'videoPlaylistAddToolbar'
    5527                         }
    5528                 };
     5540                options = options || {};
    55295541
    5530                 _.each( handlers, function( regionHandlers, region ) {
    5531                         _.each( regionHandlers, function( callback, handler ) {
    5532                                 this.on( region + ':render:' + handler, this[ callback ], this );
     5542                // Accept an object with an `id` : `view` mapping.
     5543                if ( _.isObject( id ) ) {
     5544                        _.each( id, function( view, id ) {
     5545                                this.set( id, view );
    55335546                        }, this );
    5534                 }, this );
    5535         },
     5547                        return this;
     5548                }
    55365549
    5537         activate: function() {
    5538                 // Hide menu items for states tied to particular media types if there are no items
    5539                 _.each( this.counts, function( type ) {
    5540                         if ( type.count < 1 ) {
    5541                                 this.menuItemVisibility( type.state, 'hide' );
     5550                if ( ! (view instanceof Backbone.View) ) {
     5551                        view = this.toView( view, id, options );
     5552                }
     5553                view.controller = view.controller || this.controller;
     5554
     5555                this.unset( id );
     5556
     5557                priority = view.options.priority || 10;
     5558                views = this.views.get() || [];
     5559
     5560                _.find( views, function( existing, i ) {
     5561                        if ( existing.options.priority > priority ) {
     5562                                index = i;
     5563                                return true;
    55425564                        }
    5543                 }, this );
    5544         },
     5565                });
    55455566
    5546         mediaTypeCounts: function( model, attr ) {
    5547                 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
    5548                         this.counts[ attr ].count++;
    5549                         this.menuItemVisibility( this.counts[ attr ].state, 'show' );
    5550                 }
    5551         },
     5567                this._views[ id ] = view;
     5568                this.views.add( view, {
     5569                        at: _.isNumber( index ) ? index : views.length || 0
     5570                });
    55525571
    5553         // Menus
     5572                return this;
     5573        },
    55545574        /**
    5555          * @param {wp.Backbone.View} view
     5575         * @param {string} id
     5576         * @returns {wp.media.View}
    55565577         */
    5557         mainMenu: function( view ) {
    5558                 view.set({
    5559                         'library-separator': new wp.media.View({
    5560                                 className: 'separator',
    5561                                 priority: 100
    5562                         })
    5563                 });
     5578        get: function( id ) {
     5579                return this._views[ id ];
    55645580        },
     5581        /**
     5582         * @param {string} id
     5583         * @returns {wp.media.view.PriorityList}
     5584         */
     5585        unset: function( id ) {
     5586                var view = this.get( id );
    55655587
    5566         menuItemVisibility: function( state, visibility ) {
    5567                 var menu = this.menu.get();
    5568                 if ( visibility === 'hide' ) {
    5569                         menu.hide( state );
    5570                 } else if ( visibility === 'show' ) {
    5571                         menu.show( state );
     5588                if ( view ) {
     5589                        view.remove();
    55725590                }
     5591
     5592                delete this._views[ id ];
     5593                return this;
    55735594        },
    55745595        /**
    5575          * @param {wp.Backbone.View} view
     5596         * @param {Object} options
     5597         * @returns {wp.media.View}
    55765598         */
    5577         galleryMenu: function( view ) {
    5578                 var lastState = this.lastState(),
    5579                         previous = lastState && lastState.id,
    5580                         frame = this;
     5599        toView: function( options ) {
     5600                return new wp.media.View( options );
     5601        }
     5602});
    55815603
    5582                 view.set({
    5583                         cancel: {
    5584                                 text:     l10n.cancelGalleryTitle,
    5585                                 priority: 20,
    5586                                 click:    function() {
    5587                                         if ( previous ) {
    5588                                                 frame.setState( previous );
    5589                                         } else {
    5590                                                 frame.close();
    5591                                         }
     5604module.exports = PriorityList;
    55925605
    5593                                         // Keep focus inside media modal
    5594                                         // after canceling a gallery
    5595                                         this.controller.modal.focusManager.focus();
    5596                                 }
    5597                         },
    5598                         separateCancel: new wp.media.View({
    5599                                 className: 'separator',
    5600                                 priority: 40
    5601                         })
    5602                 });
    5603         },
    56045606
    5605         playlistMenu: function( view ) {
    5606                 var lastState = this.lastState(),
    5607                         previous = lastState && lastState.id,
    5608                         frame = this;
     5607/***/ }),
     5608/* 64 */
     5609/***/ (function(module, exports) {
    56095610
    5610                 view.set({
    5611                         cancel: {
    5612                                 text:     l10n.cancelPlaylistTitle,
    5613                                 priority: 20,
    5614                                 click:    function() {
    5615                                         if ( previous ) {
    5616                                                 frame.setState( previous );
    5617                                         } else {
    5618                                                 frame.close();
    5619                                         }
    5620                                 }
    5621                         },
    5622                         separateCancel: new wp.media.View({
    5623                                 className: 'separator',
    5624                                 priority: 40
    5625                         })
    5626                 });
    5627         },
     5611var $ = jQuery,
     5612        MenuItem;
    56285613
    5629         videoPlaylistMenu: function( view ) {
    5630                 var lastState = this.lastState(),
    5631                         previous = lastState && lastState.id,
    5632                         frame = this;
     5614/**
     5615 * wp.media.view.MenuItem
     5616 *
     5617 * @memberOf wp.media.view
     5618 *
     5619 * @class
     5620 * @augments wp.media.View
     5621 * @augments wp.Backbone.View
     5622 * @augments Backbone.View
     5623 */
     5624MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{
     5625        tagName:   'a',
     5626        className: 'media-menu-item',
    56335627
    5634                 view.set({
    5635                         cancel: {
    5636                                 text:     l10n.cancelVideoPlaylistTitle,
    5637                                 priority: 20,
    5638                                 click:    function() {
    5639                                         if ( previous ) {
    5640                                                 frame.setState( previous );
    5641                                         } else {
    5642                                                 frame.close();
    5643                                         }
    5644                                 }
    5645                         },
    5646                         separateCancel: new wp.media.View({
    5647                                 className: 'separator',
    5648                                 priority: 40
    5649                         })
    5650                 });
     5628        attributes: {
     5629                href: '#'
     5630        },
     5631
     5632        events: {
     5633                'click': '_click'
    56515634        },
     5635        /**
     5636         * @param {Object} event
     5637         */
     5638        _click: function( event ) {
     5639                var clickOverride = this.options.click;
    56525640
    5653         // Content
    5654         embedContent: function() {
    5655                 var view = new wp.media.view.Embed({
    5656                         controller: this,
    5657                         model:      this.state()
    5658                 }).render();
     5641                if ( event ) {
     5642                        event.preventDefault();
     5643                }
    56595644
    5660                 this.content.set( view );
     5645                if ( clickOverride ) {
     5646                        clickOverride.call( this );
     5647                } else {
     5648                        this.click();
     5649                }
    56615650
     5651                // When selecting a tab along the left side,
     5652                // focus should be transferred into the main panel
    56625653                if ( ! wp.media.isTouchDevice ) {
    5663                         view.url.focus();
     5654                        $('.media-frame-content input').first().focus();
    56645655                }
    56655656        },
    56665657
    5667         editSelectionContent: function() {
    5668                 var state = this.state(),
    5669                         selection = state.get('selection'),
    5670                         view;
    5671 
    5672                 view = new wp.media.view.AttachmentsBrowser({
    5673                         controller: this,
    5674                         collection: selection,
    5675                         selection:  selection,
    5676                         model:      state,
    5677                         sortable:   true,
    5678                         search:     false,
    5679                         date:       false,
    5680                         dragInfo:   true,
     5658        click: function() {
     5659                var state = this.options.state;
    56815660
    5682                         AttachmentView: wp.media.view.Attachments.EditSelection
    5683                 }).render();
     5661                if ( state ) {
     5662                        this.controller.setState( state );
     5663                        this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
     5664                }
     5665        },
     5666        /**
     5667         * @returns {wp.media.view.MenuItem} returns itself to allow chaining
     5668         */
     5669        render: function() {
     5670                var options = this.options;
    56845671
    5685                 view.toolbar.set( 'backToLibrary', {
    5686                         text:     l10n.returnToLibrary,
    5687                         priority: -100,
     5672                if ( options.text ) {
     5673                        this.$el.text( options.text );
     5674                } else if ( options.html ) {
     5675                        this.$el.html( options.html );
     5676                }
    56885677
    5689                         click: function() {
    5690                                 this.controller.content.mode('browse');
    5691                         }
    5692                 });
     5678                return this;
     5679        }
     5680});
    56935681
    5694                 // Browse our library of attachments.
    5695                 this.content.set( view );
     5682module.exports = MenuItem;
    56965683
    5697                 // Trigger the controller to set focus
    5698                 this.trigger( 'edit:selection', this );
    5699         },
    57005684
    5701         editImageContent: function() {
    5702                 var image = this.state().get('image'),
    5703                         view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
     5685/***/ }),
     5686/* 65 */
     5687/***/ (function(module, exports) {
    57045688
    5705                 this.content.set( view );
     5689var MenuItem = wp.media.view.MenuItem,
     5690        PriorityList = wp.media.view.PriorityList,
     5691        Menu;
    57065692
    5707                 // after creating the wrapper view, load the actual editor via an ajax call
    5708                 view.loadEditor();
     5693/**
     5694 * wp.media.view.Menu
     5695 *
     5696 * @memberOf wp.media.view
     5697 *
     5698 * @class
     5699 * @augments wp.media.view.PriorityList
     5700 * @augments wp.media.View
     5701 * @augments wp.Backbone.View
     5702 * @augments Backbone.View
     5703 */
     5704Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{
     5705        tagName:   'div',
     5706        className: 'media-menu',
     5707        property:  'state',
     5708        ItemView:  MenuItem,
     5709        region:    'menu',
    57095710
     5711        /* TODO: alternatively hide on any click anywhere
     5712        events: {
     5713                'click': 'click'
    57105714        },
    57115715
    5712         // Toolbars
     5716        click: function() {
     5717                this.$el.removeClass( 'visible' );
     5718        },
     5719        */
    57135720
    57145721        /**
    5715          * @param {wp.Backbone.View} view
     5722         * @param {Object} options
     5723         * @param {string} id
     5724         * @returns {wp.media.View}
    57165725         */
    5717         selectionStatusToolbar: function( view ) {
    5718                 var editable = this.state().get('editable');
     5726        toView: function( options, id ) {
     5727                options = options || {};
     5728                options[ this.property ] = options[ this.property ] || id;
     5729                return new this.ItemView( options ).render();
     5730        },
    57195731
    5720                 view.set( 'selection', new wp.media.view.Selection({
    5721                         controller: this,
    5722                         collection: this.state().get('selection'),
    5723                         priority:   -40,
     5732        ready: function() {
     5733                /**
     5734                 * call 'ready' directly on the parent class
     5735                 */
     5736                PriorityList.prototype.ready.apply( this, arguments );
     5737                this.visibility();
     5738        },
    57245739
    5725                         // If the selection is editable, pass the callback to
    5726                         // switch the content mode.
    5727                         editable: editable && function() {
    5728                                 this.controller.content.mode('edit-selection');
    5729                         }
    5730                 }).render() );
     5740        set: function() {
     5741                /**
     5742                 * call 'set' directly on the parent class
     5743                 */
     5744                PriorityList.prototype.set.apply( this, arguments );
     5745                this.visibility();
     5746        },
     5747
     5748        unset: function() {
     5749                /**
     5750                 * call 'unset' directly on the parent class
     5751                 */
     5752                PriorityList.prototype.unset.apply( this, arguments );
     5753                this.visibility();
    57315754        },
    57325755
     5756        visibility: function() {
     5757                var region = this.region,
     5758                        view = this.controller[ region ].get(),
     5759                        views = this.views.get(),
     5760                        hide = ! views || views.length < 2;
     5761
     5762                if ( this === view ) {
     5763                        this.controller.$el.toggleClass( 'hide-' + region, hide );
     5764                }
     5765        },
    57335766        /**
    5734          * @param {wp.Backbone.View} view
     5767         * @param {string} id
    57355768         */
    5736         mainInsertToolbar: function( view ) {
    5737                 var controller = this;
     5769        select: function( id ) {
     5770                var view = this.get( id );
    57385771
    5739                 this.selectionStatusToolbar( view );
     5772                if ( ! view ) {
     5773                        return;
     5774                }
    57405775
    5741                 view.set( 'insert', {
    5742                         style:    'primary',
    5743                         priority: 80,
    5744                         text:     l10n.insertIntoPost,
    5745                         requires: { selection: true },
     5776                this.deselect();
     5777                view.$el.addClass('active');
     5778        },
    57465779
    5747                         /**
    5748                          * @callback
    5749                          * @fires wp.media.controller.State#insert
    5750                          */
    5751                         click: function() {
    5752                                 var state = controller.state(),
    5753                                         selection = state.get('selection');
     5780        deselect: function() {
     5781                this.$el.children().removeClass('active');
     5782        },
    57545783
    5755                                 controller.close();
    5756                                 state.trigger( 'insert', selection ).reset();
    5757                         }
    5758                 });
     5784        hide: function( id ) {
     5785                var view = this.get( id );
     5786
     5787                if ( ! view ) {
     5788                        return;
     5789                }
     5790
     5791                view.$el.addClass('hidden');
    57595792        },
    57605793
     5794        show: function( id ) {
     5795                var view = this.get( id );
     5796
     5797                if ( ! view ) {
     5798                        return;
     5799                }
     5800
     5801                view.$el.removeClass('hidden');
     5802        }
     5803});
     5804
     5805module.exports = Menu;
     5806
     5807
     5808/***/ }),
     5809/* 66 */
     5810/***/ (function(module, exports) {
     5811
     5812/**
     5813 * wp.media.view.RouterItem
     5814 *
     5815 * @memberOf wp.media.view
     5816 *
     5817 * @class
     5818 * @augments wp.media.view.MenuItem
     5819 * @augments wp.media.View
     5820 * @augments wp.Backbone.View
     5821 * @augments Backbone.View
     5822 */
     5823var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{
    57615824        /**
    5762          * @param {wp.Backbone.View} view
     5825         * On click handler to activate the content region's corresponding mode.
    57635826         */
    5764         mainGalleryToolbar: function( view ) {
    5765                 var controller = this;
     5827        click: function() {
     5828                var contentMode = this.options.contentMode;
     5829                if ( contentMode ) {
     5830                        this.controller.content.mode( contentMode );
     5831                }
     5832        }
     5833});
    57665834
    5767                 this.selectionStatusToolbar( view );
     5835module.exports = RouterItem;
     5836
     5837
     5838/***/ }),
     5839/* 67 */
     5840/***/ (function(module, exports) {
     5841
     5842var Menu = wp.media.view.Menu,
     5843        Router;
     5844
     5845/**
     5846 * wp.media.view.Router
     5847 *
     5848 * @memberOf wp.media.view
     5849 *
     5850 * @class
     5851 * @augments wp.media.view.Menu
     5852 * @augments wp.media.view.PriorityList
     5853 * @augments wp.media.View
     5854 * @augments wp.Backbone.View
     5855 * @augments Backbone.View
     5856 */
     5857Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{
     5858        tagName:   'div',
     5859        className: 'media-router',
     5860        property:  'contentMode',
     5861        ItemView:  wp.media.view.RouterItem,
     5862        region:    'router',
    57685863
    5769                 view.set( 'gallery', {
    5770                         style:    'primary',
    5771                         text:     l10n.createNewGallery,
    5772                         priority: 60,
    5773                         requires: { selection: true },
     5864        initialize: function() {
     5865                this.controller.on( 'content:render', this.update, this );
     5866                // Call 'initialize' directly on the parent class.
     5867                Menu.prototype.initialize.apply( this, arguments );
     5868        },
    57745869
    5775                         click: function() {
    5776                                 var selection = controller.state().get('selection'),
    5777                                         edit = controller.state('gallery-edit'),
    5778                                         models = selection.where({ type: 'image' });
     5870        update: function() {
     5871                var mode = this.controller.content.mode();
     5872                if ( mode ) {
     5873                        this.select( mode );
     5874                }
     5875        }
     5876});
    57795877
    5780                                 edit.set( 'library', new wp.media.model.Selection( models, {
    5781                                         props:    selection.props.toJSON(),
    5782                                         multiple: true
    5783                                 }) );
     5878module.exports = Router;
    57845879
    5785                                 this.controller.setState('gallery-edit');
    57865880
    5787                                 // Keep focus inside media modal
    5788                                 // after jumping to gallery view
    5789                                 this.controller.modal.focusManager.focus();
    5790                         }
    5791                 });
    5792         },
     5881/***/ }),
     5882/* 68 */
     5883/***/ (function(module, exports) {
    57935884
    5794         mainPlaylistToolbar: function( view ) {
    5795                 var controller = this;
     5885/**
     5886 * wp.media.view.Sidebar
     5887 *
     5888 * @memberOf wp.media.view
     5889 *
     5890 * @class
     5891 * @augments wp.media.view.PriorityList
     5892 * @augments wp.media.View
     5893 * @augments wp.Backbone.View
     5894 * @augments Backbone.View
     5895 */
     5896var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{
     5897        className: 'media-sidebar'
     5898});
    57965899
    5797                 this.selectionStatusToolbar( view );
     5900module.exports = Sidebar;
    57985901
    5799                 view.set( 'playlist', {
    5800                         style:    'primary',
    5801                         text:     l10n.createNewPlaylist,
    5802                         priority: 100,
    5803                         requires: { selection: true },
    58045902
    5805                         click: function() {
    5806                                 var selection = controller.state().get('selection'),
    5807                                         edit = controller.state('playlist-edit'),
    5808                                         models = selection.where({ type: 'audio' });
     5903/***/ }),
     5904/* 69 */
     5905/***/ (function(module, exports) {
    58095906
    5810                                 edit.set( 'library', new wp.media.model.Selection( models, {
    5811                                         props:    selection.props.toJSON(),
    5812                                         multiple: true
    5813                                 }) );
     5907var View = wp.media.View,
     5908        $ = jQuery,
     5909        Attachment;
    58145910
    5815                                 this.controller.setState('playlist-edit');
     5911/**
     5912 * wp.media.view.Attachment
     5913 *
     5914 * @memberOf wp.media.view
     5915 *
     5916 * @class
     5917 * @augments wp.media.View
     5918 * @augments wp.Backbone.View
     5919 * @augments Backbone.View
     5920 */
     5921Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{
     5922        tagName:   'li',
     5923        className: 'attachment',
     5924        template:  wp.template('attachment'),
    58165925
    5817                                 // Keep focus inside media modal
    5818                                 // after jumping to playlist view
    5819                                 this.controller.modal.focusManager.focus();
    5820                         }
    5821                 });
     5926        attributes: function() {
     5927                return {
     5928                        'tabIndex':     0,
     5929                        'role':         'checkbox',
     5930                        'aria-label':   this.model.get( 'title' ),
     5931                        'aria-checked': false,
     5932                        'data-id':      this.model.get( 'id' )
     5933                };
    58225934        },
    58235935
    5824         mainVideoPlaylistToolbar: function( view ) {
    5825                 var controller = this;
    5826 
    5827                 this.selectionStatusToolbar( view );
     5936        events: {
     5937                'click':                          'toggleSelectionHandler',
     5938                'change [data-setting]':          'updateSetting',
     5939                'change [data-setting] input':    'updateSetting',
     5940                'change [data-setting] select':   'updateSetting',
     5941                'change [data-setting] textarea': 'updateSetting',
     5942                'click .attachment-close':        'removeFromLibrary',
     5943                'click .check':                   'checkClickHandler',
     5944                'keydown':                        'toggleSelectionHandler'
     5945        },
    58285946
    5829                 view.set( 'video-playlist', {
    5830                         style:    'primary',
    5831                         text:     l10n.createNewVideoPlaylist,
    5832                         priority: 100,
    5833                         requires: { selection: true },
     5947        buttons: {},
    58345948
    5835                         click: function() {
    5836                                 var selection = controller.state().get('selection'),
    5837                                         edit = controller.state('video-playlist-edit'),
    5838                                         models = selection.where({ type: 'video' });
     5949        initialize: function() {
     5950                var selection = this.options.selection,
     5951                        options = _.defaults( this.options, {
     5952                                rerenderOnModelChange: true
     5953                        } );
    58395954
    5840                                 edit.set( 'library', new wp.media.model.Selection( models, {
    5841                                         props:    selection.props.toJSON(),
    5842                                         multiple: true
    5843                                 }) );
     5955                if ( options.rerenderOnModelChange ) {
     5956                        this.listenTo( this.model, 'change', this.render );
     5957                } else {
     5958                        this.listenTo( this.model, 'change:percent', this.progress );
     5959                }
     5960                this.listenTo( this.model, 'change:title', this._syncTitle );
     5961                this.listenTo( this.model, 'change:caption', this._syncCaption );
     5962                this.listenTo( this.model, 'change:artist', this._syncArtist );
     5963                this.listenTo( this.model, 'change:album', this._syncAlbum );
    58445964
    5845                                 this.controller.setState('video-playlist-edit');
     5965                // Update the selection.
     5966                this.listenTo( this.model, 'add', this.select );
     5967                this.listenTo( this.model, 'remove', this.deselect );
     5968                if ( selection ) {
     5969                        selection.on( 'reset', this.updateSelect, this );
     5970                        // Update the model's details view.
     5971                        this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
     5972                        this.details( this.model, this.controller.state().get('selection') );
     5973                }
    58465974
    5847                                 // Keep focus inside media modal
    5848                                 // after jumping to video playlist view
    5849                                 this.controller.modal.focusManager.focus();
    5850                         }
    5851                 });
     5975                this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
    58525976        },
     5977        /**
     5978         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     5979         */
     5980        dispose: function() {
     5981                var selection = this.options.selection;
    58535982
    5854         featuredImageToolbar: function( toolbar ) {
    5855                 this.createSelectToolbar( toolbar, {
    5856                         text:  l10n.setFeaturedImage,
    5857                         state: this.options.state
    5858                 });
    5859         },
     5983                // Make sure all settings are saved before removing the view.
     5984                this.updateAll();
    58605985
    5861         mainEmbedToolbar: function( toolbar ) {
    5862                 toolbar.view = new wp.media.view.Toolbar.Embed({
    5863                         controller: this
    5864                 });
     5986                if ( selection ) {
     5987                        selection.off( null, null, this );
     5988                }
     5989                /**
     5990                 * call 'dispose' directly on the parent class
     5991                 */
     5992                View.prototype.dispose.apply( this, arguments );
     5993                return this;
    58655994        },
     5995        /**
     5996         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     5997         */
     5998        render: function() {
     5999                var options = _.defaults( this.model.toJSON(), {
     6000                                orientation:   'landscape',
     6001                                uploading:     false,
     6002                                type:          '',
     6003                                subtype:       '',
     6004                                icon:          '',
     6005                                filename:      '',
     6006                                caption:       '',
     6007                                title:         '',
     6008                                dateFormatted: '',
     6009                                width:         '',
     6010                                height:        '',
     6011                                compat:        false,
     6012                                alt:           '',
     6013                                description:   ''
     6014                        }, this.options );
    58666015
    5867         galleryEditToolbar: function() {
    5868                 var editing = this.state().get('editing');
    5869                 this.toolbar.set( new wp.media.view.Toolbar({
    5870                         controller: this,
    5871                         items: {
    5872                                 insert: {
    5873                                         style:    'primary',
    5874                                         text:     editing ? l10n.updateGallery : l10n.insertGallery,
    5875                                         priority: 80,
    5876                                         requires: { library: true },
     6016                options.buttons  = this.buttons;
     6017                options.describe = this.controller.state().get('describe');
    58776018
    5878                                         /**
    5879                                          * @fires wp.media.controller.State#update
    5880                                          */
    5881                                         click: function() {
    5882                                                 var controller = this.controller,
    5883                                                         state = controller.state();
     6019                if ( 'image' === options.type ) {
     6020                        options.size = this.imageSize();
     6021                }
    58846022
    5885                                                 controller.close();
    5886                                                 state.trigger( 'update', state.get('library') );
     6023                options.can = {};
     6024                if ( options.nonces ) {
     6025                        options.can.remove = !! options.nonces['delete'];
     6026                        options.can.save = !! options.nonces.update;
     6027                }
    58876028
    5888                                                 // Restore and reset the default state.
    5889                                                 controller.setState( controller.options.state );
    5890                                                 controller.reset();
    5891                                         }
    5892                                 }
    5893                         }
    5894                 }) );
    5895         },
     6029                if ( this.controller.state().get('allowLocalEdits') ) {
     6030                        options.allowLocalEdits = true;
     6031                }
    58966032
    5897         galleryAddToolbar: function() {
    5898                 this.toolbar.set( new wp.media.view.Toolbar({
    5899                         controller: this,
    5900                         items: {
    5901                                 insert: {
    5902                                         style:    'primary',
    5903                                         text:     l10n.addToGallery,
    5904                                         priority: 80,
    5905                                         requires: { selection: true },
     6033                if ( options.uploading && ! options.percent ) {
     6034                        options.percent = 0;
     6035                }
    59066036
    5907                                         /**
    5908                                          * @fires wp.media.controller.State#reset
    5909                                          */
    5910                                         click: function() {
    5911                                                 var controller = this.controller,
    5912                                                         state = controller.state(),
    5913                                                         edit = controller.state('gallery-edit');
     6037                this.views.detach();
     6038                this.$el.html( this.template( options ) );
    59146039
    5915                                                 edit.get('library').add( state.get('selection').models );
    5916                                                 state.trigger('reset');
    5917                                                 controller.setState('gallery-edit');
    5918                                         }
    5919                                 }
    5920                         }
    5921                 }) );
    5922         },
     6040                this.$el.toggleClass( 'uploading', options.uploading );
    59236041
    5924         playlistEditToolbar: function() {
    5925                 var editing = this.state().get('editing');
    5926                 this.toolbar.set( new wp.media.view.Toolbar({
    5927                         controller: this,
    5928                         items: {
    5929                                 insert: {
    5930                                         style:    'primary',
    5931                                         text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
    5932                                         priority: 80,
    5933                                         requires: { library: true },
     6042                if ( options.uploading ) {
     6043                        this.$bar = this.$('.media-progress-bar div');
     6044                } else {
     6045                        delete this.$bar;
     6046                }
    59346047
    5935                                         /**
    5936                                          * @fires wp.media.controller.State#update
    5937                                          */
    5938                                         click: function() {
    5939                                                 var controller = this.controller,
    5940                                                         state = controller.state();
     6048                // Check if the model is selected.
     6049                this.updateSelect();
    59416050
    5942                                                 controller.close();
    5943                                                 state.trigger( 'update', state.get('library') );
     6051                // Update the save status.
     6052                this.updateSave();
    59446053
    5945                                                 // Restore and reset the default state.
    5946                                                 controller.setState( controller.options.state );
    5947                                                 controller.reset();
    5948                                         }
    5949                                 }
    5950                         }
    5951                 }) );
     6054                this.views.render();
     6055
     6056                return this;
    59526057        },
    59536058
    5954         playlistAddToolbar: function() {
    5955                 this.toolbar.set( new wp.media.view.Toolbar({
    5956                         controller: this,
    5957                         items: {
    5958                                 insert: {
    5959                                         style:    'primary',
    5960                                         text:     l10n.addToPlaylist,
    5961                                         priority: 80,
    5962                                         requires: { selection: true },
     6059        progress: function() {
     6060                if ( this.$bar && this.$bar.length ) {
     6061                        this.$bar.width( this.model.get('percent') + '%' );
     6062                }
     6063        },
    59636064
    5964                                         /**
    5965                                          * @fires wp.media.controller.State#reset
    5966                                          */
    5967                                         click: function() {
    5968                                                 var controller = this.controller,
    5969                                                         state = controller.state(),
    5970                                                         edit = controller.state('playlist-edit');
     6065        /**
     6066         * @param {Object} event
     6067         */
     6068        toggleSelectionHandler: function( event ) {
     6069                var method;
    59716070
    5972                                                 edit.get('library').add( state.get('selection').models );
    5973                                                 state.trigger('reset');
    5974                                                 controller.setState('playlist-edit');
    5975                                         }
    5976                                 }
    5977                         }
    5978                 }) );
    5979         },
     6071                // Don't do anything inside inputs and on the attachment check and remove buttons.
     6072                if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
     6073                        return;
     6074                }
    59806075
    5981         videoPlaylistEditToolbar: function() {
    5982                 var editing = this.state().get('editing');
    5983                 this.toolbar.set( new wp.media.view.Toolbar({
    5984                         controller: this,
    5985                         items: {
    5986                                 insert: {
    5987                                         style:    'primary',
    5988                                         text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
    5989                                         priority: 140,
    5990                                         requires: { library: true },
     6076                // Catch arrow events
     6077                if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     6078                        this.controller.trigger( 'attachment:keydown:arrow', event );
     6079                        return;
     6080                }
    59916081
    5992                                         click: function() {
    5993                                                 var controller = this.controller,
    5994                                                         state = controller.state(),
    5995                                                         library = state.get('library');
     6082                // Catch enter and space events
     6083                if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     6084                        return;
     6085                }
    59966086
    5997                                                 library.type = 'video';
     6087                event.preventDefault();
    59986088
    5999                                                 controller.close();
    6000                                                 state.trigger( 'update', library );
     6089                // In the grid view, bubble up an edit:attachment event to the controller.
     6090                if ( this.controller.isModeActive( 'grid' ) ) {
     6091                        if ( this.controller.isModeActive( 'edit' ) ) {
     6092                                // Pass the current target to restore focus when closing
     6093                                this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
     6094                                return;
     6095                        }
    60016096
    6002                                                 // Restore and reset the default state.
    6003                                                 controller.setState( controller.options.state );
    6004                                                 controller.reset();
    6005                                         }
    6006                                 }
     6097                        if ( this.controller.isModeActive( 'select' ) ) {
     6098                                method = 'toggle';
    60076099                        }
    6008                 }) );
     6100                }
     6101
     6102                if ( event.shiftKey ) {
     6103                        method = 'between';
     6104                } else if ( event.ctrlKey || event.metaKey ) {
     6105                        method = 'toggle';
     6106                }
     6107
     6108                this.toggleSelection({
     6109                        method: method
     6110                });
     6111
     6112                this.controller.trigger( 'selection:toggle' );
    60096113        },
     6114        /**
     6115         * @param {Object} options
     6116         */
     6117        toggleSelection: function( options ) {
     6118                var collection = this.collection,
     6119                        selection = this.options.selection,
     6120                        model = this.model,
     6121                        method = options && options.method,
     6122                        single, models, singleIndex, modelIndex;
    60106123
    6011         videoPlaylistAddToolbar: function() {
    6012                 this.toolbar.set( new wp.media.view.Toolbar({
    6013                         controller: this,
    6014                         items: {
    6015                                 insert: {
    6016                                         style:    'primary',
    6017                                         text:     l10n.addToVideoPlaylist,
    6018                                         priority: 140,
    6019                                         requires: { selection: true },
     6124                if ( ! selection ) {
     6125                        return;
     6126                }
    60206127
    6021                                         click: function() {
    6022                                                 var controller = this.controller,
    6023                                                         state = controller.state(),
    6024                                                         edit = controller.state('video-playlist-edit');
     6128                single = selection.single();
     6129                method = _.isUndefined( method ) ? selection.multiple : method;
    60256130
    6026                                                 edit.get('library').add( state.get('selection').models );
    6027                                                 state.trigger('reset');
    6028                                                 controller.setState('video-playlist-edit');
    6029                                         }
    6030                                 }
     6131                // If the `method` is set to `between`, select all models that
     6132                // exist between the current and the selected model.
     6133                if ( 'between' === method && single && selection.multiple ) {
     6134                        // If the models are the same, short-circuit.
     6135                        if ( single === model ) {
     6136                                return;
    60316137                        }
    6032                 }) );
    6033         }
    6034 });
    60356138
    6036 module.exports = Post;
     6139                        singleIndex = collection.indexOf( single );
     6140                        modelIndex  = collection.indexOf( this.model );
    60376141
    6038 },{}],46:[function(require,module,exports){
    6039 var MediaFrame = wp.media.view.MediaFrame,
    6040         l10n = wp.media.view.l10n,
    6041         Select;
     6142                        if ( singleIndex < modelIndex ) {
     6143                                models = collection.models.slice( singleIndex, modelIndex + 1 );
     6144                        } else {
     6145                                models = collection.models.slice( modelIndex, singleIndex + 1 );
     6146                        }
    60426147
    6043 /**
    6044  * wp.media.view.MediaFrame.Select
    6045  *
    6046  * A frame for selecting an item or items from the media library.
    6047  *
    6048  * @memberOf wp.media.view.MediaFrame
    6049  *
    6050  * @class
    6051  * @augments wp.media.view.MediaFrame
    6052  * @augments wp.media.view.Frame
    6053  * @augments wp.media.View
    6054  * @augments wp.Backbone.View
    6055  * @augments Backbone.View
    6056  * @mixes wp.media.controller.StateMachine
    6057  */
    6058 Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{
    6059         initialize: function() {
    6060                 // Call 'initialize' directly on the parent class.
    6061                 MediaFrame.prototype.initialize.apply( this, arguments );
     6148                        selection.add( models );
     6149                        selection.single( model );
     6150                        return;
    60626151
    6063                 _.defaults( this.options, {
    6064                         selection: [],
    6065                         library:   {},
    6066                         multiple:  false,
    6067                         state:    'library'
    6068                 });
     6152                // If the `method` is set to `toggle`, just flip the selection
     6153                // status, regardless of whether the model is the single model.
     6154                } else if ( 'toggle' === method ) {
     6155                        selection[ this.selected() ? 'remove' : 'add' ]( model );
     6156                        selection.single( model );
     6157                        return;
     6158                } else if ( 'add' === method ) {
     6159                        selection.add( model );
     6160                        selection.single( model );
     6161                        return;
     6162                }
    60696163
    6070                 this.createSelection();
    6071                 this.createStates();
    6072                 this.bindHandlers();
     6164                // Fixes bug that loses focus when selecting a featured image
     6165                if ( ! method ) {
     6166                        method = 'add';
     6167                }
     6168
     6169                if ( method !== 'add' ) {
     6170                        method = 'reset';
     6171                }
     6172
     6173                if ( this.selected() ) {
     6174                        // If the model is the single model, remove it.
     6175                        // If it is not the same as the single model,
     6176                        // it now becomes the single model.
     6177                        selection[ single === model ? 'remove' : 'single' ]( model );
     6178                } else {
     6179                        // If the model is not selected, run the `method` on the
     6180                        // selection. By default, we `reset` the selection, but the
     6181                        // `method` can be set to `add` the model to the selection.
     6182                        selection[ method ]( model );
     6183                        selection.single( model );
     6184                }
    60736185        },
    60746186
     6187        updateSelect: function() {
     6188                this[ this.selected() ? 'select' : 'deselect' ]();
     6189        },
    60756190        /**
    6076          * Attach a selection collection to the frame.
    6077          *
    6078          * A selection is a collection of attachments used for a specific purpose
    6079          * by a media frame. e.g. Selecting an attachment (or many) to insert into
    6080          * post content.
    6081          *
    6082          * @see media.model.Selection
     6191         * @returns {unresolved|Boolean}
    60836192         */
    6084         createSelection: function() {
     6193        selected: function() {
    60856194                var selection = this.options.selection;
    6086 
    6087                 if ( ! (selection instanceof wp.media.model.Selection) ) {
    6088                         this.options.selection = new wp.media.model.Selection( selection, {
    6089                                 multiple: this.options.multiple
    6090                         });
     6195                if ( selection ) {
     6196                        return !! selection.get( this.model.cid );
    60916197                }
    6092 
    6093                 this._selection = {
    6094                         attachments: new wp.media.model.Attachments(),
    6095                         difference: []
    6096                 };
    60976198        },
    6098 
    60996199        /**
    6100          * Create the default states on the frame.
     6200         * @param {Backbone.Model} model
     6201         * @param {Backbone.Collection} collection
    61016202         */
    6102         createStates: function() {
    6103                 var options = this.options;
     6203        select: function( model, collection ) {
     6204                var selection = this.options.selection,
     6205                        controller = this.controller;
    61046206
    6105                 if ( this.options.states ) {
     6207                // Check if a selection exists and if it's the collection provided.
     6208                // If they're not the same collection, bail; we're in another
     6209                // selection's event loop.
     6210                if ( ! selection || ( collection && collection !== selection ) ) {
    61066211                        return;
    61076212                }
    61086213
    6109                 // Add the default states.
    6110                 this.states.add([
    6111                         // Main states.
    6112                         new wp.media.controller.Library({
    6113                                 library:   wp.media.query( options.library ),
    6114                                 multiple:  options.multiple,
    6115                                 title:     options.title,
    6116                                 priority:  20
    6117                         })
    6118                 ]);
    6119         },
     6214                // Bail if the model is already selected.
     6215                if ( this.$el.hasClass( 'selected' ) ) {
     6216                        return;
     6217                }
    61206218
    6121         /**
    6122          * Bind region mode event callbacks.
    6123          *
    6124          * @see media.controller.Region.render
    6125          */
    6126         bindHandlers: function() {
    6127                 this.on( 'router:create:browse', this.createRouter, this );
    6128                 this.on( 'router:render:browse', this.browseRouter, this );
    6129                 this.on( 'content:create:browse', this.browseContent, this );
    6130                 this.on( 'content:render:upload', this.uploadContent, this );
    6131                 this.on( 'toolbar:create:select', this.createSelectToolbar, this );
     6219                // Add 'selected' class to model, set aria-checked to true.
     6220                this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
     6221                //  Make the checkbox tabable, except in media grid (bulk select mode).
     6222                if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
     6223                        this.$( '.check' ).attr( 'tabindex', '0' );
     6224                }
    61326225        },
    6133 
    61346226        /**
    6135          * Render callback for the router region in the `browse` mode.
    6136          *
    6137          * @param {wp.media.view.Router} routerView
     6227         * @param {Backbone.Model} model
     6228         * @param {Backbone.Collection} collection
    61386229         */
    6139         browseRouter: function( routerView ) {
    6140                 routerView.set({
    6141                         upload: {
    6142                                 text:     l10n.uploadFilesTitle,
    6143                                 priority: 20
    6144                         },
    6145                         browse: {
    6146                                 text:     l10n.mediaLibraryTitle,
    6147                                 priority: 40
    6148                         }
    6149                 });
    6150         },
     6230        deselect: function( model, collection ) {
     6231                var selection = this.options.selection;
    61516232
     6233                // Check if a selection exists and if it's the collection provided.
     6234                // If they're not the same collection, bail; we're in another
     6235                // selection's event loop.
     6236                if ( ! selection || ( collection && collection !== selection ) ) {
     6237                        return;
     6238                }
     6239                this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
     6240                        .find( '.check' ).attr( 'tabindex', '-1' );
     6241        },
    61526242        /**
    6153          * Render callback for the content region in the `browse` mode.
    6154          *
    6155          * @param {wp.media.controller.Region} contentRegion
     6243         * @param {Backbone.Model} model
     6244         * @param {Backbone.Collection} collection
    61566245         */
    6157         browseContent: function( contentRegion ) {
    6158                 var state = this.state();
    6159 
    6160                 this.$el.removeClass('hide-toolbar');
    6161 
    6162                 // Browse our library of attachments.
    6163                 contentRegion.view = new wp.media.view.AttachmentsBrowser({
    6164                         controller: this,
    6165                         collection: state.get('library'),
    6166                         selection:  state.get('selection'),
    6167                         model:      state,
    6168                         sortable:   state.get('sortable'),
    6169                         search:     state.get('searchable'),
    6170                         filters:    state.get('filterable'),
    6171                         date:       state.get('date'),
    6172                         display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
    6173                         dragInfo:   state.get('dragInfo'),
     6246        details: function( model, collection ) {
     6247                var selection = this.options.selection,
     6248                        details;
    61746249
    6175                         idealColumnWidth: state.get('idealColumnWidth'),
    6176                         suggestedWidth:   state.get('suggestedWidth'),
    6177                         suggestedHeight:  state.get('suggestedHeight'),
     6250                if ( selection !== collection ) {
     6251                        return;
     6252                }
    61786253
    6179                         AttachmentView: state.get('AttachmentView')
    6180                 });
     6254                details = selection.single();
     6255                this.$el.toggleClass( 'details', details === this.model );
    61816256        },
    6182 
    61836257        /**
    6184          * Render callback for the content region in the `upload` mode.
     6258         * @param {string} size
     6259         * @returns {Object}
    61856260         */
    6186         uploadContent: function() {
    6187                 this.$el.removeClass( 'hide-toolbar' );
    6188                 this.content.set( new wp.media.view.UploaderInline({
    6189                         controller: this
    6190                 }) );
    6191         },
     6261        imageSize: function( size ) {
     6262                var sizes = this.model.get('sizes'), matched = false;
    61926263
    6193         /**
    6194          * Toolbars
    6195          *
    6196          * @param {Object} toolbar
    6197          * @param {Object} [options={}]
    6198          * @this wp.media.controller.Region
    6199          */
    6200         createSelectToolbar: function( toolbar, options ) {
    6201                 options = options || this.options.button || {};
    6202                 options.controller = this;
     6264                size = size || 'medium';
    62036265
    6204                 toolbar.view = new wp.media.view.Toolbar.Select( options );
    6205         }
    6206 });
     6266                // Use the provided image size if possible.
     6267                if ( sizes ) {
     6268                        if ( sizes[ size ] ) {
     6269                                matched = sizes[ size ];
     6270                        } else if ( sizes.large ) {
     6271                                matched = sizes.large;
     6272                        } else if ( sizes.thumbnail ) {
     6273                                matched = sizes.thumbnail;
     6274                        } else if ( sizes.full ) {
     6275                                matched = sizes.full;
     6276                        }
    62076277
    6208 module.exports = Select;
     6278                        if ( matched ) {
     6279                                return _.clone( matched );
     6280                        }
     6281                }
    62096282
    6210 },{}],47:[function(require,module,exports){
    6211 /**
    6212  * wp.media.view.Iframe
    6213  *
    6214  * @memberOf wp.media.view
    6215  *
    6216  * @class
    6217  * @augments wp.media.View
    6218  * @augments wp.Backbone.View
    6219  * @augments Backbone.View
    6220  */
    6221 var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{
    6222         className: 'media-iframe',
     6283                return {
     6284                        url:         this.model.get('url'),
     6285                        width:       this.model.get('width'),
     6286                        height:      this.model.get('height'),
     6287                        orientation: this.model.get('orientation')
     6288                };
     6289        },
    62236290        /**
    6224          * @returns {wp.media.view.Iframe} Returns itself to allow chaining
     6291         * @param {Object} event
    62256292         */
    6226         render: function() {
    6227                 this.views.detach();
    6228                 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
    6229                 this.views.render();
    6230                 return this;
    6231         }
    6232 });
    6233 
    6234 module.exports = Iframe;
    6235 
    6236 },{}],48:[function(require,module,exports){
    6237 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    6238         $ = jQuery,
    6239         ImageDetails;
    6240 
    6241 /**
    6242  * wp.media.view.ImageDetails
    6243  *
    6244  * @memberOf wp.media.view
    6245  *
    6246  * @class
    6247  * @augments wp.media.view.Settings.AttachmentDisplay
    6248  * @augments wp.media.view.Settings
    6249  * @augments wp.media.View
    6250  * @augments wp.Backbone.View
    6251  * @augments Backbone.View
    6252  */
    6253 ImageDetails = AttachmentDisplay.extend(/** @lends wp.media.view.ImageDetails.prototype */{
    6254         className: 'image-details',
    6255         template:  wp.template('image-details'),
    6256         events: _.defaults( AttachmentDisplay.prototype.events, {
    6257                 'click .edit-attachment': 'editAttachment',
    6258                 'click .replace-attachment': 'replaceAttachment',
    6259                 'click .advanced-toggle': 'onToggleAdvanced',
    6260                 'change [data-setting="customWidth"]': 'onCustomSize',
    6261                 'change [data-setting="customHeight"]': 'onCustomSize',
    6262                 'keyup [data-setting="customWidth"]': 'onCustomSize',
    6263                 'keyup [data-setting="customHeight"]': 'onCustomSize'
    6264         } ),
    6265         initialize: function() {
    6266                 // used in AttachmentDisplay.prototype.updateLinkTo
    6267                 this.options.attachment = this.model.attachment;
    6268                 this.listenTo( this.model, 'change:url', this.updateUrl );
    6269                 this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
    6270                 this.listenTo( this.model, 'change:size', this.toggleCustomSize );
     6293        updateSetting: function( event ) {
     6294                var $setting = $( event.target ).closest('[data-setting]'),
     6295                        setting, value;
    62716296
    6272                 AttachmentDisplay.prototype.initialize.apply( this, arguments );
    6273         },
     6297                if ( ! $setting.length ) {
     6298                        return;
     6299                }
    62746300
    6275         prepare: function() {
    6276                 var attachment = false;
     6301                setting = $setting.data('setting');
     6302                value   = event.target.value;
    62776303
    6278                 if ( this.model.attachment ) {
    6279                         attachment = this.model.attachment.toJSON();
     6304                if ( this.model.get( setting ) !== value ) {
     6305                        this.save( setting, value );
    62806306                }
    6281                 return _.defaults({
    6282                         model: this.model.toJSON(),
    6283                         attachment: attachment
    6284                 }, this.options );
    62856307        },
    62866308
    6287         render: function() {
    6288                 var args = arguments;
     6309        /**
     6310         * Pass all the arguments to the model's save method.
     6311         *
     6312         * Records the aggregate status of all save requests and updates the
     6313         * view's classes accordingly.
     6314         */
     6315        save: function() {
     6316                var view = this,
     6317                        save = this._save = this._save || { status: 'ready' },
     6318                        request = this.model.save.apply( this.model, arguments ),
     6319                        requests = save.requests ? $.when( request, save.requests ) : request;
    62896320
    6290                 if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
    6291                         this.model.dfd
    6292                                 .done( _.bind( function() {
    6293                                         AttachmentDisplay.prototype.render.apply( this, args );
    6294                                         this.postRender();
    6295                                 }, this ) )
    6296                                 .fail( _.bind( function() {
    6297                                         this.model.attachment = false;
    6298                                         AttachmentDisplay.prototype.render.apply( this, args );
    6299                                         this.postRender();
    6300                                 }, this ) );
    6301                 } else {
    6302                         AttachmentDisplay.prototype.render.apply( this, arguments );
    6303                         this.postRender();
     6321                // If we're waiting to remove 'Saved.', stop.
     6322                if ( save.savedTimer ) {
     6323                        clearTimeout( save.savedTimer );
    63046324                }
    63056325
    6306                 return this;
     6326                this.updateSave('waiting');
     6327                save.requests = requests;
     6328                requests.always( function() {
     6329                        // If we've performed another request since this one, bail.
     6330                        if ( save.requests !== requests ) {
     6331                                return;
     6332                        }
     6333
     6334                        view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
     6335                        save.savedTimer = setTimeout( function() {
     6336                                view.updateSave('ready');
     6337                                delete save.savedTimer;
     6338                        }, 2000 );
     6339                });
    63076340        },
     6341        /**
     6342         * @param {string} status
     6343         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6344         */
     6345        updateSave: function( status ) {
     6346                var save = this._save = this._save || { status: 'ready' };
    63086347
    6309         postRender: function() {
    6310                 setTimeout( _.bind( this.resetFocus, this ), 10 );
    6311                 this.toggleLinkSettings();
    6312                 if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
    6313                         this.toggleAdvanced( true );
     6348                if ( status && status !== save.status ) {
     6349                        this.$el.removeClass( 'save-' + save.status );
     6350                        save.status = status;
    63146351                }
    6315                 this.trigger( 'post-render' );
    6316         },
    63176352
    6318         resetFocus: function() {
    6319                 this.$( '.link-to-custom' ).blur();
    6320                 this.$( '.embed-media-settings' ).scrollTop( 0 );
     6353                this.$el.addClass( 'save-' + save.status );
     6354                return this;
    63216355        },
    63226356
    6323         updateUrl: function() {
    6324                 this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
    6325                 this.$( '.url' ).val( this.model.get( 'url' ) );
    6326         },
     6357        updateAll: function() {
     6358                var $settings = this.$('[data-setting]'),
     6359                        model = this.model,
     6360                        changed;
    63276361
    6328         toggleLinkSettings: function() {
    6329                 if ( this.model.get( 'link' ) === 'none' ) {
    6330                         this.$( '.link-settings' ).addClass('hidden');
    6331                 } else {
    6332                         this.$( '.link-settings' ).removeClass('hidden');
    6333                 }
    6334         },
     6362                changed = _.chain( $settings ).map( function( el ) {
     6363                        var $input = $('input, textarea, select, [value]', el ),
     6364                                setting, value;
    63356365
    6336         toggleCustomSize: function() {
    6337                 if ( this.model.get( 'size' ) !== 'custom' ) {
    6338                         this.$( '.custom-size' ).addClass('hidden');
    6339                 } else {
    6340                         this.$( '.custom-size' ).removeClass('hidden');
     6366                        if ( ! $input.length ) {
     6367                                return;
     6368                        }
     6369
     6370                        setting = $(el).data('setting');
     6371                        value = $input.val();
     6372
     6373                        // Record the value if it changed.
     6374                        if ( model.get( setting ) !== value ) {
     6375                                return [ setting, value ];
     6376                        }
     6377                }).compact().object().value();
     6378
     6379                if ( ! _.isEmpty( changed ) ) {
     6380                        model.save( changed );
    63416381                }
    63426382        },
     6383        /**
     6384         * @param {Object} event
     6385         */
     6386        removeFromLibrary: function( event ) {
     6387                // Catch enter and space events
     6388                if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     6389                        return;
     6390                }
    63436391
    6344         onCustomSize: function( event ) {
    6345                 var dimension = $( event.target ).data('setting'),
    6346                         num = $( event.target ).val(),
    6347                         value;
     6392                // Stop propagation so the model isn't selected.
     6393                event.stopPropagation();
    63486394
    6349                 // Ignore bogus input
    6350                 if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
    6351                         event.preventDefault();
     6395                this.collection.remove( this.model );
     6396        },
     6397
     6398        /**
     6399         * Add the model if it isn't in the selection, if it is in the selection,
     6400         * remove it.
     6401         *
     6402         * @param  {[type]} event [description]
     6403         * @return {[type]}       [description]
     6404         */
     6405        checkClickHandler: function ( event ) {
     6406                var selection = this.options.selection;
     6407                if ( ! selection ) {
    63526408                        return;
    63536409                }
    6354 
    6355                 if ( dimension === 'customWidth' ) {
    6356                         value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );
    6357                         this.model.set( 'customHeight', value, { silent: true } );
    6358                         this.$( '[data-setting="customHeight"]' ).val( value );
     6410                event.stopPropagation();
     6411                if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
     6412                        selection.remove( this.model );
     6413                        // Move focus back to the attachment tile (from the check).
     6414                        this.$el.focus();
    63596415                } else {
    6360                         value = Math.round( this.model.get( 'aspectRatio' ) * num );
    6361                         this.model.set( 'customWidth', value, { silent: true  } );
    6362                         this.$( '[data-setting="customWidth"]' ).val( value );
     6416                        selection.add( this.model );
    63636417                }
    6364         },
     6418        }
     6419});
    63656420
    6366         onToggleAdvanced: function( event ) {
    6367                 event.preventDefault();
    6368                 this.toggleAdvanced();
    6369         },
     6421// Ensure settings remain in sync between attachment views.
     6422_.each({
     6423        caption: '_syncCaption',
     6424        title:   '_syncTitle',
     6425        artist:  '_syncArtist',
     6426        album:   '_syncAlbum'
     6427}, function( method, setting ) {
     6428        /**
     6429         * @function _syncCaption
     6430         * @memberOf wp.media.view.Attachment
     6431         * @instance
     6432         *
     6433         * @param {Backbone.Model} model
     6434         * @param {string} value
     6435         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6436         */
     6437        /**
     6438         * @function _syncTitle
     6439         * @memberOf wp.media.view.Attachment
     6440         * @instance
     6441         *
     6442         * @param {Backbone.Model} model
     6443         * @param {string} value
     6444         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6445         */
     6446        /**
     6447         * @function _syncArtist
     6448         * @memberOf wp.media.view.Attachment
     6449         * @instance
     6450         *
     6451         * @param {Backbone.Model} model
     6452         * @param {string} value
     6453         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6454         */
     6455        /**
     6456         * @function _syncAlbum
     6457         * @memberOf wp.media.view.Attachment
     6458         * @instance
     6459         *
     6460         * @param {Backbone.Model} model
     6461         * @param {string} value
     6462         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     6463         */
     6464        Attachment.prototype[ method ] = function( model, value ) {
     6465                var $setting = this.$('[data-setting="' + setting + '"]');
    63706466
    6371         toggleAdvanced: function( show ) {
    6372                 var $advanced = this.$el.find( '.advanced-section' ),
    6373                         mode;
     6467                if ( ! $setting.length ) {
     6468                        return this;
     6469                }
    63746470
    6375                 if ( $advanced.hasClass('advanced-visible') || show === false ) {
    6376                         $advanced.removeClass('advanced-visible');
    6377                         $advanced.find('.advanced-settings').addClass('hidden');
    6378                         mode = 'hide';
    6379                 } else {
    6380                         $advanced.addClass('advanced-visible');
    6381                         $advanced.find('.advanced-settings').removeClass('hidden');
    6382                         mode = 'show';
     6471                // If the updated value is in sync with the value in the DOM, there
     6472                // is no need to re-render. If we're currently editing the value,
     6473                // it will automatically be in sync, suppressing the re-render for
     6474                // the view we're editing, while updating any others.
     6475                if ( value === $setting.find('input, textarea, select, [value]').val() ) {
     6476                        return this;
    63836477                }
    63846478
    6385                 window.setUserSetting( 'advImgDetails', mode );
    6386         },
     6479                return this.render();
     6480        };
     6481});
    63876482
    6388         editAttachment: function( event ) {
    6389                 var editState = this.controller.states.get( 'edit-image' );
     6483module.exports = Attachment;
    63906484
    6391                 if ( window.imageEdit && editState ) {
    6392                         event.preventDefault();
    6393                         editState.set( 'image', this.model.attachment );
    6394                         this.controller.setState( 'edit-image' );
    6395                 }
    6396         },
    63976485
    6398         replaceAttachment: function( event ) {
    6399                 event.preventDefault();
    6400                 this.controller.setState( 'replace-image' );
     6486/***/ }),
     6487/* 70 */
     6488/***/ (function(module, exports) {
     6489
     6490/**
     6491 * wp.media.view.Attachment.Library
     6492 *
     6493 * @memberOf wp.media.view.Attachment
     6494 *
     6495 * @class
     6496 * @augments wp.media.view.Attachment
     6497 * @augments wp.media.View
     6498 * @augments wp.Backbone.View
     6499 * @augments Backbone.View
     6500 */
     6501var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{
     6502        buttons: {
     6503                check: true
    64016504        }
    64026505});
    64036506
    6404 module.exports = ImageDetails;
     6507module.exports = Library;
     6508
     6509
     6510/***/ }),
     6511/* 71 */
     6512/***/ (function(module, exports) {
    64056513
    6406 },{}],49:[function(require,module,exports){
    64076514/**
    6408  * wp.media.view.Label
     6515 * wp.media.view.Attachment.EditLibrary
    64096516 *
    6410  * @memberOf wp.media.view
     6517 * @memberOf wp.media.view.Attachment
    64116518 *
    64126519 * @class
     6520 * @augments wp.media.view.Attachment
    64136521 * @augments wp.media.View
    64146522 * @augments wp.Backbone.View
    64156523 * @augments Backbone.View
    64166524 */
    6417 var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{
    6418         tagName: 'label',
    6419         className: 'screen-reader-text',
    6420 
    6421         initialize: function() {
    6422                 this.value = this.options.value;
    6423         },
    6424 
    6425         render: function() {
    6426                 this.$el.html( this.value );
    6427 
    6428                 return this;
     6525var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{
     6526        buttons: {
     6527                close: true
    64296528        }
    64306529});
    64316530
    6432 module.exports = Label;
     6531module.exports = EditLibrary;
    64336532
    6434 },{}],50:[function(require,module,exports){
    6435 var Frame = wp.media.view.Frame,
     6533
     6534/***/ }),
     6535/* 72 */
     6536/***/ (function(module, exports) {
     6537
     6538var View = wp.media.View,
    64366539        $ = jQuery,
    6437         MediaFrame;
     6540        Attachments;
    64386541
    64396542/**
    6440  * wp.media.view.MediaFrame
    6441  *
    6442  * The frame used to create the media modal.
     6543 * wp.media.view.Attachments
    64436544 *
    64446545 * @memberOf wp.media.view
    64456546 *
    64466547 * @class
    6447  * @augments wp.media.view.Frame
    64486548 * @augments wp.media.View
    64496549 * @augments wp.Backbone.View
    64506550 * @augments Backbone.View
    6451  * @mixes wp.media.controller.StateMachine
    64526551 */
    6453 MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{
    6454         className: 'media-frame',
    6455         template:  wp.template('media-frame'),
    6456         regions:   ['menu','title','content','toolbar','router'],
     6552Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
     6553        tagName:   'ul',
     6554        className: 'attachments',
    64576555
    6458         events: {
    6459                 'click div.media-frame-title h1': 'toggleMenu'
     6556        attributes: {
     6557                tabIndex: -1
    64606558        },
    64616559
    6462         /**
    6463          * @constructs
    6464          */
    64656560        initialize: function() {
    6466                 Frame.prototype.initialize.apply( this, arguments );
     6561                this.el.id = _.uniqueId('__attachments-view-');
    64676562
    64686563                _.defaults( this.options, {
    6469                         title:    '',
    6470                         modal:    true,
    6471                         uploader: true
     6564                        refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
     6565                        refreshThreshold:   3,
     6566                        AttachmentView:     wp.media.view.Attachment,
     6567                        sortable:           false,
     6568                        resize:             true,
     6569                        idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
    64726570                });
    64736571
    6474                 // Ensure core UI is enabled.
    6475                 this.$el.addClass('wp-core-ui');
     6572                this._viewsByCid = {};
     6573                this.$window = $( window );
     6574                this.resizeEvent = 'resize.media-modal-columns';
    64766575
    6477                 // Initialize modal container view.
    6478                 if ( this.options.modal ) {
    6479                         this.modal = new wp.media.view.Modal({
    6480                                 controller: this,
    6481                                 title:      this.options.title
     6576                this.collection.on( 'add', function( attachment ) {
     6577                        this.views.add( this.createAttachmentView( attachment ), {
     6578                                at: this.collection.indexOf( attachment )
    64826579                        });
     6580                }, this );
    64836581
    6484                         this.modal.content( this );
     6582                this.collection.on( 'remove', function( attachment ) {
     6583                        var view = this._viewsByCid[ attachment.cid ];
     6584                        delete this._viewsByCid[ attachment.cid ];
     6585
     6586                        if ( view ) {
     6587                                view.remove();
     6588                        }
     6589                }, this );
     6590
     6591                this.collection.on( 'reset', this.render, this );
     6592
     6593                this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
     6594
     6595                // Throttle the scroll handler and bind this.
     6596                this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
     6597
     6598                this.options.scrollElement = this.options.scrollElement || this.el;
     6599                $( this.options.scrollElement ).on( 'scroll', this.scroll );
     6600
     6601                this.initSortable();
     6602
     6603                _.bindAll( this, 'setColumns' );
     6604
     6605                if ( this.options.resize ) {
     6606                        this.on( 'ready', this.bindEvents );
     6607                        this.controller.on( 'open', this.setColumns );
     6608
     6609                        // Call this.setColumns() after this view has been rendered in the DOM so
     6610                        // attachments get proper width applied.
     6611                        _.defer( this.setColumns, this );
    64856612                }
     6613        },
    64866614
    6487                 // Force the uploader off if the upload limit has been exceeded or
    6488                 // if the browser isn't supported.
    6489                 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
    6490                         this.options.uploader = false;
     6615        bindEvents: function() {
     6616                this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
     6617        },
     6618
     6619        attachmentFocus: function() {
     6620                this.$( 'li:first' ).focus();
     6621        },
     6622
     6623        restoreFocus: function() {
     6624                this.$( 'li.selected:first' ).focus();
     6625        },
     6626
     6627        arrowEvent: function( event ) {
     6628                var attachments = this.$el.children( 'li' ),
     6629                        perRow = this.columns,
     6630                        index = attachments.filter( ':focus' ).index(),
     6631                        row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
     6632
     6633                if ( index === -1 ) {
     6634                        return;
    64916635                }
    64926636
    6493                 // Initialize window-wide uploader.
    6494                 if ( this.options.uploader ) {
    6495                         this.uploader = new wp.media.view.UploaderWindow({
    6496                                 controller: this,
    6497                                 uploader: {
    6498                                         dropzone:  this.modal ? this.modal.$el : this.$el,
    6499                                         container: this.$el
    6500                                 }
    6501                         });
    6502                         this.views.set( '.media-frame-uploader', this.uploader );
     6637                // Left arrow
     6638                if ( 37 === event.keyCode ) {
     6639                        if ( 0 === index ) {
     6640                                return;
     6641                        }
     6642                        attachments.eq( index - 1 ).focus();
    65036643                }
    65046644
    6505                 this.on( 'attach', _.bind( this.views.ready, this.views ), this );
     6645                // Up arrow
     6646                if ( 38 === event.keyCode ) {
     6647                        if ( 1 === row ) {
     6648                                return;
     6649                        }
     6650                        attachments.eq( index - perRow ).focus();
     6651                }
    65066652
    6507                 // Bind default title creation.
    6508                 this.on( 'title:create:default', this.createTitle, this );
    6509                 this.title.mode('default');
     6653                // Right arrow
     6654                if ( 39 === event.keyCode ) {
     6655                        if ( attachments.length === index ) {
     6656                                return;
     6657                        }
     6658                        attachments.eq( index + 1 ).focus();
     6659                }
    65106660
    6511                 this.on( 'title:render', function( view ) {
    6512                         view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
    6513                 });
     6661                // Down arrow
     6662                if ( 40 === event.keyCode ) {
     6663                        if ( Math.ceil( attachments.length / perRow ) === row ) {
     6664                                return;
     6665                        }
     6666                        attachments.eq( index + perRow ).focus();
     6667                }
     6668        },
     6669
     6670        dispose: function() {
     6671                this.collection.props.off( null, null, this );
     6672                if ( this.options.resize ) {
     6673                        this.$window.off( this.resizeEvent );
     6674                }
     6675
     6676                /**
     6677                 * call 'dispose' directly on the parent class
     6678                 */
     6679                View.prototype.dispose.apply( this, arguments );
     6680        },
     6681
     6682        setColumns: function() {
     6683                var prev = this.columns,
     6684                        width = this.$el.width();
     6685
     6686                if ( width ) {
     6687                        this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
     6688
     6689                        if ( ! prev || prev !== this.columns ) {
     6690                                this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
     6691                        }
     6692                }
     6693        },
     6694
     6695        initSortable: function() {
     6696                var collection = this.collection;
     6697
     6698                if ( ! this.options.sortable || ! $.fn.sortable ) {
     6699                        return;
     6700                }
     6701
     6702                this.$el.sortable( _.extend({
     6703                        // If the `collection` has a `comparator`, disable sorting.
     6704                        disabled: !! collection.comparator,
     6705
     6706                        // Change the position of the attachment as soon as the
     6707                        // mouse pointer overlaps a thumbnail.
     6708                        tolerance: 'pointer',
     6709
     6710                        // Record the initial `index` of the dragged model.
     6711                        start: function( event, ui ) {
     6712                                ui.item.data('sortableIndexStart', ui.item.index());
     6713                        },
     6714
     6715                        // Update the model's index in the collection.
     6716                        // Do so silently, as the view is already accurate.
     6717                        update: function( event, ui ) {
     6718                                var model = collection.at( ui.item.data('sortableIndexStart') ),
     6719                                        comparator = collection.comparator;
     6720
     6721                                // Temporarily disable the comparator to prevent `add`
     6722                                // from re-sorting.
     6723                                delete collection.comparator;
     6724
     6725                                // Silently shift the model to its new index.
     6726                                collection.remove( model, {
     6727                                        silent: true
     6728                                });
     6729                                collection.add( model, {
     6730                                        silent: true,
     6731                                        at:     ui.item.index()
     6732                                });
     6733
     6734                                // Restore the comparator.
     6735                                collection.comparator = comparator;
     6736
     6737                                // Fire the `reset` event to ensure other collections sync.
     6738                                collection.trigger( 'reset', collection );
    65146739
    6515                 // Bind default menu.
    6516                 this.on( 'menu:create:default', this.createMenu, this );
    6517         },
    6518         /**
    6519          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6520          */
    6521         render: function() {
    6522                 // Activate the default state if no active state exists.
    6523                 if ( ! this.state() && this.options.state ) {
    6524                         this.setState( this.options.state );
    6525                 }
    6526                 /**
    6527                  * call 'render' directly on the parent class
    6528                  */
    6529                 return Frame.prototype.render.apply( this, arguments );
    6530         },
    6531         /**
    6532          * @param {Object} title
    6533          * @this wp.media.controller.Region
    6534          */
    6535         createTitle: function( title ) {
    6536                 title.view = new wp.media.View({
    6537                         controller: this,
    6538                         tagName: 'h1'
    6539                 });
    6540         },
    6541         /**
    6542          * @param {Object} menu
    6543          * @this wp.media.controller.Region
    6544          */
    6545         createMenu: function( menu ) {
    6546                 menu.view = new wp.media.view.Menu({
    6547                         controller: this
    6548                 });
    6549         },
     6740                                // If the collection is sorted by menu order,
     6741                                // update the menu order.
     6742                                collection.saveMenuOrder();
     6743                        }
     6744                }, this.options.sortable ) );
    65506745
    6551         toggleMenu: function() {
    6552                 this.$el.find( '.media-menu' ).toggleClass( 'visible' );
    6553         },
     6746                // If the `orderby` property is changed on the `collection`,
     6747                // check to see if we have a `comparator`. If so, disable sorting.
     6748                collection.props.on( 'change:orderby', function() {
     6749                        this.$el.sortable( 'option', 'disabled', !! collection.comparator );
     6750                }, this );
    65546751
    6555         /**
    6556          * @param {Object} toolbar
    6557          * @this wp.media.controller.Region
    6558          */
    6559         createToolbar: function( toolbar ) {
    6560                 toolbar.view = new wp.media.view.Toolbar({
    6561                         controller: this
    6562                 });
    6563         },
    6564         /**
    6565          * @param {Object} router
    6566          * @this wp.media.controller.Region
    6567          */
    6568         createRouter: function( router ) {
    6569                 router.view = new wp.media.view.Router({
    6570                         controller: this
    6571                 });
     6752                this.collection.props.on( 'change:orderby', this.refreshSortable, this );
     6753                this.refreshSortable();
    65726754        },
    6573         /**
    6574          * @param {Object} options
    6575          */
    6576         createIframeStates: function( options ) {
    6577                 var settings = wp.media.view.settings,
    6578                         tabs = settings.tabs,
    6579                         tabUrl = settings.tabUrl,
    6580                         $postId;
    65816755
    6582                 if ( ! tabs || ! tabUrl ) {
     6756        refreshSortable: function() {
     6757                if ( ! this.options.sortable || ! $.fn.sortable ) {
    65836758                        return;
    65846759                }
    65856760
    6586                 // Add the post ID to the tab URL if it exists.
    6587                 $postId = $('#post_ID');
    6588                 if ( $postId.length ) {
    6589                         tabUrl += '&post_id=' + $postId.val();
    6590                 }
    6591 
    6592                 // Generate the tab states.
    6593                 _.each( tabs, function( title, id ) {
    6594                         this.state( 'iframe:' + id ).set( _.defaults({
    6595                                 tab:     id,
    6596                                 src:     tabUrl + '&tab=' + id,
    6597                                 title:   title,
    6598                                 content: 'iframe',
    6599                                 menu:    'default'
    6600                         }, options ) );
    6601                 }, this );
     6761                // If the `collection` has a `comparator`, disable sorting.
     6762                var collection = this.collection,
     6763                        orderby = collection.props.get('orderby'),
     6764                        enabled = 'menuOrder' === orderby || ! collection.comparator;
    66026765
    6603                 this.on( 'content:create:iframe', this.iframeContent, this );
    6604                 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
    6605                 this.on( 'menu:render:default', this.iframeMenu, this );
    6606                 this.on( 'open', this.hijackThickbox, this );
    6607                 this.on( 'close', this.restoreThickbox, this );
     6766                this.$el.sortable( 'option', 'disabled', ! enabled );
    66086767        },
    66096768
    66106769        /**
    6611          * @param {Object} content
    6612          * @this wp.media.controller.Region
     6770         * @param {wp.media.model.Attachment} attachment
     6771         * @returns {wp.media.View}
    66136772         */
    6614         iframeContent: function( content ) {
    6615                 this.$el.addClass('hide-toolbar');
    6616                 content.view = new wp.media.view.Iframe({
    6617                         controller: this
     6773        createAttachmentView: function( attachment ) {
     6774                var view = new this.options.AttachmentView({
     6775                        controller:           this.controller,
     6776                        model:                attachment,
     6777                        collection:           this.collection,
     6778                        selection:            this.options.selection
    66186779                });
    6619         },
    66206780
    6621         iframeContentCleanup: function() {
    6622                 this.$el.removeClass('hide-toolbar');
     6781                return this._viewsByCid[ attachment.cid ] = view;
    66236782        },
    66246783
    6625         iframeMenu: function( view ) {
    6626                 var views = {};
     6784        prepare: function() {
     6785                // Create all of the Attachment views, and replace
     6786                // the list in a single DOM operation.
     6787                if ( this.collection.length ) {
     6788                        this.views.set( this.collection.map( this.createAttachmentView, this ) );
    66276789
    6628                 if ( ! view ) {
    6629                         return;
     6790                // If there are no elements, clear the views and load some.
     6791                } else {
     6792                        this.views.unset();
     6793                        this.collection.more().done( this.scroll );
    66306794                }
     6795        },
    66316796
    6632                 _.each( wp.media.view.settings.tabs, function( title, id ) {
    6633                         views[ 'iframe:' + id ] = {
    6634                                 text: this.state( 'iframe:' + id ).get('title'),
    6635                                 priority: 200
    6636                         };
    6637                 }, this );
    6638 
    6639                 view.set( views );
     6797        ready: function() {
     6798                // Trigger the scroll event to check if we're within the
     6799                // threshold to query for additional attachments.
     6800                this.scroll();
    66406801        },
    66416802
    6642         hijackThickbox: function() {
    6643                 var frame = this;
     6803        scroll: function() {
     6804                var view = this,
     6805                        el = this.options.scrollElement,
     6806                        scrollTop = el.scrollTop,
     6807                        toolbar;
    66446808
    6645                 if ( ! window.tb_remove || this._tb_remove ) {
     6809                // The scroll event occurs on the document, but the element
     6810                // that should be checked is the document body.
     6811                if ( el === document ) {
     6812                        el = document.body;
     6813                        scrollTop = $(document).scrollTop();
     6814                }
     6815
     6816                if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
    66466817                        return;
    66476818                }
    66486819
    6649                 this._tb_remove = window.tb_remove;
    6650                 window.tb_remove = function() {
    6651                         frame.close();
    6652                         frame.reset();
    6653                         frame.setState( frame.options.state );
    6654                         frame._tb_remove.call( window );
    6655                 };
    6656         },
     6820                toolbar = this.views.parent.toolbar;
    66576821
    6658         restoreThickbox: function() {
    6659                 if ( ! this._tb_remove ) {
    6660                         return;
     6822                // Show the spinner only if we are close to the bottom.
     6823                if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
     6824                        toolbar.get('spinner').show();
    66616825                }
    66626826
    6663                 window.tb_remove = this._tb_remove;
    6664                 delete this._tb_remove;
     6827                if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
     6828                        this.collection.more().done(function() {
     6829                                view.scroll();
     6830                                toolbar.get('spinner').hide();
     6831                        });
     6832                }
    66656833        }
    66666834});
    66676835
    6668 // Map some of the modal's methods to the frame.
    6669 _.each(['open','close','attach','detach','escape'], function( method ) {
    6670         /**
    6671          * @function open
    6672          * @memberOf wp.media.view.MediaFrame
    6673          * @instance
    6674          *
    6675          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6676          */
    6677         /**
    6678          * @function close
    6679          * @memberOf wp.media.view.MediaFrame
    6680          * @instance
    6681          *
    6682          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6683          */
    6684         /**
    6685          * @function attach
    6686          * @memberOf wp.media.view.MediaFrame
    6687          * @instance
    6688          *
    6689          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6690          */
    6691         /**
    6692          * @function detach
    6693          * @memberOf wp.media.view.MediaFrame
    6694          * @instance
    6695          *
    6696          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6697          */
    6698         /**
    6699          * @function escape
    6700          * @memberOf wp.media.view.MediaFrame
    6701          * @instance
    6702          *
    6703          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     6836module.exports = Attachments;
     6837
     6838
     6839/***/ }),
     6840/* 73 */
     6841/***/ (function(module, exports) {
     6842
     6843var l10n = wp.media.view.l10n,
     6844        Search;
     6845
     6846/**
     6847 * wp.media.view.Search
     6848 *
     6849 * @memberOf wp.media.view
     6850 *
     6851 * @class
     6852 * @augments wp.media.View
     6853 * @augments wp.Backbone.View
     6854 * @augments Backbone.View
     6855 */
     6856Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{
     6857        tagName:   'input',
     6858        className: 'search',
     6859        id:        'media-search-input',
     6860
     6861        attributes: {
     6862                type:        'search',
     6863                placeholder: l10n.searchMediaPlaceholder
     6864        },
     6865
     6866        events: {
     6867                'input':  'search',
     6868                'keyup':  'search'
     6869        },
     6870
     6871        /**
     6872         * @returns {wp.media.view.Search} Returns itself to allow chaining
    67046873         */
    6705         MediaFrame.prototype[ method ] = function() {
    6706                 if ( this.modal ) {
    6707                         this.modal[ method ].apply( this.modal, arguments );
    6708                 }
     6874        render: function() {
     6875                this.el.value = this.model.escape('search');
    67096876                return this;
    6710         };
     6877        },
     6878
     6879        search: _.debounce( function( event ) {
     6880                if ( event.target.value ) {
     6881                        this.model.set( 'search', event.target.value );
     6882                } else {
     6883                        this.model.unset('search');
     6884                }
     6885        }, 300 )
    67116886});
    67126887
    6713 module.exports = MediaFrame;
     6888module.exports = Search;
     6889
     6890
     6891/***/ }),
     6892/* 74 */
     6893/***/ (function(module, exports) {
    67146894
    6715 },{}],51:[function(require,module,exports){
    67166895var $ = jQuery,
    6717         MenuItem;
     6896        AttachmentFilters;
    67186897
    67196898/**
    6720  * wp.media.view.MenuItem
     6899 * wp.media.view.AttachmentFilters
    67216900 *
    67226901 * @memberOf wp.media.view
    67236902 *
    var $ = jQuery, 
    67266905 * @augments wp.Backbone.View
    67276906 * @augments Backbone.View
    67286907 */
    6729 MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{
    6730         tagName:   'a',
    6731         className: 'media-menu-item',
    6732 
    6733         attributes: {
    6734                 href: '#'
    6735         },
     6908AttachmentFilters = wp.media.View.extend(/** @lends wp.media.view.AttachmentFilters.prototype */{
     6909        tagName:   'select',
     6910        className: 'attachment-filters',
     6911        id:        'media-attachment-filters',
    67366912
    67376913        events: {
    6738                 'click': '_click'
     6914                change: 'change'
    67396915        },
    6740         /**
    6741          * @param {Object} event
    6742          */
    6743         _click: function( event ) {
    6744                 var clickOverride = this.options.click;
    6745 
    6746                 if ( event ) {
    6747                         event.preventDefault();
    6748                 }
    67496916
    6750                 if ( clickOverride ) {
    6751                         clickOverride.call( this );
    6752                 } else {
    6753                         this.click();
    6754                 }
     6917        keys: [],
    67556918
    6756                 // When selecting a tab along the left side,
    6757                 // focus should be transferred into the main panel
    6758                 if ( ! wp.media.isTouchDevice ) {
    6759                         $('.media-frame-content input').first().focus();
    6760                 }
    6761         },
     6919        initialize: function() {
     6920                this.createFilters();
     6921                _.extend( this.filters, this.options.filters );
    67626922
    6763         click: function() {
    6764                 var state = this.options.state;
     6923                // Build `<option>` elements.
     6924                this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
     6925                        return {
     6926                                el: $( '<option></option>' ).val( value ).html( filter.text )[0],
     6927                                priority: filter.priority || 50
     6928                        };
     6929                }, this ).sortBy('priority').pluck('el').value() );
    67656930
    6766                 if ( state ) {
    6767                         this.controller.setState( state );
    6768                         this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
    6769                 }
     6931                this.listenTo( this.model, 'change', this.select );
     6932                this.select();
    67706933        },
     6934
    67716935        /**
    6772          * @returns {wp.media.view.MenuItem} returns itself to allow chaining
     6936         * @abstract
    67736937         */
    6774         render: function() {
    6775                 var options = this.options;
     6938        createFilters: function() {
     6939                this.filters = {};
     6940        },
    67766941
    6777                 if ( options.text ) {
    6778                         this.$el.text( options.text );
    6779                 } else if ( options.html ) {
    6780                         this.$el.html( options.html );
     6942        /**
     6943         * When the selected filter changes, update the Attachment Query properties to match.
     6944         */
     6945        change: function() {
     6946                var filter = this.filters[ this.el.value ];
     6947                if ( filter ) {
     6948                        this.model.set( filter.props );
    67816949                }
     6950        },
    67826951
    6783                 return this;
     6952        select: function() {
     6953                var model = this.model,
     6954                        value = 'all',
     6955                        props = model.toJSON();
     6956
     6957                _.find( this.filters, function( filter, id ) {
     6958                        var equal = _.all( filter.props, function( prop, key ) {
     6959                                return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
     6960                        });
     6961
     6962                        if ( equal ) {
     6963                                return value = id;
     6964                        }
     6965                });
     6966
     6967                this.$el.val( value );
    67846968        }
    67856969});
    67866970
    6787 module.exports = MenuItem;
     6971module.exports = AttachmentFilters;
    67886972
    6789 },{}],52:[function(require,module,exports){
    6790 var MenuItem = wp.media.view.MenuItem,
    6791         PriorityList = wp.media.view.PriorityList,
    6792         Menu;
     6973
     6974/***/ }),
     6975/* 75 */
     6976/***/ (function(module, exports) {
     6977
     6978var l10n = wp.media.view.l10n,
     6979        DateFilter;
    67936980
    67946981/**
    6795  * wp.media.view.Menu
     6982 * A filter dropdown for month/dates.
    67966983 *
    6797  * @memberOf wp.media.view
     6984 * @memberOf wp.media.view.AttachmentFilters
    67986985 *
    67996986 * @class
    6800  * @augments wp.media.view.PriorityList
     6987 * @augments wp.media.view.AttachmentFilters
    68016988 * @augments wp.media.View
    68026989 * @augments wp.Backbone.View
    68036990 * @augments Backbone.View
    68046991 */
    6805 Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{
    6806         tagName:   'div',
    6807         className: 'media-menu',
    6808         property:  'state',
    6809         ItemView:  MenuItem,
    6810         region:    'menu',
     6992DateFilter = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Date.prototype */{
     6993        id: 'media-attachment-date-filters',
    68116994
    6812         /* TODO: alternatively hide on any click anywhere
    6813         events: {
    6814                 'click': 'click'
    6815         },
     6995        createFilters: function() {
     6996                var filters = {};
     6997                _.each( wp.media.view.settings.months || {}, function( value, index ) {
     6998                        filters[ index ] = {
     6999                                text: value.text,
     7000                                props: {
     7001                                        year: value.year,
     7002                                        monthnum: value.month
     7003                                }
     7004                        };
     7005                });
     7006                filters.all = {
     7007                        text:  l10n.allDates,
     7008                        props: {
     7009                                monthnum: false,
     7010                                year:  false
     7011                        },
     7012                        priority: 10
     7013                };
     7014                this.filters = filters;
     7015        }
     7016});
    68167017
    6817         click: function() {
    6818                 this.$el.removeClass( 'visible' );
    6819         },
    6820         */
     7018module.exports = DateFilter;
    68217019
    6822         /**
    6823          * @param {Object} options
    6824          * @param {string} id
    6825          * @returns {wp.media.View}
    6826          */
    6827         toView: function( options, id ) {
    6828                 options = options || {};
    6829                 options[ this.property ] = options[ this.property ] || id;
    6830                 return new this.ItemView( options ).render();
    6831         },
    68327020
    6833         ready: function() {
    6834                 /**
    6835                  * call 'ready' directly on the parent class
    6836                  */
    6837                 PriorityList.prototype.ready.apply( this, arguments );
    6838                 this.visibility();
    6839         },
     7021/***/ }),
     7022/* 76 */
     7023/***/ (function(module, exports) {
    68407024
    6841         set: function() {
    6842                 /**
    6843                  * call 'set' directly on the parent class
    6844                  */
    6845                 PriorityList.prototype.set.apply( this, arguments );
    6846                 this.visibility();
    6847         },
     7025var l10n = wp.media.view.l10n,
     7026        Uploaded;
    68487027
    6849         unset: function() {
    6850                 /**
    6851                  * call 'unset' directly on the parent class
    6852                  */
    6853                 PriorityList.prototype.unset.apply( this, arguments );
    6854                 this.visibility();
    6855         },
     7028/**
     7029 * wp.media.view.AttachmentFilters.Uploaded
     7030 *
     7031 * @memberOf wp.media.view.AttachmentFilters
     7032 *
     7033 * @class
     7034 * @augments wp.media.view.AttachmentFilters
     7035 * @augments wp.media.View
     7036 * @augments wp.Backbone.View
     7037 * @augments Backbone.View
     7038 */
     7039Uploaded = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Uploaded.prototype */{
     7040        createFilters: function() {
     7041                var type = this.model.get('type'),
     7042                        types = wp.media.view.settings.mimeTypes,
     7043                        text;
     7044
     7045                if ( types && type ) {
     7046                        text = types[ type ];
     7047                }
     7048
     7049                this.filters = {
     7050                        all: {
     7051                                text:  text || l10n.allMediaItems,
     7052                                props: {
     7053                                        uploadedTo: null,
     7054                                        orderby: 'date',
     7055                                        order:   'DESC'
     7056                                },
     7057                                priority: 10
     7058                        },
     7059
     7060                        uploaded: {
     7061                                text:  l10n.uploadedToThisPost,
     7062                                props: {
     7063                                        uploadedTo: wp.media.view.settings.post.id,
     7064                                        orderby: 'menuOrder',
     7065                                        order:   'ASC'
     7066                                },
     7067                                priority: 20
     7068                        },
     7069
     7070                        unattached: {
     7071                                text:  l10n.unattached,
     7072                                props: {
     7073                                        uploadedTo: 0,
     7074                                        orderby: 'menuOrder',
     7075                                        order:   'ASC'
     7076                                },
     7077                                priority: 50
     7078                        }
     7079                };
     7080        }
     7081});
    68567082
    6857         visibility: function() {
    6858                 var region = this.region,
    6859                         view = this.controller[ region ].get(),
    6860                         views = this.views.get(),
    6861                         hide = ! views || views.length < 2;
     7083module.exports = Uploaded;
    68627084
    6863                 if ( this === view ) {
    6864                         this.controller.$el.toggleClass( 'hide-' + region, hide );
    6865                 }
    6866         },
    6867         /**
    6868          * @param {string} id
    6869          */
    6870         select: function( id ) {
    6871                 var view = this.get( id );
    68727085
    6873                 if ( ! view ) {
    6874                         return;
    6875                 }
     7086/***/ }),
     7087/* 77 */
     7088/***/ (function(module, exports) {
    68767089
    6877                 this.deselect();
    6878                 view.$el.addClass('active');
    6879         },
     7090var l10n = wp.media.view.l10n,
     7091        All;
    68807092
    6881         deselect: function() {
    6882                 this.$el.children().removeClass('active');
    6883         },
     7093/**
     7094 * wp.media.view.AttachmentFilters.All
     7095 *
     7096 * @memberOf wp.media.view.AttachmentFilters
     7097 *
     7098 * @class
     7099 * @augments wp.media.view.AttachmentFilters
     7100 * @augments wp.media.View
     7101 * @augments wp.Backbone.View
     7102 * @augments Backbone.View
     7103 */
     7104All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{
     7105        createFilters: function() {
     7106                var filters = {};
    68847107
    6885         hide: function( id ) {
    6886                 var view = this.get( id );
     7108                _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
     7109                        filters[ key ] = {
     7110                                text: text,
     7111                                props: {
     7112                                        status:  null,
     7113                                        type:    key,
     7114                                        uploadedTo: null,
     7115                                        orderby: 'date',
     7116                                        order:   'DESC'
     7117                                }
     7118                        };
     7119                });
    68877120
    6888                 if ( ! view ) {
    6889                         return;
     7121                filters.all = {
     7122                        text:  l10n.allMediaItems,
     7123                        props: {
     7124                                status:  null,
     7125                                type:    null,
     7126                                uploadedTo: null,
     7127                                orderby: 'date',
     7128                                order:   'DESC'
     7129                        },
     7130                        priority: 10
     7131                };
     7132
     7133                if ( wp.media.view.settings.post.id ) {
     7134                        filters.uploaded = {
     7135                                text:  l10n.uploadedToThisPost,
     7136                                props: {
     7137                                        status:  null,
     7138                                        type:    null,
     7139                                        uploadedTo: wp.media.view.settings.post.id,
     7140                                        orderby: 'menuOrder',
     7141                                        order:   'ASC'
     7142                                },
     7143                                priority: 20
     7144                        };
    68907145                }
    68917146
    6892                 view.$el.addClass('hidden');
    6893         },
     7147                filters.unattached = {
     7148                        text:  l10n.unattached,
     7149                        props: {
     7150                                status:     null,
     7151                                uploadedTo: 0,
     7152                                type:       null,
     7153                                orderby:    'menuOrder',
     7154                                order:      'ASC'
     7155                        },
     7156                        priority: 50
     7157                };
    68947158
    6895         show: function( id ) {
    6896                 var view = this.get( id );
     7159                if ( wp.media.view.settings.mediaTrash &&
     7160                        this.controller.isModeActive( 'grid' ) ) {
    68977161
    6898                 if ( ! view ) {
    6899                         return;
     7162                        filters.trash = {
     7163                                text:  l10n.trash,
     7164                                props: {
     7165                                        uploadedTo: null,
     7166                                        status:     'trash',
     7167                                        type:       null,
     7168                                        orderby:    'date',
     7169                                        order:      'DESC'
     7170                                },
     7171                                priority: 50
     7172                        };
    69007173                }
    69017174
    6902                 view.$el.removeClass('hidden');
     7175                this.filters = filters;
    69037176        }
    69047177});
    69057178
    6906 module.exports = Menu;
     7179module.exports = All;
    69077180
    6908 },{}],53:[function(require,module,exports){
    6909 var $ = jQuery,
    6910         Modal;
     7181
     7182/***/ }),
     7183/* 78 */
     7184/***/ (function(module, exports) {
     7185
     7186var View = wp.media.View,
     7187        mediaTrash = wp.media.view.settings.mediaTrash,
     7188        l10n = wp.media.view.l10n,
     7189        $ = jQuery,
     7190        AttachmentsBrowser;
    69117191
    69127192/**
    6913  * wp.media.view.Modal
    6914  *
    6915  * A modal view, which the media modal uses as its default container.
     7193 * wp.media.view.AttachmentsBrowser
    69167194 *
    69177195 * @memberOf wp.media.view
    69187196 *
    var $ = jQuery, 
    69207198 * @augments wp.media.View
    69217199 * @augments wp.Backbone.View
    69227200 * @augments Backbone.View
     7201 *
     7202 * @param {object}         [options]               The options hash passed to the view.
     7203 * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar.
     7204 *                                                 Accepts 'uploaded' and 'all'.
     7205 * @param {boolean}        [options.search=true]   Whether to show the search interface in the
     7206 *                                                 browser's toolbar.
     7207 * @param {boolean}        [options.date=true]     Whether to show the date filter in the
     7208 *                                                 browser's toolbar.
     7209 * @param {boolean}        [options.display=false] Whether to show the attachments display settings
     7210 *                                                 view in the sidebar.
     7211 * @param {boolean|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
     7212 *                                                 Accepts true, false, and 'errors'.
    69237213 */
    6924 Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{
    6925         tagName:  'div',
    6926         template: wp.template('media-modal'),
    6927 
    6928         attributes: {
    6929                 tabindex: 0
    6930         },
    6931 
    6932         events: {
    6933                 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
    6934                 'keydown': 'keydown'
    6935         },
    6936 
    6937         clickedOpenerEl: null,
     7214AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{
     7215        tagName:   'div',
     7216        className: 'attachments-browser',
    69387217
    69397218        initialize: function() {
    69407219                _.defaults( this.options, {
    6941                         container: document.body,
    6942                         title:     '',
    6943                         propagate: true,
    6944                         freeze:    true
     7220                        filters: false,
     7221                        search:  true,
     7222                        date:    true,
     7223                        display: false,
     7224                        sidebar: true,
     7225                        AttachmentView: wp.media.view.Attachment.Library
    69457226                });
    69467227
    6947                 this.focusManager = new wp.media.view.FocusManager({
    6948                         el: this.el
    6949                 });
    6950         },
    6951         /**
    6952          * @returns {Object}
    6953          */
    6954         prepare: function() {
    6955                 return {
    6956                         title: this.options.title
    6957                 };
    6958         },
     7228                this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this );
     7229                this.controller.on( 'edit:selection', this.editSelection );
    69597230
    6960         /**
    6961          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6962          */
    6963         attach: function() {
    6964                 if ( this.views.attached ) {
    6965                         return this;
     7231                // In the Media Library, the sidebar is used to display errors before the attachments grid.
     7232                if ( this.options.sidebar && 'errors' === this.options.sidebar ) {
     7233                        this.createSidebar();
    69667234                }
    69677235
    6968                 if ( ! this.views.rendered ) {
    6969                         this.render();
     7236                /*
     7237                 * For accessibility reasons, place the Inline Uploader before other sections.
     7238                 * This way, in the Media Library, it's right after the Add New button, see ticket #37188.
     7239                 */
     7240                this.createUploader();
     7241
     7242                /*
     7243                 * Create a multi-purpose toolbar. Used as main toolbar in the Media Library
     7244                 * and also for other things, for example the "Drag and drop to reorder" and
     7245                 * "Suggested dimensions" info in the media modal.
     7246                 */
     7247                this.createToolbar();
     7248
     7249                // Create the list of attachments.
     7250                this.createAttachments();
     7251
     7252                // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909.
     7253                if ( this.options.sidebar && 'errors' !== this.options.sidebar ) {
     7254                        this.createSidebar();
    69707255                }
    69717256
    6972                 this.$el.appendTo( this.options.container );
     7257                this.updateContent();
    69737258
    6974                 // Manually mark the view as attached and trigger ready.
    6975                 this.views.attached = true;
    6976                 this.views.ready();
     7259                if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
     7260                        this.$el.addClass( 'hide-sidebar' );
    69777261
    6978                 return this.propagate('attach');
     7262                        if ( 'errors' === this.options.sidebar ) {
     7263                                this.$el.addClass( 'sidebar-for-errors' );
     7264                        }
     7265                }
     7266
     7267                this.collection.on( 'add remove reset', this.updateContent, this );
     7268        },
     7269
     7270        editSelection: function( modal ) {
     7271                modal.$( '.media-button-backToLibrary' ).focus();
    69797272        },
    69807273
    69817274        /**
    6982          * @returns {wp.media.view.Modal} Returns itself to allow chaining
     7275         * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
    69837276         */
    6984         detach: function() {
    6985                 if ( this.$el.is(':visible') ) {
    6986                         this.close();
     7277        dispose: function() {
     7278                this.options.selection.off( null, null, this );
     7279                View.prototype.dispose.apply( this, arguments );
     7280                return this;
     7281        },
     7282
     7283        createToolbar: function() {
     7284                var LibraryViewSwitcher, Filters, toolbarOptions;
     7285
     7286                toolbarOptions = {
     7287                        controller: this.controller
     7288                };
     7289
     7290                if ( this.controller.isModeActive( 'grid' ) ) {
     7291                        toolbarOptions.className = 'media-toolbar wp-filter';
    69877292                }
    69887293
    6989                 this.$el.detach();
    6990                 this.views.attached = false;
    6991                 return this.propagate('detach');
    6992         },
     7294                /**
     7295                * @member {wp.media.view.Toolbar}
     7296                */
     7297                this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
    69937298
    6994         /**
    6995          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6996          */
    6997         open: function() {
    6998                 var $el = this.$el,
    6999                         options = this.options,
    7000                         mceEditor;
     7299                this.views.add( this.toolbar );
    70017300
    7002                 if ( $el.is(':visible') ) {
    7003                         return this;
    7004                 }
     7301                this.toolbar.set( 'spinner', new wp.media.view.Spinner({
     7302                        priority: -60
     7303                }) );
    70057304
    7006                 this.clickedOpenerEl = document.activeElement;
     7305                if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
     7306                        // "Filters" will return a <select>, need to render
     7307                        // screen reader text before
     7308                        this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
     7309                                value: l10n.filterByType,
     7310                                attributes: {
     7311                                        'for':  'media-attachment-filters'
     7312                                },
     7313                                priority:   -80
     7314                        }).render() );
    70077315
    7008                 if ( ! this.views.attached ) {
    7009                         this.attach();
    7010                 }
     7316                        if ( 'uploaded' === this.options.filters ) {
     7317                                this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({
     7318                                        controller: this.controller,
     7319                                        model:      this.collection.props,
     7320                                        priority:   -80
     7321                                }).render() );
     7322                        } else {
     7323                                Filters = new wp.media.view.AttachmentFilters.All({
     7324                                        controller: this.controller,
     7325                                        model:      this.collection.props,
     7326                                        priority:   -80
     7327                                });
    70117328
    7012                 // If the `freeze` option is set, record the window's scroll position.
    7013                 if ( options.freeze ) {
    7014                         this._freeze = {
    7015                                 scrollTop: $( window ).scrollTop()
    7016                         };
     7329                                this.toolbar.set( 'filters', Filters.render() );
     7330                        }
    70177331                }
    70187332
    7019                 // Disable page scrolling.
    7020                 $( 'body' ).addClass( 'modal-open' );
     7333                // Feels odd to bring the global media library switcher into the Attachment
     7334                // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
     7335                // which the controller can tap into and add this view?
     7336                if ( this.controller.isModeActive( 'grid' ) ) {
     7337                        LibraryViewSwitcher = View.extend({
     7338                                className: 'view-switch media-grid-view-switch',
     7339                                template: wp.template( 'media-library-view-switcher')
     7340                        });
    70217341
    7022                 $el.show();
     7342                        this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
     7343                                controller: this.controller,
     7344                                priority: -90
     7345                        }).render() );
    70237346
    7024                 // Try to close the onscreen keyboard
    7025                 if ( 'ontouchend' in document ) {
    7026                         if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
    7027                                 mceEditor.iframeElement.focus();
    7028                                 mceEditor.iframeElement.blur();
     7347                        // DateFilter is a <select>, screen reader text needs to be rendered before
     7348                        this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
     7349                                value: l10n.filterByDate,
     7350                                attributes: {
     7351                                        'for': 'media-attachment-date-filters'
     7352                                },
     7353                                priority: -75
     7354                        }).render() );
     7355                        this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
     7356                                controller: this.controller,
     7357                                model:      this.collection.props,
     7358                                priority: -75
     7359                        }).render() );
    70297360
    7030                                 setTimeout( function() {
    7031                                         mceEditor.iframeElement.blur();
    7032                                 }, 100 );
    7033                         }
    7034                 }
     7361                        // BulkSelection is a <div> with subviews, including screen reader text
     7362                        this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
     7363                                text: l10n.bulkSelect,
     7364                                controller: this.controller,
     7365                                priority: -70
     7366                        }).render() );
    70357367
    7036                 this.$el.focus();
     7368                        this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
     7369                                filters: Filters,
     7370                                style: 'primary',
     7371                                disabled: true,
     7372                                text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
     7373                                controller: this.controller,
     7374                                priority: -60,
     7375                                click: function() {
     7376                                        var changed = [], removed = [],
     7377                                                selection = this.controller.state().get( 'selection' ),
     7378                                                library = this.controller.state().get( 'library' );
    70377379
    7038                 return this.propagate('open');
    7039         },
     7380                                        if ( ! selection.length ) {
     7381                                                return;
     7382                                        }
    70407383
    7041         /**
    7042          * @param {Object} options
    7043          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    7044          */
    7045         close: function( options ) {
    7046                 var freeze = this._freeze;
     7384                                        if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
     7385                                                return;
     7386                                        }
    70477387
    7048                 if ( ! this.views.attached || ! this.$el.is(':visible') ) {
    7049                         return this;
    7050                 }
     7388                                        if ( mediaTrash &&
     7389                                                'trash' !== selection.at( 0 ).get( 'status' ) &&
     7390                                                ! window.confirm( l10n.warnBulkTrash ) ) {
    70517391
    7052                 // Enable page scrolling.
    7053                 $( 'body' ).removeClass( 'modal-open' );
     7392                                                return;
     7393                                        }
    70547394
    7055                 // Hide modal and remove restricted media modal tab focus once it's closed
    7056                 this.$el.hide().undelegate( 'keydown' );
     7395                                        selection.each( function( model ) {
     7396                                                if ( ! model.get( 'nonces' )['delete'] ) {
     7397                                                        removed.push( model );
     7398                                                        return;
     7399                                                }
    70577400
    7058                 // Put focus back in useful location once modal is closed.
    7059                 if ( null !== this.clickedOpenerEl ) {
    7060                         this.clickedOpenerEl.focus();
    7061                 } else {
    7062                         $( '#wpbody-content' ).focus();
    7063                 }
     7401                                                if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
     7402                                                        model.set( 'status', 'inherit' );
     7403                                                        changed.push( model.save() );
     7404                                                        removed.push( model );
     7405                                                } else if ( mediaTrash ) {
     7406                                                        model.set( 'status', 'trash' );
     7407                                                        changed.push( model.save() );
     7408                                                        removed.push( model );
     7409                                                } else {
     7410                                                        model.destroy({wait: true});
     7411                                                }
     7412                                        } );
    70647413
    7065                 this.propagate('close');
     7414                                        if ( changed.length ) {
     7415                                                selection.remove( removed );
    70667416
    7067                 // If the `freeze` option is set, restore the container's scroll position.
    7068                 if ( freeze ) {
    7069                         $( window ).scrollTop( freeze.scrollTop );
    7070                 }
     7417                                                $.when.apply( null, changed ).then( _.bind( function() {
     7418                                                        library._requery( true );
     7419                                                        this.controller.trigger( 'selection:action:done' );
     7420                                                }, this ) );
     7421                                        } else {
     7422                                                this.controller.trigger( 'selection:action:done' );
     7423                                        }
     7424                                }
     7425                        }).render() );
    70717426
    7072                 if ( options && options.escape ) {
    7073                         this.propagate('escape');
    7074                 }
     7427                        if ( mediaTrash ) {
     7428                                this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
     7429                                        filters: Filters,
     7430                                        style: 'primary',
     7431                                        disabled: true,
     7432                                        text: l10n.deleteSelected,
     7433                                        controller: this.controller,
     7434                                        priority: -55,
     7435                                        click: function() {
     7436                                                var removed = [],
     7437                                                        destroy = [],
     7438                                                        selection = this.controller.state().get( 'selection' );
    70757439
    7076                 return this;
    7077         },
    7078         /**
    7079          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    7080          */
    7081         escape: function() {
    7082                 return this.close({ escape: true });
    7083         },
    7084         /**
    7085          * @param {Object} event
    7086          */
    7087         escapeHandler: function( event ) {
    7088                 event.preventDefault();
    7089                 this.escape();
    7090         },
     7440                                                if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
     7441                                                        return;
     7442                                                }
    70917443
    7092         /**
    7093          * @param {Array|Object} content Views to register to '.media-modal-content'
    7094          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    7095          */
    7096         content: function( content ) {
    7097                 this.views.set( '.media-modal-content', content );
    7098                 return this;
    7099         },
     7444                                                selection.each( function( model ) {
     7445                                                        if ( ! model.get( 'nonces' )['delete'] ) {
     7446                                                                removed.push( model );
     7447                                                                return;
     7448                                                        }
    71007449
    7101         /**
    7102          * Triggers a modal event and if the `propagate` option is set,
    7103          * forwards events to the modal's controller.
    7104          *
    7105          * @param {string} id
    7106          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    7107          */
    7108         propagate: function( id ) {
    7109                 this.trigger( id );
     7450                                                        destroy.push( model );
     7451                                                } );
    71107452
    7111                 if ( this.options.propagate ) {
    7112                         this.controller.trigger( id );
     7453                                                if ( removed.length ) {
     7454                                                        selection.remove( removed );
     7455                                                }
     7456
     7457                                                if ( destroy.length ) {
     7458                                                        $.when.apply( null, destroy.map( function (item) {
     7459                                                                return item.destroy();
     7460                                                        } ) ).then( _.bind( function() {
     7461                                                                this.controller.trigger( 'selection:action:done' );
     7462                                                        }, this ) );
     7463                                                }
     7464                                        }
     7465                                }).render() );
     7466                        }
     7467
     7468                } else if ( this.options.date ) {
     7469                        // DateFilter is a <select>, screen reader text needs to be rendered before
     7470                        this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
     7471                                value: l10n.filterByDate,
     7472                                attributes: {
     7473                                        'for': 'media-attachment-date-filters'
     7474                                },
     7475                                priority: -75
     7476                        }).render() );
     7477                        this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
     7478                                controller: this.controller,
     7479                                model:      this.collection.props,
     7480                                priority: -75
     7481                        }).render() );
    71137482                }
    71147483
    7115                 return this;
    7116         },
    7117         /**
    7118          * @param {Object} event
    7119          */
    7120         keydown: function( event ) {
    7121                 // Close the modal when escape is pressed.
    7122                 if ( 27 === event.which && this.$el.is(':visible') ) {
    7123                         this.escape();
    7124                         event.stopImmediatePropagation();
     7484                if ( this.options.search ) {
     7485                        // Search is an input, screen reader text needs to be rendered before
     7486                        this.toolbar.set( 'searchLabel', new wp.media.view.Label({
     7487                                value: l10n.searchMediaLabel,
     7488                                attributes: {
     7489                                        'for': 'media-search-input'
     7490                                },
     7491                                priority:   60
     7492                        }).render() );
     7493                        this.toolbar.set( 'search', new wp.media.view.Search({
     7494                                controller: this.controller,
     7495                                model:      this.collection.props,
     7496                                priority:   60
     7497                        }).render() );
    71257498                }
    7126         }
    7127 });
    71287499
    7129 module.exports = Modal;
     7500                if ( this.options.dragInfo ) {
     7501                        this.toolbar.set( 'dragInfo', new View({
     7502                                el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
     7503                                priority: -40
     7504                        }) );
     7505                }
    71307506
    7131 },{}],54:[function(require,module,exports){
    7132 /**
    7133  * wp.media.view.PriorityList
    7134  *
    7135  * @memberOf wp.media.view
    7136  *
    7137  * @class
    7138  * @augments wp.media.View
    7139  * @augments wp.Backbone.View
    7140  * @augments Backbone.View
    7141  */
    7142 var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{
    7143         tagName:   'div',
     7507                if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
     7508                        this.toolbar.set( 'suggestedDimensions', new View({
     7509                                el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0],
     7510                                priority: -40
     7511                        }) );
     7512                }
     7513        },
    71447514
    7145         initialize: function() {
    7146                 this._views = {};
     7515        updateContent: function() {
     7516                var view = this,
     7517                        noItemsView;
    71477518
    7148                 this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
    7149                 delete this.options.views;
     7519                if ( this.controller.isModeActive( 'grid' ) ) {
     7520                        noItemsView = view.attachmentsNoResults;
     7521                } else {
     7522                        noItemsView = view.uploader;
     7523                }
    71507524
    7151                 if ( ! this.options.silent ) {
    7152                         this.render();
     7525                if ( ! this.collection.length ) {
     7526                        this.toolbar.get( 'spinner' ).show();
     7527                        this.dfd = this.collection.more().done( function() {
     7528                                if ( ! view.collection.length ) {
     7529                                        noItemsView.$el.removeClass( 'hidden' );
     7530                                } else {
     7531                                        noItemsView.$el.addClass( 'hidden' );
     7532                                }
     7533                                view.toolbar.get( 'spinner' ).hide();
     7534                        } );
     7535                } else {
     7536                        noItemsView.$el.addClass( 'hidden' );
     7537                        view.toolbar.get( 'spinner' ).hide();
    71537538                }
    71547539        },
    7155         /**
    7156          * @param {string} id
    7157          * @param {wp.media.View|Object} view
    7158          * @param {Object} options
    7159          * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
    7160          */
    7161         set: function( id, view, options ) {
    7162                 var priority, views, index;
    71637540
    7164                 options = options || {};
     7541        createUploader: function() {
     7542                this.uploader = new wp.media.view.UploaderInline({
     7543                        controller: this.controller,
     7544                        status:     false,
     7545                        message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
     7546                        canClose:   this.controller.isModeActive( 'grid' )
     7547                });
    71657548
    7166                 // Accept an object with an `id` : `view` mapping.
    7167                 if ( _.isObject( id ) ) {
    7168                         _.each( id, function( view, id ) {
    7169                                 this.set( id, view );
    7170                         }, this );
    7171                         return this;
    7172                 }
     7549                this.uploader.$el.addClass( 'hidden' );
     7550                this.views.add( this.uploader );
     7551        },
    71737552
    7174                 if ( ! (view instanceof Backbone.View) ) {
    7175                         view = this.toView( view, id, options );
     7553        toggleUploader: function() {
     7554                if ( this.uploader.$el.hasClass( 'hidden' ) ) {
     7555                        this.uploader.show();
     7556                } else {
     7557                        this.uploader.hide();
    71767558                }
    7177                 view.controller = view.controller || this.controller;
    7178 
    7179                 this.unset( id );
     7559        },
    71807560
    7181                 priority = view.options.priority || 10;
    7182                 views = this.views.get() || [];
     7561        createAttachments: function() {
     7562                this.attachments = new wp.media.view.Attachments({
     7563                        controller:           this.controller,
     7564                        collection:           this.collection,
     7565                        selection:            this.options.selection,
     7566                        model:                this.model,
     7567                        sortable:             this.options.sortable,
     7568                        scrollElement:        this.options.scrollElement,
     7569                        idealColumnWidth:     this.options.idealColumnWidth,
    71837570
    7184                 _.find( views, function( existing, i ) {
    7185                         if ( existing.options.priority > priority ) {
    7186                                 index = i;
    7187                                 return true;
    7188                         }
     7571                        // The single `Attachment` view to be used in the `Attachments` view.
     7572                        AttachmentView: this.options.AttachmentView
    71897573                });
    71907574
    7191                 this._views[ id ] = view;
    7192                 this.views.add( view, {
    7193                         at: _.isNumber( index ) ? index : views.length || 0
    7194                 });
     7575                // Add keydown listener to the instance of the Attachments view
     7576                this.controller.on( 'attachment:keydown:arrow',     _.bind( this.attachments.arrowEvent, this.attachments ) );
     7577                this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) );
    71957578
    7196                 return this;
    7197         },
    7198         /**
    7199          * @param {string} id
    7200          * @returns {wp.media.View}
    7201          */
    7202         get: function( id ) {
    7203                 return this._views[ id ];
    7204         },
    7205         /**
    7206          * @param {string} id
    7207          * @returns {wp.media.view.PriorityList}
    7208          */
    7209         unset: function( id ) {
    7210                 var view = this.get( id );
     7579                this.views.add( this.attachments );
    72117580
    7212                 if ( view ) {
    7213                         view.remove();
    7214                 }
    72157581
    7216                 delete this._views[ id ];
    7217                 return this;
    7218         },
    7219         /**
    7220          * @param {Object} options
    7221          * @returns {wp.media.View}
    7222          */
    7223         toView: function( options ) {
    7224                 return new wp.media.View( options );
    7225         }
    7226 });
     7582                if ( this.controller.isModeActive( 'grid' ) ) {
     7583                        this.attachmentsNoResults = new View({
     7584                                controller: this.controller,
     7585                                tagName: 'p'
     7586                        });
    72277587
    7228 module.exports = PriorityList;
     7588                        this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
     7589                        this.attachmentsNoResults.$el.html( l10n.noMedia );
    72297590
    7230 },{}],55:[function(require,module,exports){
    7231 /**
    7232  * wp.media.view.RouterItem
    7233  *
    7234  * @memberOf wp.media.view
    7235  *
    7236  * @class
    7237  * @augments wp.media.view.MenuItem
    7238  * @augments wp.media.View
    7239  * @augments wp.Backbone.View
    7240  * @augments Backbone.View
    7241  */
    7242 var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{
    7243         /**
    7244          * On click handler to activate the content region's corresponding mode.
    7245          */
    7246         click: function() {
    7247                 var contentMode = this.options.contentMode;
    7248                 if ( contentMode ) {
    7249                         this.controller.content.mode( contentMode );
     7591                        this.views.add( this.attachmentsNoResults );
    72507592                }
    7251         }
    7252 });
     7593        },
    72537594
    7254 module.exports = RouterItem;
     7595        createSidebar: function() {
     7596                var options = this.options,
     7597                        selection = options.selection,
     7598                        sidebar = this.sidebar = new wp.media.view.Sidebar({
     7599                                controller: this.controller
     7600                        });
    72557601
    7256 },{}],56:[function(require,module,exports){
    7257 var Menu = wp.media.view.Menu,
    7258         Router;
     7602                this.views.add( sidebar );
    72597603
    7260 /**
    7261  * wp.media.view.Router
    7262  *
    7263  * @memberOf wp.media.view
    7264  *
    7265  * @class
    7266  * @augments wp.media.view.Menu
    7267  * @augments wp.media.view.PriorityList
    7268  * @augments wp.media.View
    7269  * @augments wp.Backbone.View
    7270  * @augments Backbone.View
    7271  */
    7272 Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{
    7273         tagName:   'div',
    7274         className: 'media-router',
    7275         property:  'contentMode',
    7276         ItemView:  wp.media.view.RouterItem,
    7277         region:    'router',
     7604                if ( this.controller.uploader ) {
     7605                        sidebar.set( 'uploads', new wp.media.view.UploaderStatus({
     7606                                controller: this.controller,
     7607                                priority:   40
     7608                        }) );
     7609                }
    72787610
    7279         initialize: function() {
    7280                 this.controller.on( 'content:render', this.update, this );
    7281                 // Call 'initialize' directly on the parent class.
    7282                 Menu.prototype.initialize.apply( this, arguments );
    7283         },
     7611                selection.on( 'selection:single', this.createSingle, this );
     7612                selection.on( 'selection:unsingle', this.disposeSingle, this );
    72847613
    7285         update: function() {
    7286                 var mode = this.controller.content.mode();
    7287                 if ( mode ) {
    7288                         this.select( mode );
     7614                if ( selection.single() ) {
     7615                        this.createSingle();
    72897616                }
    7290         }
    7291 });
     7617        },
     7618
     7619        createSingle: function() {
     7620                var sidebar = this.sidebar,
     7621                        single = this.options.selection.single();
    72927622
    7293 module.exports = Router;
     7623                sidebar.set( 'details', new wp.media.view.Attachment.Details({
     7624                        controller: this.controller,
     7625                        model:      single,
     7626                        priority:   80
     7627                }) );
    72947628
    7295 },{}],57:[function(require,module,exports){
    7296 var l10n = wp.media.view.l10n,
    7297         Search;
     7629                sidebar.set( 'compat', new wp.media.view.AttachmentCompat({
     7630                        controller: this.controller,
     7631                        model:      single,
     7632                        priority:   120
     7633                }) );
    72987634
    7299 /**
    7300  * wp.media.view.Search
    7301  *
    7302  * @memberOf wp.media.view
    7303  *
    7304  * @class
    7305  * @augments wp.media.View
    7306  * @augments wp.Backbone.View
    7307  * @augments Backbone.View
    7308  */
    7309 Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{
    7310         tagName:   'input',
    7311         className: 'search',
    7312         id:        'media-search-input',
     7635                if ( this.options.display ) {
     7636                        sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({
     7637                                controller:   this.controller,
     7638                                model:        this.model.display( single ),
     7639                                attachment:   single,
     7640                                priority:     160,
     7641                                userSettings: this.model.get('displayUserSettings')
     7642                        }) );
     7643                }
    73137644
    7314         attributes: {
    7315                 type:        'search',
    7316                 placeholder: l10n.searchMediaPlaceholder
     7645                // Show the sidebar on mobile
     7646                if ( this.model.id === 'insert' ) {
     7647                        sidebar.$el.addClass( 'visible' );
     7648                }
    73177649        },
    73187650
    7319         events: {
    7320                 'input':  'search',
    7321                 'keyup':  'search'
    7322         },
     7651        disposeSingle: function() {
     7652                var sidebar = this.sidebar;
     7653                sidebar.unset('details');
     7654                sidebar.unset('compat');
     7655                sidebar.unset('display');
     7656                // Hide the sidebar on mobile
     7657                sidebar.$el.removeClass( 'visible' );
     7658        }
     7659});
    73237660
    7324         /**
    7325          * @returns {wp.media.view.Search} Returns itself to allow chaining
    7326          */
    7327         render: function() {
    7328                 this.el.value = this.model.escape('search');
    7329                 return this;
    7330         },
     7661module.exports = AttachmentsBrowser;
    73317662
    7332         search: _.debounce( function( event ) {
    7333                 if ( event.target.value ) {
    7334                         this.model.set( 'search', event.target.value );
    7335                 } else {
    7336                         this.model.unset('search');
    7337                 }
    7338         }, 300 )
    7339 });
    73407663
    7341 module.exports = Search;
     7664/***/ }),
     7665/* 79 */
     7666/***/ (function(module, exports) {
    73427667
    7343 },{}],58:[function(require,module,exports){
    73447668var l10n = wp.media.view.l10n,
    73457669        Selection;
    73467670
    Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */ 
    74257749
    74267750module.exports = Selection;
    74277751
    7428 },{}],59:[function(require,module,exports){
     7752
     7753/***/ }),
     7754/* 80 */
     7755/***/ (function(module, exports) {
     7756
     7757/**
     7758 * wp.media.view.Attachment.Selection
     7759 *
     7760 * @memberOf wp.media.view.Attachment
     7761 *
     7762 * @class
     7763 * @augments wp.media.view.Attachment
     7764 * @augments wp.media.View
     7765 * @augments wp.Backbone.View
     7766 * @augments Backbone.View
     7767 */
     7768var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{
     7769        className: 'attachment selection',
     7770
     7771        // On click, just select the model, instead of removing the model from
     7772        // the selection.
     7773        toggleSelection: function() {
     7774                this.options.selection.single( this.model );
     7775        }
     7776});
     7777
     7778module.exports = Selection;
     7779
     7780
     7781/***/ }),
     7782/* 81 */
     7783/***/ (function(module, exports) {
     7784
     7785var Attachments = wp.media.view.Attachments,
     7786        Selection;
     7787
     7788/**
     7789 * wp.media.view.Attachments.Selection
     7790 *
     7791 * @memberOf wp.media.view.Attachments
     7792 *
     7793 * @class
     7794 * @augments wp.media.view.Attachments
     7795 * @augments wp.media.View
     7796 * @augments wp.Backbone.View
     7797 * @augments Backbone.View
     7798 */
     7799Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{
     7800        events: {},
     7801        initialize: function() {
     7802                _.defaults( this.options, {
     7803                        sortable:   false,
     7804                        resize:     false,
     7805
     7806                        // The single `Attachment` view to be used in the `Attachments` view.
     7807                        AttachmentView: wp.media.view.Attachment.Selection
     7808                });
     7809                // Call 'initialize' directly on the parent class.
     7810                return Attachments.prototype.initialize.apply( this, arguments );
     7811        }
     7812});
     7813
     7814module.exports = Selection;
     7815
     7816
     7817/***/ }),
     7818/* 82 */
     7819/***/ (function(module, exports) {
     7820
     7821/**
     7822 * wp.media.view.Attachment.EditSelection
     7823 *
     7824 * @memberOf wp.media.view.Attachment
     7825 *
     7826 * @class
     7827 * @augments wp.media.view.Attachment.Selection
     7828 * @augments wp.media.view.Attachment
     7829 * @augments wp.media.View
     7830 * @augments wp.Backbone.View
     7831 * @augments Backbone.View
     7832 */
     7833var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{
     7834        buttons: {
     7835                close: true
     7836        }
     7837});
     7838
     7839module.exports = EditSelection;
     7840
     7841
     7842/***/ }),
     7843/* 83 */
     7844/***/ (function(module, exports) {
     7845
    74297846var View = wp.media.View,
    74307847        $ = Backbone.$,
    74317848        Settings;
    Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{ 
    75487965
    75497966module.exports = Settings;
    75507967
    7551 },{}],60:[function(require,module,exports){
     7968
     7969/***/ }),
     7970/* 84 */
     7971/***/ (function(module, exports) {
     7972
    75527973var Settings = wp.media.view.Settings,
    75537974        AttachmentDisplay;
    75547975
    AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.Attachment 
    76258046                        if ( 'post' === linkTo ) {
    76268047                                $input.val( attachment.get('link') );
    76278048                        } else if ( 'file' === linkTo ) {
    7628                                 $input.val( attachment.get('url') );
    7629                         } else if ( ! this.model.get('linkUrl') ) {
    7630                                 $input.val('http://');
    7631                         }
    7632 
    7633                         $input.prop( 'readonly', 'custom' !== linkTo );
    7634                 }
    7635 
    7636                 $input.removeClass( 'hidden' );
    7637 
    7638                 // If the input is visible, focus and select its contents.
    7639                 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
    7640                         $input.focus()[0].select();
    7641                 }
    7642         }
    7643 });
    7644 
    7645 module.exports = AttachmentDisplay;
    7646 
    7647 },{}],61:[function(require,module,exports){
    7648 /**
    7649  * wp.media.view.Settings.Gallery
    7650  *
    7651  * @memberOf wp.media.view.Settings
    7652  *
    7653  * @class
    7654  * @augments wp.media.view.Settings
    7655  * @augments wp.media.View
    7656  * @augments wp.Backbone.View
    7657  * @augments Backbone.View
    7658  */
    7659 var Gallery = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Gallery.prototype */{
    7660         className: 'collection-settings gallery-settings',
    7661         template:  wp.template('gallery-settings')
    7662 });
    7663 
    7664 module.exports = Gallery;
    7665 
    7666 },{}],62:[function(require,module,exports){
    7667 /**
    7668  * wp.media.view.Settings.Playlist
    7669  *
    7670  * @memberOf wp.media.view.Settings
    7671  *
    7672  * @class
    7673  * @augments wp.media.view.Settings
    7674  * @augments wp.media.View
    7675  * @augments wp.Backbone.View
    7676  * @augments Backbone.View
    7677  */
    7678 var Playlist = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Playlist.prototype */{
    7679         className: 'collection-settings playlist-settings',
    7680         template:  wp.template('playlist-settings')
    7681 });
    7682 
    7683 module.exports = Playlist;
    7684 
    7685 },{}],63:[function(require,module,exports){
    7686 /**
    7687  * wp.media.view.Sidebar
    7688  *
    7689  * @memberOf wp.media.view
    7690  *
    7691  * @class
    7692  * @augments wp.media.view.PriorityList
    7693  * @augments wp.media.View
    7694  * @augments wp.Backbone.View
    7695  * @augments Backbone.View
    7696  */
    7697 var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{
    7698         className: 'media-sidebar'
    7699 });
    7700 
    7701 module.exports = Sidebar;
    7702 
    7703 },{}],64:[function(require,module,exports){
    7704 var View = wp.media.view,
    7705         SiteIconCropper;
    7706 
    7707 /**
    7708  * wp.media.view.SiteIconCropper
    7709  *
    7710  * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon.
    7711  *
    7712  * Takes imgAreaSelect options from
    7713  * wp.customize.SiteIconControl.calculateImageSelectOptions.
    7714  *
    7715  * @memberOf wp.media.view
    7716  *
    7717  * @class
    7718  * @augments wp.media.view.Cropper
    7719  * @augments wp.media.View
    7720  * @augments wp.Backbone.View
    7721  * @augments Backbone.View
    7722  */
    7723 SiteIconCropper = View.Cropper.extend(/** @lends wp.media.view.SiteIconCropper.prototype */{
    7724         className: 'crop-content site-icon',
    7725 
    7726         ready: function () {
    7727                 View.Cropper.prototype.ready.apply( this, arguments );
    7728 
    7729                 this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) );
    7730         },
     8049                                $input.val( attachment.get('url') );
     8050                        } else if ( ! this.model.get('linkUrl') ) {
     8051                                $input.val('http://');
     8052                        }
    77318053
    7732         addSidebar: function() {
    7733                 this.sidebar = new wp.media.view.Sidebar({
    7734                         controller: this.controller
    7735                 });
     8054                        $input.prop( 'readonly', 'custom' !== linkTo );
     8055                }
    77368056
    7737                 this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({
    7738                         controller: this.controller,
    7739                         attachment: this.options.attachment
    7740                 }) );
     8057                $input.removeClass( 'hidden' );
    77418058
    7742                 this.controller.cropperView.views.add( this.sidebar );
     8059                // If the input is visible, focus and select its contents.
     8060                if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
     8061                        $input.focus()[0].select();
     8062                }
    77438063        }
    77448064});
    77458065
    7746 module.exports = SiteIconCropper;
     8066module.exports = AttachmentDisplay;
    77478067
    7748 },{}],65:[function(require,module,exports){
    7749 var View = wp.media.View,
    7750         $ = jQuery,
    7751         SiteIconPreview;
     8068
     8069/***/ }),
     8070/* 85 */
     8071/***/ (function(module, exports) {
    77528072
    77538073/**
    7754  * wp.media.view.SiteIconPreview
    7755  *
    7756  * Shows a preview of the Site Icon as a favicon and app icon while cropping.
     8074 * wp.media.view.Settings.Gallery
    77578075 *
    7758  * @memberOf wp.media.view
     8076 * @memberOf wp.media.view.Settings
    77598077 *
    77608078 * @class
     8079 * @augments wp.media.view.Settings
    77618080 * @augments wp.media.View
    77628081 * @augments wp.Backbone.View
    77638082 * @augments Backbone.View
    77648083 */
    7765 SiteIconPreview = View.extend(/** @lends wp.media.view.SiteIconPreview.prototype */{
    7766         className: 'site-icon-preview',
    7767         template: wp.template( 'site-icon-preview' ),
    7768 
    7769         ready: function() {
    7770                 this.controller.imgSelect.setOptions({
    7771                         onInit: this.updatePreview,
    7772                         onSelectChange: this.updatePreview
    7773                 });
    7774         },
    7775 
    7776         prepare: function() {
    7777                 return {
    7778                         url: this.options.attachment.get( 'url' )
    7779                 };
    7780         },
    7781 
    7782         updatePreview: function( img, coords ) {
    7783                 var rx = 64 / coords.width,
    7784                         ry = 64 / coords.height,
    7785                         preview_rx = 16 / coords.width,
    7786                         preview_ry = 16 / coords.height;
     8084var Gallery = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Gallery.prototype */{
     8085        className: 'collection-settings gallery-settings',
     8086        template:  wp.template('gallery-settings')
     8087});
    77878088
    7788                 $( '#preview-app-icon' ).css({
    7789                         width: Math.round(rx * this.imageWidth ) + 'px',
    7790                         height: Math.round(ry * this.imageHeight ) + 'px',
    7791                         marginLeft: '-' + Math.round(rx * coords.x1) + 'px',
    7792                         marginTop: '-' + Math.round(ry * coords.y1) + 'px'
    7793                 });
     8089module.exports = Gallery;
    77948090
    7795                 $( '#preview-favicon' ).css({
    7796                         width: Math.round( preview_rx * this.imageWidth ) + 'px',
    7797                         height: Math.round( preview_ry * this.imageHeight ) + 'px',
    7798                         marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px',
    7799                         marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px'
    7800                 });
    7801         }
    7802 });
    78038091
    7804 module.exports = SiteIconPreview;
     8092/***/ }),
     8093/* 86 */
     8094/***/ (function(module, exports) {
    78058095
    7806 },{}],66:[function(require,module,exports){
    78078096/**
    7808  * wp.media.view.Spinner
     8097 * wp.media.view.Settings.Playlist
    78098098 *
    7810  * @memberOf wp.media.view
     8099 * @memberOf wp.media.view.Settings
    78118100 *
    78128101 * @class
     8102 * @augments wp.media.view.Settings
    78138103 * @augments wp.media.View
    78148104 * @augments wp.Backbone.View
    78158105 * @augments Backbone.View
    78168106 */
    7817 var Spinner = wp.media.View.extend(/** @lends wp.media.view.Spinner.prototype */{
    7818         tagName:   'span',
    7819         className: 'spinner',
    7820         spinnerTimeout: false,
    7821         delay: 400,
    7822 
    7823         show: function() {
    7824                 if ( ! this.spinnerTimeout ) {
    7825                         this.spinnerTimeout = _.delay(function( $el ) {
    7826                                 $el.addClass( 'is-active' );
    7827                         }, this.delay, this.$el );
    7828                 }
    7829 
    7830                 return this;
    7831         },
     8107var Playlist = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Playlist.prototype */{
     8108        className: 'collection-settings playlist-settings',
     8109        template:  wp.template('playlist-settings')
     8110});
    78328111
    7833         hide: function() {
    7834                 this.$el.removeClass( 'is-active' );
    7835                 this.spinnerTimeout = clearTimeout( this.spinnerTimeout );
     8112module.exports = Playlist;
    78368113
    7837                 return this;
    7838         }
    7839 });
    78408114
    7841 module.exports = Spinner;
     8115/***/ }),
     8116/* 87 */
     8117/***/ (function(module, exports) {
    78428118
    7843 },{}],67:[function(require,module,exports){
    7844 var View = wp.media.View,
    7845         Toolbar;
     8119var Attachment = wp.media.view.Attachment,
     8120        l10n = wp.media.view.l10n,
     8121        Details;
    78468122
    78478123/**
    7848  * wp.media.view.Toolbar
    7849  *
    7850  * A toolbar which consists of a primary and a secondary section. Each sections
    7851  * can be filled with views.
     8124 * wp.media.view.Attachment.Details
    78528125 *
    7853  * @memberOf wp.media.view
     8126 * @memberOf wp.media.view.Attachment
    78548127 *
    78558128 * @class
     8129 * @augments wp.media.view.Attachment
    78568130 * @augments wp.media.View
    78578131 * @augments wp.Backbone.View
    78588132 * @augments Backbone.View
    78598133 */
    7860 Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{
     8134Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{
    78618135        tagName:   'div',
    7862         className: 'media-toolbar',
     8136        className: 'attachment-details',
     8137        template:  wp.template('attachment-details'),
     8138
     8139        attributes: function() {
     8140                return {
     8141                        'tabIndex':     0,
     8142                        'data-id':      this.model.get( 'id' )
     8143                };
     8144        },
     8145
     8146        events: {
     8147                'change [data-setting]':          'updateSetting',
     8148                'change [data-setting] input':    'updateSetting',
     8149                'change [data-setting] select':   'updateSetting',
     8150                'change [data-setting] textarea': 'updateSetting',
     8151                'click .delete-attachment':       'deleteAttachment',
     8152                'click .trash-attachment':        'trashAttachment',
     8153                'click .untrash-attachment':      'untrashAttachment',
     8154                'click .edit-attachment':         'editAttachment',
     8155                'keydown':                        'toggleSelectionHandler'
     8156        },
    78638157
    78648158        initialize: function() {
    7865                 var state = this.controller.state(),
    7866                         selection = this.selection = state.get('selection'),
    7867                         library = this.library = state.get('library');
     8159                this.options = _.defaults( this.options, {
     8160                        rerenderOnModelChange: false
     8161                });
    78688162
    7869                 this._views = {};
     8163                this.on( 'ready', this.initialFocus );
     8164                // Call 'initialize' directly on the parent class.
     8165                Attachment.prototype.initialize.apply( this, arguments );
     8166        },
    78708167
    7871                 // The toolbar is composed of two `PriorityList` views.
    7872                 this.primary   = new wp.media.view.PriorityList();
    7873                 this.secondary = new wp.media.view.PriorityList();
    7874                 this.primary.$el.addClass('media-toolbar-primary search-form');
    7875                 this.secondary.$el.addClass('media-toolbar-secondary');
     8168        initialFocus: function() {
     8169                if ( ! wp.media.isTouchDevice ) {
     8170                        /*
     8171                        Previously focused the first ':input' (the readonly URL text field).
     8172                        Since the first ':input' is now a button (delete/trash): when pressing
     8173                        spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
     8174                        as soon as focus is moved. Explicitly target the first text field for now.
     8175                        @todo change initial focus logic, also for accessibility.
     8176                        */
     8177                        this.$( 'input[type="text"]' ).eq( 0 ).focus();
     8178                }
     8179        },
     8180        /**
     8181         * @param {Object} event
     8182         */
     8183        deleteAttachment: function( event ) {
     8184                event.preventDefault();
    78768185
    7877                 this.views.set([ this.secondary, this.primary ]);
     8186                if ( window.confirm( l10n.warnDelete ) ) {
     8187                        this.model.destroy();
     8188                        // Keep focus inside media modal
     8189                        // after image is deleted
     8190                        this.controller.modal.focusManager.focus();
     8191                }
     8192        },
     8193        /**
     8194         * @param {Object} event
     8195         */
     8196        trashAttachment: function( event ) {
     8197                var library = this.controller.library;
     8198                event.preventDefault();
    78788199
    7879                 if ( this.options.items ) {
    7880                         this.set( this.options.items, { silent: true });
     8200                if ( wp.media.view.settings.mediaTrash &&
     8201                        'edit-metadata' === this.controller.content.mode() ) {
     8202
     8203                        this.model.set( 'status', 'trash' );
     8204                        this.model.save().done( function() {
     8205                                library._requery( true );
     8206                        } );
     8207                }  else {
     8208                        this.model.destroy();
    78818209                }
     8210        },
     8211        /**
     8212         * @param {Object} event
     8213         */
     8214        untrashAttachment: function( event ) {
     8215                var library = this.controller.library;
     8216                event.preventDefault();
    78828217
    7883                 if ( ! this.options.silent ) {
    7884                         this.render();
     8218                this.model.set( 'status', 'inherit' );
     8219                this.model.save().done( function() {
     8220                        library._requery( true );
     8221                } );
     8222        },
     8223        /**
     8224         * @param {Object} event
     8225         */
     8226        editAttachment: function( event ) {
     8227                var editState = this.controller.states.get( 'edit-image' );
     8228                if ( window.imageEdit && editState ) {
     8229                        event.preventDefault();
     8230
     8231                        editState.set( 'image', this.model );
     8232                        this.controller.setState( 'edit-image' );
     8233                } else {
     8234                        this.$el.addClass('needs-refresh');
     8235                }
     8236        },
     8237        /**
     8238         * When reverse tabbing(shift+tab) out of the right details panel, deliver
     8239         * the focus to the item in the list that was being edited.
     8240         *
     8241         * @param {Object} event
     8242         */
     8243        toggleSelectionHandler: function( event ) {
     8244                if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
     8245                        this.controller.trigger( 'attachment:details:shift-tab', event );
     8246                        return false;
    78858247                }
    78868248
    7887                 if ( selection ) {
    7888                         selection.on( 'add remove reset', this.refresh, this );
    7889                 }
     8249                if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     8250                        this.controller.trigger( 'attachment:keydown:arrow', event );
     8251                        return;
     8252                }
     8253        }
     8254});
     8255
     8256module.exports = Details;
     8257
     8258
     8259/***/ }),
     8260/* 88 */
     8261/***/ (function(module, exports) {
     8262
     8263var View = wp.media.View,
     8264        AttachmentCompat;
     8265
     8266/**
     8267 * wp.media.view.AttachmentCompat
     8268 *
     8269 * A view to display fields added via the `attachment_fields_to_edit` filter.
     8270 *
     8271 * @memberOf wp.media.view
     8272 *
     8273 * @class
     8274 * @augments wp.media.View
     8275 * @augments wp.Backbone.View
     8276 * @augments Backbone.View
     8277 */
     8278AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{
     8279        tagName:   'form',
     8280        className: 'compat-item',
     8281
     8282        events: {
     8283                'submit':          'preventDefault',
     8284                'change input':    'save',
     8285                'change select':   'save',
     8286                'change textarea': 'save'
     8287        },
    78908288
    7891                 if ( library ) {
    7892                         library.on( 'add remove reset', this.refresh, this );
    7893                 }
     8289        initialize: function() {
     8290                this.listenTo( this.model, 'change:compat', this.render );
    78948291        },
    78958292        /**
    7896          * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
     8293         * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    78978294         */
    78988295        dispose: function() {
    7899                 if ( this.selection ) {
    7900                         this.selection.off( null, null, this );
    7901                 }
    7902 
    7903                 if ( this.library ) {
    7904                         this.library.off( null, null, this );
     8296                if ( this.$(':focus').length ) {
     8297                        this.save();
    79058298                }
    79068299                /**
    79078300                 * call 'dispose' directly on the parent class
    79088301                 */
    79098302                return View.prototype.dispose.apply( this, arguments );
    79108303        },
    7911 
    7912         ready: function() {
    7913                 this.refresh();
    7914         },
    7915 
    79168304        /**
    7917          * @param {string} id
    7918          * @param {Backbone.View|Object} view
    7919          * @param {Object} [options={}]
    7920          * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     8305         * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    79218306         */
    7922         set: function( id, view, options ) {
    7923                 var list;
    7924                 options = options || {};
    7925 
    7926                 // Accept an object with an `id` : `view` mapping.
    7927                 if ( _.isObject( id ) ) {
    7928                         _.each( id, function( view, id ) {
    7929                                 this.set( id, view, { silent: true });
    7930                         }, this );
    7931 
    7932                 } else {
    7933                         if ( ! ( view instanceof Backbone.View ) ) {
    7934                                 view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
    7935                                 view = new wp.media.view.Button( view ).render();
    7936                         }
    7937 
    7938                         view.controller = view.controller || this.controller;
    7939 
    7940                         this._views[ id ] = view;
    7941 
    7942                         list = view.options.priority < 0 ? 'secondary' : 'primary';
    7943                         this[ list ].set( id, view, options );
    7944                 }
    7945 
    7946                 if ( ! options.silent ) {
    7947                         this.refresh();
     8307        render: function() {
     8308                var compat = this.model.get('compat');
     8309                if ( ! compat || ! compat.item ) {
     8310                        return;
    79488311                }
    79498312
     8313                this.views.detach();
     8314                this.$el.html( compat.item );
     8315                this.views.render();
    79508316                return this;
    79518317        },
    79528318        /**
    7953          * @param {string} id
    7954          * @returns {wp.media.view.Button}
     8319         * @param {Object} event
    79558320         */
    7956         get: function( id ) {
    7957                 return this._views[ id ];
     8321        preventDefault: function( event ) {
     8322                event.preventDefault();
    79588323        },
    79598324        /**
    7960          * @param {string} id
    7961          * @param {Object} options
    7962          * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     8325         * @param {Object} event
    79638326         */
    7964         unset: function( id, options ) {
    7965                 delete this._views[ id ];
    7966                 this.primary.unset( id, options );
    7967                 this.secondary.unset( id, options );
     8327        save: function( event ) {
     8328                var data = {};
    79688329
    7969                 if ( ! options || ! options.silent ) {
    7970                         this.refresh();
     8330                if ( event ) {
     8331                        event.preventDefault();
    79718332                }
    7972                 return this;
    7973         },
    7974 
    7975         refresh: function() {
    7976                 var state = this.controller.state(),
    7977                         library = state.get('library'),
    7978                         selection = state.get('selection');
    7979 
    7980                 _.each( this._views, function( button ) {
    7981                         if ( ! button.model || ! button.options || ! button.options.requires ) {
    7982                                 return;
    7983                         }
    79848333
    7985                         var requires = button.options.requires,
    7986                                 disabled = false;
     8334                _.each( this.$el.serializeArray(), function( pair ) {
     8335                        data[ pair.name ] = pair.value;
     8336                });
    79878337
    7988                         // Prevent insertion of attachments if any of them are still uploading
    7989                         if ( selection && selection.models ) {
    7990                                 disabled = _.some( selection.models, function( attachment ) {
    7991                                         return attachment.get('uploading') === true;
    7992                                 });
    7993                         }
     8338                this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
     8339                this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
     8340        },
    79948341
    7995                         if ( requires.selection && selection && ! selection.length ) {
    7996                                 disabled = true;
    7997                         } else if ( requires.library && library && ! library.length ) {
    7998                                 disabled = true;
    7999                         }
    8000                         button.model.set( 'disabled', disabled );
    8001                 });
     8342        postSave: function() {
     8343                this.controller.trigger( 'attachment:compat:ready', ['ready'] );
    80028344        }
    80038345});
    80048346
    8005 module.exports = Toolbar;
     8347module.exports = AttachmentCompat;
    80068348
    8007 },{}],68:[function(require,module,exports){
    8008 var Select = wp.media.view.Toolbar.Select,
    8009         l10n = wp.media.view.l10n,
    8010         Embed;
     8349
     8350/***/ }),
     8351/* 89 */
     8352/***/ (function(module, exports) {
    80118353
    80128354/**
    8013  * wp.media.view.Toolbar.Embed
     8355 * wp.media.view.Iframe
    80148356 *
    8015  * @memberOf wp.media.view.Toolbar
     8357 * @memberOf wp.media.view
    80168358 *
    80178359 * @class
    8018  * @augments wp.media.view.Toolbar.Select
    8019  * @augments wp.media.view.Toolbar
    80208360 * @augments wp.media.View
    80218361 * @augments wp.Backbone.View
    80228362 * @augments Backbone.View
    80238363 */
    8024 Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{
    8025         initialize: function() {
    8026                 _.defaults( this.options, {
    8027                         text: l10n.insertIntoPost,
    8028                         requires: false
    8029                 });
    8030                 // Call 'initialize' directly on the parent class.
    8031                 Select.prototype.initialize.apply( this, arguments );
    8032         },
    8033 
    8034         refresh: function() {
    8035                 var url = this.controller.state().props.get('url');
    8036                 this.get('select').model.set( 'disabled', ! url || url === 'http://' );
    8037                 /**
    8038                  * call 'refresh' directly on the parent class
    8039                  */
    8040                 Select.prototype.refresh.apply( this, arguments );
     8364var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{
     8365        className: 'media-iframe',
     8366        /**
     8367         * @returns {wp.media.view.Iframe} Returns itself to allow chaining
     8368         */
     8369        render: function() {
     8370                this.views.detach();
     8371                this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
     8372                this.views.render();
     8373                return this;
    80418374        }
    80428375});
    80438376
    8044 module.exports = Embed;
     8377module.exports = Iframe;
    80458378
    8046 },{}],69:[function(require,module,exports){
    8047 var Toolbar = wp.media.view.Toolbar,
    8048         l10n = wp.media.view.l10n,
    8049         Select;
     8379
     8380/***/ }),
     8381/* 90 */
     8382/***/ (function(module, exports) {
    80508383
    80518384/**
    8052  * wp.media.view.Toolbar.Select
     8385 * wp.media.view.Embed
    80538386 *
    8054  * @memberOf wp.media.view.Toolbar
     8387 * @memberOf wp.media.view
    80558388 *
    80568389 * @class
    8057  * @augments wp.media.view.Toolbar
    80588390 * @augments wp.media.View
    80598391 * @augments wp.Backbone.View
    80608392 * @augments Backbone.View
    80618393 */
    8062 Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{
    8063         initialize: function() {
    8064                 var options = this.options;
    8065 
    8066                 _.bindAll( this, 'clickSelect' );
    8067 
    8068                 _.defaults( options, {
    8069                         event: 'select',
    8070                         state: false,
    8071                         reset: true,
    8072                         close: true,
    8073                         text:  l10n.select,
     8394var Embed = wp.media.View.extend(/** @lends wp.media.view.Ember.prototype */{
     8395        className: 'media-embed',
    80748396
    8075                         // Does the button rely on the selection?
    8076                         requires: {
    8077                                 selection: true
    8078                         }
    8079                 });
     8397        initialize: function() {
     8398                /**
     8399                 * @member {wp.media.view.EmbedUrl}
     8400                 */
     8401                this.url = new wp.media.view.EmbedUrl({
     8402                        controller: this.controller,
     8403                        model:      this.model.props
     8404                }).render();
    80808405
    8081                 options.items = _.defaults( options.items || {}, {
    8082                         select: {
    8083                                 style:    'primary',
    8084                                 text:     options.text,
    8085                                 priority: 80,
    8086                                 click:    this.clickSelect,
    8087                                 requires: options.requires
    8088                         }
    8089                 });
    8090                 // Call 'initialize' directly on the parent class.
    8091                 Toolbar.prototype.initialize.apply( this, arguments );
     8406                this.views.set([ this.url ]);
     8407                this.refresh();
     8408                this.listenTo( this.model, 'change:type', this.refresh );
     8409                this.listenTo( this.model, 'change:loading', this.loading );
    80928410        },
    80938411
    8094         clickSelect: function() {
    8095                 var options = this.options,
    8096                         controller = this.controller;
    8097 
    8098                 if ( options.close ) {
    8099                         controller.close();
     8412        /**
     8413         * @param {Object} view
     8414         */
     8415        settings: function( view ) {
     8416                if ( this._settings ) {
     8417                        this._settings.remove();
    81008418                }
     8419                this._settings = view;
     8420                this.views.add( view );
     8421        },
    81018422
    8102                 if ( options.event ) {
    8103                         controller.state().trigger( options.event );
     8423        refresh: function() {
     8424                var type = this.model.get('type'),
     8425                        constructor;
     8426
     8427                if ( 'image' === type ) {
     8428                        constructor = wp.media.view.EmbedImage;
     8429                } else if ( 'link' === type ) {
     8430                        constructor = wp.media.view.EmbedLink;
     8431                } else {
     8432                        return;
    81048433                }
    81058434
    8106                 if ( options.state ) {
    8107                         controller.setState( options.state );
    8108                 }
     8435                this.settings( new constructor({
     8436                        controller: this.controller,
     8437                        model:      this.model.props,
     8438                        priority:   40
     8439                }) );
     8440        },
    81098441
    8110                 if ( options.reset ) {
    8111                         controller.reset();
    8112                 }
     8442        loading: function() {
     8443                this.$el.toggleClass( 'embed-loading', this.model.get('loading') );
    81138444        }
    81148445});
    81158446
    8116 module.exports = Select;
     8447module.exports = Embed;
    81178448
    8118 },{}],70:[function(require,module,exports){
    8119 var View = wp.media.View,
    8120         l10n = wp.media.view.l10n,
    8121         $ = jQuery,
    8122         EditorUploader;
     8449
     8450/***/ }),
     8451/* 91 */
     8452/***/ (function(module, exports) {
    81238453
    81248454/**
    8125  * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
    8126  * and relays drag'n'dropped files to a media workflow.
    8127  *
    8128  * wp.media.view.EditorUploader
     8455 * wp.media.view.Label
    81298456 *
    81308457 * @memberOf wp.media.view
    81318458 *
    var View = wp.media.View, 
    81348461 * @augments wp.Backbone.View
    81358462 * @augments Backbone.View
    81368463 */
    8137 EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{
    8138         tagName:   'div',
    8139         className: 'uploader-editor',
    8140         template:  wp.template( 'uploader-editor' ),
    8141 
    8142         localDrag: false,
    8143         overContainer: false,
    8144         overDropzone: false,
    8145         draggingFile: null,
     8464var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{
     8465        tagName: 'label',
     8466        className: 'screen-reader-text',
    81468467
    8147         /**
    8148          * Bind drag'n'drop events to callbacks.
    8149          */
    81508468        initialize: function() {
    8151                 this.initialized = false;
    8152 
    8153                 // Bail if not enabled or UA does not support drag'n'drop or File API.
    8154                 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
    8155                         return this;
    8156                 }
     8469                this.value = this.options.value;
     8470        },
    81578471
    8158                 this.$document = $(document);
    8159                 this.dropzones = [];
    8160                 this.files = [];
     8472        render: function() {
     8473                this.$el.html( this.value );
    81618474
    8162                 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
    8163                 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
    8164                 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
    8165                 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
     8475                return this;
     8476        }
     8477});
    81668478
    8167                 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
    8168                 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
     8479module.exports = Label;
    81698480
    8170                 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
    8171                         this.localDrag = event.type === 'dragstart';
    81728481
    8173                         if ( event.type === 'drop' ) {
    8174                                 this.containerDragleave();
    8175                         }
    8176                 }, this ) );
     8482/***/ }),
     8483/* 92 */
     8484/***/ (function(module, exports) {
    81778485
    8178                 this.initialized = true;
    8179                 return this;
    8180         },
     8486var View = wp.media.View,
     8487        $ = jQuery,
     8488        EmbedUrl;
    81818489
    8182         /**
    8183          * Check browser support for drag'n'drop.
    8184          *
    8185          * @return Boolean
    8186          */
    8187         browserSupport: function() {
    8188                 var supports = false, div = document.createElement('div');
     8490/**
     8491 * wp.media.view.EmbedUrl
     8492 *
     8493 * @memberOf wp.media.view
     8494 *
     8495 * @class
     8496 * @augments wp.media.View
     8497 * @augments wp.Backbone.View
     8498 * @augments Backbone.View
     8499 */
     8500EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{
     8501        tagName:   'label',
     8502        className: 'embed-url',
    81898503
    8190                 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
    8191                 supports = supports && !! ( window.File && window.FileList && window.FileReader );
    8192                 return supports;
     8504        events: {
     8505                'input':  'url',
     8506                'keyup':  'url',
     8507                'change': 'url'
    81938508        },
    81948509
    8195         isDraggingFile: function( event ) {
    8196                 if ( this.draggingFile !== null ) {
    8197                         return this.draggingFile;
    8198                 }
    8199 
    8200                 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
    8201                         return false;
    8202                 }
    8203 
    8204                 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
    8205                         _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
     8510        initialize: function() {
     8511                this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
     8512                this.input = this.$input[0];
    82068513
    8207                 return this.draggingFile;
    8208         },
     8514                this.spinner = $('<span class="spinner" />')[0];
     8515                this.$el.append([ this.input, this.spinner ]);
    82098516
    8210         refresh: function( e ) {
    8211                 var dropzone_id;
    8212                 for ( dropzone_id in this.dropzones ) {
    8213                         // Hide the dropzones only if dragging has left the screen.
    8214                         this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
    8215                 }
     8517                this.listenTo( this.model, 'change:url', this.render );
    82168518
    8217                 if ( ! _.isUndefined( e ) ) {
    8218                         $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
     8519                if ( this.model.get( 'url' ) ) {
     8520                        _.delay( _.bind( function () {
     8521                                this.model.trigger( 'change:url' );
     8522                        }, this ), 500 );
    82198523                }
     8524        },
     8525        /**
     8526         * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
     8527         */
     8528        render: function() {
     8529                var $input = this.$input;
    82208530
    8221                 if ( ! this.overContainer && ! this.overDropzone ) {
    8222                         this.draggingFile = null;
     8531                if ( $input.is(':focus') ) {
     8532                        return;
    82238533                }
    82248534
     8535                this.input.value = this.model.get('url') || 'http://';
     8536                /**
     8537                 * Call `render` directly on parent class with passed arguments
     8538                 */
     8539                View.prototype.render.apply( this, arguments );
    82258540                return this;
    82268541        },
    82278542
    8228         render: function() {
    8229                 if ( ! this.initialized ) {
    8230                         return this;
     8543        ready: function() {
     8544                if ( ! wp.media.isTouchDevice ) {
     8545                        this.focus();
    82318546                }
    8232 
    8233                 View.prototype.render.apply( this, arguments );
    8234                 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
    8235                 return this;
    82368547        },
    82378548
    8238         attach: function( index, editor ) {
    8239                 // Attach a dropzone to an editor.
    8240                 var dropzone = this.$el.clone();
    8241                 this.dropzones.push( dropzone );
    8242                 $( editor ).append( dropzone );
    8243                 return this;
     8549        url: function( event ) {
     8550                this.model.set( 'url', $.trim( event.target.value ) );
    82448551        },
    82458552
    82468553        /**
    8247          * When a file is dropped on the editor uploader, open up an editor media workflow
    8248          * and upload the file immediately.
    8249          *
    8250          * @param  {jQuery.Event} event The 'drop' event.
     8554         * If the input is visible, focus and select its contents.
    82518555         */
    8252         drop: function( event ) {
    8253                 var $wrap, uploadView;
     8556        focus: function() {
     8557                var $input = this.$input;
     8558                if ( $input.is(':visible') ) {
     8559                        $input.focus()[0].select();
     8560                }
     8561        }
     8562});
    82548563
    8255                 this.containerDragleave( event );
    8256                 this.dropzoneDragleave( event );
     8564module.exports = EmbedUrl;
    82578565
    8258                 this.files = event.originalEvent.dataTransfer.files;
    8259                 if ( this.files.length < 1 ) {
     8566
     8567/***/ }),
     8568/* 93 */
     8569/***/ (function(module, exports) {
     8570
     8571var $ = jQuery,
     8572        EmbedLink;
     8573
     8574/**
     8575 * wp.media.view.EmbedLink
     8576 *
     8577 * @memberOf wp.media.view
     8578 *
     8579 * @class
     8580 * @augments wp.media.view.Settings
     8581 * @augments wp.media.View
     8582 * @augments wp.Backbone.View
     8583 * @augments Backbone.View
     8584 */
     8585EmbedLink = wp.media.view.Settings.extend(/** @lends wp.media.view.EmbedLink.prototype */{
     8586        className: 'embed-link-settings',
     8587        template:  wp.template('embed-link-settings'),
     8588
     8589        initialize: function() {
     8590                this.listenTo( this.model, 'change:url', this.updateoEmbed );
     8591        },
     8592
     8593        updateoEmbed: _.debounce( function() {
     8594                var url = this.model.get( 'url' );
     8595
     8596                // clear out previous results
     8597                this.$('.embed-container').hide().find('.embed-preview').empty();
     8598                this.$( '.setting' ).hide();
     8599
     8600                // only proceed with embed if the field contains more than 11 characters
     8601                // Example: http://a.io is 11 chars
     8602                if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {
    82608603                        return;
    82618604                }
    82628605
    8263                 // Set the active editor to the drop target.
    8264                 $wrap = $( event.target ).parents( '.wp-editor-wrap' );
    8265                 if ( $wrap.length > 0 && $wrap[0].id ) {
    8266                         window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
    8267                 }
     8606                this.fetch();
     8607        }, wp.media.controller.Embed.sensitivity ),
    82688608
    8269                 if ( ! this.workflow ) {
    8270                         this.workflow = wp.media.editor.open( window.wpActiveEditor, {
    8271                                 frame:    'post',
    8272                                 state:    'insert',
    8273                                 title:    l10n.addMedia,
    8274                                 multiple: true
    8275                         });
     8609        fetch: function() {
     8610                var url = this.model.get( 'url' ), re, youTubeEmbedMatch;
    82768611
    8277                         uploadView = this.workflow.uploader;
     8612                // check if they haven't typed in 500 ms
     8613                if ( $('#embed-url-field').val() !== url ) {
     8614                        return;
     8615                }
    82788616
    8279                         if ( uploadView.uploader && uploadView.uploader.ready ) {
    8280                                 this.addFiles.apply( this );
    8281                         } else {
    8282                                 this.workflow.on( 'uploader:ready', this.addFiles, this );
    8283                         }
    8284                 } else {
    8285                         this.workflow.state().reset();
    8286                         this.addFiles.apply( this );
    8287                         this.workflow.open();
     8617                if ( this.dfd && 'pending' === this.dfd.state() ) {
     8618                        this.dfd.abort();
     8619                }
     8620
     8621                // Support YouTube embed urls, since they work once in the editor.
     8622                re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/;
     8623                youTubeEmbedMatch = re.exec( url );
     8624                if ( youTubeEmbedMatch ) {
     8625                        url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ];
    82888626                }
    82898627
    8290                 return false;
     8628                this.dfd = wp.apiRequest({
     8629                        url: wp.media.view.settings.oEmbedProxyUrl,
     8630                        data: {
     8631                                url: url,
     8632                                maxwidth: this.model.get( 'width' ),
     8633                                maxheight: this.model.get( 'height' )
     8634                        },
     8635                        type: 'GET',
     8636                        dataType: 'json',
     8637                        context: this
     8638                })
     8639                        .done( function( response ) {
     8640                                this.renderoEmbed( {
     8641                                        data: {
     8642                                                body: response.html || ''
     8643                                        }
     8644                                } );
     8645                        } )
     8646                        .fail( this.renderFail );
    82918647        },
    82928648
    8293         /**
    8294          * Add the files to the uploader.
    8295          */
    8296         addFiles: function() {
    8297                 if ( this.files.length ) {
    8298                         this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
    8299                         this.files = [];
     8649        renderFail: function ( response, status ) {
     8650                if ( 'abort' === status ) {
     8651                        return;
    83008652                }
    8301                 return this;
     8653                this.$( '.link-text' ).show();
    83028654        },
    83038655
    8304         containerDragover: function( event ) {
    8305                 if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    8306                         return;
     8656        renderoEmbed: function( response ) {
     8657                var html = ( response && response.data && response.data.body ) || '';
     8658
     8659                if ( html ) {
     8660                        this.$('.embed-container').show().find('.embed-preview').html( html );
     8661                } else {
     8662                        this.renderFail();
    83078663                }
     8664        }
     8665});
    83088666
    8309                 this.overContainer = true;
    8310                 this.refresh();
    8311         },
     8667module.exports = EmbedLink;
    83128668
    8313         containerDragleave: function() {
    8314                 this.overContainer = false;
    83158669
    8316                 // Throttle dragleave because it's called when bouncing from some elements to others.
    8317                 _.delay( _.bind( this.refresh, this ), 50 );
    8318         },
     8670/***/ }),
     8671/* 94 */
     8672/***/ (function(module, exports) {
    83198673
    8320         dropzoneDragover: function( event ) {
    8321                 if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    8322                         return;
    8323                 }
     8674var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     8675        EmbedImage;
    83248676
    8325                 this.overDropzone = true;
    8326                 this.refresh( event );
    8327                 return false;
    8328         },
     8677/**
     8678 * wp.media.view.EmbedImage
     8679 *
     8680 * @memberOf wp.media.view
     8681 *
     8682 * @class
     8683 * @augments wp.media.view.Settings.AttachmentDisplay
     8684 * @augments wp.media.view.Settings
     8685 * @augments wp.media.View
     8686 * @augments wp.Backbone.View
     8687 * @augments Backbone.View
     8688 */
     8689EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{
     8690        className: 'embed-media-settings',
     8691        template:  wp.template('embed-image-settings'),
    83298692
    8330         dropzoneDragleave: function( e ) {
    8331                 this.overDropzone = false;
    8332                 _.delay( _.bind( this.refresh, this, e ), 50 );
     8693        initialize: function() {
     8694                /**
     8695                 * Call `initialize` directly on parent class with passed arguments
     8696                 */
     8697                AttachmentDisplay.prototype.initialize.apply( this, arguments );
     8698                this.listenTo( this.model, 'change:url', this.updateImage );
    83338699        },
    83348700
    8335         click: function( e ) {
    8336                 // In the rare case where the dropzone gets stuck, hide it on click.
    8337                 this.containerDragleave( e );
    8338                 this.dropzoneDragleave( e );
    8339                 this.localDrag = false;
     8701        updateImage: function() {
     8702                this.$('img').attr( 'src', this.model.get('url') );
    83408703        }
    83418704});
    83428705
    8343 module.exports = EditorUploader;
     8706module.exports = EmbedImage;
    83448707
    8345 },{}],71:[function(require,module,exports){
    8346 var View = wp.media.View,
    8347         UploaderInline;
     8708
     8709/***/ }),
     8710/* 95 */
     8711/***/ (function(module, exports) {
     8712
     8713var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     8714        $ = jQuery,
     8715        ImageDetails;
    83488716
    83498717/**
    8350  * wp.media.view.UploaderInline
    8351  *
    8352  * The inline uploader that shows up in the 'Upload Files' tab.
     8718 * wp.media.view.ImageDetails
    83538719 *
    83548720 * @memberOf wp.media.view
    83558721 *
    83568722 * @class
     8723 * @augments wp.media.view.Settings.AttachmentDisplay
     8724 * @augments wp.media.view.Settings
    83578725 * @augments wp.media.View
    83588726 * @augments wp.Backbone.View
    83598727 * @augments Backbone.View
    83608728 */
    8361 UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{
    8362         tagName:   'div',
    8363         className: 'uploader-inline',
    8364         template:  wp.template('uploader-inline'),
     8729ImageDetails = AttachmentDisplay.extend(/** @lends wp.media.view.ImageDetails.prototype */{
     8730        className: 'image-details',
     8731        template:  wp.template('image-details'),
     8732        events: _.defaults( AttachmentDisplay.prototype.events, {
     8733                'click .edit-attachment': 'editAttachment',
     8734                'click .replace-attachment': 'replaceAttachment',
     8735                'click .advanced-toggle': 'onToggleAdvanced',
     8736                'change [data-setting="customWidth"]': 'onCustomSize',
     8737                'change [data-setting="customHeight"]': 'onCustomSize',
     8738                'keyup [data-setting="customWidth"]': 'onCustomSize',
     8739                'keyup [data-setting="customHeight"]': 'onCustomSize'
     8740        } ),
     8741        initialize: function() {
     8742                // used in AttachmentDisplay.prototype.updateLinkTo
     8743                this.options.attachment = this.model.attachment;
     8744                this.listenTo( this.model, 'change:url', this.updateUrl );
     8745                this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
     8746                this.listenTo( this.model, 'change:size', this.toggleCustomSize );
    83658747
    8366         events: {
    8367                 'click .close': 'hide'
     8748                AttachmentDisplay.prototype.initialize.apply( this, arguments );
    83688749        },
    83698750
    8370         initialize: function() {
    8371                 _.defaults( this.options, {
    8372                         message: '',
    8373                         status:  true,
    8374                         canClose: false
    8375                 });
     8751        prepare: function() {
     8752                var attachment = false;
    83768753
    8377                 if ( ! this.options.$browser && this.controller.uploader ) {
    8378                         this.options.$browser = this.controller.uploader.$browser;
     8754                if ( this.model.attachment ) {
     8755                        attachment = this.model.attachment.toJSON();
    83798756                }
     8757                return _.defaults({
     8758                        model: this.model.toJSON(),
     8759                        attachment: attachment
     8760                }, this.options );
     8761        },
    83808762
    8381                 if ( _.isUndefined( this.options.postId ) ) {
    8382                         this.options.postId = wp.media.view.settings.post.id;
     8763        render: function() {
     8764                var args = arguments;
     8765
     8766                if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
     8767                        this.model.dfd
     8768                                .done( _.bind( function() {
     8769                                        AttachmentDisplay.prototype.render.apply( this, args );
     8770                                        this.postRender();
     8771                                }, this ) )
     8772                                .fail( _.bind( function() {
     8773                                        this.model.attachment = false;
     8774                                        AttachmentDisplay.prototype.render.apply( this, args );
     8775                                        this.postRender();
     8776                                }, this ) );
     8777                } else {
     8778                        AttachmentDisplay.prototype.render.apply( this, arguments );
     8779                        this.postRender();
    83838780                }
    83848781
    8385                 if ( this.options.status ) {
    8386                         this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
    8387                                 controller: this.controller
    8388                         }) );
     8782                return this;
     8783        },
     8784
     8785        postRender: function() {
     8786                setTimeout( _.bind( this.resetFocus, this ), 10 );
     8787                this.toggleLinkSettings();
     8788                if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
     8789                        this.toggleAdvanced( true );
    83898790                }
     8791                this.trigger( 'post-render' );
    83908792        },
    83918793
    8392         prepare: function() {
    8393                 var suggestedWidth = this.controller.state().get('suggestedWidth'),
    8394                         suggestedHeight = this.controller.state().get('suggestedHeight'),
    8395                         data = {};
     8794        resetFocus: function() {
     8795                this.$( '.link-to-custom' ).blur();
     8796                this.$( '.embed-media-settings' ).scrollTop( 0 );
     8797        },
    83968798
    8397                 data.message = this.options.message;
    8398                 data.canClose = this.options.canClose;
     8799        updateUrl: function() {
     8800                this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
     8801                this.$( '.url' ).val( this.model.get( 'url' ) );
     8802        },
    83998803
    8400                 if ( suggestedWidth && suggestedHeight ) {
    8401                         data.suggestedWidth = suggestedWidth;
    8402                         data.suggestedHeight = suggestedHeight;
     8804        toggleLinkSettings: function() {
     8805                if ( this.model.get( 'link' ) === 'none' ) {
     8806                        this.$( '.link-settings' ).addClass('hidden');
     8807                } else {
     8808                        this.$( '.link-settings' ).removeClass('hidden');
    84038809                }
    8404 
    8405                 return data;
    84068810        },
    8407         /**
    8408          * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    8409          */
    8410         dispose: function() {
    8411                 if ( this.disposing ) {
    8412                         /**
    8413                          * call 'dispose' directly on the parent class
    8414                          */
    8415                         return View.prototype.dispose.apply( this, arguments );
    8416                 }
    84178811
    8418                 // Run remove on `dispose`, so we can be sure to refresh the
    8419                 // uploader with a view-less DOM. Track whether we're disposing
    8420                 // so we don't trigger an infinite loop.
    8421                 this.disposing = true;
    8422                 return this.remove();
     8812        toggleCustomSize: function() {
     8813                if ( this.model.get( 'size' ) !== 'custom' ) {
     8814                        this.$( '.custom-size' ).addClass('hidden');
     8815                } else {
     8816                        this.$( '.custom-size' ).removeClass('hidden');
     8817                }
    84238818        },
    8424         /**
    8425          * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    8426          */
    8427         remove: function() {
    8428                 /**
    8429                  * call 'remove' directly on the parent class
    8430                  */
    8431                 var result = View.prototype.remove.apply( this, arguments );
    84328819
    8433                 _.defer( _.bind( this.refresh, this ) );
    8434                 return result;
    8435         },
     8820        onCustomSize: function( event ) {
     8821                var dimension = $( event.target ).data('setting'),
     8822                        num = $( event.target ).val(),
     8823                        value;
    84368824
    8437         refresh: function() {
    8438                 var uploader = this.controller.uploader;
     8825                // Ignore bogus input
     8826                if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
     8827                        event.preventDefault();
     8828                        return;
     8829                }
    84398830
    8440                 if ( uploader ) {
    8441                         uploader.refresh();
     8831                if ( dimension === 'customWidth' ) {
     8832                        value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );
     8833                        this.model.set( 'customHeight', value, { silent: true } );
     8834                        this.$( '[data-setting="customHeight"]' ).val( value );
     8835                } else {
     8836                        value = Math.round( this.model.get( 'aspectRatio' ) * num );
     8837                        this.model.set( 'customWidth', value, { silent: true  } );
     8838                        this.$( '[data-setting="customWidth"]' ).val( value );
    84428839                }
    84438840        },
    8444         /**
    8445          * @returns {wp.media.view.UploaderInline}
    8446          */
    8447         ready: function() {
    8448                 var $browser = this.options.$browser,
    8449                         $placeholder;
    84508841
    8451                 if ( this.controller.uploader ) {
    8452                         $placeholder = this.$('.browser');
     8842        onToggleAdvanced: function( event ) {
     8843                event.preventDefault();
     8844                this.toggleAdvanced();
     8845        },
    84538846
    8454                         // Check if we've already replaced the placeholder.
    8455                         if ( $placeholder[0] === $browser[0] ) {
    8456                                 return;
    8457                         }
     8847        toggleAdvanced: function( show ) {
     8848                var $advanced = this.$el.find( '.advanced-section' ),
     8849                        mode;
    84588850
    8459                         $browser.detach().text( $placeholder.text() );
    8460                         $browser[0].className = $placeholder[0].className;
    8461                         $placeholder.replaceWith( $browser.show() );
     8851                if ( $advanced.hasClass('advanced-visible') || show === false ) {
     8852                        $advanced.removeClass('advanced-visible');
     8853                        $advanced.find('.advanced-settings').addClass('hidden');
     8854                        mode = 'hide';
     8855                } else {
     8856                        $advanced.addClass('advanced-visible');
     8857                        $advanced.find('.advanced-settings').removeClass('hidden');
     8858                        mode = 'show';
    84628859                }
    84638860
    8464                 this.refresh();
    8465                 return this;
     8861                window.setUserSetting( 'advImgDetails', mode );
    84668862        },
    8467         show: function() {
    8468                 this.$el.removeClass( 'hidden' );
    8469                 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
    8470                         this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' );
     8863
     8864        editAttachment: function( event ) {
     8865                var editState = this.controller.states.get( 'edit-image' );
     8866
     8867                if ( window.imageEdit && editState ) {
     8868                        event.preventDefault();
     8869                        editState.set( 'image', this.model.attachment );
     8870                        this.controller.setState( 'edit-image' );
    84718871                }
    84728872        },
    8473         hide: function() {
    8474                 this.$el.addClass( 'hidden' );
    8475                 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
    8476                         this.controller.$uploaderToggler
    8477                                 .attr( 'aria-expanded', 'false' )
    8478                                 // Move focus back to the toggle button when closing the uploader.
    8479                                 .focus();
    8480                 }
    8481         }
    84828873
     8874        replaceAttachment: function( event ) {
     8875                event.preventDefault();
     8876                this.controller.setState( 'replace-image' );
     8877        }
    84838878});
    84848879
    8485 module.exports = UploaderInline;
     8880module.exports = ImageDetails;
    84868881
    8487 },{}],72:[function(require,module,exports){
    8488 /**
    8489  * wp.media.view.UploaderStatusError
    8490  *
    8491  * @memberOf wp.media.view
    8492  *
    8493  * @class
    8494  * @augments wp.media.View
    8495  * @augments wp.Backbone.View
    8496  * @augments Backbone.View
    8497  */
    8498 var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{
    8499         className: 'upload-error',
    8500         template:  wp.template('uploader-status-error')
    8501 });
    85028882
    8503 module.exports = UploaderStatusError;
     8883/***/ }),
     8884/* 96 */
     8885/***/ (function(module, exports) {
    85048886
    8505 },{}],73:[function(require,module,exports){
    85068887var View = wp.media.View,
    8507         UploaderStatus;
     8888        UploaderStatus = wp.media.view.UploaderStatus,
     8889        l10n = wp.media.view.l10n,
     8890        $ = jQuery,
     8891        Cropper;
    85088892
    85098893/**
    8510  * wp.media.view.UploaderStatus
     8894 * wp.media.view.Cropper
    85118895 *
    8512  * An uploader status for on-going uploads.
     8896 * Uses the imgAreaSelect plugin to allow a user to crop an image.
     8897 *
     8898 * Takes imgAreaSelect options from
     8899 * wp.customize.HeaderControl.calculateImageSelectOptions via
     8900 * wp.customize.HeaderControl.openMM.
    85138901 *
    85148902 * @memberOf wp.media.view
    85158903 *
    var View = wp.media.View, 
    85188906 * @augments wp.Backbone.View
    85198907 * @augments Backbone.View
    85208908 */
    8521 UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{
    8522         className: 'media-uploader-status',
    8523         template:  wp.template('uploader-status'),
    8524 
    8525         events: {
    8526                 'click .upload-dismiss-errors': 'dismiss'
    8527         },
    8528 
     8909Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{
     8910        className: 'crop-content',
     8911        template: wp.template('crop-content'),
    85298912        initialize: function() {
    8530                 this.queue = wp.Uploader.queue;
    8531                 this.queue.on( 'add remove reset', this.visibility, this );
    8532                 this.queue.on( 'add remove reset change:percent', this.progress, this );
    8533                 this.queue.on( 'add remove reset change:uploading', this.info, this );
    8534 
    8535                 this.errors = wp.Uploader.errors;
    8536                 this.errors.reset();
    8537                 this.errors.on( 'add remove reset', this.visibility, this );
    8538                 this.errors.on( 'add', this.error, this );
     8913                _.bindAll(this, 'onImageLoad');
    85398914        },
    8540         /**
    8541          * @returns {wp.media.view.UploaderStatus}
    8542          */
    8543         dispose: function() {
    8544                 wp.Uploader.queue.off( null, null, this );
    8545                 /**
    8546                  * call 'dispose' directly on the parent class
    8547                  */
    8548                 View.prototype.dispose.apply( this, arguments );
    8549                 return this;
     8915        ready: function() {
     8916                this.controller.frame.on('content:error:crop', this.onError, this);
     8917                this.$image = this.$el.find('.crop-image');
     8918                this.$image.on('load', this.onImageLoad);
     8919                $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
    85508920        },
    8551 
    8552         visibility: function() {
    8553                 this.$el.toggleClass( 'uploading', !! this.queue.length );
    8554                 this.$el.toggleClass( 'errors', !! this.errors.length );
    8555                 this.$el.toggle( !! this.queue.length || !! this.errors.length );
     8921        remove: function() {
     8922                $(window).off('resize.cropper');
     8923                this.$el.remove();
     8924                this.$el.off();
     8925                View.prototype.remove.apply(this, arguments);
    85568926        },
    8557 
    8558         ready: function() {
    8559                 _.each({
    8560                         '$bar':      '.media-progress-bar div',
    8561                         '$index':    '.upload-index',
    8562                         '$total':    '.upload-total',
    8563                         '$filename': '.upload-filename'
    8564                 }, function( selector, key ) {
    8565                         this[ key ] = this.$( selector );
    8566                 }, this );
    8567 
    8568                 this.visibility();
    8569                 this.progress();
    8570                 this.info();
     8927        prepare: function() {
     8928                return {
     8929                        title: l10n.cropYourImage,
     8930                        url: this.options.attachment.get('url')
     8931                };
    85718932        },
     8933        onImageLoad: function() {
     8934                var imgOptions = this.controller.get('imgSelectOptions'),
     8935                        imgSelect;
    85728936
    8573         progress: function() {
    8574                 var queue = this.queue,
    8575                         $bar = this.$bar;
    8576 
    8577                 if ( ! $bar || ! queue.length ) {
    8578                         return;
     8937                if (typeof imgOptions === 'function') {
     8938                        imgOptions = imgOptions(this.options.attachment, this.controller);
    85798939                }
    85808940
    8581                 $bar.width( ( queue.reduce( function( memo, attachment ) {
    8582                         if ( ! attachment.get('uploading') ) {
    8583                                 return memo + 100;
    8584                         }
     8941                imgOptions = _.extend(imgOptions, {
     8942                        parent: this.$el,
     8943                        onInit: function() {
     8944                                this.parent.children().on( 'mousedown touchstart', function( e ){
    85858945
    8586                         var percent = attachment.get('percent');
    8587                         return memo + ( _.isNumber( percent ) ? percent : 100 );
    8588                 }, 0 ) / queue.length ) + '%' );
     8946                                        if ( e.shiftKey ) {
     8947                                                imgSelect.setOptions( {
     8948                                                        aspectRatio: '1:1'
     8949                                                } );
     8950                                        } else {
     8951                                                imgSelect.setOptions( {
     8952                                                        aspectRatio: false
     8953                                                } );
     8954                                        }
     8955                                } );
     8956                        }
     8957                } );
     8958                this.trigger('image-loaded');
     8959                imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
    85898960        },
     8961        onError: function() {
     8962                var filename = this.options.attachment.get('filename');
    85908963
    8591         info: function() {
    8592                 var queue = this.queue,
    8593                         index = 0, active;
     8964                this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     8965                        filename: UploaderStatus.prototype.filename(filename),
     8966                        message: window._wpMediaViewsL10n.cropError
     8967                }), { at: 0 });
     8968        }
     8969});
    85948970
    8595                 if ( ! queue.length ) {
    8596                         return;
    8597                 }
     8971module.exports = Cropper;
    85988972
    8599                 active = this.queue.find( function( attachment, i ) {
    8600                         index = i;
    8601                         return attachment.get('uploading');
    8602                 });
    86038973
    8604                 this.$index.text( index + 1 );
    8605                 this.$total.text( queue.length );
    8606                 this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
    8607         },
    8608         /**
    8609          * @param {string} filename
    8610          * @returns {string}
    8611          */
    8612         filename: function( filename ) {
    8613                 return _.escape( filename );
    8614         },
    8615         /**
    8616          * @param {Backbone.Model} error
    8617          */
    8618         error: function( error ) {
    8619                 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    8620                         filename: this.filename( error.get('file').name ),
    8621                         message:  error.get('message')
    8622                 }), { at: 0 });
     8974/***/ }),
     8975/* 97 */
     8976/***/ (function(module, exports) {
     8977
     8978var View = wp.media.view,
     8979        SiteIconCropper;
     8980
     8981/**
     8982 * wp.media.view.SiteIconCropper
     8983 *
     8984 * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon.
     8985 *
     8986 * Takes imgAreaSelect options from
     8987 * wp.customize.SiteIconControl.calculateImageSelectOptions.
     8988 *
     8989 * @memberOf wp.media.view
     8990 *
     8991 * @class
     8992 * @augments wp.media.view.Cropper
     8993 * @augments wp.media.View
     8994 * @augments wp.Backbone.View
     8995 * @augments Backbone.View
     8996 */
     8997SiteIconCropper = View.Cropper.extend(/** @lends wp.media.view.SiteIconCropper.prototype */{
     8998        className: 'crop-content site-icon',
     8999
     9000        ready: function () {
     9001                View.Cropper.prototype.ready.apply( this, arguments );
     9002
     9003                this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) );
    86239004        },
    86249005
    8625         /**
    8626          * @param {Object} event
    8627          */
    8628         dismiss: function( event ) {
    8629                 var errors = this.views.get('.upload-errors');
     9006        addSidebar: function() {
     9007                this.sidebar = new wp.media.view.Sidebar({
     9008                        controller: this.controller
     9009                });
    86309010
    8631                 event.preventDefault();
     9011                this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({
     9012                        controller: this.controller,
     9013                        attachment: this.options.attachment
     9014                }) );
    86329015
    8633                 if ( errors ) {
    8634                         _.invoke( errors, 'remove' );
    8635                 }
    8636                 wp.Uploader.errors.reset();
     9016                this.controller.cropperView.views.add( this.sidebar );
    86379017        }
    86389018});
    86399019
    8640 module.exports = UploaderStatus;
     9020module.exports = SiteIconCropper;
    86419021
    8642 },{}],74:[function(require,module,exports){
    8643 var $ = jQuery,
    8644         UploaderWindow;
     9022
     9023/***/ }),
     9024/* 98 */
     9025/***/ (function(module, exports) {
     9026
     9027var View = wp.media.View,
     9028        $ = jQuery,
     9029        SiteIconPreview;
    86459030
    86469031/**
    8647  * wp.media.view.UploaderWindow
     9032 * wp.media.view.SiteIconPreview
    86489033 *
    8649  * An uploader window that allows for dragging and dropping media.
     9034 * Shows a preview of the Site Icon as a favicon and app icon while cropping.
    86509035 *
    86519036 * @memberOf wp.media.view
    86529037 *
    var $ = jQuery, 
    86549039 * @augments wp.media.View
    86559040 * @augments wp.Backbone.View
    86569041 * @augments Backbone.View
    8657  *
    8658  * @param {object} [options]                   Options hash passed to the view.
    8659  * @param {object} [options.uploader]          Uploader properties.
    8660  * @param {jQuery} [options.uploader.browser]
    8661  * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
    8662  * @param {object} [options.uploader.params]
    86639042 */
    8664 UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{
    8665         tagName:   'div',
    8666         className: 'uploader-window',
    8667         template:  wp.template('uploader-window'),
     9043SiteIconPreview = View.extend(/** @lends wp.media.view.SiteIconPreview.prototype */{
     9044        className: 'site-icon-preview',
     9045        template: wp.template( 'site-icon-preview' ),
    86689046
    8669         initialize: function() {
    8670                 var uploader;
     9047        ready: function() {
     9048                this.controller.imgSelect.setOptions({
     9049                        onInit: this.updatePreview,
     9050                        onSelectChange: this.updatePreview
     9051                });
     9052        },
    86719053
    8672                 this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' );
     9054        prepare: function() {
     9055                return {
     9056                        url: this.options.attachment.get( 'url' )
     9057                };
     9058        },
    86739059
    8674                 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
    8675                         dropzone:  this.$el,
    8676                         browser:   this.$browser,
    8677                         params:    {}
    8678                 });
     9060        updatePreview: function( img, coords ) {
     9061                var rx = 64 / coords.width,
     9062                        ry = 64 / coords.height,
     9063                        preview_rx = 16 / coords.width,
     9064                        preview_ry = 16 / coords.height;
    86799065
    8680                 // Ensure the dropzone is a jQuery collection.
    8681                 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
    8682                         uploader.dropzone = $( uploader.dropzone );
    8683                 }
     9066                $( '#preview-app-icon' ).css({
     9067                        width: Math.round(rx * this.imageWidth ) + 'px',
     9068                        height: Math.round(ry * this.imageHeight ) + 'px',
     9069                        marginLeft: '-' + Math.round(rx * coords.x1) + 'px',
     9070                        marginTop: '-' + Math.round(ry * coords.y1) + 'px'
     9071                });
    86849072
    8685                 this.controller.on( 'activate', this.refresh, this );
     9073                $( '#preview-favicon' ).css({
     9074                        width: Math.round( preview_rx * this.imageWidth ) + 'px',
     9075                        height: Math.round( preview_ry * this.imageHeight ) + 'px',
     9076                        marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px',
     9077                        marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px'
     9078                });
     9079        }
     9080});
    86869081
    8687                 this.controller.on( 'detach', function() {
    8688                         this.$browser.remove();
    8689                 }, this );
    8690         },
     9082module.exports = SiteIconPreview;
    86919083
    8692         refresh: function() {
    8693                 if ( this.uploader ) {
    8694                         this.uploader.refresh();
    8695                 }
    8696         },
    86979084
    8698         ready: function() {
    8699                 var postId = wp.media.view.settings.post.id,
    8700                         dropzone;
     9085/***/ }),
     9086/* 99 */
     9087/***/ (function(module, exports) {
    87019088
    8702                 // If the uploader already exists, bail.
    8703                 if ( this.uploader ) {
    8704                         return;
    8705                 }
     9089var View = wp.media.View,
     9090        EditImage;
    87069091
    8707                 if ( postId ) {
    8708                         this.options.uploader.params.post_id = postId;
    8709                 }
    8710                 this.uploader = new wp.Uploader( this.options.uploader );
     9092/**
     9093 * wp.media.view.EditImage
     9094 *
     9095 * @memberOf wp.media.view
     9096 *
     9097 * @class
     9098 * @augments wp.media.View
     9099 * @augments wp.Backbone.View
     9100 * @augments Backbone.View
     9101 */
     9102EditImage = View.extend(/** @lends wp.media.view.EditImage.prototype */{
     9103        className: 'image-editor',
     9104        template: wp.template('image-editor'),
    87119105
    8712                 dropzone = this.uploader.dropzone;
    8713                 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
    8714                 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
     9106        initialize: function( options ) {
     9107                this.editor = window.imageEdit;
     9108                this.controller = options.controller;
     9109                View.prototype.initialize.apply( this, arguments );
     9110        },
    87159111
    8716                 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
     9112        prepare: function() {
     9113                return this.model.toJSON();
    87179114        },
    87189115
    8719         _ready: function() {
    8720                 this.controller.trigger( 'uploader:ready' );
     9116        loadEditor: function() {
     9117                var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
     9118                dfd.done( _.bind( this.focus, this ) );
    87219119        },
    87229120
    8723         show: function() {
    8724                 var $el = this.$el.show();
     9121        focus: function() {
     9122                this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
     9123        },
    87259124
    8726                 // Ensure that the animation is triggered by waiting until
    8727                 // the transparent element is painted into the DOM.
    8728                 _.defer( function() {
    8729                         $el.css({ opacity: 1 });
    8730                 });
     9125        back: function() {
     9126                var lastState = this.controller.lastState();
     9127                this.controller.setState( lastState );
    87319128        },
    87329129
    8733         hide: function() {
    8734                 var $el = this.$el.css({ opacity: 0 });
     9130        refresh: function() {
     9131                this.model.fetch();
     9132        },
    87359133
    8736                 wp.media.transition( $el ).done( function() {
    8737                         // Transition end events are subject to race conditions.
    8738                         // Make sure that the value is set as intended.
    8739                         if ( '0' === $el.css('opacity') ) {
    8740                                 $el.hide();
    8741                         }
    8742                 });
     9134        save: function() {
     9135                var lastState = this.controller.lastState();
    87439136
    8744                 // https://core.trac.wordpress.org/ticket/27341
    8745                 _.delay( function() {
    8746                         if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
    8747                                 $el.hide();
    8748                         }
    8749                 }, 500 );
     9137                this.model.fetch().done( _.bind( function() {
     9138                        this.controller.setState( lastState );
     9139                }, this ) );
    87509140        }
     9141
    87519142});
    87529143
    8753 module.exports = UploaderWindow;
     9144module.exports = EditImage;
     9145
     9146
     9147/***/ }),
     9148/* 100 */
     9149/***/ (function(module, exports) {
    87549150
    8755 },{}],75:[function(require,module,exports){
    87569151/**
    8757  * wp.media.View
    8758  *
    8759  * The base view class for media.
    8760  *
    8761  * Undelegating events, removing events from the model, and
    8762  * removing events from the controller mirror the code for
    8763  * `Backbone.View.dispose` in Backbone 0.9.8 development.
    8764  *
    8765  * This behavior has since been removed, and should not be used
    8766  * outside of the media manager.
     9152 * wp.media.view.Spinner
    87679153 *
    8768  * @memberOf wp.media
     9154 * @memberOf wp.media.view
    87699155 *
    87709156 * @class
     9157 * @augments wp.media.View
    87719158 * @augments wp.Backbone.View
    87729159 * @augments Backbone.View
    87739160 */
    8774 var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{
    8775         constructor: function( options ) {
    8776                 if ( options && options.controller ) {
    8777                         this.controller = options.controller;
    8778                 }
    8779                 wp.Backbone.View.apply( this, arguments );
    8780         },
    8781         /**
    8782          * @todo The internal comment mentions this might have been a stop-gap
    8783          *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
    8784          *       care of this in Backbone.View now.
    8785          *
    8786          * @returns {wp.media.View} Returns itself to allow chaining
    8787          */
    8788         dispose: function() {
    8789                 // Undelegating events, removing events from the model, and
    8790                 // removing events from the controller mirror the code for
    8791                 // `Backbone.View.dispose` in Backbone 0.9.8 development.
    8792                 this.undelegateEvents();
     9161var Spinner = wp.media.View.extend(/** @lends wp.media.view.Spinner.prototype */{
     9162        tagName:   'span',
     9163        className: 'spinner',
     9164        spinnerTimeout: false,
     9165        delay: 400,
    87939166
    8794                 if ( this.model && this.model.off ) {
    8795                         this.model.off( null, null, this );
     9167        show: function() {
     9168                if ( ! this.spinnerTimeout ) {
     9169                        this.spinnerTimeout = _.delay(function( $el ) {
     9170                                $el.addClass( 'is-active' );
     9171                        }, this.delay, this.$el );
    87969172                }
    87979173
    8798                 if ( this.collection && this.collection.off ) {
    8799                         this.collection.off( null, null, this );
    8800                 }
     9174                return this;
     9175        },
    88019176
    8802                 // Unbind controller events.
    8803                 if ( this.controller && this.controller.off ) {
    8804                         this.controller.off( null, null, this );
    8805                 }
     9177        hide: function() {
     9178                this.$el.removeClass( 'is-active' );
     9179                this.spinnerTimeout = clearTimeout( this.spinnerTimeout );
    88069180
    88079181                return this;
    8808         },
    8809         /**
    8810          * @returns {wp.media.View} Returns itself to allow chaining
    8811          */
    8812         remove: function() {
    8813                 this.dispose();
    8814                 /**
    8815                  * call 'remove' directly on the parent class
    8816                  */
    8817                 return wp.Backbone.View.prototype.remove.apply( this, arguments );
    88189182        }
    88199183});
    88209184
    8821 module.exports = View;
     9185module.exports = Spinner;
     9186
    88229187
    8823 },{}]},{},[19]);
     9188/***/ })
     9189/******/ ]));
     9190 No newline at end of file
  • new file webpack-dev.config.js

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

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