Make WordPress Core

Ticket #28510: browserify.2.diff

File browserify.2.diff, 1014.2 KB (added by wonderboymusic, 9 years ago)
  • Gruntfile.js

     
    114114                                }
    115115                        }
    116116                },
     117                browserify: {
     118                        media: {
     119                                files: {
     120                                        'src/wp-includes/js/media/models.js' : [ SOURCE_DIR + 'wp-includes/js/media/models.manifest.js' ],
     121                                        'src/wp-includes/js/media/views.js' : [ SOURCE_DIR + 'wp-includes/js/media/views.manifest.js' ],
     122                                        'src/wp-includes/js/media/audio-video.js' : [ SOURCE_DIR + 'wp-includes/js/media/audio-video.manifest.js' ],
     123                                        'src/wp-includes/js/media/grid.js' : [ SOURCE_DIR + 'wp-includes/js/media/grid.manifest.js' ]
     124                                },
     125                                options: { debug: true }
     126                        }
     127                },
    117128                sass: {
    118129                        colors: {
    119130                                expand: true,
     
    358369                                        '!wp-includes/js/zxcvbn.min.js'
    359370                                ]
    360371                        },
     372                        media: {
     373                                expand: true,
     374                                cwd: SOURCE_DIR,
     375                                dest: BUILD_DIR,
     376                                ext: '.min.js',
     377                                src: [
     378                                        'wp-includes/js/media/audio-video.js',
     379                                        'wp-includes/js/media/grid.js',
     380                                        'wp-includes/js/media/models.js',
     381                                        'wp-includes/js/media/views.js'
     382                                ]
     383                        },
    361384                        jqueryui: {
    362385                                options: {
    363386                                        preserveComments: 'some'
     
    435458                                        interval: 2000
    436459                                }
    437460                        },
     461                        browserify: {
     462                                files: [
     463                                        SOURCE_DIR + 'wp-includes/js/media/**/*.js',
     464                                        '!' + SOURCE_DIR + 'wp-includes/js/media/audio-video.js',
     465                                        '!' + SOURCE_DIR + 'wp-includes/js/media/grid.js',
     466                                        '!' + SOURCE_DIR + 'wp-includes/js/media/models.js',
     467                                        '!' + SOURCE_DIR + 'wp-includes/js/media/views.js'
     468                                ],
     469                                tasks: ['browserify', 'uglify:media']
     470                        },
    438471                        config: {
    439472                                files: 'Gruntfile.js'
    440473                        },
     
    483516
    484517        // Build task.
    485518        grunt.registerTask('build', ['clean:all', 'copy:all', 'cssmin:core', 'colors', 'rtl', 'cssmin:rtl', 'cssmin:colors',
    486                 'uglify:core', 'uglify:jqueryui', 'concat:tinymce', 'compress:tinymce', 'clean:tinymce', 'jsvalidate:build']);
     519                'browserify:media', 'uglify:core', 'uglify:media', 'uglify:jqueryui', 'concat:tinymce', 'compress:tinymce',
     520                'clean:tinymce', 'jsvalidate:build']);
    487521
    488522        // Testing tasks.
    489523        grunt.registerMultiTask('phpunit', 'Runs PHPUnit tests, including the ajax, external-http, and multisite tests.', function() {
  • package.json

     
    1111  "devDependencies": {
    1212    "grunt": "~0.4.5",
    1313    "grunt-autoprefixer": "~1.0.1",
     14        "grunt-browserify": "~3.2.1",
    1415    "grunt-contrib-clean": "~0.6.0",
    1516    "grunt-contrib-compress": "~0.12.0",
    1617    "grunt-contrib-concat": "~0.5.0",
  • src/wp-includes/js/media/audio-video.js

     
     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/* global _wpMediaViewsL10n, _wpmejsSettings, MediaElementPlayer */
     3
     4(function($, _, Backbone) {
     5        var media = wp.media,
     6                baseSettings = {},
     7                l10n = typeof _wpMediaViewsL10n === 'undefined' ? {} : _wpMediaViewsL10n;
     8
     9        if ( ! _.isUndefined( window._wpmejsSettings ) ) {
     10                baseSettings = _wpmejsSettings;
     11        }
     12
     13        /**
     14         * @mixin
     15         */
     16        wp.media.mixin = {
     17                mejsSettings: baseSettings,
     18
     19                removeAllPlayers: function() {
     20                        var p;
     21
     22                        if ( window.mejs && window.mejs.players ) {
     23                                for ( p in window.mejs.players ) {
     24                                        window.mejs.players[p].pause();
     25                                        this.removePlayer( window.mejs.players[p] );
     26                                }
     27                        }
     28                },
     29
     30                /**
     31                 * Override the MediaElement method for removing a player.
     32                 *      MediaElement tries to pull the audio/video tag out of
     33                 *      its container and re-add it to the DOM.
     34                 */
     35                removePlayer: function(t) {
     36                        var featureIndex, feature;
     37
     38                        if ( ! t.options ) {
     39                                return;
     40                        }
     41
     42                        // invoke features cleanup
     43                        for ( featureIndex in t.options.features ) {
     44                                feature = t.options.features[featureIndex];
     45                                if ( t['clean' + feature] ) {
     46                                        try {
     47                                                t['clean' + feature](t);
     48                                        } catch (e) {}
     49                                }
     50                        }
     51
     52                        if ( ! t.isDynamic ) {
     53                                t.$node.remove();
     54                        }
     55
     56                        if ( 'native' !== t.media.pluginType ) {
     57                                t.media.remove();
     58                        }
     59
     60                        delete window.mejs.players[t.id];
     61
     62                        t.container.remove();
     63                        t.globalUnbind();
     64                        delete t.node.player;
     65                },
     66
     67                /**
     68                 * Allows any class that has set 'player' to a MediaElementPlayer
     69                 *  instance to remove the player when listening to events.
     70                 *
     71                 *  Examples: modal closes, shortcode properties are removed, etc.
     72                 */
     73                unsetPlayers : function() {
     74                        if ( this.players && this.players.length ) {
     75                                _.each( this.players, function (player) {
     76                                        player.pause();
     77                                        wp.media.mixin.removePlayer( player );
     78                                } );
     79                                this.players = [];
     80                        }
     81                }
     82        };
     83
     84        /**
     85         * Autowire "collection"-type shortcodes
     86         */
     87        wp.media.playlist = new wp.media.collection({
     88                tag: 'playlist',
     89                editTitle : l10n.editPlaylistTitle,
     90                defaults : {
     91                        id: wp.media.view.settings.post.id,
     92                        style: 'light',
     93                        tracklist: true,
     94                        tracknumbers: true,
     95                        images: true,
     96                        artists: true,
     97                        type: 'audio'
     98                }
     99        });
     100
     101        /**
     102         * Shortcode modeling for audio
     103         *  `edit()` prepares the shortcode for the media modal
     104         *  `shortcode()` builds the new shortcode after update
     105         *
     106         * @namespace
     107         */
     108        wp.media.audio = {
     109                coerce : wp.media.coerce,
     110
     111                defaults : {
     112                        id : wp.media.view.settings.post.id,
     113                        src : '',
     114                        loop : false,
     115                        autoplay : false,
     116                        preload : 'none',
     117                        width : 400
     118                },
     119
     120                edit : function( data ) {
     121                        var frame, shortcode = wp.shortcode.next( 'audio', data ).shortcode;
     122
     123                        frame = wp.media({
     124                                frame: 'audio',
     125                                state: 'audio-details',
     126                                metadata: _.defaults( shortcode.attrs.named, this.defaults )
     127                        });
     128
     129                        return frame;
     130                },
     131
     132                shortcode : function( model ) {
     133                        var self = this, content;
     134
     135                        _.each( this.defaults, function( value, key ) {
     136                                model[ key ] = self.coerce( model, key );
     137
     138                                if ( value === model[ key ] ) {
     139                                        delete model[ key ];
     140                                }
     141                        });
     142
     143                        content = model.content;
     144                        delete model.content;
     145
     146                        return new wp.shortcode({
     147                                tag: 'audio',
     148                                attrs: model,
     149                                content: content
     150                        });
     151                }
     152        };
     153
     154        /**
     155         * Shortcode modeling for video
     156         *  `edit()` prepares the shortcode for the media modal
     157         *  `shortcode()` builds the new shortcode after update
     158         *
     159         * @namespace
     160         */
     161        wp.media.video = {
     162                coerce : wp.media.coerce,
     163
     164                defaults : {
     165                        id : wp.media.view.settings.post.id,
     166                        src : '',
     167                        poster : '',
     168                        loop : false,
     169                        autoplay : false,
     170                        preload : 'metadata',
     171                        content : '',
     172                        width : 640,
     173                        height : 360
     174                },
     175
     176                edit : function( data ) {
     177                        var frame,
     178                                shortcode = wp.shortcode.next( 'video', data ).shortcode,
     179                                attrs;
     180
     181                        attrs = shortcode.attrs.named;
     182                        attrs.content = shortcode.content;
     183
     184                        frame = wp.media({
     185                                frame: 'video',
     186                                state: 'video-details',
     187                                metadata: _.defaults( attrs, this.defaults )
     188                        });
     189
     190                        return frame;
     191                },
     192
     193                shortcode : function( model ) {
     194                        var self = this, content;
     195
     196                        _.each( this.defaults, function( value, key ) {
     197                                model[ key ] = self.coerce( model, key );
     198
     199                                if ( value === model[ key ] ) {
     200                                        delete model[ key ];
     201                                }
     202                        });
     203
     204                        content = model.content;
     205                        delete model.content;
     206
     207                        return new wp.shortcode({
     208                                tag: 'video',
     209                                attrs: model,
     210                                content: content
     211                        });
     212                }
     213        };
     214
     215        media.model.PostMedia = require( './models/post-media.js' );
     216        media.controller.AudioDetails = require( './controllers/audio-details.js' );
     217        media.controller.VideoDetails = require( './controllers/video-details.js' );
     218        media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' );
     219        media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' );
     220        media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' );
     221        media.view.MediaDetails = require( './views/media-details.js' );
     222        media.view.AudioDetails = require( './views/audio-details.js' );
     223        media.view.VideoDetails = require( './views/video-details.js' );
     224
     225}(jQuery, _, Backbone));
     226
     227},{"./controllers/audio-details.js":2,"./controllers/video-details.js":8,"./models/post-media.js":11,"./views/audio-details.js":25,"./views/frame/audio-details.js":29,"./views/frame/media-details.js":30,"./views/frame/video-details.js":32,"./views/media-details.js":35,"./views/video-details.js":54}],2:[function(require,module,exports){
     228/**
     229 * The controller for the Audio Details state
     230 *
     231 * @constructor
     232 * @augments wp.media.controller.State
     233 * @augments Backbone.Model
     234 */
     235var State = require( './state.js' ),
     236        l10n = wp.media.view.l10n,
     237        AudioDetails;
     238
     239AudioDetails = State.extend({
     240        defaults: {
     241                id: 'audio-details',
     242                toolbar: 'audio-details',
     243                title: l10n.audioDetailsTitle,
     244                content: 'audio-details',
     245                menu: 'audio-details',
     246                router: false,
     247                priority: 60
     248        },
     249
     250        initialize: function( options ) {
     251                this.media = options.media;
     252                State.prototype.initialize.apply( this, arguments );
     253        }
     254});
     255
     256module.exports = AudioDetails;
     257
     258},{"./state.js":7}],3:[function(require,module,exports){
     259/**
     260 * wp.media.controller.Library
     261 *
     262 * A state for choosing an attachment or group of attachments from the media library.
     263 *
     264 * @class
     265 * @augments wp.media.controller.State
     266 * @augments Backbone.Model
     267 * @mixes media.selectionSync
     268 *
     269 * @param {object}                          [attributes]                         The attributes hash passed to the state.
     270 * @param {string}                          [attributes.id=library]              Unique identifier.
     271 * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
     272 * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
     273 *                                                                               If one is not supplied, a collection of all attachments will be created.
     274 * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
     275 *                                                                               If the 'selection' attribute is a plain JS object,
     276 *                                                                               a Selection will be created using its values as the selection instance's `props` model.
     277 *                                                                               Otherwise, it will copy the library's `props` model.
     278 * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
     279 * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
     280 *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
     281 * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
     282 * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
     283 * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
     284 * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
     285 * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
     286 *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
     287 * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     288 * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     289 * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     290 * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     291 * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
     292 */
     293var selectionSync = require( '../utils/selection-sync.js' ),
     294        SelectionModel = require( '../models/selection.js' ),
     295        State = require( './state.js' ),
     296        l10n = wp.media.view.l10n,
     297        Library;
     298
     299Library = State.extend({
     300        defaults: {
     301                id:                 'library',
     302                title:              l10n.mediaLibraryTitle,
     303                multiple:           false,
     304                content:            'upload',
     305                menu:               'default',
     306                router:             'browse',
     307                toolbar:            'select',
     308                searchable:         true,
     309                filterable:         false,
     310                sortable:           true,
     311                autoSelect:         true,
     312                describe:           false,
     313                contentUserSetting: true,
     314                syncSelection:      true
     315        },
     316
     317        /**
     318         * If a library isn't provided, query all media items.
     319         * If a selection instance isn't provided, create one.
     320         *
     321         * @since 3.5.0
     322         */
     323        initialize: function() {
     324                var selection = this.get('selection'),
     325                        props;
     326
     327                if ( ! this.get('library') ) {
     328                        this.set( 'library', wp.media.query() );
     329                }
     330
     331                if ( ! (selection instanceof SelectionModel) ) {
     332                        props = selection;
     333
     334                        if ( ! props ) {
     335                                props = this.get('library').props.toJSON();
     336                                props = _.omit( props, 'orderby', 'query' );
     337                        }
     338
     339                        this.set( 'selection', new SelectionModel( null, {
     340                                multiple: this.get('multiple'),
     341                                props: props
     342                        }) );
     343                }
     344
     345                this.resetDisplays();
     346        },
     347
     348        /**
     349         * @since 3.5.0
     350         */
     351        activate: function() {
     352                this.syncSelection();
     353
     354                wp.Uploader.queue.on( 'add', this.uploading, this );
     355
     356                this.get('selection').on( 'add remove reset', this.refreshContent, this );
     357
     358                if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
     359                        this.frame.on( 'content:activate', this.saveContentMode, this );
     360                        this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
     361                }
     362        },
     363
     364        /**
     365         * @since 3.5.0
     366         */
     367        deactivate: function() {
     368                this.recordSelection();
     369
     370                this.frame.off( 'content:activate', this.saveContentMode, this );
     371
     372                // Unbind all event handlers that use this state as the context
     373                // from the selection.
     374                this.get('selection').off( null, null, this );
     375
     376                wp.Uploader.queue.off( null, null, this );
     377        },
     378
     379        /**
     380         * Reset the library to its initial state.
     381         *
     382         * @since 3.5.0
     383         */
     384        reset: function() {
     385                this.get('selection').reset();
     386                this.resetDisplays();
     387                this.refreshContent();
     388        },
     389
     390        /**
     391         * Reset the attachment display settings defaults to the site options.
     392         *
     393         * If site options don't define them, fall back to a persistent user setting.
     394         *
     395         * @since 3.5.0
     396         */
     397        resetDisplays: function() {
     398                var defaultProps = wp.media.view.settings.defaultProps;
     399                this._displays = [];
     400                this._defaultDisplaySettings = {
     401                        align: defaultProps.align || getUserSetting( 'align', 'none' ),
     402                        size:  defaultProps.size  || getUserSetting( 'imgsize', 'medium' ),
     403                        link:  defaultProps.link  || getUserSetting( 'urlbutton', 'file' )
     404                };
     405        },
     406
     407        /**
     408         * Create a model to represent display settings (alignment, etc.) for an attachment.
     409         *
     410         * @since 3.5.0
     411         *
     412         * @param {wp.media.model.Attachment} attachment
     413         * @returns {Backbone.Model}
     414         */
     415        display: function( attachment ) {
     416                var displays = this._displays;
     417
     418                if ( ! displays[ attachment.cid ] ) {
     419                        displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
     420                }
     421                return displays[ attachment.cid ];
     422        },
     423
     424        /**
     425         * Given an attachment, create attachment display settings properties.
     426         *
     427         * @since 3.6.0
     428         *
     429         * @param {wp.media.model.Attachment} attachment
     430         * @returns {Object}
     431         */
     432        defaultDisplaySettings: function( attachment ) {
     433                var settings = this._defaultDisplaySettings;
     434                if ( settings.canEmbed = this.canEmbed( attachment ) ) {
     435                        settings.link = 'embed';
     436                }
     437                return settings;
     438        },
     439
     440        /**
     441         * Whether an attachment can be embedded (audio or video).
     442         *
     443         * @since 3.6.0
     444         *
     445         * @param {wp.media.model.Attachment} attachment
     446         * @returns {Boolean}
     447         */
     448        canEmbed: function( attachment ) {
     449                // If uploading, we know the filename but not the mime type.
     450                if ( ! attachment.get('uploading') ) {
     451                        var type = attachment.get('type');
     452                        if ( type !== 'audio' && type !== 'video' ) {
     453                                return false;
     454                        }
     455                }
     456
     457                return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
     458        },
     459
     460
     461        /**
     462         * If the state is active, no items are selected, and the current
     463         * content mode is not an option in the state's router (provided
     464         * the state has a router), reset the content mode to the default.
     465         *
     466         * @since 3.5.0
     467         */
     468        refreshContent: function() {
     469                var selection = this.get('selection'),
     470                        frame = this.frame,
     471                        router = frame.router.get(),
     472                        mode = frame.content.mode();
     473
     474                if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
     475                        this.frame.content.render( this.get('content') );
     476                }
     477        },
     478
     479        /**
     480         * Callback handler when an attachment is uploaded.
     481         *
     482         * Switch to the Media Library if uploaded from the 'Upload Files' tab.
     483         *
     484         * Adds any uploading attachments to the selection.
     485         *
     486         * If the state only supports one attachment to be selected and multiple
     487         * attachments are uploaded, the last attachment in the upload queue will
     488         * be selected.
     489         *
     490         * @since 3.5.0
     491         *
     492         * @param {wp.media.model.Attachment} attachment
     493         */
     494        uploading: function( attachment ) {
     495                var content = this.frame.content;
     496
     497                if ( 'upload' === content.mode() ) {
     498                        this.frame.content.mode('browse');
     499                }
     500
     501                if ( this.get( 'autoSelect' ) ) {
     502                        this.get('selection').add( attachment );
     503                        this.frame.trigger( 'library:selection:add' );
     504                }
     505        },
     506
     507        /**
     508         * Persist the mode of the content region as a user setting.
     509         *
     510         * @since 3.5.0
     511         */
     512        saveContentMode: function() {
     513                if ( 'browse' !== this.get('router') ) {
     514                        return;
     515                }
     516
     517                var mode = this.frame.content.mode(),
     518                        view = this.frame.router.get();
     519
     520                if ( view && view.get( mode ) ) {
     521                        setUserSetting( 'libraryContent', mode );
     522                }
     523        }
     524});
     525
     526// Make selectionSync available on any Media Library state.
     527_.extend( Library.prototype, selectionSync );
     528
     529module.exports = Library;
     530},{"../models/selection.js":13,"../utils/selection-sync.js":14,"./state.js":7}],4:[function(require,module,exports){
     531/**
     532 * wp.media.controller.MediaLibrary
     533 *
     534 * @class
     535 * @augments wp.media.controller.Library
     536 * @augments wp.media.controller.State
     537 * @augments Backbone.Model
     538 */
     539var Library = require( './library.js' ),
     540        MediaLibrary;
     541
     542MediaLibrary = Library.extend({
     543        defaults: _.defaults({
     544                // Attachments browser defaults. @see media.view.AttachmentsBrowser
     545                filterable:      'uploaded',
     546
     547                displaySettings: false,
     548                priority:        80,
     549                syncSelection:   false
     550        }, Library.prototype.defaults ),
     551
     552        /**
     553         * @since 3.9.0
     554         *
     555         * @param options
     556         */
     557        initialize: function( options ) {
     558                this.media = options.media;
     559                this.type = options.type;
     560                this.set( 'library', wp.media.query({ type: this.type }) );
     561
     562                Library.prototype.initialize.apply( this, arguments );
     563        },
     564
     565        /**
     566         * @since 3.9.0
     567         */
     568        activate: function() {
     569                // @todo this should use this.frame.
     570                if ( wp.media.frame.lastMime ) {
     571                        this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
     572                        delete wp.media.frame.lastMime;
     573                }
     574                Library.prototype.activate.apply( this, arguments );
     575        }
     576});
     577
     578module.exports = MediaLibrary;
     579},{"./library.js":3}],5:[function(require,module,exports){
     580/**
     581 * wp.media.controller.Region
     582 *
     583 * A region is a persistent application layout area.
     584 *
     585 * A region assumes one mode at any time, and can be switched to another.
     586 *
     587 * When mode changes, events are triggered on the region's parent view.
     588 * The parent view will listen to specific events and fill the region with an
     589 * appropriate view depending on mode. For example, a frame listens for the
     590 * 'browse' mode t be activated on the 'content' view and then fills the region
     591 * with an AttachmentsBrowser view.
     592 *
     593 * @class
     594 *
     595 * @param {object}        options          Options hash for the region.
     596 * @param {string}        options.id       Unique identifier for the region.
     597 * @param {Backbone.View} options.view     A parent view the region exists within.
     598 * @param {string}        options.selector jQuery selector for the region within the parent view.
     599 */
     600var Region = function( options ) {
     601        _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
     602};
     603
     604// Use Backbone's self-propagating `extend` inheritance method.
     605Region.extend = Backbone.Model.extend;
     606
     607_.extend( Region.prototype, {
     608        /**
     609         * Activate a mode.
     610         *
     611         * @since 3.5.0
     612         *
     613         * @param {string} mode
     614         *
     615         * @fires this.view#{this.id}:activate:{this._mode}
     616         * @fires this.view#{this.id}:activate
     617         * @fires this.view#{this.id}:deactivate:{this._mode}
     618         * @fires this.view#{this.id}:deactivate
     619         *
     620         * @returns {wp.media.controller.Region} Returns itself to allow chaining.
     621         */
     622        mode: function( mode ) {
     623                if ( ! mode ) {
     624                        return this._mode;
     625                }
     626                // Bail if we're trying to change to the current mode.
     627                if ( mode === this._mode ) {
     628                        return this;
     629                }
     630
     631                /**
     632                 * Region mode deactivation event.
     633                 *
     634                 * @event this.view#{this.id}:deactivate:{this._mode}
     635                 * @event this.view#{this.id}:deactivate
     636                 */
     637                this.trigger('deactivate');
     638
     639                this._mode = mode;
     640                this.render( mode );
     641
     642                /**
     643                 * Region mode activation event.
     644                 *
     645                 * @event this.view#{this.id}:activate:{this._mode}
     646                 * @event this.view#{this.id}:activate
     647                 */
     648                this.trigger('activate');
     649                return this;
     650        },
     651        /**
     652         * Render a mode.
     653         *
     654         * @since 3.5.0
     655         *
     656         * @param {string} mode
     657         *
     658         * @fires this.view#{this.id}:create:{this._mode}
     659         * @fires this.view#{this.id}:create
     660         * @fires this.view#{this.id}:render:{this._mode}
     661         * @fires this.view#{this.id}:render
     662         *
     663         * @returns {wp.media.controller.Region} Returns itself to allow chaining
     664         */
     665        render: function( mode ) {
     666                // If the mode isn't active, activate it.
     667                if ( mode && mode !== this._mode ) {
     668                        return this.mode( mode );
     669                }
     670
     671                var set = { view: null },
     672                        view;
     673
     674                /**
     675                 * Create region view event.
     676                 *
     677                 * Region view creation takes place in an event callback on the frame.
     678                 *
     679                 * @event this.view#{this.id}:create:{this._mode}
     680                 * @event this.view#{this.id}:create
     681                 */
     682                this.trigger( 'create', set );
     683                view = set.view;
     684
     685                /**
     686                 * Render region view event.
     687                 *
     688                 * Region view creation takes place in an event callback on the frame.
     689                 *
     690                 * @event this.view#{this.id}:create:{this._mode}
     691                 * @event this.view#{this.id}:create
     692                 */
     693                this.trigger( 'render', view );
     694                if ( view ) {
     695                        this.set( view );
     696                }
     697                return this;
     698        },
     699
     700        /**
     701         * Get the region's view.
     702         *
     703         * @since 3.5.0
     704         *
     705         * @returns {wp.media.View}
     706         */
     707        get: function() {
     708                return this.view.views.first( this.selector );
     709        },
     710
     711        /**
     712         * Set the region's view as a subview of the frame.
     713         *
     714         * @since 3.5.0
     715         *
     716         * @param {Array|Object} views
     717         * @param {Object} [options={}]
     718         * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
     719         */
     720        set: function( views, options ) {
     721                if ( options ) {
     722                        options.add = false;
     723                }
     724                return this.view.views.set( this.selector, views, options );
     725        },
     726
     727        /**
     728         * Trigger regional view events on the frame.
     729         *
     730         * @since 3.5.0
     731         *
     732         * @param {string} event
     733         * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
     734         */
     735        trigger: function( event ) {
     736                var base, args;
     737
     738                if ( ! this._mode ) {
     739                        return;
     740                }
     741
     742                args = _.toArray( arguments );
     743                base = this.id + ':' + event;
     744
     745                // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
     746                args[0] = base + ':' + this._mode;
     747                this.view.trigger.apply( this.view, args );
     748
     749                // Trigger `{this.id}:{event}` event on the frame.
     750                args[0] = base;
     751                this.view.trigger.apply( this.view, args );
     752                return this;
     753        }
     754});
     755
     756module.exports = Region;
     757},{}],6:[function(require,module,exports){
     758/**
     759 * wp.media.controller.StateMachine
     760 *
     761 * A state machine keeps track of state. It is in one state at a time,
     762 * and can change from one state to another.
     763 *
     764 * States are stored as models in a Backbone collection.
     765 *
     766 * @since 3.5.0
     767 *
     768 * @class
     769 * @augments Backbone.Model
     770 * @mixin
     771 * @mixes Backbone.Events
     772 *
     773 * @param {Array} states
     774 */
     775var StateMachine = function( states ) {
     776        // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
     777        this.states = new Backbone.Collection( states );
     778};
     779
     780// Use Backbone's self-propagating `extend` inheritance method.
     781StateMachine.extend = Backbone.Model.extend;
     782
     783_.extend( StateMachine.prototype, Backbone.Events, {
     784        /**
     785         * Fetch a state.
     786         *
     787         * If no `id` is provided, returns the active state.
     788         *
     789         * Implicitly creates states.
     790         *
     791         * Ensure that the `states` collection exists so the `StateMachine`
     792         *   can be used as a mixin.
     793         *
     794         * @since 3.5.0
     795         *
     796         * @param {string} id
     797         * @returns {wp.media.controller.State} Returns a State model
     798         *   from the StateMachine collection
     799         */
     800        state: function( id ) {
     801                this.states = this.states || new Backbone.Collection();
     802
     803                // Default to the active state.
     804                id = id || this._state;
     805
     806                if ( id && ! this.states.get( id ) ) {
     807                        this.states.add({ id: id });
     808                }
     809                return this.states.get( id );
     810        },
     811
     812        /**
     813         * Sets the active state.
     814         *
     815         * Bail if we're trying to select the current state, if we haven't
     816         * created the `states` collection, or are trying to select a state
     817         * that does not exist.
     818         *
     819         * @since 3.5.0
     820         *
     821         * @param {string} id
     822         *
     823         * @fires wp.media.controller.State#deactivate
     824         * @fires wp.media.controller.State#activate
     825         *
     826         * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
     827         */
     828        setState: function( id ) {
     829                var previous = this.state();
     830
     831                if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
     832                        return this;
     833                }
     834
     835                if ( previous ) {
     836                        previous.trigger('deactivate');
     837                        this._lastState = previous.id;
     838                }
     839
     840                this._state = id;
     841                this.state().trigger('activate');
     842
     843                return this;
     844        },
     845
     846        /**
     847         * Returns the previous active state.
     848         *
     849         * Call the `state()` method with no parameters to retrieve the current
     850         * active state.
     851         *
     852         * @since 3.5.0
     853         *
     854         * @returns {wp.media.controller.State} Returns a State model
     855         *    from the StateMachine collection
     856         */
     857        lastState: function() {
     858                if ( this._lastState ) {
     859                        return this.state( this._lastState );
     860                }
     861        }
     862});
     863
     864// Map all event binding and triggering on a StateMachine to its `states` collection.
     865_.each([ 'on', 'off', 'trigger' ], function( method ) {
     866        /**
     867         * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     868         */
     869        StateMachine.prototype[ method ] = function() {
     870                // Ensure that the `states` collection exists so the `StateMachine`
     871                // can be used as a mixin.
     872                this.states = this.states || new Backbone.Collection();
     873                // Forward the method to the `states` collection.
     874                this.states[ method ].apply( this.states, arguments );
     875                return this;
     876        };
     877});
     878
     879module.exports = StateMachine;
     880},{}],7:[function(require,module,exports){
     881/**
     882 * wp.media.controller.State
     883 *
     884 * A state is a step in a workflow that when set will trigger the controllers
     885 * for the regions to be updated as specified in the frame.
     886 *
     887 * A state has an event-driven lifecycle:
     888 *
     889 *     'ready'      triggers when a state is added to a state machine's collection.
     890 *     'activate'   triggers when a state is activated by a state machine.
     891 *     'deactivate' triggers when a state is deactivated by a state machine.
     892 *     'reset'      is not triggered automatically. It should be invoked by the
     893 *                  proper controller to reset the state to its default.
     894 *
     895 * @class
     896 * @augments Backbone.Model
     897 */
     898var State = Backbone.Model.extend({
     899        /**
     900         * Constructor.
     901         *
     902         * @since 3.5.0
     903         */
     904        constructor: function() {
     905                this.on( 'activate', this._preActivate, this );
     906                this.on( 'activate', this.activate, this );
     907                this.on( 'activate', this._postActivate, this );
     908                this.on( 'deactivate', this._deactivate, this );
     909                this.on( 'deactivate', this.deactivate, this );
     910                this.on( 'reset', this.reset, this );
     911                this.on( 'ready', this._ready, this );
     912                this.on( 'ready', this.ready, this );
     913                /**
     914                 * Call parent constructor with passed arguments
     915                 */
     916                Backbone.Model.apply( this, arguments );
     917                this.on( 'change:menu', this._updateMenu, this );
     918        },
     919        /**
     920         * Ready event callback.
     921         *
     922         * @abstract
     923         * @since 3.5.0
     924         */
     925        ready: function() {},
     926
     927        /**
     928         * Activate event callback.
     929         *
     930         * @abstract
     931         * @since 3.5.0
     932         */
     933        activate: function() {},
     934
     935        /**
     936         * Deactivate event callback.
     937         *
     938         * @abstract
     939         * @since 3.5.0
     940         */
     941        deactivate: function() {},
     942
     943        /**
     944         * Reset event callback.
     945         *
     946         * @abstract
     947         * @since 3.5.0
     948         */
     949        reset: function() {},
     950
     951        /**
     952         * @access private
     953         * @since 3.5.0
     954         */
     955        _ready: function() {
     956                this._updateMenu();
     957        },
     958
     959        /**
     960         * @access private
     961         * @since 3.5.0
     962        */
     963        _preActivate: function() {
     964                this.active = true;
     965        },
     966
     967        /**
     968         * @access private
     969         * @since 3.5.0
     970         */
     971        _postActivate: function() {
     972                this.on( 'change:menu', this._menu, this );
     973                this.on( 'change:titleMode', this._title, this );
     974                this.on( 'change:content', this._content, this );
     975                this.on( 'change:toolbar', this._toolbar, this );
     976
     977                this.frame.on( 'title:render:default', this._renderTitle, this );
     978
     979                this._title();
     980                this._menu();
     981                this._toolbar();
     982                this._content();
     983                this._router();
     984        },
     985
     986        /**
     987         * @access private
     988         * @since 3.5.0
     989         */
     990        _deactivate: function() {
     991                this.active = false;
     992
     993                this.frame.off( 'title:render:default', this._renderTitle, this );
     994
     995                this.off( 'change:menu', this._menu, this );
     996                this.off( 'change:titleMode', this._title, this );
     997                this.off( 'change:content', this._content, this );
     998                this.off( 'change:toolbar', this._toolbar, this );
     999        },
     1000
     1001        /**
     1002         * @access private
     1003         * @since 3.5.0
     1004         */
     1005        _title: function() {
     1006                this.frame.title.render( this.get('titleMode') || 'default' );
     1007        },
     1008
     1009        /**
     1010         * @access private
     1011         * @since 3.5.0
     1012         */
     1013        _renderTitle: function( view ) {
     1014                view.$el.text( this.get('title') || '' );
     1015        },
     1016
     1017        /**
     1018         * @access private
     1019         * @since 3.5.0
     1020         */
     1021        _router: function() {
     1022                var router = this.frame.router,
     1023                        mode = this.get('router'),
     1024                        view;
     1025
     1026                this.frame.$el.toggleClass( 'hide-router', ! mode );
     1027                if ( ! mode ) {
     1028                        return;
     1029                }
     1030
     1031                this.frame.router.render( mode );
     1032
     1033                view = router.get();
     1034                if ( view && view.select ) {
     1035                        view.select( this.frame.content.mode() );
     1036                }
     1037        },
     1038
     1039        /**
     1040         * @access private
     1041         * @since 3.5.0
     1042         */
     1043        _menu: function() {
     1044                var menu = this.frame.menu,
     1045                        mode = this.get('menu'),
     1046                        view;
     1047
     1048                this.frame.$el.toggleClass( 'hide-menu', ! mode );
     1049                if ( ! mode ) {
     1050                        return;
     1051                }
     1052
     1053                menu.mode( mode );
     1054
     1055                view = menu.get();
     1056                if ( view && view.select ) {
     1057                        view.select( this.id );
     1058                }
     1059        },
     1060
     1061        /**
     1062         * @access private
     1063         * @since 3.5.0
     1064         */
     1065        _updateMenu: function() {
     1066                var previous = this.previous('menu'),
     1067                        menu = this.get('menu');
     1068
     1069                if ( previous ) {
     1070                        this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
     1071                }
     1072
     1073                if ( menu ) {
     1074                        this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
     1075                }
     1076        },
     1077
     1078        /**
     1079         * Create a view in the media menu for the state.
     1080         *
     1081         * @access private
     1082         * @since 3.5.0
     1083         *
     1084         * @param {media.view.Menu} view The menu view.
     1085         */
     1086        _renderMenu: function( view ) {
     1087                var menuItem = this.get('menuItem'),
     1088                        title = this.get('title'),
     1089                        priority = this.get('priority');
     1090
     1091                if ( ! menuItem && title ) {
     1092                        menuItem = { text: title };
     1093
     1094                        if ( priority ) {
     1095                                menuItem.priority = priority;
     1096                        }
     1097                }
     1098
     1099                if ( ! menuItem ) {
     1100                        return;
     1101                }
     1102
     1103                view.set( this.id, menuItem );
     1104        }
     1105});
     1106
     1107_.each(['toolbar','content'], function( region ) {
     1108        /**
     1109         * @access private
     1110         */
     1111        State.prototype[ '_' + region ] = function() {
     1112                var mode = this.get( region );
     1113                if ( mode ) {
     1114                        this.frame[ region ].render( mode );
     1115                }
     1116        };
     1117});
     1118
     1119module.exports = State;
     1120},{}],8:[function(require,module,exports){
     1121/**
     1122 * The controller for the Video Details state
     1123 *
     1124 * @constructor
     1125 * @augments wp.media.controller.State
     1126 * @augments Backbone.Model
     1127 */
     1128var State = require( './state.js' ),
     1129        l10n = wp.media.view.l10n,
     1130        VideoDetails;
     1131
     1132VideoDetails = State.extend({
     1133        defaults: {
     1134                id: 'video-details',
     1135                toolbar: 'video-details',
     1136                title: l10n.videoDetailsTitle,
     1137                content: 'video-details',
     1138                menu: 'video-details',
     1139                router: false,
     1140                priority: 60
     1141        },
     1142
     1143        initialize: function( options ) {
     1144                this.media = options.media;
     1145                State.prototype.initialize.apply( this, arguments );
     1146        }
     1147});
     1148
     1149module.exports = VideoDetails;
     1150},{"./state.js":7}],9:[function(require,module,exports){
     1151/**
     1152 * wp.media.model.Attachment
     1153 *
     1154 * @class
     1155 * @augments Backbone.Model
     1156 */
     1157var $ = jQuery,
     1158        Attachment;
     1159
     1160Attachment = Backbone.Model.extend({
     1161        /**
     1162         * Triggered when attachment details change
     1163         * Overrides Backbone.Model.sync
     1164         *
     1165         * @param {string} method
     1166         * @param {wp.media.model.Attachment} model
     1167         * @param {Object} [options={}]
     1168         *
     1169         * @returns {Promise}
     1170         */
     1171        sync: function( method, model, options ) {
     1172                // If the attachment does not yet have an `id`, return an instantly
     1173                // rejected promise. Otherwise, all of our requests will fail.
     1174                if ( _.isUndefined( this.id ) ) {
     1175                        return $.Deferred().rejectWith( this ).promise();
     1176                }
     1177
     1178                // Overload the `read` request so Attachment.fetch() functions correctly.
     1179                if ( 'read' === method ) {
     1180                        options = options || {};
     1181                        options.context = this;
     1182                        options.data = _.extend( options.data || {}, {
     1183                                action: 'get-attachment',
     1184                                id: this.id
     1185                        });
     1186                        return wp.media.ajax( options );
     1187
     1188                // Overload the `update` request so properties can be saved.
     1189                } else if ( 'update' === method ) {
     1190                        // If we do not have the necessary nonce, fail immeditately.
     1191                        if ( ! this.get('nonces') || ! this.get('nonces').update ) {
     1192                                return $.Deferred().rejectWith( this ).promise();
     1193                        }
     1194
     1195                        options = options || {};
     1196                        options.context = this;
     1197
     1198                        // Set the action and ID.
     1199                        options.data = _.extend( options.data || {}, {
     1200                                action:  'save-attachment',
     1201                                id:      this.id,
     1202                                nonce:   this.get('nonces').update,
     1203                                post_id: wp.media.model.settings.post.id
     1204                        });
     1205
     1206                        // Record the values of the changed attributes.
     1207                        if ( model.hasChanged() ) {
     1208                                options.data.changes = {};
     1209
     1210                                _.each( model.changed, function( value, key ) {
     1211                                        options.data.changes[ key ] = this.get( key );
     1212                                }, this );
     1213                        }
     1214
     1215                        return wp.media.ajax( options );
     1216
     1217                // Overload the `delete` request so attachments can be removed.
     1218                // This will permanently delete an attachment.
     1219                } else if ( 'delete' === method ) {
     1220                        options = options || {};
     1221
     1222                        if ( ! options.wait ) {
     1223                                this.destroyed = true;
     1224                        }
     1225
     1226                        options.context = this;
     1227                        options.data = _.extend( options.data || {}, {
     1228                                action:   'delete-post',
     1229                                id:       this.id,
     1230                                _wpnonce: this.get('nonces')['delete']
     1231                        });
     1232
     1233                        return wp.media.ajax( options ).done( function() {
     1234                                this.destroyed = true;
     1235                        }).fail( function() {
     1236                                this.destroyed = false;
     1237                        });
     1238
     1239                // Otherwise, fall back to `Backbone.sync()`.
     1240                } else {
     1241                        /**
     1242                         * Call `sync` directly on Backbone.Model
     1243                         */
     1244                        return Backbone.Model.prototype.sync.apply( this, arguments );
     1245                }
     1246        },
     1247        /**
     1248         * Convert date strings into Date objects.
     1249         *
     1250         * @param {Object} resp The raw response object, typically returned by fetch()
     1251         * @returns {Object} The modified response object, which is the attributes hash
     1252         *    to be set on the model.
     1253         */
     1254        parse: function( resp ) {
     1255                if ( ! resp ) {
     1256                        return resp;
     1257                }
     1258
     1259                resp.date = new Date( resp.date );
     1260                resp.modified = new Date( resp.modified );
     1261                return resp;
     1262        },
     1263        /**
     1264         * @param {Object} data The properties to be saved.
     1265         * @param {Object} options Sync options. e.g. patch, wait, success, error.
     1266         *
     1267         * @this Backbone.Model
     1268         *
     1269         * @returns {Promise}
     1270         */
     1271        saveCompat: function( data, options ) {
     1272                var model = this;
     1273
     1274                // If we do not have the necessary nonce, fail immeditately.
     1275                if ( ! this.get('nonces') || ! this.get('nonces').update ) {
     1276                        return $.Deferred().rejectWith( this ).promise();
     1277                }
     1278
     1279                return media.post( 'save-attachment-compat', _.defaults({
     1280                        id:      this.id,
     1281                        nonce:   this.get('nonces').update,
     1282                        post_id: wp.media.model.settings.post.id
     1283                }, data ) ).done( function( resp, status, xhr ) {
     1284                        model.set( model.parse( resp, xhr ), options );
     1285                });
     1286        }
     1287}, {
     1288        /**
     1289         * Create a new model on the static 'all' attachments collection and return it.
     1290         *
     1291         * @static
     1292         * @param {Object} attrs
     1293         * @returns {wp.media.model.Attachment}
     1294         */
     1295        create: function( attrs ) {
     1296                var Attachments = require( './attachments.js' );
     1297                return Attachments.all.push( attrs );
     1298        },
     1299        /**
     1300         * Create a new model on the static 'all' attachments collection and return it.
     1301         *
     1302         * If this function has already been called for the id,
     1303         * it returns the specified attachment.
     1304         *
     1305         * @static
     1306         * @param {string} id A string used to identify a model.
     1307         * @param {Backbone.Model|undefined} attachment
     1308         * @returns {wp.media.model.Attachment}
     1309         */
     1310        get: _.memoize( function( id, attachment ) {
     1311                var Attachments = require( './attachments.js' );
     1312                return Attachments.all.push( attachment || { id: id } );
     1313        })
     1314});
     1315
     1316module.exports = Attachment;
     1317},{"./attachments.js":10}],10:[function(require,module,exports){
     1318/**
     1319 * wp.media.model.Attachments
     1320 *
     1321 * A collection of attachments.
     1322 *
     1323 * This collection has no persistence with the server without supplying
     1324 * 'options.props.query = true', which will mirror the collection
     1325 * to an Attachments Query collection - @see wp.media.model.Attachments.mirror().
     1326 *
     1327 * @class
     1328 * @augments Backbone.Collection
     1329 *
     1330 * @param {array}  [models]                Models to initialize with the collection.
     1331 * @param {object} [options]               Options hash for the collection.
     1332 * @param {string} [options.props]         Options hash for the initial query properties.
     1333 * @param {string} [options.props.order]   Initial order (ASC or DESC) for the collection.
     1334 * @param {string} [options.props.orderby] Initial attribute key to order the collection by.
     1335 * @param {string} [options.props.query]   Whether the collection is linked to an attachments query.
     1336 * @param {string} [options.observe]
     1337 * @param {string} [options.filters]
     1338 *
     1339 */
     1340var Attachment = require( './attachment.js' ),
     1341        Attachments;
     1342
     1343Attachments = Backbone.Collection.extend({
     1344        /**
     1345         * @type {wp.media.model.Attachment}
     1346         */
     1347        model: Attachment,
     1348        /**
     1349         * @param {Array} [models=[]] Array of models used to populate the collection.
     1350         * @param {Object} [options={}]
     1351         */
     1352        initialize: function( models, options ) {
     1353                options = options || {};
     1354
     1355                this.props   = new Backbone.Model();
     1356                this.filters = options.filters || {};
     1357
     1358                // Bind default `change` events to the `props` model.
     1359                this.props.on( 'change', this._changeFilteredProps, this );
     1360
     1361                this.props.on( 'change:order',   this._changeOrder,   this );
     1362                this.props.on( 'change:orderby', this._changeOrderby, this );
     1363                this.props.on( 'change:query',   this._changeQuery,   this );
     1364
     1365                this.props.set( _.defaults( options.props || {} ) );
     1366
     1367                if ( options.observe ) {
     1368                        this.observe( options.observe );
     1369                }
     1370        },
     1371        /**
     1372         * Sort the collection when the order attribute changes.
     1373         *
     1374         * @access private
     1375         */
     1376        _changeOrder: function() {
     1377                if ( this.comparator ) {
     1378                        this.sort();
     1379                }
     1380        },
     1381        /**
     1382         * Set the default comparator only when the `orderby` property is set.
     1383         *
     1384         * @access private
     1385         *
     1386         * @param {Backbone.Model} model
     1387         * @param {string} orderby
     1388         */
     1389        _changeOrderby: function( model, orderby ) {
     1390                // If a different comparator is defined, bail.
     1391                if ( this.comparator && this.comparator !== Attachments.comparator ) {
     1392                        return;
     1393                }
     1394
     1395                if ( orderby && 'post__in' !== orderby ) {
     1396                        this.comparator = Attachments.comparator;
     1397                } else {
     1398                        delete this.comparator;
     1399                }
     1400        },
     1401        /**
     1402         * If the `query` property is set to true, query the server using
     1403         * the `props` values, and sync the results to this collection.
     1404         *
     1405         * @access private
     1406         *
     1407         * @param {Backbone.Model} model
     1408         * @param {Boolean} query
     1409         */
     1410        _changeQuery: function( model, query ) {
     1411                if ( query ) {
     1412                        this.props.on( 'change', this._requery, this );
     1413                        this._requery();
     1414                } else {
     1415                        this.props.off( 'change', this._requery, this );
     1416                }
     1417        },
     1418        /**
     1419         * @access private
     1420         *
     1421         * @param {Backbone.Model} model
     1422         */
     1423        _changeFilteredProps: function( model ) {
     1424                // If this is a query, updating the collection will be handled by
     1425                // `this._requery()`.
     1426                if ( this.props.get('query') ) {
     1427                        return;
     1428                }
     1429
     1430                var changed = _.chain( model.changed ).map( function( t, prop ) {
     1431                        var filter = Attachments.filters[ prop ],
     1432                                term = model.get( prop );
     1433
     1434                        if ( ! filter ) {
     1435                                return;
     1436                        }
     1437
     1438                        if ( term && ! this.filters[ prop ] ) {
     1439                                this.filters[ prop ] = filter;
     1440                        } else if ( ! term && this.filters[ prop ] === filter ) {
     1441                                delete this.filters[ prop ];
     1442                        } else {
     1443                                return;
     1444                        }
     1445
     1446                        // Record the change.
     1447                        return true;
     1448                }, this ).any().value();
     1449
     1450                if ( ! changed ) {
     1451                        return;
     1452                }
     1453
     1454                // If no `Attachments` model is provided to source the searches
     1455                // from, then automatically generate a source from the existing
     1456                // models.
     1457                if ( ! this._source ) {
     1458                        this._source = new Attachments( this.models );
     1459                }
     1460
     1461                this.reset( this._source.filter( this.validator, this ) );
     1462        },
     1463
     1464        validateDestroyed: false,
     1465        /**
     1466         * Checks whether an attachment is valid.
     1467         *
     1468         * @param {wp.media.model.Attachment} attachment
     1469         * @returns {Boolean}
     1470         */
     1471        validator: function( attachment ) {
     1472                if ( ! this.validateDestroyed && attachment.destroyed ) {
     1473                        return false;
     1474                }
     1475                return _.all( this.filters, function( filter ) {
     1476                        return !! filter.call( this, attachment );
     1477                }, this );
     1478        },
     1479        /**
     1480         * Add or remove an attachment to the collection depending on its validity.
     1481         *
     1482         * @param {wp.media.model.Attachment} attachment
     1483         * @param {Object} options
     1484         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     1485         */
     1486        validate: function( attachment, options ) {
     1487                var valid = this.validator( attachment ),
     1488                        hasAttachment = !! this.get( attachment.cid );
     1489
     1490                if ( ! valid && hasAttachment ) {
     1491                        this.remove( attachment, options );
     1492                } else if ( valid && ! hasAttachment ) {
     1493                        this.add( attachment, options );
     1494                }
     1495
     1496                return this;
     1497        },
     1498
     1499        /**
     1500         * Add or remove all attachments from another collection depending on each one's validity.
     1501         *
     1502         * @param {wp.media.model.Attachments} attachments
     1503         * @param {object} [options={}]
     1504         *
     1505         * @fires wp.media.model.Attachments#reset
     1506         *
     1507         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     1508         */
     1509        validateAll: function( attachments, options ) {
     1510                options = options || {};
     1511
     1512                _.each( attachments.models, function( attachment ) {
     1513                        this.validate( attachment, { silent: true });
     1514                }, this );
     1515
     1516                if ( ! options.silent ) {
     1517                        this.trigger( 'reset', this, options );
     1518                }
     1519                return this;
     1520        },
     1521        /**
     1522         * Start observing another attachments collection change events
     1523         * and replicate them on this collection.
     1524         *
     1525         * @param {wp.media.model.Attachments} The attachments collection to observe.
     1526         * @returns {wp.media.model.Attachments} Returns itself to allow chaining.
     1527         */
     1528        observe: function( attachments ) {
     1529                this.observers = this.observers || [];
     1530                this.observers.push( attachments );
     1531
     1532                attachments.on( 'add change remove', this._validateHandler, this );
     1533                attachments.on( 'reset', this._validateAllHandler, this );
     1534                this.validateAll( attachments );
     1535                return this;
     1536        },
     1537        /**
     1538         * Stop replicating collection change events from another attachments collection.
     1539         *
     1540         * @param {wp.media.model.Attachments} The attachments collection to stop observing.
     1541         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     1542         */
     1543        unobserve: function( attachments ) {
     1544                if ( attachments ) {
     1545                        attachments.off( null, null, this );
     1546                        this.observers = _.without( this.observers, attachments );
     1547
     1548                } else {
     1549                        _.each( this.observers, function( attachments ) {
     1550                                attachments.off( null, null, this );
     1551                        }, this );
     1552                        delete this.observers;
     1553                }
     1554
     1555                return this;
     1556        },
     1557        /**
     1558         * @access private
     1559         *
     1560         * @param {wp.media.model.Attachments} attachment
     1561         * @param {wp.media.model.Attachments} attachments
     1562         * @param {Object} options
     1563         *
     1564         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     1565         */
     1566        _validateHandler: function( attachment, attachments, options ) {
     1567                // If we're not mirroring this `attachments` collection,
     1568                // only retain the `silent` option.
     1569                options = attachments === this.mirroring ? options : {
     1570                        silent: options && options.silent
     1571                };
     1572
     1573                return this.validate( attachment, options );
     1574        },
     1575        /**
     1576         * @access private
     1577         *
     1578         * @param {wp.media.model.Attachments} attachments
     1579         * @param {Object} options
     1580         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     1581         */
     1582        _validateAllHandler: function( attachments, options ) {
     1583                return this.validateAll( attachments, options );
     1584        },
     1585        /**
     1586         * Start mirroring another attachments collection, clearing out any models already
     1587         * in the collection.
     1588         *
     1589         * @param {wp.media.model.Attachments} The attachments collection to mirror.
     1590         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     1591         */
     1592        mirror: function( attachments ) {
     1593                if ( this.mirroring && this.mirroring === attachments ) {
     1594                        return this;
     1595                }
     1596
     1597                this.unmirror();
     1598                this.mirroring = attachments;
     1599
     1600                // Clear the collection silently. A `reset` event will be fired
     1601                // when `observe()` calls `validateAll()`.
     1602                this.reset( [], { silent: true } );
     1603                this.observe( attachments );
     1604
     1605                return this;
     1606        },
     1607        /**
     1608         * Stop mirroring another attachments collection.
     1609         */
     1610        unmirror: function() {
     1611                if ( ! this.mirroring ) {
     1612                        return;
     1613                }
     1614
     1615                this.unobserve( this.mirroring );
     1616                delete this.mirroring;
     1617        },
     1618        /**
     1619         * Retrive more attachments from the server for the collection.
     1620         *
     1621         * Only works if the collection is mirroring a Query Attachments collection,
     1622         * and forwards to its `more` method. This collection class doesn't have
     1623         * server persistence by itself.
     1624         *
     1625         * @param {object} options
     1626         * @returns {Promise}
     1627         */
     1628        more: function( options ) {
     1629                var deferred = jQuery.Deferred(),
     1630                        mirroring = this.mirroring,
     1631                        attachments = this;
     1632
     1633                if ( ! mirroring || ! mirroring.more ) {
     1634                        return deferred.resolveWith( this ).promise();
     1635                }
     1636                // If we're mirroring another collection, forward `more` to
     1637                // the mirrored collection. Account for a race condition by
     1638                // checking if we're still mirroring that collection when
     1639                // the request resolves.
     1640                mirroring.more( options ).done( function() {
     1641                        if ( this === attachments.mirroring )
     1642                                deferred.resolveWith( this );
     1643                });
     1644
     1645                return deferred.promise();
     1646        },
     1647        /**
     1648         * Whether there are more attachments that haven't been sync'd from the server
     1649         * that match the collection's query.
     1650         *
     1651         * Only works if the collection is mirroring a Query Attachments collection,
     1652         * and forwards to its `hasMore` method. This collection class doesn't have
     1653         * server persistence by itself.
     1654         *
     1655         * @returns {boolean}
     1656         */
     1657        hasMore: function() {
     1658                return this.mirroring ? this.mirroring.hasMore() : false;
     1659        },
     1660        /**
     1661         * A custom AJAX-response parser.
     1662         *
     1663         * See trac ticket #24753
     1664         *
     1665         * @param {Object|Array} resp The raw response Object/Array.
     1666         * @param {Object} xhr
     1667         * @returns {Array} The array of model attributes to be added to the collection
     1668         */
     1669        parse: function( resp, xhr ) {
     1670                if ( ! _.isArray( resp ) ) {
     1671                        resp = [resp];
     1672                }
     1673
     1674                return _.map( resp, function( attrs ) {
     1675                        var id, attachment, newAttributes;
     1676
     1677                        if ( attrs instanceof Backbone.Model ) {
     1678                                id = attrs.get( 'id' );
     1679                                attrs = attrs.attributes;
     1680                        } else {
     1681                                id = attrs.id;
     1682                        }
     1683
     1684                        attachment = Attachment.get( id );
     1685                        newAttributes = attachment.parse( attrs, xhr );
     1686
     1687                        if ( ! _.isEqual( attachment.attributes, newAttributes ) ) {
     1688                                attachment.set( newAttributes );
     1689                        }
     1690
     1691                        return attachment;
     1692                });
     1693        },
     1694        /**
     1695         * If the collection is a query, create and mirror an Attachments Query collection.
     1696         *
     1697         * @access private
     1698         */
     1699        _requery: function( refresh ) {
     1700                var props, Query;
     1701                if ( this.props.get('query') ) {
     1702                        Query = require( './query.js' );
     1703                        props = this.props.toJSON();
     1704                        props.cache = ( true !== refresh );
     1705                        this.mirror( Query.get( props ) );
     1706                }
     1707        },
     1708        /**
     1709         * If this collection is sorted by `menuOrder`, recalculates and saves
     1710         * the menu order to the database.
     1711         *
     1712         * @returns {undefined|Promise}
     1713         */
     1714        saveMenuOrder: function() {
     1715                if ( 'menuOrder' !== this.props.get('orderby') ) {
     1716                        return;
     1717                }
     1718
     1719                // Removes any uploading attachments, updates each attachment's
     1720                // menu order, and returns an object with an { id: menuOrder }
     1721                // mapping to pass to the request.
     1722                var attachments = this.chain().filter( function( attachment ) {
     1723                        return ! _.isUndefined( attachment.id );
     1724                }).map( function( attachment, index ) {
     1725                        // Indices start at 1.
     1726                        index = index + 1;
     1727                        attachment.set( 'menuOrder', index );
     1728                        return [ attachment.id, index ];
     1729                }).object().value();
     1730
     1731                if ( _.isEmpty( attachments ) ) {
     1732                        return;
     1733                }
     1734
     1735                return wp.media.post( 'save-attachment-order', {
     1736                        nonce:       wp.media.model.settings.post.nonce,
     1737                        post_id:     wp.media.model.settings.post.id,
     1738                        attachments: attachments
     1739                });
     1740        }
     1741}, {
     1742        /**
     1743         * A function to compare two attachment models in an attachments collection.
     1744         *
     1745         * Used as the default comparator for instances of wp.media.model.Attachments
     1746         * and its subclasses. @see wp.media.model.Attachments._changeOrderby().
     1747         *
     1748         * @static
     1749         *
     1750         * @param {Backbone.Model} a
     1751         * @param {Backbone.Model} b
     1752         * @param {Object} options
     1753         * @returns {Number} -1 if the first model should come before the second,
     1754         *    0 if they are of the same rank and
     1755         *    1 if the first model should come after.
     1756         */
     1757        comparator: function( a, b, options ) {
     1758                var key   = this.props.get('orderby'),
     1759                        order = this.props.get('order') || 'DESC',
     1760                        ac    = a.cid,
     1761                        bc    = b.cid;
     1762
     1763                a = a.get( key );
     1764                b = b.get( key );
     1765
     1766                if ( 'date' === key || 'modified' === key ) {
     1767                        a = a || new Date();
     1768                        b = b || new Date();
     1769                }
     1770
     1771                // If `options.ties` is set, don't enforce the `cid` tiebreaker.
     1772                if ( options && options.ties ) {
     1773                        ac = bc = null;
     1774                }
     1775
     1776                return ( 'DESC' === order ) ? wp.media.compare( a, b, ac, bc ) : wp.media.compare( b, a, bc, ac );
     1777        },
     1778        /**
     1779         * @namespace
     1780         */
     1781        filters: {
     1782                /**
     1783                 * @static
     1784                 * Note that this client-side searching is *not* equivalent
     1785                 * to our server-side searching.
     1786                 *
     1787                 * @param {wp.media.model.Attachment} attachment
     1788                 *
     1789                 * @this wp.media.model.Attachments
     1790                 *
     1791                 * @returns {Boolean}
     1792                 */
     1793                search: function( attachment ) {
     1794                        if ( ! this.props.get('search') ) {
     1795                                return true;
     1796                        }
     1797
     1798                        return _.any(['title','filename','description','caption','name'], function( key ) {
     1799                                var value = attachment.get( key );
     1800                                return value && -1 !== value.search( this.props.get('search') );
     1801                        }, this );
     1802                },
     1803                /**
     1804                 * @static
     1805                 * @param {wp.media.model.Attachment} attachment
     1806                 *
     1807                 * @this wp.media.model.Attachments
     1808                 *
     1809                 * @returns {Boolean}
     1810                 */
     1811                type: function( attachment ) {
     1812                        var type = this.props.get('type');
     1813                        return ! type || -1 !== type.indexOf( attachment.get('type') );
     1814                },
     1815                /**
     1816                 * @static
     1817                 * @param {wp.media.model.Attachment} attachment
     1818                 *
     1819                 * @this wp.media.model.Attachments
     1820                 *
     1821                 * @returns {Boolean}
     1822                 */
     1823                uploadedTo: function( attachment ) {
     1824                        var uploadedTo = this.props.get('uploadedTo');
     1825                        if ( _.isUndefined( uploadedTo ) ) {
     1826                                return true;
     1827                        }
     1828
     1829                        return uploadedTo === attachment.get('uploadedTo');
     1830                },
     1831                /**
     1832                 * @static
     1833                 * @param {wp.media.model.Attachment} attachment
     1834                 *
     1835                 * @this wp.media.model.Attachments
     1836                 *
     1837                 * @returns {Boolean}
     1838                 */
     1839                status: function( attachment ) {
     1840                        var status = this.props.get('status');
     1841                        if ( _.isUndefined( status ) ) {
     1842                                return true;
     1843                        }
     1844
     1845                        return status === attachment.get('status');
     1846                }
     1847        }
     1848});
     1849
     1850module.exports = Attachments;
     1851},{"./attachment.js":9,"./query.js":12}],11:[function(require,module,exports){
     1852/**
     1853 * Shared model class for audio and video. Updates the model after
     1854 *   "Add Audio|Video Source" and "Replace Audio|Video" states return
     1855 *
     1856 * @constructor
     1857 * @augments Backbone.Model
     1858 */
     1859var PostMedia = Backbone.Model.extend({
     1860        initialize: function() {
     1861                this.attachment = false;
     1862        },
     1863
     1864        setSource: function( attachment ) {
     1865                this.attachment = attachment;
     1866                this.extension = attachment.get( 'filename' ).split('.').pop();
     1867
     1868                if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
     1869                        this.unset( 'src' );
     1870                }
     1871
     1872                if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
     1873                        this.set( this.extension, this.attachment.get( 'url' ) );
     1874                } else {
     1875                        this.unset( this.extension );
     1876                }
     1877        },
     1878
     1879        changeAttachment: function( attachment ) {
     1880                var self = this;
     1881
     1882                this.setSource( attachment );
     1883
     1884                this.unset( 'src' );
     1885                _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
     1886                        self.unset( ext );
     1887                } );
     1888        }
     1889});
     1890
     1891module.exports = PostMedia;
     1892},{}],12:[function(require,module,exports){
     1893/**
     1894 * wp.media.model.Query
     1895 *
     1896 * A collection of attachments that match the supplied query arguments.
     1897 *
     1898 * Note: Do NOT change this.args after the query has been initialized.
     1899 *       Things will break.
     1900 *
     1901 * @class
     1902 * @augments wp.media.model.Attachments
     1903 * @augments Backbone.Collection
     1904 *
     1905 * @param {array}  [models]                      Models to initialize with the collection.
     1906 * @param {object} [options]                     Options hash.
     1907 * @param {object} [options.args]                Attachments query arguments.
     1908 * @param {object} [options.args.posts_per_page]
     1909 */
     1910var Attachments = require( './attachments.js' ),
     1911        Query;
     1912
     1913Query = Attachments.extend({
     1914        /**
     1915         * @global wp.Uploader
     1916         *
     1917         * @param {array}  [models=[]]  Array of initial models to populate the collection.
     1918         * @param {object} [options={}]
     1919         */
     1920        initialize: function( models, options ) {
     1921                var allowed;
     1922
     1923                options = options || {};
     1924                Attachments.prototype.initialize.apply( this, arguments );
     1925
     1926                this.args     = options.args;
     1927                this._hasMore = true;
     1928                this.created  = new Date();
     1929
     1930                this.filters.order = function( attachment ) {
     1931                        var orderby = this.props.get('orderby'),
     1932                                order = this.props.get('order');
     1933
     1934                        if ( ! this.comparator ) {
     1935                                return true;
     1936                        }
     1937
     1938                        // We want any items that can be placed before the last
     1939                        // item in the set. If we add any items after the last
     1940                        // item, then we can't guarantee the set is complete.
     1941                        if ( this.length ) {
     1942                                return 1 !== this.comparator( attachment, this.last(), { ties: true });
     1943
     1944                        // Handle the case where there are no items yet and
     1945                        // we're sorting for recent items. In that case, we want
     1946                        // changes that occurred after we created the query.
     1947                        } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) {
     1948                                return attachment.get( orderby ) >= this.created;
     1949
     1950                        // If we're sorting by menu order and we have no items,
     1951                        // accept any items that have the default menu order (0).
     1952                        } else if ( 'ASC' === order && 'menuOrder' === orderby ) {
     1953                                return attachment.get( orderby ) === 0;
     1954                        }
     1955
     1956                        // Otherwise, we don't want any items yet.
     1957                        return false;
     1958                };
     1959
     1960                // Observe the central `wp.Uploader.queue` collection to watch for
     1961                // new matches for the query.
     1962                //
     1963                // Only observe when a limited number of query args are set. There
     1964                // are no filters for other properties, so observing will result in
     1965                // false positives in those queries.
     1966                allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ];
     1967                if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) {
     1968                        this.observe( wp.Uploader.queue );
     1969                }
     1970        },
     1971        /**
     1972         * Whether there are more attachments that haven't been sync'd from the server
     1973         * that match the collection's query.
     1974         *
     1975         * @returns {boolean}
     1976         */
     1977        hasMore: function() {
     1978                return this._hasMore;
     1979        },
     1980        /**
     1981         * Fetch more attachments from the server for the collection.
     1982         *
     1983         * @param   {object}  [options={}]
     1984         * @returns {Promise}
     1985         */
     1986        more: function( options ) {
     1987                var query = this;
     1988
     1989                // If there is already a request pending, return early with the Deferred object.
     1990                if ( this._more && 'pending' === this._more.state() ) {
     1991                        return this._more;
     1992                }
     1993
     1994                if ( ! this.hasMore() ) {
     1995                        return jQuery.Deferred().resolveWith( this ).promise();
     1996                }
     1997
     1998                options = options || {};
     1999                options.remove = false;
     2000
     2001                return this._more = this.fetch( options ).done( function( resp ) {
     2002                        if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) {
     2003                                query._hasMore = false;
     2004                        }
     2005                });
     2006        },
     2007        /**
     2008         * Overrides Backbone.Collection.sync
     2009         * Overrides wp.media.model.Attachments.sync
     2010         *
     2011         * @param {String} method
     2012         * @param {Backbone.Model} model
     2013         * @param {Object} [options={}]
     2014         * @returns {Promise}
     2015         */
     2016        sync: function( method, model, options ) {
     2017                var args, fallback;
     2018
     2019                // Overload the read method so Attachment.fetch() functions correctly.
     2020                if ( 'read' === method ) {
     2021                        options = options || {};
     2022                        options.context = this;
     2023                        options.data = _.extend( options.data || {}, {
     2024                                action:  'query-attachments',
     2025                                post_id: wp.media.model.settings.post.id
     2026                        });
     2027
     2028                        // Clone the args so manipulation is non-destructive.
     2029                        args = _.clone( this.args );
     2030
     2031                        // Determine which page to query.
     2032                        if ( -1 !== args.posts_per_page ) {
     2033                                args.paged = Math.floor( this.length / args.posts_per_page ) + 1;
     2034                        }
     2035
     2036                        options.data.query = args;
     2037                        return wp.media.ajax( options );
     2038
     2039                // Otherwise, fall back to Backbone.sync()
     2040                } else {
     2041                        /**
     2042                         * Call wp.media.model.Attachments.sync or Backbone.sync
     2043                         */
     2044                        fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone;
     2045                        return fallback.sync.apply( this, arguments );
     2046                }
     2047        }
     2048}, {
     2049        /**
     2050         * @readonly
     2051         */
     2052        defaultProps: {
     2053                orderby: 'date',
     2054                order:   'DESC'
     2055        },
     2056        /**
     2057         * @readonly
     2058         */
     2059        defaultArgs: {
     2060                posts_per_page: 40
     2061        },
     2062        /**
     2063         * @readonly
     2064         */
     2065        orderby: {
     2066                allowed:  [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ],
     2067                /**
     2068                 * A map of JavaScript orderby values to their WP_Query equivalents.
     2069                 * @type {Object}
     2070                 */
     2071                valuemap: {
     2072                        'id':         'ID',
     2073                        'uploadedTo': 'parent',
     2074                        'menuOrder':  'menu_order ID'
     2075                }
     2076        },
     2077        /**
     2078         * A map of JavaScript query properties to their WP_Query equivalents.
     2079         *
     2080         * @readonly
     2081         */
     2082        propmap: {
     2083                'search':    's',
     2084                'type':      'post_mime_type',
     2085                'perPage':   'posts_per_page',
     2086                'menuOrder': 'menu_order',
     2087                'uploadedTo': 'post_parent',
     2088                'status':     'post_status',
     2089                'include':    'post__in',
     2090                'exclude':    'post__not_in'
     2091        },
     2092        /**
     2093         * Creates and returns an Attachments Query collection given the properties.
     2094         *
     2095         * Caches query objects and reuses where possible.
     2096         *
     2097         * @static
     2098         * @method
     2099         *
     2100         * @param {object} [props]
     2101         * @param {Object} [props.cache=true]   Whether to use the query cache or not.
     2102         * @param {Object} [props.order]
     2103         * @param {Object} [props.orderby]
     2104         * @param {Object} [props.include]
     2105         * @param {Object} [props.exclude]
     2106         * @param {Object} [props.s]
     2107         * @param {Object} [props.post_mime_type]
     2108         * @param {Object} [props.posts_per_page]
     2109         * @param {Object} [props.menu_order]
     2110         * @param {Object} [props.post_parent]
     2111         * @param {Object} [props.post_status]
     2112         * @param {Object} [options]
     2113         *
     2114         * @returns {wp.media.model.Query} A new Attachments Query collection.
     2115         */
     2116        get: (function(){
     2117                /**
     2118                 * @static
     2119                 * @type Array
     2120                 */
     2121                var queries = [];
     2122
     2123                /**
     2124                 * @returns {Query}
     2125                 */
     2126                return function( props, options ) {
     2127                        var args     = {},
     2128                                orderby  = Query.orderby,
     2129                                defaults = Query.defaultProps,
     2130                                query,
     2131                                cache    = !! props.cache || _.isUndefined( props.cache );
     2132
     2133                        // Remove the `query` property. This isn't linked to a query,
     2134                        // this *is* the query.
     2135                        delete props.query;
     2136                        delete props.cache;
     2137
     2138                        // Fill default args.
     2139                        _.defaults( props, defaults );
     2140
     2141                        // Normalize the order.
     2142                        props.order = props.order.toUpperCase();
     2143                        if ( 'DESC' !== props.order && 'ASC' !== props.order ) {
     2144                                props.order = defaults.order.toUpperCase();
     2145                        }
     2146
     2147                        // Ensure we have a valid orderby value.
     2148                        if ( ! _.contains( orderby.allowed, props.orderby ) ) {
     2149                                props.orderby = defaults.orderby;
     2150                        }
     2151
     2152                        _.each( [ 'include', 'exclude' ], function( prop ) {
     2153                                if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) {
     2154                                        props[ prop ] = [ props[ prop ] ];
     2155                                }
     2156                        } );
     2157
     2158                        // Generate the query `args` object.
     2159                        // Correct any differing property names.
     2160                        _.each( props, function( value, prop ) {
     2161                                if ( _.isNull( value ) ) {
     2162                                        return;
     2163                                }
     2164
     2165                                args[ Query.propmap[ prop ] || prop ] = value;
     2166                        });
     2167
     2168                        // Fill any other default query args.
     2169                        _.defaults( args, Query.defaultArgs );
     2170
     2171                        // `props.orderby` does not always map directly to `args.orderby`.
     2172                        // Substitute exceptions specified in orderby.keymap.
     2173                        args.orderby = orderby.valuemap[ props.orderby ] || props.orderby;
     2174
     2175                        // Search the query cache for a matching query.
     2176                        if ( cache ) {
     2177                                query = _.find( queries, function( query ) {
     2178                                        return _.isEqual( query.args, args );
     2179                                });
     2180                        } else {
     2181                                queries = [];
     2182                        }
     2183
     2184                        // Otherwise, create a new query and add it to the cache.
     2185                        if ( ! query ) {
     2186                                query = new Query( [], _.extend( options || {}, {
     2187                                        props: props,
     2188                                        args:  args
     2189                                } ) );
     2190                                queries.push( query );
     2191                        }
     2192
     2193                        return query;
     2194                };
     2195        }())
     2196});
     2197
     2198module.exports = Query;
     2199},{"./attachments.js":10}],13:[function(require,module,exports){
     2200/**
     2201 * wp.media.model.Selection
     2202 *
     2203 * A selection of attachments.
     2204 *
     2205 * @class
     2206 * @augments wp.media.model.Attachments
     2207 * @augments Backbone.Collection
     2208 */
     2209var Attachments = require( './attachments.js' ),
     2210        Selection;
     2211
     2212Selection = Attachments.extend({
     2213        /**
     2214         * Refresh the `single` model whenever the selection changes.
     2215         * Binds `single` instead of using the context argument to ensure
     2216         * it receives no parameters.
     2217         *
     2218         * @param {Array} [models=[]] Array of models used to populate the collection.
     2219         * @param {Object} [options={}]
     2220         */
     2221        initialize: function( models, options ) {
     2222                /**
     2223                 * call 'initialize' directly on the parent class
     2224                 */
     2225                Attachments.prototype.initialize.apply( this, arguments );
     2226                this.multiple = options && options.multiple;
     2227
     2228                this.on( 'add remove reset', _.bind( this.single, this, false ) );
     2229        },
     2230
     2231        /**
     2232         * If the workflow does not support multi-select, clear out the selection
     2233         * before adding a new attachment to it.
     2234         *
     2235         * @param {Array} models
     2236         * @param {Object} options
     2237         * @returns {wp.media.model.Attachment[]}
     2238         */
     2239        add: function( models, options ) {
     2240                if ( ! this.multiple ) {
     2241                        this.remove( this.models );
     2242                }
     2243                /**
     2244                 * call 'add' directly on the parent class
     2245                 */
     2246                return Attachments.prototype.add.call( this, models, options );
     2247        },
     2248
     2249        /**
     2250         * Fired when toggling (clicking on) an attachment in the modal.
     2251         *
     2252         * @param {undefined|boolean|wp.media.model.Attachment} model
     2253         *
     2254         * @fires wp.media.model.Selection#selection:single
     2255         * @fires wp.media.model.Selection#selection:unsingle
     2256         *
     2257         * @returns {Backbone.Model}
     2258         */
     2259        single: function( model ) {
     2260                var previous = this._single;
     2261
     2262                // If a `model` is provided, use it as the single model.
     2263                if ( model ) {
     2264                        this._single = model;
     2265                }
     2266                // If the single model isn't in the selection, remove it.
     2267                if ( this._single && ! this.get( this._single.cid ) ) {
     2268                        delete this._single;
     2269                }
     2270
     2271                this._single = this._single || this.last();
     2272
     2273                // If single has changed, fire an event.
     2274                if ( this._single !== previous ) {
     2275                        if ( previous ) {
     2276                                previous.trigger( 'selection:unsingle', previous, this );
     2277
     2278                                // If the model was already removed, trigger the collection
     2279                                // event manually.
     2280                                if ( ! this.get( previous.cid ) ) {
     2281                                        this.trigger( 'selection:unsingle', previous, this );
     2282                                }
     2283                        }
     2284                        if ( this._single ) {
     2285                                this._single.trigger( 'selection:single', this._single, this );
     2286                        }
     2287                }
     2288
     2289                // Return the single model, or the last model as a fallback.
     2290                return this._single;
     2291        }
     2292});
     2293
     2294module.exports = Selection;
     2295},{"./attachments.js":10}],14:[function(require,module,exports){
     2296/**
     2297 * wp.media.selectionSync
     2298 *
     2299 * Sync an attachments selection in a state with another state.
     2300 *
     2301 * Allows for selecting multiple images in the Insert Media workflow, and then
     2302 * switching to the Insert Gallery workflow while preserving the attachments selection.
     2303 *
     2304 * @mixin
     2305 */
     2306var selectionSync = {
     2307        /**
     2308         * @since 3.5.0
     2309         */
     2310        syncSelection: function() {
     2311                var selection = this.get('selection'),
     2312                        manager = this.frame._selection;
     2313
     2314                if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     2315                        return;
     2316                }
     2317
     2318                // If the selection supports multiple items, validate the stored
     2319                // attachments based on the new selection's conditions. Record
     2320                // the attachments that are not included; we'll maintain a
     2321                // reference to those. Other attachments are considered in flux.
     2322                if ( selection.multiple ) {
     2323                        selection.reset( [], { silent: true });
     2324                        selection.validateAll( manager.attachments );
     2325                        manager.difference = _.difference( manager.attachments.models, selection.models );
     2326                }
     2327
     2328                // Sync the selection's single item with the master.
     2329                selection.single( manager.single );
     2330        },
     2331
     2332        /**
     2333         * Record the currently active attachments, which is a combination
     2334         * of the selection's attachments and the set of selected
     2335         * attachments that this specific selection considered invalid.
     2336         * Reset the difference and record the single attachment.
     2337         *
     2338         * @since 3.5.0
     2339         */
     2340        recordSelection: function() {
     2341                var selection = this.get('selection'),
     2342                        manager = this.frame._selection;
     2343
     2344                if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     2345                        return;
     2346                }
     2347
     2348                if ( selection.multiple ) {
     2349                        manager.attachments.reset( selection.toArray().concat( manager.difference ) );
     2350                        manager.difference = [];
     2351                } else {
     2352                        manager.attachments.add( selection.toArray() );
     2353                }
     2354
     2355                manager.single = selection._single;
     2356        }
     2357};
     2358
     2359module.exports = selectionSync;
     2360},{}],15:[function(require,module,exports){
     2361/**
     2362 * wp.media.view.AttachmentCompat
     2363 *
     2364 * A view to display fields added via the `attachment_fields_to_edit` filter.
     2365 *
     2366 * @class
     2367 * @augments wp.media.View
     2368 * @augments wp.Backbone.View
     2369 * @augments Backbone.View
     2370 */
     2371var View = require( './view.js' ),
     2372        AttachmentCompat;
     2373
     2374AttachmentCompat = View.extend({
     2375        tagName:   'form',
     2376        className: 'compat-item',
     2377
     2378        events: {
     2379                'submit':          'preventDefault',
     2380                'change input':    'save',
     2381                'change select':   'save',
     2382                'change textarea': 'save'
     2383        },
     2384
     2385        initialize: function() {
     2386                this.model.on( 'change:compat', this.render, this );
     2387        },
     2388        /**
     2389         * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     2390         */
     2391        dispose: function() {
     2392                if ( this.$(':focus').length ) {
     2393                        this.save();
     2394                }
     2395                /**
     2396                 * call 'dispose' directly on the parent class
     2397                 */
     2398                return View.prototype.dispose.apply( this, arguments );
     2399        },
     2400        /**
     2401         * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     2402         */
     2403        render: function() {
     2404                var compat = this.model.get('compat');
     2405                if ( ! compat || ! compat.item ) {
     2406                        return;
     2407                }
     2408
     2409                this.views.detach();
     2410                this.$el.html( compat.item );
     2411                this.views.render();
     2412                return this;
     2413        },
     2414        /**
     2415         * @param {Object} event
     2416         */
     2417        preventDefault: function( event ) {
     2418                event.preventDefault();
     2419        },
     2420        /**
     2421         * @param {Object} event
     2422         */
     2423        save: function( event ) {
     2424                var data = {};
     2425
     2426                if ( event ) {
     2427                        event.preventDefault();
     2428                }
     2429
     2430                _.each( this.$el.serializeArray(), function( pair ) {
     2431                        data[ pair.name ] = pair.value;
     2432                });
     2433
     2434                this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
     2435                this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
     2436        },
     2437
     2438        postSave: function() {
     2439                this.controller.trigger( 'attachment:compat:ready', ['ready'] );
     2440        }
     2441});
     2442
     2443module.exports = AttachmentCompat;
     2444},{"./view.js":55}],16:[function(require,module,exports){
     2445/**
     2446 * wp.media.view.AttachmentFilters
     2447 *
     2448 * @class
     2449 * @augments wp.media.View
     2450 * @augments wp.Backbone.View
     2451 * @augments Backbone.View
     2452 */
     2453var View = require( './view.js' ),
     2454        $ = jQuery,
     2455        AttachmentFilters;
     2456
     2457AttachmentFilters = View.extend({
     2458        tagName:   'select',
     2459        className: 'attachment-filters',
     2460        id:        'media-attachment-filters',
     2461
     2462        events: {
     2463                change: 'change'
     2464        },
     2465
     2466        keys: [],
     2467
     2468        initialize: function() {
     2469                this.createFilters();
     2470                _.extend( this.filters, this.options.filters );
     2471
     2472                // Build `<option>` elements.
     2473                this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
     2474                        return {
     2475                                el: $( '<option></option>' ).val( value ).html( filter.text )[0],
     2476                                priority: filter.priority || 50
     2477                        };
     2478                }, this ).sortBy('priority').pluck('el').value() );
     2479
     2480                this.model.on( 'change', this.select, this );
     2481                this.select();
     2482        },
     2483
     2484        /**
     2485         * @abstract
     2486         */
     2487        createFilters: function() {
     2488                this.filters = {};
     2489        },
     2490
     2491        /**
     2492         * When the selected filter changes, update the Attachment Query properties to match.
     2493         */
     2494        change: function() {
     2495                var filter = this.filters[ this.el.value ];
     2496                if ( filter ) {
     2497                        this.model.set( filter.props );
     2498                }
     2499        },
     2500
     2501        select: function() {
     2502                var model = this.model,
     2503                        value = 'all',
     2504                        props = model.toJSON();
     2505
     2506                _.find( this.filters, function( filter, id ) {
     2507                        var equal = _.all( filter.props, function( prop, key ) {
     2508                                return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
     2509                        });
     2510
     2511                        if ( equal ) {
     2512                                return value = id;
     2513                        }
     2514                });
     2515
     2516                this.$el.val( value );
     2517        }
     2518});
     2519
     2520module.exports = AttachmentFilters;
     2521},{"./view.js":55}],17:[function(require,module,exports){
     2522/**
     2523 * wp.media.view.AttachmentFilters.All
     2524 *
     2525 * @class
     2526 * @augments wp.media.view.AttachmentFilters
     2527 * @augments wp.media.View
     2528 * @augments wp.Backbone.View
     2529 * @augments Backbone.View
     2530 */
     2531var AttachmentFilters = require( '../attachment-filters.js' ),
     2532        l10n = wp.media.view.l10n,
     2533        All;
     2534
     2535All = AttachmentFilters.extend({
     2536        createFilters: function() {
     2537                var filters = {};
     2538
     2539                _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
     2540                        filters[ key ] = {
     2541                                text: text,
     2542                                props: {
     2543                                        status:  null,
     2544                                        type:    key,
     2545                                        uploadedTo: null,
     2546                                        orderby: 'date',
     2547                                        order:   'DESC'
     2548                                }
     2549                        };
     2550                });
     2551
     2552                filters.all = {
     2553                        text:  l10n.allMediaItems,
     2554                        props: {
     2555                                status:  null,
     2556                                type:    null,
     2557                                uploadedTo: null,
     2558                                orderby: 'date',
     2559                                order:   'DESC'
     2560                        },
     2561                        priority: 10
     2562                };
     2563
     2564                if ( wp.media.view.settings.post.id ) {
     2565                        filters.uploaded = {
     2566                                text:  l10n.uploadedToThisPost,
     2567                                props: {
     2568                                        status:  null,
     2569                                        type:    null,
     2570                                        uploadedTo: wp.media.view.settings.post.id,
     2571                                        orderby: 'menuOrder',
     2572                                        order:   'ASC'
     2573                                },
     2574                                priority: 20
     2575                        };
     2576                }
     2577
     2578                filters.unattached = {
     2579                        text:  l10n.unattached,
     2580                        props: {
     2581                                status:     null,
     2582                                uploadedTo: 0,
     2583                                type:       null,
     2584                                orderby:    'menuOrder',
     2585                                order:      'ASC'
     2586                        },
     2587                        priority: 50
     2588                };
     2589
     2590                if ( wp.media.view.settings.mediaTrash &&
     2591                        this.controller.isModeActive( 'grid' ) ) {
     2592
     2593                        filters.trash = {
     2594                                text:  l10n.trash,
     2595                                props: {
     2596                                        uploadedTo: null,
     2597                                        status:     'trash',
     2598                                        type:       null,
     2599                                        orderby:    'date',
     2600                                        order:      'DESC'
     2601                                },
     2602                                priority: 50
     2603                        };
     2604                }
     2605
     2606                this.filters = filters;
     2607        }
     2608});
     2609
     2610module.exports = All;
     2611},{"../attachment-filters.js":16}],18:[function(require,module,exports){
     2612/**
     2613 * A filter dropdown for month/dates.
     2614 *
     2615 * @class
     2616 * @augments wp.media.view.AttachmentFilters
     2617 * @augments wp.media.View
     2618 * @augments wp.Backbone.View
     2619 * @augments Backbone.View
     2620 */
     2621var AttachmentFilters = require( '../attachment-filters.js' ),
     2622        l10n = wp.media.view.l10n,
     2623        DateFilter;
     2624
     2625DateFilter = AttachmentFilters.extend({
     2626        id: 'media-attachment-date-filters',
     2627
     2628        createFilters: function() {
     2629                var filters = {};
     2630                _.each( wp.media.view.settings.months || {}, function( value, index ) {
     2631                        filters[ index ] = {
     2632                                text: value.text,
     2633                                props: {
     2634                                        year: value.year,
     2635                                        monthnum: value.month
     2636                                }
     2637                        };
     2638                });
     2639                filters.all = {
     2640                        text:  l10n.allDates,
     2641                        props: {
     2642                                monthnum: false,
     2643                                year:  false
     2644                        },
     2645                        priority: 10
     2646                };
     2647                this.filters = filters;
     2648        }
     2649});
     2650
     2651module.exports = DateFilter;
     2652},{"../attachment-filters.js":16}],19:[function(require,module,exports){
     2653/**
     2654 * wp.media.view.AttachmentFilters.Uploaded
     2655 *
     2656 * @class
     2657 * @augments wp.media.view.AttachmentFilters
     2658 * @augments wp.media.View
     2659 * @augments wp.Backbone.View
     2660 * @augments Backbone.View
     2661 */
     2662var AttachmentFilters = require( '../attachment-filters.js' ),
     2663        l10n = wp.media.view.l10n,
     2664        Uploaded;
     2665
     2666Uploaded = AttachmentFilters.extend({
     2667        createFilters: function() {
     2668                var type = this.model.get('type'),
     2669                        types = wp.media.view.settings.mimeTypes,
     2670                        text;
     2671
     2672                if ( types && type ) {
     2673                        text = types[ type ];
     2674                }
     2675
     2676                this.filters = {
     2677                        all: {
     2678                                text:  text || l10n.allMediaItems,
     2679                                props: {
     2680                                        uploadedTo: null,
     2681                                        orderby: 'date',
     2682                                        order:   'DESC'
     2683                                },
     2684                                priority: 10
     2685                        },
     2686
     2687                        uploaded: {
     2688                                text:  l10n.uploadedToThisPost,
     2689                                props: {
     2690                                        uploadedTo: wp.media.view.settings.post.id,
     2691                                        orderby: 'menuOrder',
     2692                                        order:   'ASC'
     2693                                },
     2694                                priority: 20
     2695                        },
     2696
     2697                        unattached: {
     2698                                text:  l10n.unattached,
     2699                                props: {
     2700                                        uploadedTo: 0,
     2701                                        orderby: 'menuOrder',
     2702                                        order:   'ASC'
     2703                                },
     2704                                priority: 50
     2705                        }
     2706                };
     2707        }
     2708});
     2709
     2710module.exports = Uploaded;
     2711},{"../attachment-filters.js":16}],20:[function(require,module,exports){
     2712/**
     2713 * wp.media.view.Attachment
     2714 *
     2715 * @class
     2716 * @augments wp.media.View
     2717 * @augments wp.Backbone.View
     2718 * @augments Backbone.View
     2719 */
     2720var View = require( './view.js' ),
     2721        $ = jQuery,
     2722        Attachment;
     2723
     2724Attachment = View.extend({
     2725        tagName:   'li',
     2726        className: 'attachment',
     2727        template:  wp.template('attachment'),
     2728
     2729        attributes: function() {
     2730                return {
     2731                        'tabIndex':     0,
     2732                        'role':         'checkbox',
     2733                        'aria-label':   this.model.get( 'title' ),
     2734                        'aria-checked': false,
     2735                        'data-id':      this.model.get( 'id' )
     2736                };
     2737        },
     2738
     2739        events: {
     2740                'click .js--select-attachment':   'toggleSelectionHandler',
     2741                'change [data-setting]':          'updateSetting',
     2742                'change [data-setting] input':    'updateSetting',
     2743                'change [data-setting] select':   'updateSetting',
     2744                'change [data-setting] textarea': 'updateSetting',
     2745                'click .close':                   'removeFromLibrary',
     2746                'click .check':                   'checkClickHandler',
     2747                'click a':                        'preventDefault',
     2748                'keydown .close':                 'removeFromLibrary',
     2749                'keydown':                        'toggleSelectionHandler'
     2750        },
     2751
     2752        buttons: {},
     2753
     2754        initialize: function() {
     2755                var selection = this.options.selection,
     2756                        options = _.defaults( this.options, {
     2757                                rerenderOnModelChange: true
     2758                        } );
     2759
     2760                if ( options.rerenderOnModelChange ) {
     2761                        this.model.on( 'change', this.render, this );
     2762                } else {
     2763                        this.model.on( 'change:percent', this.progress, this );
     2764                }
     2765                this.model.on( 'change:title', this._syncTitle, this );
     2766                this.model.on( 'change:caption', this._syncCaption, this );
     2767                this.model.on( 'change:artist', this._syncArtist, this );
     2768                this.model.on( 'change:album', this._syncAlbum, this );
     2769
     2770                // Update the selection.
     2771                this.model.on( 'add', this.select, this );
     2772                this.model.on( 'remove', this.deselect, this );
     2773                if ( selection ) {
     2774                        selection.on( 'reset', this.updateSelect, this );
     2775                        // Update the model's details view.
     2776                        this.model.on( 'selection:single selection:unsingle', this.details, this );
     2777                        this.details( this.model, this.controller.state().get('selection') );
     2778                }
     2779
     2780                this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
     2781        },
     2782        /**
     2783         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     2784         */
     2785        dispose: function() {
     2786                var selection = this.options.selection;
     2787
     2788                // Make sure all settings are saved before removing the view.
     2789                this.updateAll();
     2790
     2791                if ( selection ) {
     2792                        selection.off( null, null, this );
     2793                }
     2794                /**
     2795                 * call 'dispose' directly on the parent class
     2796                 */
     2797                View.prototype.dispose.apply( this, arguments );
     2798                return this;
     2799        },
     2800        /**
     2801         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     2802         */
     2803        render: function() {
     2804                var options = _.defaults( this.model.toJSON(), {
     2805                                orientation:   'landscape',
     2806                                uploading:     false,
     2807                                type:          '',
     2808                                subtype:       '',
     2809                                icon:          '',
     2810                                filename:      '',
     2811                                caption:       '',
     2812                                title:         '',
     2813                                dateFormatted: '',
     2814                                width:         '',
     2815                                height:        '',
     2816                                compat:        false,
     2817                                alt:           '',
     2818                                description:   ''
     2819                        }, this.options );
     2820
     2821                options.buttons  = this.buttons;
     2822                options.describe = this.controller.state().get('describe');
     2823
     2824                if ( 'image' === options.type ) {
     2825                        options.size = this.imageSize();
     2826                }
     2827
     2828                options.can = {};
     2829                if ( options.nonces ) {
     2830                        options.can.remove = !! options.nonces['delete'];
     2831                        options.can.save = !! options.nonces.update;
     2832                }
     2833
     2834                if ( this.controller.state().get('allowLocalEdits') ) {
     2835                        options.allowLocalEdits = true;
     2836                }
     2837
     2838                if ( options.uploading && ! options.percent ) {
     2839                        options.percent = 0;
     2840                }
     2841
     2842                this.views.detach();
     2843                this.$el.html( this.template( options ) );
     2844
     2845                this.$el.toggleClass( 'uploading', options.uploading );
     2846
     2847                if ( options.uploading ) {
     2848                        this.$bar = this.$('.media-progress-bar div');
     2849                } else {
     2850                        delete this.$bar;
     2851                }
     2852
     2853                // Check if the model is selected.
     2854                this.updateSelect();
     2855
     2856                // Update the save status.
     2857                this.updateSave();
     2858
     2859                this.views.render();
     2860
     2861                return this;
     2862        },
     2863
     2864        progress: function() {
     2865                if ( this.$bar && this.$bar.length ) {
     2866                        this.$bar.width( this.model.get('percent') + '%' );
     2867                }
     2868        },
     2869
     2870        /**
     2871         * @param {Object} event
     2872         */
     2873        toggleSelectionHandler: function( event ) {
     2874                var method;
     2875
     2876                // Don't do anything inside inputs.
     2877                if ( 'INPUT' === event.target.nodeName ) {
     2878                        return;
     2879                }
     2880
     2881                // Catch arrow events
     2882                if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     2883                        this.controller.trigger( 'attachment:keydown:arrow', event );
     2884                        return;
     2885                }
     2886
     2887                // Catch enter and space events
     2888                if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     2889                        return;
     2890                }
     2891
     2892                event.preventDefault();
     2893
     2894                // In the grid view, bubble up an edit:attachment event to the controller.
     2895                if ( this.controller.isModeActive( 'grid' ) ) {
     2896                        if ( this.controller.isModeActive( 'edit' ) ) {
     2897                                // Pass the current target to restore focus when closing
     2898                                this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
     2899                                return;
     2900                        }
     2901
     2902                        if ( this.controller.isModeActive( 'select' ) ) {
     2903                                method = 'toggle';
     2904                        }
     2905                }
     2906
     2907                if ( event.shiftKey ) {
     2908                        method = 'between';
     2909                } else if ( event.ctrlKey || event.metaKey ) {
     2910                        method = 'toggle';
     2911                }
     2912
     2913                this.toggleSelection({
     2914                        method: method
     2915                });
     2916
     2917                this.controller.trigger( 'selection:toggle' );
     2918        },
     2919        /**
     2920         * @param {Object} options
     2921         */
     2922        toggleSelection: function( options ) {
     2923                var collection = this.collection,
     2924                        selection = this.options.selection,
     2925                        model = this.model,
     2926                        method = options && options.method,
     2927                        single, models, singleIndex, modelIndex;
     2928
     2929                if ( ! selection ) {
     2930                        return;
     2931                }
     2932
     2933                single = selection.single();
     2934                method = _.isUndefined( method ) ? selection.multiple : method;
     2935
     2936                // If the `method` is set to `between`, select all models that
     2937                // exist between the current and the selected model.
     2938                if ( 'between' === method && single && selection.multiple ) {
     2939                        // If the models are the same, short-circuit.
     2940                        if ( single === model ) {
     2941                                return;
     2942                        }
     2943
     2944                        singleIndex = collection.indexOf( single );
     2945                        modelIndex  = collection.indexOf( this.model );
     2946
     2947                        if ( singleIndex < modelIndex ) {
     2948                                models = collection.models.slice( singleIndex, modelIndex + 1 );
     2949                        } else {
     2950                                models = collection.models.slice( modelIndex, singleIndex + 1 );
     2951                        }
     2952
     2953                        selection.add( models );
     2954                        selection.single( model );
     2955                        return;
     2956
     2957                // If the `method` is set to `toggle`, just flip the selection
     2958                // status, regardless of whether the model is the single model.
     2959                } else if ( 'toggle' === method ) {
     2960                        selection[ this.selected() ? 'remove' : 'add' ]( model );
     2961                        selection.single( model );
     2962                        return;
     2963                } else if ( 'add' === method ) {
     2964                        selection.add( model );
     2965                        selection.single( model );
     2966                        return;
     2967                }
     2968
     2969                // Fixes bug that loses focus when selecting a featured image
     2970                if ( ! method ) {
     2971                        method = 'add';
     2972                }
     2973
     2974                if ( method !== 'add' ) {
     2975                        method = 'reset';
     2976                }
     2977
     2978                if ( this.selected() ) {
     2979                        // If the model is the single model, remove it.
     2980                        // If it is not the same as the single model,
     2981                        // it now becomes the single model.
     2982                        selection[ single === model ? 'remove' : 'single' ]( model );
     2983                } else {
     2984                        // If the model is not selected, run the `method` on the
     2985                        // selection. By default, we `reset` the selection, but the
     2986                        // `method` can be set to `add` the model to the selection.
     2987                        selection[ method ]( model );
     2988                        selection.single( model );
     2989                }
     2990        },
     2991
     2992        updateSelect: function() {
     2993                this[ this.selected() ? 'select' : 'deselect' ]();
     2994        },
     2995        /**
     2996         * @returns {unresolved|Boolean}
     2997         */
     2998        selected: function() {
     2999                var selection = this.options.selection;
     3000                if ( selection ) {
     3001                        return !! selection.get( this.model.cid );
     3002                }
     3003        },
     3004        /**
     3005         * @param {Backbone.Model} model
     3006         * @param {Backbone.Collection} collection
     3007         */
     3008        select: function( model, collection ) {
     3009                var selection = this.options.selection,
     3010                        controller = this.controller;
     3011
     3012                // Check if a selection exists and if it's the collection provided.
     3013                // If they're not the same collection, bail; we're in another
     3014                // selection's event loop.
     3015                if ( ! selection || ( collection && collection !== selection ) ) {
     3016                        return;
     3017                }
     3018
     3019                // Bail if the model is already selected.
     3020                if ( this.$el.hasClass( 'selected' ) ) {
     3021                        return;
     3022                }
     3023
     3024                // Add 'selected' class to model, set aria-checked to true.
     3025                this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
     3026                //  Make the checkbox tabable, except in media grid (bulk select mode).
     3027                if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
     3028                        this.$( '.check' ).attr( 'tabindex', '0' );
     3029                }
     3030        },
     3031        /**
     3032         * @param {Backbone.Model} model
     3033         * @param {Backbone.Collection} collection
     3034         */
     3035        deselect: function( model, collection ) {
     3036                var selection = this.options.selection;
     3037
     3038                // Check if a selection exists and if it's the collection provided.
     3039                // If they're not the same collection, bail; we're in another
     3040                // selection's event loop.
     3041                if ( ! selection || ( collection && collection !== selection ) ) {
     3042                        return;
     3043                }
     3044                this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
     3045                        .find( '.check' ).attr( 'tabindex', '-1' );
     3046        },
     3047        /**
     3048         * @param {Backbone.Model} model
     3049         * @param {Backbone.Collection} collection
     3050         */
     3051        details: function( model, collection ) {
     3052                var selection = this.options.selection,
     3053                        details;
     3054
     3055                if ( selection !== collection ) {
     3056                        return;
     3057                }
     3058
     3059                details = selection.single();
     3060                this.$el.toggleClass( 'details', details === this.model );
     3061        },
     3062        /**
     3063         * @param {Object} event
     3064         */
     3065        preventDefault: function( event ) {
     3066                event.preventDefault();
     3067        },
     3068        /**
     3069         * @param {string} size
     3070         * @returns {Object}
     3071         */
     3072        imageSize: function( size ) {
     3073                var sizes = this.model.get('sizes');
     3074
     3075                size = size || 'medium';
     3076
     3077                // Use the provided image size if possible.
     3078                if ( sizes && sizes[ size ] ) {
     3079                        return _.clone( sizes[ size ] );
     3080                } else {
     3081                        return {
     3082                                url:         this.model.get('url'),
     3083                                width:       this.model.get('width'),
     3084                                height:      this.model.get('height'),
     3085                                orientation: this.model.get('orientation')
     3086                        };
     3087                }
     3088        },
     3089        /**
     3090         * @param {Object} event
     3091         */
     3092        updateSetting: function( event ) {
     3093                var $setting = $( event.target ).closest('[data-setting]'),
     3094                        setting, value;
     3095
     3096                if ( ! $setting.length ) {
     3097                        return;
     3098                }
     3099
     3100                setting = $setting.data('setting');
     3101                value   = event.target.value;
     3102
     3103                if ( this.model.get( setting ) !== value ) {
     3104                        this.save( setting, value );
     3105                }
     3106        },
     3107
     3108        /**
     3109         * Pass all the arguments to the model's save method.
     3110         *
     3111         * Records the aggregate status of all save requests and updates the
     3112         * view's classes accordingly.
     3113         */
     3114        save: function() {
     3115                var view = this,
     3116                        save = this._save = this._save || { status: 'ready' },
     3117                        request = this.model.save.apply( this.model, arguments ),
     3118                        requests = save.requests ? $.when( request, save.requests ) : request;
     3119
     3120                // If we're waiting to remove 'Saved.', stop.
     3121                if ( save.savedTimer ) {
     3122                        clearTimeout( save.savedTimer );
     3123                }
     3124
     3125                this.updateSave('waiting');
     3126                save.requests = requests;
     3127                requests.always( function() {
     3128                        // If we've performed another request since this one, bail.
     3129                        if ( save.requests !== requests ) {
     3130                                return;
     3131                        }
     3132
     3133                        view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
     3134                        save.savedTimer = setTimeout( function() {
     3135                                view.updateSave('ready');
     3136                                delete save.savedTimer;
     3137                        }, 2000 );
     3138                });
     3139        },
     3140        /**
     3141         * @param {string} status
     3142         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     3143         */
     3144        updateSave: function( status ) {
     3145                var save = this._save = this._save || { status: 'ready' };
     3146
     3147                if ( status && status !== save.status ) {
     3148                        this.$el.removeClass( 'save-' + save.status );
     3149                        save.status = status;
     3150                }
     3151
     3152                this.$el.addClass( 'save-' + save.status );
     3153                return this;
     3154        },
     3155
     3156        updateAll: function() {
     3157                var $settings = this.$('[data-setting]'),
     3158                        model = this.model,
     3159                        changed;
     3160
     3161                changed = _.chain( $settings ).map( function( el ) {
     3162                        var $input = $('input, textarea, select, [value]', el ),
     3163                                setting, value;
     3164
     3165                        if ( ! $input.length ) {
     3166                                return;
     3167                        }
     3168
     3169                        setting = $(el).data('setting');
     3170                        value = $input.val();
     3171
     3172                        // Record the value if it changed.
     3173                        if ( model.get( setting ) !== value ) {
     3174                                return [ setting, value ];
     3175                        }
     3176                }).compact().object().value();
     3177
     3178                if ( ! _.isEmpty( changed ) ) {
     3179                        model.save( changed );
     3180                }
     3181        },
     3182        /**
     3183         * @param {Object} event
     3184         */
     3185        removeFromLibrary: function( event ) {
     3186                // Catch enter and space events
     3187                if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     3188                        return;
     3189                }
     3190
     3191                // Stop propagation so the model isn't selected.
     3192                event.stopPropagation();
     3193
     3194                this.collection.remove( this.model );
     3195        },
     3196
     3197        /**
     3198         * Add the model if it isn't in the selection, if it is in the selection,
     3199         * remove it.
     3200         *
     3201         * @param  {[type]} event [description]
     3202         * @return {[type]}       [description]
     3203         */
     3204        checkClickHandler: function ( event ) {
     3205                var selection = this.options.selection;
     3206                if ( ! selection ) {
     3207                        return;
     3208                }
     3209                event.stopPropagation();
     3210                if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
     3211                        selection.remove( this.model );
     3212                        // Move focus back to the attachment tile (from the check).
     3213                        this.$el.focus();
     3214                } else {
     3215                        selection.add( this.model );
     3216                }
     3217        }
     3218});
     3219
     3220// Ensure settings remain in sync between attachment views.
     3221_.each({
     3222        caption: '_syncCaption',
     3223        title:   '_syncTitle',
     3224        artist:  '_syncArtist',
     3225        album:   '_syncAlbum'
     3226}, function( method, setting ) {
     3227        /**
     3228         * @param {Backbone.Model} model
     3229         * @param {string} value
     3230         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     3231         */
     3232        Attachment.prototype[ method ] = function( model, value ) {
     3233                var $setting = this.$('[data-setting="' + setting + '"]');
     3234
     3235                if ( ! $setting.length ) {
     3236                        return this;
     3237                }
     3238
     3239                // If the updated value is in sync with the value in the DOM, there
     3240                // is no need to re-render. If we're currently editing the value,
     3241                // it will automatically be in sync, suppressing the re-render for
     3242                // the view we're editing, while updating any others.
     3243                if ( value === $setting.find('input, textarea, select, [value]').val() ) {
     3244                        return this;
     3245                }
     3246
     3247                return this.render();
     3248        };
     3249});
     3250
     3251module.exports = Attachment;
     3252},{"./view.js":55}],21:[function(require,module,exports){
     3253/**
     3254 * wp.media.view.Attachment.Details
     3255 *
     3256 * @class
     3257 * @augments wp.media.view.Attachment
     3258 * @augments wp.media.View
     3259 * @augments wp.Backbone.View
     3260 * @augments Backbone.View
     3261 */
     3262var Attachment = require( '../attachment.js' ),
     3263        l10n = wp.media.view.l10n,
     3264        Details;
     3265
     3266Details = Attachment.extend({
     3267        tagName:   'div',
     3268        className: 'attachment-details',
     3269        template:  wp.template('attachment-details'),
     3270
     3271        attributes: function() {
     3272                return {
     3273                        'tabIndex':     0,
     3274                        'data-id':      this.model.get( 'id' )
     3275                };
     3276        },
     3277
     3278        events: {
     3279                'change [data-setting]':          'updateSetting',
     3280                'change [data-setting] input':    'updateSetting',
     3281                'change [data-setting] select':   'updateSetting',
     3282                'change [data-setting] textarea': 'updateSetting',
     3283                'click .delete-attachment':       'deleteAttachment',
     3284                'click .trash-attachment':        'trashAttachment',
     3285                'click .untrash-attachment':      'untrashAttachment',
     3286                'click .edit-attachment':         'editAttachment',
     3287                'click .refresh-attachment':      'refreshAttachment',
     3288                'keydown':                        'toggleSelectionHandler'
     3289        },
     3290
     3291        initialize: function() {
     3292                this.options = _.defaults( this.options, {
     3293                        rerenderOnModelChange: false
     3294                });
     3295
     3296                this.on( 'ready', this.initialFocus );
     3297                // Call 'initialize' directly on the parent class.
     3298                Attachment.prototype.initialize.apply( this, arguments );
     3299        },
     3300
     3301        initialFocus: function() {
     3302                if ( ! wp.media.isTouchDevice ) {
     3303                        this.$( ':input' ).eq( 0 ).focus();
     3304                }
     3305        },
     3306        /**
     3307         * @param {Object} event
     3308         */
     3309        deleteAttachment: function( event ) {
     3310                event.preventDefault();
     3311
     3312                if ( confirm( l10n.warnDelete ) ) {
     3313                        this.model.destroy();
     3314                        // Keep focus inside media modal
     3315                        // after image is deleted
     3316                        this.controller.modal.focusManager.focus();
     3317                }
     3318        },
     3319        /**
     3320         * @param {Object} event
     3321         */
     3322        trashAttachment: function( event ) {
     3323                var library = this.controller.library;
     3324                event.preventDefault();
     3325
     3326                if ( wp.media.view.settings.mediaTrash &&
     3327                        'edit-metadata' === this.controller.content.mode() ) {
     3328
     3329                        this.model.set( 'status', 'trash' );
     3330                        this.model.save().done( function() {
     3331                                library._requery( true );
     3332                        } );
     3333                }  else {
     3334                        this.model.destroy();
     3335                }
     3336        },
     3337        /**
     3338         * @param {Object} event
     3339         */
     3340        untrashAttachment: function( event ) {
     3341                var library = this.controller.library;
     3342                event.preventDefault();
     3343
     3344                this.model.set( 'status', 'inherit' );
     3345                this.model.save().done( function() {
     3346                        library._requery( true );
     3347                } );
     3348        },
     3349        /**
     3350         * @param {Object} event
     3351         */
     3352        editAttachment: function( event ) {
     3353                var editState = this.controller.states.get( 'edit-image' );
     3354                if ( window.imageEdit && editState ) {
     3355                        event.preventDefault();
     3356
     3357                        editState.set( 'image', this.model );
     3358                        this.controller.setState( 'edit-image' );
     3359                } else {
     3360                        this.$el.addClass('needs-refresh');
     3361                }
     3362        },
     3363        /**
     3364         * @param {Object} event
     3365         */
     3366        refreshAttachment: function( event ) {
     3367                this.$el.removeClass('needs-refresh');
     3368                event.preventDefault();
     3369                this.model.fetch();
     3370        },
     3371        /**
     3372         * When reverse tabbing(shift+tab) out of the right details panel, deliver
     3373         * the focus to the item in the list that was being edited.
     3374         *
     3375         * @param {Object} event
     3376         */
     3377        toggleSelectionHandler: function( event ) {
     3378                if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
     3379                        this.controller.trigger( 'attachment:details:shift-tab', event );
     3380                        return false;
     3381                }
     3382
     3383                if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     3384                        this.controller.trigger( 'attachment:keydown:arrow', event );
     3385                        return;
     3386                }
     3387        }
     3388});
     3389
     3390module.exports = Details;
     3391},{"../attachment.js":20}],22:[function(require,module,exports){
     3392/**
     3393 * wp.media.view.Attachment.Library
     3394 *
     3395 * @class
     3396 * @augments wp.media.view.Attachment
     3397 * @augments wp.media.View
     3398 * @augments wp.Backbone.View
     3399 * @augments Backbone.View
     3400 */
     3401var Attachment = require( '../attachment.js' ),
     3402        Library;
     3403
     3404Library = Attachment.extend({
     3405        buttons: {
     3406                check: true
     3407        }
     3408});
     3409
     3410module.exports = Library;
     3411},{"../attachment.js":20}],23:[function(require,module,exports){
     3412/**
     3413 * wp.media.view.Attachments
     3414 *
     3415 * @class
     3416 * @augments wp.media.View
     3417 * @augments wp.Backbone.View
     3418 * @augments Backbone.View
     3419 */
     3420var View = require( './view.js' ),
     3421        Attachment = require( './attachment.js' ),
     3422        $ = jQuery,
     3423        Attachments;
     3424
     3425Attachments = View.extend({
     3426        tagName:   'ul',
     3427        className: 'attachments',
     3428
     3429        attributes: {
     3430                tabIndex: -1
     3431        },
     3432
     3433        initialize: function() {
     3434                this.el.id = _.uniqueId('__attachments-view-');
     3435
     3436                _.defaults( this.options, {
     3437                        refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
     3438                        refreshThreshold:   3,
     3439                        AttachmentView:     Attachment,
     3440                        sortable:           false,
     3441                        resize:             true,
     3442                        idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
     3443                });
     3444
     3445                this._viewsByCid = {};
     3446                this.$window = $( window );
     3447                this.resizeEvent = 'resize.media-modal-columns';
     3448
     3449                this.collection.on( 'add', function( attachment ) {
     3450                        this.views.add( this.createAttachmentView( attachment ), {
     3451                                at: this.collection.indexOf( attachment )
     3452                        });
     3453                }, this );
     3454
     3455                this.collection.on( 'remove', function( attachment ) {
     3456                        var view = this._viewsByCid[ attachment.cid ];
     3457                        delete this._viewsByCid[ attachment.cid ];
     3458
     3459                        if ( view ) {
     3460                                view.remove();
     3461                        }
     3462                }, this );
     3463
     3464                this.collection.on( 'reset', this.render, this );
     3465
     3466                this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
     3467
     3468                // Throttle the scroll handler and bind this.
     3469                this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
     3470
     3471                this.options.scrollElement = this.options.scrollElement || this.el;
     3472                $( this.options.scrollElement ).on( 'scroll', this.scroll );
     3473
     3474                this.initSortable();
     3475
     3476                _.bindAll( this, 'setColumns' );
     3477
     3478                if ( this.options.resize ) {
     3479                        this.on( 'ready', this.bindEvents );
     3480                        this.controller.on( 'open', this.setColumns );
     3481
     3482                        // Call this.setColumns() after this view has been rendered in the DOM so
     3483                        // attachments get proper width applied.
     3484                        _.defer( this.setColumns, this );
     3485                }
     3486        },
     3487
     3488        bindEvents: function() {
     3489                this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
     3490        },
     3491
     3492        attachmentFocus: function() {
     3493                this.$( 'li:first' ).focus();
     3494        },
     3495
     3496        restoreFocus: function() {
     3497                this.$( 'li.selected:first' ).focus();
     3498        },
     3499
     3500        arrowEvent: function( event ) {
     3501                var attachments = this.$el.children( 'li' ),
     3502                        perRow = this.columns,
     3503                        index = attachments.filter( ':focus' ).index(),
     3504                        row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
     3505
     3506                if ( index === -1 ) {
     3507                        return;
     3508                }
     3509
     3510                // Left arrow
     3511                if ( 37 === event.keyCode ) {
     3512                        if ( 0 === index ) {
     3513                                return;
     3514                        }
     3515                        attachments.eq( index - 1 ).focus();
     3516                }
     3517
     3518                // Up arrow
     3519                if ( 38 === event.keyCode ) {
     3520                        if ( 1 === row ) {
     3521                                return;
     3522                        }
     3523                        attachments.eq( index - perRow ).focus();
     3524                }
     3525
     3526                // Right arrow
     3527                if ( 39 === event.keyCode ) {
     3528                        if ( attachments.length === index ) {
     3529                                return;
     3530                        }
     3531                        attachments.eq( index + 1 ).focus();
     3532                }
     3533
     3534                // Down arrow
     3535                if ( 40 === event.keyCode ) {
     3536                        if ( Math.ceil( attachments.length / perRow ) === row ) {
     3537                                return;
     3538                        }
     3539                        attachments.eq( index + perRow ).focus();
     3540                }
     3541        },
     3542
     3543        dispose: function() {
     3544                this.collection.props.off( null, null, this );
     3545                if ( this.options.resize ) {
     3546                        this.$window.off( this.resizeEvent );
     3547                }
     3548
     3549                /**
     3550                 * call 'dispose' directly on the parent class
     3551                 */
     3552                View.prototype.dispose.apply( this, arguments );
     3553        },
     3554
     3555        setColumns: function() {
     3556                var prev = this.columns,
     3557                        width = this.$el.width();
     3558
     3559                if ( width ) {
     3560                        this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
     3561
     3562                        if ( ! prev || prev !== this.columns ) {
     3563                                this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
     3564                        }
     3565                }
     3566        },
     3567
     3568        initSortable: function() {
     3569                var collection = this.collection;
     3570
     3571                if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
     3572                        return;
     3573                }
     3574
     3575                this.$el.sortable( _.extend({
     3576                        // If the `collection` has a `comparator`, disable sorting.
     3577                        disabled: !! collection.comparator,
     3578
     3579                        // Change the position of the attachment as soon as the
     3580                        // mouse pointer overlaps a thumbnail.
     3581                        tolerance: 'pointer',
     3582
     3583                        // Record the initial `index` of the dragged model.
     3584                        start: function( event, ui ) {
     3585                                ui.item.data('sortableIndexStart', ui.item.index());
     3586                        },
     3587
     3588                        // Update the model's index in the collection.
     3589                        // Do so silently, as the view is already accurate.
     3590                        update: function( event, ui ) {
     3591                                var model = collection.at( ui.item.data('sortableIndexStart') ),
     3592                                        comparator = collection.comparator;
     3593
     3594                                // Temporarily disable the comparator to prevent `add`
     3595                                // from re-sorting.
     3596                                delete collection.comparator;
     3597
     3598                                // Silently shift the model to its new index.
     3599                                collection.remove( model, {
     3600                                        silent: true
     3601                                });
     3602                                collection.add( model, {
     3603                                        silent: true,
     3604                                        at:     ui.item.index()
     3605                                });
     3606
     3607                                // Restore the comparator.
     3608                                collection.comparator = comparator;
     3609
     3610                                // Fire the `reset` event to ensure other collections sync.
     3611                                collection.trigger( 'reset', collection );
     3612
     3613                                // If the collection is sorted by menu order,
     3614                                // update the menu order.
     3615                                collection.saveMenuOrder();
     3616                        }
     3617                }, this.options.sortable ) );
     3618
     3619                // If the `orderby` property is changed on the `collection`,
     3620                // check to see if we have a `comparator`. If so, disable sorting.
     3621                collection.props.on( 'change:orderby', function() {
     3622                        this.$el.sortable( 'option', 'disabled', !! collection.comparator );
     3623                }, this );
     3624
     3625                this.collection.props.on( 'change:orderby', this.refreshSortable, this );
     3626                this.refreshSortable();
     3627        },
     3628
     3629        refreshSortable: function() {
     3630                if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
     3631                        return;
     3632                }
     3633
     3634                // If the `collection` has a `comparator`, disable sorting.
     3635                var collection = this.collection,
     3636                        orderby = collection.props.get('orderby'),
     3637                        enabled = 'menuOrder' === orderby || ! collection.comparator;
     3638
     3639                this.$el.sortable( 'option', 'disabled', ! enabled );
     3640        },
     3641
     3642        /**
     3643         * @param {wp.media.model.Attachment} attachment
     3644         * @returns {wp.media.View}
     3645         */
     3646        createAttachmentView: function( attachment ) {
     3647                var view = new this.options.AttachmentView({
     3648                        controller:           this.controller,
     3649                        model:                attachment,
     3650                        collection:           this.collection,
     3651                        selection:            this.options.selection
     3652                });
     3653
     3654                return this._viewsByCid[ attachment.cid ] = view;
     3655        },
     3656
     3657        prepare: function() {
     3658                // Create all of the Attachment views, and replace
     3659                // the list in a single DOM operation.
     3660                if ( this.collection.length ) {
     3661                        this.views.set( this.collection.map( this.createAttachmentView, this ) );
     3662
     3663                // If there are no elements, clear the views and load some.
     3664                } else {
     3665                        this.views.unset();
     3666                        this.collection.more().done( this.scroll );
     3667                }
     3668        },
     3669
     3670        ready: function() {
     3671                // Trigger the scroll event to check if we're within the
     3672                // threshold to query for additional attachments.
     3673                this.scroll();
     3674        },
     3675
     3676        scroll: function() {
     3677                var view = this,
     3678                        el = this.options.scrollElement,
     3679                        scrollTop = el.scrollTop,
     3680                        toolbar;
     3681
     3682                // The scroll event occurs on the document, but the element
     3683                // that should be checked is the document body.
     3684                if ( el == document ) {
     3685                        el = document.body;
     3686                        scrollTop = $(document).scrollTop();
     3687                }
     3688
     3689                if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
     3690                        return;
     3691                }
     3692
     3693                toolbar = this.views.parent.toolbar;
     3694
     3695                // Show the spinner only if we are close to the bottom.
     3696                if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
     3697                        toolbar.get('spinner').show();
     3698                }
     3699
     3700                if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
     3701                        this.collection.more().done(function() {
     3702                                view.scroll();
     3703                                toolbar.get('spinner').hide();
     3704                        });
     3705                }
     3706        }
     3707});
     3708
     3709module.exports = Attachments;
     3710},{"./attachment.js":20,"./view.js":55}],24:[function(require,module,exports){
     3711/**
     3712 * wp.media.view.AttachmentsBrowser
     3713 *
     3714 * @class
     3715 * @augments wp.media.View
     3716 * @augments wp.Backbone.View
     3717 * @augments Backbone.View
     3718 *
     3719 * @param {object}      options
     3720 * @param {object}      [options.filters=false] Which filters to show in the browser's toolbar.
     3721 *                                              Accepts 'uploaded' and 'all'.
     3722 * @param {object}      [options.search=true]   Whether to show the search interface in the
     3723 *                                              browser's toolbar.
     3724 * @param {object}      [options.display=false] Whether to show the attachments display settings
     3725 *                                              view in the sidebar.
     3726 * @param {bool|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
     3727 *                                              Accepts true, false, and 'errors'.
     3728 */
     3729var View = require( '../view.js' ),
     3730        Library = require( '../attachment/library.js' ),
     3731        Toolbar = require( '../toolbar.js' ),
     3732        Spinner = require( '../spinner.js' ),
     3733        Search = require( '../search.js' ),
     3734        Label = require( '../label.js' ),
     3735        Uploaded = require( '../attachment-filters/uploaded.js' ),
     3736        All = require( '../attachment-filters/all.js' ),
     3737        DateFilter = require( '../attachment-filters/date.js' ),
     3738        UploaderInline = require( '../uploader/inline.js' ),
     3739        Attachments = require( '../attachments.js' ),
     3740        Sidebar = require( '../sidebar.js' ),
     3741        UploaderStatus = require( '../uploader/status.js' ),
     3742        Details = require( '../attachment/details.js' ),
     3743        AttachmentCompat = require( '../attachment-compat.js' ),
     3744        AttachmentDisplay = require( '../settings/attachment-display.js' ),
     3745        mediaTrash = wp.media.view.settings.mediaTrash,
     3746        l10n = wp.media.view.l10n,
     3747        $ = jQuery,
     3748        AttachmentsBrowser;
     3749
     3750AttachmentsBrowser = View.extend({
     3751        tagName:   'div',
     3752        className: 'attachments-browser',
     3753
     3754        initialize: function() {
     3755                _.defaults( this.options, {
     3756                        filters: false,
     3757                        search:  true,
     3758                        display: false,
     3759                        sidebar: true,
     3760                        AttachmentView: Library
     3761                });
     3762
     3763                this.listenTo( this.controller, 'toggle:upload:attachment', _.bind( this.toggleUploader, this ) );
     3764                this.controller.on( 'edit:selection', this.editSelection );
     3765                this.createToolbar();
     3766                if ( this.options.sidebar ) {
     3767                        this.createSidebar();
     3768                }
     3769                this.createUploader();
     3770                this.createAttachments();
     3771                this.updateContent();
     3772
     3773                if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
     3774                        this.$el.addClass( 'hide-sidebar' );
     3775
     3776                        if ( 'errors' === this.options.sidebar ) {
     3777                                this.$el.addClass( 'sidebar-for-errors' );
     3778                        }
     3779                }
     3780
     3781                this.collection.on( 'add remove reset', this.updateContent, this );
     3782        },
     3783
     3784        editSelection: function( modal ) {
     3785                modal.$( '.media-button-backToLibrary' ).focus();
     3786        },
     3787
     3788        /**
     3789         * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
     3790         */
     3791        dispose: function() {
     3792                this.options.selection.off( null, null, this );
     3793                View.prototype.dispose.apply( this, arguments );
     3794                return this;
     3795        },
     3796
     3797        createToolbar: function() {
     3798                var LibraryViewSwitcher, Filters, toolbarOptions;
     3799
     3800                toolbarOptions = {
     3801                        controller: this.controller
     3802                };
     3803
     3804                if ( this.controller.isModeActive( 'grid' ) ) {
     3805                        toolbarOptions.className = 'media-toolbar wp-filter';
     3806                }
     3807
     3808                /**
     3809                * @member {wp.media.view.Toolbar}
     3810                */
     3811                this.toolbar = new Toolbar( toolbarOptions );
     3812
     3813                this.views.add( this.toolbar );
     3814
     3815                this.toolbar.set( 'spinner', new Spinner({
     3816                        priority: -60
     3817                }) );
     3818
     3819                if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
     3820                        // "Filters" will return a <select>, need to render
     3821                        // screen reader text before
     3822                        this.toolbar.set( 'filtersLabel', new Label({
     3823                                value: l10n.filterByType,
     3824                                attributes: {
     3825                                        'for':  'media-attachment-filters'
     3826                                },
     3827                                priority:   -80
     3828                        }).render() );
     3829
     3830                        if ( 'uploaded' === this.options.filters ) {
     3831                                this.toolbar.set( 'filters', new Uploaded({
     3832                                        controller: this.controller,
     3833                                        model:      this.collection.props,
     3834                                        priority:   -80
     3835                                }).render() );
     3836                        } else {
     3837                                Filters = new All({
     3838                                        controller: this.controller,
     3839                                        model:      this.collection.props,
     3840                                        priority:   -80
     3841                                });
     3842
     3843                                this.toolbar.set( 'filters', Filters.render() );
     3844                        }
     3845                }
     3846
     3847                // Feels odd to bring the global media library switcher into the Attachment
     3848                // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
     3849                // which the controller can tap into and add this view?
     3850                if ( this.controller.isModeActive( 'grid' ) ) {
     3851                        LibraryViewSwitcher = View.extend({
     3852                                className: 'view-switch media-grid-view-switch',
     3853                                template: wp.template( 'media-library-view-switcher')
     3854                        });
     3855
     3856                        this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
     3857                                controller: this.controller,
     3858                                priority: -90
     3859                        }).render() );
     3860
     3861                        // DateFilter is a <select>, screen reader text needs to be rendered before
     3862                        this.toolbar.set( 'dateFilterLabel', new Label({
     3863                                value: l10n.filterByDate,
     3864                                attributes: {
     3865                                        'for': 'media-attachment-date-filters'
     3866                                },
     3867                                priority: -75
     3868                        }).render() );
     3869                        this.toolbar.set( 'dateFilter', new DateFilter({
     3870                                controller: this.controller,
     3871                                model:      this.collection.props,
     3872                                priority: -75
     3873                        }).render() );
     3874
     3875                        // BulkSelection is a <div> with subviews, including screen reader text
     3876                        this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
     3877                                text: l10n.bulkSelect,
     3878                                controller: this.controller,
     3879                                priority: -70
     3880                        }).render() );
     3881
     3882                        this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
     3883                                filters: Filters,
     3884                                style: 'primary',
     3885                                disabled: true,
     3886                                text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
     3887                                controller: this.controller,
     3888                                priority: -60,
     3889                                click: function() {
     3890                                        var changed = [], removed = [], self = this,
     3891                                                selection = this.controller.state().get( 'selection' ),
     3892                                                library = this.controller.state().get( 'library' );
     3893
     3894                                        if ( ! selection.length ) {
     3895                                                return;
     3896                                        }
     3897
     3898                                        if ( ! mediaTrash && ! confirm( l10n.warnBulkDelete ) ) {
     3899                                                return;
     3900                                        }
     3901
     3902                                        if ( mediaTrash &&
     3903                                                'trash' !== selection.at( 0 ).get( 'status' ) &&
     3904                                                ! confirm( l10n.warnBulkTrash ) ) {
     3905
     3906                                                return;
     3907                                        }
     3908
     3909                                        selection.each( function( model ) {
     3910                                                if ( ! model.get( 'nonces' )['delete'] ) {
     3911                                                        removed.push( model );
     3912                                                        return;
     3913                                                }
     3914
     3915                                                if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
     3916                                                        model.set( 'status', 'inherit' );
     3917                                                        changed.push( model.save() );
     3918                                                        removed.push( model );
     3919                                                } else if ( mediaTrash ) {
     3920                                                        model.set( 'status', 'trash' );
     3921                                                        changed.push( model.save() );
     3922                                                        removed.push( model );
     3923                                                } else {
     3924                                                        model.destroy({wait: true});
     3925                                                }
     3926                                        } );
     3927
     3928                                        if ( changed.length ) {
     3929                                                selection.remove( removed );
     3930
     3931                                                $.when.apply( null, changed ).then( function() {
     3932                                                        library._requery( true );
     3933                                                        self.controller.trigger( 'selection:action:done' );
     3934                                                } );
     3935                                        } else {
     3936                                                this.controller.trigger( 'selection:action:done' );
     3937                                        }
     3938                                }
     3939                        }).render() );
     3940
     3941                        if ( mediaTrash ) {
     3942                                this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
     3943                                        filters: Filters,
     3944                                        style: 'primary',
     3945                                        disabled: true,
     3946                                        text: l10n.deleteSelected,
     3947                                        controller: this.controller,
     3948                                        priority: -55,
     3949                                        click: function() {
     3950                                                var removed = [], selection = this.controller.state().get( 'selection' );
     3951
     3952                                                if ( ! selection.length || ! confirm( l10n.warnBulkDelete ) ) {
     3953                                                        return;
     3954                                                }
     3955
     3956                                                selection.each( function( model ) {
     3957                                                        if ( ! model.get( 'nonces' )['delete'] ) {
     3958                                                                removed.push( model );
     3959                                                                return;
     3960                                                        }
     3961
     3962                                                        model.destroy();
     3963                                                } );
     3964
     3965                                                selection.remove( removed );
     3966                                                this.controller.trigger( 'selection:action:done' );
     3967                                        }
     3968                                }).render() );
     3969                        }
     3970
     3971                } else {
     3972                        // DateFilter is a <select>, screen reader text needs to be rendered before
     3973                        this.toolbar.set( 'dateFilterLabel', new Label({
     3974                                value: l10n.filterByDate,
     3975                                attributes: {
     3976                                        'for': 'media-attachment-date-filters'
     3977                                },
     3978                                priority: -75
     3979                        }).render() );
     3980                        this.toolbar.set( 'dateFilter', new DateFilter({
     3981                                controller: this.controller,
     3982                                model:      this.collection.props,
     3983                                priority: -75
     3984                        }).render() );
     3985                }
     3986
     3987                if ( this.options.search ) {
     3988                        // Search is an input, screen reader text needs to be rendered before
     3989                        this.toolbar.set( 'searchLabel', new Label({
     3990                                value: l10n.searchMediaLabel,
     3991                                attributes: {
     3992                                        'for': 'media-search-input'
     3993                                },
     3994                                priority:   60
     3995                        }).render() );
     3996                        this.toolbar.set( 'search', new Search({
     3997                                controller: this.controller,
     3998                                model:      this.collection.props,
     3999                                priority:   60
     4000                        }).render() );
     4001                }
     4002
     4003                if ( this.options.dragInfo ) {
     4004                        this.toolbar.set( 'dragInfo', new View({
     4005                                el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
     4006                                priority: -40
     4007                        }) );
     4008                }
     4009
     4010                if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
     4011                        this.toolbar.set( 'suggestedDimensions', new View({
     4012                                el: $( '<div class="instructions">' + l10n.suggestedDimensions + ' ' + this.options.suggestedWidth + ' &times; ' + this.options.suggestedHeight + '</div>' )[0],
     4013                                priority: -40
     4014                        }) );
     4015                }
     4016        },
     4017
     4018        updateContent: function() {
     4019                var view = this,
     4020                        noItemsView;
     4021
     4022                if ( this.controller.isModeActive( 'grid' ) ) {
     4023                        noItemsView = view.attachmentsNoResults;
     4024                } else {
     4025                        noItemsView = view.uploader;
     4026                }
     4027
     4028                if ( ! this.collection.length ) {
     4029                        this.toolbar.get( 'spinner' ).show();
     4030                        this.dfd = this.collection.more().done( function() {
     4031                                if ( ! view.collection.length ) {
     4032                                        noItemsView.$el.removeClass( 'hidden' );
     4033                                } else {
     4034                                        noItemsView.$el.addClass( 'hidden' );
     4035                                }
     4036                                view.toolbar.get( 'spinner' ).hide();
     4037                        } );
     4038                } else {
     4039                        noItemsView.$el.addClass( 'hidden' );
     4040                        view.toolbar.get( 'spinner' ).hide();
     4041                }
     4042        },
     4043
     4044        createUploader: function() {
     4045                this.uploader = new UploaderInline({
     4046                        controller: this.controller,
     4047                        status:     false,
     4048                        message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
     4049                        canClose:   this.controller.isModeActive( 'grid' )
     4050                });
     4051
     4052                this.uploader.hide();
     4053                this.views.add( this.uploader );
     4054        },
     4055
     4056        toggleUploader: function() {
     4057                if ( this.uploader.$el.hasClass( 'hidden' ) ) {
     4058                        this.uploader.show();
     4059                } else {
     4060                        this.uploader.hide();
     4061                }
     4062        },
     4063
     4064        createAttachments: function() {
     4065                this.attachments = new Attachments({
     4066                        controller:           this.controller,
     4067                        collection:           this.collection,
     4068                        selection:            this.options.selection,
     4069                        model:                this.model,
     4070                        sortable:             this.options.sortable,
     4071                        scrollElement:        this.options.scrollElement,
     4072                        idealColumnWidth:     this.options.idealColumnWidth,
     4073
     4074                        // The single `Attachment` view to be used in the `Attachments` view.
     4075                        AttachmentView: this.options.AttachmentView
     4076                });
     4077
     4078                // Add keydown listener to the instance of the Attachments view
     4079                this.attachments.listenTo( this.controller, 'attachment:keydown:arrow',     this.attachments.arrowEvent );
     4080                this.attachments.listenTo( this.controller, 'attachment:details:shift-tab', this.attachments.restoreFocus );
     4081
     4082                this.views.add( this.attachments );
     4083
     4084
     4085                if ( this.controller.isModeActive( 'grid' ) ) {
     4086                        this.attachmentsNoResults = new View({
     4087                                controller: this.controller,
     4088                                tagName: 'p'
     4089                        });
     4090
     4091                        this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
     4092                        this.attachmentsNoResults.$el.html( l10n.noMedia );
     4093
     4094                        this.views.add( this.attachmentsNoResults );
     4095                }
     4096        },
     4097
     4098        createSidebar: function() {
     4099                var options = this.options,
     4100                        selection = options.selection,
     4101                        sidebar = this.sidebar = new Sidebar({
     4102                                controller: this.controller
     4103                        });
     4104
     4105                this.views.add( sidebar );
     4106
     4107                if ( this.controller.uploader ) {
     4108                        sidebar.set( 'uploads', new UploaderStatus({
     4109                                controller: this.controller,
     4110                                priority:   40
     4111                        }) );
     4112                }
     4113
     4114                selection.on( 'selection:single', this.createSingle, this );
     4115                selection.on( 'selection:unsingle', this.disposeSingle, this );
     4116
     4117                if ( selection.single() ) {
     4118                        this.createSingle();
     4119                }
     4120        },
     4121
     4122        createSingle: function() {
     4123                var sidebar = this.sidebar,
     4124                        single = this.options.selection.single();
     4125
     4126                sidebar.set( 'details', new Details({
     4127                        controller: this.controller,
     4128                        model:      single,
     4129                        priority:   80
     4130                }) );
     4131
     4132                sidebar.set( 'compat', new AttachmentCompat({
     4133                        controller: this.controller,
     4134                        model:      single,
     4135                        priority:   120
     4136                }) );
     4137
     4138                if ( this.options.display ) {
     4139                        sidebar.set( 'display', new AttachmentDisplay({
     4140                                controller:   this.controller,
     4141                                model:        this.model.display( single ),
     4142                                attachment:   single,
     4143                                priority:     160,
     4144                                userSettings: this.model.get('displayUserSettings')
     4145                        }) );
     4146                }
     4147
     4148                // Show the sidebar on mobile
     4149                if ( this.model.id === 'insert' ) {
     4150                        sidebar.$el.addClass( 'visible' );
     4151                }
     4152        },
     4153
     4154        disposeSingle: function() {
     4155                var sidebar = this.sidebar;
     4156                sidebar.unset('details');
     4157                sidebar.unset('compat');
     4158                sidebar.unset('display');
     4159                // Hide the sidebar on mobile
     4160                sidebar.$el.removeClass( 'visible' );
     4161        }
     4162});
     4163
     4164module.exports = AttachmentsBrowser;
     4165},{"../attachment-compat.js":15,"../attachment-filters/all.js":17,"../attachment-filters/date.js":18,"../attachment-filters/uploaded.js":19,"../attachment/details.js":21,"../attachment/library.js":22,"../attachments.js":23,"../label.js":34,"../search.js":43,"../settings/attachment-display.js":45,"../sidebar.js":46,"../spinner.js":47,"../toolbar.js":48,"../uploader/inline.js":50,"../uploader/status.js":52,"../view.js":55}],25:[function(require,module,exports){
     4166/**
     4167 * wp.media.view.AudioDetails
     4168 *
     4169 * @constructor
     4170 * @augments wp.media.view.MediaDetails
     4171 * @augments wp.media.view.Settings.AttachmentDisplay
     4172 * @augments wp.media.view.Settings
     4173 * @augments wp.media.View
     4174 * @augments wp.Backbone.View
     4175 * @augments Backbone.View
     4176 */
     4177var MediaDetails = require( './media-details' ),
     4178        AudioDetails;
     4179
     4180AudioDetails = MediaDetails.extend({
     4181        className: 'audio-details',
     4182        template:  wp.template('audio-details'),
     4183
     4184        setMedia: function() {
     4185                var audio = this.$('.wp-audio-shortcode');
     4186
     4187                if ( audio.find( 'source' ).length ) {
     4188                        if ( audio.is(':hidden') ) {
     4189                                audio.show();
     4190                        }
     4191                        this.media = MediaDetails.prepareSrc( audio.get(0) );
     4192                } else {
     4193                        audio.hide();
     4194                        this.media = false;
     4195                }
     4196
     4197                return this;
     4198        }
     4199});
     4200
     4201module.exports = AudioDetails;
     4202},{"./media-details":35}],26:[function(require,module,exports){
     4203/**
     4204 * wp.media.view.Button
     4205 *
     4206 * @class
     4207 * @augments wp.media.View
     4208 * @augments wp.Backbone.View
     4209 * @augments Backbone.View
     4210 */
     4211var View = require( './view.js' ),
     4212        Button;
     4213
     4214Button = View.extend({
     4215        tagName:    'a',
     4216        className:  'media-button',
     4217        attributes: { href: '#' },
     4218
     4219        events: {
     4220                'click': 'click'
     4221        },
     4222
     4223        defaults: {
     4224                text:     '',
     4225                style:    '',
     4226                size:     'large',
     4227                disabled: false
     4228        },
     4229
     4230        initialize: function() {
     4231                /**
     4232                 * Create a model with the provided `defaults`.
     4233                 *
     4234                 * @member {Backbone.Model}
     4235                 */
     4236                this.model = new Backbone.Model( this.defaults );
     4237
     4238                // If any of the `options` have a key from `defaults`, apply its
     4239                // value to the `model` and remove it from the `options object.
     4240                _.each( this.defaults, function( def, key ) {
     4241                        var value = this.options[ key ];
     4242                        if ( _.isUndefined( value ) ) {
     4243                                return;
     4244                        }
     4245
     4246                        this.model.set( key, value );
     4247                        delete this.options[ key ];
     4248                }, this );
     4249
     4250                this.model.on( 'change', this.render, this );
     4251        },
     4252        /**
     4253         * @returns {wp.media.view.Button} Returns itself to allow chaining
     4254         */
     4255        render: function() {
     4256                var classes = [ 'button', this.className ],
     4257                        model = this.model.toJSON();
     4258
     4259                if ( model.style ) {
     4260                        classes.push( 'button-' + model.style );
     4261                }
     4262
     4263                if ( model.size ) {
     4264                        classes.push( 'button-' + model.size );
     4265                }
     4266
     4267                classes = _.uniq( classes.concat( this.options.classes ) );
     4268                this.el.className = classes.join(' ');
     4269
     4270                this.$el.attr( 'disabled', model.disabled );
     4271                this.$el.text( this.model.get('text') );
     4272
     4273                return this;
     4274        },
     4275        /**
     4276         * @param {Object} event
     4277         */
     4278        click: function( event ) {
     4279                if ( '#' === this.attributes.href ) {
     4280                        event.preventDefault();
     4281                }
     4282
     4283                if ( this.options.click && ! this.model.get('disabled') ) {
     4284                        this.options.click.apply( this, arguments );
     4285                }
     4286        }
     4287});
     4288
     4289module.exports = Button;
     4290},{"./view.js":55}],27:[function(require,module,exports){
     4291/**
     4292 * wp.media.view.FocusManager
     4293 *
     4294 * @class
     4295 * @augments wp.media.View
     4296 * @augments wp.Backbone.View
     4297 * @augments Backbone.View
     4298 */
     4299var View = require( './view.js' ),
     4300        FocusManager;
     4301
     4302FocusManager = View.extend({
     4303
     4304        events: {
     4305                'keydown': 'constrainTabbing'
     4306        },
     4307
     4308        focus: function() { // Reset focus on first left menu item
     4309                this.$('.media-menu-item').first().focus();
     4310        },
     4311        /**
     4312         * @param {Object} event
     4313         */
     4314        constrainTabbing: function( event ) {
     4315                var tabbables;
     4316
     4317                // Look for the tab key.
     4318                if ( 9 !== event.keyCode ) {
     4319                        return;
     4320                }
     4321
     4322                tabbables = this.$( ':tabbable' );
     4323
     4324                // Keep tab focus within media modal while it's open
     4325                if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
     4326                        tabbables.first().focus();
     4327                        return false;
     4328                } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
     4329                        tabbables.last().focus();
     4330                        return false;
     4331                }
     4332        }
     4333
     4334});
     4335
     4336module.exports = FocusManager;
     4337},{"./view.js":55}],28:[function(require,module,exports){
     4338/**
     4339 * wp.media.view.Frame
     4340 *
     4341 * A frame is a composite view consisting of one or more regions and one or more
     4342 * states.
     4343 *
     4344 * @see wp.media.controller.State
     4345 * @see wp.media.controller.Region
     4346 *
     4347 * @class
     4348 * @augments wp.media.View
     4349 * @augments wp.Backbone.View
     4350 * @augments Backbone.View
     4351 * @mixes wp.media.controller.StateMachine
     4352 */
     4353var StateMachine = require( '../controllers/state-machine.js' ),
     4354        State = require( '../controllers/state.js' ),
     4355        Region = require( '../controllers/region.js' ),
     4356        View = require( './view.js' ),
     4357        Frame;
     4358
     4359Frame = View.extend({
     4360        initialize: function() {
     4361                _.defaults( this.options, {
     4362                        mode: [ 'select' ]
     4363                });
     4364                this._createRegions();
     4365                this._createStates();
     4366                this._createModes();
     4367        },
     4368
     4369        _createRegions: function() {
     4370                // Clone the regions array.
     4371                this.regions = this.regions ? this.regions.slice() : [];
     4372
     4373                // Initialize regions.
     4374                _.each( this.regions, function( region ) {
     4375                        this[ region ] = new Region({
     4376                                view:     this,
     4377                                id:       region,
     4378                                selector: '.media-frame-' + region
     4379                        });
     4380                }, this );
     4381        },
     4382        /**
     4383         * Create the frame's states.
     4384         *
     4385         * @see wp.media.controller.State
     4386         * @see wp.media.controller.StateMachine
     4387         *
     4388         * @fires wp.media.controller.State#ready
     4389         */
     4390        _createStates: function() {
     4391                // Create the default `states` collection.
     4392                this.states = new Backbone.Collection( null, {
     4393                        model: State
     4394                });
     4395
     4396                // Ensure states have a reference to the frame.
     4397                this.states.on( 'add', function( model ) {
     4398                        model.frame = this;
     4399                        model.trigger('ready');
     4400                }, this );
     4401
     4402                if ( this.options.states ) {
     4403                        this.states.add( this.options.states );
     4404                }
     4405        },
     4406
     4407        /**
     4408         * A frame can be in a mode or multiple modes at one time.
     4409         *
     4410         * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
     4411         */
     4412        _createModes: function() {
     4413                // Store active "modes" that the frame is in. Unrelated to region modes.
     4414                this.activeModes = new Backbone.Collection();
     4415                this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
     4416
     4417                _.each( this.options.mode, function( mode ) {
     4418                        this.activateMode( mode );
     4419                }, this );
     4420        },
     4421        /**
     4422         * Reset all states on the frame to their defaults.
     4423         *
     4424         * @returns {wp.media.view.Frame} Returns itself to allow chaining
     4425         */
     4426        reset: function() {
     4427                this.states.invoke( 'trigger', 'reset' );
     4428                return this;
     4429        },
     4430        /**
     4431         * Map activeMode collection events to the frame.
     4432         */
     4433        triggerModeEvents: function( model, collection, options ) {
     4434                var collectionEvent,
     4435                        modeEventMap = {
     4436                                add: 'activate',
     4437                                remove: 'deactivate'
     4438                        },
     4439                        eventToTrigger;
     4440                // Probably a better way to do this.
     4441                _.each( options, function( value, key ) {
     4442                        if ( value ) {
     4443                                collectionEvent = key;
     4444                        }
     4445                } );
     4446
     4447                if ( ! _.has( modeEventMap, collectionEvent ) ) {
     4448                        return;
     4449                }
     4450
     4451                eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
     4452                this.trigger( eventToTrigger );
     4453        },
     4454        /**
     4455         * Activate a mode on the frame.
     4456         *
     4457         * @param string mode Mode ID.
     4458         * @returns {this} Returns itself to allow chaining.
     4459         */
     4460        activateMode: function( mode ) {
     4461                // Bail if the mode is already active.
     4462                if ( this.isModeActive( mode ) ) {
     4463                        return;
     4464                }
     4465                this.activeModes.add( [ { id: mode } ] );
     4466                // Add a CSS class to the frame so elements can be styled for the mode.
     4467                this.$el.addClass( 'mode-' + mode );
     4468
     4469                return this;
     4470        },
     4471        /**
     4472         * Deactivate a mode on the frame.
     4473         *
     4474         * @param string mode Mode ID.
     4475         * @returns {this} Returns itself to allow chaining.
     4476         */
     4477        deactivateMode: function( mode ) {
     4478                // Bail if the mode isn't active.
     4479                if ( ! this.isModeActive( mode ) ) {
     4480                        return this;
     4481                }
     4482                this.activeModes.remove( this.activeModes.where( { id: mode } ) );
     4483                this.$el.removeClass( 'mode-' + mode );
     4484                /**
     4485                 * Frame mode deactivation event.
     4486                 *
     4487                 * @event this#{mode}:deactivate
     4488                 */
     4489                this.trigger( mode + ':deactivate' );
     4490
     4491                return this;
     4492        },
     4493        /**
     4494         * Check if a mode is enabled on the frame.
     4495         *
     4496         * @param  string mode Mode ID.
     4497         * @return bool
     4498         */
     4499        isModeActive: function( mode ) {
     4500                return Boolean( this.activeModes.where( { id: mode } ).length );
     4501        }
     4502});
     4503
     4504// Make the `Frame` a `StateMachine`.
     4505_.extend( Frame.prototype, StateMachine.prototype );
     4506
     4507module.exports = Frame;
     4508},{"../controllers/region.js":5,"../controllers/state-machine.js":6,"../controllers/state.js":7,"./view.js":55}],29:[function(require,module,exports){
     4509/**
     4510 * wp.media.view.MediaFrame.AudioDetails
     4511 *
     4512 * @constructor
     4513 * @augments wp.media.view.MediaFrame.MediaDetails
     4514 * @augments wp.media.view.MediaFrame.Select
     4515 * @augments wp.media.view.MediaFrame
     4516 * @augments wp.media.view.Frame
     4517 * @augments wp.media.View
     4518 * @augments wp.Backbone.View
     4519 * @augments Backbone.View
     4520 * @mixes wp.media.controller.StateMachine
     4521 */
     4522var MediaDetails = require( './media-details' ),
     4523        MediaLibrary = require( '../../controllers/media-library.js' ),
     4524        AudioDetailsView = require( '../audio-details.js' ),
     4525        AudioDetailsController = require( '../../controllers/audio-details.js' ),
     4526        l10n = wp.media.view.l10n,
     4527        AudioDetails;
     4528
     4529AudioDetails = MediaDetails.extend({
     4530        defaults: {
     4531                id:      'audio',
     4532                url:     '',
     4533                menu:    'audio-details',
     4534                content: 'audio-details',
     4535                toolbar: 'audio-details',
     4536                type:    'link',
     4537                title:    l10n.audioDetailsTitle,
     4538                priority: 120
     4539        },
     4540
     4541        initialize: function( options ) {
     4542                options.DetailsView = AudioDetailsView;
     4543                options.cancelText = l10n.audioDetailsCancel;
     4544                options.addText = l10n.audioAddSourceTitle;
     4545
     4546                MediaDetails.prototype.initialize.call( this, options );
     4547        },
     4548
     4549        bindHandlers: function() {
     4550                MediaDetails.prototype.bindHandlers.apply( this, arguments );
     4551
     4552                this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
     4553                this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
     4554        },
     4555
     4556        createStates: function() {
     4557                this.states.add([
     4558                        new AudioDetailsController( {
     4559                                media: this.media
     4560                        } ),
     4561
     4562                        new MediaLibrary( {
     4563                                type: 'audio',
     4564                                id: 'replace-audio',
     4565                                title: l10n.audioReplaceTitle,
     4566                                toolbar: 'replace-audio',
     4567                                media: this.media,
     4568                                menu: 'audio-details'
     4569                        } ),
     4570
     4571                        new MediaLibrary( {
     4572                                type: 'audio',
     4573                                id: 'add-audio-source',
     4574                                title: l10n.audioAddSourceTitle,
     4575                                toolbar: 'add-audio-source',
     4576                                media: this.media,
     4577                                menu: false
     4578                        } )
     4579                ]);
     4580        }
     4581});
     4582
     4583module.exports = AudioDetails;
     4584},{"../../controllers/audio-details.js":2,"../../controllers/media-library.js":4,"../audio-details.js":25,"./media-details":30}],30:[function(require,module,exports){
     4585/**
     4586 * wp.media.view.MediaFrame.MediaDetails
     4587 *
     4588 * @constructor
     4589 * @augments wp.media.view.MediaFrame.Select
     4590 * @augments wp.media.view.MediaFrame
     4591 * @augments wp.media.view.Frame
     4592 * @augments wp.media.View
     4593 * @augments wp.Backbone.View
     4594 * @augments Backbone.View
     4595 * @mixes wp.media.controller.StateMachine
     4596 */
     4597var View = require( '../view.js' ),
     4598        Toolbar = require( '../toolbar.js' ),
     4599        Select = require( './select.js' ),
     4600        Selection = require( '../../models/selection.js' ),
     4601        PostMedia = require( '../../models/post-media.js' ),
     4602        l10n = wp.media.view.l10n,
     4603        MediaDetails;
     4604
     4605MediaDetails = Select.extend({
     4606        defaults: {
     4607                id:      'media',
     4608                url:     '',
     4609                menu:    'media-details',
     4610                content: 'media-details',
     4611                toolbar: 'media-details',
     4612                type:    'link',
     4613                priority: 120
     4614        },
     4615
     4616        initialize: function( options ) {
     4617                this.DetailsView = options.DetailsView;
     4618                this.cancelText = options.cancelText;
     4619                this.addText = options.addText;
     4620
     4621                this.media = new PostMedia( options.metadata );
     4622                this.options.selection = new Selection( this.media.attachment, { multiple: false } );
     4623                Select.prototype.initialize.apply( this, arguments );
     4624        },
     4625
     4626        bindHandlers: function() {
     4627                var menu = this.defaults.menu;
     4628
     4629                Select.prototype.bindHandlers.apply( this, arguments );
     4630
     4631                this.on( 'menu:create:' + menu, this.createMenu, this );
     4632                this.on( 'content:render:' + menu, this.renderDetailsContent, this );
     4633                this.on( 'menu:render:' + menu, this.renderMenu, this );
     4634                this.on( 'toolbar:render:' + menu, this.renderDetailsToolbar, this );
     4635        },
     4636
     4637        renderDetailsContent: function() {
     4638                var view = new this.DetailsView({
     4639                        controller: this,
     4640                        model: this.state().media,
     4641                        attachment: this.state().media.attachment
     4642                }).render();
     4643
     4644                this.content.set( view );
     4645        },
     4646
     4647        renderMenu: function( view ) {
     4648                var lastState = this.lastState(),
     4649                        previous = lastState && lastState.id,
     4650                        frame = this;
     4651
     4652                view.set({
     4653                        cancel: {
     4654                                text:     this.cancelText,
     4655                                priority: 20,
     4656                                click:    function() {
     4657                                        if ( previous ) {
     4658                                                frame.setState( previous );
     4659                                        } else {
     4660                                                frame.close();
     4661                                        }
     4662                                }
     4663                        },
     4664                        separateCancel: new View({
     4665                                className: 'separator',
     4666                                priority: 40
     4667                        })
     4668                });
     4669
     4670        },
     4671
     4672        setPrimaryButton: function(text, handler) {
     4673                this.toolbar.set( new Toolbar({
     4674                        controller: this,
     4675                        items: {
     4676                                button: {
     4677                                        style:    'primary',
     4678                                        text:     text,
     4679                                        priority: 80,
     4680                                        click:    function() {
     4681                                                var controller = this.controller;
     4682                                                handler.call( this, controller, controller.state() );
     4683                                                // Restore and reset the default state.
     4684                                                controller.setState( controller.options.state );
     4685                                                controller.reset();
     4686                                        }
     4687                                }
     4688                        }
     4689                }) );
     4690        },
     4691
     4692        renderDetailsToolbar: function() {
     4693                this.setPrimaryButton( l10n.update, function( controller, state ) {
     4694                        controller.close();
     4695                        state.trigger( 'update', controller.media.toJSON() );
     4696                } );
     4697        },
     4698
     4699        renderReplaceToolbar: function() {
     4700                this.setPrimaryButton( l10n.replace, function( controller, state ) {
     4701                        var attachment = state.get( 'selection' ).single();
     4702                        controller.media.changeAttachment( attachment );
     4703                        state.trigger( 'replace', controller.media.toJSON() );
     4704                } );
     4705        },
     4706
     4707        renderAddSourceToolbar: function() {
     4708                this.setPrimaryButton( this.addText, function( controller, state ) {
     4709                        var attachment = state.get( 'selection' ).single();
     4710                        controller.media.setSource( attachment );
     4711                        state.trigger( 'add-source', controller.media.toJSON() );
     4712                } );
     4713        }
     4714});
     4715
     4716module.exports = MediaDetails;
     4717},{"../../models/post-media.js":11,"../../models/selection.js":13,"../toolbar.js":48,"../view.js":55,"./select.js":31}],31:[function(require,module,exports){
     4718/**
     4719 * wp.media.view.MediaFrame.Select
     4720 *
     4721 * A frame for selecting an item or items from the media library.
     4722 *
     4723 * @class
     4724 * @augments wp.media.view.MediaFrame
     4725 * @augments wp.media.view.Frame
     4726 * @augments wp.media.View
     4727 * @augments wp.Backbone.View
     4728 * @augments Backbone.View
     4729 * @mixes wp.media.controller.StateMachine
     4730 */
     4731
     4732var MediaFrame = require( '../media-frame.js' ),
     4733        Library = require( '../../controllers/library.js' ),
     4734        AttachmentsModel = require( '../../models/attachments.js' ),
     4735        SelectionModel = require( '../../models/selection.js' ),
     4736        AttachmentsBrowser = require( '../attachments/browser.js' ),
     4737        UploaderInline = require( '../uploader/inline.js' ),
     4738        ToolbarSelect = require( '../toolbar/select.js' ),
     4739        l10n = wp.media.view.l10n,
     4740        Select;
     4741
     4742Select = MediaFrame.extend({
     4743        initialize: function() {
     4744                // Call 'initialize' directly on the parent class.
     4745                MediaFrame.prototype.initialize.apply( this, arguments );
     4746
     4747                _.defaults( this.options, {
     4748                        selection: [],
     4749                        library:   {},
     4750                        multiple:  false,
     4751                        state:    'library'
     4752                });
     4753
     4754                this.createSelection();
     4755                this.createStates();
     4756                this.bindHandlers();
     4757        },
     4758
     4759        /**
     4760         * Attach a selection collection to the frame.
     4761         *
     4762         * A selection is a collection of attachments used for a specific purpose
     4763         * by a media frame. e.g. Selecting an attachment (or many) to insert into
     4764         * post content.
     4765         *
     4766         * @see media.model.Selection
     4767         */
     4768        createSelection: function() {
     4769                var selection = this.options.selection;
     4770
     4771                if ( ! (selection instanceof SelectionModel) ) {
     4772                        this.options.selection = new SelectionModel( selection, {
     4773                                multiple: this.options.multiple
     4774                        });
     4775                }
     4776
     4777                this._selection = {
     4778                        attachments: new AttachmentsModel(),
     4779                        difference: []
     4780                };
     4781        },
     4782
     4783        /**
     4784         * Create the default states on the frame.
     4785         */
     4786        createStates: function() {
     4787                var options = this.options;
     4788
     4789                if ( this.options.states ) {
     4790                        return;
     4791                }
     4792
     4793                // Add the default states.
     4794                this.states.add([
     4795                        // Main states.
     4796                        new Library({
     4797                                library:   wp.media.query( options.library ),
     4798                                multiple:  options.multiple,
     4799                                title:     options.title,
     4800                                priority:  20
     4801                        })
     4802                ]);
     4803        },
     4804
     4805        /**
     4806         * Bind region mode event callbacks.
     4807         *
     4808         * @see media.controller.Region.render
     4809         */
     4810        bindHandlers: function() {
     4811                this.on( 'router:create:browse', this.createRouter, this );
     4812                this.on( 'router:render:browse', this.browseRouter, this );
     4813                this.on( 'content:create:browse', this.browseContent, this );
     4814                this.on( 'content:render:upload', this.uploadContent, this );
     4815                this.on( 'toolbar:create:select', this.createSelectToolbar, this );
     4816        },
     4817
     4818        /**
     4819         * Render callback for the router region in the `browse` mode.
     4820         *
     4821         * @param {wp.media.view.Router} routerView
     4822         */
     4823        browseRouter: function( routerView ) {
     4824                routerView.set({
     4825                        upload: {
     4826                                text:     l10n.uploadFilesTitle,
     4827                                priority: 20
     4828                        },
     4829                        browse: {
     4830                                text:     l10n.mediaLibraryTitle,
     4831                                priority: 40
     4832                        }
     4833                });
     4834        },
     4835
     4836        /**
     4837         * Render callback for the content region in the `browse` mode.
     4838         *
     4839         * @param {wp.media.controller.Region} contentRegion
     4840         */
     4841        browseContent: function( contentRegion ) {
     4842                var state = this.state();
     4843
     4844                this.$el.removeClass('hide-toolbar');
     4845
     4846                // Browse our library of attachments.
     4847                contentRegion.view = new AttachmentsBrowser({
     4848                        controller: this,
     4849                        collection: state.get('library'),
     4850                        selection:  state.get('selection'),
     4851                        model:      state,
     4852                        sortable:   state.get('sortable'),
     4853                        search:     state.get('searchable'),
     4854                        filters:    state.get('filterable'),
     4855                        display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
     4856                        dragInfo:   state.get('dragInfo'),
     4857
     4858                        idealColumnWidth: state.get('idealColumnWidth'),
     4859                        suggestedWidth:   state.get('suggestedWidth'),
     4860                        suggestedHeight:  state.get('suggestedHeight'),
     4861
     4862                        AttachmentView: state.get('AttachmentView')
     4863                });
     4864        },
     4865
     4866        /**
     4867         * Render callback for the content region in the `upload` mode.
     4868         */
     4869        uploadContent: function() {
     4870                this.$el.removeClass( 'hide-toolbar' );
     4871                this.content.set( new UploaderInline({
     4872                        controller: this
     4873                }) );
     4874        },
     4875
     4876        /**
     4877         * Toolbars
     4878         *
     4879         * @param {Object} toolbar
     4880         * @param {Object} [options={}]
     4881         * @this wp.media.controller.Region
     4882         */
     4883        createSelectToolbar: function( toolbar, options ) {
     4884                options = options || this.options.button || {};
     4885                options.controller = this;
     4886
     4887                toolbar.view = new ToolbarSelect( options );
     4888        }
     4889});
     4890
     4891module.exports = Select;
     4892},{"../../controllers/library.js":3,"../../models/attachments.js":10,"../../models/selection.js":13,"../attachments/browser.js":24,"../media-frame.js":36,"../toolbar/select.js":49,"../uploader/inline.js":50}],32:[function(require,module,exports){
     4893/**
     4894 * wp.media.view.MediaFrame.VideoDetails
     4895 *
     4896 * @constructor
     4897 * @augments wp.media.view.MediaFrame.MediaDetails
     4898 * @augments wp.media.view.MediaFrame.Select
     4899 * @augments wp.media.view.MediaFrame
     4900 * @augments wp.media.view.Frame
     4901 * @augments wp.media.View
     4902 * @augments wp.Backbone.View
     4903 * @augments Backbone.View
     4904 * @mixes wp.media.controller.StateMachine
     4905 */
     4906var MediaDetails = require( './media-details' ),
     4907        MediaLibrary = require( '../../controllers/media-library.js' ),
     4908        VideoDetailsView = require( '../video-details.js' ),
     4909        VideoDetailsController = require( '../../controllers/video-details.js' ),
     4910        l10n = wp.media.view.l10n,
     4911        VideoDetails;
     4912
     4913VideoDetails = MediaDetails.extend({
     4914        defaults: {
     4915                id:      'video',
     4916                url:     '',
     4917                menu:    'video-details',
     4918                content: 'video-details',
     4919                toolbar: 'video-details',
     4920                type:    'link',
     4921                title:    l10n.videoDetailsTitle,
     4922                priority: 120
     4923        },
     4924
     4925        initialize: function( options ) {
     4926                options.DetailsView = VideoDetailsView;
     4927                options.cancelText = l10n.videoDetailsCancel;
     4928                options.addText = l10n.videoAddSourceTitle;
     4929
     4930                MediaDetails.prototype.initialize.call( this, options );
     4931        },
     4932
     4933        bindHandlers: function() {
     4934                MediaDetails.prototype.bindHandlers.apply( this, arguments );
     4935
     4936                this.on( 'toolbar:render:replace-video', this.renderReplaceToolbar, this );
     4937                this.on( 'toolbar:render:add-video-source', this.renderAddSourceToolbar, this );
     4938                this.on( 'toolbar:render:select-poster-image', this.renderSelectPosterImageToolbar, this );
     4939                this.on( 'toolbar:render:add-track', this.renderAddTrackToolbar, this );
     4940        },
     4941
     4942        createStates: function() {
     4943                this.states.add([
     4944                        new VideoDetailsController({
     4945                                media: this.media
     4946                        }),
     4947
     4948                        new MediaLibrary( {
     4949                                type: 'video',
     4950                                id: 'replace-video',
     4951                                title: l10n.videoReplaceTitle,
     4952                                toolbar: 'replace-video',
     4953                                media: this.media,
     4954                                menu: 'video-details'
     4955                        } ),
     4956
     4957                        new MediaLibrary( {
     4958                                type: 'video',
     4959                                id: 'add-video-source',
     4960                                title: l10n.videoAddSourceTitle,
     4961                                toolbar: 'add-video-source',
     4962                                media: this.media,
     4963                                menu: false
     4964                        } ),
     4965
     4966                        new MediaLibrary( {
     4967                                type: 'image',
     4968                                id: 'select-poster-image',
     4969                                title: l10n.videoSelectPosterImageTitle,
     4970                                toolbar: 'select-poster-image',
     4971                                media: this.media,
     4972                                menu: 'video-details'
     4973                        } ),
     4974
     4975                        new MediaLibrary( {
     4976                                type: 'text',
     4977                                id: 'add-track',
     4978                                title: l10n.videoAddTrackTitle,
     4979                                toolbar: 'add-track',
     4980                                media: this.media,
     4981                                menu: 'video-details'
     4982                        } )
     4983                ]);
     4984        },
     4985
     4986        renderSelectPosterImageToolbar: function() {
     4987                this.setPrimaryButton( l10n.videoSelectPosterImageTitle, function( controller, state ) {
     4988                        var urls = [], attachment = state.get( 'selection' ).single();
     4989
     4990                        controller.media.set( 'poster', attachment.get( 'url' ) );
     4991                        state.trigger( 'set-poster-image', controller.media.toJSON() );
     4992
     4993                        _.each( wp.media.view.settings.embedExts, function (ext) {
     4994                                if ( controller.media.get( ext ) ) {
     4995                                        urls.push( controller.media.get( ext ) );
     4996                                }
     4997                        } );
     4998
     4999                        wp.ajax.send( 'set-attachment-thumbnail', {
     5000                                data : {
     5001                                        urls: urls,
     5002                                        thumbnail_id: attachment.get( 'id' )
     5003                                }
     5004                        } );
     5005                } );
     5006        },
     5007
     5008        renderAddTrackToolbar: function() {
     5009                this.setPrimaryButton( l10n.videoAddTrackTitle, function( controller, state ) {
     5010                        var attachment = state.get( 'selection' ).single(),
     5011                                content = controller.media.get( 'content' );
     5012
     5013                        if ( -1 === content.indexOf( attachment.get( 'url' ) ) ) {
     5014                                content += [
     5015                                        '<track srclang="en" label="English"kind="subtitles" src="',
     5016                                        attachment.get( 'url' ),
     5017                                        '" />'
     5018                                ].join('');
     5019
     5020                                controller.media.set( 'content', content );
     5021                        }
     5022                        state.trigger( 'add-track', controller.media.toJSON() );
     5023                } );
     5024        }
     5025});
     5026
     5027module.exports = VideoDetails;
     5028},{"../../controllers/media-library.js":4,"../../controllers/video-details.js":8,"../video-details.js":54,"./media-details":30}],33:[function(require,module,exports){
     5029/**
     5030 * wp.media.view.Iframe
     5031 *
     5032 * @class
     5033 * @augments wp.media.View
     5034 * @augments wp.Backbone.View
     5035 * @augments Backbone.View
     5036 */
     5037var View = require( './view.js' ),
     5038        Iframe;
     5039
     5040Iframe = View.extend({
     5041        className: 'media-iframe',
     5042        /**
     5043         * @returns {wp.media.view.Iframe} Returns itself to allow chaining
     5044         */
     5045        render: function() {
     5046                this.views.detach();
     5047                this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
     5048                this.views.render();
     5049                return this;
     5050        }
     5051});
     5052
     5053module.exports = Iframe;
     5054},{"./view.js":55}],34:[function(require,module,exports){
     5055/**
     5056 * @class
     5057 * @augments wp.media.View
     5058 * @augments wp.Backbone.View
     5059 * @augments Backbone.View
     5060 */
     5061var View = require( './view.js' ),
     5062        Label;
     5063
     5064Label = View.extend({
     5065        tagName: 'label',
     5066        className: 'screen-reader-text',
     5067
     5068        initialize: function() {
     5069                this.value = this.options.value;
     5070        },
     5071
     5072        render: function() {
     5073                this.$el.html( this.value );
     5074
     5075                return this;
     5076        }
     5077});
     5078
     5079module.exports = Label;
     5080},{"./view.js":55}],35:[function(require,module,exports){
     5081/**
     5082 * wp.media.view.MediaDetails
     5083 *
     5084 * @constructor
     5085 * @augments wp.media.view.Settings.AttachmentDisplay
     5086 * @augments wp.media.view.Settings
     5087 * @augments wp.media.View
     5088 * @augments wp.Backbone.View
     5089 * @augments Backbone.View
     5090 */
     5091var AttachmentDisplay = require( './settings/attachment-display.js' ),
     5092        $ = jQuery,
     5093        MediaDetails;
     5094
     5095MediaDetails = AttachmentDisplay.extend({
     5096        initialize: function() {
     5097                _.bindAll(this, 'success');
     5098                this.players = [];
     5099                this.listenTo( this.controller, 'close', wp.media.mixin.unsetPlayers );
     5100                this.on( 'ready', this.setPlayer );
     5101                this.on( 'media:setting:remove', wp.media.mixin.unsetPlayers, this );
     5102                this.on( 'media:setting:remove', this.render );
     5103                this.on( 'media:setting:remove', this.setPlayer );
     5104                this.events = _.extend( this.events, {
     5105                        'click .remove-setting' : 'removeSetting',
     5106                        'change .content-track' : 'setTracks',
     5107                        'click .remove-track' : 'setTracks',
     5108                        'click .add-media-source' : 'addSource'
     5109                } );
     5110
     5111                AttachmentDisplay.prototype.initialize.apply( this, arguments );
     5112        },
     5113
     5114        prepare: function() {
     5115                return _.defaults({
     5116                        model: this.model.toJSON()
     5117                }, this.options );
     5118        },
     5119
     5120        /**
     5121         * Remove a setting's UI when the model unsets it
     5122         *
     5123         * @fires wp.media.view.MediaDetails#media:setting:remove
     5124         *
     5125         * @param {Event} e
     5126         */
     5127        removeSetting : function(e) {
     5128                var wrap = $( e.currentTarget ).parent(), setting;
     5129                setting = wrap.find( 'input' ).data( 'setting' );
     5130
     5131                if ( setting ) {
     5132                        this.model.unset( setting );
     5133                        this.trigger( 'media:setting:remove', this );
     5134                }
     5135
     5136                wrap.remove();
     5137        },
     5138
     5139        /**
     5140         *
     5141         * @fires wp.media.view.MediaDetails#media:setting:remove
     5142         */
     5143        setTracks : function() {
     5144                var tracks = '';
     5145
     5146                _.each( this.$('.content-track'), function(track) {
     5147                        tracks += $( track ).val();
     5148                } );
     5149
     5150                this.model.set( 'content', tracks );
     5151                this.trigger( 'media:setting:remove', this );
     5152        },
     5153
     5154        addSource : function( e ) {
     5155                this.controller.lastMime = $( e.currentTarget ).data( 'mime' );
     5156                this.controller.setState( 'add-' + this.controller.defaults.id + '-source' );
     5157        },
     5158
     5159        /**
     5160         * @global MediaElementPlayer
     5161         */
     5162        setPlayer : function() {
     5163                if ( ! this.players.length && this.media ) {
     5164                        this.players.push( new MediaElementPlayer( this.media, this.settings ) );
     5165                }
     5166        },
     5167
     5168        /**
     5169         * @abstract
     5170         */
     5171        setMedia : function() {
     5172                return this;
     5173        },
     5174
     5175        success : function(mejs) {
     5176                var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay;
     5177
     5178                if ( 'flash' === mejs.pluginType && autoplay ) {
     5179                        mejs.addEventListener( 'canplay', function() {
     5180                                mejs.play();
     5181                        }, false );
     5182                }
     5183
     5184                this.mejs = mejs;
     5185        },
     5186
     5187        /**
     5188         * @returns {media.view.MediaDetails} Returns itself to allow chaining
     5189         */
     5190        render: function() {
     5191                var self = this;
     5192
     5193                AttachmentDisplay.prototype.render.apply( this, arguments );
     5194                setTimeout( function() { self.resetFocus(); }, 10 );
     5195
     5196                this.settings = _.defaults( {
     5197                        success : this.success
     5198                }, wp.media.mixin.mejsSettings );
     5199
     5200                return this.setMedia();
     5201        },
     5202
     5203        resetFocus: function() {
     5204                this.$( '.embed-media-settings' ).scrollTop( 0 );
     5205        }
     5206}, {
     5207        instances : 0,
     5208        /**
     5209         * When multiple players in the DOM contain the same src, things get weird.
     5210         *
     5211         * @param {HTMLElement} elem
     5212         * @returns {HTMLElement}
     5213         */
     5214        prepareSrc : function( elem ) {
     5215                var i = MediaDetails.instances++;
     5216                _.each( $( elem ).find( 'source' ), function( source ) {
     5217                        source.src = [
     5218                                source.src,
     5219                                source.src.indexOf('?') > -1 ? '&' : '?',
     5220                                '_=',
     5221                                i
     5222                        ].join('');
     5223                } );
     5224
     5225                return elem;
     5226        }
     5227});
     5228
     5229module.exports = MediaDetails;
     5230},{"./settings/attachment-display.js":45}],36:[function(require,module,exports){
     5231/**
     5232 * wp.media.view.MediaFrame
     5233 *
     5234 * The frame used to create the media modal.
     5235 *
     5236 * @class
     5237 * @augments wp.media.view.Frame
     5238 * @augments wp.media.View
     5239 * @augments wp.Backbone.View
     5240 * @augments Backbone.View
     5241 * @mixes wp.media.controller.StateMachine
     5242 */
     5243var View = require( './view.js' ),
     5244        Frame = require( './frame.js' ),
     5245        Modal = require( './modal.js' ),
     5246        UploaderWindow = require( './uploader/window.js' ),
     5247        Menu = require( './menu.js' ),
     5248        Toolbar = require( './toolbar.js' ),
     5249        Router = require( './router.js' ),
     5250        Iframe = require( './iframe.js' ),
     5251        $ = jQuery,
     5252        MediaFrame;
     5253
     5254MediaFrame = Frame.extend({
     5255        className: 'media-frame',
     5256        template:  wp.template('media-frame'),
     5257        regions:   ['menu','title','content','toolbar','router'],
     5258
     5259        events: {
     5260                'click div.media-frame-title h1': 'toggleMenu'
     5261        },
     5262
     5263        /**
     5264         * @global wp.Uploader
     5265         */
     5266        initialize: function() {
     5267                Frame.prototype.initialize.apply( this, arguments );
     5268
     5269                _.defaults( this.options, {
     5270                        title:    '',
     5271                        modal:    true,
     5272                        uploader: true
     5273                });
     5274
     5275                // Ensure core UI is enabled.
     5276                this.$el.addClass('wp-core-ui');
     5277
     5278                // Initialize modal container view.
     5279                if ( this.options.modal ) {
     5280                        this.modal = new Modal({
     5281                                controller: this,
     5282                                title:      this.options.title
     5283                        });
     5284
     5285                        this.modal.content( this );
     5286                }
     5287
     5288                // Force the uploader off if the upload limit has been exceeded or
     5289                // if the browser isn't supported.
     5290                if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     5291                        this.options.uploader = false;
     5292                }
     5293
     5294                // Initialize window-wide uploader.
     5295                if ( this.options.uploader ) {
     5296                        this.uploader = new UploaderWindow({
     5297                                controller: this,
     5298                                uploader: {
     5299                                        dropzone:  this.modal ? this.modal.$el : this.$el,
     5300                                        container: this.$el
     5301                                }
     5302                        });
     5303                        this.views.set( '.media-frame-uploader', this.uploader );
     5304                }
     5305
     5306                this.on( 'attach', _.bind( this.views.ready, this.views ), this );
     5307
     5308                // Bind default title creation.
     5309                this.on( 'title:create:default', this.createTitle, this );
     5310                this.title.mode('default');
     5311
     5312                this.on( 'title:render', function( view ) {
     5313                        view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
     5314                });
     5315
     5316                // Bind default menu.
     5317                this.on( 'menu:create:default', this.createMenu, this );
     5318        },
     5319        /**
     5320         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     5321         */
     5322        render: function() {
     5323                // Activate the default state if no active state exists.
     5324                if ( ! this.state() && this.options.state ) {
     5325                        this.setState( this.options.state );
     5326                }
     5327                /**
     5328                 * call 'render' directly on the parent class
     5329                 */
     5330                return Frame.prototype.render.apply( this, arguments );
     5331        },
     5332        /**
     5333         * @param {Object} title
     5334         * @this wp.media.controller.Region
     5335         */
     5336        createTitle: function( title ) {
     5337                title.view = new View({
     5338                        controller: this,
     5339                        tagName: 'h1'
     5340                });
     5341        },
     5342        /**
     5343         * @param {Object} menu
     5344         * @this wp.media.controller.Region
     5345         */
     5346        createMenu: function( menu ) {
     5347                menu.view = new Menu({
     5348                        controller: this
     5349                });
     5350        },
     5351
     5352        toggleMenu: function() {
     5353                this.$el.find( '.media-menu' ).toggleClass( 'visible' );
     5354        },
     5355
     5356        /**
     5357         * @param {Object} toolbar
     5358         * @this wp.media.controller.Region
     5359         */
     5360        createToolbar: function( toolbar ) {
     5361                toolbar.view = new Toolbar({
     5362                        controller: this
     5363                });
     5364        },
     5365        /**
     5366         * @param {Object} router
     5367         * @this wp.media.controller.Region
     5368         */
     5369        createRouter: function( router ) {
     5370                router.view = new Router({
     5371                        controller: this
     5372                });
     5373        },
     5374        /**
     5375         * @param {Object} options
     5376         */
     5377        createIframeStates: function( options ) {
     5378                var settings = wp.media.view.settings,
     5379                        tabs = settings.tabs,
     5380                        tabUrl = settings.tabUrl,
     5381                        $postId;
     5382
     5383                if ( ! tabs || ! tabUrl ) {
     5384                        return;
     5385                }
     5386
     5387                // Add the post ID to the tab URL if it exists.
     5388                $postId = $('#post_ID');
     5389                if ( $postId.length ) {
     5390                        tabUrl += '&post_id=' + $postId.val();
     5391                }
     5392
     5393                // Generate the tab states.
     5394                _.each( tabs, function( title, id ) {
     5395                        this.state( 'iframe:' + id ).set( _.defaults({
     5396