Make WordPress Core

Ticket #28510: browserify.diff

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