Make WordPress Core

Changeset 41752


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

Build: Webpack instead of Browserify as JavaScript bundler.

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

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

Location:
trunk
Files:
2 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/.jshintrc

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

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

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

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

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

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

    r41557 r41752  
    1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
    2 var Selection = wp.media.model.Selection,
    3     Library = wp.media.controller.Library,
    4     CollectionAdd;
     1/******/ (function(modules) { // webpackBootstrap
     2/******/    // The module cache
     3/******/    var installedModules = {};
     4/******/
     5/******/    // The require function
     6/******/    function __webpack_require__(moduleId) {
     7/******/
     8/******/        // Check if module is in cache
     9/******/        if(installedModules[moduleId]) {
     10/******/            return installedModules[moduleId].exports;
     11/******/        }
     12/******/        // Create a new module (and put it into the cache)
     13/******/        var module = installedModules[moduleId] = {
     14/******/            i: moduleId,
     15/******/            l: false,
     16/******/            exports: {}
     17/******/        };
     18/******/
     19/******/        // Execute the module function
     20/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     21/******/
     22/******/        // Flag the module as loaded
     23/******/        module.l = true;
     24/******/
     25/******/        // Return the exports of the module
     26/******/        return module.exports;
     27/******/    }
     28/******/
     29/******/
     30/******/    // expose the modules object (__webpack_modules__)
     31/******/    __webpack_require__.m = modules;
     32/******/
     33/******/    // expose the module cache
     34/******/    __webpack_require__.c = installedModules;
     35/******/
     36/******/    // define getter function for harmony exports
     37/******/    __webpack_require__.d = function(exports, name, getter) {
     38/******/        if(!__webpack_require__.o(exports, name)) {
     39/******/            Object.defineProperty(exports, name, {
     40/******/                configurable: false,
     41/******/                enumerable: true,
     42/******/                get: getter
     43/******/            });
     44/******/        }
     45/******/    };
     46/******/
     47/******/    // getDefaultExport function for compatibility with non-harmony modules
     48/******/    __webpack_require__.n = function(module) {
     49/******/        var getter = module && module.__esModule ?
     50/******/            function getDefault() { return module['default']; } :
     51/******/            function getModuleExports() { return module; };
     52/******/        __webpack_require__.d(getter, 'a', getter);
     53/******/        return getter;
     54/******/    };
     55/******/
     56/******/    // Object.prototype.hasOwnProperty.call
     57/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     58/******/
     59/******/    // __webpack_public_path__
     60/******/    __webpack_require__.p = "";
     61/******/
     62/******/    // Load entry module and return exports
     63/******/    return __webpack_require__(__webpack_require__.s = 26);
     64/******/ })
     65/************************************************************************/
     66/******/ (Array(26).concat([
     67/* 26 */
     68/***/ (function(module, exports, __webpack_require__) {
     69
     70var media = wp.media,
     71    $ = jQuery,
     72    l10n;
     73
     74media.isTouchDevice = ( 'ontouchend' in document );
     75
     76// Link any localized strings.
     77l10n = media.view.l10n = window._wpMediaViewsL10n || {};
     78
     79// Link any settings.
     80media.view.settings = l10n.settings || {};
     81delete l10n.settings;
     82
     83// Copy the `post` setting over to the model settings.
     84media.model.settings.post = media.view.settings.post;
     85
     86// Check if the browser supports CSS 3.0 transitions
     87$.support.transition = (function(){
     88    var style = document.documentElement.style,
     89        transitions = {
     90            WebkitTransition: 'webkitTransitionEnd',
     91            MozTransition:    'transitionend',
     92            OTransition:      'oTransitionEnd otransitionend',
     93            transition:       'transitionend'
     94        }, transition;
     95
     96    transition = _.find( _.keys( transitions ), function( transition ) {
     97        return ! _.isUndefined( style[ transition ] );
     98    });
     99
     100    return transition && {
     101        end: transitions[ transition ]
     102    };
     103}());
    5104
    6105/**
    7  * wp.media.controller.CollectionAdd
    8  *
    9  * A state for adding attachments to a collection (e.g. video playlist).
     106 * A shared event bus used to provide events into
     107 * the media workflows that 3rd-party devs can use to hook
     108 * in.
     109 */
     110media.events = _.extend( {}, Backbone.Events );
     111
     112/**
     113 * Makes it easier to bind events using transitions.
     114 *
     115 * @param {string} selector
     116 * @param {Number} sensitivity
     117 * @returns {Promise}
     118 */
     119media.transition = function( selector, sensitivity ) {
     120    var deferred = $.Deferred();
     121
     122    sensitivity = sensitivity || 2000;
     123
     124    if ( $.support.transition ) {
     125        if ( ! (selector instanceof $) ) {
     126            selector = $( selector );
     127        }
     128
     129        // Resolve the deferred when the first element finishes animating.
     130        selector.first().one( $.support.transition.end, deferred.resolve );
     131
     132        // Just in case the event doesn't trigger, fire a callback.
     133        _.delay( deferred.resolve, sensitivity );
     134
     135    // Otherwise, execute on the spot.
     136    } else {
     137        deferred.resolve();
     138    }
     139
     140    return deferred.promise();
     141};
     142
     143media.controller.Region = __webpack_require__( 27 );
     144media.controller.StateMachine = __webpack_require__( 28 );
     145media.controller.State = __webpack_require__( 29 );
     146
     147media.selectionSync = __webpack_require__( 30 );
     148media.controller.Library = __webpack_require__( 31 );
     149media.controller.ImageDetails = __webpack_require__( 32 );
     150media.controller.GalleryEdit = __webpack_require__( 33 );
     151media.controller.GalleryAdd = __webpack_require__( 34 );
     152media.controller.CollectionEdit = __webpack_require__( 35 );
     153media.controller.CollectionAdd = __webpack_require__( 36 );
     154media.controller.FeaturedImage = __webpack_require__( 37 );
     155media.controller.ReplaceImage = __webpack_require__( 38 );
     156media.controller.EditImage = __webpack_require__( 39 );
     157media.controller.MediaLibrary = __webpack_require__( 40 );
     158media.controller.Embed = __webpack_require__( 41 );
     159media.controller.Cropper = __webpack_require__( 42 );
     160media.controller.CustomizeImageCropper = __webpack_require__( 43 );
     161media.controller.SiteIconCropper = __webpack_require__( 44 );
     162
     163media.View = __webpack_require__( 45 );
     164media.view.Frame = __webpack_require__( 46 );
     165media.view.MediaFrame = __webpack_require__( 47 );
     166media.view.MediaFrame.Select = __webpack_require__( 48 );
     167media.view.MediaFrame.Post = __webpack_require__( 49 );
     168media.view.MediaFrame.ImageDetails = __webpack_require__( 50 );
     169media.view.Modal = __webpack_require__( 51 );
     170media.view.FocusManager = __webpack_require__( 52 );
     171media.view.UploaderWindow = __webpack_require__( 53 );
     172media.view.EditorUploader = __webpack_require__( 54 );
     173media.view.UploaderInline = __webpack_require__( 55 );
     174media.view.UploaderStatus = __webpack_require__( 56 );
     175media.view.UploaderStatusError = __webpack_require__( 57 );
     176media.view.Toolbar = __webpack_require__( 58 );
     177media.view.Toolbar.Select = __webpack_require__( 59 );
     178media.view.Toolbar.Embed = __webpack_require__( 60 );
     179media.view.Button = __webpack_require__( 61 );
     180media.view.ButtonGroup = __webpack_require__( 62 );
     181media.view.PriorityList = __webpack_require__( 63 );
     182media.view.MenuItem = __webpack_require__( 64 );
     183media.view.Menu = __webpack_require__( 65 );
     184media.view.RouterItem = __webpack_require__( 66 );
     185media.view.Router = __webpack_require__( 67 );
     186media.view.Sidebar = __webpack_require__( 68 );
     187media.view.Attachment = __webpack_require__( 69 );
     188media.view.Attachment.Library = __webpack_require__( 70 );
     189media.view.Attachment.EditLibrary = __webpack_require__( 71 );
     190media.view.Attachments = __webpack_require__( 72 );
     191media.view.Search = __webpack_require__( 73 );
     192media.view.AttachmentFilters = __webpack_require__( 74 );
     193media.view.DateFilter = __webpack_require__( 75 );
     194media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 );
     195media.view.AttachmentFilters.All = __webpack_require__( 77 );
     196media.view.AttachmentsBrowser = __webpack_require__( 78 );
     197media.view.Selection = __webpack_require__( 79 );
     198media.view.Attachment.Selection = __webpack_require__( 80 );
     199media.view.Attachments.Selection = __webpack_require__( 81 );
     200media.view.Attachment.EditSelection = __webpack_require__( 82 );
     201media.view.Settings = __webpack_require__( 83 );
     202media.view.Settings.AttachmentDisplay = __webpack_require__( 84 );
     203media.view.Settings.Gallery = __webpack_require__( 85 );
     204media.view.Settings.Playlist = __webpack_require__( 86 );
     205media.view.Attachment.Details = __webpack_require__( 87 );
     206media.view.AttachmentCompat = __webpack_require__( 88 );
     207media.view.Iframe = __webpack_require__( 89 );
     208media.view.Embed = __webpack_require__( 90 );
     209media.view.Label = __webpack_require__( 91 );
     210media.view.EmbedUrl = __webpack_require__( 92 );
     211media.view.EmbedLink = __webpack_require__( 93 );
     212media.view.EmbedImage = __webpack_require__( 94 );
     213media.view.ImageDetails = __webpack_require__( 95 );
     214media.view.Cropper = __webpack_require__( 96 );
     215media.view.SiteIconCropper = __webpack_require__( 97 );
     216media.view.SiteIconPreview = __webpack_require__( 98 );
     217media.view.EditImage = __webpack_require__( 99 );
     218media.view.Spinner = __webpack_require__( 100 );
     219
     220
     221/***/ }),
     222/* 27 */
     223/***/ (function(module, exports) {
     224
     225/**
     226 * wp.media.controller.Region
     227 *
     228 * A region is a persistent application layout area.
     229 *
     230 * A region assumes one mode at any time, and can be switched to another.
     231 *
     232 * When mode changes, events are triggered on the region's parent view.
     233 * The parent view will listen to specific events and fill the region with an
     234 * appropriate view depending on mode. For example, a frame listens for the
     235 * 'browse' mode t be activated on the 'content' view and then fills the region
     236 * with an AttachmentsBrowser view.
    10237 *
    11238 * @memberOf wp.media.controller
    12239 *
    13240 * @class
    14  * @augments wp.media.controller.Library
    15  * @augments wp.media.controller.State
    16  * @augments Backbone.Model
    17  *
    18  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    19  * @param {string}                     [attributes.id=library]      Unique identifier.
    20  * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
    21  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    22  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    23  *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
    24  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    25  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    26  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    27  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    28  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    29  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    30  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    31  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    32  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    33  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    34  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    35  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    36  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    37  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    38  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    39  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     241 *
     242 * @param {object}        options          Options hash for the region.
     243 * @param {string}        options.id       Unique identifier for the region.
     244 * @param {Backbone.View} options.view     A parent view the region exists within.
     245 * @param {string}        options.selector jQuery selector for the region within the parent view.
    40246 */
    41 CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{
    42     defaults: _.defaults( {
    43         // Selection defaults. @see media.model.Selection
    44         multiple:      'add',
    45         // Attachments browser defaults. @see media.view.AttachmentsBrowser
    46         filterable:    'uploaded',
    47 
    48         priority:      100,
    49         syncSelection: false
    50     }, Library.prototype.defaults ),
    51 
    52     /**
    53      * @since 3.9.0
    54      */
    55     initialize: function() {
    56         var collectionType = this.get('collectionType');
    57 
    58         if ( 'video' === this.get( 'type' ) ) {
    59             collectionType = 'video-' + collectionType;
    60         }
    61 
    62         this.set( 'id', collectionType + '-library' );
    63         this.set( 'toolbar', collectionType + '-add' );
    64         this.set( 'menu', collectionType );
    65 
    66         // If we haven't been provided a `library`, create a `Selection`.
    67         if ( ! this.get('library') ) {
    68             this.set( 'library', wp.media.query({ type: this.get('type') }) );
    69         }
    70         Library.prototype.initialize.apply( this, arguments );
    71     },
    72 
    73     /**
    74      * @since 3.9.0
    75      */
    76     activate: function() {
    77         var library = this.get('library'),
    78             editLibrary = this.get('editLibrary'),
    79             edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
    80 
    81         if ( editLibrary && editLibrary !== edit ) {
    82             library.unobserve( editLibrary );
    83         }
    84 
    85         // Accepts attachments that exist in the original library and
    86         // that do not exist in gallery's library.
    87         library.validator = function( attachment ) {
    88             return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    89         };
    90 
    91         // Reset the library to ensure that all attachments are re-added
    92         // to the collection. Do so silently, as calling `observe` will
    93         // trigger the `reset` event.
    94         library.reset( library.mirroring.models, { silent: true });
    95         library.observe( edit );
    96         this.set('editLibrary', edit);
    97 
    98         Library.prototype.activate.apply( this, arguments );
     247var Region = function( options ) {
     248    _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
     249};
     250
     251// Use Backbone's self-propagating `extend` inheritance method.
     252Region.extend = Backbone.Model.extend;
     253
     254_.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{
     255    /**
     256     * Activate a mode.
     257     *
     258     * @since 3.5.0
     259     *
     260     * @param {string} mode
     261     *
     262     * @fires Region#activate
     263     * @fires Region#deactivate
     264     *
     265     * @returns {wp.media.controller.Region} Returns itself to allow chaining.
     266     */
     267    mode: function( mode ) {
     268        if ( ! mode ) {
     269            return this._mode;
     270        }
     271        // Bail if we're trying to change to the current mode.
     272        if ( mode === this._mode ) {
     273            return this;
     274        }
     275
     276        /**
     277         * Region mode deactivation event.
     278         *
     279         * @event wp.media.controller.Region#deactivate
     280         */
     281        this.trigger('deactivate');
     282
     283        this._mode = mode;
     284        this.render( mode );
     285
     286        /**
     287         * Region mode activation event.
     288         *
     289         * @event wp.media.controller.Region#activate
     290         */
     291        this.trigger('activate');
     292        return this;
     293    },
     294    /**
     295     * Render a mode.
     296     *
     297     * @since 3.5.0
     298     *
     299     * @param {string} mode
     300     *
     301     * @fires Region#create
     302     * @fires Region#render
     303     *
     304     * @returns {wp.media.controller.Region} Returns itself to allow chaining
     305     */
     306    render: function( mode ) {
     307        // If the mode isn't active, activate it.
     308        if ( mode && mode !== this._mode ) {
     309            return this.mode( mode );
     310        }
     311
     312        var set = { view: null },
     313            view;
     314
     315        /**
     316         * Create region view event.
     317         *
     318         * Region view creation takes place in an event callback on the frame.
     319         *
     320         * @event wp.media.controller.Region#create
     321         * @type {object}
     322         * @property {object} view
     323         */
     324        this.trigger( 'create', set );
     325        view = set.view;
     326
     327        /**
     328         * Render region view event.
     329         *
     330         * Region view creation takes place in an event callback on the frame.
     331         *
     332         * @event wp.media.controller.Region#render
     333         * @type {object}
     334         */
     335        this.trigger( 'render', view );
     336        if ( view ) {
     337            this.set( view );
     338        }
     339        return this;
     340    },
     341
     342    /**
     343     * Get the region's view.
     344     *
     345     * @since 3.5.0
     346     *
     347     * @returns {wp.media.View}
     348     */
     349    get: function() {
     350        return this.view.views.first( this.selector );
     351    },
     352
     353    /**
     354     * Set the region's view as a subview of the frame.
     355     *
     356     * @since 3.5.0
     357     *
     358     * @param {Array|Object} views
     359     * @param {Object} [options={}]
     360     * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
     361     */
     362    set: function( views, options ) {
     363        if ( options ) {
     364            options.add = false;
     365        }
     366        return this.view.views.set( this.selector, views, options );
     367    },
     368
     369    /**
     370     * Trigger regional view events on the frame.
     371     *
     372     * @since 3.5.0
     373     *
     374     * @param {string} event
     375     * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
     376     */
     377    trigger: function( event ) {
     378        var base, args;
     379
     380        if ( ! this._mode ) {
     381            return;
     382        }
     383
     384        args = _.toArray( arguments );
     385        base = this.id + ':' + event;
     386
     387        // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
     388        args[0] = base + ':' + this._mode;
     389        this.view.trigger.apply( this.view, args );
     390
     391        // Trigger `{this.id}:{event}` event on the frame.
     392        args[0] = base;
     393        this.view.trigger.apply( this.view, args );
     394        return this;
    99395    }
    100396});
    101397
    102 module.exports = CollectionAdd;
    103 
    104 },{}],2:[function(require,module,exports){
    105 var Library = wp.media.controller.Library,
    106     l10n = wp.media.view.l10n,
    107     $ = jQuery,
    108     CollectionEdit;
     398module.exports = Region;
     399
     400
     401/***/ }),
     402/* 28 */
     403/***/ (function(module, exports) {
    109404
    110405/**
    111  * wp.media.controller.CollectionEdit
    112  *
    113  * A state for editing a collection, which is used by audio and video playlists,
    114  * and can be used for other collections.
     406 * wp.media.controller.StateMachine
     407 *
     408 * A state machine keeps track of state. It is in one state at a time,
     409 * and can change from one state to another.
     410 *
     411 * States are stored as models in a Backbone collection.
    115412 *
    116413 * @memberOf wp.media.controller
    117414 *
     415 * @since 3.5.0
     416 *
    118417 * @class
    119  * @augments wp.media.controller.Library
    120  * @augments wp.media.controller.State
    121418 * @augments Backbone.Model
    122  *
    123  * @param {object}                     [attributes]                      The attributes hash passed to the state.
    124  * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
    125  * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
    126  *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
    127  * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
    128  * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
    129  * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
    130  * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
    131  * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    132  * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
    133  * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
    134  * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
    135  * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
    136  * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
    137  * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
    138  * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
    139  * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
    140  *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
    141  * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
    142  * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
    143  *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    144  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    145  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     419 * @mixin
     420 * @mixes Backbone.Events
     421 *
     422 * @param {Array} states
    146423 */
    147 CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{
    148     defaults: {
    149         multiple:         false,
    150         sortable:         true,
    151         date:             false,
    152         searchable:       false,
    153         content:          'browse',
    154         describe:         true,
    155         dragInfo:         true,
    156         idealColumnWidth: 170,
    157         editing:          false,
    158         priority:         60,
    159         SettingsView:     false,
    160         syncSelection:    false
    161     },
    162 
    163     /**
    164      * @since 3.9.0
    165      */
    166     initialize: function() {
    167         var collectionType = this.get('collectionType');
    168 
    169         if ( 'video' === this.get( 'type' ) ) {
    170             collectionType = 'video-' + collectionType;
    171         }
    172 
    173         this.set( 'id', collectionType + '-edit' );
    174         this.set( 'toolbar', collectionType + '-edit' );
    175 
    176         // If we haven't been provided a `library`, create a `Selection`.
    177         if ( ! this.get('library') ) {
    178             this.set( 'library', new wp.media.model.Selection() );
    179         }
    180         // The single `Attachment` view to be used in the `Attachments` view.
    181         if ( ! this.get('AttachmentView') ) {
    182             this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    183         }
    184         Library.prototype.initialize.apply( this, arguments );
    185     },
    186 
    187     /**
    188      * @since 3.9.0
    189      */
    190     activate: function() {
    191         var library = this.get('library');
    192 
    193         // Limit the library to images only.
    194         library.props.set( 'type', this.get( 'type' ) );
    195 
    196         // Watch for uploaded attachments.
    197         this.get('library').observe( wp.Uploader.queue );
    198 
    199         this.frame.on( 'content:render:browse', this.renderSettings, this );
    200 
    201         Library.prototype.activate.apply( this, arguments );
    202     },
    203 
    204     /**
    205      * @since 3.9.0
    206      */
    207     deactivate: function() {
    208         // Stop watching for uploaded attachments.
    209         this.get('library').unobserve( wp.Uploader.queue );
    210 
    211         this.frame.off( 'content:render:browse', this.renderSettings, this );
    212 
    213         Library.prototype.deactivate.apply( this, arguments );
    214     },
    215 
    216     /**
    217      * Render the collection embed settings view in the browser sidebar.
     424var StateMachine = function( states ) {
     425    // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
     426    this.states = new Backbone.Collection( states );
     427};
     428
     429// Use Backbone's self-propagating `extend` inheritance method.
     430StateMachine.extend = Backbone.Model.extend;
     431
     432_.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{
     433    /**
     434     * Fetch a state.
    218435     *
    219      * @todo This is against the pattern elsewhere in media. Typically the frame
    220      *       is responsible for adding region mode callbacks. Explain.
     436     * If no `id` is provided, returns the active state.
    221437     *
    222      * @since 3.9.0
     438     * Implicitly creates states.
    223439     *
    224      * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
    225      */
    226     renderSettings: function( attachmentsBrowserView ) {
    227         var library = this.get('library'),
    228             collectionType = this.get('collectionType'),
    229             dragInfoText = this.get('dragInfoText'),
    230             SettingsView = this.get('SettingsView'),
    231             obj = {};
    232 
    233         if ( ! library || ! attachmentsBrowserView ) {
    234             return;
    235         }
    236 
    237         library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
    238 
    239         obj[ collectionType ] = new SettingsView({
    240             controller: this,
    241             model:      library[ collectionType ],
    242             priority:   40
    243         });
    244 
    245         attachmentsBrowserView.sidebar.set( obj );
    246 
    247         if ( dragInfoText ) {
    248             attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
    249                 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
    250                 priority: -40
    251             }) );
    252         }
    253 
    254         // Add the 'Reverse order' button to the toolbar.
    255         attachmentsBrowserView.toolbar.set( 'reverse', {
    256             text:     l10n.reverseOrder,
    257             priority: 80,
    258 
    259             click: function() {
    260                 library.reset( library.toArray().reverse() );
    261             }
    262         });
     440     * Ensure that the `states` collection exists so the `StateMachine`
     441     *   can be used as a mixin.
     442     *
     443     * @since 3.5.0
     444     *
     445     * @param {string} id
     446     * @returns {wp.media.controller.State} Returns a State model
     447     *   from the StateMachine collection
     448     */
     449    state: function( id ) {
     450        this.states = this.states || new Backbone.Collection();
     451
     452        // Default to the active state.
     453        id = id || this._state;
     454
     455        if ( id && ! this.states.get( id ) ) {
     456            this.states.add({ id: id });
     457        }
     458        return this.states.get( id );
     459    },
     460
     461    /**
     462     * Sets the active state.
     463     *
     464     * Bail if we're trying to select the current state, if we haven't
     465     * created the `states` collection, or are trying to select a state
     466     * that does not exist.
     467     *
     468     * @since 3.5.0
     469     *
     470     * @param {string} id
     471     *
     472     * @fires wp.media.controller.State#deactivate
     473     * @fires wp.media.controller.State#activate
     474     *
     475     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
     476     */
     477    setState: function( id ) {
     478        var previous = this.state();
     479
     480        if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
     481            return this;
     482        }
     483
     484        if ( previous ) {
     485            previous.trigger('deactivate');
     486            this._lastState = previous.id;
     487        }
     488
     489        this._state = id;
     490        this.state().trigger('activate');
     491
     492        return this;
     493    },
     494
     495    /**
     496     * Returns the previous active state.
     497     *
     498     * Call the `state()` method with no parameters to retrieve the current
     499     * active state.
     500     *
     501     * @since 3.5.0
     502     *
     503     * @returns {wp.media.controller.State} Returns a State model
     504     *    from the StateMachine collection
     505     */
     506    lastState: function() {
     507        if ( this._lastState ) {
     508            return this.state( this._lastState );
     509        }
    263510    }
    264511});
    265512
    266 module.exports = CollectionEdit;
    267 
    268 },{}],3:[function(require,module,exports){
     513// Map all event binding and triggering on a StateMachine to its `states` collection.
     514_.each([ 'on', 'off', 'trigger' ], function( method ) {
     515    /**
     516     * @function on
     517     * @memberOf wp.media.controller.StateMachine
     518     * @instance
     519     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     520     */
     521    /**
     522     * @function off
     523     * @memberOf wp.media.controller.StateMachine
     524     * @instance
     525     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     526     */
     527    /**
     528     * @function trigger
     529     * @memberOf wp.media.controller.StateMachine
     530     * @instance
     531     * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     532     */
     533    StateMachine.prototype[ method ] = function() {
     534        // Ensure that the `states` collection exists so the `StateMachine`
     535        // can be used as a mixin.
     536        this.states = this.states || new Backbone.Collection();
     537        // Forward the method to the `states` collection.
     538        this.states[ method ].apply( this.states, arguments );
     539        return this;
     540    };
     541});
     542
     543module.exports = StateMachine;
     544
     545
     546/***/ }),
     547/* 29 */
     548/***/ (function(module, exports) {
     549
     550/**
     551 * wp.media.controller.State
     552 *
     553 * A state is a step in a workflow that when set will trigger the controllers
     554 * for the regions to be updated as specified in the frame.
     555 *
     556 * A state has an event-driven lifecycle:
     557 *
     558 *     'ready'      triggers when a state is added to a state machine's collection.
     559 *     'activate'   triggers when a state is activated by a state machine.
     560 *     'deactivate' triggers when a state is deactivated by a state machine.
     561 *     'reset'      is not triggered automatically. It should be invoked by the
     562 *                  proper controller to reset the state to its default.
     563 *
     564 * @memberOf wp.media.controller
     565 *
     566 * @class
     567 * @augments Backbone.Model
     568 */
     569var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{
     570    /**
     571     * Constructor.
     572     *
     573     * @since 3.5.0
     574     */
     575    constructor: function() {
     576        this.on( 'activate', this._preActivate, this );
     577        this.on( 'activate', this.activate, this );
     578        this.on( 'activate', this._postActivate, this );
     579        this.on( 'deactivate', this._deactivate, this );
     580        this.on( 'deactivate', this.deactivate, this );
     581        this.on( 'reset', this.reset, this );
     582        this.on( 'ready', this._ready, this );
     583        this.on( 'ready', this.ready, this );
     584        /**
     585         * Call parent constructor with passed arguments
     586         */
     587        Backbone.Model.apply( this, arguments );
     588        this.on( 'change:menu', this._updateMenu, this );
     589    },
     590    /**
     591     * Ready event callback.
     592     *
     593     * @abstract
     594     * @since 3.5.0
     595     */
     596    ready: function() {},
     597
     598    /**
     599     * Activate event callback.
     600     *
     601     * @abstract
     602     * @since 3.5.0
     603     */
     604    activate: function() {},
     605
     606    /**
     607     * Deactivate event callback.
     608     *
     609     * @abstract
     610     * @since 3.5.0
     611     */
     612    deactivate: function() {},
     613
     614    /**
     615     * Reset event callback.
     616     *
     617     * @abstract
     618     * @since 3.5.0
     619     */
     620    reset: function() {},
     621
     622    /**
     623     * @access private
     624     * @since 3.5.0
     625     */
     626    _ready: function() {
     627        this._updateMenu();
     628    },
     629
     630    /**
     631     * @access private
     632     * @since 3.5.0
     633    */
     634    _preActivate: function() {
     635        this.active = true;
     636    },
     637
     638    /**
     639     * @access private
     640     * @since 3.5.0
     641     */
     642    _postActivate: function() {
     643        this.on( 'change:menu', this._menu, this );
     644        this.on( 'change:titleMode', this._title, this );
     645        this.on( 'change:content', this._content, this );
     646        this.on( 'change:toolbar', this._toolbar, this );
     647
     648        this.frame.on( 'title:render:default', this._renderTitle, this );
     649
     650        this._title();
     651        this._menu();
     652        this._toolbar();
     653        this._content();
     654        this._router();
     655    },
     656
     657    /**
     658     * @access private
     659     * @since 3.5.0
     660     */
     661    _deactivate: function() {
     662        this.active = false;
     663
     664        this.frame.off( 'title:render:default', this._renderTitle, this );
     665
     666        this.off( 'change:menu', this._menu, this );
     667        this.off( 'change:titleMode', this._title, this );
     668        this.off( 'change:content', this._content, this );
     669        this.off( 'change:toolbar', this._toolbar, this );
     670    },
     671
     672    /**
     673     * @access private
     674     * @since 3.5.0
     675     */
     676    _title: function() {
     677        this.frame.title.render( this.get('titleMode') || 'default' );
     678    },
     679
     680    /**
     681     * @access private
     682     * @since 3.5.0
     683     */
     684    _renderTitle: function( view ) {
     685        view.$el.text( this.get('title') || '' );
     686    },
     687
     688    /**
     689     * @access private
     690     * @since 3.5.0
     691     */
     692    _router: function() {
     693        var router = this.frame.router,
     694            mode = this.get('router'),
     695            view;
     696
     697        this.frame.$el.toggleClass( 'hide-router', ! mode );
     698        if ( ! mode ) {
     699            return;
     700        }
     701
     702        this.frame.router.render( mode );
     703
     704        view = router.get();
     705        if ( view && view.select ) {
     706            view.select( this.frame.content.mode() );
     707        }
     708    },
     709
     710    /**
     711     * @access private
     712     * @since 3.5.0
     713     */
     714    _menu: function() {
     715        var menu = this.frame.menu,
     716            mode = this.get('menu'),
     717            view;
     718
     719        this.frame.$el.toggleClass( 'hide-menu', ! mode );
     720        if ( ! mode ) {
     721            return;
     722        }
     723
     724        menu.mode( mode );
     725
     726        view = menu.get();
     727        if ( view && view.select ) {
     728            view.select( this.id );
     729        }
     730    },
     731
     732    /**
     733     * @access private
     734     * @since 3.5.0
     735     */
     736    _updateMenu: function() {
     737        var previous = this.previous('menu'),
     738            menu = this.get('menu');
     739
     740        if ( previous ) {
     741            this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
     742        }
     743
     744        if ( menu ) {
     745            this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
     746        }
     747    },
     748
     749    /**
     750     * Create a view in the media menu for the state.
     751     *
     752     * @access private
     753     * @since 3.5.0
     754     *
     755     * @param {media.view.Menu} view The menu view.
     756     */
     757    _renderMenu: function( view ) {
     758        var menuItem = this.get('menuItem'),
     759            title = this.get('title'),
     760            priority = this.get('priority');
     761
     762        if ( ! menuItem && title ) {
     763            menuItem = { text: title };
     764
     765            if ( priority ) {
     766                menuItem.priority = priority;
     767            }
     768        }
     769
     770        if ( ! menuItem ) {
     771            return;
     772        }
     773
     774        view.set( this.id, menuItem );
     775    }
     776});
     777
     778_.each(['toolbar','content'], function( region ) {
     779    /**
     780     * @access private
     781     */
     782    State.prototype[ '_' + region ] = function() {
     783        var mode = this.get( region );
     784        if ( mode ) {
     785            this.frame[ region ].render( mode );
     786        }
     787    };
     788});
     789
     790module.exports = State;
     791
     792
     793/***/ }),
     794/* 30 */
     795/***/ (function(module, exports) {
     796
     797/**
     798 * wp.media.selectionSync
     799 *
     800 * Sync an attachments selection in a state with another state.
     801 *
     802 * Allows for selecting multiple images in the Add Media workflow, and then
     803 * switching to the Insert Gallery workflow while preserving the attachments selection.
     804 *
     805 * @memberOf wp.media
     806 *
     807 * @mixin
     808 */
     809var selectionSync = {
     810    /**
     811     * @since 3.5.0
     812     */
     813    syncSelection: function() {
     814        var selection = this.get('selection'),
     815            manager = this.frame._selection;
     816
     817        if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     818            return;
     819        }
     820
     821        // If the selection supports multiple items, validate the stored
     822        // attachments based on the new selection's conditions. Record
     823        // the attachments that are not included; we'll maintain a
     824        // reference to those. Other attachments are considered in flux.
     825        if ( selection.multiple ) {
     826            selection.reset( [], { silent: true });
     827            selection.validateAll( manager.attachments );
     828            manager.difference = _.difference( manager.attachments.models, selection.models );
     829        }
     830
     831        // Sync the selection's single item with the master.
     832        selection.single( manager.single );
     833    },
     834
     835    /**
     836     * Record the currently active attachments, which is a combination
     837     * of the selection's attachments and the set of selected
     838     * attachments that this specific selection considered invalid.
     839     * Reset the difference and record the single attachment.
     840     *
     841     * @since 3.5.0
     842     */
     843    recordSelection: function() {
     844        var selection = this.get('selection'),
     845            manager = this.frame._selection;
     846
     847        if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     848            return;
     849        }
     850
     851        if ( selection.multiple ) {
     852            manager.attachments.reset( selection.toArray().concat( manager.difference ) );
     853            manager.difference = [];
     854        } else {
     855            manager.attachments.add( selection.toArray() );
     856        }
     857
     858        manager.single = selection._single;
     859    }
     860};
     861
     862module.exports = selectionSync;
     863
     864
     865/***/ }),
     866/* 31 */
     867/***/ (function(module, exports) {
     868
    269869var l10n = wp.media.view.l10n,
    270     Cropper;
     870    getUserSetting = window.getUserSetting,
     871    setUserSetting = window.setUserSetting,
     872    Library;
    271873
    272874/**
    273  * wp.media.controller.Cropper
    274  *
    275  * A state for cropping an image.
     875 * wp.media.controller.Library
     876 *
     877 * A state for choosing an attachment or group of attachments from the media library.
    276878 *
    277879 * @memberOf wp.media.controller
     
    280882 * @augments wp.media.controller.State
    281883 * @augments Backbone.Model
     884 * @mixes media.selectionSync
     885 *
     886 * @param {object}                          [attributes]                         The attributes hash passed to the state.
     887 * @param {string}                          [attributes.id=library]              Unique identifier.
     888 * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
     889 * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
     890 *                                                                               If one is not supplied, a collection of all attachments will be created.
     891 * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
     892 *                                                                               If the 'selection' attribute is a plain JS object,
     893 *                                                                               a Selection will be created using its values as the selection instance's `props` model.
     894 *                                                                               Otherwise, it will copy the library's `props` model.
     895 * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
     896 * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
     897 *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
     898 * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
     899 * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
     900 * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
     901 * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
     902 * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
     903 *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
     904 * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     905 * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     906 * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     907 * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     908 * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    282909 */
    283 Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{
     910Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{
    284911    defaults: {
    285         id:          'cropper',
    286         title:       l10n.cropImage,
    287         // Region mode defaults.
    288         toolbar:     'crop',
    289         content:     'crop',
    290         router:      false,
    291         canSkipCrop: false,
    292 
    293         // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state.
    294         doCropArgs: {}
    295     },
    296 
     912        id:                 'library',
     913        title:              l10n.mediaLibraryTitle,
     914        multiple:           false,
     915        content:            'upload',
     916        menu:               'default',
     917        router:             'browse',
     918        toolbar:            'select',
     919        searchable:         true,
     920        filterable:         false,
     921        sortable:           true,
     922        autoSelect:         true,
     923        describe:           false,
     924        contentUserSetting: true,
     925        syncSelection:      true
     926    },
     927
     928    /**
     929     * If a library isn't provided, query all media items.
     930     * If a selection instance isn't provided, create one.
     931     *
     932     * @since 3.5.0
     933     */
     934    initialize: function() {
     935        var selection = this.get('selection'),
     936            props;
     937
     938        if ( ! this.get('library') ) {
     939            this.set( 'library', wp.media.query() );
     940        }
     941
     942        if ( ! ( selection instanceof wp.media.model.Selection ) ) {
     943            props = selection;
     944
     945            if ( ! props ) {
     946                props = this.get('library').props.toJSON();
     947                props = _.omit( props, 'orderby', 'query' );
     948            }
     949
     950            this.set( 'selection', new wp.media.model.Selection( null, {
     951                multiple: this.get('multiple'),
     952                props: props
     953            }) );
     954        }
     955
     956        this.resetDisplays();
     957    },
     958
     959    /**
     960     * @since 3.5.0
     961     */
    297962    activate: function() {
    298         this.frame.on( 'content:create:crop', this.createCropContent, this );
    299         this.frame.on( 'close', this.removeCropper, this );
    300         this.set('selection', new Backbone.Collection(this.frame._selection.single));
    301     },
    302 
     963        this.syncSelection();
     964
     965        wp.Uploader.queue.on( 'add', this.uploading, this );
     966
     967        this.get('selection').on( 'add remove reset', this.refreshContent, this );
     968
     969        if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
     970            this.frame.on( 'content:activate', this.saveContentMode, this );
     971            this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
     972        }
     973    },
     974
     975    /**
     976     * @since 3.5.0
     977     */
    303978    deactivate: function() {
    304         this.frame.toolbar.mode('browse');
    305     },
    306 
    307     createCropContent: function() {
    308         this.cropperView = new wp.media.view.Cropper({
    309             controller: this,
    310             attachment: this.get('selection').first()
    311         });
    312         this.cropperView.on('image-loaded', this.createCropToolbar, this);
    313         this.frame.content.set(this.cropperView);
    314 
    315     },
    316     removeCropper: function() {
    317         this.imgSelect.cancelSelection();
    318         this.imgSelect.setOptions({remove: true});
    319         this.imgSelect.update();
    320         this.cropperView.remove();
    321     },
    322     createCropToolbar: function() {
    323         var canSkipCrop, toolbarOptions;
    324 
    325         canSkipCrop = this.get('canSkipCrop') || false;
    326 
    327         toolbarOptions = {
    328             controller: this.frame,
    329             items: {
    330                 insert: {
    331                     style:    'primary',
    332                     text:     l10n.cropImage,
    333                     priority: 80,
    334                     requires: { library: false, selection: false },
    335 
    336                     click: function() {
    337                         var controller = this.controller,
    338                             selection;
    339 
    340                         selection = controller.state().get('selection').first();
    341                         selection.set({cropDetails: controller.state().imgSelect.getSelection()});
    342 
    343                         this.$el.text(l10n.cropping);
    344                         this.$el.attr('disabled', true);
    345 
    346                         controller.state().doCrop( selection ).done( function( croppedImage ) {
    347                             controller.trigger('cropped', croppedImage );
    348                             controller.close();
    349                         }).fail( function() {
    350                             controller.trigger('content:error:crop');
    351                         });
    352                     }
    353                 }
     979        this.recordSelection();
     980
     981        this.frame.off( 'content:activate', this.saveContentMode, this );
     982
     983        // Unbind all event handlers that use this state as the context
     984        // from the selection.
     985        this.get('selection').off( null, null, this );
     986
     987        wp.Uploader.queue.off( null, null, this );
     988    },
     989
     990    /**
     991     * Reset the library to its initial state.
     992     *
     993     * @since 3.5.0
     994     */
     995    reset: function() {
     996        this.get('selection').reset();
     997        this.resetDisplays();
     998        this.refreshContent();
     999    },
     1000
     1001    /**
     1002     * Reset the attachment display settings defaults to the site options.
     1003     *
     1004     * If site options don't define them, fall back to a persistent user setting.
     1005     *
     1006     * @since 3.5.0
     1007     */
     1008    resetDisplays: function() {
     1009        var defaultProps = wp.media.view.settings.defaultProps;
     1010        this._displays = [];
     1011        this._defaultDisplaySettings = {
     1012            align: getUserSetting( 'align', defaultProps.align ) || 'none',
     1013            size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
     1014            link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
     1015        };
     1016    },
     1017
     1018    /**
     1019     * Create a model to represent display settings (alignment, etc.) for an attachment.
     1020     *
     1021     * @since 3.5.0
     1022     *
     1023     * @param {wp.media.model.Attachment} attachment
     1024     * @returns {Backbone.Model}
     1025     */
     1026    display: function( attachment ) {
     1027        var displays = this._displays;
     1028
     1029        if ( ! displays[ attachment.cid ] ) {
     1030            displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
     1031        }
     1032        return displays[ attachment.cid ];
     1033    },
     1034
     1035    /**
     1036     * Given an attachment, create attachment display settings properties.
     1037     *
     1038     * @since 3.6.0
     1039     *
     1040     * @param {wp.media.model.Attachment} attachment
     1041     * @returns {Object}
     1042     */
     1043    defaultDisplaySettings: function( attachment ) {
     1044        var settings = _.clone( this._defaultDisplaySettings );
     1045
     1046        if ( settings.canEmbed = this.canEmbed( attachment ) ) {
     1047            settings.link = 'embed';
     1048        } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
     1049            settings.link = 'file';
     1050        }
     1051
     1052        return settings;
     1053    },
     1054
     1055    /**
     1056     * Whether an attachment is image.
     1057     *
     1058     * @since 4.4.1
     1059     *
     1060     * @param {wp.media.model.Attachment} attachment
     1061     * @returns {Boolean}
     1062     */
     1063    isImageAttachment: function( attachment ) {
     1064        // If uploading, we know the filename but not the mime type.
     1065        if ( attachment.get('uploading') ) {
     1066            return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
     1067        }
     1068
     1069        return attachment.get('type') === 'image';
     1070    },
     1071
     1072    /**
     1073     * Whether an attachment can be embedded (audio or video).
     1074     *
     1075     * @since 3.6.0
     1076     *
     1077     * @param {wp.media.model.Attachment} attachment
     1078     * @returns {Boolean}
     1079     */
     1080    canEmbed: function( attachment ) {
     1081        // If uploading, we know the filename but not the mime type.
     1082        if ( ! attachment.get('uploading') ) {
     1083            var type = attachment.get('type');
     1084            if ( type !== 'audio' && type !== 'video' ) {
     1085                return false;
    3541086            }
    355         };
    356 
    357         if ( canSkipCrop ) {
    358             _.extend( toolbarOptions.items, {
    359                 skip: {
    360                     style:      'secondary',
    361                     text:       l10n.skipCropping,
    362                     priority:   70,
    363                     requires:   { library: false, selection: false },
    364                     click:      function() {
    365                         var selection = this.controller.state().get('selection').first();
    366                         this.controller.state().cropperView.remove();
    367                         this.controller.trigger('skippedcrop', selection);
    368                         this.controller.close();
    369                     }
    370                 }
    371             });
    372         }
    373 
    374         this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
    375     },
    376 
    377     doCrop: function( attachment ) {
    378         return wp.ajax.post( 'custom-header-crop', _.extend(
    379             {},
    380             this.defaults.doCropArgs,
    381             {
    382                 nonce: attachment.get( 'nonces' ).edit,
    383                 id: attachment.get( 'id' ),
    384                 cropDetails: attachment.get( 'cropDetails' )
    385             }
    386         ) );
     1087        }
     1088
     1089        return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
     1090    },
     1091
     1092
     1093    /**
     1094     * If the state is active, no items are selected, and the current
     1095     * content mode is not an option in the state's router (provided
     1096     * the state has a router), reset the content mode to the default.
     1097     *
     1098     * @since 3.5.0
     1099     */
     1100    refreshContent: function() {
     1101        var selection = this.get('selection'),
     1102            frame = this.frame,
     1103            router = frame.router.get(),
     1104            mode = frame.content.mode();
     1105
     1106        if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
     1107            this.frame.content.render( this.get('content') );
     1108        }
     1109    },
     1110
     1111    /**
     1112     * Callback handler when an attachment is uploaded.
     1113     *
     1114     * Switch to the Media Library if uploaded from the 'Upload Files' tab.
     1115     *
     1116     * Adds any uploading attachments to the selection.
     1117     *
     1118     * If the state only supports one attachment to be selected and multiple
     1119     * attachments are uploaded, the last attachment in the upload queue will
     1120     * be selected.
     1121     *
     1122     * @since 3.5.0
     1123     *
     1124     * @param {wp.media.model.Attachment} attachment
     1125     */
     1126    uploading: function( attachment ) {
     1127        var content = this.frame.content;
     1128
     1129        if ( 'upload' === content.mode() ) {
     1130            this.frame.content.mode('browse');
     1131        }
     1132
     1133        if ( this.get( 'autoSelect' ) ) {
     1134            this.get('selection').add( attachment );
     1135            this.frame.trigger( 'library:selection:add' );
     1136        }
     1137    },
     1138
     1139    /**
     1140     * Persist the mode of the content region as a user setting.
     1141     *
     1142     * @since 3.5.0
     1143     */
     1144    saveContentMode: function() {
     1145        if ( 'browse' !== this.get('router') ) {
     1146            return;
     1147        }
     1148
     1149        var mode = this.frame.content.mode(),
     1150            view = this.frame.router.get();
     1151
     1152        if ( view && view.get( mode ) ) {
     1153            setUserSetting( 'libraryContent', mode );
     1154        }
    3871155    }
    3881156});
    3891157
    390 module.exports = Cropper;
    391 
    392 },{}],4:[function(require,module,exports){
    393 var Controller = wp.media.controller,
    394     CustomizeImageCropper;
     1158// Make selectionSync available on any Media Library state.
     1159_.extend( Library.prototype, wp.media.selectionSync );
     1160
     1161module.exports = Library;
     1162
     1163
     1164/***/ }),
     1165/* 32 */
     1166/***/ (function(module, exports) {
     1167
     1168var State = wp.media.controller.State,
     1169    Library = wp.media.controller.Library,
     1170    l10n = wp.media.view.l10n,
     1171    ImageDetails;
    3951172
    3961173/**
    397  * wp.media.controller.CustomizeImageCropper
    398  *
    399  * @memberOf wp.media.controller
    400  *
    401  * A state for cropping an image.
    402  *
    403  * @class
    404  * @augments wp.media.controller.Cropper
    405  * @augments wp.media.controller.State
    406  * @augments Backbone.Model
    407  */
    408 CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{
    409     doCrop: function( attachment ) {
    410         var cropDetails = attachment.get( 'cropDetails' ),
    411             control = this.get( 'control' ),
    412             ratio = cropDetails.width / cropDetails.height;
    413 
    414         // Use crop measurements when flexible in both directions.
    415         if ( control.params.flex_width && control.params.flex_height ) {
    416             cropDetails.dst_width  = cropDetails.width;
    417             cropDetails.dst_height = cropDetails.height;
    418 
    419         // Constrain flexible side based on image ratio and size of the fixed side.
    420         } else {
    421             cropDetails.dst_width  = control.params.flex_width  ? control.params.height * ratio : control.params.width;
    422             cropDetails.dst_height = control.params.flex_height ? control.params.width  / ratio : control.params.height;
    423         }
    424 
    425         return wp.ajax.post( 'crop-image', {
    426             wp_customize: 'on',
    427             nonce: attachment.get( 'nonces' ).edit,
    428             id: attachment.get( 'id' ),
    429             context: control.id,
    430             cropDetails: cropDetails
    431         } );
    432     }
    433 });
    434 
    435 module.exports = CustomizeImageCropper;
    436 
    437 },{}],5:[function(require,module,exports){
    438 var l10n = wp.media.view.l10n,
    439     EditImage;
    440 
    441 /**
    442  * wp.media.controller.EditImage
    443  *
    444  * A state for editing (cropping, etc.) an image.
     1174 * wp.media.controller.ImageDetails
     1175 *
     1176 * A state for editing the attachment display settings of an image that's been
     1177 * inserted into the editor.
    4451178 *
    4461179 * @memberOf wp.media.controller
     
    4501183 * @augments Backbone.Model
    4511184 *
    452  * @param {object}                    attributes                      The attributes hash passed to the state.
    453  * @param {wp.media.model.Attachment} attributes.model                The attachment.
    454  * @param {string}                    [attributes.id=edit-image]      Unique identifier.
    455  * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
    456  * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
    457  * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
    458  * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
    459  * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
     1185 * @param {object}                    [attributes]                       The attributes hash passed to the state.
     1186 * @param {string}                    [attributes.id=image-details]      Unique identifier.
     1187 * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
     1188 * @param {wp.media.model.Attachment} attributes.image                   The image's model.
     1189 * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
     1190 * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
     1191 * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
     1192 * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
     1193 * @param {boolean}                   [attributes.editing=false]         Unused.
     1194 * @param {int}                       [attributes.priority=60]           Unused.
     1195 *
     1196 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
     1197 *       however this may not do anything.
    4601198 */
    461 EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{
    462     defaults: {
    463         id:      'edit-image',
    464         title:   l10n.editImage,
    465         menu:    false,
    466         toolbar: 'edit-image',
    467         content: 'edit-image',
    468         url:     ''
    469     },
     1199ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototype */{
     1200    defaults: _.defaults({
     1201        id:       'image-details',
     1202        title:    l10n.imageDetailsTitle,
     1203        content:  'image-details',
     1204        menu:     false,
     1205        router:   false,
     1206        toolbar:  'image-details',
     1207        editing:  false,
     1208        priority: 60
     1209    }, Library.prototype.defaults ),
    4701210
    4711211    /**
    4721212     * @since 3.9.0
     1213     *
     1214     * @param options Attributes
     1215     */
     1216    initialize: function( options ) {
     1217        this.image = options.image;
     1218        State.prototype.initialize.apply( this, arguments );
     1219    },
     1220
     1221    /**
     1222     * @since 3.9.0
    4731223     */
    4741224    activate: function() {
    475         this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
    476     },
    477 
    478     /**
    479      * @since 3.9.0
    480      */
    481     deactivate: function() {
    482         this.frame.off( 'toolbar:render:edit-image' );
    483     },
    484 
    485     /**
    486      * @since 3.9.0
    487      */
    488     toolbar: function() {
    489         var frame = this.frame,
    490             lastState = frame.lastState(),
    491             previous = lastState && lastState.id;
    492 
    493         frame.toolbar.set( new wp.media.view.Toolbar({
    494             controller: frame,
    495             items: {
    496                 back: {
    497                     style: 'primary',
    498                     text:     l10n.back,
    499                     priority: 20,
    500                     click:    function() {
    501                         if ( previous ) {
    502                             frame.setState( previous );
    503                         } else {
    504                             frame.close();
    505                         }
    506                     }
    507                 }
    508             }
    509         }) );
     1225        this.frame.modal.$el.addClass('image-details');
    5101226    }
    5111227});
    5121228
    513 module.exports = EditImage;
    514 
    515 },{}],6:[function(require,module,exports){
    516 var l10n = wp.media.view.l10n,
    517     $ = Backbone.$,
    518     Embed;
    519 
    520 /**
    521  * wp.media.controller.Embed
    522  *
    523  * A state for embedding media from a URL.
    524  *
    525  * @memberOf wp.media.controller
    526  *
    527  * @class
    528  * @augments wp.media.controller.State
    529  * @augments Backbone.Model
    530  *
    531  * @param {object} attributes                         The attributes hash passed to the state.
    532  * @param {string} [attributes.id=embed]              Unique identifier.
    533  * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
    534  * @param {string} [attributes.content=embed]         Initial mode for the content region.
    535  * @param {string} [attributes.menu=default]          Initial mode for the menu region.
    536  * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
    537  * @param {string} [attributes.menu=false]            Initial mode for the menu region.
    538  * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
    539  * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
    540  * @param {string} [attributes.url]                   The embed URL.
    541  * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
    542  */
    543 Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{
    544     defaults: {
    545         id:       'embed',
    546         title:    l10n.insertFromUrlTitle,
    547         content:  'embed',
    548         menu:     'default',
    549         toolbar:  'main-embed',
    550         priority: 120,
    551         type:     'link',
    552         url:      '',
    553         metadata: {}
    554     },
    555 
    556     // The amount of time used when debouncing the scan.
    557     sensitivity: 400,
    558 
    559     initialize: function(options) {
    560         this.metadata = options.metadata;
    561         this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
    562         this.props = new Backbone.Model( this.metadata || { url: '' });
    563         this.props.on( 'change:url', this.debouncedScan, this );
    564         this.props.on( 'change:url', this.refresh, this );
    565         this.on( 'scan', this.scanImage, this );
    566     },
    567 
    568     /**
    569      * Trigger a scan of the embedded URL's content for metadata required to embed.
    570      *
    571      * @fires wp.media.controller.Embed#scan
    572      */
    573     scan: function() {
    574         var scanners,
    575             embed = this,
    576             attributes = {
    577                 type: 'link',
    578                 scanners: []
    579             };
    580 
    581         // Scan is triggered with the list of `attributes` to set on the
    582         // state, useful for the 'type' attribute and 'scanners' attribute,
    583         // an array of promise objects for asynchronous scan operations.
    584         if ( this.props.get('url') ) {
    585             this.trigger( 'scan', attributes );
    586         }
    587 
    588         if ( attributes.scanners.length ) {
    589             scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
    590             scanners.always( function() {
    591                 if ( embed.get('scanners') === scanners ) {
    592                     embed.set( 'loading', false );
    593                 }
    594             });
    595         } else {
    596             attributes.scanners = null;
    597         }
    598 
    599         attributes.loading = !! attributes.scanners;
    600         this.set( attributes );
    601     },
    602     /**
    603      * Try scanning the embed as an image to discover its dimensions.
    604      *
    605      * @param {Object} attributes
    606      */
    607     scanImage: function( attributes ) {
    608         var frame = this.frame,
    609             state = this,
    610             url = this.props.get('url'),
    611             image = new Image(),
    612             deferred = $.Deferred();
    613 
    614         attributes.scanners.push( deferred.promise() );
    615 
    616         // Try to load the image and find its width/height.
    617         image.onload = function() {
    618             deferred.resolve();
    619 
    620             if ( state !== frame.state() || url !== state.props.get('url') ) {
    621                 return;
    622             }
    623 
    624             state.set({
    625                 type: 'image'
    626             });
    627 
    628             state.props.set({
    629                 width:  image.width,
    630                 height: image.height
    631             });
    632         };
    633 
    634         image.onerror = deferred.reject;
    635         image.src = url;
    636     },
    637 
    638     refresh: function() {
    639         this.frame.toolbar.get().refresh();
    640     },
    641 
    642     reset: function() {
    643         this.props.clear().set({ url: '' });
    644 
    645         if ( this.active ) {
    646             this.refresh();
    647         }
    648     }
    649 });
    650 
    651 module.exports = Embed;
    652 
    653 },{}],7:[function(require,module,exports){
    654 var Attachment = wp.media.model.Attachment,
    655     Library = wp.media.controller.Library,
    656     l10n = wp.media.view.l10n,
    657     FeaturedImage;
    658 
    659 /**
    660  * wp.media.controller.FeaturedImage
    661  *
    662  * A state for selecting a featured image for a post.
    663  *
    664  * @memberOf wp.media.controller
    665  *
    666  * @class
    667  * @augments wp.media.controller.Library
    668  * @augments wp.media.controller.State
    669  * @augments Backbone.Model
    670  *
    671  * @param {object}                     [attributes]                          The attributes hash passed to the state.
    672  * @param {string}                     [attributes.id=featured-image]        Unique identifier.
    673  * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
    674  * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
    675  *                                                                           If one is not supplied, a collection of all images will be created.
    676  * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
    677  * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
    678  *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
    679  * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
    680  * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
    681  * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
    682  * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
    683  * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
    684  * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
    685  *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
    686  * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    687  * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
    688  * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    689  * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
    690  * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
    691  */
    692 FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{
    693     defaults: _.defaults({
    694         id:            'featured-image',
    695         title:         l10n.setFeaturedImageTitle,
    696         multiple:      false,
    697         filterable:    'uploaded',
    698         toolbar:       'featured-image',
    699         priority:      60,
    700         syncSelection: true
    701     }, Library.prototype.defaults ),
    702 
    703     /**
    704      * @since 3.5.0
    705      */
    706     initialize: function() {
    707         var library, comparator;
    708 
    709         // If we haven't been provided a `library`, create a `Selection`.
    710         if ( ! this.get('library') ) {
    711             this.set( 'library', wp.media.query({ type: 'image' }) );
    712         }
    713 
    714         Library.prototype.initialize.apply( this, arguments );
    715 
    716         library    = this.get('library');
    717         comparator = library.comparator;
    718 
    719         // Overload the library's comparator to push items that are not in
    720         // the mirrored query to the front of the aggregate collection.
    721         library.comparator = function( a, b ) {
    722             var aInQuery = !! this.mirroring.get( a.cid ),
    723                 bInQuery = !! this.mirroring.get( b.cid );
    724 
    725             if ( ! aInQuery && bInQuery ) {
    726                 return -1;
    727             } else if ( aInQuery && ! bInQuery ) {
    728                 return 1;
    729             } else {
    730                 return comparator.apply( this, arguments );
    731             }
    732         };
    733 
    734         // Add all items in the selection to the library, so any featured
    735         // images that are not initially loaded still appear.
    736         library.observe( this.get('selection') );
    737     },
    738 
    739     /**
    740      * @since 3.5.0
    741      */
    742     activate: function() {
    743         this.updateSelection();
    744         this.frame.on( 'open', this.updateSelection, this );
    745 
    746         Library.prototype.activate.apply( this, arguments );
    747     },
    748 
    749     /**
    750      * @since 3.5.0
    751      */
    752     deactivate: function() {
    753         this.frame.off( 'open', this.updateSelection, this );
    754 
    755         Library.prototype.deactivate.apply( this, arguments );
    756     },
    757 
    758     /**
    759      * @since 3.5.0
    760      */
    761     updateSelection: function() {
    762         var selection = this.get('selection'),
    763             id = wp.media.view.settings.post.featuredImageId,
    764             attachment;
    765 
    766         if ( '' !== id && -1 !== id ) {
    767             attachment = Attachment.get( id );
    768             attachment.fetch();
    769         }
    770 
    771         selection.reset( attachment ? [ attachment ] : [] );
    772     }
    773 });
    774 
    775 module.exports = FeaturedImage;
    776 
    777 },{}],8:[function(require,module,exports){
    778 var Selection = wp.media.model.Selection,
    779     Library = wp.media.controller.Library,
    780     l10n = wp.media.view.l10n,
    781     GalleryAdd;
    782 
    783 /**
    784  * wp.media.controller.GalleryAdd
    785  *
    786  * A state for selecting more images to add to a gallery.
    787  *
    788  * @memberOf wp.media.controller
    789  *
    790  * @class
    791  * @augments wp.media.controller.Library
    792  * @augments wp.media.controller.State
    793  * @augments Backbone.Model
    794  *
    795  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    796  * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
    797  * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
    798  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    799  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    800  *                                                                          If one is not supplied, a collection of all images will be created.
    801  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    802  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    803  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    804  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    805  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    806  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    807  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    808  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    809  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    810  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    811  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    812  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    813  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    814  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    815  */
    816 GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{
    817     defaults: _.defaults({
    818         id:            'gallery-library',
    819         title:         l10n.addToGalleryTitle,
    820         multiple:      'add',
    821         filterable:    'uploaded',
    822         menu:          'gallery',
    823         toolbar:       'gallery-add',
    824         priority:      100,
    825         syncSelection: false
    826     }, Library.prototype.defaults ),
    827 
    828     /**
    829      * @since 3.5.0
    830      */
    831     initialize: function() {
    832         // If a library wasn't supplied, create a library of images.
    833         if ( ! this.get('library') ) {
    834             this.set( 'library', wp.media.query({ type: 'image' }) );
    835         }
    836 
    837         Library.prototype.initialize.apply( this, arguments );
    838     },
    839 
    840     /**
    841      * @since 3.5.0
    842      */
    843     activate: function() {
    844         var library = this.get('library'),
    845             edit    = this.frame.state('gallery-edit').get('library');
    846 
    847         if ( this.editLibrary && this.editLibrary !== edit ) {
    848             library.unobserve( this.editLibrary );
    849         }
    850 
    851         // Accepts attachments that exist in the original library and
    852         // that do not exist in gallery's library.
    853         library.validator = function( attachment ) {
    854             return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    855         };
    856 
    857         // Reset the library to ensure that all attachments are re-added
    858         // to the collection. Do so silently, as calling `observe` will
    859         // trigger the `reset` event.
    860         library.reset( library.mirroring.models, { silent: true });
    861         library.observe( edit );
    862         this.editLibrary = edit;
    863 
    864         Library.prototype.activate.apply( this, arguments );
    865     }
    866 });
    867 
    868 module.exports = GalleryAdd;
    869 
    870 },{}],9:[function(require,module,exports){
     1229module.exports = ImageDetails;
     1230
     1231
     1232/***/ }),
     1233/* 33 */
     1234/***/ (function(module, exports) {
     1235
    8711236var Library = wp.media.controller.Library,
    8721237    l10n = wp.media.view.l10n,
     
    10121377module.exports = GalleryEdit;
    10131378
    1014 },{}],10:[function(require,module,exports){
    1015 var State = wp.media.controller.State,
     1379
     1380/***/ }),
     1381/* 34 */
     1382/***/ (function(module, exports) {
     1383
     1384var Selection = wp.media.model.Selection,
    10161385    Library = wp.media.controller.Library,
    10171386    l10n = wp.media.view.l10n,
    1018     ImageDetails;
     1387    GalleryAdd;
    10191388
    10201389/**
    1021  * wp.media.controller.ImageDetails
    1022  *
    1023  * A state for editing the attachment display settings of an image that's been
    1024  * inserted into the editor.
    1025  *
    1026  * @memberOf wp.media.controller
    1027  *
    1028  * @class
    1029  * @augments wp.media.controller.State
    1030  * @augments Backbone.Model
    1031  *
    1032  * @param {object}                    [attributes]                       The attributes hash passed to the state.
    1033  * @param {string}                    [attributes.id=image-details]      Unique identifier.
    1034  * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
    1035  * @param {wp.media.model.Attachment} attributes.image                   The image's model.
    1036  * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
    1037  * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
    1038  * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
    1039  * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
    1040  * @param {boolean}                   [attributes.editing=false]         Unused.
    1041  * @param {int}                       [attributes.priority=60]           Unused.
    1042  *
    1043  * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
    1044  *       however this may not do anything.
    1045  */
    1046 ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototype */{
    1047     defaults: _.defaults({
    1048         id:       'image-details',
    1049         title:    l10n.imageDetailsTitle,
    1050         content:  'image-details',
    1051         menu:     false,
    1052         router:   false,
    1053         toolbar:  'image-details',
    1054         editing:  false,
    1055         priority: 60
    1056     }, Library.prototype.defaults ),
    1057 
    1058     /**
    1059      * @since 3.9.0
    1060      *
    1061      * @param options Attributes
    1062      */
    1063     initialize: function( options ) {
    1064         this.image = options.image;
    1065         State.prototype.initialize.apply( this, arguments );
    1066     },
    1067 
    1068     /**
    1069      * @since 3.9.0
    1070      */
    1071     activate: function() {
    1072         this.frame.modal.$el.addClass('image-details');
    1073     }
    1074 });
    1075 
    1076 module.exports = ImageDetails;
    1077 
    1078 },{}],11:[function(require,module,exports){
    1079 var l10n = wp.media.view.l10n,
    1080     getUserSetting = window.getUserSetting,
    1081     setUserSetting = window.setUserSetting,
    1082     Library;
    1083 
    1084 /**
    1085  * wp.media.controller.Library
    1086  *
    1087  * A state for choosing an attachment or group of attachments from the media library.
    1088  *
    1089  * @memberOf wp.media.controller
    1090  *
    1091  * @class
    1092  * @augments wp.media.controller.State
    1093  * @augments Backbone.Model
    1094  * @mixes media.selectionSync
    1095  *
    1096  * @param {object}                          [attributes]                         The attributes hash passed to the state.
    1097  * @param {string}                          [attributes.id=library]              Unique identifier.
    1098  * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
    1099  * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
    1100  *                                                                               If one is not supplied, a collection of all attachments will be created.
    1101  * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
    1102  *                                                                               If the 'selection' attribute is a plain JS object,
    1103  *                                                                               a Selection will be created using its values as the selection instance's `props` model.
    1104  *                                                                               Otherwise, it will copy the library's `props` model.
    1105  * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
    1106  * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
    1107  *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
    1108  * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
    1109  * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
    1110  * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
    1111  * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
    1112  * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
    1113  *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
    1114  * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1115  * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1116  * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1117  * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1118  * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    1119  */
    1120 Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{
    1121     defaults: {
    1122         id:                 'library',
    1123         title:              l10n.mediaLibraryTitle,
    1124         multiple:           false,
    1125         content:            'upload',
    1126         menu:               'default',
    1127         router:             'browse',
    1128         toolbar:            'select',
    1129         searchable:         true,
    1130         filterable:         false,
    1131         sortable:           true,
    1132         autoSelect:         true,
    1133         describe:           false,
    1134         contentUserSetting: true,
    1135         syncSelection:      true
    1136     },
    1137 
    1138     /**
    1139      * If a library isn't provided, query all media items.
    1140      * If a selection instance isn't provided, create one.
    1141      *
    1142      * @since 3.5.0
    1143      */
    1144     initialize: function() {
    1145         var selection = this.get('selection'),
    1146             props;
    1147 
    1148         if ( ! this.get('library') ) {
    1149             this.set( 'library', wp.media.query() );
    1150         }
    1151 
    1152         if ( ! ( selection instanceof wp.media.model.Selection ) ) {
    1153             props = selection;
    1154 
    1155             if ( ! props ) {
    1156                 props = this.get('library').props.toJSON();
    1157                 props = _.omit( props, 'orderby', 'query' );
    1158             }
    1159 
    1160             this.set( 'selection', new wp.media.model.Selection( null, {
    1161                 multiple: this.get('multiple'),
    1162                 props: props
    1163             }) );
    1164         }
    1165 
    1166         this.resetDisplays();
    1167     },
    1168 
    1169     /**
    1170      * @since 3.5.0
    1171      */
    1172     activate: function() {
    1173         this.syncSelection();
    1174 
    1175         wp.Uploader.queue.on( 'add', this.uploading, this );
    1176 
    1177         this.get('selection').on( 'add remove reset', this.refreshContent, this );
    1178 
    1179         if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
    1180             this.frame.on( 'content:activate', this.saveContentMode, this );
    1181             this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
    1182         }
    1183     },
    1184 
    1185     /**
    1186      * @since 3.5.0
    1187      */
    1188     deactivate: function() {
    1189         this.recordSelection();
    1190 
    1191         this.frame.off( 'content:activate', this.saveContentMode, this );
    1192 
    1193         // Unbind all event handlers that use this state as the context
    1194         // from the selection.
    1195         this.get('selection').off( null, null, this );
    1196 
    1197         wp.Uploader.queue.off( null, null, this );
    1198     },
    1199 
    1200     /**
    1201      * Reset the library to its initial state.
    1202      *
    1203      * @since 3.5.0
    1204      */
    1205     reset: function() {
    1206         this.get('selection').reset();
    1207         this.resetDisplays();
    1208         this.refreshContent();
    1209     },
    1210 
    1211     /**
    1212      * Reset the attachment display settings defaults to the site options.
    1213      *
    1214      * If site options don't define them, fall back to a persistent user setting.
    1215      *
    1216      * @since 3.5.0
    1217      */
    1218     resetDisplays: function() {
    1219         var defaultProps = wp.media.view.settings.defaultProps;
    1220         this._displays = [];
    1221         this._defaultDisplaySettings = {
    1222             align: getUserSetting( 'align', defaultProps.align ) || 'none',
    1223             size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
    1224             link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
    1225         };
    1226     },
    1227 
    1228     /**
    1229      * Create a model to represent display settings (alignment, etc.) for an attachment.
    1230      *
    1231      * @since 3.5.0
    1232      *
    1233      * @param {wp.media.model.Attachment} attachment
    1234      * @returns {Backbone.Model}
    1235      */
    1236     display: function( attachment ) {
    1237         var displays = this._displays;
    1238 
    1239         if ( ! displays[ attachment.cid ] ) {
    1240             displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
    1241         }
    1242         return displays[ attachment.cid ];
    1243     },
    1244 
    1245     /**
    1246      * Given an attachment, create attachment display settings properties.
    1247      *
    1248      * @since 3.6.0
    1249      *
    1250      * @param {wp.media.model.Attachment} attachment
    1251      * @returns {Object}
    1252      */
    1253     defaultDisplaySettings: function( attachment ) {
    1254         var settings = _.clone( this._defaultDisplaySettings );
    1255 
    1256         if ( settings.canEmbed = this.canEmbed( attachment ) ) {
    1257             settings.link = 'embed';
    1258         } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
    1259             settings.link = 'file';
    1260         }
    1261 
    1262         return settings;
    1263     },
    1264 
    1265     /**
    1266      * Whether an attachment is image.
    1267      *
    1268      * @since 4.4.1
    1269      *
    1270      * @param {wp.media.model.Attachment} attachment
    1271      * @returns {Boolean}
    1272      */
    1273     isImageAttachment: function( attachment ) {
    1274         // If uploading, we know the filename but not the mime type.
    1275         if ( attachment.get('uploading') ) {
    1276             return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
    1277         }
    1278 
    1279         return attachment.get('type') === 'image';
    1280     },
    1281 
    1282     /**
    1283      * Whether an attachment can be embedded (audio or video).
    1284      *
    1285      * @since 3.6.0
    1286      *
    1287      * @param {wp.media.model.Attachment} attachment
    1288      * @returns {Boolean}
    1289      */
    1290     canEmbed: function( attachment ) {
    1291         // If uploading, we know the filename but not the mime type.
    1292         if ( ! attachment.get('uploading') ) {
    1293             var type = attachment.get('type');
    1294             if ( type !== 'audio' && type !== 'video' ) {
    1295                 return false;
    1296             }
    1297         }
    1298 
    1299         return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
    1300     },
    1301 
    1302 
    1303     /**
    1304      * If the state is active, no items are selected, and the current
    1305      * content mode is not an option in the state's router (provided
    1306      * the state has a router), reset the content mode to the default.
    1307      *
    1308      * @since 3.5.0
    1309      */
    1310     refreshContent: function() {
    1311         var selection = this.get('selection'),
    1312             frame = this.frame,
    1313             router = frame.router.get(),
    1314             mode = frame.content.mode();
    1315 
    1316         if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
    1317             this.frame.content.render( this.get('content') );
    1318         }
    1319     },
    1320 
    1321     /**
    1322      * Callback handler when an attachment is uploaded.
    1323      *
    1324      * Switch to the Media Library if uploaded from the 'Upload Files' tab.
    1325      *
    1326      * Adds any uploading attachments to the selection.
    1327      *
    1328      * If the state only supports one attachment to be selected and multiple
    1329      * attachments are uploaded, the last attachment in the upload queue will
    1330      * be selected.
    1331      *
    1332      * @since 3.5.0
    1333      *
    1334      * @param {wp.media.model.Attachment} attachment
    1335      */
    1336     uploading: function( attachment ) {
    1337         var content = this.frame.content;
    1338 
    1339         if ( 'upload' === content.mode() ) {
    1340             this.frame.content.mode('browse');
    1341         }
    1342 
    1343         if ( this.get( 'autoSelect' ) ) {
    1344             this.get('selection').add( attachment );
    1345             this.frame.trigger( 'library:selection:add' );
    1346         }
    1347     },
    1348 
    1349     /**
    1350      * Persist the mode of the content region as a user setting.
    1351      *
    1352      * @since 3.5.0
    1353      */
    1354     saveContentMode: function() {
    1355         if ( 'browse' !== this.get('router') ) {
    1356             return;
    1357         }
    1358 
    1359         var mode = this.frame.content.mode(),
    1360             view = this.frame.router.get();
    1361 
    1362         if ( view && view.get( mode ) ) {
    1363             setUserSetting( 'libraryContent', mode );
    1364         }
    1365     }
    1366 });
    1367 
    1368 // Make selectionSync available on any Media Library state.
    1369 _.extend( Library.prototype, wp.media.selectionSync );
    1370 
    1371 module.exports = Library;
    1372 
    1373 },{}],12:[function(require,module,exports){
    1374 /**
    1375  * wp.media.controller.MediaLibrary
     1390 * wp.media.controller.GalleryAdd
     1391 *
     1392 * A state for selecting more images to add to a gallery.
    13761393 *
    13771394 * @memberOf wp.media.controller
     
    13811398 * @augments wp.media.controller.State
    13821399 * @augments Backbone.Model
     1400 *
     1401 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     1402 * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
     1403 * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
     1404 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     1405 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     1406 *                                                                          If one is not supplied, a collection of all images will be created.
     1407 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     1408 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     1409 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     1410 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     1411 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     1412 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     1413 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     1414 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     1415 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1416 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     1417 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     1418 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     1419 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     1420 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    13831421 */
    1384 var Library = wp.media.controller.Library,
    1385     MediaLibrary;
    1386 
    1387 MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{
     1422GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{
    13881423    defaults: _.defaults({
    1389         // Attachments browser defaults. @see media.view.AttachmentsBrowser
    1390         filterable:      'uploaded',
    1391 
    1392         displaySettings: false,
    1393         priority:        80,
    1394         syncSelection:   false
     1424        id:            'gallery-library',
     1425        title:         l10n.addToGalleryTitle,
     1426        multiple:      'add',
     1427        filterable:    'uploaded',
     1428        menu:          'gallery',
     1429        toolbar:       'gallery-add',
     1430        priority:      100,
     1431        syncSelection: false
    13951432    }, Library.prototype.defaults ),
    13961433
    13971434    /**
    1398      * @since 3.9.0
    1399      *
    1400      * @param options
    1401      */
    1402     initialize: function( options ) {
    1403         this.media = options.media;
    1404         this.type = options.type;
    1405         this.set( 'library', wp.media.query({ type: this.type }) );
     1435     * @since 3.5.0
     1436     */
     1437    initialize: function() {
     1438        // If a library wasn't supplied, create a library of images.
     1439        if ( ! this.get('library') ) {
     1440            this.set( 'library', wp.media.query({ type: 'image' }) );
     1441        }
    14061442
    14071443        Library.prototype.initialize.apply( this, arguments );
     
    14091445
    14101446    /**
    1411      * @since 3.9.0
     1447     * @since 3.5.0
    14121448     */
    14131449    activate: function() {
    1414         // @todo this should use this.frame.
    1415         if ( wp.media.frame.lastMime ) {
    1416             this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
    1417             delete wp.media.frame.lastMime;
    1418         }
     1450        var library = this.get('library'),
     1451            edit    = this.frame.state('gallery-edit').get('library');
     1452
     1453        if ( this.editLibrary && this.editLibrary !== edit ) {
     1454            library.unobserve( this.editLibrary );
     1455        }
     1456
     1457        // Accepts attachments that exist in the original library and
     1458        // that do not exist in gallery's library.
     1459        library.validator = function( attachment ) {
     1460            return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     1461        };
     1462
     1463        // Reset the library to ensure that all attachments are re-added
     1464        // to the collection. Do so silently, as calling `observe` will
     1465        // trigger the `reset` event.
     1466        library.reset( library.mirroring.models, { silent: true });
     1467        library.observe( edit );
     1468        this.editLibrary = edit;
     1469
    14191470        Library.prototype.activate.apply( this, arguments );
    14201471    }
    14211472});
    14221473
    1423 module.exports = MediaLibrary;
    1424 
    1425 },{}],13:[function(require,module,exports){
     1474module.exports = GalleryAdd;
     1475
     1476
     1477/***/ }),
     1478/* 35 */
     1479/***/ (function(module, exports) {
     1480
     1481var Library = wp.media.controller.Library,
     1482    l10n = wp.media.view.l10n,
     1483    $ = jQuery,
     1484    CollectionEdit;
     1485
    14261486/**
    1427  * wp.media.controller.Region
    1428  *
    1429  * A region is a persistent application layout area.
    1430  *
    1431  * A region assumes one mode at any time, and can be switched to another.
    1432  *
    1433  * When mode changes, events are triggered on the region's parent view.
    1434  * The parent view will listen to specific events and fill the region with an
    1435  * appropriate view depending on mode. For example, a frame listens for the
    1436  * 'browse' mode t be activated on the 'content' view and then fills the region
    1437  * with an AttachmentsBrowser view.
     1487 * wp.media.controller.CollectionEdit
     1488 *
     1489 * A state for editing a collection, which is used by audio and video playlists,
     1490 * and can be used for other collections.
    14381491 *
    14391492 * @memberOf wp.media.controller
    14401493 *
    14411494 * @class
    1442  *
    1443  * @param {object}        options          Options hash for the region.
    1444  * @param {string}        options.id       Unique identifier for the region.
    1445  * @param {Backbone.View} options.view     A parent view the region exists within.
    1446  * @param {string}        options.selector jQuery selector for the region within the parent view.
     1495 * @augments wp.media.controller.Library
     1496 * @augments wp.media.controller.State
     1497 * @augments Backbone.Model
     1498 *
     1499 * @param {object}                     [attributes]                      The attributes hash passed to the state.
     1500 * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
     1501 * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
     1502 *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
     1503 * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
     1504 * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
     1505 * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
     1506 * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
     1507 * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1508 * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
     1509 * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
     1510 * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
     1511 * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
     1512 * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
     1513 * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
     1514 * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
     1515 * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
     1516 *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
     1517 * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
     1518 * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
     1519 *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     1520 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     1521 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
    14471522 */
    1448 var Region = function( options ) {
    1449     _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
    1450 };
    1451 
    1452 // Use Backbone's self-propagating `extend` inheritance method.
    1453 Region.extend = Backbone.Model.extend;
    1454 
    1455 _.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{
    1456     /**
    1457      * Activate a mode.
     1523CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{
     1524    defaults: {
     1525        multiple:         false,
     1526        sortable:         true,
     1527        date:             false,
     1528        searchable:       false,
     1529        content:          'browse',
     1530        describe:         true,
     1531        dragInfo:         true,
     1532        idealColumnWidth: 170,
     1533        editing:          false,
     1534        priority:         60,
     1535        SettingsView:     false,
     1536        syncSelection:    false
     1537    },
     1538
     1539    /**
     1540     * @since 3.9.0
     1541     */
     1542    initialize: function() {
     1543        var collectionType = this.get('collectionType');
     1544
     1545        if ( 'video' === this.get( 'type' ) ) {
     1546            collectionType = 'video-' + collectionType;
     1547        }
     1548
     1549        this.set( 'id', collectionType + '-edit' );
     1550        this.set( 'toolbar', collectionType + '-edit' );
     1551
     1552        // If we haven't been provided a `library`, create a `Selection`.
     1553        if ( ! this.get('library') ) {
     1554            this.set( 'library', new wp.media.model.Selection() );
     1555        }
     1556        // The single `Attachment` view to be used in the `Attachments` view.
     1557        if ( ! this.get('AttachmentView') ) {
     1558            this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     1559        }
     1560        Library.prototype.initialize.apply( this, arguments );
     1561    },
     1562
     1563    /**
     1564     * @since 3.9.0
     1565     */
     1566    activate: function() {
     1567        var library = this.get('library');
     1568
     1569        // Limit the library to images only.
     1570        library.props.set( 'type', this.get( 'type' ) );
     1571
     1572        // Watch for uploaded attachments.
     1573        this.get('library').observe( wp.Uploader.queue );
     1574
     1575        this.frame.on( 'content:render:browse', this.renderSettings, this );
     1576
     1577        Library.prototype.activate.apply( this, arguments );
     1578    },
     1579
     1580    /**
     1581     * @since 3.9.0
     1582     */
     1583    deactivate: function() {
     1584        // Stop watching for uploaded attachments.
     1585        this.get('library').unobserve( wp.Uploader.queue );
     1586
     1587        this.frame.off( 'content:render:browse', this.renderSettings, this );
     1588
     1589        Library.prototype.deactivate.apply( this, arguments );
     1590    },
     1591
     1592    /**
     1593     * Render the collection embed settings view in the browser sidebar.
    14581594     *
    1459      * @since 3.5.0
     1595     * @todo This is against the pattern elsewhere in media. Typically the frame
     1596     *       is responsible for adding region mode callbacks. Explain.
    14601597     *
    1461      * @param {string} mode
     1598     * @since 3.9.0
    14621599     *
    1463      * @fires Region#activate
    1464      * @fires Region#deactivate
    1465      *
    1466      * @returns {wp.media.controller.Region} Returns itself to allow chaining.
    1467      */
    1468     mode: function( mode ) {
    1469         if ( ! mode ) {
    1470             return this._mode;
    1471         }
    1472         // Bail if we're trying to change to the current mode.
    1473         if ( mode === this._mode ) {
    1474             return this;
    1475         }
    1476 
    1477         /**
    1478          * Region mode deactivation event.
    1479          *
    1480          * @event wp.media.controller.Region#deactivate
    1481          */
    1482         this.trigger('deactivate');
    1483 
    1484         this._mode = mode;
    1485         this.render( mode );
    1486 
    1487         /**
    1488          * Region mode activation event.
    1489          *
    1490          * @event wp.media.controller.Region#activate
    1491          */
    1492         this.trigger('activate');
    1493         return this;
    1494     },
    1495     /**
    1496      * Render a mode.
    1497      *
    1498      * @since 3.5.0
    1499      *
    1500      * @param {string} mode
    1501      *
    1502      * @fires Region#create
    1503      * @fires Region#render
    1504      *
    1505      * @returns {wp.media.controller.Region} Returns itself to allow chaining
    1506      */
    1507     render: function( mode ) {
    1508         // If the mode isn't active, activate it.
    1509         if ( mode && mode !== this._mode ) {
    1510             return this.mode( mode );
    1511         }
    1512 
    1513         var set = { view: null },
    1514             view;
    1515 
    1516         /**
    1517          * Create region view event.
    1518          *
    1519          * Region view creation takes place in an event callback on the frame.
    1520          *
    1521          * @event wp.media.controller.Region#create
    1522          * @type {object}
    1523          * @property {object} view
    1524          */
    1525         this.trigger( 'create', set );
    1526         view = set.view;
    1527 
    1528         /**
    1529          * Render region view event.
    1530          *
    1531          * Region view creation takes place in an event callback on the frame.
    1532          *
    1533          * @event wp.media.controller.Region#render
    1534          * @type {object}
    1535          */
    1536         this.trigger( 'render', view );
    1537         if ( view ) {
    1538             this.set( view );
    1539         }
    1540         return this;
    1541     },
    1542 
    1543     /**
    1544      * Get the region's view.
    1545      *
    1546      * @since 3.5.0
    1547      *
    1548      * @returns {wp.media.View}
    1549      */
    1550     get: function() {
    1551         return this.view.views.first( this.selector );
    1552     },
    1553 
    1554     /**
    1555      * Set the region's view as a subview of the frame.
    1556      *
    1557      * @since 3.5.0
    1558      *
    1559      * @param {Array|Object} views
    1560      * @param {Object} [options={}]
    1561      * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
    1562      */
    1563     set: function( views, options ) {
    1564         if ( options ) {
    1565             options.add = false;
    1566         }
    1567         return this.view.views.set( this.selector, views, options );
    1568     },
    1569 
    1570     /**
    1571      * Trigger regional view events on the frame.
    1572      *
    1573      * @since 3.5.0
    1574      *
    1575      * @param {string} event
    1576      * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
    1577      */
    1578     trigger: function( event ) {
    1579         var base, args;
    1580 
    1581         if ( ! this._mode ) {
     1600     * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
     1601     */
     1602    renderSettings: function( attachmentsBrowserView ) {
     1603        var library = this.get('library'),
     1604            collectionType = this.get('collectionType'),
     1605            dragInfoText = this.get('dragInfoText'),
     1606            SettingsView = this.get('SettingsView'),
     1607            obj = {};
     1608
     1609        if ( ! library || ! attachmentsBrowserView ) {
    15821610            return;
    15831611        }
    15841612
    1585         args = _.toArray( arguments );
    1586         base = this.id + ':' + event;
    1587 
    1588         // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
    1589         args[0] = base + ':' + this._mode;
    1590         this.view.trigger.apply( this.view, args );
    1591 
    1592         // Trigger `{this.id}:{event}` event on the frame.
    1593         args[0] = base;
    1594         this.view.trigger.apply( this.view, args );
    1595         return this;
     1613        library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
     1614
     1615        obj[ collectionType ] = new SettingsView({
     1616            control