Make WordPress Core

Ticket #31912: 31912.patch

File 31912.patch, 649.8 KB (added by iseulde, 11 years ago)
  • Gruntfile.js

     
    44                SOURCE_DIR = 'src/',
    55                BUILD_DIR = 'build/',
    66                mediaConfig = {},
    7                 mediaBuilds = ['audio-video', 'grid', 'models', 'views'];
     7                mediaBuilds = ['audiovideo', 'grid', 'models', 'views'];
    88
    99        // Load tasks.
    1010        require('matchdep').filterDev(['grunt-*', '!grunt-legacy-util']).forEach( grunt.loadNpmTasks );
     
    1212        grunt.util = require('grunt-legacy-util');
    1313
    1414        mediaBuilds.forEach( function ( build ) {
    15                 var path = SOURCE_DIR + 'wp-includes/js/media/';
     15                var path = SOURCE_DIR + 'wp-includes/js/media';
    1616                mediaConfig[ build ] = { files : {} };
    17                 mediaConfig[ build ].files[ path + build + '.js' ] = [ path + build + '.manifest.js' ];
     17                mediaConfig[ build ].files[ path + '-' + build + '.js' ] = [ path + '/' + build + '.manifest.js' ];
    1818        } );
    1919
    2020        // Project configuration.
     
    6262                                                cwd: SOURCE_DIR,
    6363                                                src: [
    6464                                                        '**',
     65                                                        '!wp-includes/js/media/**',
    6566                                                        '!**/.{svn,git}/**', // Ignore version control directories.
    6667                                                        // Ignore unminified versions of external libs we don't ship:
    6768                                                        '!wp-includes/js/backbone.js',
     
    272273                                options: {
    273274                                        browserify: true
    274275                                },
    275                                 expand: true,
    276                                 cwd: SOURCE_DIR,
    277276                                src: [
    278                                         'wp-includes/js/media/**/*.js',
    279                                         '!wp-includes/js/media/*.js',
    280                                         'wp-includes/js/media/*.manifest.js'
     277                                        SOURCE_DIR + 'wp-includes/js/media/**/*.js'
    281278                                ]
    282279                        },
    283280                        core: {
     
    286283                                src: [
    287284                                        'wp-admin/js/*.js',
    288285                                        'wp-includes/js/*.js',
     286                                        // Built scripts.
     287                                        '!wp-includes/js/media-*',
    289288                                        // WordPress scripts inside directories
    290289                                        'wp-includes/js/jquery/jquery.table-hotkeys.js',
    291290                                        'wp-includes/js/mediaelement/wp-mediaelement.js',
     
    627626                'rtl',
    628627                'cssmin:rtl',
    629628                'cssmin:colors',
    630                 'browserify',
    631629                'uglify:core',
    632                 'uglify:media',
    633630                'uglify:jqueryui',
    634631                'concat:tinymce',
    635632                'compress:tinymce',
  • src/wp-admin/includes/update-core.php

     
    694694'wp-includes/js/jquery/ui/jquery.ui.tabs.min.js',
    695695'wp-includes/js/jquery/ui/jquery.ui.tooltip.min.js',
    696696'wp-includes/js/jquery/ui/jquery.ui.widget.min.js',
    697 'wp-includes/js/tinymce/skins/wordpress/images/dashicon-no-alt.png',
    698 // 4.2
    699 'wp-includes/js/media-audiovideo.js',
    700 'wp-includes/js/media-audiovideo.min.js',
    701 'wp-includes/js/media-grid.js',
    702 'wp-includes/js/media-grid.min.js',
    703 'wp-includes/js/media-models.js',
    704 'wp-includes/js/media-models.min.js',
    705 'wp-includes/js/media-views.js',
    706 'wp-includes/js/media-views.min.js',
     697'wp-includes/js/tinymce/skins/wordpress/images/dashicon-no-alt.png'
    707698);
    708699
    709700/**
  • 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 /*globals wp, _ */
    3 
    4 var media = wp.media,
    5         baseSettings = window._wpmejsSettings || {},
    6         l10n = window._wpMediaViewsL10n || {};
    7 
    8 /**
    9  * @mixin
    10  */
    11 wp.media.mixin = {
    12         mejsSettings: baseSettings,
    13 
    14         removeAllPlayers: function() {
    15                 var p;
    16 
    17                 if ( window.mejs && window.mejs.players ) {
    18                         for ( p in window.mejs.players ) {
    19                                 window.mejs.players[p].pause();
    20                                 this.removePlayer( window.mejs.players[p] );
    21                         }
    22                 }
    23         },
    24 
    25         /**
    26          * Override the MediaElement method for removing a player.
    27          *      MediaElement tries to pull the audio/video tag out of
    28          *      its container and re-add it to the DOM.
    29          */
    30         removePlayer: function(t) {
    31                 var featureIndex, feature;
    32 
    33                 if ( ! t.options ) {
    34                         return;
    35                 }
    36 
    37                 // invoke features cleanup
    38                 for ( featureIndex in t.options.features ) {
    39                         feature = t.options.features[featureIndex];
    40                         if ( t['clean' + feature] ) {
    41                                 try {
    42                                         t['clean' + feature](t);
    43                                 } catch (e) {}
    44                         }
    45                 }
    46 
    47                 if ( ! t.isDynamic ) {
    48                         t.$node.remove();
    49                 }
    50 
    51                 if ( 'native' !== t.media.pluginType ) {
    52                         t.$media.remove();
    53                 }
    54 
    55                 delete window.mejs.players[t.id];
    56 
    57                 t.container.remove();
    58                 t.globalUnbind();
    59                 delete t.node.player;
    60         },
    61 
    62         /**
    63          * Allows any class that has set 'player' to a MediaElementPlayer
    64          *  instance to remove the player when listening to events.
    65          *
    66          *  Examples: modal closes, shortcode properties are removed, etc.
    67          */
    68         unsetPlayers : function() {
    69                 if ( this.players && this.players.length ) {
    70                         _.each( this.players, function (player) {
    71                                 player.pause();
    72                                 wp.media.mixin.removePlayer( player );
    73                         } );
    74                         this.players = [];
    75                 }
    76         }
    77 };
    78 
    79 /**
    80  * Autowire "collection"-type shortcodes
    81  */
    82 wp.media.playlist = new wp.media.collection({
    83         tag: 'playlist',
    84         editTitle : l10n.editPlaylistTitle,
    85         defaults : {
    86                 id: wp.media.view.settings.post.id,
    87                 style: 'light',
    88                 tracklist: true,
    89                 tracknumbers: true,
    90                 images: true,
    91                 artists: true,
    92                 type: 'audio'
    93         }
    94 });
    95 
    96 /**
    97  * Shortcode modeling for audio
    98  *  `edit()` prepares the shortcode for the media modal
    99  *  `shortcode()` builds the new shortcode after update
    100  *
    101  * @namespace
    102  */
    103 wp.media.audio = {
    104         coerce : wp.media.coerce,
    105 
    106         defaults : {
    107                 id : wp.media.view.settings.post.id,
    108                 src : '',
    109                 loop : false,
    110                 autoplay : false,
    111                 preload : 'none',
    112                 width : 400
    113         },
    114 
    115         edit : function( data ) {
    116                 var frame, shortcode = wp.shortcode.next( 'audio', data ).shortcode;
    117 
    118                 frame = wp.media({
    119                         frame: 'audio',
    120                         state: 'audio-details',
    121                         metadata: _.defaults( shortcode.attrs.named, this.defaults )
    122                 });
    123 
    124                 return frame;
    125         },
    126 
    127         shortcode : function( model ) {
    128                 var content;
    129 
    130                 _.each( this.defaults, function( value, key ) {
    131                         model[ key ] = this.coerce( model, key );
    132 
    133                         if ( value === model[ key ] ) {
    134                                 delete model[ key ];
    135                         }
    136                 }, this );
    137 
    138                 content = model.content;
    139                 delete model.content;
    140 
    141                 return new wp.shortcode({
    142                         tag: 'audio',
    143                         attrs: model,
    144                         content: content
    145                 });
    146         }
    147 };
    148 
    149 /**
    150  * Shortcode modeling for video
    151  *  `edit()` prepares the shortcode for the media modal
    152  *  `shortcode()` builds the new shortcode after update
    153  *
    154  * @namespace
    155  */
    156 wp.media.video = {
    157         coerce : wp.media.coerce,
    158 
    159         defaults : {
    160                 id : wp.media.view.settings.post.id,
    161                 src : '',
    162                 poster : '',
    163                 loop : false,
    164                 autoplay : false,
    165                 preload : 'metadata',
    166                 content : '',
    167                 width : 640,
    168                 height : 360
    169         },
    170 
    171         edit : function( data ) {
    172                 var frame,
    173                         shortcode = wp.shortcode.next( 'video', data ).shortcode,
    174                         attrs;
    175 
    176                 attrs = shortcode.attrs.named;
    177                 attrs.content = shortcode.content;
    178 
    179                 frame = wp.media({
    180                         frame: 'video',
    181                         state: 'video-details',
    182                         metadata: _.defaults( attrs, this.defaults )
    183                 });
    184 
    185                 return frame;
    186         },
    187 
    188         shortcode : function( model ) {
    189                 var content;
    190 
    191                 _.each( this.defaults, function( value, key ) {
    192                         model[ key ] = this.coerce( model, key );
    193 
    194                         if ( value === model[ key ] ) {
    195                                 delete model[ key ];
    196                         }
    197                 }, this );
    198 
    199                 content = model.content;
    200                 delete model.content;
    201 
    202                 return new wp.shortcode({
    203                         tag: 'video',
    204                         attrs: model,
    205                         content: content
    206                 });
    207         }
    208 };
    209 
    210 media.model.PostMedia = require( './models/post-media.js' );
    211 media.controller.AudioDetails = require( './controllers/audio-details.js' );
    212 media.controller.VideoDetails = require( './controllers/video-details.js' );
    213 media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' );
    214 media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' );
    215 media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' );
    216 media.view.MediaDetails = require( './views/media-details.js' );
    217 media.view.AudioDetails = require( './views/audio-details.js' );
    218 media.view.VideoDetails = require( './views/video-details.js' );
    219 
    220 },{"./controllers/audio-details.js":2,"./controllers/video-details.js":3,"./models/post-media.js":4,"./views/audio-details.js":5,"./views/frame/audio-details.js":6,"./views/frame/media-details.js":7,"./views/frame/video-details.js":8,"./views/media-details.js":9,"./views/video-details.js":10}],2:[function(require,module,exports){
    221 /*globals wp */
    222 
    223 /**
    224  * wp.media.controller.AudioDetails
    225  *
    226  * The controller for the Audio Details state
    227  *
    228  * @class
    229  * @augments wp.media.controller.State
    230  * @augments Backbone.Model
    231  */
    232 var State = wp.media.controller.State,
    233         l10n = wp.media.view.l10n,
    234         AudioDetails;
    235 
    236 AudioDetails = State.extend({
    237         defaults: {
    238                 id: 'audio-details',
    239                 toolbar: 'audio-details',
    240                 title: l10n.audioDetailsTitle,
    241                 content: 'audio-details',
    242                 menu: 'audio-details',
    243                 router: false,
    244                 priority: 60
    245         },
    246 
    247         initialize: function( options ) {
    248                 this.media = options.media;
    249                 State.prototype.initialize.apply( this, arguments );
    250         }
    251 });
    252 
    253 module.exports = AudioDetails;
    254 
    255 },{}],3:[function(require,module,exports){
    256 /*globals wp */
    257 
    258 /**
    259  * wp.media.controller.VideoDetails
    260  *
    261  * The controller for the Video Details state
    262  *
    263  * @class
    264  * @augments wp.media.controller.State
    265  * @augments Backbone.Model
    266  */
    267 var State = wp.media.controller.State,
    268         l10n = wp.media.view.l10n,
    269         VideoDetails;
    270 
    271 VideoDetails = State.extend({
    272         defaults: {
    273                 id: 'video-details',
    274                 toolbar: 'video-details',
    275                 title: l10n.videoDetailsTitle,
    276                 content: 'video-details',
    277                 menu: 'video-details',
    278                 router: false,
    279                 priority: 60
    280         },
    281 
    282         initialize: function( options ) {
    283                 this.media = options.media;
    284                 State.prototype.initialize.apply( this, arguments );
    285         }
    286 });
    287 
    288 module.exports = VideoDetails;
    289 
    290 },{}],4:[function(require,module,exports){
    291 /*globals wp, Backbone, _ */
    292 
    293 /**
    294  * wp.media.model.PostMedia
    295  *
    296  * Shared model class for audio and video. Updates the model after
    297  *   "Add Audio|Video Source" and "Replace Audio|Video" states return
    298  *
    299  * @class
    300  * @augments Backbone.Model
    301  */
    302 var PostMedia = Backbone.Model.extend({
    303         initialize: function() {
    304                 this.attachment = false;
    305         },
    306 
    307         setSource: function( attachment ) {
    308                 this.attachment = attachment;
    309                 this.extension = attachment.get( 'filename' ).split('.').pop();
    310 
    311                 if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
    312                         this.unset( 'src' );
    313                 }
    314 
    315                 if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
    316                         this.set( this.extension, this.attachment.get( 'url' ) );
    317                 } else {
    318                         this.unset( this.extension );
    319                 }
    320         },
    321 
    322         changeAttachment: function( attachment ) {
    323                 this.setSource( attachment );
    324 
    325                 this.unset( 'src' );
    326                 _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
    327                         this.unset( ext );
    328                 }, this );
    329         }
    330 });
    331 
    332 module.exports = PostMedia;
    333 
    334 },{}],5:[function(require,module,exports){
    335 /*globals wp */
    336 
    337 /**
    338  * wp.media.view.AudioDetails
    339  *
    340  * @class
    341  * @augments wp.media.view.MediaDetails
    342  * @augments wp.media.view.Settings.AttachmentDisplay
    343  * @augments wp.media.view.Settings
    344  * @augments wp.media.View
    345  * @augments wp.Backbone.View
    346  * @augments Backbone.View
    347  */
    348 var MediaDetails = wp.media.view.MediaDetails,
    349         AudioDetails;
    350 
    351 AudioDetails = MediaDetails.extend({
    352         className: 'audio-details',
    353         template:  wp.template('audio-details'),
    354 
    355         setMedia: function() {
    356                 var audio = this.$('.wp-audio-shortcode');
    357 
    358                 if ( audio.find( 'source' ).length ) {
    359                         if ( audio.is(':hidden') ) {
    360                                 audio.show();
    361                         }
    362                         this.media = MediaDetails.prepareSrc( audio.get(0) );
    363                 } else {
    364                         audio.hide();
    365                         this.media = false;
    366                 }
    367 
    368                 return this;
    369         }
    370 });
    371 
    372 module.exports = AudioDetails;
    373 
    374 },{}],6:[function(require,module,exports){
    375 /*globals wp */
    376 
    377 /**
    378  * wp.media.view.MediaFrame.AudioDetails
    379  *
    380  * @class
    381  * @augments wp.media.view.MediaFrame.MediaDetails
    382  * @augments wp.media.view.MediaFrame.Select
    383  * @augments wp.media.view.MediaFrame
    384  * @augments wp.media.view.Frame
    385  * @augments wp.media.View
    386  * @augments wp.Backbone.View
    387  * @augments Backbone.View
    388  * @mixes wp.media.controller.StateMachine
    389  */
    390 var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
    391         MediaLibrary = wp.media.controller.MediaLibrary,
    392 
    393         l10n = wp.media.view.l10n,
    394         AudioDetails;
    395 
    396 AudioDetails = MediaDetails.extend({
    397         defaults: {
    398                 id:      'audio',
    399                 url:     '',
    400                 menu:    'audio-details',
    401                 content: 'audio-details',
    402                 toolbar: 'audio-details',
    403                 type:    'link',
    404                 title:    l10n.audioDetailsTitle,
    405                 priority: 120
    406         },
    407 
    408         initialize: function( options ) {
    409                 options.DetailsView = wp.media.view.AudioDetails;
    410                 options.cancelText = l10n.audioDetailsCancel;
    411                 options.addText = l10n.audioAddSourceTitle;
    412 
    413                 MediaDetails.prototype.initialize.call( this, options );
    414         },
    415 
    416         bindHandlers: function() {
    417                 MediaDetails.prototype.bindHandlers.apply( this, arguments );
    418 
    419                 this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
    420                 this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
    421         },
    422 
    423         createStates: function() {
    424                 this.states.add([
    425                         new wp.media.controller.AudioDetails( {
    426                                 media: this.media
    427                         } ),
    428 
    429                         new MediaLibrary( {
    430                                 type: 'audio',
    431                                 id: 'replace-audio',
    432                                 title: l10n.audioReplaceTitle,
    433                                 toolbar: 'replace-audio',
    434                                 media: this.media,
    435                                 menu: 'audio-details'
    436                         } ),
    437 
    438                         new MediaLibrary( {
    439                                 type: 'audio',
    440                                 id: 'add-audio-source',
    441                                 title: l10n.audioAddSourceTitle,
    442                                 toolbar: 'add-audio-source',
    443                                 media: this.media,
    444                                 menu: false
    445                         } )
    446                 ]);
    447         }
    448 });
    449 
    450 module.exports = AudioDetails;
    451 
    452 },{}],7:[function(require,module,exports){
    453 /*globals wp */
    454 
    455 /**
    456  * wp.media.view.MediaFrame.MediaDetails
    457  *
    458  * @class
    459  * @augments wp.media.view.MediaFrame.Select
    460  * @augments wp.media.view.MediaFrame
    461  * @augments wp.media.view.Frame
    462  * @augments wp.media.View
    463  * @augments wp.Backbone.View
    464  * @augments Backbone.View
    465  * @mixes wp.media.controller.StateMachine
    466  */
    467 var Select = wp.media.view.MediaFrame.Select,
    468         l10n = wp.media.view.l10n,
    469         MediaDetails;
    470 
    471 MediaDetails = Select.extend({
    472         defaults: {
    473                 id:      'media',
    474                 url:     '',
    475                 menu:    'media-details',
    476                 content: 'media-details',
    477                 toolbar: 'media-details',
    478                 type:    'link',
    479                 priority: 120
    480         },
    481 
    482         initialize: function( options ) {
    483                 this.DetailsView = options.DetailsView;
    484                 this.cancelText = options.cancelText;
    485                 this.addText = options.addText;
    486 
    487                 this.media = new wp.media.model.PostMedia( options.metadata );
    488                 this.options.selection = new wp.media.model.Selection( this.media.attachment, { multiple: false } );
    489                 Select.prototype.initialize.apply( this, arguments );
    490         },
    491 
    492         bindHandlers: function() {
    493                 var menu = this.defaults.menu;
    494 
    495                 Select.prototype.bindHandlers.apply( this, arguments );
    496 
    497                 this.on( 'menu:create:' + menu, this.createMenu, this );
    498                 this.on( 'content:render:' + menu, this.renderDetailsContent, this );
    499                 this.on( 'menu:render:' + menu, this.renderMenu, this );
    500                 this.on( 'toolbar:render:' + menu, this.renderDetailsToolbar, this );
    501         },
    502 
    503         renderDetailsContent: function() {
    504                 var view = new this.DetailsView({
    505                         controller: this,
    506                         model: this.state().media,
    507                         attachment: this.state().media.attachment
    508                 }).render();
    509 
    510                 this.content.set( view );
    511         },
    512 
    513         renderMenu: function( view ) {
    514                 var lastState = this.lastState(),
    515                         previous = lastState && lastState.id,
    516                         frame = this;
    517 
    518                 view.set({
    519                         cancel: {
    520                                 text:     this.cancelText,
    521                                 priority: 20,
    522                                 click:    function() {
    523                                         if ( previous ) {
    524                                                 frame.setState( previous );
    525                                         } else {
    526                                                 frame.close();
    527                                         }
    528                                 }
    529                         },
    530                         separateCancel: new wp.media.View({
    531                                 className: 'separator',
    532                                 priority: 40
    533                         })
    534                 });
    535 
    536         },
    537 
    538         setPrimaryButton: function(text, handler) {
    539                 this.toolbar.set( new wp.media.view.Toolbar({
    540                         controller: this,
    541                         items: {
    542                                 button: {
    543                                         style:    'primary',
    544                                         text:     text,
    545                                         priority: 80,
    546                                         click:    function() {
    547                                                 var controller = this.controller;
    548                                                 handler.call( this, controller, controller.state() );
    549                                                 // Restore and reset the default state.
    550                                                 controller.setState( controller.options.state );
    551                                                 controller.reset();
    552                                         }
    553                                 }
    554                         }
    555                 }) );
    556         },
    557 
    558         renderDetailsToolbar: function() {
    559                 this.setPrimaryButton( l10n.update, function( controller, state ) {
    560                         controller.close();
    561                         state.trigger( 'update', controller.media.toJSON() );
    562                 } );
    563         },
    564 
    565         renderReplaceToolbar: function() {
    566                 this.setPrimaryButton( l10n.replace, function( controller, state ) {
    567                         var attachment = state.get( 'selection' ).single();
    568                         controller.media.changeAttachment( attachment );
    569                         state.trigger( 'replace', controller.media.toJSON() );
    570                 } );
    571         },
    572 
    573         renderAddSourceToolbar: function() {
    574                 this.setPrimaryButton( this.addText, function( controller, state ) {
    575                         var attachment = state.get( 'selection' ).single();
    576                         controller.media.setSource( attachment );
    577                         state.trigger( 'add-source', controller.media.toJSON() );
    578                 } );
    579         }
    580 });
    581 
    582 module.exports = MediaDetails;
    583 
    584 },{}],8:[function(require,module,exports){
    585 /*globals wp, _ */
    586 
    587 /**
    588  * wp.media.view.MediaFrame.VideoDetails
    589  *
    590  * @class
    591  * @augments wp.media.view.MediaFrame.MediaDetails
    592  * @augments wp.media.view.MediaFrame.Select
    593  * @augments wp.media.view.MediaFrame
    594  * @augments wp.media.view.Frame
    595  * @augments wp.media.View
    596  * @augments wp.Backbone.View
    597  * @augments Backbone.View
    598  * @mixes wp.media.controller.StateMachine
    599  */
    600 var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
    601         MediaLibrary = wp.media.controller.MediaLibrary,
    602         l10n = wp.media.view.l10n,
    603         VideoDetails;
    604 
    605 VideoDetails = MediaDetails.extend({
    606         defaults: {
    607                 id:      'video',
    608                 url:     '',
    609                 menu:    'video-details',
    610                 content: 'video-details',
    611                 toolbar: 'video-details',
    612                 type:    'link',
    613                 title:    l10n.videoDetailsTitle,
    614                 priority: 120
    615         },
    616 
    617         initialize: function( options ) {
    618                 options.DetailsView = wp.media.view.VideoDetails;
    619                 options.cancelText = l10n.videoDetailsCancel;
    620                 options.addText = l10n.videoAddSourceTitle;
    621 
    622                 MediaDetails.prototype.initialize.call( this, options );
    623         },
    624 
    625         bindHandlers: function() {
    626                 MediaDetails.prototype.bindHandlers.apply( this, arguments );
    627 
    628                 this.on( 'toolbar:render:replace-video', this.renderReplaceToolbar, this );
    629                 this.on( 'toolbar:render:add-video-source', this.renderAddSourceToolbar, this );
    630                 this.on( 'toolbar:render:select-poster-image', this.renderSelectPosterImageToolbar, this );
    631                 this.on( 'toolbar:render:add-track', this.renderAddTrackToolbar, this );
    632         },
    633 
    634         createStates: function() {
    635                 this.states.add([
    636                         new wp.media.controller.VideoDetails({
    637                                 media: this.media
    638                         }),
    639 
    640                         new MediaLibrary( {
    641                                 type: 'video',
    642                                 id: 'replace-video',
    643                                 title: l10n.videoReplaceTitle,
    644                                 toolbar: 'replace-video',
    645                                 media: this.media,
    646                                 menu: 'video-details'
    647                         } ),
    648 
    649                         new MediaLibrary( {
    650                                 type: 'video',
    651                                 id: 'add-video-source',
    652                                 title: l10n.videoAddSourceTitle,
    653                                 toolbar: 'add-video-source',
    654                                 media: this.media,
    655                                 menu: false
    656                         } ),
    657 
    658                         new MediaLibrary( {
    659                                 type: 'image',
    660                                 id: 'select-poster-image',
    661                                 title: l10n.videoSelectPosterImageTitle,
    662                                 toolbar: 'select-poster-image',
    663                                 media: this.media,
    664                                 menu: 'video-details'
    665                         } ),
    666 
    667                         new MediaLibrary( {
    668                                 type: 'text',
    669                                 id: 'add-track',
    670                                 title: l10n.videoAddTrackTitle,
    671                                 toolbar: 'add-track',
    672                                 media: this.media,
    673                                 menu: 'video-details'
    674                         } )
    675                 ]);
    676         },
    677 
    678         renderSelectPosterImageToolbar: function() {
    679                 this.setPrimaryButton( l10n.videoSelectPosterImageTitle, function( controller, state ) {
    680                         var urls = [], attachment = state.get( 'selection' ).single();
    681 
    682                         controller.media.set( 'poster', attachment.get( 'url' ) );
    683                         state.trigger( 'set-poster-image', controller.media.toJSON() );
    684 
    685                         _.each( wp.media.view.settings.embedExts, function (ext) {
    686                                 if ( controller.media.get( ext ) ) {
    687                                         urls.push( controller.media.get( ext ) );
    688                                 }
    689                         } );
    690 
    691                         wp.ajax.send( 'set-attachment-thumbnail', {
    692                                 data : {
    693                                         urls: urls,
    694                                         thumbnail_id: attachment.get( 'id' )
    695                                 }
    696                         } );
    697                 } );
    698         },
    699 
    700         renderAddTrackToolbar: function() {
    701                 this.setPrimaryButton( l10n.videoAddTrackTitle, function( controller, state ) {
    702                         var attachment = state.get( 'selection' ).single(),
    703                                 content = controller.media.get( 'content' );
    704 
    705                         if ( -1 === content.indexOf( attachment.get( 'url' ) ) ) {
    706                                 content += [
    707                                         '<track srclang="en" label="English"kind="subtitles" src="',
    708                                         attachment.get( 'url' ),
    709                                         '" />'
    710                                 ].join('');
    711 
    712                                 controller.media.set( 'content', content );
    713                         }
    714                         state.trigger( 'add-track', controller.media.toJSON() );
    715                 } );
    716         }
    717 });
    718 
    719 module.exports = VideoDetails;
    720 
    721 },{}],9:[function(require,module,exports){
    722 /*global wp, jQuery, _, MediaElementPlayer */
    723 
    724 /**
    725  * wp.media.view.MediaDetails
    726  *
    727  * @class
    728  * @augments wp.media.view.Settings.AttachmentDisplay
    729  * @augments wp.media.view.Settings
    730  * @augments wp.media.View
    731  * @augments wp.Backbone.View
    732  * @augments Backbone.View
    733  */
    734 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    735         $ = jQuery,
    736         MediaDetails;
    737 
    738 MediaDetails = AttachmentDisplay.extend({
    739         initialize: function() {
    740                 _.bindAll(this, 'success');
    741                 this.players = [];
    742                 this.listenTo( this.controller, 'close', wp.media.mixin.unsetPlayers );
    743                 this.on( 'ready', this.setPlayer );
    744                 this.on( 'media:setting:remove', wp.media.mixin.unsetPlayers, this );
    745                 this.on( 'media:setting:remove', this.render );
    746                 this.on( 'media:setting:remove', this.setPlayer );
    747                 this.events = _.extend( this.events, {
    748                         'click .remove-setting' : 'removeSetting',
    749                         'change .content-track' : 'setTracks',
    750                         'click .remove-track' : 'setTracks',
    751                         'click .add-media-source' : 'addSource'
    752                 } );
    753 
    754                 AttachmentDisplay.prototype.initialize.apply( this, arguments );
    755         },
    756 
    757         prepare: function() {
    758                 return _.defaults({
    759                         model: this.model.toJSON()
    760                 }, this.options );
    761         },
    762 
    763         /**
    764          * Remove a setting's UI when the model unsets it
    765          *
    766          * @fires wp.media.view.MediaDetails#media:setting:remove
    767          *
    768          * @param {Event} e
    769          */
    770         removeSetting : function(e) {
    771                 var wrap = $( e.currentTarget ).parent(), setting;
    772                 setting = wrap.find( 'input' ).data( 'setting' );
    773 
    774                 if ( setting ) {
    775                         this.model.unset( setting );
    776                         this.trigger( 'media:setting:remove', this );
    777                 }
    778 
    779                 wrap.remove();
    780         },
    781 
    782         /**
    783          *
    784          * @fires wp.media.view.MediaDetails#media:setting:remove
    785          */
    786         setTracks : function() {
    787                 var tracks = '';
    788 
    789                 _.each( this.$('.content-track'), function(track) {
    790                         tracks += $( track ).val();
    791                 } );
    792 
    793                 this.model.set( 'content', tracks );
    794                 this.trigger( 'media:setting:remove', this );
    795         },
    796 
    797         addSource : function( e ) {
    798                 this.controller.lastMime = $( e.currentTarget ).data( 'mime' );
    799                 this.controller.setState( 'add-' + this.controller.defaults.id + '-source' );
    800         },
    801 
    802         loadPlayer: function () {
    803                 this.players.push( new MediaElementPlayer( this.media, this.settings ) );
    804                 this.scriptXhr = false;
    805         },
    806 
    807         /**
    808          * @global MediaElementPlayer
    809          */
    810         setPlayer : function() {
    811                 var baseSettings;
    812 
    813                 if ( this.players.length || ! this.media || this.scriptXhr ) {
    814                         return;
    815                 }
    816 
    817                 if ( this.model.get( 'src' ).indexOf( 'vimeo' ) > -1 && ! ( 'Froogaloop' in window ) ) {
    818                         baseSettings = wp.media.mixin.mejsSettings;
    819                         this.scriptXhr = $.getScript( baseSettings.pluginPath + 'froogaloop.min.js', _.bind( this.loadPlayer, this ) );
    820                 } else {
    821                         this.loadPlayer();
    822                 }
    823         },
    824 
    825         /**
    826          * @abstract
    827          */
    828         setMedia : function() {
    829                 return this;
    830         },
    831 
    832         success : function(mejs) {
    833                 var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay;
    834 
    835                 if ( 'flash' === mejs.pluginType && autoplay ) {
    836                         mejs.addEventListener( 'canplay', function() {
    837                                 mejs.play();
    838                         }, false );
    839                 }
    840 
    841                 this.mejs = mejs;
    842         },
    843 
    844         /**
    845          * @returns {media.view.MediaDetails} Returns itself to allow chaining
    846          */
    847         render: function() {
    848                 AttachmentDisplay.prototype.render.apply( this, arguments );
    849 
    850                 setTimeout( _.bind( function() {
    851                         this.resetFocus();
    852                 }, this ), 10 );
    853 
    854                 this.settings = _.defaults( {
    855                         success : this.success
    856                 }, wp.media.mixin.mejsSettings );
    857 
    858                 return this.setMedia();
    859         },
    860 
    861         resetFocus: function() {
    862                 this.$( '.embed-media-settings' ).scrollTop( 0 );
    863         }
    864 }, {
    865         instances : 0,
    866         /**
    867          * When multiple players in the DOM contain the same src, things get weird.
    868          *
    869          * @param {HTMLElement} elem
    870          * @returns {HTMLElement}
    871          */
    872         prepareSrc : function( elem ) {
    873                 var i = MediaDetails.instances++;
    874                 _.each( $( elem ).find( 'source' ), function( source ) {
    875                         source.src = [
    876                                 source.src,
    877                                 source.src.indexOf('?') > -1 ? '&' : '?',
    878                                 '_=',
    879                                 i
    880                         ].join('');
    881                 } );
    882 
    883                 return elem;
    884         }
    885 });
    886 
    887 module.exports = MediaDetails;
    888 
    889 },{}],10:[function(require,module,exports){
    890 /*globals wp */
    891 
    892 /**
    893  * wp.media.view.VideoDetails
    894  *
    895  * @class
    896  * @augments wp.media.view.MediaDetails
    897  * @augments wp.media.view.Settings.AttachmentDisplay
    898  * @augments wp.media.view.Settings
    899  * @augments wp.media.View
    900  * @augments wp.Backbone.View
    901  * @augments Backbone.View
    902  */
    903 var MediaDetails = wp.media.view.MediaDetails,
    904         VideoDetails;
    905 
    906 VideoDetails = MediaDetails.extend({
    907         className: 'video-details',
    908         template:  wp.template('video-details'),
    909 
    910         setMedia: function() {
    911                 var video = this.$('.wp-video-shortcode');
    912 
    913                 if ( video.find( 'source' ).length ) {
    914                         if ( video.is(':hidden') ) {
    915                                 video.show();
    916                         }
    917 
    918                         if ( ! video.hasClass( 'youtube-video' ) && ! video.hasClass( 'vimeo-video' ) ) {
    919                                 this.media = MediaDetails.prepareSrc( video.get(0) );
    920                         } else {
    921                                 this.media = video.get(0);
    922                         }
    923                 } else {
    924                         video.hide();
    925                         this.media = false;
    926                 }
    927 
    928                 return this;
    929         }
    930 });
    931 
    932 module.exports = VideoDetails;
    933 
    934 },{}]},{},[1]);
  • src/wp-includes/js/media/audio-video.manifest.js

     
    1 /*globals wp, _ */
    2 
    3 var media = wp.media,
    4         baseSettings = window._wpmejsSettings || {},
    5         l10n = window._wpMediaViewsL10n || {};
    6 
    7 /**
    8  * @mixin
    9  */
    10 wp.media.mixin = {
    11         mejsSettings: baseSettings,
    12 
    13         removeAllPlayers: function() {
    14                 var p;
    15 
    16                 if ( window.mejs && window.mejs.players ) {
    17                         for ( p in window.mejs.players ) {
    18                                 window.mejs.players[p].pause();
    19                                 this.removePlayer( window.mejs.players[p] );
    20                         }
    21                 }
    22         },
    23 
    24         /**
    25          * Override the MediaElement method for removing a player.
    26          *      MediaElement tries to pull the audio/video tag out of
    27          *      its container and re-add it to the DOM.
    28          */
    29         removePlayer: function(t) {
    30                 var featureIndex, feature;
    31 
    32                 if ( ! t.options ) {
    33                         return;
    34                 }
    35 
    36                 // invoke features cleanup
    37                 for ( featureIndex in t.options.features ) {
    38                         feature = t.options.features[featureIndex];
    39                         if ( t['clean' + feature] ) {
    40                                 try {
    41                                         t['clean' + feature](t);
    42                                 } catch (e) {}
    43                         }
    44                 }
    45 
    46                 if ( ! t.isDynamic ) {
    47                         t.$node.remove();
    48                 }
    49 
    50                 if ( 'native' !== t.media.pluginType ) {
    51                         t.$media.remove();
    52                 }
    53 
    54                 delete window.mejs.players[t.id];
    55 
    56                 t.container.remove();
    57                 t.globalUnbind();
    58                 delete t.node.player;
    59         },
    60 
    61         /**
    62          * Allows any class that has set 'player' to a MediaElementPlayer
    63          *  instance to remove the player when listening to events.
    64          *
    65          *  Examples: modal closes, shortcode properties are removed, etc.
    66          */
    67         unsetPlayers : function() {
    68                 if ( this.players && this.players.length ) {
    69                         _.each( this.players, function (player) {
    70                                 player.pause();
    71                                 wp.media.mixin.removePlayer( player );
    72                         } );
    73                         this.players = [];
    74                 }
    75         }
    76 };
    77 
    78 /**
    79  * Autowire "collection"-type shortcodes
    80  */
    81 wp.media.playlist = new wp.media.collection({
    82         tag: 'playlist',
    83         editTitle : l10n.editPlaylistTitle,
    84         defaults : {
    85                 id: wp.media.view.settings.post.id,
    86                 style: 'light',
    87                 tracklist: true,
    88                 tracknumbers: true,
    89                 images: true,
    90                 artists: true,
    91                 type: 'audio'
    92         }
    93 });
    94 
    95 /**
    96  * Shortcode modeling for audio
    97  *  `edit()` prepares the shortcode for the media modal
    98  *  `shortcode()` builds the new shortcode after update
    99  *
    100  * @namespace
    101  */
    102 wp.media.audio = {
    103         coerce : wp.media.coerce,
    104 
    105         defaults : {
    106                 id : wp.media.view.settings.post.id,
    107                 src : '',
    108                 loop : false,
    109                 autoplay : false,
    110                 preload : 'none',
    111                 width : 400
    112         },
    113 
    114         edit : function( data ) {
    115                 var frame, shortcode = wp.shortcode.next( 'audio', data ).shortcode;
    116 
    117                 frame = wp.media({
    118                         frame: 'audio',
    119                         state: 'audio-details',
    120                         metadata: _.defaults( shortcode.attrs.named, this.defaults )
    121                 });
    122 
    123                 return frame;
    124         },
    125 
    126         shortcode : function( model ) {
    127                 var content;
    128 
    129                 _.each( this.defaults, function( value, key ) {
    130                         model[ key ] = this.coerce( model, key );
    131 
    132                         if ( value === model[ key ] ) {
    133                                 delete model[ key ];
    134                         }
    135                 }, this );
    136 
    137                 content = model.content;
    138                 delete model.content;
    139 
    140                 return new wp.shortcode({
    141                         tag: 'audio',
    142                         attrs: model,
    143                         content: content
    144                 });
    145         }
    146 };
    147 
    148 /**
    149  * Shortcode modeling for video
    150  *  `edit()` prepares the shortcode for the media modal
    151  *  `shortcode()` builds the new shortcode after update
    152  *
    153  * @namespace
    154  */
    155 wp.media.video = {
    156         coerce : wp.media.coerce,
    157 
    158         defaults : {
    159                 id : wp.media.view.settings.post.id,
    160                 src : '',
    161                 poster : '',
    162                 loop : false,
    163                 autoplay : false,
    164                 preload : 'metadata',
    165                 content : '',
    166                 width : 640,
    167                 height : 360
    168         },
    169 
    170         edit : function( data ) {
    171                 var frame,
    172                         shortcode = wp.shortcode.next( 'video', data ).shortcode,
    173                         attrs;
    174 
    175                 attrs = shortcode.attrs.named;
    176                 attrs.content = shortcode.content;
    177 
    178                 frame = wp.media({
    179                         frame: 'video',
    180                         state: 'video-details',
    181                         metadata: _.defaults( attrs, this.defaults )
    182                 });
    183 
    184                 return frame;
    185         },
    186 
    187         shortcode : function( model ) {
    188                 var content;
    189 
    190                 _.each( this.defaults, function( value, key ) {
    191                         model[ key ] = this.coerce( model, key );
    192 
    193                         if ( value === model[ key ] ) {
    194                                 delete model[ key ];
    195                         }
    196                 }, this );
    197 
    198                 content = model.content;
    199                 delete model.content;
    200 
    201                 return new wp.shortcode({
    202                         tag: 'video',
    203                         attrs: model,
    204                         content: content
    205                 });
    206         }
    207 };
    208 
    209 media.model.PostMedia = require( './models/post-media.js' );
    210 media.controller.AudioDetails = require( './controllers/audio-details.js' );
    211 media.controller.VideoDetails = require( './controllers/video-details.js' );
    212 media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' );
    213 media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' );
    214 media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' );
    215 media.view.MediaDetails = require( './views/media-details.js' );
    216 media.view.AudioDetails = require( './views/audio-details.js' );
    217 media.view.VideoDetails = require( './views/video-details.js' );
  • src/wp-includes/js/media/audiovideo.manifest.js

     
     1/*globals wp, _ */
     2
     3var media = wp.media,
     4        baseSettings = window._wpmejsSettings || {},
     5        l10n = window._wpMediaViewsL10n || {};
     6
     7/**
     8 * @mixin
     9 */
     10wp.media.mixin = {
     11        mejsSettings: baseSettings,
     12
     13        removeAllPlayers: function() {
     14                var p;
     15
     16                if ( window.mejs && window.mejs.players ) {
     17                        for ( p in window.mejs.players ) {
     18                                window.mejs.players[p].pause();
     19                                this.removePlayer( window.mejs.players[p] );
     20                        }
     21                }
     22        },
     23
     24        /**
     25         * Override the MediaElement method for removing a player.
     26         *      MediaElement tries to pull the audio/video tag out of
     27         *      its container and re-add it to the DOM.
     28         */
     29        removePlayer: function(t) {
     30                var featureIndex, feature;
     31
     32                if ( ! t.options ) {
     33                        return;
     34                }
     35
     36                // invoke features cleanup
     37                for ( featureIndex in t.options.features ) {
     38                        feature = t.options.features[featureIndex];
     39                        if ( t['clean' + feature] ) {
     40                                try {
     41                                        t['clean' + feature](t);
     42                                } catch (e) {}
     43                        }
     44                }
     45
     46                if ( ! t.isDynamic ) {
     47                        t.$node.remove();
     48                }
     49
     50                if ( 'native' !== t.media.pluginType ) {
     51                        t.$media.remove();
     52                }
     53
     54                delete window.mejs.players[t.id];
     55
     56                t.container.remove();
     57                t.globalUnbind();
     58                delete t.node.player;
     59        },
     60
     61        /**
     62         * Allows any class that has set 'player' to a MediaElementPlayer
     63         *  instance to remove the player when listening to events.
     64         *
     65         *  Examples: modal closes, shortcode properties are removed, etc.
     66         */
     67        unsetPlayers : function() {
     68                if ( this.players && this.players.length ) {
     69                        _.each( this.players, function (player) {
     70                                player.pause();
     71                                wp.media.mixin.removePlayer( player );
     72                        } );
     73                        this.players = [];
     74                }
     75        }
     76};
     77
     78/**
     79 * Autowire "collection"-type shortcodes
     80 */
     81wp.media.playlist = new wp.media.collection({
     82        tag: 'playlist',
     83        editTitle : l10n.editPlaylistTitle,
     84        defaults : {
     85                id: wp.media.view.settings.post.id,
     86                style: 'light',
     87                tracklist: true,
     88                tracknumbers: true,
     89                images: true,
     90                artists: true,
     91                type: 'audio'
     92        }
     93});
     94
     95/**
     96 * Shortcode modeling for audio
     97 *  `edit()` prepares the shortcode for the media modal
     98 *  `shortcode()` builds the new shortcode after update
     99 *
     100 * @namespace
     101 */
     102wp.media.audio = {
     103        coerce : wp.media.coerce,
     104
     105        defaults : {
     106                id : wp.media.view.settings.post.id,
     107                src : '',
     108                loop : false,
     109                autoplay : false,
     110                preload : 'none',
     111                width : 400
     112        },
     113
     114        edit : function( data ) {
     115                var frame, shortcode = wp.shortcode.next( 'audio', data ).shortcode;
     116
     117                frame = wp.media({
     118                        frame: 'audio',
     119                        state: 'audio-details',
     120                        metadata: _.defaults( shortcode.attrs.named, this.defaults )
     121                });
     122
     123                return frame;
     124        },
     125
     126        shortcode : function( model ) {
     127                var content;
     128
     129                _.each( this.defaults, function( value, key ) {
     130                        model[ key ] = this.coerce( model, key );
     131
     132                        if ( value === model[ key ] ) {
     133                                delete model[ key ];
     134                        }
     135                }, this );
     136
     137                content = model.content;
     138                delete model.content;
     139
     140                return new wp.shortcode({
     141                        tag: 'audio',
     142                        attrs: model,
     143                        content: content
     144                });
     145        }
     146};
     147
     148/**
     149 * Shortcode modeling for video
     150 *  `edit()` prepares the shortcode for the media modal
     151 *  `shortcode()` builds the new shortcode after update
     152 *
     153 * @namespace
     154 */
     155wp.media.video = {
     156        coerce : wp.media.coerce,
     157
     158        defaults : {
     159                id : wp.media.view.settings.post.id,
     160                src : '',
     161                poster : '',
     162                loop : false,
     163                autoplay : false,
     164                preload : 'metadata',
     165                content : '',
     166                width : 640,
     167                height : 360
     168        },
     169
     170        edit : function( data ) {
     171                var frame,
     172                        shortcode = wp.shortcode.next( 'video', data ).shortcode,
     173                        attrs;
     174
     175                attrs = shortcode.attrs.named;
     176                attrs.content = shortcode.content;
     177
     178                frame = wp.media({
     179                        frame: 'video',
     180                        state: 'video-details',
     181                        metadata: _.defaults( attrs, this.defaults )
     182                });
     183
     184                return frame;
     185        },
     186
     187        shortcode : function( model ) {
     188                var content;
     189
     190                _.each( this.defaults, function( value, key ) {
     191                        model[ key ] = this.coerce( model, key );
     192
     193                        if ( value === model[ key ] ) {
     194                                delete model[ key ];
     195                        }
     196                }, this );
     197
     198                content = model.content;
     199                delete model.content;
     200
     201                return new wp.shortcode({
     202                        tag: 'video',
     203                        attrs: model,
     204                        content: content
     205                });
     206        }
     207};
     208
     209media.model.PostMedia = require( './models/post-media.js' );
     210media.controller.AudioDetails = require( './controllers/audio-details.js' );
     211media.controller.VideoDetails = require( './controllers/video-details.js' );
     212media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' );
     213media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' );
     214media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' );
     215media.view.MediaDetails = require( './views/media-details.js' );
     216media.view.AudioDetails = require( './views/audio-details.js' );
     217media.view.VideoDetails = require( './views/video-details.js' );
  • src/wp-includes/js/media/grid.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 /*globals wp */
    3 
    4 /**
    5  * wp.media.controller.EditAttachmentMetadata
    6  *
    7  * A state for editing an attachment's metadata.
    8  *
    9  * @class
    10  * @augments wp.media.controller.State
    11  * @augments Backbone.Model
    12  */
    13 var l10n = wp.media.view.l10n,
    14         EditAttachmentMetadata;
    15 
    16 EditAttachmentMetadata = wp.media.controller.State.extend({
    17         defaults: {
    18                 id:      'edit-attachment',
    19                 // Title string passed to the frame's title region view.
    20                 title:   l10n.attachmentDetails,
    21                 // Region mode defaults.
    22                 content: 'edit-metadata',
    23                 menu:    false,
    24                 toolbar: false,
    25                 router:  false
    26         }
    27 });
    28 
    29 module.exports = EditAttachmentMetadata;
    30 
    31 },{}],2:[function(require,module,exports){
    32 /*globals wp */
    33 
    34 var media = wp.media;
    35 
    36 media.controller.EditAttachmentMetadata = require( './controllers/edit-attachment-metadata.js' );
    37 media.view.MediaFrame.Manage = require( './views/frame/manage.js' );
    38 media.view.Attachment.Details.TwoColumn = require( './views/attachment/details-two-column.js' );
    39 media.view.MediaFrame.Manage.Router = require( './routers/manage.js' );
    40 media.view.EditImage.Details = require( './views/edit-image-details.js' );
    41 media.view.MediaFrame.EditAttachments = require( './views/frame/edit-attachments.js' );
    42 media.view.SelectModeToggleButton = require( './views/button/select-mode-toggle.js' );
    43 media.view.DeleteSelectedButton = require( './views/button/delete-selected.js' );
    44 media.view.DeleteSelectedPermanentlyButton = require( './views/button/delete-selected-permanently.js' );
    45 
    46 },{"./controllers/edit-attachment-metadata.js":1,"./routers/manage.js":3,"./views/attachment/details-two-column.js":4,"./views/button/delete-selected-permanently.js":5,"./views/button/delete-selected.js":6,"./views/button/select-mode-toggle.js":7,"./views/edit-image-details.js":8,"./views/frame/edit-attachments.js":9,"./views/frame/manage.js":10}],3:[function(require,module,exports){
    47 /*globals wp, Backbone */
    48 
    49 /**
    50  * wp.media.view.MediaFrame.Manage.Router
    51  *
    52  * A router for handling the browser history and application state.
    53  *
    54  * @class
    55  * @augments Backbone.Router
    56  */
    57 var Router = Backbone.Router.extend({
    58         routes: {
    59                 'upload.php?item=:slug':    'showItem',
    60                 'upload.php?search=:query': 'search'
    61         },
    62 
    63         // Map routes against the page URL
    64         baseUrl: function( url ) {
    65                 return 'upload.php' + url;
    66         },
    67 
    68         // Respond to the search route by filling the search field and trigggering the input event
    69         search: function( query ) {
    70                 jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
    71         },
    72 
    73         // Show the modal with a specific item
    74         showItem: function( query ) {
    75                 var media = wp.media,
    76                         library = media.frame.state().get('library'),
    77                         item;
    78 
    79                 // Trigger the media frame to open the correct item
    80                 item = library.findWhere( { id: parseInt( query, 10 ) } );
    81                 if ( item ) {
    82                         media.frame.trigger( 'edit:attachment', item );
    83                 } else {
    84                         item = media.attachment( query );
    85                         media.frame.listenTo( item, 'change', function( model ) {
    86                                 media.frame.stopListening( item );
    87                                 media.frame.trigger( 'edit:attachment', model );
    88                         } );
    89                         item.fetch();
    90                 }
    91         }
    92 });
    93 
    94 module.exports = Router;
    95 
    96 },{}],4:[function(require,module,exports){
    97 /*globals wp */
    98 
    99 /**
    100  * wp.media.view.Attachment.Details.TwoColumn
    101  *
    102  * A similar view to media.view.Attachment.Details
    103  * for use in the Edit Attachment modal.
    104  *
    105  * @class
    106  * @augments wp.media.view.Attachment.Details
    107  * @augments wp.media.view.Attachment
    108  * @augments wp.media.View
    109  * @augments wp.Backbone.View
    110  * @augments Backbone.View
    111  */
    112 var Details = wp.media.view.Attachment.Details,
    113         TwoColumn;
    114 
    115 TwoColumn = Details.extend({
    116         template: wp.template( 'attachment-details-two-column' ),
    117 
    118         editAttachment: function( event ) {
    119                 event.preventDefault();
    120                 this.controller.content.mode( 'edit-image' );
    121         },
    122 
    123         /**
    124          * Noop this from parent class, doesn't apply here.
    125          */
    126         toggleSelectionHandler: function() {},
    127 
    128         render: function() {
    129                 Details.prototype.render.apply( this, arguments );
    130 
    131                 wp.media.mixin.removeAllPlayers();
    132                 this.$( 'audio, video' ).each( function (i, elem) {
    133                         var el = wp.media.view.MediaDetails.prepareSrc( elem );
    134                         new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
    135                 } );
    136         }
    137 });
    138 
    139 module.exports = TwoColumn;
    140 
    141 },{}],5:[function(require,module,exports){
    142 /*globals wp */
    143 
    144 /**
    145  * wp.media.view.DeleteSelectedPermanentlyButton
    146  *
    147  * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
    148  *
    149  * @class
    150  * @augments wp.media.view.DeleteSelectedButton
    151  * @augments wp.media.view.Button
    152  * @augments wp.media.View
    153  * @augments wp.Backbone.View
    154  * @augments Backbone.View
    155  */
    156 var Button = wp.media.view.Button,
    157         DeleteSelected = wp.media.view.DeleteSelectedButton,
    158         DeleteSelectedPermanently;
    159 
    160 DeleteSelectedPermanently = DeleteSelected.extend({
    161         initialize: function() {
    162                 DeleteSelected.prototype.initialize.apply( this, arguments );
    163                 this.listenTo( this.controller, 'select:activate', this.selectActivate );
    164                 this.listenTo( this.controller, 'select:deactivate', this.selectDeactivate );
    165         },
    166 
    167         filterChange: function( model ) {
    168                 this.canShow = ( 'trash' === model.get( 'status' ) );
    169         },
    170 
    171         selectActivate: function() {
    172                 this.toggleDisabled();
    173                 this.$el.toggleClass( 'hidden', ! this.canShow );
    174         },
    175 
    176         selectDeactivate: function() {
    177                 this.toggleDisabled();
    178                 this.$el.addClass( 'hidden' );
    179         },
    180 
    181         render: function() {
    182                 Button.prototype.render.apply( this, arguments );
    183                 this.selectActivate();
    184                 return this;
    185         }
    186 });
    187 
    188 module.exports = DeleteSelectedPermanently;
    189 
    190 },{}],6:[function(require,module,exports){
    191 /*globals wp */
    192 
    193 /**
    194  * wp.media.view.DeleteSelectedButton
    195  *
    196  * A button that handles bulk Delete/Trash logic
    197  *
    198  * @class
    199  * @augments wp.media.view.Button
    200  * @augments wp.media.View
    201  * @augments wp.Backbone.View
    202  * @augments Backbone.View
    203  */
    204 var Button = wp.media.view.Button,
    205         l10n = wp.media.view.l10n,
    206         DeleteSelected;
    207 
    208 DeleteSelected = Button.extend({
    209         initialize: function() {
    210                 Button.prototype.initialize.apply( this, arguments );
    211                 if ( this.options.filters ) {
    212                         this.listenTo( this.options.filters.model, 'change', this.filterChange );
    213                 }
    214                 this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled );
    215         },
    216 
    217         filterChange: function( model ) {
    218                 if ( 'trash' === model.get( 'status' ) ) {
    219                         this.model.set( 'text', l10n.untrashSelected );
    220                 } else if ( wp.media.view.settings.mediaTrash ) {
    221                         this.model.set( 'text', l10n.trashSelected );
    222                 } else {
    223                         this.model.set( 'text', l10n.deleteSelected );
    224                 }
    225         },
    226 
    227         toggleDisabled: function() {
    228                 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
    229         },
    230 
    231         render: function() {
    232                 Button.prototype.render.apply( this, arguments );
    233                 if ( this.controller.isModeActive( 'select' ) ) {
    234                         this.$el.addClass( 'delete-selected-button' );
    235                 } else {
    236                         this.$el.addClass( 'delete-selected-button hidden' );
    237                 }
    238                 this.toggleDisabled();
    239                 return this;
    240         }
    241 });
    242 
    243 module.exports = DeleteSelected;
    244 
    245 },{}],7:[function(require,module,exports){
    246 /*globals wp */
    247 
    248 /**
    249  * wp.media.view.SelectModeToggleButton
    250  *
    251  * @class
    252  * @augments wp.media.view.Button
    253  * @augments wp.media.View
    254  * @augments wp.Backbone.View
    255  * @augments Backbone.View
    256  */
    257 var Button = wp.media.view.Button,
    258         l10n = wp.media.view.l10n,
    259         SelectModeToggle;
    260 
    261 SelectModeToggle = Button.extend({
    262         initialize: function() {
    263                 Button.prototype.initialize.apply( this, arguments );
    264                 this.listenTo( this.controller, 'select:activate select:deactivate', this.toggleBulkEditHandler );
    265                 this.listenTo( this.controller, 'selection:action:done', this.back );
    266         },
    267 
    268         back: function () {
    269                 this.controller.deactivateMode( 'select' ).activateMode( 'edit' );
    270         },
    271 
    272         click: function() {
    273                 Button.prototype.click.apply( this, arguments );
    274                 if ( this.controller.isModeActive( 'select' ) ) {
    275                         this.back();
    276                 } else {
    277                         this.controller.deactivateMode( 'edit' ).activateMode( 'select' );
    278                 }
    279         },
    280 
    281         render: function() {
    282                 Button.prototype.render.apply( this, arguments );
    283                 this.$el.addClass( 'select-mode-toggle-button' );
    284                 return this;
    285         },
    286 
    287         toggleBulkEditHandler: function() {
    288                 var toolbar = this.controller.content.get().toolbar, children;
    289 
    290                 children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' );
    291 
    292                 // TODO: the Frame should be doing all of this.
    293                 if ( this.controller.isModeActive( 'select' ) ) {
    294                         this.model.set( 'text', l10n.cancelSelection );
    295                         children.not( '.media-button' ).hide();
    296                         this.$el.show();
    297                         toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' );
    298                 } else {
    299                         this.model.set( 'text', l10n.bulkSelect );
    300                         this.controller.content.get().$el.removeClass( 'fixed' );
    301                         toolbar.$el.css( 'width', '' );
    302                         toolbar.$( '.delete-selected-button' ).addClass( 'hidden' );
    303                         children.not( '.spinner, .media-button' ).show();
    304                         this.controller.state().get( 'selection' ).reset();
    305                 }
    306         }
    307 });
    308 
    309 module.exports = SelectModeToggle;
    310 
    311 },{}],8:[function(require,module,exports){
    312 /*globals wp, _ */
    313 
    314 /**
    315  * wp.media.view.EditImage.Details
    316  *
    317  * @class
    318  * @augments wp.media.view.EditImage
    319  * @augments wp.media.View
    320  * @augments wp.Backbone.View
    321  * @augments Backbone.View
    322  */
    323 var View = wp.media.View,
    324         EditImage = wp.media.view.EditImage,
    325         Details;
    326 
    327 Details = EditImage.extend({
    328         initialize: function( options ) {
    329                 this.editor = window.imageEdit;
    330                 this.frame = options.frame;
    331                 this.controller = options.controller;
    332                 View.prototype.initialize.apply( this, arguments );
    333         },
    334 
    335         back: function() {
    336                 this.frame.content.mode( 'edit-metadata' );
    337         },
    338 
    339         save: function() {
    340                 this.model.fetch().done( _.bind( function() {
    341                         this.frame.content.mode( 'edit-metadata' );
    342                 }, this ) );
    343         }
    344 });
    345 
    346 module.exports = Details;
    347 
    348 },{}],9:[function(require,module,exports){
    349 /*globals wp, _, jQuery */
    350 
    351 /**
    352  * wp.media.view.MediaFrame.EditAttachments
    353  *
    354  * A frame for editing the details of a specific media item.
    355  *
    356  * Opens in a modal by default.
    357  *
    358  * Requires an attachment model to be passed in the options hash under `model`.
    359  *
    360  * @class
    361  * @augments wp.media.view.Frame
    362  * @augments wp.media.View
    363  * @augments wp.Backbone.View
    364  * @augments Backbone.View
    365  * @mixes wp.media.controller.StateMachine
    366  */
    367 var Frame = wp.media.view.Frame,
    368         MediaFrame = wp.media.view.MediaFrame,
    369 
    370         $ = jQuery,
    371         EditAttachments;
    372 
    373 EditAttachments = MediaFrame.extend({
    374 
    375         className: 'edit-attachment-frame',
    376         template:  wp.template( 'edit-attachment-frame' ),
    377         regions:   [ 'title', 'content' ],
    378 
    379         events: {
    380                 'click .left':  'previousMediaItem',
    381                 'click .right': 'nextMediaItem'
    382         },
    383 
    384         initialize: function() {
    385                 Frame.prototype.initialize.apply( this, arguments );
    386 
    387                 _.defaults( this.options, {
    388                         modal: true,
    389                         state: 'edit-attachment'
    390                 });
    391 
    392                 this.controller = this.options.controller;
    393                 this.gridRouter = this.controller.gridRouter;
    394                 this.library = this.options.library;
    395 
    396                 if ( this.options.model ) {
    397                         this.model = this.options.model;
    398                 }
    399 
    400                 this.bindHandlers();
    401                 this.createStates();
    402                 this.createModal();
    403 
    404                 this.title.mode( 'default' );
    405                 this.toggleNav();
    406         },
    407 
    408         bindHandlers: function() {
    409                 // Bind default title creation.
    410                 this.on( 'title:create:default', this.createTitle, this );
    411 
    412                 // Close the modal if the attachment is deleted.
    413                 this.listenTo( this.model, 'change:status destroy', this.close, this );
    414 
    415                 this.on( 'content:create:edit-metadata', this.editMetadataMode, this );
    416                 this.on( 'content:create:edit-image', this.editImageMode, this );
    417                 this.on( 'content:render:edit-image', this.editImageModeRender, this );
    418                 this.on( 'close', this.detach );
    419         },
    420 
    421         createModal: function() {
    422                 // Initialize modal container view.
    423                 if ( this.options.modal ) {
    424                         this.modal = new wp.media.view.Modal({
    425                                 controller: this,
    426                                 title:      this.options.title
    427                         });
    428 
    429                         this.modal.on( 'open', _.bind( function () {
    430                                 $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) );
    431                         }, this ) );
    432 
    433                         // Completely destroy the modal DOM element when closing it.
    434                         this.modal.on( 'close', _.bind( function() {
    435                                 this.modal.remove();
    436                                 $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
    437                                 // Restore the original focus item if possible
    438                                 $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus();
    439                                 this.resetRoute();
    440                         }, this ) );
    441 
    442                         // Set this frame as the modal's content.
    443                         this.modal.content( this );
    444                         this.modal.open();
    445                 }
    446         },
    447 
    448         /**
    449          * Add the default states to the frame.
    450          */
    451         createStates: function() {
    452                 this.states.add([
    453                         new wp.media.controller.EditAttachmentMetadata( { model: this.model } )
    454                 ]);
    455         },
    456 
    457         /**
    458          * Content region rendering callback for the `edit-metadata` mode.
    459          *
    460          * @param {Object} contentRegion Basic object with a `view` property, which
    461          *                               should be set with the proper region view.
    462          */
    463         editMetadataMode: function( contentRegion ) {
    464                 contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({
    465                         controller: this,
    466                         model:      this.model
    467                 });
    468 
    469                 /**
    470                  * Attach a subview to display fields added via the
    471                  * `attachment_fields_to_edit` filter.
    472                  */
    473                 contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({
    474                         controller: this,
    475                         model:      this.model
    476                 }) );
    477 
    478                 // Update browser url when navigating media details
    479                 if ( this.model ) {
    480                         this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
    481                 }
    482         },
    483 
    484         /**
    485          * Render the EditImage view into the frame's content region.
    486          *
    487          * @param {Object} contentRegion Basic object with a `view` property, which
    488          *                               should be set with the proper region view.
    489          */
    490         editImageMode: function( contentRegion ) {
    491                 var editImageController = new wp.media.controller.EditImage( {
    492                         model: this.model,
    493                         frame: this
    494                 } );
    495                 // Noop some methods.
    496                 editImageController._toolbar = function() {};
    497                 editImageController._router = function() {};
    498                 editImageController._menu = function() {};
    499 
    500                 contentRegion.view = new wp.media.view.EditImage.Details( {
    501                         model: this.model,
    502                         frame: this,
    503                         controller: editImageController
    504                 } );
    505         },
    506 
    507         editImageModeRender: function( view ) {
    508                 view.on( 'ready', view.loadEditor );
    509         },
    510 
    511         toggleNav: function() {
    512                 this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() );
    513                 this.$('.right').toggleClass( 'disabled', ! this.hasNext() );
    514         },
    515 
    516         /**
    517          * Rerender the view.
    518          */
    519         rerender: function() {
    520                 // Only rerender the `content` region.
    521                 if ( this.content.mode() !== 'edit-metadata' ) {
    522                         this.content.mode( 'edit-metadata' );
    523                 } else {
    524                         this.content.render();
    525                 }
    526 
    527                 this.toggleNav();
    528         },
    529 
    530         /**
    531          * Click handler to switch to the previous media item.
    532          */
    533         previousMediaItem: function() {
    534                 if ( ! this.hasPrevious() ) {
    535                         this.$( '.left' ).blur();
    536                         return;
    537                 }
    538                 this.model = this.library.at( this.getCurrentIndex() - 1 );
    539                 this.rerender();
    540                 this.$( '.left' ).focus();
    541         },
    542 
    543         /**
    544          * Click handler to switch to the next media item.
    545          */
    546         nextMediaItem: function() {
    547                 if ( ! this.hasNext() ) {
    548                         this.$( '.right' ).blur();
    549                         return;
    550                 }
    551                 this.model = this.library.at( this.getCurrentIndex() + 1 );
    552                 this.rerender();
    553                 this.$( '.right' ).focus();
    554         },
    555 
    556         getCurrentIndex: function() {
    557                 return this.library.indexOf( this.model );
    558         },
    559 
    560         hasNext: function() {
    561                 return ( this.getCurrentIndex() + 1 ) < this.library.length;
    562         },
    563 
    564         hasPrevious: function() {
    565                 return ( this.getCurrentIndex() - 1 ) > -1;
    566         },
    567         /**
    568          * Respond to the keyboard events: right arrow, left arrow, except when
    569          * focus is in a textarea or input field.
    570          */
    571         keyEvent: function( event ) {
    572                 if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) {
    573                         return;
    574                 }
    575 
    576                 // The right arrow key
    577                 if ( 39 === event.keyCode ) {
    578                         this.nextMediaItem();
    579                 }
    580                 // The left arrow key
    581                 if ( 37 === event.keyCode ) {
    582                         this.previousMediaItem();
    583                 }
    584         },
    585 
    586         resetRoute: function() {
    587                 this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) );
    588         }
    589 });
    590 
    591 module.exports = EditAttachments;
    592 
    593 },{}],10:[function(require,module,exports){
    594 /*globals wp, _, Backbone */
    595 
    596 /**
    597  * wp.media.view.MediaFrame.Manage
    598  *
    599  * A generic management frame workflow.
    600  *
    601  * Used in the media grid view.
    602  *
    603  * @class
    604  * @augments wp.media.view.MediaFrame
    605  * @augments wp.media.view.Frame
    606  * @augments wp.media.View
    607  * @augments wp.Backbone.View
    608  * @augments Backbone.View
    609  * @mixes wp.media.controller.StateMachine
    610  */
    611 var MediaFrame = wp.media.view.MediaFrame,
    612         Library = wp.media.controller.Library,
    613 
    614         $ = Backbone.$,
    615         Manage;
    616 
    617 Manage = MediaFrame.extend({
    618         /**
    619          * @global wp.Uploader
    620          */
    621         initialize: function() {
    622                 _.defaults( this.options, {
    623                         title:     '',
    624                         modal:     false,
    625                         selection: [],
    626                         library:   {}, // Options hash for the query to the media library.
    627                         multiple:  'add',
    628                         state:     'library',
    629                         uploader:  true,
    630                         mode:      [ 'grid', 'edit' ]
    631                 });
    632 
    633                 this.$body = $( document.body );
    634                 this.$window = $( window );
    635                 this.$adminBar = $( '#wpadminbar' );
    636                 this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) );
    637                 $( document ).on( 'click', '.add-new-h2', _.bind( this.addNewClickHandler, this ) );
    638 
    639                 // Ensure core and media grid view UI is enabled.
    640                 this.$el.addClass('wp-core-ui');
    641 
    642                 // Force the uploader off if the upload limit has been exceeded or
    643                 // if the browser isn't supported.
    644                 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
    645                         this.options.uploader = false;
    646                 }
    647 
    648                 // Initialize a window-wide uploader.
    649                 if ( this.options.uploader ) {
    650                         this.uploader = new wp.media.view.UploaderWindow({
    651                                 controller: this,
    652                                 uploader: {
    653                                         dropzone:  document.body,
    654                                         container: document.body
    655                                 }
    656                         }).render();
    657                         this.uploader.ready();
    658                         $('body').append( this.uploader.el );
    659 
    660                         this.options.uploader = false;
    661                 }
    662 
    663                 this.gridRouter = new wp.media.view.MediaFrame.Manage.Router();
    664 
    665                 // Call 'initialize' directly on the parent class.
    666                 MediaFrame.prototype.initialize.apply( this, arguments );
    667 
    668                 // Append the frame view directly the supplied container.
    669                 this.$el.appendTo( this.options.container );
    670 
    671                 this.createStates();
    672                 this.bindRegionModeHandlers();
    673                 this.render();
    674                 this.bindSearchHandler();
    675         },
    676 
    677         bindSearchHandler: function() {
    678                 var search = this.$( '#media-search-input' ),
    679                         currentSearch = this.options.container.data( 'search' ),
    680                         searchView = this.browserView.toolbar.get( 'search' ).$el,
    681                         listMode = this.$( '.view-list' ),
    682 
    683                         input  = _.debounce( function (e) {
    684                                 var val = $( e.currentTarget ).val(),
    685                                         url = '';
    686 
    687                                 if ( val ) {
    688                                         url += '?search=' + val;
    689                                 }
    690                                 this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
    691                         }, 1000 );
    692 
    693                 // Update the URL when entering search string (at most once per second)
    694                 search.on( 'input', _.bind( input, this ) );
    695                 searchView.val( currentSearch ).trigger( 'input' );
    696 
    697                 this.gridRouter.on( 'route:search', function () {
    698                         var href = window.location.href;
    699                         if ( href.indexOf( 'mode=' ) > -1 ) {
    700                                 href = href.replace( /mode=[^&]+/g, 'mode=list' );
    701                         } else {
    702                                 href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
    703                         }
    704                         href = href.replace( 'search=', 's=' );
    705                         listMode.prop( 'href', href );
    706                 } );
    707         },
    708 
    709         /**
    710          * Create the default states for the frame.
    711          */
    712         createStates: function() {
    713                 var options = this.options;
    714 
    715                 if ( this.options.states ) {
    716                         return;
    717                 }
    718 
    719                 // Add the default states.
    720                 this.states.add([
    721                         new Library({
    722                                 library:            wp.media.query( options.library ),
    723                                 multiple:           options.multiple,
    724                                 title:              options.title,
    725                                 content:            'browse',
    726                                 toolbar:            'select',
    727                                 contentUserSetting: false,
    728                                 filterable:         'all',
    729                                 autoSelect:         false
    730                         })
    731                 ]);
    732         },
    733 
    734         /**
    735          * Bind region mode activation events to proper handlers.
    736          */
    737         bindRegionModeHandlers: function() {
    738                 this.on( 'content:create:browse', this.browseContent, this );
    739 
    740                 // Handle a frame-level event for editing an attachment.
    741                 this.on( 'edit:attachment', this.openEditAttachmentModal, this );
    742 
    743                 this.on( 'select:activate', this.bindKeydown, this );
    744                 this.on( 'select:deactivate', this.unbindKeydown, this );
    745         },
    746 
    747         handleKeydown: function( e ) {
    748                 if ( 27 === e.which ) {
    749                         e.preventDefault();
    750                         this.deactivateMode( 'select' ).activateMode( 'edit' );
    751                 }
    752         },
    753 
    754         bindKeydown: function() {
    755                 this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) );
    756         },
    757 
    758         unbindKeydown: function() {
    759                 this.$body.off( 'keydown.select' );
    760         },
    761 
    762         fixPosition: function() {
    763                 var $browser, $toolbar;
    764                 if ( ! this.isModeActive( 'select' ) ) {
    765                         return;
    766                 }
    767 
    768                 $browser = this.$('.attachments-browser');
    769                 $toolbar = $browser.find('.media-toolbar');
    770 
    771                 // Offset doesn't appear to take top margin into account, hence +16
    772                 if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) {
    773                         $browser.addClass( 'fixed' );
    774                         $toolbar.css('width', $browser.width() + 'px');
    775                 } else {
    776                         $browser.removeClass( 'fixed' );
    777                         $toolbar.css('width', '');
    778                 }
    779         },
    780 
    781         /**
    782          * Click handler for the `Add New` button.
    783          */
    784         addNewClickHandler: function( event ) {
    785                 event.preventDefault();
    786                 this.trigger( 'toggle:upload:attachment' );
    787         },
    788 
    789         /**
    790          * Open the Edit Attachment modal.
    791          */
    792         openEditAttachmentModal: function( model ) {
    793                 // Create a new EditAttachment frame, passing along the library and the attachment model.
    794                 wp.media( {
    795                         frame:       'edit-attachments',
    796                         controller:  this,
    797                         library:     this.state().get('library'),
    798                         model:       model
    799                 } );
    800         },
    801 
    802         /**
    803          * Create an attachments browser view within the content region.
    804          *
    805          * @param {Object} contentRegion Basic object with a `view` property, which
    806          *                               should be set with the proper region view.
    807          * @this wp.media.controller.Region
    808          */
    809         browseContent: function( contentRegion ) {
    810                 var state = this.state();
    811 
    812                 // Browse our library of attachments.
    813                 this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({
    814                         controller: this,
    815                         collection: state.get('library'),
    816                         selection:  state.get('selection'),
    817                         model:      state,
    818                         sortable:   state.get('sortable'),
    819                         search:     state.get('searchable'),
    820                         filters:    state.get('filterable'),
    821                         date:       state.get('date'),
    822                         display:    state.get('displaySettings'),
    823                         dragInfo:   state.get('dragInfo'),
    824                         sidebar:    'errors',
    825 
    826                         suggestedWidth:  state.get('suggestedWidth'),
    827                         suggestedHeight: state.get('suggestedHeight'),
    828 
    829                         AttachmentView: state.get('AttachmentView'),
    830 
    831                         scrollElement: document
    832                 });
    833                 this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) );
    834 
    835                 this.errors = wp.Uploader.errors;
    836                 this.errors.on( 'add remove reset', this.sidebarVisibility, this );
    837         },
    838 
    839         sidebarVisibility: function() {
    840                 this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length );
    841         },
    842 
    843         bindDeferred: function() {
    844                 if ( ! this.browserView.dfd ) {
    845                         return;
    846                 }
    847                 this.browserView.dfd.done( _.bind( this.startHistory, this ) );
    848         },
    849 
    850         startHistory: function() {
    851                 // Verify pushState support and activate
    852                 if ( window.history && window.history.pushState ) {
    853                         Backbone.history.start( {
    854                                 root: window._wpMediaGridSettings.adminUrl,
    855                                 pushState: true
    856                         } );
    857                 }
    858         }
    859 });
    860 
    861 module.exports = Manage;
    862 
    863 },{}]},{},[2]);
  • src/wp-includes/js/media/models.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 /*globals wp, _, jQuery */
    3 
    4 var $ = jQuery,
    5         Attachment, Attachments, l10n, media;
    6 
    7 window.wp = window.wp || {};
    8 
    9 /**
    10  * Create and return a media frame.
    11  *
    12  * Handles the default media experience.
    13  *
    14  * @param  {object} attributes The properties passed to the main media controller.
    15  * @return {wp.media.view.MediaFrame} A media workflow.
    16  */
    17 media = wp.media = function( attributes ) {
    18         var MediaFrame = media.view.MediaFrame,
    19                 frame;
    20 
    21         if ( ! MediaFrame ) {
    22                 return;
    23         }
    24 
    25         attributes = _.defaults( attributes || {}, {
    26                 frame: 'select'
    27         });
    28 
    29         if ( 'select' === attributes.frame && MediaFrame.Select ) {
    30                 frame = new MediaFrame.Select( attributes );
    31         } else if ( 'post' === attributes.frame && MediaFrame.Post ) {
    32                 frame = new MediaFrame.Post( attributes );
    33         } else if ( 'manage' === attributes.frame && MediaFrame.Manage ) {
    34                 frame = new MediaFrame.Manage( attributes );
    35         } else if ( 'image' === attributes.frame && MediaFrame.ImageDetails ) {
    36                 frame = new MediaFrame.ImageDetails( attributes );
    37         } else if ( 'audio' === attributes.frame && MediaFrame.AudioDetails ) {
    38                 frame = new MediaFrame.AudioDetails( attributes );
    39         } else if ( 'video' === attributes.frame && MediaFrame.VideoDetails ) {
    40                 frame = new MediaFrame.VideoDetails( attributes );
    41         } else if ( 'edit-attachments' === attributes.frame && MediaFrame.EditAttachments ) {
    42                 frame = new MediaFrame.EditAttachments( attributes );
    43         }
    44 
    45         delete attributes.frame;
    46 
    47         media.frame = frame;
    48 
    49         return frame;
    50 };
    51 
    52 _.extend( media, { model: {}, view: {}, controller: {}, frames: {} });
    53 
    54 // Link any localized strings.
    55 l10n = media.model.l10n = window._wpMediaModelsL10n || {};
    56 
    57 // Link any settings.
    58 media.model.settings = l10n.settings || {};
    59 delete l10n.settings;
    60 
    61 Attachment = media.model.Attachment = require( './models/attachment.js' );
    62 Attachments = media.model.Attachments = require( './models/attachments.js' );
    63 
    64 media.model.Query = require( './models/query.js' );
    65 media.model.PostImage = require( './models/post-image.js' );
    66 media.model.Selection = require( './models/selection.js' );
    67 
    68 /**
    69  * ========================================================================
    70  * UTILITIES
    71  * ========================================================================
    72  */
    73 
    74 /**
    75  * A basic equality comparator for Backbone models.
    76  *
    77  * Used to order models within a collection - @see wp.media.model.Attachments.comparator().
    78  *
    79  * @param  {mixed}  a  The primary parameter to compare.
    80  * @param  {mixed}  b  The primary parameter to compare.
    81  * @param  {string} ac The fallback parameter to compare, a's cid.
    82  * @param  {string} bc The fallback parameter to compare, b's cid.
    83  * @return {number}    -1: a should come before b.
    84  *                      0: a and b are of the same rank.
    85  *                      1: b should come before a.
    86  */
    87 media.compare = function( a, b, ac, bc ) {
    88         if ( _.isEqual( a, b ) ) {
    89                 return ac === bc ? 0 : (ac > bc ? -1 : 1);
    90         } else {
    91                 return a > b ? -1 : 1;
    92         }
    93 };
    94 
    95 _.extend( media, {
    96         /**
    97          * media.template( id )
    98          *
    99          * Fetch a JavaScript template for an id, and return a templating function for it.
    100          *
    101          * See wp.template() in `wp-includes/js/wp-util.js`.
    102          *
    103          * @borrows wp.template as template
    104          */
    105         template: wp.template,
    106 
    107         /**
    108          * media.post( [action], [data] )
    109          *
    110          * Sends a POST request to WordPress.
    111          * See wp.ajax.post() in `wp-includes/js/wp-util.js`.
    112          *
    113          * @borrows wp.ajax.post as post
    114          */
    115         post: wp.ajax.post,
    116 
    117         /**
    118          * media.ajax( [action], [options] )
    119          *
    120          * Sends an XHR request to WordPress.
    121          * See wp.ajax.send() in `wp-includes/js/wp-util.js`.
    122          *
    123          * @borrows wp.ajax.send as ajax
    124          */
    125         ajax: wp.ajax.send,
    126 
    127         /**
    128          * Scales a set of dimensions to fit within bounding dimensions.
    129          *
    130          * @param {Object} dimensions
    131          * @returns {Object}
    132          */
    133         fit: function( dimensions ) {
    134                 var width     = dimensions.width,
    135                         height    = dimensions.height,
    136                         maxWidth  = dimensions.maxWidth,
    137                         maxHeight = dimensions.maxHeight,
    138                         constraint;
    139 
    140                 // Compare ratios between the two values to determine which
    141                 // max to constrain by. If a max value doesn't exist, then the
    142                 // opposite side is the constraint.
    143                 if ( ! _.isUndefined( maxWidth ) && ! _.isUndefined( maxHeight ) ) {
    144                         constraint = ( width / height > maxWidth / maxHeight ) ? 'width' : 'height';
    145                 } else if ( _.isUndefined( maxHeight ) ) {
    146                         constraint = 'width';
    147                 } else if (  _.isUndefined( maxWidth ) && height > maxHeight ) {
    148                         constraint = 'height';
    149                 }
    150 
    151                 // If the value of the constrained side is larger than the max,
    152                 // then scale the values. Otherwise return the originals; they fit.
    153                 if ( 'width' === constraint && width > maxWidth ) {
    154                         return {
    155                                 width : maxWidth,
    156                                 height: Math.round( maxWidth * height / width )
    157                         };
    158                 } else if ( 'height' === constraint && height > maxHeight ) {
    159                         return {
    160                                 width : Math.round( maxHeight * width / height ),
    161                                 height: maxHeight
    162                         };
    163                 } else {
    164                         return {
    165                                 width : width,
    166                                 height: height
    167                         };
    168                 }
    169         },
    170         /**
    171          * Truncates a string by injecting an ellipsis into the middle.
    172          * Useful for filenames.
    173          *
    174          * @param {String} string
    175          * @param {Number} [length=30]
    176          * @param {String} [replacement=&hellip;]
    177          * @returns {String} The string, unless length is greater than string.length.
    178          */
    179         truncate: function( string, length, replacement ) {
    180                 length = length || 30;
    181                 replacement = replacement || '&hellip;';
    182 
    183                 if ( string.length <= length ) {
    184                         return string;
    185                 }
    186 
    187                 return string.substr( 0, length / 2 ) + replacement + string.substr( -1 * length / 2 );
    188         }
    189 });
    190 
    191 /**
    192  * ========================================================================
    193  * MODELS
    194  * ========================================================================
    195  */
    196 /**
    197  * wp.media.attachment
    198  *
    199  * @static
    200  * @param {String} id A string used to identify a model.
    201  * @returns {wp.media.model.Attachment}
    202  */
    203 media.attachment = function( id ) {
    204         return Attachment.get( id );
    205 };
    206 
    207 /**
    208  * A collection of all attachments that have been fetched from the server.
    209  *
    210  * @static
    211  * @member {wp.media.model.Attachments}
    212  */
    213 Attachments.all = new Attachments();
    214 
    215 /**
    216  * wp.media.query
    217  *
    218  * Shorthand for creating a new Attachments Query.
    219  *
    220  * @param {object} [props]
    221  * @returns {wp.media.model.Attachments}
    222  */
    223 media.query = function( props ) {
    224         return new Attachments( null, {
    225                 props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } )
    226         });
    227 };
    228 
    229 // Clean up. Prevents mobile browsers caching
    230 $(window).on('unload', function(){
    231         window.wp = null;
    232 });
    233 
    234 },{"./models/attachment.js":2,"./models/attachments.js":3,"./models/post-image.js":4,"./models/query.js":5,"./models/selection.js":6}],2:[function(require,module,exports){
    235 /*globals wp, _, Backbone */
    236 
    237 /**
    238  * wp.media.model.Attachment
    239  *
    240  * @class
    241  * @augments Backbone.Model
    242  */
    243 var $ = Backbone.$,
    244         Attachment;
    245 
    246 Attachment = Backbone.Model.extend({
    247         /**
    248          * Triggered when attachment details change
    249          * Overrides Backbone.Model.sync
    250          *
    251          * @param {string} method
    252          * @param {wp.media.model.Attachment} model
    253          * @param {Object} [options={}]
    254          *
    255          * @returns {Promise}
    256          */
    257         sync: function( method, model, options ) {
    258                 // If the attachment does not yet have an `id`, return an instantly
    259                 // rejected promise. Otherwise, all of our requests will fail.
    260                 if ( _.isUndefined( this.id ) ) {
    261                         return $.Deferred().rejectWith( this ).promise();
    262                 }
    263 
    264                 // Overload the `read` request so Attachment.fetch() functions correctly.
    265                 if ( 'read' === method ) {
    266                         options = options || {};
    267                         options.context = this;
    268                         options.data = _.extend( options.data || {}, {
    269                                 action: 'get-attachment',
    270                                 id: this.id
    271                         });
    272                         return wp.media.ajax( options );
    273 
    274                 // Overload the `update` request so properties can be saved.
    275                 } else if ( 'update' === method ) {
    276                         // If we do not have the necessary nonce, fail immeditately.
    277                         if ( ! this.get('nonces') || ! this.get('nonces').update ) {
    278                                 return $.Deferred().rejectWith( this ).promise();
    279                         }
    280 
    281                         options = options || {};
    282                         options.context = this;
    283 
    284                         // Set the action and ID.
    285                         options.data = _.extend( options.data || {}, {
    286                                 action:  'save-attachment',
    287                                 id:      this.id,
    288                                 nonce:   this.get('nonces').update,
    289                                 post_id: wp.media.model.settings.post.id
    290                         });
    291 
    292                         // Record the values of the changed attributes.
    293                         if ( model.hasChanged() ) {
    294                                 options.data.changes = {};
    295 
    296                                 _.each( model.changed, function( value, key ) {
    297                                         options.data.changes[ key ] = this.get( key );
    298                                 }, this );
    299                         }
    300 
    301                         return wp.media.ajax( options );
    302 
    303                 // Overload the `delete` request so attachments can be removed.
    304                 // This will permanently delete an attachment.
    305                 } else if ( 'delete' === method ) {
    306                         options = options || {};
    307 
    308                         if ( ! options.wait ) {
    309                                 this.destroyed = true;
    310                         }
    311 
    312                         options.context = this;
    313                         options.data = _.extend( options.data || {}, {
    314                                 action:   'delete-post',
    315                                 id:       this.id,
    316                                 _wpnonce: this.get('nonces')['delete']
    317                         });
    318 
    319                         return wp.media.ajax( options ).done( function() {
    320                                 this.destroyed = true;
    321                         }).fail( function() {
    322                                 this.destroyed = false;
    323                         });
    324 
    325                 // Otherwise, fall back to `Backbone.sync()`.
    326                 } else {
    327                         /**
    328                          * Call `sync` directly on Backbone.Model
    329                          */
    330                         return Backbone.Model.prototype.sync.apply( this, arguments );
    331                 }
    332         },
    333         /**
    334          * Convert date strings into Date objects.
    335          *
    336          * @param {Object} resp The raw response object, typically returned by fetch()
    337          * @returns {Object} The modified response object, which is the attributes hash
    338          *    to be set on the model.
    339          */
    340         parse: function( resp ) {
    341                 if ( ! resp ) {
    342                         return resp;
    343                 }
    344 
    345                 resp.date = new Date( resp.date );
    346                 resp.modified = new Date( resp.modified );
    347                 return resp;
    348         },
    349         /**
    350          * @param {Object} data The properties to be saved.
    351          * @param {Object} options Sync options. e.g. patch, wait, success, error.
    352          *
    353          * @this Backbone.Model
    354          *
    355          * @returns {Promise}
    356          */
    357         saveCompat: function( data, options ) {
    358                 var model = this;
    359 
    360                 // If we do not have the necessary nonce, fail immeditately.
    361                 if ( ! this.get('nonces') || ! this.get('nonces').update ) {
    362                         return $.Deferred().rejectWith( this ).promise();
    363                 }
    364 
    365                 return wp.media.post( 'save-attachment-compat', _.defaults({
    366                         id:      this.id,
    367                         nonce:   this.get('nonces').update,
    368                         post_id: wp.media.model.settings.post.id
    369                 }, data ) ).done( function( resp, status, xhr ) {
    370                         model.set( model.parse( resp, xhr ), options );
    371                 });
    372         }
    373 }, {
    374         /**
    375          * Create a new model on the static 'all' attachments collection and return it.
    376          *
    377          * @static
    378          * @param {Object} attrs
    379          * @returns {wp.media.model.Attachment}
    380          */
    381         create: function( attrs ) {
    382                 var Attachments = wp.media.model.Attachments;
    383                 return Attachments.all.push( attrs );
    384         },
    385         /**
    386          * Create a new model on the static 'all' attachments collection and return it.
    387          *
    388          * If this function has already been called for the id,
    389          * it returns the specified attachment.
    390          *
    391          * @static
    392          * @param {string} id A string used to identify a model.
    393          * @param {Backbone.Model|undefined} attachment
    394          * @returns {wp.media.model.Attachment}
    395          */
    396         get: _.memoize( function( id, attachment ) {
    397                 var Attachments = wp.media.model.Attachments;
    398                 return Attachments.all.push( attachment || { id: id } );
    399         })
    400 });
    401 
    402 module.exports = Attachment;
    403 
    404 },{}],3:[function(require,module,exports){
    405 /*globals wp, _, Backbone */
    406 
    407 /**
    408  * wp.media.model.Attachments
    409  *
    410  * A collection of attachments.
    411  *
    412  * This collection has no persistence with the server without supplying
    413  * 'options.props.query = true', which will mirror the collection
    414  * to an Attachments Query collection - @see wp.media.model.Attachments.mirror().
    415  *
    416  * @class
    417  * @augments Backbone.Collection
    418  *
    419  * @param {array}  [models]                Models to initialize with the collection.
    420  * @param {object} [options]               Options hash for the collection.
    421  * @param {string} [options.props]         Options hash for the initial query properties.
    422  * @param {string} [options.props.order]   Initial order (ASC or DESC) for the collection.
    423  * @param {string} [options.props.orderby] Initial attribute key to order the collection by.
    424  * @param {string} [options.props.query]   Whether the collection is linked to an attachments query.
    425  * @param {string} [options.observe]
    426  * @param {string} [options.filters]
    427  *
    428  */
    429 var Attachments = Backbone.Collection.extend({
    430         /**
    431          * @type {wp.media.model.Attachment}
    432          */
    433         model: wp.media.model.Attachment,
    434         /**
    435          * @param {Array} [models=[]] Array of models used to populate the collection.
    436          * @param {Object} [options={}]
    437          */
    438         initialize: function( models, options ) {
    439                 options = options || {};
    440 
    441                 this.props   = new Backbone.Model();
    442                 this.filters = options.filters || {};
    443 
    444                 // Bind default `change` events to the `props` model.
    445                 this.props.on( 'change', this._changeFilteredProps, this );
    446 
    447                 this.props.on( 'change:order',   this._changeOrder,   this );
    448                 this.props.on( 'change:orderby', this._changeOrderby, this );
    449                 this.props.on( 'change:query',   this._changeQuery,   this );
    450 
    451                 this.props.set( _.defaults( options.props || {} ) );
    452 
    453                 if ( options.observe ) {
    454                         this.observe( options.observe );
    455                 }
    456         },
    457         /**
    458          * Sort the collection when the order attribute changes.
    459          *
    460          * @access private
    461          */
    462         _changeOrder: function() {
    463                 if ( this.comparator ) {
    464                         this.sort();
    465                 }
    466         },
    467         /**
    468          * Set the default comparator only when the `orderby` property is set.
    469          *
    470          * @access private
    471          *
    472          * @param {Backbone.Model} model
    473          * @param {string} orderby
    474          */
    475         _changeOrderby: function( model, orderby ) {
    476                 // If a different comparator is defined, bail.
    477                 if ( this.comparator && this.comparator !== Attachments.comparator ) {
    478                         return;
    479                 }
    480 
    481                 if ( orderby && 'post__in' !== orderby ) {
    482                         this.comparator = Attachments.comparator;
    483                 } else {
    484                         delete this.comparator;
    485                 }
    486         },
    487         /**
    488          * If the `query` property is set to true, query the server using
    489          * the `props` values, and sync the results to this collection.
    490          *
    491          * @access private
    492          *
    493          * @param {Backbone.Model} model
    494          * @param {Boolean} query
    495          */
    496         _changeQuery: function( model, query ) {
    497                 if ( query ) {
    498                         this.props.on( 'change', this._requery, this );
    499                         this._requery();
    500                 } else {
    501                         this.props.off( 'change', this._requery, this );
    502                 }
    503         },
    504         /**
    505          * @access private
    506          *
    507          * @param {Backbone.Model} model
    508          */
    509         _changeFilteredProps: function( model ) {
    510                 // If this is a query, updating the collection will be handled by
    511                 // `this._requery()`.
    512                 if ( this.props.get('query') ) {
    513                         return;
    514                 }
    515 
    516                 var changed = _.chain( model.changed ).map( function( t, prop ) {
    517                         var filter = Attachments.filters[ prop ],
    518                                 term = model.get( prop );
    519 
    520                         if ( ! filter ) {
    521                                 return;
    522                         }
    523 
    524                         if ( term && ! this.filters[ prop ] ) {
    525                                 this.filters[ prop ] = filter;
    526                         } else if ( ! term && this.filters[ prop ] === filter ) {
    527                                 delete this.filters[ prop ];
    528                         } else {
    529                                 return;
    530                         }
    531 
    532                         // Record the change.
    533                         return true;
    534                 }, this ).any().value();
    535 
    536                 if ( ! changed ) {
    537                         return;
    538                 }
    539 
    540                 // If no `Attachments` model is provided to source the searches
    541                 // from, then automatically generate a source from the existing
    542                 // models.
    543                 if ( ! this._source ) {
    544                         this._source = new Attachments( this.models );
    545                 }
    546 
    547                 this.reset( this._source.filter( this.validator, this ) );
    548         },
    549 
    550         validateDestroyed: false,
    551         /**
    552          * Checks whether an attachment is valid.
    553          *
    554          * @param {wp.media.model.Attachment} attachment
    555          * @returns {Boolean}
    556          */
    557         validator: function( attachment ) {
    558                 if ( ! this.validateDestroyed && attachment.destroyed ) {
    559                         return false;
    560                 }
    561                 return _.all( this.filters, function( filter ) {
    562                         return !! filter.call( this, attachment );
    563                 }, this );
    564         },
    565         /**
    566          * Add or remove an attachment to the collection depending on its validity.
    567          *
    568          * @param {wp.media.model.Attachment} attachment
    569          * @param {Object} options
    570          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
    571          */
    572         validate: function( attachment, options ) {
    573                 var valid = this.validator( attachment ),
    574                         hasAttachment = !! this.get( attachment.cid );
    575 
    576                 if ( ! valid && hasAttachment ) {
    577                         this.remove( attachment, options );
    578                 } else if ( valid && ! hasAttachment ) {
    579                         this.add( attachment, options );
    580                 }
    581 
    582                 return this;
    583         },
    584 
    585         /**
    586          * Add or remove all attachments from another collection depending on each one's validity.
    587          *
    588          * @param {wp.media.model.Attachments} attachments
    589          * @param {object} [options={}]
    590          *
    591          * @fires wp.media.model.Attachments#reset
    592          *
    593          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
    594          */
    595         validateAll: function( attachments, options ) {
    596                 options = options || {};
    597 
    598                 _.each( attachments.models, function( attachment ) {
    599                         this.validate( attachment, { silent: true });
    600                 }, this );
    601 
    602                 if ( ! options.silent ) {
    603                         this.trigger( 'reset', this, options );
    604                 }
    605                 return this;
    606         },
    607         /**
    608          * Start observing another attachments collection change events
    609          * and replicate them on this collection.
    610          *
    611          * @param {wp.media.model.Attachments} The attachments collection to observe.
    612          * @returns {wp.media.model.Attachments} Returns itself to allow chaining.
    613          */
    614         observe: function( attachments ) {
    615                 this.observers = this.observers || [];
    616                 this.observers.push( attachments );
    617 
    618                 attachments.on( 'add change remove', this._validateHandler, this );
    619                 attachments.on( 'reset', this._validateAllHandler, this );
    620                 this.validateAll( attachments );
    621                 return this;
    622         },
    623         /**
    624          * Stop replicating collection change events from another attachments collection.
    625          *
    626          * @param {wp.media.model.Attachments} The attachments collection to stop observing.
    627          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
    628          */
    629         unobserve: function( attachments ) {
    630                 if ( attachments ) {
    631                         attachments.off( null, null, this );
    632                         this.observers = _.without( this.observers, attachments );
    633 
    634                 } else {
    635                         _.each( this.observers, function( attachments ) {
    636                                 attachments.off( null, null, this );
    637                         }, this );
    638                         delete this.observers;
    639                 }
    640 
    641                 return this;
    642         },
    643         /**
    644          * @access private
    645          *
    646          * @param {wp.media.model.Attachments} attachment
    647          * @param {wp.media.model.Attachments} attachments
    648          * @param {Object} options
    649          *
    650          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
    651          */
    652         _validateHandler: function( attachment, attachments, options ) {
    653                 // If we're not mirroring this `attachments` collection,
    654                 // only retain the `silent` option.
    655                 options = attachments === this.mirroring ? options : {
    656                         silent: options && options.silent
    657                 };
    658 
    659                 return this.validate( attachment, options );
    660         },
    661         /**
    662          * @access private
    663          *
    664          * @param {wp.media.model.Attachments} attachments
    665          * @param {Object} options
    666          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
    667          */
    668         _validateAllHandler: function( attachments, options ) {
    669                 return this.validateAll( attachments, options );
    670         },
    671         /**
    672          * Start mirroring another attachments collection, clearing out any models already
    673          * in the collection.
    674          *
    675          * @param {wp.media.model.Attachments} The attachments collection to mirror.
    676          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
    677          */
    678         mirror: function( attachments ) {
    679                 if ( this.mirroring && this.mirroring === attachments ) {
    680                         return this;
    681                 }
    682 
    683                 this.unmirror();
    684                 this.mirroring = attachments;
    685 
    686                 // Clear the collection silently. A `reset` event will be fired
    687                 // when `observe()` calls `validateAll()`.
    688                 this.reset( [], { silent: true } );
    689                 this.observe( attachments );
    690 
    691                 return this;
    692         },
    693         /**
    694          * Stop mirroring another attachments collection.
    695          */
    696         unmirror: function() {
    697                 if ( ! this.mirroring ) {
    698                         return;
    699                 }
    700 
    701                 this.unobserve( this.mirroring );
    702                 delete this.mirroring;
    703         },
    704         /**
    705          * Retrive more attachments from the server for the collection.
    706          *
    707          * Only works if the collection is mirroring a Query Attachments collection,
    708          * and forwards to its `more` method. This collection class doesn't have
    709          * server persistence by itself.
    710          *
    711          * @param {object} options
    712          * @returns {Promise}
    713          */
    714         more: function( options ) {
    715                 var deferred = jQuery.Deferred(),
    716                         mirroring = this.mirroring,
    717                         attachments = this;
    718 
    719                 if ( ! mirroring || ! mirroring.more ) {
    720                         return deferred.resolveWith( this ).promise();
    721                 }
    722                 // If we're mirroring another collection, forward `more` to
    723                 // the mirrored collection. Account for a race condition by
    724                 // checking if we're still mirroring that collection when
    725                 // the request resolves.
    726                 mirroring.more( options ).done( function() {
    727                         if ( this === attachments.mirroring ) {
    728                                 deferred.resolveWith( this );
    729                         }
    730                 });
    731 
    732                 return deferred.promise();
    733         },
    734         /**
    735          * Whether there are more attachments that haven't been sync'd from the server
    736          * that match the collection's query.
    737          *
    738          * Only works if the collection is mirroring a Query Attachments collection,
    739          * and forwards to its `hasMore` method. This collection class doesn't have
    740          * server persistence by itself.
    741          *
    742          * @returns {boolean}
    743          */
    744         hasMore: function() {
    745                 return this.mirroring ? this.mirroring.hasMore() : false;
    746         },
    747         /**
    748          * A custom AJAX-response parser.
    749          *
    750          * See trac ticket #24753
    751          *
    752          * @param {Object|Array} resp The raw response Object/Array.
    753          * @param {Object} xhr
    754          * @returns {Array} The array of model attributes to be added to the collection
    755          */
    756         parse: function( resp, xhr ) {
    757                 if ( ! _.isArray( resp ) ) {
    758                         resp = [resp];
    759                 }
    760 
    761                 return _.map( resp, function( attrs ) {
    762                         var id, attachment, newAttributes;
    763 
    764                         if ( attrs instanceof Backbone.Model ) {
    765                                 id = attrs.get( 'id' );
    766                                 attrs = attrs.attributes;
    767                         } else {
    768                                 id = attrs.id;
    769                         }
    770 
    771                         attachment = wp.media.model.Attachment.get( id );
    772                         newAttributes = attachment.parse( attrs, xhr );
    773 
    774                         if ( ! _.isEqual( attachment.attributes, newAttributes ) ) {
    775                                 attachment.set( newAttributes );
    776                         }
    777 
    778                         return attachment;
    779                 });
    780         },
    781         /**
    782          * If the collection is a query, create and mirror an Attachments Query collection.
    783          *
    784          * @access private
    785          */
    786         _requery: function( refresh ) {
    787                 var props;
    788                 if ( this.props.get('query') ) {
    789                         props = this.props.toJSON();
    790                         props.cache = ( true !== refresh );
    791                         this.mirror( wp.media.model.Query.get( props ) );
    792                 }
    793         },
    794         /**
    795          * If this collection is sorted by `menuOrder`, recalculates and saves
    796          * the menu order to the database.
    797          *
    798          * @returns {undefined|Promise}
    799          */
    800         saveMenuOrder: function() {
    801                 if ( 'menuOrder' !== this.props.get('orderby') ) {
    802                         return;
    803                 }
    804 
    805                 // Removes any uploading attachments, updates each attachment's
    806                 // menu order, and returns an object with an { id: menuOrder }
    807                 // mapping to pass to the request.
    808                 var attachments = this.chain().filter( function( attachment ) {
    809                         return ! _.isUndefined( attachment.id );
    810                 }).map( function( attachment, index ) {
    811                         // Indices start at 1.
    812                         index = index + 1;
    813                         attachment.set( 'menuOrder', index );
    814                         return [ attachment.id, index ];
    815                 }).object().value();
    816 
    817                 if ( _.isEmpty( attachments ) ) {
    818                         return;
    819                 }
    820 
    821                 return wp.media.post( 'save-attachment-order', {
    822                         nonce:       wp.media.model.settings.post.nonce,
    823                         post_id:     wp.media.model.settings.post.id,
    824                         attachments: attachments
    825                 });
    826         }
    827 }, {
    828         /**
    829          * A function to compare two attachment models in an attachments collection.
    830          *
    831          * Used as the default comparator for instances of wp.media.model.Attachments
    832          * and its subclasses. @see wp.media.model.Attachments._changeOrderby().
    833          *
    834          * @static
    835          *
    836          * @param {Backbone.Model} a
    837          * @param {Backbone.Model} b
    838          * @param {Object} options
    839          * @returns {Number} -1 if the first model should come before the second,
    840          *    0 if they are of the same rank and
    841          *    1 if the first model should come after.
    842          */
    843         comparator: function( a, b, options ) {
    844                 var key   = this.props.get('orderby'),
    845                         order = this.props.get('order') || 'DESC',
    846                         ac    = a.cid,
    847                         bc    = b.cid;
    848 
    849                 a = a.get( key );
    850                 b = b.get( key );
    851 
    852                 if ( 'date' === key || 'modified' === key ) {
    853                         a = a || new Date();
    854                         b = b || new Date();
    855                 }
    856 
    857                 // If `options.ties` is set, don't enforce the `cid` tiebreaker.
    858                 if ( options && options.ties ) {
    859                         ac = bc = null;
    860                 }
    861 
    862                 return ( 'DESC' === order ) ? wp.media.compare( a, b, ac, bc ) : wp.media.compare( b, a, bc, ac );
    863         },
    864         /**
    865          * @namespace
    866          */
    867         filters: {
    868                 /**
    869                  * @static
    870                  * Note that this client-side searching is *not* equivalent
    871                  * to our server-side searching.
    872                  *
    873                  * @param {wp.media.model.Attachment} attachment
    874                  *
    875                  * @this wp.media.model.Attachments
    876                  *
    877                  * @returns {Boolean}
    878                  */
    879                 search: function( attachment ) {
    880                         if ( ! this.props.get('search') ) {
    881                                 return true;
    882                         }
    883 
    884                         return _.any(['title','filename','description','caption','name'], function( key ) {
    885                                 var value = attachment.get( key );
    886                                 return value && -1 !== value.search( this.props.get('search') );
    887                         }, this );
    888                 },
    889                 /**
    890                  * @static
    891                  * @param {wp.media.model.Attachment} attachment
    892                  *
    893                  * @this wp.media.model.Attachments
    894                  *
    895                  * @returns {Boolean}
    896                  */
    897                 type: function( attachment ) {
    898                         var type = this.props.get('type');
    899                         return ! type || -1 !== type.indexOf( attachment.get('type') );
    900                 },
    901                 /**
    902                  * @static
    903                  * @param {wp.media.model.Attachment} attachment
    904                  *
    905                  * @this wp.media.model.Attachments
    906                  *
    907                  * @returns {Boolean}
    908                  */
    909                 uploadedTo: function( attachment ) {
    910                         var uploadedTo = this.props.get('uploadedTo');
    911                         if ( _.isUndefined( uploadedTo ) ) {
    912                                 return true;
    913                         }
    914 
    915                         return uploadedTo === attachment.get('uploadedTo');
    916                 },
    917                 /**
    918                  * @static
    919                  * @param {wp.media.model.Attachment} attachment
    920                  *
    921                  * @this wp.media.model.Attachments
    922                  *
    923                  * @returns {Boolean}
    924                  */
    925                 status: function( attachment ) {
    926                         var status = this.props.get('status');
    927                         if ( _.isUndefined( status ) ) {
    928                                 return true;
    929                         }
    930 
    931                         return status === attachment.get('status');
    932                 }
    933         }
    934 });
    935 
    936 module.exports = Attachments;
    937 
    938 },{}],4:[function(require,module,exports){
    939 /*globals Backbone */
    940 
    941 /**
    942  * wp.media.model.PostImage
    943  *
    944  * An instance of an image that's been embedded into a post.
    945  *
    946  * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails.
    947  *
    948  * @class
    949  * @augments Backbone.Model
    950  *
    951  * @param {int} [attributes]               Initial model attributes.
    952  * @param {int} [attributes.attachment_id] ID of the attachment.
    953  **/
    954 var PostImage = Backbone.Model.extend({
    955 
    956         initialize: function( attributes ) {
    957                 var Attachment = wp.media.model.Attachment;
    958                 this.attachment = false;
    959 
    960                 if ( attributes.attachment_id ) {
    961                         this.attachment = Attachment.get( attributes.attachment_id );
    962                         if ( this.attachment.get( 'url' ) ) {
    963                                 this.dfd = jQuery.Deferred();
    964                                 this.dfd.resolve();
    965                         } else {
    966                                 this.dfd = this.attachment.fetch();
    967                         }
    968                         this.bindAttachmentListeners();
    969                 }
    970 
    971                 // keep url in sync with changes to the type of link
    972                 this.on( 'change:link', this.updateLinkUrl, this );
    973                 this.on( 'change:size', this.updateSize, this );
    974 
    975                 this.setLinkTypeFromUrl();
    976                 this.setAspectRatio();
    977 
    978                 this.set( 'originalUrl', attributes.url );
    979         },
    980 
    981         bindAttachmentListeners: function() {
    982                 this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );
    983                 this.listenTo( this.attachment, 'sync', this.setAspectRatio );
    984                 this.listenTo( this.attachment, 'change', this.updateSize );
    985         },
    986 
    987         changeAttachment: function( attachment, props ) {
    988                 this.stopListening( this.attachment );
    989                 this.attachment = attachment;
    990                 this.bindAttachmentListeners();
    991 
    992                 this.set( 'attachment_id', this.attachment.get( 'id' ) );
    993                 this.set( 'caption', this.attachment.get( 'caption' ) );
    994                 this.set( 'alt', this.attachment.get( 'alt' ) );
    995                 this.set( 'size', props.get( 'size' ) );
    996                 this.set( 'align', props.get( 'align' ) );
    997                 this.set( 'link', props.get( 'link' ) );
    998                 this.updateLinkUrl();
    999                 this.updateSize();
    1000         },
    1001 
    1002         setLinkTypeFromUrl: function() {
    1003                 var linkUrl = this.get( 'linkUrl' ),
    1004                         type;
    1005 
    1006                 if ( ! linkUrl ) {
    1007                         this.set( 'link', 'none' );
    1008                         return;
    1009                 }
    1010 
    1011                 // default to custom if there is a linkUrl
    1012                 type = 'custom';
    1013 
    1014                 if ( this.attachment ) {
    1015                         if ( this.attachment.get( 'url' ) === linkUrl ) {
    1016                                 type = 'file';
    1017                         } else if ( this.attachment.get( 'link' ) === linkUrl ) {
    1018                                 type = 'post';
    1019                         }
    1020                 } else {
    1021                         if ( this.get( 'url' ) === linkUrl ) {
    1022                                 type = 'file';
    1023                         }
    1024                 }
    1025 
    1026                 this.set( 'link', type );
    1027         },
    1028 
    1029         updateLinkUrl: function() {
    1030                 var link = this.get( 'link' ),
    1031                         url;
    1032 
    1033                 switch( link ) {
    1034                         case 'file':
    1035                                 if ( this.attachment ) {
    1036                                         url = this.attachment.get( 'url' );
    1037                                 } else {
    1038                                         url = this.get( 'url' );
    1039                                 }
    1040                                 this.set( 'linkUrl', url );
    1041                                 break;
    1042                         case 'post':
    1043                                 this.set( 'linkUrl', this.attachment.get( 'link' ) );
    1044                                 break;
    1045                         case 'none':
    1046                                 this.set( 'linkUrl', '' );
    1047                                 break;
    1048                 }
    1049         },
    1050 
    1051         updateSize: function() {
    1052                 var size;
    1053 
    1054                 if ( ! this.attachment ) {
    1055                         return;
    1056                 }
    1057 
    1058                 if ( this.get( 'size' ) === 'custom' ) {
    1059                         this.set( 'width', this.get( 'customWidth' ) );
    1060                         this.set( 'height', this.get( 'customHeight' ) );
    1061                         this.set( 'url', this.get( 'originalUrl' ) );
    1062                         return;
    1063                 }
    1064 
    1065                 size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ];
    1066 
    1067                 if ( ! size ) {
    1068                         return;
    1069                 }
    1070 
    1071                 this.set( 'url', size.url );
    1072                 this.set( 'width', size.width );
    1073                 this.set( 'height', size.height );
    1074         },
    1075 
    1076         setAspectRatio: function() {
    1077                 var full;
    1078 
    1079                 if ( this.attachment && this.attachment.get( 'sizes' ) ) {
    1080                         full = this.attachment.get( 'sizes' ).full;
    1081 
    1082                         if ( full ) {
    1083                                 this.set( 'aspectRatio', full.width / full.height );
    1084                                 return;
    1085                         }
    1086                 }
    1087 
    1088                 this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) );
    1089         }
    1090 });
    1091 
    1092 module.exports = PostImage;
    1093 
    1094 },{}],5:[function(require,module,exports){
    1095 /*globals wp, _ */
    1096 
    1097 /**
    1098  * wp.media.model.Query
    1099  *
    1100  * A collection of attachments that match the supplied query arguments.
    1101  *
    1102  * Note: Do NOT change this.args after the query has been initialized.
    1103  *       Things will break.
    1104  *
    1105  * @class
    1106  * @augments wp.media.model.Attachments
    1107  * @augments Backbone.Collection
    1108  *
    1109  * @param {array}  [models]                      Models to initialize with the collection.
    1110  * @param {object} [options]                     Options hash.
    1111  * @param {object} [options.args]                Attachments query arguments.
    1112  * @param {object} [options.args.posts_per_page]
    1113  */
    1114 var Attachments = wp.media.model.Attachments,
    1115         Query;
    1116 
    1117 Query = Attachments.extend({
    1118         /**
    1119          * @global wp.Uploader
    1120          *
    1121          * @param {array}  [models=[]]  Array of initial models to populate the collection.
    1122          * @param {object} [options={}]
    1123          */
    1124         initialize: function( models, options ) {
    1125                 var allowed;
    1126 
    1127                 options = options || {};
    1128                 Attachments.prototype.initialize.apply( this, arguments );
    1129 
    1130                 this.args     = options.args;
    1131                 this._hasMore = true;
    1132                 this.created  = new Date();
    1133 
    1134                 this.filters.order = function( attachment ) {
    1135                         var orderby = this.props.get('orderby'),
    1136                                 order = this.props.get('order');
    1137 
    1138                         if ( ! this.comparator ) {
    1139                                 return true;
    1140                         }
    1141 
    1142                         // We want any items that can be placed before the last
    1143                         // item in the set. If we add any items after the last
    1144                         // item, then we can't guarantee the set is complete.
    1145                         if ( this.length ) {
    1146                                 return 1 !== this.comparator( attachment, this.last(), { ties: true });
    1147 
    1148                         // Handle the case where there are no items yet and
    1149                         // we're sorting for recent items. In that case, we want
    1150                         // changes that occurred after we created the query.
    1151                         } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) {
    1152                                 return attachment.get( orderby ) >= this.created;
    1153 
    1154                         // If we're sorting by menu order and we have no items,
    1155                         // accept any items that have the default menu order (0).
    1156                         } else if ( 'ASC' === order && 'menuOrder' === orderby ) {
    1157                                 return attachment.get( orderby ) === 0;
    1158                         }
    1159 
    1160                         // Otherwise, we don't want any items yet.
    1161                         return false;
    1162                 };
    1163 
    1164                 // Observe the central `wp.Uploader.queue` collection to watch for
    1165                 // new matches for the query.
    1166                 //
    1167                 // Only observe when a limited number of query args are set. There
    1168                 // are no filters for other properties, so observing will result in
    1169                 // false positives in those queries.
    1170                 allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ];
    1171                 if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) {
    1172                         this.observe( wp.Uploader.queue );
    1173                 }
    1174         },
    1175         /**
    1176          * Whether there are more attachments that haven't been sync'd from the server
    1177          * that match the collection's query.
    1178          *
    1179          * @returns {boolean}
    1180          */
    1181         hasMore: function() {
    1182                 return this._hasMore;
    1183         },
    1184         /**
    1185          * Fetch more attachments from the server for the collection.
    1186          *
    1187          * @param   {object}  [options={}]
    1188          * @returns {Promise}
    1189          */
    1190         more: function( options ) {
    1191                 var query = this;
    1192 
    1193                 // If there is already a request pending, return early with the Deferred object.
    1194                 if ( this._more && 'pending' === this._more.state() ) {
    1195                         return this._more;
    1196                 }
    1197 
    1198                 if ( ! this.hasMore() ) {
    1199                         return jQuery.Deferred().resolveWith( this ).promise();
    1200                 }
    1201 
    1202                 options = options || {};
    1203                 options.remove = false;
    1204 
    1205                 return this._more = this.fetch( options ).done( function( resp ) {
    1206                         if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) {
    1207                                 query._hasMore = false;
    1208                         }
    1209                 });
    1210         },
    1211         /**
    1212          * Overrides Backbone.Collection.sync
    1213          * Overrides wp.media.model.Attachments.sync
    1214          *
    1215          * @param {String} method
    1216          * @param {Backbone.Model} model
    1217          * @param {Object} [options={}]
    1218          * @returns {Promise}
    1219          */
    1220         sync: function( method, model, options ) {
    1221                 var args, fallback;
    1222 
    1223                 // Overload the read method so Attachment.fetch() functions correctly.
    1224                 if ( 'read' === method ) {
    1225                         options = options || {};
    1226                         options.context = this;
    1227                         options.data = _.extend( options.data || {}, {
    1228                                 action:  'query-attachments',
    1229                                 post_id: wp.media.model.settings.post.id
    1230                         });
    1231 
    1232                         // Clone the args so manipulation is non-destructive.
    1233                         args = _.clone( this.args );
    1234 
    1235                         // Determine which page to query.
    1236                         if ( -1 !== args.posts_per_page ) {
    1237                                 args.paged = Math.round( this.length / args.posts_per_page ) + 1;
    1238                         }
    1239 
    1240                         options.data.query = args;
    1241                         return wp.media.ajax( options );
    1242 
    1243                 // Otherwise, fall back to Backbone.sync()
    1244                 } else {
    1245                         /**
    1246                          * Call wp.media.model.Attachments.sync or Backbone.sync
    1247                          */
    1248                         fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone;
    1249                         return fallback.sync.apply( this, arguments );
    1250                 }
    1251         }
    1252 }, {
    1253         /**
    1254          * @readonly
    1255          */
    1256         defaultProps: {
    1257                 orderby: 'date',
    1258                 order:   'DESC'
    1259         },
    1260         /**
    1261          * @readonly
    1262          */
    1263         defaultArgs: {
    1264                 posts_per_page: 40
    1265         },
    1266         /**
    1267          * @readonly
    1268          */
    1269         orderby: {
    1270                 allowed:  [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ],
    1271                 /**
    1272                  * A map of JavaScript orderby values to their WP_Query equivalents.
    1273                  * @type {Object}
    1274                  */
    1275                 valuemap: {
    1276                         'id':         'ID',
    1277                         'uploadedTo': 'parent',
    1278                         'menuOrder':  'menu_order ID'
    1279                 }
    1280         },
    1281         /**
    1282          * A map of JavaScript query properties to their WP_Query equivalents.
    1283          *
    1284          * @readonly
    1285          */
    1286         propmap: {
    1287                 'search':    's',
    1288                 'type':      'post_mime_type',
    1289                 'perPage':   'posts_per_page',
    1290                 'menuOrder': 'menu_order',
    1291                 'uploadedTo': 'post_parent',
    1292                 'status':     'post_status',
    1293                 'include':    'post__in',
    1294                 'exclude':    'post__not_in'
    1295         },
    1296         /**
    1297          * Creates and returns an Attachments Query collection given the properties.
    1298          *
    1299          * Caches query objects and reuses where possible.
    1300          *
    1301          * @static
    1302          * @method
    1303          *
    1304          * @param {object} [props]
    1305          * @param {Object} [props.cache=true]   Whether to use the query cache or not.
    1306          * @param {Object} [props.order]
    1307          * @param {Object} [props.orderby]
    1308          * @param {Object} [props.include]
    1309          * @param {Object} [props.exclude]
    1310          * @param {Object} [props.s]
    1311          * @param {Object} [props.post_mime_type]
    1312          * @param {Object} [props.posts_per_page]
    1313          * @param {Object} [props.menu_order]
    1314          * @param {Object} [props.post_parent]
    1315          * @param {Object} [props.post_status]
    1316          * @param {Object} [options]
    1317          *
    1318          * @returns {wp.media.model.Query} A new Attachments Query collection.
    1319          */
    1320         get: (function(){
    1321                 /**
    1322                  * @static
    1323                  * @type Array
    1324                  */
    1325                 var queries = [];
    1326 
    1327                 /**
    1328                  * @returns {Query}
    1329                  */
    1330                 return function( props, options ) {
    1331                         var args     = {},
    1332                                 orderby  = Query.orderby,
    1333                                 defaults = Query.defaultProps,
    1334                                 query,
    1335                                 cache    = !! props.cache || _.isUndefined( props.cache );
    1336 
    1337                         // Remove the `query` property. This isn't linked to a query,
    1338                         // this *is* the query.
    1339                         delete props.query;
    1340                         delete props.cache;
    1341 
    1342                         // Fill default args.
    1343                         _.defaults( props, defaults );
    1344 
    1345                         // Normalize the order.
    1346                         props.order = props.order.toUpperCase();
    1347                         if ( 'DESC' !== props.order && 'ASC' !== props.order ) {
    1348                                 props.order = defaults.order.toUpperCase();
    1349                         }
    1350 
    1351                         // Ensure we have a valid orderby value.
    1352                         if ( ! _.contains( orderby.allowed, props.orderby ) ) {
    1353                                 props.orderby = defaults.orderby;
    1354                         }
    1355 
    1356                         _.each( [ 'include', 'exclude' ], function( prop ) {
    1357                                 if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) {
    1358                                         props[ prop ] = [ props[ prop ] ];
    1359                                 }
    1360                         } );
    1361 
    1362                         // Generate the query `args` object.
    1363                         // Correct any differing property names.
    1364                         _.each( props, function( value, prop ) {
    1365                                 if ( _.isNull( value ) ) {
    1366                                         return;
    1367                                 }
    1368 
    1369                                 args[ Query.propmap[ prop ] || prop ] = value;
    1370                         });
    1371 
    1372                         // Fill any other default query args.
    1373                         _.defaults( args, Query.defaultArgs );
    1374 
    1375                         // `props.orderby` does not always map directly to `args.orderby`.
    1376                         // Substitute exceptions specified in orderby.keymap.
    1377                         args.orderby = orderby.valuemap[ props.orderby ] || props.orderby;
    1378 
    1379                         // Search the query cache for a matching query.
    1380                         if ( cache ) {
    1381                                 query = _.find( queries, function( query ) {
    1382                                         return _.isEqual( query.args, args );
    1383                                 });
    1384                         } else {
    1385                                 queries = [];
    1386                         }
    1387 
    1388                         // Otherwise, create a new query and add it to the cache.
    1389                         if ( ! query ) {
    1390                                 query = new Query( [], _.extend( options || {}, {
    1391                                         props: props,
    1392                                         args:  args
    1393                                 } ) );
    1394                                 queries.push( query );
    1395                         }
    1396 
    1397                         return query;
    1398                 };
    1399         }())
    1400 });
    1401 
    1402 module.exports = Query;
    1403 
    1404 },{}],6:[function(require,module,exports){
    1405 /*globals wp, _ */
    1406 
    1407 /**
    1408  * wp.media.model.Selection
    1409  *
    1410  * A selection of attachments.
    1411  *
    1412  * @class
    1413  * @augments wp.media.model.Attachments
    1414  * @augments Backbone.Collection
    1415  */
    1416 var Attachments = wp.media.model.Attachments,
    1417         Selection;
    1418 
    1419 Selection = Attachments.extend({
    1420         /**
    1421          * Refresh the `single` model whenever the selection changes.
    1422          * Binds `single` instead of using the context argument to ensure
    1423          * it receives no parameters.
    1424          *
    1425          * @param {Array} [models=[]] Array of models used to populate the collection.
    1426          * @param {Object} [options={}]
    1427          */
    1428         initialize: function( models, options ) {
    1429                 /**
    1430                  * call 'initialize' directly on the parent class
    1431                  */
    1432                 Attachments.prototype.initialize.apply( this, arguments );
    1433                 this.multiple = options && options.multiple;
    1434 
    1435                 this.on( 'add remove reset', _.bind( this.single, this, false ) );
    1436         },
    1437 
    1438         /**
    1439          * If the workflow does not support multi-select, clear out the selection
    1440          * before adding a new attachment to it.
    1441          *
    1442          * @param {Array} models
    1443          * @param {Object} options
    1444          * @returns {wp.media.model.Attachment[]}
    1445          */
    1446         add: function( models, options ) {
    1447                 if ( ! this.multiple ) {
    1448                         this.remove( this.models );
    1449                 }
    1450                 /**
    1451                  * call 'add' directly on the parent class
    1452                  */
    1453                 return Attachments.prototype.add.call( this, models, options );
    1454         },
    1455 
    1456         /**
    1457          * Fired when toggling (clicking on) an attachment in the modal.
    1458          *
    1459          * @param {undefined|boolean|wp.media.model.Attachment} model
    1460          *
    1461          * @fires wp.media.model.Selection#selection:single
    1462          * @fires wp.media.model.Selection#selection:unsingle
    1463          *
    1464          * @returns {Backbone.Model}
    1465          */
    1466         single: function( model ) {
    1467                 var previous = this._single;
    1468 
    1469                 // If a `model` is provided, use it as the single model.
    1470                 if ( model ) {
    1471                         this._single = model;
    1472                 }
    1473                 // If the single model isn't in the selection, remove it.
    1474                 if ( this._single && ! this.get( this._single.cid ) ) {
    1475                         delete this._single;
    1476                 }
    1477 
    1478                 this._single = this._single || this.last();
    1479 
    1480                 // If single has changed, fire an event.
    1481                 if ( this._single !== previous ) {
    1482                         if ( previous ) {
    1483                                 previous.trigger( 'selection:unsingle', previous, this );
    1484 
    1485                                 // If the model was already removed, trigger the collection
    1486                                 // event manually.
    1487                                 if ( ! this.get( previous.cid ) ) {
    1488                                         this.trigger( 'selection:unsingle', previous, this );
    1489                                 }
    1490                         }
    1491                         if ( this._single ) {
    1492                                 this._single.trigger( 'selection:single', this._single, this );
    1493                         }
    1494                 }
    1495 
    1496                 // Return the single model, or the last model as a fallback.
    1497                 return this._single;
    1498         }
    1499 });
    1500 
    1501 module.exports = Selection;
    1502 
    1503 },{}]},{},[1]);
  • src/wp-includes/js/media/views.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 /*globals wp, _ */
    3 
    4 /**
    5  * wp.media.controller.CollectionAdd
    6  *
    7  * A state for adding attachments to a collection (e.g. video playlist).
    8  *
    9  * @class
    10  * @augments wp.media.controller.Library
    11  * @augments wp.media.controller.State
    12  * @augments Backbone.Model
    13  *
    14  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    15  * @param {string}                     [attributes.id=library]      Unique identifier.
    16  * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
    17  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    18  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    19  *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
    20  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    21  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    22  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    23  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    24  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    25  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    26  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    27  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    28  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    29  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    30  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    31  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    32  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    33  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    34  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    35  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
    36  */
    37 var Selection = wp.media.model.Selection,
    38         Library = wp.media.controller.Library,
    39         CollectionAdd;
    40 
    41 CollectionAdd = Library.extend({
    42         defaults: _.defaults( {
    43                 // Selection defaults. @see media.model.Selection
    44                 multiple:      'add',
    45                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
    46                 filterable:    'uploaded',
    47 
    48                 priority:      100,
    49                 syncSelection: false
    50         }, Library.prototype.defaults ),
    51 
    52         /**
    53          * @since 3.9.0
    54          */
    55         initialize: function() {
    56                 var collectionType = this.get('collectionType');
    57 
    58                 if ( 'video' === this.get( 'type' ) ) {
    59                         collectionType = 'video-' + collectionType;
    60                 }
    61 
    62                 this.set( 'id', collectionType + '-library' );
    63                 this.set( 'toolbar', collectionType + '-add' );
    64                 this.set( 'menu', collectionType );
    65 
    66                 // If we haven't been provided a `library`, create a `Selection`.
    67                 if ( ! this.get('library') ) {
    68                         this.set( 'library', wp.media.query({ type: this.get('type') }) );
    69                 }
    70                 Library.prototype.initialize.apply( this, arguments );
    71         },
    72 
    73         /**
    74          * @since 3.9.0
    75          */
    76         activate: function() {
    77                 var library = this.get('library'),
    78                         editLibrary = this.get('editLibrary'),
    79                         edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
    80 
    81                 if ( editLibrary && editLibrary !== edit ) {
    82                         library.unobserve( editLibrary );
    83                 }
    84 
    85                 // Accepts attachments that exist in the original library and
    86                 // that do not exist in gallery's library.
    87                 library.validator = function( attachment ) {
    88                         return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    89                 };
    90 
    91                 // Reset the library to ensure that all attachments are re-added
    92                 // to the collection. Do so silently, as calling `observe` will
    93                 // trigger the `reset` event.
    94                 library.reset( library.mirroring.models, { silent: true });
    95                 library.observe( edit );
    96                 this.set('editLibrary', edit);
    97 
    98                 Library.prototype.activate.apply( this, arguments );
    99         }
    100 });
    101 
    102 module.exports = CollectionAdd;
    103 
    104 },{}],2:[function(require,module,exports){
    105 /*globals wp, Backbone */
    106 
    107 /**
    108  * wp.media.controller.CollectionEdit
    109  *
    110  * A state for editing a collection, which is used by audio and video playlists,
    111  * and can be used for other collections.
    112  *
    113  * @class
    114  * @augments wp.media.controller.Library
    115  * @augments wp.media.controller.State
    116  * @augments Backbone.Model
    117  *
    118  * @param {object}                     [attributes]                      The attributes hash passed to the state.
    119  * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
    120  * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
    121  *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
    122  * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
    123  * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
    124  * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
    125  * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
    126  * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    127  * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
    128  * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
    129  * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
    130  * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
    131  * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
    132  * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
    133  * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
    134  *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
    135  * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
    136  * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
    137  *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    138  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
    139  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
    140  */
    141 var Library = wp.media.controller.Library,
    142         l10n = wp.media.view.l10n,
    143         $ = jQuery,
    144         CollectionEdit;
    145 
    146 CollectionEdit = Library.extend({
    147         defaults: {
    148                 multiple:         false,
    149                 sortable:         true,
    150                 searchable:       false,
    151                 content:          'browse',
    152                 describe:         true,
    153                 dragInfo:         true,
    154                 idealColumnWidth: 170,
    155                 editing:          false,
    156                 priority:         60,
    157                 SettingsView:     false,
    158                 syncSelection:    false
    159         },
    160 
    161         /**
    162          * @since 3.9.0
    163          */
    164         initialize: function() {
    165                 var collectionType = this.get('collectionType');
    166 
    167                 if ( 'video' === this.get( 'type' ) ) {
    168                         collectionType = 'video-' + collectionType;
    169                 }
    170 
    171                 this.set( 'id', collectionType + '-edit' );
    172                 this.set( 'toolbar', collectionType + '-edit' );
    173 
    174                 // If we haven't been provided a `library`, create a `Selection`.
    175                 if ( ! this.get('library') ) {
    176                         this.set( 'library', new wp.media.model.Selection() );
    177                 }
    178                 // The single `Attachment` view to be used in the `Attachments` view.
    179                 if ( ! this.get('AttachmentView') ) {
    180                         this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    181                 }
    182                 Library.prototype.initialize.apply( this, arguments );
    183         },
    184 
    185         /**
    186          * @since 3.9.0
    187          */
    188         activate: function() {
    189                 var library = this.get('library');
    190 
    191                 // Limit the library to images only.
    192                 library.props.set( 'type', this.get( 'type' ) );
    193 
    194                 // Watch for uploaded attachments.
    195                 this.get('library').observe( wp.Uploader.queue );
    196 
    197                 this.frame.on( 'content:render:browse', this.renderSettings, this );
    198 
    199                 Library.prototype.activate.apply( this, arguments );
    200         },
    201 
    202         /**
    203          * @since 3.9.0
    204          */
    205         deactivate: function() {
    206                 // Stop watching for uploaded attachments.
    207                 this.get('library').unobserve( wp.Uploader.queue );
    208 
    209                 this.frame.off( 'content:render:browse', this.renderSettings, this );
    210 
    211                 Library.prototype.deactivate.apply( this, arguments );
    212         },
    213 
    214         /**
    215          * Render the collection embed settings view in the browser sidebar.
    216          *
    217          * @todo This is against the pattern elsewhere in media. Typically the frame
    218          *       is responsible for adding region mode callbacks. Explain.
    219          *
    220          * @since 3.9.0
    221          *
    222          * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
    223          */
    224         renderSettings: function( attachmentsBrowserView ) {
    225                 var library = this.get('library'),
    226                         collectionType = this.get('collectionType'),
    227                         dragInfoText = this.get('dragInfoText'),
    228                         SettingsView = this.get('SettingsView'),
    229                         obj = {};
    230 
    231                 if ( ! library || ! attachmentsBrowserView ) {
    232                         return;
    233                 }
    234 
    235                 library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
    236 
    237                 obj[ collectionType ] = new SettingsView({
    238                         controller: this,
    239                         model:      library[ collectionType ],
    240                         priority:   40
    241                 });
    242 
    243                 attachmentsBrowserView.sidebar.set( obj );
    244 
    245                 if ( dragInfoText ) {
    246                         attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
    247                                 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
    248                                 priority: -40
    249                         }) );
    250                 }
    251 
    252                 // Add the 'Reverse order' button to the toolbar.
    253                 attachmentsBrowserView.toolbar.set( 'reverse', {
    254                         text:     l10n.reverseOrder,
    255                         priority: 80,
    256 
    257                         click: function() {
    258                                 library.reset( library.toArray().reverse() );
    259                         }
    260                 });
    261         }
    262 });
    263 
    264 module.exports = CollectionEdit;
    265 
    266 },{}],3:[function(require,module,exports){
    267 /*globals wp, _, Backbone */
    268 
    269 /**
    270  * wp.media.controller.Cropper
    271  *
    272  * A state for cropping an image.
    273  *
    274  * @class
    275  * @augments wp.media.controller.State
    276  * @augments Backbone.Model
    277  */
    278 var l10n = wp.media.view.l10n,
    279         Cropper;
    280 
    281 Cropper = wp.media.controller.State.extend({
    282         defaults: {
    283                 id:          'cropper',
    284                 title:       l10n.cropImage,
    285                 // Region mode defaults.
    286                 toolbar:     'crop',
    287                 content:     'crop',
    288                 router:      false,
    289 
    290                 canSkipCrop: false
    291         },
    292 
    293         activate: function() {
    294                 this.frame.on( 'content:create:crop', this.createCropContent, this );
    295                 this.frame.on( 'close', this.removeCropper, this );
    296                 this.set('selection', new Backbone.Collection(this.frame._selection.single));
    297         },
    298 
    299         deactivate: function() {
    300                 this.frame.toolbar.mode('browse');
    301         },
    302 
    303         createCropContent: function() {
    304                 this.cropperView = new wp.media.view.Cropper({
    305                         controller: this,
    306                         attachment: this.get('selection').first()
    307                 });
    308                 this.cropperView.on('image-loaded', this.createCropToolbar, this);
    309                 this.frame.content.set(this.cropperView);
    310 
    311         },
    312         removeCropper: function() {
    313                 this.imgSelect.cancelSelection();
    314                 this.imgSelect.setOptions({remove: true});
    315                 this.imgSelect.update();
    316                 this.cropperView.remove();
    317         },
    318         createCropToolbar: function() {
    319                 var canSkipCrop, toolbarOptions;
    320 
    321                 canSkipCrop = this.get('canSkipCrop') || false;
    322 
    323                 toolbarOptions = {
    324                         controller: this.frame,
    325                         items: {
    326                                 insert: {
    327                                         style:    'primary',
    328                                         text:     l10n.cropImage,
    329                                         priority: 80,
    330                                         requires: { library: false, selection: false },
    331 
    332                                         click: function() {
    333                                                 var controller = this.controller,
    334                                                         selection;
    335 
    336                                                 selection = controller.state().get('selection').first();
    337                                                 selection.set({cropDetails: controller.state().imgSelect.getSelection()});
    338 
    339                                                 this.$el.text(l10n.cropping);
    340                                                 this.$el.attr('disabled', true);
    341 
    342                                                 controller.state().doCrop( selection ).done( function( croppedImage ) {
    343                                                         controller.trigger('cropped', croppedImage );
    344                                                         controller.close();
    345                                                 }).fail( function() {
    346                                                         controller.trigger('content:error:crop');
    347                                                 });
    348                                         }
    349                                 }
    350                         }
    351                 };
    352 
    353                 if ( canSkipCrop ) {
    354                         _.extend( toolbarOptions.items, {
    355                                 skip: {
    356                                         style:      'secondary',
    357                                         text:       l10n.skipCropping,
    358                                         priority:   70,
    359                                         requires:   { library: false, selection: false },
    360                                         click:      function() {
    361                                                 var selection = this.controller.state().get('selection').first();
    362                                                 this.controller.state().cropperView.remove();
    363                                                 this.controller.trigger('skippedcrop', selection);
    364                                                 this.controller.close();
    365                                         }
    366                                 }
    367                         });
    368                 }
    369 
    370                 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
    371         },
    372 
    373         doCrop: function( attachment ) {
    374                 return wp.ajax.post( 'custom-header-crop', {
    375                         nonce: attachment.get('nonces').edit,
    376                         id: attachment.get('id'),
    377                         cropDetails: attachment.get('cropDetails')
    378                 } );
    379         }
    380 });
    381 
    382 module.exports = Cropper;
    383 
    384 },{}],4:[function(require,module,exports){
    385 /*globals wp */
    386 
    387 /**
    388  * wp.media.controller.EditImage
    389  *
    390  * A state for editing (cropping, etc.) an image.
    391  *
    392  * @class
    393  * @augments wp.media.controller.State
    394  * @augments Backbone.Model
    395  *
    396  * @param {object}                    attributes                      The attributes hash passed to the state.
    397  * @param {wp.media.model.Attachment} attributes.model                The attachment.
    398  * @param {string}                    [attributes.id=edit-image]      Unique identifier.
    399  * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
    400  * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
    401  * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
    402  * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
    403  * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
    404  */
    405 var l10n = wp.media.view.l10n,
    406         EditImage;
    407 
    408 EditImage = wp.media.controller.State.extend({
    409         defaults: {
    410                 id:      'edit-image',
    411                 title:   l10n.editImage,
    412                 menu:    false,
    413                 toolbar: 'edit-image',
    414                 content: 'edit-image',
    415                 url:     ''
    416         },
    417 
    418         /**
    419          * @since 3.9.0
    420          */
    421         activate: function() {
    422                 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
    423         },
    424 
    425         /**
    426          * @since 3.9.0
    427          */
    428         deactivate: function() {
    429                 this.stopListening( this.frame );
    430         },
    431 
    432         /**
    433          * @since 3.9.0
    434          */
    435         toolbar: function() {
    436                 var frame = this.frame,
    437                         lastState = frame.lastState(),
    438                         previous = lastState && lastState.id;
    439 
    440                 frame.toolbar.set( new wp.media.view.Toolbar({
    441                         controller: frame,
    442                         items: {
    443                                 back: {
    444                                         style: 'primary',
    445                                         text:     l10n.back,
    446                                         priority: 20,
    447                                         click:    function() {
    448                                                 if ( previous ) {
    449                                                         frame.setState( previous );
    450                                                 } else {
    451                                                         frame.close();
    452                                                 }
    453                                         }
    454                                 }
    455                         }
    456                 }) );
    457         }
    458 });
    459 
    460 module.exports = EditImage;
    461 
    462 },{}],5:[function(require,module,exports){
    463 /*globals wp, _, Backbone */
    464 
    465 /**
    466  * wp.media.controller.Embed
    467  *
    468  * A state for embedding media from a URL.
    469  *
    470  * @class
    471  * @augments wp.media.controller.State
    472  * @augments Backbone.Model
    473  *
    474  * @param {object} attributes                         The attributes hash passed to the state.
    475  * @param {string} [attributes.id=embed]              Unique identifier.
    476  * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
    477  * @param {string} [attributes.content=embed]         Initial mode for the content region.
    478  * @param {string} [attributes.menu=default]          Initial mode for the menu region.
    479  * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
    480  * @param {string} [attributes.menu=false]            Initial mode for the menu region.
    481  * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
    482  * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
    483  * @param {string} [attributes.url]                   The embed URL.
    484  * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
    485  */
    486 var l10n = wp.media.view.l10n,
    487         $ = Backbone.$,
    488         Embed;
    489 
    490 Embed = wp.media.controller.State.extend({
    491         defaults: {
    492                 id:       'embed',
    493                 title:    l10n.insertFromUrlTitle,
    494                 content:  'embed',
    495                 menu:     'default',
    496                 toolbar:  'main-embed',
    497                 priority: 120,
    498                 type:     'link',
    499                 url:      '',
    500                 metadata: {}
    501         },
    502 
    503         // The amount of time used when debouncing the scan.
    504         sensitivity: 200,
    505 
    506         initialize: function(options) {
    507                 this.metadata = options.metadata;
    508                 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
    509                 this.props = new Backbone.Model( this.metadata || { url: '' });
    510                 this.props.on( 'change:url', this.debouncedScan, this );
    511                 this.props.on( 'change:url', this.refresh, this );
    512                 this.on( 'scan', this.scanImage, this );
    513         },
    514 
    515         /**
    516          * Trigger a scan of the embedded URL's content for metadata required to embed.
    517          *
    518          * @fires wp.media.controller.Embed#scan
    519          */
    520         scan: function() {
    521                 var scanners,
    522                         embed = this,
    523                         attributes = {
    524                                 type: 'link',
    525                                 scanners: []
    526                         };
    527 
    528                 // Scan is triggered with the list of `attributes` to set on the
    529                 // state, useful for the 'type' attribute and 'scanners' attribute,
    530                 // an array of promise objects for asynchronous scan operations.
    531                 if ( this.props.get('url') ) {
    532                         this.trigger( 'scan', attributes );
    533                 }
    534 
    535                 if ( attributes.scanners.length ) {
    536                         scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
    537                         scanners.always( function() {
    538                                 if ( embed.get('scanners') === scanners ) {
    539                                         embed.set( 'loading', false );
    540                                 }
    541                         });
    542                 } else {
    543                         attributes.scanners = null;
    544                 }
    545 
    546                 attributes.loading = !! attributes.scanners;
    547                 this.set( attributes );
    548         },
    549         /**
    550          * Try scanning the embed as an image to discover its dimensions.
    551          *
    552          * @param {Object} attributes
    553          */
    554         scanImage: function( attributes ) {
    555                 var frame = this.frame,
    556                         state = this,
    557                         url = this.props.get('url'),
    558                         image = new Image(),
    559                         deferred = $.Deferred();
    560 
    561                 attributes.scanners.push( deferred.promise() );
    562 
    563                 // Try to load the image and find its width/height.
    564                 image.onload = function() {
    565                         deferred.resolve();
    566 
    567                         if ( state !== frame.state() || url !== state.props.get('url') ) {
    568                                 return;
    569                         }
    570 
    571                         state.set({
    572                                 type: 'image'
    573                         });
    574 
    575                         state.props.set({
    576                                 width:  image.width,
    577                                 height: image.height
    578                         });
    579                 };
    580 
    581                 image.onerror = deferred.reject;
    582                 image.src = url;
    583         },
    584 
    585         refresh: function() {
    586                 this.frame.toolbar.get().refresh();
    587         },
    588 
    589         reset: function() {
    590                 this.props.clear().set({ url: '' });
    591 
    592                 if ( this.active ) {
    593                         this.refresh();
    594                 }
    595         }
    596 });
    597 
    598 module.exports = Embed;
    599 
    600 },{}],6:[function(require,module,exports){
    601 /*globals wp, _ */
    602 
    603 /**
    604  * wp.media.controller.FeaturedImage
    605  *
    606  * A state for selecting a featured image for a post.
    607  *
    608  * @class
    609  * @augments wp.media.controller.Library
    610  * @augments wp.media.controller.State
    611  * @augments Backbone.Model
    612  *
    613  * @param {object}                     [attributes]                          The attributes hash passed to the state.
    614  * @param {string}                     [attributes.id=featured-image]        Unique identifier.
    615  * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
    616  * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
    617  *                                                                           If one is not supplied, a collection of all images will be created.
    618  * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
    619  * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
    620  *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
    621  * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
    622  * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
    623  * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
    624  * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
    625  * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
    626  * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
    627  *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
    628  * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    629  * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
    630  * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    631  * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
    632  * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
    633  */
    634 var Attachment = wp.media.model.Attachment,
    635         Library = wp.media.controller.Library,
    636         l10n = wp.media.view.l10n,
    637         FeaturedImage;
    638 
    639 FeaturedImage = Library.extend({
    640         defaults: _.defaults({
    641                 id:            'featured-image',
    642                 title:         l10n.setFeaturedImageTitle,
    643                 multiple:      false,
    644                 filterable:    'uploaded',
    645                 toolbar:       'featured-image',
    646                 priority:      60,
    647                 syncSelection: true
    648         }, Library.prototype.defaults ),
    649 
    650         /**
    651          * @since 3.5.0
    652          */
    653         initialize: function() {
    654                 var library, comparator;
    655 
    656                 // If we haven't been provided a `library`, create a `Selection`.
    657                 if ( ! this.get('library') ) {
    658                         this.set( 'library', wp.media.query({ type: 'image' }) );
    659                 }
    660 
    661                 Library.prototype.initialize.apply( this, arguments );
    662 
    663                 library    = this.get('library');
    664                 comparator = library.comparator;
    665 
    666                 // Overload the library's comparator to push items that are not in
    667                 // the mirrored query to the front of the aggregate collection.
    668                 library.comparator = function( a, b ) {
    669                         var aInQuery = !! this.mirroring.get( a.cid ),
    670                                 bInQuery = !! this.mirroring.get( b.cid );
    671 
    672                         if ( ! aInQuery && bInQuery ) {
    673                                 return -1;
    674                         } else if ( aInQuery && ! bInQuery ) {
    675                                 return 1;
    676                         } else {
    677                                 return comparator.apply( this, arguments );
    678                         }
    679                 };
    680 
    681                 // Add all items in the selection to the library, so any featured
    682                 // images that are not initially loaded still appear.
    683                 library.observe( this.get('selection') );
    684         },
    685 
    686         /**
    687          * @since 3.5.0
    688          */
    689         activate: function() {
    690                 this.updateSelection();
    691                 this.frame.on( 'open', this.updateSelection, this );
    692 
    693                 Library.prototype.activate.apply( this, arguments );
    694         },
    695 
    696         /**
    697          * @since 3.5.0
    698          */
    699         deactivate: function() {
    700                 this.frame.off( 'open', this.updateSelection, this );
    701 
    702                 Library.prototype.deactivate.apply( this, arguments );
    703         },
    704 
    705         /**
    706          * @since 3.5.0
    707          */
    708         updateSelection: function() {
    709                 var selection = this.get('selection'),
    710                         id = wp.media.view.settings.post.featuredImageId,
    711                         attachment;
    712 
    713                 if ( '' !== id && -1 !== id ) {
    714                         attachment = Attachment.get( id );
    715                         attachment.fetch();
    716                 }
    717 
    718                 selection.reset( attachment ? [ attachment ] : [] );
    719         }
    720 });
    721 
    722 module.exports = FeaturedImage;
    723 
    724 },{}],7:[function(require,module,exports){
    725 /*globals wp, _ */
    726 
    727 /**
    728  * wp.media.controller.GalleryAdd
    729  *
    730  * A state for selecting more images to add to a gallery.
    731  *
    732  * @class
    733  * @augments wp.media.controller.Library
    734  * @augments wp.media.controller.State
    735  * @augments Backbone.Model
    736  *
    737  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    738  * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
    739  * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
    740  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
    741  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    742  *                                                                          If one is not supplied, a collection of all images will be created.
    743  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    744  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    745  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
    746  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    747  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    748  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    749  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
    750  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    751  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    752  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    753  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    754  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
    755  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
    756  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
    757  */
    758 var Selection = wp.media.model.Selection,
    759         Library = wp.media.controller.Library,
    760         l10n = wp.media.view.l10n,
    761         GalleryAdd;
    762 
    763 GalleryAdd = Library.extend({
    764         defaults: _.defaults({
    765                 id:            'gallery-library',
    766                 title:         l10n.addToGalleryTitle,
    767                 multiple:      'add',
    768                 filterable:    'uploaded',
    769                 menu:          'gallery',
    770                 toolbar:       'gallery-add',
    771                 priority:      100,
    772                 syncSelection: false
    773         }, Library.prototype.defaults ),
    774 
    775         /**
    776          * @since 3.5.0
    777          */
    778         initialize: function() {
    779                 // If a library wasn't supplied, create a library of images.
    780                 if ( ! this.get('library') ) {
    781                         this.set( 'library', wp.media.query({ type: 'image' }) );
    782                 }
    783 
    784                 Library.prototype.initialize.apply( this, arguments );
    785         },
    786 
    787         /**
    788          * @since 3.5.0
    789          */
    790         activate: function() {
    791                 var library = this.get('library'),
    792                         edit    = this.frame.state('gallery-edit').get('library');
    793 
    794                 if ( this.editLibrary && this.editLibrary !== edit ) {
    795                         library.unobserve( this.editLibrary );
    796                 }
    797 
    798                 // Accepts attachments that exist in the original library and
    799                 // that do not exist in gallery's library.
    800                 library.validator = function( attachment ) {
    801                         return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
    802                 };
    803 
    804                 // Reset the library to ensure that all attachments are re-added
    805                 // to the collection. Do so silently, as calling `observe` will
    806                 // trigger the `reset` event.
    807                 library.reset( library.mirroring.models, { silent: true });
    808                 library.observe( edit );
    809                 this.editLibrary = edit;
    810 
    811                 Library.prototype.activate.apply( this, arguments );
    812         }
    813 });
    814 
    815 module.exports = GalleryAdd;
    816 
    817 },{}],8:[function(require,module,exports){
    818 /*globals wp */
    819 
    820 /**
    821  * wp.media.controller.GalleryEdit
    822  *
    823  * A state for editing a gallery's images and settings.
    824  *
    825  * @class
    826  * @augments wp.media.controller.Library
    827  * @augments wp.media.controller.State
    828  * @augments Backbone.Model
    829  *
    830  * @param {object}                     [attributes]                       The attributes hash passed to the state.
    831  * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
    832  * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
    833  * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
    834  *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
    835  * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
    836  * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
    837  * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    838  * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
    839  * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
    840  * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    841  * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
    842  * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
    843  * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
    844  * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
    845  * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
    846  * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
    847  *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
    848  * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
    849  *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
    850  */
    851 var Library = wp.media.controller.Library,
    852         l10n = wp.media.view.l10n,
    853         GalleryEdit;
    854 
    855 GalleryEdit = Library.extend({
    856         defaults: {
    857                 id:               'gallery-edit',
    858                 title:            l10n.editGalleryTitle,
    859                 multiple:         false,
    860                 searchable:       false,
    861                 sortable:         true,
    862                 display:          false,
    863                 content:          'browse',
    864                 toolbar:          'gallery-edit',
    865                 describe:         true,
    866                 displaySettings:  true,
    867                 dragInfo:         true,
    868                 idealColumnWidth: 170,
    869                 editing:          false,
    870                 priority:         60,
    871                 syncSelection:    false
    872         },
    873 
    874         /**
    875          * @since 3.5.0
    876          */
    877         initialize: function() {
    878                 // If we haven't been provided a `library`, create a `Selection`.
    879                 if ( ! this.get('library') ) {
    880                         this.set( 'library', new wp.media.model.Selection() );
    881                 }
    882 
    883                 // The single `Attachment` view to be used in the `Attachments` view.
    884                 if ( ! this.get('AttachmentView') ) {
    885                         this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
    886                 }
    887 
    888                 Library.prototype.initialize.apply( this, arguments );
    889         },
    890 
    891         /**
    892          * @since 3.5.0
    893          */
    894         activate: function() {
    895                 var library = this.get('library');
    896 
    897                 // Limit the library to images only.
    898                 library.props.set( 'type', 'image' );
    899 
    900                 // Watch for uploaded attachments.
    901                 this.get('library').observe( wp.Uploader.queue );
    902 
    903                 this.frame.on( 'content:render:browse', this.gallerySettings, this );
    904 
    905                 Library.prototype.activate.apply( this, arguments );
    906         },
    907 
    908         /**
    909          * @since 3.5.0
    910          */
    911         deactivate: function() {
    912                 // Stop watching for uploaded attachments.
    913                 this.get('library').unobserve( wp.Uploader.queue );
    914 
    915                 this.frame.off( 'content:render:browse', this.gallerySettings, this );
    916 
    917                 Library.prototype.deactivate.apply( this, arguments );
    918         },
    919 
    920         /**
    921          * @since 3.5.0
    922          *
    923          * @param browser
    924          */
    925         gallerySettings: function( browser ) {
    926                 if ( ! this.get('displaySettings') ) {
    927                         return;
    928                 }
    929 
    930                 var library = this.get('library');
    931 
    932                 if ( ! library || ! browser ) {
    933                         return;
    934                 }
    935 
    936                 library.gallery = library.gallery || new Backbone.Model();
    937 
    938                 browser.sidebar.set({
    939                         gallery: new wp.media.view.Settings.Gallery({
    940                                 controller: this,
    941                                 model:      library.gallery,
    942                                 priority:   40
    943                         })
    944                 });
    945 
    946                 browser.toolbar.set( 'reverse', {
    947                         text:     l10n.reverseOrder,
    948                         priority: 80,
    949 
    950                         click: function() {
    951                                 library.reset( library.toArray().reverse() );
    952                         }
    953                 });
    954         }
    955 });
    956 
    957 module.exports = GalleryEdit;
    958 
    959 },{}],9:[function(require,module,exports){
    960 /*globals wp, _ */
    961 
    962 /**
    963  * wp.media.controller.ImageDetails
    964  *
    965  * A state for editing the attachment display settings of an image that's been
    966  * inserted into the editor.
    967  *
    968  * @class
    969  * @augments wp.media.controller.State
    970  * @augments Backbone.Model
    971  *
    972  * @param {object}                    [attributes]                       The attributes hash passed to the state.
    973  * @param {string}                    [attributes.id=image-details]      Unique identifier.
    974  * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
    975  * @param {wp.media.model.Attachment} attributes.image                   The image's model.
    976  * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
    977  * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
    978  * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
    979  * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
    980  * @param {boolean}                   [attributes.editing=false]         Unused.
    981  * @param {int}                       [attributes.priority=60]           Unused.
    982  *
    983  * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
    984  *       however this may not do anything.
    985  */
    986 var State = wp.media.controller.State,
    987         Library = wp.media.controller.Library,
    988         l10n = wp.media.view.l10n,
    989         ImageDetails;
    990 
    991 ImageDetails = State.extend({
    992         defaults: _.defaults({
    993                 id:       'image-details',
    994                 title:    l10n.imageDetailsTitle,
    995                 content:  'image-details',
    996                 menu:     false,
    997                 router:   false,
    998                 toolbar:  'image-details',
    999                 editing:  false,
    1000                 priority: 60
    1001         }, Library.prototype.defaults ),
    1002 
    1003         /**
    1004          * @since 3.9.0
    1005          *
    1006          * @param options Attributes
    1007          */
    1008         initialize: function( options ) {
    1009                 this.image = options.image;
    1010                 State.prototype.initialize.apply( this, arguments );
    1011         },
    1012 
    1013         /**
    1014          * @since 3.9.0
    1015          */
    1016         activate: function() {
    1017                 this.frame.modal.$el.addClass('image-details');
    1018         }
    1019 });
    1020 
    1021 module.exports = ImageDetails;
    1022 
    1023 },{}],10:[function(require,module,exports){
    1024 /*globals wp, _, Backbone */
    1025 
    1026 /**
    1027  * wp.media.controller.Library
    1028  *
    1029  * A state for choosing an attachment or group of attachments from the media library.
    1030  *
    1031  * @class
    1032  * @augments wp.media.controller.State
    1033  * @augments Backbone.Model
    1034  * @mixes media.selectionSync
    1035  *
    1036  * @param {object}                          [attributes]                         The attributes hash passed to the state.
    1037  * @param {string}                          [attributes.id=library]              Unique identifier.
    1038  * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
    1039  * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
    1040  *                                                                               If one is not supplied, a collection of all attachments will be created.
    1041  * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
    1042  *                                                                               If the 'selection' attribute is a plain JS object,
    1043  *                                                                               a Selection will be created using its values as the selection instance's `props` model.
    1044  *                                                                               Otherwise, it will copy the library's `props` model.
    1045  * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
    1046  * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
    1047  *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
    1048  * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
    1049  * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
    1050  * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
    1051  * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
    1052  * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
    1053  *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
    1054  * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1055  * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1056  * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1057  * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1058  * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    1059  */
    1060 var l10n = wp.media.view.l10n,
    1061         getUserSetting = window.getUserSetting,
    1062         setUserSetting = window.setUserSetting,
    1063         Library;
    1064 
    1065 Library = wp.media.controller.State.extend({
    1066         defaults: {
    1067                 id:                 'library',
    1068                 title:              l10n.mediaLibraryTitle,
    1069                 multiple:           false,
    1070                 content:            'upload',
    1071                 menu:               'default',
    1072                 router:             'browse',
    1073                 toolbar:            'select',
    1074                 searchable:         true,
    1075                 filterable:         false,
    1076                 sortable:           true,
    1077                 autoSelect:         true,
    1078                 describe:           false,
    1079                 contentUserSetting: true,
    1080                 syncSelection:      true
    1081         },
    1082 
    1083         /**
    1084          * If a library isn't provided, query all media items.
    1085          * If a selection instance isn't provided, create one.
    1086          *
    1087          * @since 3.5.0
    1088          */
    1089         initialize: function() {
    1090                 var selection = this.get('selection'),
    1091                         props;
    1092 
    1093                 if ( ! this.get('library') ) {
    1094                         this.set( 'library', wp.media.query() );
    1095                 }
    1096 
    1097                 if ( ! ( selection instanceof wp.media.model.Selection ) ) {
    1098                         props = selection;
    1099 
    1100                         if ( ! props ) {
    1101                                 props = this.get('library').props.toJSON();
    1102                                 props = _.omit( props, 'orderby', 'query' );
    1103                         }
    1104 
    1105                         this.set( 'selection', new wp.media.model.Selection( null, {
    1106                                 multiple: this.get('multiple'),
    1107                                 props: props
    1108                         }) );
    1109                 }
    1110 
    1111                 this.resetDisplays();
    1112         },
    1113 
    1114         /**
    1115          * @since 3.5.0
    1116          */
    1117         activate: function() {
    1118                 this.syncSelection();
    1119 
    1120                 wp.Uploader.queue.on( 'add', this.uploading, this );
    1121 
    1122                 this.get('selection').on( 'add remove reset', this.refreshContent, this );
    1123 
    1124                 if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
    1125                         this.frame.on( 'content:activate', this.saveContentMode, this );
    1126                         this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
    1127                 }
    1128         },
    1129 
    1130         /**
    1131          * @since 3.5.0
    1132          */
    1133         deactivate: function() {
    1134                 this.recordSelection();
    1135 
    1136                 this.frame.off( 'content:activate', this.saveContentMode, this );
    1137 
    1138                 // Unbind all event handlers that use this state as the context
    1139                 // from the selection.
    1140                 this.get('selection').off( null, null, this );
    1141 
    1142                 wp.Uploader.queue.off( null, null, this );
    1143         },
    1144 
    1145         /**
    1146          * Reset the library to its initial state.
    1147          *
    1148          * @since 3.5.0
    1149          */
    1150         reset: function() {
    1151                 this.get('selection').reset();
    1152                 this.resetDisplays();
    1153                 this.refreshContent();
    1154         },
    1155 
    1156         /**
    1157          * Reset the attachment display settings defaults to the site options.
    1158          *
    1159          * If site options don't define them, fall back to a persistent user setting.
    1160          *
    1161          * @since 3.5.0
    1162          */
    1163         resetDisplays: function() {
    1164                 var defaultProps = wp.media.view.settings.defaultProps;
    1165                 this._displays = [];
    1166                 this._defaultDisplaySettings = {
    1167                         align: defaultProps.align || getUserSetting( 'align', 'none' ),
    1168                         size:  defaultProps.size  || getUserSetting( 'imgsize', 'medium' ),
    1169                         link:  defaultProps.link  || getUserSetting( 'urlbutton', 'file' )
    1170                 };
    1171         },
    1172 
    1173         /**
    1174          * Create a model to represent display settings (alignment, etc.) for an attachment.
    1175          *
    1176          * @since 3.5.0
    1177          *
    1178          * @param {wp.media.model.Attachment} attachment
    1179          * @returns {Backbone.Model}
    1180          */
    1181         display: function( attachment ) {
    1182                 var displays = this._displays;
    1183 
    1184                 if ( ! displays[ attachment.cid ] ) {
    1185                         displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
    1186                 }
    1187                 return displays[ attachment.cid ];
    1188         },
    1189 
    1190         /**
    1191          * Given an attachment, create attachment display settings properties.
    1192          *
    1193          * @since 3.6.0
    1194          *
    1195          * @param {wp.media.model.Attachment} attachment
    1196          * @returns {Object}
    1197          */
    1198         defaultDisplaySettings: function( attachment ) {
    1199                 var settings = this._defaultDisplaySettings;
    1200                 if ( settings.canEmbed = this.canEmbed( attachment ) ) {
    1201                         settings.link = 'embed';
    1202                 }
    1203                 return settings;
    1204         },
    1205 
    1206         /**
    1207          * Whether an attachment can be embedded (audio or video).
    1208          *
    1209          * @since 3.6.0
    1210          *
    1211          * @param {wp.media.model.Attachment} attachment
    1212          * @returns {Boolean}
    1213          */
    1214         canEmbed: function( attachment ) {
    1215                 // If uploading, we know the filename but not the mime type.
    1216                 if ( ! attachment.get('uploading') ) {
    1217                         var type = attachment.get('type');
    1218                         if ( type !== 'audio' && type !== 'video' ) {
    1219                                 return false;
    1220                         }
    1221                 }
    1222 
    1223                 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
    1224         },
    1225 
    1226 
    1227         /**
    1228          * If the state is active, no items are selected, and the current
    1229          * content mode is not an option in the state's router (provided
    1230          * the state has a router), reset the content mode to the default.
    1231          *
    1232          * @since 3.5.0
    1233          */
    1234         refreshContent: function() {
    1235                 var selection = this.get('selection'),
    1236                         frame = this.frame,
    1237                         router = frame.router.get(),
    1238                         mode = frame.content.mode();
    1239 
    1240                 if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
    1241                         this.frame.content.render( this.get('content') );
    1242                 }
    1243         },
    1244 
    1245         /**
    1246          * Callback handler when an attachment is uploaded.
    1247          *
    1248          * Switch to the Media Library if uploaded from the 'Upload Files' tab.
    1249          *
    1250          * Adds any uploading attachments to the selection.
    1251          *
    1252          * If the state only supports one attachment to be selected and multiple
    1253          * attachments are uploaded, the last attachment in the upload queue will
    1254          * be selected.
    1255          *
    1256          * @since 3.5.0
    1257          *
    1258          * @param {wp.media.model.Attachment} attachment
    1259          */
    1260         uploading: function( attachment ) {
    1261                 var content = this.frame.content;
    1262 
    1263                 if ( 'upload' === content.mode() ) {
    1264                         this.frame.content.mode('browse');
    1265                 }
    1266 
    1267                 if ( this.get( 'autoSelect' ) ) {
    1268                         this.get('selection').add( attachment );
    1269                         this.frame.trigger( 'library:selection:add' );
    1270                 }
    1271         },
    1272 
    1273         /**
    1274          * Persist the mode of the content region as a user setting.
    1275          *
    1276          * @since 3.5.0
    1277          */
    1278         saveContentMode: function() {
    1279                 if ( 'browse' !== this.get('router') ) {
    1280                         return;
    1281                 }
    1282 
    1283                 var mode = this.frame.content.mode(),
    1284                         view = this.frame.router.get();
    1285 
    1286                 if ( view && view.get( mode ) ) {
    1287                         setUserSetting( 'libraryContent', mode );
    1288                 }
    1289         }
    1290 });
    1291 
    1292 // Make selectionSync available on any Media Library state.
    1293 _.extend( Library.prototype, wp.media.selectionSync );
    1294 
    1295 module.exports = Library;
    1296 
    1297 },{}],11:[function(require,module,exports){
    1298 /*globals wp, _ */
    1299 
    1300 /**
    1301  * wp.media.controller.MediaLibrary
    1302  *
    1303  * @class
    1304  * @augments wp.media.controller.Library
    1305  * @augments wp.media.controller.State
    1306  * @augments Backbone.Model
    1307  */
    1308 var Library = wp.media.controller.Library,
    1309         MediaLibrary;
    1310 
    1311 MediaLibrary = Library.extend({
    1312         defaults: _.defaults({
    1313                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
    1314                 filterable:      'uploaded',
    1315 
    1316                 displaySettings: false,
    1317                 priority:        80,
    1318                 syncSelection:   false
    1319         }, Library.prototype.defaults ),
    1320 
    1321         /**
    1322          * @since 3.9.0
    1323          *
    1324          * @param options
    1325          */
    1326         initialize: function( options ) {
    1327                 this.media = options.media;
    1328                 this.type = options.type;
    1329                 this.set( 'library', wp.media.query({ type: this.type }) );
    1330 
    1331                 Library.prototype.initialize.apply( this, arguments );
    1332         },
    1333 
    1334         /**
    1335          * @since 3.9.0
    1336          */
    1337         activate: function() {
    1338                 // @todo this should use this.frame.
    1339                 if ( wp.media.frame.lastMime ) {
    1340                         this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
    1341                         delete wp.media.frame.lastMime;
    1342                 }
    1343                 Library.prototype.activate.apply( this, arguments );
    1344         }
    1345 });
    1346 
    1347 module.exports = MediaLibrary;
    1348 
    1349 },{}],12:[function(require,module,exports){
    1350 /*globals Backbone, _ */
    1351 
    1352 /**
    1353  * wp.media.controller.Region
    1354  *
    1355  * A region is a persistent application layout area.
    1356  *
    1357  * A region assumes one mode at any time, and can be switched to another.
    1358  *
    1359  * When mode changes, events are triggered on the region's parent view.
    1360  * The parent view will listen to specific events and fill the region with an
    1361  * appropriate view depending on mode. For example, a frame listens for the
    1362  * 'browse' mode t be activated on the 'content' view and then fills the region
    1363  * with an AttachmentsBrowser view.
    1364  *
    1365  * @class
    1366  *
    1367  * @param {object}        options          Options hash for the region.
    1368  * @param {string}        options.id       Unique identifier for the region.
    1369  * @param {Backbone.View} options.view     A parent view the region exists within.
    1370  * @param {string}        options.selector jQuery selector for the region within the parent view.
    1371  */
    1372 var Region = function( options ) {
    1373         _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
    1374 };
    1375 
    1376 // Use Backbone's self-propagating `extend` inheritance method.
    1377 Region.extend = Backbone.Model.extend;
    1378 
    1379 _.extend( Region.prototype, {
    1380         /**
    1381          * Activate a mode.
    1382          *
    1383          * @since 3.5.0
    1384          *
    1385          * @param {string} mode
    1386          *
    1387          * @fires this.view#{this.id}:activate:{this._mode}
    1388          * @fires this.view#{this.id}:activate
    1389          * @fires this.view#{this.id}:deactivate:{this._mode}
    1390          * @fires this.view#{this.id}:deactivate
    1391          *
    1392          * @returns {wp.media.controller.Region} Returns itself to allow chaining.
    1393          */
    1394         mode: function( mode ) {
    1395                 if ( ! mode ) {
    1396                         return this._mode;
    1397                 }
    1398                 // Bail if we're trying to change to the current mode.
    1399                 if ( mode === this._mode ) {
    1400                         return this;
    1401                 }
    1402 
    1403                 /**
    1404                  * Region mode deactivation event.
    1405                  *
    1406                  * @event this.view#{this.id}:deactivate:{this._mode}
    1407                  * @event this.view#{this.id}:deactivate
    1408                  */
    1409                 this.trigger('deactivate');
    1410 
    1411                 this._mode = mode;
    1412                 this.render( mode );
    1413 
    1414                 /**
    1415                  * Region mode activation event.
    1416                  *
    1417                  * @event this.view#{this.id}:activate:{this._mode}
    1418                  * @event this.view#{this.id}:activate
    1419                  */
    1420                 this.trigger('activate');
    1421                 return this;
    1422         },
    1423         /**
    1424          * Render a mode.
    1425          *
    1426          * @since 3.5.0
    1427          *
    1428          * @param {string} mode
    1429          *
    1430          * @fires this.view#{this.id}:create:{this._mode}
    1431          * @fires this.view#{this.id}:create
    1432          * @fires this.view#{this.id}:render:{this._mode}
    1433          * @fires this.view#{this.id}:render
    1434          *
    1435          * @returns {wp.media.controller.Region} Returns itself to allow chaining
    1436          */
    1437         render: function( mode ) {
    1438                 // If the mode isn't active, activate it.
    1439                 if ( mode && mode !== this._mode ) {
    1440                         return this.mode( mode );
    1441                 }
    1442 
    1443                 var set = { view: null },
    1444                         view;
    1445 
    1446                 /**
    1447                  * Create region view event.
    1448                  *
    1449                  * Region view creation takes place in an event callback on the frame.
    1450                  *
    1451                  * @event this.view#{this.id}:create:{this._mode}
    1452                  * @event this.view#{this.id}:create
    1453                  */
    1454                 this.trigger( 'create', set );
    1455                 view = set.view;
    1456 
    1457                 /**
    1458                  * Render region view event.
    1459                  *
    1460                  * Region view creation takes place in an event callback on the frame.
    1461                  *
    1462                  * @event this.view#{this.id}:create:{this._mode}
    1463                  * @event this.view#{this.id}:create
    1464                  */
    1465                 this.trigger( 'render', view );
    1466                 if ( view ) {
    1467                         this.set( view );
    1468                 }
    1469                 return this;
    1470         },
    1471 
    1472         /**
    1473          * Get the region's view.
    1474          *
    1475          * @since 3.5.0
    1476          *
    1477          * @returns {wp.media.View}
    1478          */
    1479         get: function() {
    1480                 return this.view.views.first( this.selector );
    1481         },
    1482 
    1483         /**
    1484          * Set the region's view as a subview of the frame.
    1485          *
    1486          * @since 3.5.0
    1487          *
    1488          * @param {Array|Object} views
    1489          * @param {Object} [options={}]
    1490          * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
    1491          */
    1492         set: function( views, options ) {
    1493                 if ( options ) {
    1494                         options.add = false;
    1495                 }
    1496                 return this.view.views.set( this.selector, views, options );
    1497         },
    1498 
    1499         /**
    1500          * Trigger regional view events on the frame.
    1501          *
    1502          * @since 3.5.0
    1503          *
    1504          * @param {string} event
    1505          * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
    1506          */
    1507         trigger: function( event ) {
    1508                 var base, args;
    1509 
    1510                 if ( ! this._mode ) {
    1511                         return;
    1512                 }
    1513 
    1514                 args = _.toArray( arguments );
    1515                 base = this.id + ':' + event;
    1516 
    1517                 // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
    1518                 args[0] = base + ':' + this._mode;
    1519                 this.view.trigger.apply( this.view, args );
    1520 
    1521                 // Trigger `{this.id}:{event}` event on the frame.
    1522                 args[0] = base;
    1523                 this.view.trigger.apply( this.view, args );
    1524                 return this;
    1525         }
    1526 });
    1527 
    1528 module.exports = Region;
    1529 
    1530 },{}],13:[function(require,module,exports){
    1531 /*globals wp, _ */
    1532 
    1533 /**
    1534  * wp.media.controller.ReplaceImage
    1535  *
    1536  * A state for replacing an image.
    1537  *
    1538  * @class
    1539  * @augments wp.media.controller.Library
    1540  * @augments wp.media.controller.State
    1541  * @augments Backbone.Model
    1542  *
    1543  * @param {object}                     [attributes]                         The attributes hash passed to the state.
    1544  * @param {string}                     [attributes.id=replace-image]        Unique identifier.
    1545  * @param {string}                     [attributes.title=Replace Image]     Title for the state. Displays in the media menu and the frame's title region.
    1546  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
    1547  *                                                                          If one is not supplied, a collection of all images will be created.
    1548  * @param {boolean}                    [attributes.multiple=false]          Whether multi-select is enabled.
    1549  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
    1550  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
    1551  * @param {string}                     [attributes.menu=default]            Initial mode for the menu region.
    1552  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
    1553  * @param {string}                     [attributes.toolbar=replace]         Initial mode for the toolbar region.
    1554  * @param {int}                        [attributes.priority=60]             The priority for the state link in the media menu.
    1555  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
    1556  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
    1557  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
    1558  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
    1559  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
    1560  * @param {boolean}                    [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
    1561  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
    1562  * @param {boolean}                    [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
    1563  */
    1564 var Library = wp.media.controller.Library,
    1565         l10n = wp.media.view.l10n,
    1566         ReplaceImage;
    1567 
    1568 ReplaceImage = Library.extend({
    1569         defaults: _.defaults({
    1570                 id:            'replace-image',
    1571                 title:         l10n.replaceImageTitle,
    1572                 multiple:      false,
    1573                 filterable:    'uploaded',
    1574                 toolbar:       'replace',
    1575                 menu:          false,
    1576                 priority:      60,
    1577                 syncSelection: true
    1578         }, Library.prototype.defaults ),
    1579 
    1580         /**
    1581          * @since 3.9.0
    1582          *
    1583          * @param options
    1584          */
    1585         initialize: function( options ) {
    1586                 var library, comparator;
    1587 
    1588                 this.image = options.image;
    1589                 // If we haven't been provided a `library`, create a `Selection`.
    1590                 if ( ! this.get('library') ) {
    1591                         this.set( 'library', wp.media.query({ type: 'image' }) );
    1592                 }
    1593 
    1594                 Library.prototype.initialize.apply( this, arguments );
    1595 
    1596                 library    = this.get('library');
    1597                 comparator = library.comparator;
    1598 
    1599                 // Overload the library's comparator to push items that are not in
    1600                 // the mirrored query to the front of the aggregate collection.
    1601                 library.comparator = function( a, b ) {
    1602                         var aInQuery = !! this.mirroring.get( a.cid ),
    1603                                 bInQuery = !! this.mirroring.get( b.cid );
    1604 
    1605                         if ( ! aInQuery && bInQuery ) {
    1606                                 return -1;
    1607                         } else if ( aInQuery && ! bInQuery ) {
    1608                                 return 1;
    1609                         } else {
    1610                                 return comparator.apply( this, arguments );
    1611                         }
    1612                 };
    1613 
    1614                 // Add all items in the selection to the library, so any featured
    1615                 // images that are not initially loaded still appear.
    1616                 library.observe( this.get('selection') );
    1617         },
    1618 
    1619         /**
    1620          * @since 3.9.0
    1621          */
    1622         activate: function() {
    1623                 this.updateSelection();
    1624                 Library.prototype.activate.apply( this, arguments );
    1625         },
    1626 
    1627         /**
    1628          * @since 3.9.0
    1629          */
    1630         updateSelection: function() {
    1631                 var selection = this.get('selection'),
    1632                         attachment = this.image.attachment;
    1633 
    1634                 selection.reset( attachment ? [ attachment ] : [] );
    1635         }
    1636 });
    1637 
    1638 module.exports = ReplaceImage;
    1639 
    1640 },{}],14:[function(require,module,exports){
    1641 /*globals _, Backbone */
    1642 
    1643 /**
    1644  * wp.media.controller.StateMachine
    1645  *
    1646  * A state machine keeps track of state. It is in one state at a time,
    1647  * and can change from one state to another.
    1648  *
    1649  * States are stored as models in a Backbone collection.
    1650  *
    1651  * @since 3.5.0
    1652  *
    1653  * @class
    1654  * @augments Backbone.Model
    1655  * @mixin
    1656  * @mixes Backbone.Events
    1657  *
    1658  * @param {Array} states
    1659  */
    1660 var StateMachine = function( states ) {
    1661         // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
    1662         this.states = new Backbone.Collection( states );
    1663 };
    1664 
    1665 // Use Backbone's self-propagating `extend` inheritance method.
    1666 StateMachine.extend = Backbone.Model.extend;
    1667 
    1668 _.extend( StateMachine.prototype, Backbone.Events, {
    1669         /**
    1670          * Fetch a state.
    1671          *
    1672          * If no `id` is provided, returns the active state.
    1673          *
    1674          * Implicitly creates states.
    1675          *
    1676          * Ensure that the `states` collection exists so the `StateMachine`
    1677          *   can be used as a mixin.
    1678          *
    1679          * @since 3.5.0
    1680          *
    1681          * @param {string} id
    1682          * @returns {wp.media.controller.State} Returns a State model
    1683          *   from the StateMachine collection
    1684          */
    1685         state: function( id ) {
    1686                 this.states = this.states || new Backbone.Collection();
    1687 
    1688                 // Default to the active state.
    1689                 id = id || this._state;
    1690 
    1691                 if ( id && ! this.states.get( id ) ) {
    1692                         this.states.add({ id: id });
    1693                 }
    1694                 return this.states.get( id );
    1695         },
    1696 
    1697         /**
    1698          * Sets the active state.
    1699          *
    1700          * Bail if we're trying to select the current state, if we haven't
    1701          * created the `states` collection, or are trying to select a state
    1702          * that does not exist.
    1703          *
    1704          * @since 3.5.0
    1705          *
    1706          * @param {string} id
    1707          *
    1708          * @fires wp.media.controller.State#deactivate
    1709          * @fires wp.media.controller.State#activate
    1710          *
    1711          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
    1712          */
    1713         setState: function( id ) {
    1714                 var previous = this.state();
    1715 
    1716                 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
    1717                         return this;
    1718                 }
    1719 
    1720                 if ( previous ) {
    1721                         previous.trigger('deactivate');
    1722                         this._lastState = previous.id;
    1723                 }
    1724 
    1725                 this._state = id;
    1726                 this.state().trigger('activate');
    1727 
    1728                 return this;
    1729         },
    1730 
    1731         /**
    1732          * Returns the previous active state.
    1733          *
    1734          * Call the `state()` method with no parameters to retrieve the current
    1735          * active state.
    1736          *
    1737          * @since 3.5.0
    1738          *
    1739          * @returns {wp.media.controller.State} Returns a State model
    1740          *    from the StateMachine collection
    1741          */
    1742         lastState: function() {
    1743                 if ( this._lastState ) {
    1744                         return this.state( this._lastState );
    1745                 }
    1746         }
    1747 });
    1748 
    1749 // Map all event binding and triggering on a StateMachine to its `states` collection.
    1750 _.each([ 'on', 'off', 'trigger' ], function( method ) {
    1751         /**
    1752          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
    1753          */
    1754         StateMachine.prototype[ method ] = function() {
    1755                 // Ensure that the `states` collection exists so the `StateMachine`
    1756                 // can be used as a mixin.
    1757                 this.states = this.states || new Backbone.Collection();
    1758                 // Forward the method to the `states` collection.
    1759                 this.states[ method ].apply( this.states, arguments );
    1760                 return this;
    1761         };
    1762 });
    1763 
    1764 module.exports = StateMachine;
    1765 
    1766 },{}],15:[function(require,module,exports){
    1767 /*globals _, Backbone */
    1768 
    1769 /**
    1770  * wp.media.controller.State
    1771  *
    1772  * A state is a step in a workflow that when set will trigger the controllers
    1773  * for the regions to be updated as specified in the frame.
    1774  *
    1775  * A state has an event-driven lifecycle:
    1776  *
    1777  *     'ready'      triggers when a state is added to a state machine's collection.
    1778  *     'activate'   triggers when a state is activated by a state machine.
    1779  *     'deactivate' triggers when a state is deactivated by a state machine.
    1780  *     'reset'      is not triggered automatically. It should be invoked by the
    1781  *                  proper controller to reset the state to its default.
    1782  *
    1783  * @class
    1784  * @augments Backbone.Model
    1785  */
    1786 var State = Backbone.Model.extend({
    1787         /**
    1788          * Constructor.
    1789          *
    1790          * @since 3.5.0
    1791          */
    1792         constructor: function() {
    1793                 this.on( 'activate', this._preActivate, this );
    1794                 this.on( 'activate', this.activate, this );
    1795                 this.on( 'activate', this._postActivate, this );
    1796                 this.on( 'deactivate', this._deactivate, this );
    1797                 this.on( 'deactivate', this.deactivate, this );
    1798                 this.on( 'reset', this.reset, this );
    1799                 this.on( 'ready', this._ready, this );
    1800                 this.on( 'ready', this.ready, this );
    1801                 /**
    1802                  * Call parent constructor with passed arguments
    1803                  */
    1804                 Backbone.Model.apply( this, arguments );
    1805                 this.on( 'change:menu', this._updateMenu, this );
    1806         },
    1807         /**
    1808          * Ready event callback.
    1809          *
    1810          * @abstract
    1811          * @since 3.5.0
    1812          */
    1813         ready: function() {},
    1814 
    1815         /**
    1816          * Activate event callback.
    1817          *
    1818          * @abstract
    1819          * @since 3.5.0
    1820          */
    1821         activate: function() {},
    1822 
    1823         /**
    1824          * Deactivate event callback.
    1825          *
    1826          * @abstract
    1827          * @since 3.5.0
    1828          */
    1829         deactivate: function() {},
    1830 
    1831         /**
    1832          * Reset event callback.
    1833          *
    1834          * @abstract
    1835          * @since 3.5.0
    1836          */
    1837         reset: function() {},
    1838 
    1839         /**
    1840          * @access private
    1841          * @since 3.5.0
    1842          */
    1843         _ready: function() {
    1844                 this._updateMenu();
    1845         },
    1846 
    1847         /**
    1848          * @access private
    1849          * @since 3.5.0
    1850         */
    1851         _preActivate: function() {
    1852                 this.active = true;
    1853         },
    1854 
    1855         /**
    1856          * @access private
    1857          * @since 3.5.0
    1858          */
    1859         _postActivate: function() {
    1860                 this.on( 'change:menu', this._menu, this );
    1861                 this.on( 'change:titleMode', this._title, this );
    1862                 this.on( 'change:content', this._content, this );
    1863                 this.on( 'change:toolbar', this._toolbar, this );
    1864 
    1865                 this.frame.on( 'title:render:default', this._renderTitle, this );
    1866 
    1867                 this._title();
    1868                 this._menu();
    1869                 this._toolbar();
    1870                 this._content();
    1871                 this._router();
    1872         },
    1873 
    1874         /**
    1875          * @access private
    1876          * @since 3.5.0
    1877          */
    1878         _deactivate: function() {
    1879                 this.active = false;
    1880 
    1881                 this.frame.off( 'title:render:default', this._renderTitle, this );
    1882 
    1883                 this.off( 'change:menu', this._menu, this );
    1884                 this.off( 'change:titleMode', this._title, this );
    1885                 this.off( 'change:content', this._content, this );
    1886                 this.off( 'change:toolbar', this._toolbar, this );
    1887         },
    1888 
    1889         /**
    1890          * @access private
    1891          * @since 3.5.0
    1892          */
    1893         _title: function() {
    1894                 this.frame.title.render( this.get('titleMode') || 'default' );
    1895         },
    1896 
    1897         /**
    1898          * @access private
    1899          * @since 3.5.0
    1900          */
    1901         _renderTitle: function( view ) {
    1902                 view.$el.text( this.get('title') || '' );
    1903         },
    1904 
    1905         /**
    1906          * @access private
    1907          * @since 3.5.0
    1908          */
    1909         _router: function() {
    1910                 var router = this.frame.router,
    1911                         mode = this.get('router'),
    1912                         view;
    1913 
    1914                 this.frame.$el.toggleClass( 'hide-router', ! mode );
    1915                 if ( ! mode ) {
    1916                         return;
    1917                 }
    1918 
    1919                 this.frame.router.render( mode );
    1920 
    1921                 view = router.get();
    1922                 if ( view && view.select ) {
    1923                         view.select( this.frame.content.mode() );
    1924                 }
    1925         },
    1926 
    1927         /**
    1928          * @access private
    1929          * @since 3.5.0
    1930          */
    1931         _menu: function() {
    1932                 var menu = this.frame.menu,
    1933                         mode = this.get('menu'),
    1934                         view;
    1935 
    1936                 this.frame.$el.toggleClass( 'hide-menu', ! mode );
    1937                 if ( ! mode ) {
    1938                         return;
    1939                 }
    1940 
    1941                 menu.mode( mode );
    1942 
    1943                 view = menu.get();
    1944                 if ( view && view.select ) {
    1945                         view.select( this.id );
    1946                 }
    1947         },
    1948 
    1949         /**
    1950          * @access private
    1951          * @since 3.5.0
    1952          */
    1953         _updateMenu: function() {
    1954                 var previous = this.previous('menu'),
    1955                         menu = this.get('menu');
    1956 
    1957                 if ( previous ) {
    1958                         this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
    1959                 }
    1960 
    1961                 if ( menu ) {
    1962                         this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
    1963                 }
    1964         },
    1965 
    1966         /**
    1967          * Create a view in the media menu for the state.
    1968          *
    1969          * @access private
    1970          * @since 3.5.0
    1971          *
    1972          * @param {media.view.Menu} view The menu view.
    1973          */
    1974         _renderMenu: function( view ) {
    1975                 var menuItem = this.get('menuItem'),
    1976                         title = this.get('title'),
    1977                         priority = this.get('priority');
    1978 
    1979                 if ( ! menuItem && title ) {
    1980                         menuItem = { text: title };
    1981 
    1982                         if ( priority ) {
    1983                                 menuItem.priority = priority;
    1984                         }
    1985                 }
    1986 
    1987                 if ( ! menuItem ) {
    1988                         return;
    1989                 }
    1990 
    1991                 view.set( this.id, menuItem );
    1992         }
    1993 });
    1994 
    1995 _.each(['toolbar','content'], function( region ) {
    1996         /**
    1997          * @access private
    1998          */
    1999         State.prototype[ '_' + region ] = function() {
    2000                 var mode = this.get( region );
    2001                 if ( mode ) {
    2002                         this.frame[ region ].render( mode );
    2003                 }
    2004         };
    2005 });
    2006 
    2007 module.exports = State;
    2008 
    2009 },{}],16:[function(require,module,exports){
    2010 /*globals _ */
    2011 
    2012 /**
    2013  * wp.media.selectionSync
    2014  *
    2015  * Sync an attachments selection in a state with another state.
    2016  *
    2017  * Allows for selecting multiple images in the Insert Media workflow, and then
    2018  * switching to the Insert Gallery workflow while preserving the attachments selection.
    2019  *
    2020  * @mixin
    2021  */
    2022 var selectionSync = {
    2023         /**
    2024          * @since 3.5.0
    2025          */
    2026         syncSelection: function() {
    2027                 var selection = this.get('selection'),
    2028                         manager = this.frame._selection;
    2029 
    2030                 if ( ! this.get('syncSelection') || ! manager || ! selection ) {
    2031                         return;
    2032                 }
    2033 
    2034                 // If the selection supports multiple items, validate the stored
    2035                 // attachments based on the new selection's conditions. Record
    2036                 // the attachments that are not included; we'll maintain a
    2037                 // reference to those. Other attachments are considered in flux.
    2038                 if ( selection.multiple ) {
    2039                         selection.reset( [], { silent: true });
    2040                         selection.validateAll( manager.attachments );
    2041                         manager.difference = _.difference( manager.attachments.models, selection.models );
    2042                 }
    2043 
    2044                 // Sync the selection's single item with the master.
    2045                 selection.single( manager.single );
    2046         },
    2047 
    2048         /**
    2049          * Record the currently active attachments, which is a combination
    2050          * of the selection's attachments and the set of selected
    2051          * attachments that this specific selection considered invalid.
    2052          * Reset the difference and record the single attachment.
    2053          *
    2054          * @since 3.5.0
    2055          */
    2056         recordSelection: function() {
    2057                 var selection = this.get('selection'),
    2058                         manager = this.frame._selection;
    2059 
    2060                 if ( ! this.get('syncSelection') || ! manager || ! selection ) {
    2061                         return;
    2062                 }
    2063 
    2064                 if ( selection.multiple ) {
    2065                         manager.attachments.reset( selection.toArray().concat( manager.difference ) );
    2066                         manager.difference = [];
    2067                 } else {
    2068                         manager.attachments.add( selection.toArray() );
    2069                 }
    2070 
    2071                 manager.single = selection._single;
    2072         }
    2073 };
    2074 
    2075 module.exports = selectionSync;
    2076 
    2077 },{}],17:[function(require,module,exports){
    2078 /*globals wp, jQuery, _, Backbone */
    2079 
    2080 var media = wp.media,
    2081         $ = jQuery,
    2082         l10n;
    2083 
    2084 media.isTouchDevice = ( 'ontouchend' in document );
    2085 
    2086 // Link any localized strings.
    2087 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
    2088 
    2089 // Link any settings.
    2090 media.view.settings = l10n.settings || {};
    2091 delete l10n.settings;
    2092 
    2093 // Copy the `post` setting over to the model settings.
    2094 media.model.settings.post = media.view.settings.post;
    2095 
    2096 // Check if the browser supports CSS 3.0 transitions
    2097 $.support.transition = (function(){
    2098         var style = document.documentElement.style,
    2099                 transitions = {
    2100                         WebkitTransition: 'webkitTransitionEnd',
    2101                         MozTransition:    'transitionend',
    2102                         OTransition:      'oTransitionEnd otransitionend',
    2103                         transition:       'transitionend'
    2104                 }, transition;
    2105 
    2106         transition = _.find( _.keys( transitions ), function( transition ) {
    2107                 return ! _.isUndefined( style[ transition ] );
    2108         });
    2109 
    2110         return transition && {
    2111                 end: transitions[ transition ]
    2112         };
    2113 }());
    2114 
    2115 /**
    2116  * A shared event bus used to provide events into
    2117  * the media workflows that 3rd-party devs can use to hook
    2118  * in.
    2119  */
    2120 media.events = _.extend( {}, Backbone.Events );
    2121 
    2122 /**
    2123  * Makes it easier to bind events using transitions.
    2124  *
    2125  * @param {string} selector
    2126  * @param {Number} sensitivity
    2127  * @returns {Promise}
    2128  */
    2129 media.transition = function( selector, sensitivity ) {
    2130         var deferred = $.Deferred();
    2131 
    2132         sensitivity = sensitivity || 2000;
    2133 
    2134         if ( $.support.transition ) {
    2135                 if ( ! (selector instanceof $) ) {
    2136                         selector = $( selector );
    2137                 }
    2138 
    2139                 // Resolve the deferred when the first element finishes animating.
    2140                 selector.first().one( $.support.transition.end, deferred.resolve );
    2141 
    2142                 // Just in case the event doesn't trigger, fire a callback.
    2143                 _.delay( deferred.resolve, sensitivity );
    2144 
    2145         // Otherwise, execute on the spot.
    2146         } else {
    2147                 deferred.resolve();
    2148         }
    2149 
    2150         return deferred.promise();
    2151 };
    2152 
    2153 media.controller.Region = require( './controllers/region.js' );
    2154 media.controller.StateMachine = require( './controllers/state-machine.js' );
    2155 media.controller.State = require( './controllers/state.js' );
    2156 
    2157 media.selectionSync = require( './utils/selection-sync.js' );
    2158 media.controller.Library = require( './controllers/library.js' );
    2159 media.controller.ImageDetails = require( './controllers/image-details.js' );
    2160 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
    2161 media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
    2162 media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
    2163 media.controller.CollectionAdd = require( './controllers/collection-add.js' );
    2164 media.controller.FeaturedImage = require( './controllers/featured-image.js' );
    2165 media.controller.ReplaceImage = require( './controllers/replace-image.js' );
    2166 media.controller.EditImage = require( './controllers/edit-image.js' );
    2167 media.controller.MediaLibrary = require( './controllers/media-library.js' );
    2168 media.controller.Embed = require( './controllers/embed.js' );
    2169 media.controller.Cropper = require( './controllers/cropper.js' );
    2170 
    2171 media.View = require( './views/view.js' );
    2172 media.view.Frame = require( './views/frame.js' );
    2173 media.view.MediaFrame = require( './views/media-frame.js' );
    2174 media.view.MediaFrame.Select = require( './views/frame/select.js' );
    2175 media.view.MediaFrame.Post = require( './views/frame/post.js' );
    2176 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
    2177 media.view.Modal = require( './views/modal.js' );
    2178 media.view.FocusManager = require( './views/focus-manager.js' );
    2179 media.view.UploaderWindow = require( './views/uploader/window.js' );
    2180 media.view.EditorUploader = require( './views/uploader/editor.js' );
    2181 media.view.UploaderInline = require( './views/uploader/inline.js' );
    2182 media.view.UploaderStatus = require( './views/uploader/status.js' );
    2183 media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
    2184 media.view.Toolbar = require( './views/toolbar.js' );
    2185 media.view.Toolbar.Select = require( './views/toolbar/select.js' );
    2186 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
    2187 media.view.Button = require( './views/button.js' );
    2188 media.view.ButtonGroup = require( './views/button-group.js' );
    2189 media.view.PriorityList = require( './views/priority-list.js' );
    2190 media.view.MenuItem = require( './views/menu-item.js' );
    2191 media.view.Menu = require( './views/menu.js' );
    2192 media.view.RouterItem = require( './views/router-item.js' );
    2193 media.view.Router = require( './views/router.js' );
    2194 media.view.Sidebar = require( './views/sidebar.js' );
    2195 media.view.Attachment = require( './views/attachment.js' );
    2196 media.view.Attachment.Library = require( './views/attachment/library.js' );
    2197 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
    2198 media.view.Attachments = require( './views/attachments.js' );
    2199 media.view.Search = require( './views/search.js' );
    2200 media.view.AttachmentFilters = require( './views/attachment-filters.js' );
    2201 media.view.DateFilter = require( './views/attachment-filters/date.js' );
    2202 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
    2203 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
    2204 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
    2205 media.view.Selection = require( './views/selection.js' );
    2206 media.view.Attachment.Selection = require( './views/attachment/selection.js' );
    2207 media.view.Attachments.Selection = require( './views/attachments/selection.js' );
    2208 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
    2209 media.view.Settings = require( './views/settings.js' );
    2210 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
    2211 media.view.Settings.Gallery = require( './views/settings/gallery.js' );
    2212 media.view.Settings.Playlist = require( './views/settings/playlist.js' );
    2213 media.view.Attachment.Details = require( './views/attachment/details.js' );
    2214 media.view.AttachmentCompat = require( './views/attachment-compat.js' );
    2215 media.view.Iframe = require( './views/iframe.js' );
    2216 media.view.Embed = require( './views/embed.js' );
    2217 media.view.Label = require( './views/label.js' );
    2218 media.view.EmbedUrl = require( './views/embed/url.js' );
    2219 media.view.EmbedLink = require( './views/embed/link.js' );
    2220 media.view.EmbedImage = require( './views/embed/image.js' );
    2221 media.view.ImageDetails = require( './views/image-details.js' );
    2222 media.view.Cropper = require( './views/cropper.js' );
    2223 media.view.EditImage = require( './views/edit-image.js' );
    2224 media.view.Spinner = require( './views/spinner.js' );
    2225 
    2226 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/edit-image.js":4,"./controllers/embed.js":5,"./controllers/featured-image.js":6,"./controllers/gallery-add.js":7,"./controllers/gallery-edit.js":8,"./controllers/image-details.js":9,"./controllers/library.js":10,"./controllers/media-library.js":11,"./controllers/region.js":12,"./controllers/replace-image.js":13,"./controllers/state-machine.js":14,"./controllers/state.js":15,"./utils/selection-sync.js":16,"./views/attachment-compat.js":18,"./views/attachment-filters.js":19,"./views/attachment-filters/all.js":20,"./views/attachment-filters/date.js":21,"./views/attachment-filters/uploaded.js":22,"./views/attachment.js":23,"./views/attachment/details.js":24,"./views/attachment/edit-library.js":25,"./views/attachment/edit-selection.js":26,"./views/attachment/library.js":27,"./views/attachment/selection.js":28,"./views/attachments.js":29,"./views/attachments/browser.js":30,"./views/attachments/selection.js":31,"./views/button-group.js":32,"./views/button.js":33,"./views/cropper.js":34,"./views/edit-image.js":35,"./views/embed.js":36,"./views/embed/image.js":37,"./views/embed/link.js":38,"./views/embed/url.js":39,"./views/focus-manager.js":40,"./views/frame.js":41,"./views/frame/image-details.js":42,"./views/frame/post.js":43,"./views/frame/select.js":44,"./views/iframe.js":45,"./views/image-details.js":46,"./views/label.js":47,"./views/media-frame.js":48,"./views/menu-item.js":49,"./views/menu.js":50,"./views/modal.js":51,"./views/priority-list.js":52,"./views/router-item.js":53,"./views/router.js":54,"./views/search.js":55,"./views/selection.js":56,"./views/settings.js":57,"./views/settings/attachment-display.js":58,"./views/settings/gallery.js":59,"./views/settings/playlist.js":60,"./views/sidebar.js":61,"./views/spinner.js":62,"./views/toolbar.js":63,"./views/toolbar/embed.js":64,"./views/toolbar/select.js":65,"./views/uploader/editor.js":66,"./views/uploader/inline.js":67,"./views/uploader/status-error.js":68,"./views/uploader/status.js":69,"./views/uploader/window.js":70,"./views/view.js":71}],18:[function(require,module,exports){
    2227 /*globals _ */
    2228 
    2229 /**
    2230  * wp.media.view.AttachmentCompat
    2231  *
    2232  * A view to display fields added via the `attachment_fields_to_edit` filter.
    2233  *
    2234  * @class
    2235  * @augments wp.media.View
    2236  * @augments wp.Backbone.View
    2237  * @augments Backbone.View
    2238  */
    2239 var View = wp.media.View,
    2240         AttachmentCompat;
    2241 
    2242 AttachmentCompat = View.extend({
    2243         tagName:   'form',
    2244         className: 'compat-item',
    2245 
    2246         events: {
    2247                 'submit':          'preventDefault',
    2248                 'change input':    'save',
    2249                 'change select':   'save',
    2250                 'change textarea': 'save'
    2251         },
    2252 
    2253         initialize: function() {
    2254                 this.listenTo( this.model, 'change:compat', this.render );
    2255         },
    2256         /**
    2257          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    2258          */
    2259         dispose: function() {
    2260                 if ( this.$(':focus').length ) {
    2261                         this.save();
    2262                 }
    2263                 /**
    2264                  * call 'dispose' directly on the parent class
    2265                  */
    2266                 return View.prototype.dispose.apply( this, arguments );
    2267         },
    2268         /**
    2269          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
    2270          */
    2271         render: function() {
    2272                 var compat = this.model.get('compat');
    2273                 if ( ! compat || ! compat.item ) {
    2274                         return;
    2275                 }
    2276 
    2277                 this.views.detach();
    2278                 this.$el.html( compat.item );
    2279                 this.views.render();
    2280                 return this;
    2281         },
    2282         /**
    2283          * @param {Object} event
    2284          */
    2285         preventDefault: function( event ) {
    2286                 event.preventDefault();
    2287         },
    2288         /**
    2289          * @param {Object} event
    2290          */
    2291         save: function( event ) {
    2292                 var data = {};
    2293 
    2294                 if ( event ) {
    2295                         event.preventDefault();
    2296                 }
    2297 
    2298                 _.each( this.$el.serializeArray(), function( pair ) {
    2299                         data[ pair.name ] = pair.value;
    2300                 });
    2301 
    2302                 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
    2303                 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
    2304         },
    2305 
    2306         postSave: function() {
    2307                 this.controller.trigger( 'attachment:compat:ready', ['ready'] );
    2308         }
    2309 });
    2310 
    2311 module.exports = AttachmentCompat;
    2312 
    2313 },{}],19:[function(require,module,exports){
    2314 /*globals _, jQuery */
    2315 
    2316 /**
    2317  * wp.media.view.AttachmentFilters
    2318  *
    2319  * @class
    2320  * @augments wp.media.View
    2321  * @augments wp.Backbone.View
    2322  * @augments Backbone.View
    2323  */
    2324 var $ = jQuery,
    2325         AttachmentFilters;
    2326 
    2327 AttachmentFilters = wp.media.View.extend({
    2328         tagName:   'select',
    2329         className: 'attachment-filters',
    2330         id:        'media-attachment-filters',
    2331 
    2332         events: {
    2333                 change: 'change'
    2334         },
    2335 
    2336         keys: [],
    2337 
    2338         initialize: function() {
    2339                 this.createFilters();
    2340                 _.extend( this.filters, this.options.filters );
    2341 
    2342                 // Build `<option>` elements.
    2343                 this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
    2344                         return {
    2345                                 el: $( '<option></option>' ).val( value ).html( filter.text )[0],
    2346                                 priority: filter.priority || 50
    2347                         };
    2348                 }, this ).sortBy('priority').pluck('el').value() );
    2349 
    2350                 this.listenTo( this.model, 'change', this.select );
    2351                 this.select();
    2352         },
    2353 
    2354         /**
    2355          * @abstract
    2356          */
    2357         createFilters: function() {
    2358                 this.filters = {};
    2359         },
    2360 
    2361         /**
    2362          * When the selected filter changes, update the Attachment Query properties to match.
    2363          */
    2364         change: function() {
    2365                 var filter = this.filters[ this.el.value ];
    2366                 if ( filter ) {
    2367                         this.model.set( filter.props );
    2368                 }
    2369         },
    2370 
    2371         select: function() {
    2372                 var model = this.model,
    2373                         value = 'all',
    2374                         props = model.toJSON();
    2375 
    2376                 _.find( this.filters, function( filter, id ) {
    2377                         var equal = _.all( filter.props, function( prop, key ) {
    2378                                 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
    2379                         });
    2380 
    2381                         if ( equal ) {
    2382                                 return value = id;
    2383                         }
    2384                 });
    2385 
    2386                 this.$el.val( value );
    2387         }
    2388 });
    2389 
    2390 module.exports = AttachmentFilters;
    2391 
    2392 },{}],20:[function(require,module,exports){
    2393 /*globals wp */
    2394 
    2395 /**
    2396  * wp.media.view.AttachmentFilters.All
    2397  *
    2398  * @class
    2399  * @augments wp.media.view.AttachmentFilters
    2400  * @augments wp.media.View
    2401  * @augments wp.Backbone.View
    2402  * @augments Backbone.View
    2403  */
    2404 var l10n = wp.media.view.l10n,
    2405         All;
    2406 
    2407 All = wp.media.view.AttachmentFilters.extend({
    2408         createFilters: function() {
    2409                 var filters = {};
    2410 
    2411                 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
    2412                         filters[ key ] = {
    2413                                 text: text,
    2414                                 props: {
    2415                                         status:  null,
    2416                                         type:    key,
    2417                                         uploadedTo: null,
    2418                                         orderby: 'date',
    2419                                         order:   'DESC'
    2420                                 }
    2421                         };
    2422                 });
    2423 
    2424                 filters.all = {
    2425                         text:  l10n.allMediaItems,
    2426                         props: {
    2427                                 status:  null,
    2428                                 type:    null,
    2429                                 uploadedTo: null,
    2430                                 orderby: 'date',
    2431                                 order:   'DESC'
    2432                         },
    2433                         priority: 10
    2434                 };
    2435 
    2436                 if ( wp.media.view.settings.post.id ) {
    2437                         filters.uploaded = {
    2438                                 text:  l10n.uploadedToThisPost,
    2439                                 props: {
    2440                                         status:  null,
    2441                                         type:    null,
    2442                                         uploadedTo: wp.media.view.settings.post.id,
    2443                                         orderby: 'menuOrder',
    2444                                         order:   'ASC'
    2445                                 },
    2446                                 priority: 20
    2447                         };
    2448                 }
    2449 
    2450                 filters.unattached = {
    2451                         text:  l10n.unattached,
    2452                         props: {
    2453                                 status:     null,
    2454                                 uploadedTo: 0,
    2455                                 type:       null,
    2456                                 orderby:    'menuOrder',
    2457                                 order:      'ASC'
    2458                         },
    2459                         priority: 50
    2460                 };
    2461 
    2462                 if ( wp.media.view.settings.mediaTrash &&
    2463                         this.controller.isModeActive( 'grid' ) ) {
    2464 
    2465                         filters.trash = {
    2466                                 text:  l10n.trash,
    2467                                 props: {
    2468                                         uploadedTo: null,
    2469                                         status:     'trash',
    2470                                         type:       null,
    2471                                         orderby:    'date',
    2472                                         order:      'DESC'
    2473                                 },
    2474                                 priority: 50
    2475                         };
    2476                 }
    2477 
    2478                 this.filters = filters;
    2479         }
    2480 });
    2481 
    2482 module.exports = All;
    2483 
    2484 },{}],21:[function(require,module,exports){
    2485 /*globals wp, _ */
    2486 
    2487 /**
    2488  * A filter dropdown for month/dates.
    2489  *
    2490  * @class
    2491  * @augments wp.media.view.AttachmentFilters
    2492  * @augments wp.media.View
    2493  * @augments wp.Backbone.View
    2494  * @augments Backbone.View
    2495  */
    2496 var l10n = wp.media.view.l10n,
    2497         DateFilter;
    2498 
    2499 DateFilter = wp.media.view.AttachmentFilters.extend({
    2500         id: 'media-attachment-date-filters',
    2501 
    2502         createFilters: function() {
    2503                 var filters = {};
    2504                 _.each( wp.media.view.settings.months || {}, function( value, index ) {
    2505                         filters[ index ] = {
    2506                                 text: value.text,
    2507                                 props: {
    2508                                         year: value.year,
    2509                                         monthnum: value.month
    2510                                 }
    2511                         };
    2512                 });
    2513                 filters.all = {
    2514                         text:  l10n.allDates,
    2515                         props: {
    2516                                 monthnum: false,
    2517                                 year:  false
    2518                         },
    2519                         priority: 10
    2520                 };
    2521                 this.filters = filters;
    2522         }
    2523 });
    2524 
    2525 module.exports = DateFilter;
    2526 
    2527 },{}],22:[function(require,module,exports){
    2528 /*globals wp */
    2529 
    2530 /**
    2531  * wp.media.view.AttachmentFilters.Uploaded
    2532  *
    2533  * @class
    2534  * @augments wp.media.view.AttachmentFilters
    2535  * @augments wp.media.View
    2536  * @augments wp.Backbone.View
    2537  * @augments Backbone.View
    2538  */
    2539 var l10n = wp.media.view.l10n,
    2540         Uploaded;
    2541 
    2542 Uploaded = wp.media.view.AttachmentFilters.extend({
    2543         createFilters: function() {
    2544                 var type = this.model.get('type'),
    2545                         types = wp.media.view.settings.mimeTypes,
    2546                         text;
    2547 
    2548                 if ( types && type ) {
    2549                         text = types[ type ];
    2550                 }
    2551 
    2552                 this.filters = {
    2553                         all: {
    2554                                 text:  text || l10n.allMediaItems,
    2555                                 props: {
    2556                                         uploadedTo: null,
    2557                                         orderby: 'date',
    2558                                         order:   'DESC'
    2559                                 },
    2560                                 priority: 10
    2561                         },
    2562 
    2563                         uploaded: {
    2564                                 text:  l10n.uploadedToThisPost,
    2565                                 props: {
    2566                                         uploadedTo: wp.media.view.settings.post.id,
    2567                                         orderby: 'menuOrder',
    2568                                         order:   'ASC'
    2569                                 },
    2570                                 priority: 20
    2571                         },
    2572 
    2573                         unattached: {
    2574                                 text:  l10n.unattached,
    2575                                 props: {
    2576                                         uploadedTo: 0,
    2577                                         orderby: 'menuOrder',
    2578                                         order:   'ASC'
    2579                                 },
    2580                                 priority: 50
    2581                         }
    2582                 };
    2583         }
    2584 });
    2585 
    2586 module.exports = Uploaded;
    2587 
    2588 },{}],23:[function(require,module,exports){
    2589 /*globals wp, _, jQuery */
    2590 
    2591 /**
    2592  * wp.media.view.Attachment
    2593  *
    2594  * @class
    2595  * @augments wp.media.View
    2596  * @augments wp.Backbone.View
    2597  * @augments Backbone.View
    2598  */
    2599 var View = wp.media.View,
    2600         $ = jQuery,
    2601         Attachment;
    2602 
    2603 Attachment = View.extend({
    2604         tagName:   'li',
    2605         className: 'attachment',
    2606         template:  wp.template('attachment'),
    2607 
    2608         attributes: function() {
    2609                 return {
    2610                         'tabIndex':     0,
    2611                         'role':         'checkbox',
    2612                         'aria-label':   this.model.get( 'title' ),
    2613                         'aria-checked': false,
    2614                         'data-id':      this.model.get( 'id' )
    2615                 };
    2616         },
    2617 
    2618         events: {
    2619                 'click .js--select-attachment':   'toggleSelectionHandler',
    2620                 'change [data-setting]':          'updateSetting',
    2621                 'change [data-setting] input':    'updateSetting',
    2622                 'change [data-setting] select':   'updateSetting',
    2623                 'change [data-setting] textarea': 'updateSetting',
    2624                 'click .close':                   'removeFromLibrary',
    2625                 'click .check':                   'checkClickHandler',
    2626                 'click a':                        'preventDefault',
    2627                 'keydown .close':                 'removeFromLibrary',
    2628                 'keydown':                        'toggleSelectionHandler'
    2629         },
    2630 
    2631         buttons: {},
    2632 
    2633         initialize: function() {
    2634                 var selection = this.options.selection,
    2635                         options = _.defaults( this.options, {
    2636                                 rerenderOnModelChange: true
    2637                         } );
    2638 
    2639                 if ( options.rerenderOnModelChange ) {
    2640                         this.listenTo( this.model, 'change', this.render );
    2641                 } else {
    2642                         this.listenTo( this.model, 'change:percent', this.progress );
    2643                 }
    2644                 this.listenTo( this.model, 'change:title', this._syncTitle );
    2645                 this.listenTo( this.model, 'change:caption', this._syncCaption );
    2646                 this.listenTo( this.model, 'change:artist', this._syncArtist );
    2647                 this.listenTo( this.model, 'change:album', this._syncAlbum );
    2648 
    2649                 // Update the selection.
    2650                 this.listenTo( this.model, 'add', this.select );
    2651                 this.listenTo( this.model, 'remove', this.deselect );
    2652                 if ( selection ) {
    2653                         selection.on( 'reset', this.updateSelect, this );
    2654                         // Update the model's details view.
    2655                         this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
    2656                         this.details( this.model, this.controller.state().get('selection') );
    2657                 }
    2658 
    2659                 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
    2660         },
    2661         /**
    2662          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    2663          */
    2664         dispose: function() {
    2665                 var selection = this.options.selection;
    2666 
    2667                 // Make sure all settings are saved before removing the view.
    2668                 this.updateAll();
    2669 
    2670                 if ( selection ) {
    2671                         selection.off( null, null, this );
    2672                 }
    2673                 /**
    2674                  * call 'dispose' directly on the parent class
    2675                  */
    2676                 View.prototype.dispose.apply( this, arguments );
    2677                 return this;
    2678         },
    2679         /**
    2680          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    2681          */
    2682         render: function() {
    2683                 var options = _.defaults( this.model.toJSON(), {
    2684                                 orientation:   'landscape',
    2685                                 uploading:     false,
    2686                                 type:          '',
    2687                                 subtype:       '',
    2688                                 icon:          '',
    2689                                 filename:      '',
    2690                                 caption:       '',
    2691                                 title:         '',
    2692                                 dateFormatted: '',
    2693                                 width:         '',
    2694                                 height:        '',
    2695                                 compat:        false,
    2696                                 alt:           '',
    2697                                 description:   ''
    2698                         }, this.options );
    2699 
    2700                 options.buttons  = this.buttons;
    2701                 options.describe = this.controller.state().get('describe');
    2702 
    2703                 if ( 'image' === options.type ) {
    2704                         options.size = this.imageSize();
    2705                 }
    2706 
    2707                 options.can = {};
    2708                 if ( options.nonces ) {
    2709                         options.can.remove = !! options.nonces['delete'];
    2710                         options.can.save = !! options.nonces.update;
    2711                 }
    2712 
    2713                 if ( this.controller.state().get('allowLocalEdits') ) {
    2714                         options.allowLocalEdits = true;
    2715                 }
    2716 
    2717                 if ( options.uploading && ! options.percent ) {
    2718                         options.percent = 0;
    2719                 }
    2720 
    2721                 this.views.detach();
    2722                 this.$el.html( this.template( options ) );
    2723 
    2724                 this.$el.toggleClass( 'uploading', options.uploading );
    2725 
    2726                 if ( options.uploading ) {
    2727                         this.$bar = this.$('.media-progress-bar div');
    2728                 } else {
    2729                         delete this.$bar;
    2730                 }
    2731 
    2732                 // Check if the model is selected.
    2733                 this.updateSelect();
    2734 
    2735                 // Update the save status.
    2736                 this.updateSave();
    2737 
    2738                 this.views.render();
    2739 
    2740                 return this;
    2741         },
    2742 
    2743         progress: function() {
    2744                 if ( this.$bar && this.$bar.length ) {
    2745                         this.$bar.width( this.model.get('percent') + '%' );
    2746                 }
    2747         },
    2748 
    2749         /**
    2750          * @param {Object} event
    2751          */
    2752         toggleSelectionHandler: function( event ) {
    2753                 var method;
    2754 
    2755                 // Don't do anything inside inputs.
    2756                 if ( 'INPUT' === event.target.nodeName ) {
    2757                         return;
    2758                 }
    2759 
    2760                 // Catch arrow events
    2761                 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    2762                         this.controller.trigger( 'attachment:keydown:arrow', event );
    2763                         return;
    2764                 }
    2765 
    2766                 // Catch enter and space events
    2767                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    2768                         return;
    2769                 }
    2770 
    2771                 event.preventDefault();
    2772 
    2773                 // In the grid view, bubble up an edit:attachment event to the controller.
    2774                 if ( this.controller.isModeActive( 'grid' ) ) {
    2775                         if ( this.controller.isModeActive( 'edit' ) ) {
    2776                                 // Pass the current target to restore focus when closing
    2777                                 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
    2778                                 return;
    2779                         }
    2780 
    2781                         if ( this.controller.isModeActive( 'select' ) ) {
    2782                                 method = 'toggle';
    2783                         }
    2784                 }
    2785 
    2786                 if ( event.shiftKey ) {
    2787                         method = 'between';
    2788                 } else if ( event.ctrlKey || event.metaKey ) {
    2789                         method = 'toggle';
    2790                 }
    2791 
    2792                 this.toggleSelection({
    2793                         method: method
    2794                 });
    2795 
    2796                 this.controller.trigger( 'selection:toggle' );
    2797         },
    2798         /**
    2799          * @param {Object} options
    2800          */
    2801         toggleSelection: function( options ) {
    2802                 var collection = this.collection,
    2803                         selection = this.options.selection,
    2804                         model = this.model,
    2805                         method = options && options.method,
    2806                         single, models, singleIndex, modelIndex;
    2807 
    2808                 if ( ! selection ) {
    2809                         return;
    2810                 }
    2811 
    2812                 single = selection.single();
    2813                 method = _.isUndefined( method ) ? selection.multiple : method;
    2814 
    2815                 // If the `method` is set to `between`, select all models that
    2816                 // exist between the current and the selected model.
    2817                 if ( 'between' === method && single && selection.multiple ) {
    2818                         // If the models are the same, short-circuit.
    2819                         if ( single === model ) {
    2820                                 return;
    2821                         }
    2822 
    2823                         singleIndex = collection.indexOf( single );
    2824                         modelIndex  = collection.indexOf( this.model );
    2825 
    2826                         if ( singleIndex < modelIndex ) {
    2827                                 models = collection.models.slice( singleIndex, modelIndex + 1 );
    2828                         } else {
    2829                                 models = collection.models.slice( modelIndex, singleIndex + 1 );
    2830                         }
    2831 
    2832                         selection.add( models );
    2833                         selection.single( model );
    2834                         return;
    2835 
    2836                 // If the `method` is set to `toggle`, just flip the selection
    2837                 // status, regardless of whether the model is the single model.
    2838                 } else if ( 'toggle' === method ) {
    2839                         selection[ this.selected() ? 'remove' : 'add' ]( model );
    2840                         selection.single( model );
    2841                         return;
    2842                 } else if ( 'add' === method ) {
    2843                         selection.add( model );
    2844                         selection.single( model );
    2845                         return;
    2846                 }
    2847 
    2848                 // Fixes bug that loses focus when selecting a featured image
    2849                 if ( ! method ) {
    2850                         method = 'add';
    2851                 }
    2852 
    2853                 if ( method !== 'add' ) {
    2854                         method = 'reset';
    2855                 }
    2856 
    2857                 if ( this.selected() ) {
    2858                         // If the model is the single model, remove it.
    2859                         // If it is not the same as the single model,
    2860                         // it now becomes the single model.
    2861                         selection[ single === model ? 'remove' : 'single' ]( model );
    2862                 } else {
    2863                         // If the model is not selected, run the `method` on the
    2864                         // selection. By default, we `reset` the selection, but the
    2865                         // `method` can be set to `add` the model to the selection.
    2866                         selection[ method ]( model );
    2867                         selection.single( model );
    2868                 }
    2869         },
    2870 
    2871         updateSelect: function() {
    2872                 this[ this.selected() ? 'select' : 'deselect' ]();
    2873         },
    2874         /**
    2875          * @returns {unresolved|Boolean}
    2876          */
    2877         selected: function() {
    2878                 var selection = this.options.selection;
    2879                 if ( selection ) {
    2880                         return !! selection.get( this.model.cid );
    2881                 }
    2882         },
    2883         /**
    2884          * @param {Backbone.Model} model
    2885          * @param {Backbone.Collection} collection
    2886          */
    2887         select: function( model, collection ) {
    2888                 var selection = this.options.selection,
    2889                         controller = this.controller;
    2890 
    2891                 // Check if a selection exists and if it's the collection provided.
    2892                 // If they're not the same collection, bail; we're in another
    2893                 // selection's event loop.
    2894                 if ( ! selection || ( collection && collection !== selection ) ) {
    2895                         return;
    2896                 }
    2897 
    2898                 // Bail if the model is already selected.
    2899                 if ( this.$el.hasClass( 'selected' ) ) {
    2900                         return;
    2901                 }
    2902 
    2903                 // Add 'selected' class to model, set aria-checked to true.
    2904                 this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
    2905                 //  Make the checkbox tabable, except in media grid (bulk select mode).
    2906                 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
    2907                         this.$( '.check' ).attr( 'tabindex', '0' );
    2908                 }
    2909         },
    2910         /**
    2911          * @param {Backbone.Model} model
    2912          * @param {Backbone.Collection} collection
    2913          */
    2914         deselect: function( model, collection ) {
    2915                 var selection = this.options.selection;
    2916 
    2917                 // Check if a selection exists and if it's the collection provided.
    2918                 // If they're not the same collection, bail; we're in another
    2919                 // selection's event loop.
    2920                 if ( ! selection || ( collection && collection !== selection ) ) {
    2921                         return;
    2922                 }
    2923                 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
    2924                         .find( '.check' ).attr( 'tabindex', '-1' );
    2925         },
    2926         /**
    2927          * @param {Backbone.Model} model
    2928          * @param {Backbone.Collection} collection
    2929          */
    2930         details: function( model, collection ) {
    2931                 var selection = this.options.selection,
    2932                         details;
    2933 
    2934                 if ( selection !== collection ) {
    2935                         return;
    2936                 }
    2937 
    2938                 details = selection.single();
    2939                 this.$el.toggleClass( 'details', details === this.model );
    2940         },
    2941         /**
    2942          * @param {Object} event
    2943          */
    2944         preventDefault: function( event ) {
    2945                 event.preventDefault();
    2946         },
    2947         /**
    2948          * @param {string} size
    2949          * @returns {Object}
    2950          */
    2951         imageSize: function( size ) {
    2952                 var sizes = this.model.get('sizes'), matched = false;
    2953 
    2954                 size = size || 'medium';
    2955 
    2956                 // Use the provided image size if possible.
    2957                 if ( sizes ) {
    2958                         if ( sizes[ size ] ) {
    2959                                 matched = sizes[ size ];
    2960                         } else if ( sizes.large ) {
    2961                                 matched = sizes.large;
    2962                         } else if ( sizes.thumbnail ) {
    2963                                 matched = sizes.thumbnail;
    2964                         } else if ( sizes.full ) {
    2965                                 matched = sizes.full;
    2966                         }
    2967 
    2968                         if ( matched ) {
    2969                                 return _.clone( matched );
    2970                         }
    2971                 }
    2972 
    2973                 return {
    2974                         url:         this.model.get('url'),
    2975                         width:       this.model.get('width'),
    2976                         height:      this.model.get('height'),
    2977                         orientation: this.model.get('orientation')
    2978                 };
    2979         },
    2980         /**
    2981          * @param {Object} event
    2982          */
    2983         updateSetting: function( event ) {
    2984                 var $setting = $( event.target ).closest('[data-setting]'),
    2985                         setting, value;
    2986 
    2987                 if ( ! $setting.length ) {
    2988                         return;
    2989                 }
    2990 
    2991                 setting = $setting.data('setting');
    2992                 value   = event.target.value;
    2993 
    2994                 if ( this.model.get( setting ) !== value ) {
    2995                         this.save( setting, value );
    2996                 }
    2997         },
    2998 
    2999         /**
    3000          * Pass all the arguments to the model's save method.
    3001          *
    3002          * Records the aggregate status of all save requests and updates the
    3003          * view's classes accordingly.
    3004          */
    3005         save: function() {
    3006                 var view = this,
    3007                         save = this._save = this._save || { status: 'ready' },
    3008                         request = this.model.save.apply( this.model, arguments ),
    3009                         requests = save.requests ? $.when( request, save.requests ) : request;
    3010 
    3011                 // If we're waiting to remove 'Saved.', stop.
    3012                 if ( save.savedTimer ) {
    3013                         clearTimeout( save.savedTimer );
    3014                 }
    3015 
    3016                 this.updateSave('waiting');
    3017                 save.requests = requests;
    3018                 requests.always( function() {
    3019                         // If we've performed another request since this one, bail.
    3020                         if ( save.requests !== requests ) {
    3021                                 return;
    3022                         }
    3023 
    3024                         view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
    3025                         save.savedTimer = setTimeout( function() {
    3026                                 view.updateSave('ready');
    3027                                 delete save.savedTimer;
    3028                         }, 2000 );
    3029                 });
    3030         },
    3031         /**
    3032          * @param {string} status
    3033          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3034          */
    3035         updateSave: function( status ) {
    3036                 var save = this._save = this._save || { status: 'ready' };
    3037 
    3038                 if ( status && status !== save.status ) {
    3039                         this.$el.removeClass( 'save-' + save.status );
    3040                         save.status = status;
    3041                 }
    3042 
    3043                 this.$el.addClass( 'save-' + save.status );
    3044                 return this;
    3045         },
    3046 
    3047         updateAll: function() {
    3048                 var $settings = this.$('[data-setting]'),
    3049                         model = this.model,
    3050                         changed;
    3051 
    3052                 changed = _.chain( $settings ).map( function( el ) {
    3053                         var $input = $('input, textarea, select, [value]', el ),
    3054                                 setting, value;
    3055 
    3056                         if ( ! $input.length ) {
    3057                                 return;
    3058                         }
    3059 
    3060                         setting = $(el).data('setting');
    3061                         value = $input.val();
    3062 
    3063                         // Record the value if it changed.
    3064                         if ( model.get( setting ) !== value ) {
    3065                                 return [ setting, value ];
    3066                         }
    3067                 }).compact().object().value();
    3068 
    3069                 if ( ! _.isEmpty( changed ) ) {
    3070                         model.save( changed );
    3071                 }
    3072         },
    3073         /**
    3074          * @param {Object} event
    3075          */
    3076         removeFromLibrary: function( event ) {
    3077                 // Catch enter and space events
    3078                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    3079                         return;
    3080                 }
    3081 
    3082                 // Stop propagation so the model isn't selected.
    3083                 event.stopPropagation();
    3084 
    3085                 this.collection.remove( this.model );
    3086         },
    3087 
    3088         /**
    3089          * Add the model if it isn't in the selection, if it is in the selection,
    3090          * remove it.
    3091          *
    3092          * @param  {[type]} event [description]
    3093          * @return {[type]}       [description]
    3094          */
    3095         checkClickHandler: function ( event ) {
    3096                 var selection = this.options.selection;
    3097                 if ( ! selection ) {
    3098                         return;
    3099                 }
    3100                 event.stopPropagation();
    3101                 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
    3102                         selection.remove( this.model );
    3103                         // Move focus back to the attachment tile (from the check).
    3104                         this.$el.focus();
    3105                 } else {
    3106                         selection.add( this.model );
    3107                 }
    3108         }
    3109 });
    3110 
    3111 // Ensure settings remain in sync between attachment views.
    3112 _.each({
    3113         caption: '_syncCaption',
    3114         title:   '_syncTitle',
    3115         artist:  '_syncArtist',
    3116         album:   '_syncAlbum'
    3117 }, function( method, setting ) {
    3118         /**
    3119          * @param {Backbone.Model} model
    3120          * @param {string} value
    3121          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
    3122          */
    3123         Attachment.prototype[ method ] = function( model, value ) {
    3124                 var $setting = this.$('[data-setting="' + setting + '"]');
    3125 
    3126                 if ( ! $setting.length ) {
    3127                         return this;
    3128                 }
    3129 
    3130                 // If the updated value is in sync with the value in the DOM, there
    3131                 // is no need to re-render. If we're currently editing the value,
    3132                 // it will automatically be in sync, suppressing the re-render for
    3133                 // the view we're editing, while updating any others.
    3134                 if ( value === $setting.find('input, textarea, select, [value]').val() ) {
    3135                         return this;
    3136                 }
    3137 
    3138                 return this.render();
    3139         };
    3140 });
    3141 
    3142 module.exports = Attachment;
    3143 
    3144 },{}],24:[function(require,module,exports){
    3145 /*globals wp, _ */
    3146 
    3147 /**
    3148  * wp.media.view.Attachment.Details
    3149  *
    3150  * @class
    3151  * @augments wp.media.view.Attachment
    3152  * @augments wp.media.View
    3153  * @augments wp.Backbone.View
    3154  * @augments Backbone.View
    3155  */
    3156 var Attachment = wp.media.view.Attachment,
    3157         l10n = wp.media.view.l10n,
    3158         Details;
    3159 
    3160 Details = Attachment.extend({
    3161         tagName:   'div',
    3162         className: 'attachment-details',
    3163         template:  wp.template('attachment-details'),
    3164 
    3165         attributes: function() {
    3166                 return {
    3167                         'tabIndex':     0,
    3168                         'data-id':      this.model.get( 'id' )
    3169                 };
    3170         },
    3171 
    3172         events: {
    3173                 'change [data-setting]':          'updateSetting',
    3174                 'change [data-setting] input':    'updateSetting',
    3175                 'change [data-setting] select':   'updateSetting',
    3176                 'change [data-setting] textarea': 'updateSetting',
    3177                 'click .delete-attachment':       'deleteAttachment',
    3178                 'click .trash-attachment':        'trashAttachment',
    3179                 'click .untrash-attachment':      'untrashAttachment',
    3180                 'click .edit-attachment':         'editAttachment',
    3181                 'click .refresh-attachment':      'refreshAttachment',
    3182                 'keydown':                        'toggleSelectionHandler'
    3183         },
    3184 
    3185         initialize: function() {
    3186                 this.options = _.defaults( this.options, {
    3187                         rerenderOnModelChange: false
    3188                 });
    3189 
    3190                 this.on( 'ready', this.initialFocus );
    3191                 // Call 'initialize' directly on the parent class.
    3192                 Attachment.prototype.initialize.apply( this, arguments );
    3193         },
    3194 
    3195         initialFocus: function() {
    3196                 if ( ! wp.media.isTouchDevice ) {
    3197                         this.$( ':input' ).eq( 0 ).focus();
    3198                 }
    3199         },
    3200         /**
    3201          * @param {Object} event
    3202          */
    3203         deleteAttachment: function( event ) {
    3204                 event.preventDefault();
    3205 
    3206                 if ( window.confirm( l10n.warnDelete ) ) {
    3207                         this.model.destroy();
    3208                         // Keep focus inside media modal
    3209                         // after image is deleted
    3210                         this.controller.modal.focusManager.focus();
    3211                 }
    3212         },
    3213         /**
    3214          * @param {Object} event
    3215          */
    3216         trashAttachment: function( event ) {
    3217                 var library = this.controller.library;
    3218                 event.preventDefault();
    3219 
    3220                 if ( wp.media.view.settings.mediaTrash &&
    3221                         'edit-metadata' === this.controller.content.mode() ) {
    3222 
    3223                         this.model.set( 'status', 'trash' );
    3224                         this.model.save().done( function() {
    3225                                 library._requery( true );
    3226                         } );
    3227                 }  else {
    3228                         this.model.destroy();
    3229                 }
    3230         },
    3231         /**
    3232          * @param {Object} event
    3233          */
    3234         untrashAttachment: function( event ) {
    3235                 var library = this.controller.library;
    3236                 event.preventDefault();
    3237 
    3238                 this.model.set( 'status', 'inherit' );
    3239                 this.model.save().done( function() {
    3240                         library._requery( true );
    3241                 } );
    3242         },
    3243         /**
    3244          * @param {Object} event
    3245          */
    3246         editAttachment: function( event ) {
    3247                 var editState = this.controller.states.get( 'edit-image' );
    3248                 if ( window.imageEdit && editState ) {
    3249                         event.preventDefault();
    3250 
    3251                         editState.set( 'image', this.model );
    3252                         this.controller.setState( 'edit-image' );
    3253                 } else {
    3254                         this.$el.addClass('needs-refresh');
    3255                 }
    3256         },
    3257         /**
    3258          * @param {Object} event
    3259          */
    3260         refreshAttachment: function( event ) {
    3261                 this.$el.removeClass('needs-refresh');
    3262                 event.preventDefault();
    3263                 this.model.fetch();
    3264         },
    3265         /**
    3266          * When reverse tabbing(shift+tab) out of the right details panel, deliver
    3267          * the focus to the item in the list that was being edited.
    3268          *
    3269          * @param {Object} event
    3270          */
    3271         toggleSelectionHandler: function( event ) {
    3272                 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
    3273                         this.controller.trigger( 'attachment:details:shift-tab', event );
    3274                         return false;
    3275                 }
    3276 
    3277                 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
    3278                         this.controller.trigger( 'attachment:keydown:arrow', event );
    3279                         return;
    3280                 }
    3281         }
    3282 });
    3283 
    3284 module.exports = Details;
    3285 
    3286 },{}],25:[function(require,module,exports){
    3287 /*globals wp */
    3288 
    3289 /**
    3290  * wp.media.view.Attachment.EditLibrary
    3291  *
    3292  * @class
    3293  * @augments wp.media.view.Attachment
    3294  * @augments wp.media.View
    3295  * @augments wp.Backbone.View
    3296  * @augments Backbone.View
    3297  */
    3298 var EditLibrary = wp.media.view.Attachment.extend({
    3299         buttons: {
    3300                 close: true
    3301         }
    3302 });
    3303 
    3304 module.exports = EditLibrary;
    3305 
    3306 },{}],26:[function(require,module,exports){
    3307 /*globals wp */
    3308 
    3309 /**
    3310  * wp.media.view.Attachments.EditSelection
    3311  *
    3312  * @class
    3313  * @augments wp.media.view.Attachment.Selection
    3314  * @augments wp.media.view.Attachment
    3315  * @augments wp.media.View
    3316  * @augments wp.Backbone.View
    3317  * @augments Backbone.View
    3318  */
    3319 var EditSelection = wp.media.view.Attachment.Selection.extend({
    3320         buttons: {
    3321                 close: true
    3322         }
    3323 });
    3324 
    3325 module.exports = EditSelection;
    3326 
    3327 },{}],27:[function(require,module,exports){
    3328 /*globals wp */
    3329 
    3330 /**
    3331  * wp.media.view.Attachment.Library
    3332  *
    3333  * @class
    3334  * @augments wp.media.view.Attachment
    3335  * @augments wp.media.View
    3336  * @augments wp.Backbone.View
    3337  * @augments Backbone.View
    3338  */
    3339 var Library = wp.media.view.Attachment.extend({
    3340         buttons: {
    3341                 check: true
    3342         }
    3343 });
    3344 
    3345 module.exports = Library;
    3346 
    3347 },{}],28:[function(require,module,exports){
    3348 /*globals wp */
    3349 
    3350 /**
    3351  * wp.media.view.Attachment.Selection
    3352  *
    3353  * @class
    3354  * @augments wp.media.view.Attachment
    3355  * @augments wp.media.View
    3356  * @augments wp.Backbone.View
    3357  * @augments Backbone.View
    3358  */
    3359 var Selection = wp.media.view.Attachment.extend({
    3360         className: 'attachment selection',
    3361 
    3362         // On click, just select the model, instead of removing the model from
    3363         // the selection.
    3364         toggleSelection: function() {
    3365                 this.options.selection.single( this.model );
    3366         }
    3367 });
    3368 
    3369 module.exports = Selection;
    3370 
    3371 },{}],29:[function(require,module,exports){
    3372 /*globals wp, _, jQuery */
    3373 
    3374 /**
    3375  * wp.media.view.Attachments
    3376  *
    3377  * @class
    3378  * @augments wp.media.View
    3379  * @augments wp.Backbone.View
    3380  * @augments Backbone.View
    3381  */
    3382 var View = wp.media.View,
    3383         $ = jQuery,
    3384         Attachments;
    3385 
    3386 Attachments = View.extend({
    3387         tagName:   'ul',
    3388         className: 'attachments',
    3389 
    3390         attributes: {
    3391                 tabIndex: -1
    3392         },
    3393 
    3394         initialize: function() {
    3395                 this.el.id = _.uniqueId('__attachments-view-');
    3396 
    3397                 _.defaults( this.options, {
    3398                         refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
    3399                         refreshThreshold:   3,
    3400                         AttachmentView:     wp.media.view.Attachment,
    3401                         sortable:           false,
    3402                         resize:             true,
    3403                         idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
    3404                 });
    3405 
    3406                 this._viewsByCid = {};
    3407                 this.$window = $( window );
    3408                 this.resizeEvent = 'resize.media-modal-columns';
    3409 
    3410                 this.collection.on( 'add', function( attachment ) {
    3411                         this.views.add( this.createAttachmentView( attachment ), {
    3412                                 at: this.collection.indexOf( attachment )
    3413                         });
    3414                 }, this );
    3415 
    3416                 this.collection.on( 'remove', function( attachment ) {
    3417                         var view = this._viewsByCid[ attachment.cid ];
    3418                         delete this._viewsByCid[ attachment.cid ];
    3419 
    3420                         if ( view ) {
    3421                                 view.remove();
    3422                         }
    3423                 }, this );
    3424 
    3425                 this.collection.on( 'reset', this.render, this );
    3426 
    3427                 this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
    3428 
    3429                 // Throttle the scroll handler and bind this.
    3430                 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
    3431 
    3432                 this.options.scrollElement = this.options.scrollElement || this.el;
    3433                 $( this.options.scrollElement ).on( 'scroll', this.scroll );
    3434 
    3435                 this.initSortable();
    3436 
    3437                 _.bindAll( this, 'setColumns' );
    3438 
    3439                 if ( this.options.resize ) {
    3440                         this.on( 'ready', this.bindEvents );
    3441                         this.controller.on( 'open', this.setColumns );
    3442 
    3443                         // Call this.setColumns() after this view has been rendered in the DOM so
    3444                         // attachments get proper width applied.
    3445                         _.defer( this.setColumns, this );
    3446                 }
    3447         },
    3448 
    3449         bindEvents: function() {
    3450                 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
    3451         },
    3452 
    3453         attachmentFocus: function() {
    3454                 this.$( 'li:first' ).focus();
    3455         },
    3456 
    3457         restoreFocus: function() {
    3458                 this.$( 'li.selected:first' ).focus();
    3459         },
    3460 
    3461         arrowEvent: function( event ) {
    3462                 var attachments = this.$el.children( 'li' ),
    3463                         perRow = this.columns,
    3464                         index = attachments.filter( ':focus' ).index(),
    3465                         row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
    3466 
    3467                 if ( index === -1 ) {
    3468                         return;
    3469                 }
    3470 
    3471                 // Left arrow
    3472                 if ( 37 === event.keyCode ) {
    3473                         if ( 0 === index ) {
    3474                                 return;
    3475                         }
    3476                         attachments.eq( index - 1 ).focus();
    3477                 }
    3478 
    3479                 // Up arrow
    3480                 if ( 38 === event.keyCode ) {
    3481                         if ( 1 === row ) {
    3482                                 return;
    3483                         }
    3484                         attachments.eq( index - perRow ).focus();
    3485                 }
    3486 
    3487                 // Right arrow
    3488                 if ( 39 === event.keyCode ) {
    3489                         if ( attachments.length === index ) {
    3490                                 return;
    3491                         }
    3492                         attachments.eq( index + 1 ).focus();
    3493                 }
    3494 
    3495                 // Down arrow
    3496                 if ( 40 === event.keyCode ) {
    3497                         if ( Math.ceil( attachments.length / perRow ) === row ) {
    3498                                 return;
    3499                         }
    3500                         attachments.eq( index + perRow ).focus();
    3501                 }
    3502         },
    3503 
    3504         dispose: function() {
    3505                 this.collection.props.off( null, null, this );
    3506                 if ( this.options.resize ) {
    3507                         this.$window.off( this.resizeEvent );
    3508                 }
    3509 
    3510                 /**
    3511                  * call 'dispose' directly on the parent class
    3512                  */
    3513                 View.prototype.dispose.apply( this, arguments );
    3514         },
    3515 
    3516         setColumns: function() {
    3517                 var prev = this.columns,
    3518                         width = this.$el.width();
    3519 
    3520                 if ( width ) {
    3521                         this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
    3522 
    3523                         if ( ! prev || prev !== this.columns ) {
    3524                                 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
    3525                         }
    3526                 }
    3527         },
    3528 
    3529         initSortable: function() {
    3530                 var collection = this.collection;
    3531 
    3532                 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
    3533                         return;
    3534                 }
    3535 
    3536                 this.$el.sortable( _.extend({
    3537                         // If the `collection` has a `comparator`, disable sorting.
    3538                         disabled: !! collection.comparator,
    3539 
    3540                         // Change the position of the attachment as soon as the
    3541                         // mouse pointer overlaps a thumbnail.
    3542                         tolerance: 'pointer',
    3543 
    3544                         // Record the initial `index` of the dragged model.
    3545                         start: function( event, ui ) {
    3546                                 ui.item.data('sortableIndexStart', ui.item.index());
    3547                         },
    3548 
    3549                         // Update the model's index in the collection.
    3550                         // Do so silently, as the view is already accurate.
    3551                         update: function( event, ui ) {
    3552                                 var model = collection.at( ui.item.data('sortableIndexStart') ),
    3553                                         comparator = collection.comparator;
    3554 
    3555                                 // Temporarily disable the comparator to prevent `add`
    3556                                 // from re-sorting.
    3557                                 delete collection.comparator;
    3558 
    3559                                 // Silently shift the model to its new index.
    3560                                 collection.remove( model, {
    3561                                         silent: true
    3562                                 });
    3563                                 collection.add( model, {
    3564                                         silent: true,
    3565                                         at:     ui.item.index()
    3566                                 });
    3567 
    3568                                 // Restore the comparator.
    3569                                 collection.comparator = comparator;
    3570 
    3571                                 // Fire the `reset` event to ensure other collections sync.
    3572                                 collection.trigger( 'reset', collection );
    3573 
    3574                                 // If the collection is sorted by menu order,
    3575                                 // update the menu order.
    3576                                 collection.saveMenuOrder();
    3577                         }
    3578                 }, this.options.sortable ) );
    3579 
    3580                 // If the `orderby` property is changed on the `collection`,
    3581                 // check to see if we have a `comparator`. If so, disable sorting.
    3582                 collection.props.on( 'change:orderby', function() {
    3583                         this.$el.sortable( 'option', 'disabled', !! collection.comparator );
    3584                 }, this );
    3585 
    3586                 this.collection.props.on( 'change:orderby', this.refreshSortable, this );
    3587                 this.refreshSortable();
    3588         },
    3589 
    3590         refreshSortable: function() {
    3591                 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
    3592                         return;
    3593                 }
    3594 
    3595                 // If the `collection` has a `comparator`, disable sorting.
    3596                 var collection = this.collection,
    3597                         orderby = collection.props.get('orderby'),
    3598                         enabled = 'menuOrder' === orderby || ! collection.comparator;
    3599 
    3600                 this.$el.sortable( 'option', 'disabled', ! enabled );
    3601         },
    3602 
    3603         /**
    3604          * @param {wp.media.model.Attachment} attachment
    3605          * @returns {wp.media.View}
    3606          */
    3607         createAttachmentView: function( attachment ) {
    3608                 var view = new this.options.AttachmentView({
    3609                         controller:           this.controller,
    3610                         model:                attachment,
    3611                         collection:           this.collection,
    3612                         selection:            this.options.selection
    3613                 });
    3614 
    3615                 return this._viewsByCid[ attachment.cid ] = view;
    3616         },
    3617 
    3618         prepare: function() {
    3619                 // Create all of the Attachment views, and replace
    3620                 // the list in a single DOM operation.
    3621                 if ( this.collection.length ) {
    3622                         this.views.set( this.collection.map( this.createAttachmentView, this ) );
    3623 
    3624                 // If there are no elements, clear the views and load some.
    3625                 } else {
    3626                         this.views.unset();
    3627                         this.collection.more().done( this.scroll );
    3628                 }
    3629         },
    3630 
    3631         ready: function() {
    3632                 // Trigger the scroll event to check if we're within the
    3633                 // threshold to query for additional attachments.
    3634                 this.scroll();
    3635         },
    3636 
    3637         scroll: function() {
    3638                 var view = this,
    3639                         el = this.options.scrollElement,
    3640                         scrollTop = el.scrollTop,
    3641                         toolbar;
    3642 
    3643                 // The scroll event occurs on the document, but the element
    3644                 // that should be checked is the document body.
    3645                 if ( el === document ) {
    3646                         el = document.body;
    3647                         scrollTop = $(document).scrollTop();
    3648                 }
    3649 
    3650                 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
    3651                         return;
    3652                 }
    3653 
    3654                 toolbar = this.views.parent.toolbar;
    3655 
    3656                 // Show the spinner only if we are close to the bottom.
    3657                 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
    3658                         toolbar.get('spinner').show();
    3659                 }
    3660 
    3661                 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
    3662                         this.collection.more().done(function() {
    3663                                 view.scroll();
    3664                                 toolbar.get('spinner').hide();
    3665                         });
    3666                 }
    3667         }
    3668 });
    3669 
    3670 module.exports = Attachments;
    3671 
    3672 },{}],30:[function(require,module,exports){
    3673 /*globals wp, _, jQuery */
    3674 
    3675 /**
    3676  * wp.media.view.AttachmentsBrowser
    3677  *
    3678  * @class
    3679  * @augments wp.media.View
    3680  * @augments wp.Backbone.View
    3681  * @augments Backbone.View
    3682  *
    3683  * @param {object}      options
    3684  * @param {object}      [options.filters=false] Which filters to show in the browser's toolbar.
    3685  *                                              Accepts 'uploaded' and 'all'.
    3686  * @param {object}      [options.search=true]   Whether to show the search interface in the
    3687  *                                              browser's toolbar.
    3688  * @param {object}      [options.date=true]     Whether to show the date filter in the
    3689  *                                              browser's toolbar.
    3690  * @param {object}      [options.display=false] Whether to show the attachments display settings
    3691  *                                              view in the sidebar.
    3692  * @param {bool|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
    3693  *                                              Accepts true, false, and 'errors'.
    3694  */
    3695 var View = wp.media.View,
    3696         mediaTrash = wp.media.view.settings.mediaTrash,
    3697         l10n = wp.media.view.l10n,
    3698         $ = jQuery,
    3699         AttachmentsBrowser;
    3700 
    3701 AttachmentsBrowser = View.extend({
    3702         tagName:   'div',
    3703         className: 'attachments-browser',
    3704 
    3705         initialize: function() {
    3706                 _.defaults( this.options, {
    3707                         filters: false,
    3708                         search:  true,
    3709                         date:    true,
    3710                         display: false,
    3711                         sidebar: true,
    3712                         AttachmentView: wp.media.view.Attachment.Library
    3713                 });
    3714 
    3715                 this.listenTo( this.controller, 'toggle:upload:attachment', _.bind( this.toggleUploader, this ) );
    3716                 this.controller.on( 'edit:selection', this.editSelection );
    3717                 this.createToolbar();
    3718                 if ( this.options.sidebar ) {
    3719                         this.createSidebar();
    3720                 }
    3721                 this.createUploader();
    3722                 this.createAttachments();
    3723                 this.updateContent();
    3724 
    3725                 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
    3726                         this.$el.addClass( 'hide-sidebar' );
    3727 
    3728                         if ( 'errors' === this.options.sidebar ) {
    3729                                 this.$el.addClass( 'sidebar-for-errors' );
    3730                         }
    3731                 }
    3732 
    3733                 this.collection.on( 'add remove reset', this.updateContent, this );
    3734         },
    3735 
    3736         editSelection: function( modal ) {
    3737                 modal.$( '.media-button-backToLibrary' ).focus();
    3738         },
    3739 
    3740         /**
    3741          * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
    3742          */
    3743         dispose: function() {
    3744                 this.options.selection.off( null, null, this );
    3745                 View.prototype.dispose.apply( this, arguments );
    3746                 return this;
    3747         },
    3748 
    3749         createToolbar: function() {
    3750                 var LibraryViewSwitcher, Filters, toolbarOptions;
    3751 
    3752                 toolbarOptions = {
    3753                         controller: this.controller
    3754                 };
    3755 
    3756                 if ( this.controller.isModeActive( 'grid' ) ) {
    3757                         toolbarOptions.className = 'media-toolbar wp-filter';
    3758                 }
    3759 
    3760                 /**
    3761                 * @member {wp.media.view.Toolbar}
    3762                 */
    3763                 this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
    3764 
    3765                 this.views.add( this.toolbar );
    3766 
    3767                 this.toolbar.set( 'spinner', new wp.media.view.Spinner({
    3768                         priority: -60
    3769                 }) );
    3770 
    3771                 if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
    3772                         // "Filters" will return a <select>, need to render
    3773                         // screen reader text before
    3774                         this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
    3775                                 value: l10n.filterByType,
    3776                                 attributes: {
    3777                                         'for':  'media-attachment-filters'
    3778                                 },
    3779                                 priority:   -80
    3780                         }).render() );
    3781 
    3782                         if ( 'uploaded' === this.options.filters ) {
    3783                                 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({
    3784                                         controller: this.controller,
    3785                                         model:      this.collection.props,
    3786                                         priority:   -80
    3787                                 }).render() );
    3788                         } else {
    3789                                 Filters = new wp.media.view.AttachmentFilters.All({
    3790                                         controller: this.controller,
    3791                                         model:      this.collection.props,
    3792                                         priority:   -80
    3793                                 });
    3794 
    3795                                 this.toolbar.set( 'filters', Filters.render() );
    3796                         }
    3797                 }
    3798 
    3799                 // Feels odd to bring the global media library switcher into the Attachment
    3800                 // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
    3801                 // which the controller can tap into and add this view?
    3802                 if ( this.controller.isModeActive( 'grid' ) ) {
    3803                         LibraryViewSwitcher = View.extend({
    3804                                 className: 'view-switch media-grid-view-switch',
    3805                                 template: wp.template( 'media-library-view-switcher')
    3806                         });
    3807 
    3808                         this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
    3809                                 controller: this.controller,
    3810                                 priority: -90
    3811                         }).render() );
    3812 
    3813                         // DateFilter is a <select>, screen reader text needs to be rendered before
    3814                         this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
    3815                                 value: l10n.filterByDate,
    3816                                 attributes: {
    3817                                         'for': 'media-attachment-date-filters'
    3818                                 },
    3819                                 priority: -75
    3820                         }).render() );
    3821                         this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
    3822                                 controller: this.controller,
    3823                                 model:      this.collection.props,
    3824                                 priority: -75
    3825                         }).render() );
    3826 
    3827                         // BulkSelection is a <div> with subviews, including screen reader text
    3828                         this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
    3829                                 text: l10n.bulkSelect,
    3830                                 controller: this.controller,
    3831                                 priority: -70
    3832                         }).render() );
    3833 
    3834                         this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
    3835                                 filters: Filters,
    3836                                 style: 'primary',
    3837                                 disabled: true,
    3838                                 text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
    3839                                 controller: this.controller,
    3840                                 priority: -60,
    3841                                 click: function() {
    3842                                         var changed = [], removed = [],
    3843                                                 selection = this.controller.state().get( 'selection' ),
    3844                                                 library = this.controller.state().get( 'library' );
    3845 
    3846                                         if ( ! selection.length ) {
    3847                                                 return;
    3848                                         }
    3849 
    3850                                         if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
    3851                                                 return;
    3852                                         }
    3853 
    3854                                         if ( mediaTrash &&
    3855                                                 'trash' !== selection.at( 0 ).get( 'status' ) &&
    3856                                                 ! window.confirm( l10n.warnBulkTrash ) ) {
    3857 
    3858                                                 return;
    3859                                         }
    3860 
    3861                                         selection.each( function( model ) {
    3862                                                 if ( ! model.get( 'nonces' )['delete'] ) {
    3863                                                         removed.push( model );
    3864                                                         return;
    3865                                                 }
    3866 
    3867                                                 if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
    3868                                                         model.set( 'status', 'inherit' );
    3869                                                         changed.push( model.save() );
    3870                                                         removed.push( model );
    3871                                                 } else if ( mediaTrash ) {
    3872                                                         model.set( 'status', 'trash' );
    3873                                                         changed.push( model.save() );
    3874                                                         removed.push( model );
    3875                                                 } else {
    3876                                                         model.destroy({wait: true});
    3877                                                 }
    3878                                         } );
    3879 
    3880                                         if ( changed.length ) {
    3881                                                 selection.remove( removed );
    3882 
    3883                                                 $.when.apply( null, changed ).then( _.bind( function() {
    3884                                                         library._requery( true );
    3885                                                         this.controller.trigger( 'selection:action:done' );
    3886                                                 }, this ) );
    3887                                         } else {
    3888                                                 this.controller.trigger( 'selection:action:done' );
    3889                                         }
    3890                                 }
    3891                         }).render() );
    3892 
    3893                         if ( mediaTrash ) {
    3894                                 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
    3895                                         filters: Filters,
    3896                                         style: 'primary',
    3897                                         disabled: true,
    3898                                         text: l10n.deleteSelected,
    3899                                         controller: this.controller,
    3900                                         priority: -55,
    3901                                         click: function() {
    3902                                                 var removed = [], selection = this.controller.state().get( 'selection' );
    3903 
    3904                                                 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
    3905                                                         return;
    3906                                                 }
    3907 
    3908                                                 selection.each( function( model ) {
    3909                                                         if ( ! model.get( 'nonces' )['delete'] ) {
    3910                                                                 removed.push( model );
    3911                                                                 return;
    3912                                                         }
    3913 
    3914                                                         model.destroy();
    3915                                                 } );
    3916 
    3917                                                 selection.remove( removed );
    3918                                                 this.controller.trigger( 'selection:action:done' );
    3919                                         }
    3920                                 }).render() );
    3921                         }
    3922 
    3923                 } else if ( this.options.date ) {
    3924                         // DateFilter is a <select>, screen reader text needs to be rendered before
    3925                         this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
    3926                                 value: l10n.filterByDate,
    3927                                 attributes: {
    3928                                         'for': 'media-attachment-date-filters'
    3929                                 },
    3930                                 priority: -75
    3931                         }).render() );
    3932                         this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
    3933                                 controller: this.controller,
    3934                                 model:      this.collection.props,
    3935                                 priority: -75
    3936                         }).render() );
    3937                 }
    3938 
    3939                 if ( this.options.search ) {
    3940                         // Search is an input, screen reader text needs to be rendered before
    3941                         this.toolbar.set( 'searchLabel', new wp.media.view.Label({
    3942                                 value: l10n.searchMediaLabel,
    3943                                 attributes: {
    3944                                         'for': 'media-search-input'
    3945                                 },
    3946                                 priority:   60
    3947                         }).render() );
    3948                         this.toolbar.set( 'search', new wp.media.view.Search({
    3949                                 controller: this.controller,
    3950                                 model:      this.collection.props,
    3951                                 priority:   60
    3952                         }).render() );
    3953                 }
    3954 
    3955                 if ( this.options.dragInfo ) {
    3956                         this.toolbar.set( 'dragInfo', new View({
    3957                                 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
    3958                                 priority: -40
    3959                         }) );
    3960                 }
    3961 
    3962                 if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
    3963                         this.toolbar.set( 'suggestedDimensions', new View({
    3964                                 el: $( '<div class="instructions">' + l10n.suggestedDimensions + ' ' + this.options.suggestedWidth + ' &times; ' + this.options.suggestedHeight + '</div>' )[0],
    3965                                 priority: -40
    3966                         }) );
    3967                 }
    3968         },
    3969 
    3970         updateContent: function() {
    3971                 var view = this,
    3972                         noItemsView;
    3973 
    3974                 if ( this.controller.isModeActive( 'grid' ) ) {
    3975                         noItemsView = view.attachmentsNoResults;
    3976                 } else {
    3977                         noItemsView = view.uploader;
    3978                 }
    3979 
    3980                 if ( ! this.collection.length ) {
    3981                         this.toolbar.get( 'spinner' ).show();
    3982                         this.dfd = this.collection.more().done( function() {
    3983                                 if ( ! view.collection.length ) {
    3984                                         noItemsView.$el.removeClass( 'hidden' );
    3985                                 } else {
    3986                                         noItemsView.$el.addClass( 'hidden' );
    3987                                 }
    3988                                 view.toolbar.get( 'spinner' ).hide();
    3989                         } );
    3990                 } else {
    3991                         noItemsView.$el.addClass( 'hidden' );
    3992                         view.toolbar.get( 'spinner' ).hide();
    3993                 }
    3994         },
    3995 
    3996         createUploader: function() {
    3997                 this.uploader = new wp.media.view.UploaderInline({
    3998                         controller: this.controller,
    3999                         status:     false,
    4000                         message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
    4001                         canClose:   this.controller.isModeActive( 'grid' )
    4002                 });
    4003 
    4004                 this.uploader.hide();
    4005                 this.views.add( this.uploader );
    4006         },
    4007 
    4008         toggleUploader: function() {
    4009                 if ( this.uploader.$el.hasClass( 'hidden' ) ) {
    4010                         this.uploader.show();
    4011                 } else {
    4012                         this.uploader.hide();
    4013                 }
    4014         },
    4015 
    4016         createAttachments: function() {
    4017                 this.attachments = new wp.media.view.Attachments({
    4018                         controller:           this.controller,
    4019                         collection:           this.collection,
    4020                         selection:            this.options.selection,
    4021                         model:                this.model,
    4022                         sortable:             this.options.sortable,
    4023                         scrollElement:        this.options.scrollElement,
    4024                         idealColumnWidth:     this.options.idealColumnWidth,
    4025 
    4026                         // The single `Attachment` view to be used in the `Attachments` view.
    4027                         AttachmentView: this.options.AttachmentView
    4028                 });
    4029 
    4030                 // Add keydown listener to the instance of the Attachments view
    4031                 this.attachments.listenTo( this.controller, 'attachment:keydown:arrow',     this.attachments.arrowEvent );
    4032                 this.attachments.listenTo( this.controller, 'attachment:details:shift-tab', this.attachments.restoreFocus );
    4033 
    4034                 this.views.add( this.attachments );
    4035 
    4036 
    4037                 if ( this.controller.isModeActive( 'grid' ) ) {
    4038                         this.attachmentsNoResults = new View({
    4039                                 controller: this.controller,
    4040                                 tagName: 'p'
    4041                         });
    4042 
    4043                         this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
    4044                         this.attachmentsNoResults.$el.html( l10n.noMedia );
    4045 
    4046                         this.views.add( this.attachmentsNoResults );
    4047                 }
    4048         },
    4049 
    4050         createSidebar: function() {
    4051                 var options = this.options,
    4052                         selection = options.selection,
    4053                         sidebar = this.sidebar = new wp.media.view.Sidebar({
    4054                                 controller: this.controller
    4055                         });
    4056 
    4057                 this.views.add( sidebar );
    4058 
    4059                 if ( this.controller.uploader ) {
    4060                         sidebar.set( 'uploads', new wp.media.view.UploaderStatus({
    4061                                 controller: this.controller,
    4062                                 priority:   40
    4063                         }) );
    4064                 }
    4065 
    4066                 selection.on( 'selection:single', this.createSingle, this );
    4067                 selection.on( 'selection:unsingle', this.disposeSingle, this );
    4068 
    4069                 if ( selection.single() ) {
    4070                         this.createSingle();
    4071                 }
    4072         },
    4073 
    4074         createSingle: function() {
    4075                 var sidebar = this.sidebar,
    4076                         single = this.options.selection.single();
    4077 
    4078                 sidebar.set( 'details', new wp.media.view.Attachment.Details({
    4079                         controller: this.controller,
    4080                         model:      single,
    4081                         priority:   80
    4082                 }) );
    4083 
    4084                 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({
    4085                         controller: this.controller,
    4086                         model:      single,
    4087                         priority:   120
    4088                 }) );
    4089 
    4090                 if ( this.options.display ) {
    4091                         sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({
    4092                                 controller:   this.controller,
    4093                                 model:        this.model.display( single ),
    4094                                 attachment:   single,
    4095                                 priority:     160,
    4096                                 userSettings: this.model.get('displayUserSettings')
    4097                         }) );
    4098                 }
    4099 
    4100                 // Show the sidebar on mobile
    4101                 if ( this.model.id === 'insert' ) {
    4102                         sidebar.$el.addClass( 'visible' );
    4103                 }
    4104         },
    4105 
    4106         disposeSingle: function() {
    4107                 var sidebar = this.sidebar;
    4108                 sidebar.unset('details');
    4109                 sidebar.unset('compat');
    4110                 sidebar.unset('display');
    4111                 // Hide the sidebar on mobile
    4112                 sidebar.$el.removeClass( 'visible' );
    4113         }
    4114 });
    4115 
    4116 module.exports = AttachmentsBrowser;
    4117 
    4118 },{}],31:[function(require,module,exports){
    4119 /*globals wp, _ */
    4120 
    4121 /**
    4122  * wp.media.view.Attachments.Selection
    4123  *
    4124  * @class
    4125  * @augments wp.media.view.Attachments
    4126  * @augments wp.media.View
    4127  * @augments wp.Backbone.View
    4128  * @augments Backbone.View
    4129  */
    4130 var Attachments = wp.media.view.Attachments,
    4131         Selection;
    4132 
    4133 Selection = Attachments.extend({
    4134         events: {},
    4135         initialize: function() {
    4136                 _.defaults( this.options, {
    4137                         sortable:   false,
    4138                         resize:     false,
    4139 
    4140                         // The single `Attachment` view to be used in the `Attachments` view.
    4141                         AttachmentView: wp.media.view.Attachment.Selection
    4142                 });
    4143                 // Call 'initialize' directly on the parent class.
    4144                 return Attachments.prototype.initialize.apply( this, arguments );
    4145         }
    4146 });
    4147 
    4148 module.exports = Selection;
    4149 
    4150 },{}],32:[function(require,module,exports){
    4151 /*globals _, Backbone */
    4152 
    4153 /**
    4154  * wp.media.view.ButtonGroup
    4155  *
    4156  * @class
    4157  * @augments wp.media.View
    4158  * @augments wp.Backbone.View
    4159  * @augments Backbone.View
    4160  */
    4161 var $ = Backbone.$,
    4162         ButtonGroup;
    4163 
    4164 ButtonGroup = wp.media.View.extend({
    4165         tagName:   'div',
    4166         className: 'button-group button-large media-button-group',
    4167 
    4168         initialize: function() {
    4169                 /**
    4170                  * @member {wp.media.view.Button[]}
    4171                  */
    4172                 this.buttons = _.map( this.options.buttons || [], function( button ) {
    4173                         if ( button instanceof Backbone.View ) {
    4174                                 return button;
    4175                         } else {
    4176                                 return new wp.media.view.Button( button ).render();
    4177                         }
    4178                 });
    4179 
    4180                 delete this.options.buttons;
    4181 
    4182                 if ( this.options.classes ) {
    4183                         this.$el.addClass( this.options.classes );
    4184                 }
    4185         },
    4186 
    4187         /**
    4188          * @returns {wp.media.view.ButtonGroup}
    4189          */
    4190         render: function() {
    4191                 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
    4192                 return this;
    4193         }
    4194 });
    4195 
    4196 module.exports = ButtonGroup;
    4197 
    4198 },{}],33:[function(require,module,exports){
    4199 /*globals _, Backbone */
    4200 
    4201 /**
    4202  * wp.media.view.Button
    4203  *
    4204  * @class
    4205  * @augments wp.media.View
    4206  * @augments wp.Backbone.View
    4207  * @augments Backbone.View
    4208  */
    4209 var Button = wp.media.View.extend({
    4210         tagName:    'a',
    4211         className:  'media-button',
    4212         attributes: { href: '#' },
    4213 
    4214         events: {
    4215                 'click': 'click'
    4216         },
    4217 
    4218         defaults: {
    4219                 text:     '',
    4220                 style:    '',
    4221                 size:     'large',
    4222                 disabled: false
    4223         },
    4224 
    4225         initialize: function() {
    4226                 /**
    4227                  * Create a model with the provided `defaults`.
    4228                  *
    4229                  * @member {Backbone.Model}
    4230                  */
    4231                 this.model = new Backbone.Model( this.defaults );
    4232 
    4233                 // If any of the `options` have a key from `defaults`, apply its
    4234                 // value to the `model` and remove it from the `options object.
    4235                 _.each( this.defaults, function( def, key ) {
    4236                         var value = this.options[ key ];
    4237                         if ( _.isUndefined( value ) ) {
    4238                                 return;
    4239                         }
    4240 
    4241                         this.model.set( key, value );
    4242                         delete this.options[ key ];
    4243                 }, this );
    4244 
    4245                 this.listenTo( this.model, 'change', this.render );
    4246         },
    4247         /**
    4248          * @returns {wp.media.view.Button} Returns itself to allow chaining
    4249          */
    4250         render: function() {
    4251                 var classes = [ 'button', this.className ],
    4252                         model = this.model.toJSON();
    4253 
    4254                 if ( model.style ) {
    4255                         classes.push( 'button-' + model.style );
    4256                 }
    4257 
    4258                 if ( model.size ) {
    4259                         classes.push( 'button-' + model.size );
    4260                 }
    4261 
    4262                 classes = _.uniq( classes.concat( this.options.classes ) );
    4263                 this.el.className = classes.join(' ');
    4264 
    4265                 this.$el.attr( 'disabled', model.disabled );
    4266                 this.$el.text( this.model.get('text') );
    4267 
    4268                 return this;
    4269         },
    4270         /**
    4271          * @param {Object} event
    4272          */
    4273         click: function( event ) {
    4274                 if ( '#' === this.attributes.href ) {
    4275                         event.preventDefault();
    4276                 }
    4277 
    4278                 if ( this.options.click && ! this.model.get('disabled') ) {
    4279                         this.options.click.apply( this, arguments );
    4280                 }
    4281         }
    4282 });
    4283 
    4284 module.exports = Button;
    4285 
    4286 },{}],34:[function(require,module,exports){
    4287 /*globals wp, _, jQuery */
    4288 
    4289 /**
    4290  * wp.media.view.Cropper
    4291  *
    4292  * Uses the imgAreaSelect plugin to allow a user to crop an image.
    4293  *
    4294  * Takes imgAreaSelect options from
    4295  * wp.customize.HeaderControl.calculateImageSelectOptions via
    4296  * wp.customize.HeaderControl.openMM.
    4297  *
    4298  * @class
    4299  * @augments wp.media.View
    4300  * @augments wp.Backbone.View
    4301  * @augments Backbone.View
    4302  */
    4303 var View = wp.media.View,
    4304         UploaderStatus = wp.media.view.UploaderStatus,
    4305         l10n = wp.media.view.l10n,
    4306         $ = jQuery,
    4307         Cropper;
    4308 
    4309 Cropper = View.extend({
    4310         className: 'crop-content',
    4311         template: wp.template('crop-content'),
    4312         initialize: function() {
    4313                 _.bindAll(this, 'onImageLoad');
    4314         },
    4315         ready: function() {
    4316                 this.controller.frame.on('content:error:crop', this.onError, this);
    4317                 this.$image = this.$el.find('.crop-image');
    4318                 this.$image.on('load', this.onImageLoad);
    4319                 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
    4320         },
    4321         remove: function() {
    4322                 $(window).off('resize.cropper');
    4323                 this.$el.remove();
    4324                 this.$el.off();
    4325                 View.prototype.remove.apply(this, arguments);
    4326         },
    4327         prepare: function() {
    4328                 return {
    4329                         title: l10n.cropYourImage,
    4330                         url: this.options.attachment.get('url')
    4331                 };
    4332         },
    4333         onImageLoad: function() {
    4334                 var imgOptions = this.controller.get('imgSelectOptions');
    4335                 if (typeof imgOptions === 'function') {
    4336                         imgOptions = imgOptions(this.options.attachment, this.controller);
    4337                 }
    4338 
    4339                 imgOptions = _.extend(imgOptions, {parent: this.$el});
    4340                 this.trigger('image-loaded');
    4341                 this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
    4342         },
    4343         onError: function() {
    4344                 var filename = this.options.attachment.get('filename');
    4345 
    4346                 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    4347                         filename: UploaderStatus.prototype.filename(filename),
    4348                         message: window._wpMediaViewsL10n.cropError
    4349                 }), { at: 0 });
    4350         }
    4351 });
    4352 
    4353 module.exports = Cropper;
    4354 
    4355 },{}],35:[function(require,module,exports){
    4356 /*globals wp, _ */
    4357 
    4358 /**
    4359  * wp.media.view.EditImage
    4360  *
    4361  * @class
    4362  * @augments wp.media.View
    4363  * @augments wp.Backbone.View
    4364  * @augments Backbone.View
    4365  */
    4366 var View = wp.media.View,
    4367         EditImage;
    4368 
    4369 EditImage = View.extend({
    4370         className: 'image-editor',
    4371         template: wp.template('image-editor'),
    4372 
    4373         initialize: function( options ) {
    4374                 this.editor = window.imageEdit;
    4375                 this.controller = options.controller;
    4376                 View.prototype.initialize.apply( this, arguments );
    4377         },
    4378 
    4379         prepare: function() {
    4380                 return this.model.toJSON();
    4381         },
    4382 
    4383         loadEditor: function() {
    4384                 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
    4385                 dfd.done( _.bind( this.focus, this ) );
    4386         },
    4387 
    4388         focus: function() {
    4389                 this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
    4390         },
    4391 
    4392         back: function() {
    4393                 var lastState = this.controller.lastState();
    4394                 this.controller.setState( lastState );
    4395         },
    4396 
    4397         refresh: function() {
    4398                 this.model.fetch();
    4399         },
    4400 
    4401         save: function() {
    4402                 var lastState = this.controller.lastState();
    4403 
    4404                 this.model.fetch().done( _.bind( function() {
    4405                         this.controller.setState( lastState );
    4406                 }, this ) );
    4407         }
    4408 
    4409 });
    4410 
    4411 module.exports = EditImage;
    4412 
    4413 },{}],36:[function(require,module,exports){
    4414 /**
    4415  * wp.media.view.Embed
    4416  *
    4417  * @class
    4418  * @augments wp.media.View
    4419  * @augments wp.Backbone.View
    4420  * @augments Backbone.View
    4421  */
    4422 var Embed = wp.media.View.extend({
    4423         className: 'media-embed',
    4424 
    4425         initialize: function() {
    4426                 /**
    4427                  * @member {wp.media.view.EmbedUrl}
    4428                  */
    4429                 this.url = new wp.media.view.EmbedUrl({
    4430                         controller: this.controller,
    4431                         model:      this.model.props
    4432                 }).render();
    4433 
    4434                 this.views.set([ this.url ]);
    4435                 this.refresh();
    4436                 this.listenTo( this.model, 'change:type', this.refresh );
    4437                 this.listenTo( this.model, 'change:loading', this.loading );
    4438         },
    4439 
    4440         /**
    4441          * @param {Object} view
    4442          */
    4443         settings: function( view ) {
    4444                 if ( this._settings ) {
    4445                         this._settings.remove();
    4446                 }
    4447                 this._settings = view;
    4448                 this.views.add( view );
    4449         },
    4450 
    4451         refresh: function() {
    4452                 var type = this.model.get('type'),
    4453                         constructor;
    4454 
    4455                 if ( 'image' === type ) {
    4456                         constructor = wp.media.view.EmbedImage;
    4457                 } else if ( 'link' === type ) {
    4458                         constructor = wp.media.view.EmbedLink;
    4459                 } else {
    4460                         return;
    4461                 }
    4462 
    4463                 this.settings( new constructor({
    4464                         controller: this.controller,
    4465                         model:      this.model.props,
    4466                         priority:   40
    4467                 }) );
    4468         },
    4469 
    4470         loading: function() {
    4471                 this.$el.toggleClass( 'embed-loading', this.model.get('loading') );
    4472         }
    4473 });
    4474 
    4475 module.exports = Embed;
    4476 
    4477 },{}],37:[function(require,module,exports){
    4478 /*globals wp */
    4479 
    4480 /**
    4481  * wp.media.view.EmbedImage
    4482  *
    4483  * @class
    4484  * @augments wp.media.view.Settings.AttachmentDisplay
    4485  * @augments wp.media.view.Settings
    4486  * @augments wp.media.View
    4487  * @augments wp.Backbone.View
    4488  * @augments Backbone.View
    4489  */
    4490 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    4491         EmbedImage;
    4492 
    4493 EmbedImage = AttachmentDisplay.extend({
    4494         className: 'embed-media-settings',
    4495         template:  wp.template('embed-image-settings'),
    4496 
    4497         initialize: function() {
    4498                 /**
    4499                  * Call `initialize` directly on parent class with passed arguments
    4500                  */
    4501                 AttachmentDisplay.prototype.initialize.apply( this, arguments );
    4502                 this.listenTo( this.model, 'change:url', this.updateImage );
    4503         },
    4504 
    4505         updateImage: function() {
    4506                 this.$('img').attr( 'src', this.model.get('url') );
    4507         }
    4508 });
    4509 
    4510 module.exports = EmbedImage;
    4511 
    4512 },{}],38:[function(require,module,exports){
    4513 /*globals wp, _, jQuery */
    4514 
    4515 /**
    4516  * wp.media.view.EmbedLink
    4517  *
    4518  * @class
    4519  * @augments wp.media.view.Settings
    4520  * @augments wp.media.View
    4521  * @augments wp.Backbone.View
    4522  * @augments Backbone.View
    4523  */
    4524 var $ = jQuery,
    4525         EmbedLink;
    4526 
    4527 EmbedLink = wp.media.view.Settings.extend({
    4528         className: 'embed-link-settings',
    4529         template:  wp.template('embed-link-settings'),
    4530 
    4531         initialize: function() {
    4532                 this.spinner = $('<span class="spinner" />');
    4533                 this.$el.append( this.spinner[0] );
    4534                 this.listenTo( this.model, 'change:url change:width change:height', this.updateoEmbed );
    4535         },
    4536 
    4537         updateoEmbed: _.debounce( function() {
    4538                 var url = this.model.get( 'url' );
    4539 
    4540                 // clear out previous results
    4541                 this.$('.embed-container').hide().find('.embed-preview').empty();
    4542                 this.$( '.setting' ).hide();
    4543 
    4544                 // only proceed with embed if the field contains more than 6 characters
    4545                 if ( url && url.length < 6 ) {
    4546                         return;
    4547                 }
    4548 
    4549                 this.spinner.show();
    4550 
    4551                 this.fetch();
    4552         }, 600 ),
    4553 
    4554         fetch: function() {
    4555                 var embed;
    4556 
    4557                 // check if they haven't typed in 500 ms
    4558                 if ( $('#embed-url-field').val() !== this.model.get('url') ) {
    4559                         return;
    4560                 }
    4561 
    4562                 embed = new wp.shortcode({
    4563                         tag: 'embed',
    4564                         attrs: _.pick( this.model.attributes, [ 'width', 'height', 'src' ] ),
    4565                         content: this.model.get('url')
    4566                 });
    4567 
    4568                 wp.ajax.send( 'parse-embed', {
    4569                         data : {
    4570                                 post_ID: wp.media.view.settings.post.id,
    4571                                 shortcode: embed.string()
    4572                         }
    4573                 } )
    4574                         .done( _.bind( this.renderoEmbed, this ) )
    4575                         .fail( _.bind( this.renderFail, this ) );
    4576         },
    4577 
    4578         renderFail: function () {
    4579                 this.$( '.setting' ).hide().filter( '.link-text' ).show();
    4580         },
    4581 
    4582         renderoEmbed: function( response ) {
    4583                 var html = ( response && response.body ) || '',
    4584                         attr = {},
    4585                         opts = { silent: true };
    4586 
    4587                 this.$( '.setting' ).hide()
    4588                         .filter( '.link-text' )[ html ? 'hide' : 'show' ]();
    4589 
    4590                 if ( response && response.attr ) {
    4591                         attr = response.attr;
    4592 
    4593                         _.each( [ 'width', 'height' ], function ( key ) {
    4594                                 var $el = this.$( '.setting.' + key ),
    4595                                         value = attr[ key ];
    4596 
    4597                                 if ( value ) {
    4598                                         this.model.set( key, value, opts );
    4599                                         $el.show().find( 'input' ).val( value );
    4600                                 } else {
    4601                                         this.model.unset( key, opts );
    4602                                         $el.hide().find( 'input' ).val( '' );
    4603                                 }
    4604                         }, this );
    4605                 } else {
    4606                         this.model.unset( 'height', opts );
    4607                         this.model.unset( 'width', opts );
    4608                 }
    4609 
    4610                 this.spinner.hide();
    4611 
    4612                 this.$('.embed-container').show().find('.embed-preview').html( html );
    4613         }
    4614 });
    4615 
    4616 module.exports = EmbedLink;
    4617 
    4618 },{}],39:[function(require,module,exports){
    4619 /*globals wp, _, jQuery */
    4620 
    4621 /**
    4622  * wp.media.view.EmbedUrl
    4623  *
    4624  * @class
    4625  * @augments wp.media.View
    4626  * @augments wp.Backbone.View
    4627  * @augments Backbone.View
    4628  */
    4629 var View = wp.media.View,
    4630         $ = jQuery,
    4631         EmbedUrl;
    4632 
    4633 EmbedUrl = View.extend({
    4634         tagName:   'label',
    4635         className: 'embed-url',
    4636 
    4637         events: {
    4638                 'input':  'url',
    4639                 'keyup':  'url',
    4640                 'change': 'url'
    4641         },
    4642 
    4643         initialize: function() {
    4644                 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
    4645                 this.input = this.$input[0];
    4646 
    4647                 this.spinner = $('<span class="spinner" />')[0];
    4648                 this.$el.append([ this.input, this.spinner ]);
    4649 
    4650                 this.listenTo( this.model, 'change:url', this.render );
    4651 
    4652                 if ( this.model.get( 'url' ) ) {
    4653                         _.delay( _.bind( function () {
    4654                                 this.model.trigger( 'change:url' );
    4655                         }, this ), 500 );
    4656                 }
    4657         },
    4658         /**
    4659          * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
    4660          */
    4661         render: function() {
    4662                 var $input = this.$input;
    4663 
    4664                 if ( $input.is(':focus') ) {
    4665                         return;
    4666                 }
    4667 
    4668                 this.input.value = this.model.get('url') || 'http://';
    4669                 /**
    4670                  * Call `render` directly on parent class with passed arguments
    4671                  */
    4672                 View.prototype.render.apply( this, arguments );
    4673                 return this;
    4674         },
    4675 
    4676         ready: function() {
    4677                 if ( ! wp.media.isTouchDevice ) {
    4678                         this.focus();
    4679                 }
    4680         },
    4681 
    4682         url: function( event ) {
    4683                 this.model.set( 'url', event.target.value );
    4684         },
    4685 
    4686         /**
    4687          * If the input is visible, focus and select its contents.
    4688          */
    4689         focus: function() {
    4690                 var $input = this.$input;
    4691                 if ( $input.is(':visible') ) {
    4692                         $input.focus()[0].select();
    4693                 }
    4694         }
    4695 });
    4696 
    4697 module.exports = EmbedUrl;
    4698 
    4699 },{}],40:[function(require,module,exports){
    4700 /**
    4701  * wp.media.view.FocusManager
    4702  *
    4703  * @class
    4704  * @augments wp.media.View
    4705  * @augments wp.Backbone.View
    4706  * @augments Backbone.View
    4707  */
    4708 var FocusManager = wp.media.View.extend({
    4709 
    4710         events: {
    4711                 'keydown': 'constrainTabbing'
    4712         },
    4713 
    4714         focus: function() { // Reset focus on first left menu item
    4715                 this.$('.media-menu-item').first().focus();
    4716         },
    4717         /**
    4718          * @param {Object} event
    4719          */
    4720         constrainTabbing: function( event ) {
    4721                 var tabbables;
    4722 
    4723                 // Look for the tab key.
    4724                 if ( 9 !== event.keyCode ) {
    4725                         return;
    4726                 }
    4727 
    4728                 // Skip the file input added by Plupload.
    4729                 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
    4730 
    4731                 // Keep tab focus within media modal while it's open
    4732                 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
    4733                         tabbables.first().focus();
    4734                         return false;
    4735                 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
    4736                         tabbables.last().focus();
    4737                         return false;
    4738                 }
    4739         }
    4740 
    4741 });
    4742 
    4743 module.exports = FocusManager;
    4744 
    4745 },{}],41:[function(require,module,exports){
    4746 /*globals _, Backbone */
    4747 
    4748 /**
    4749  * wp.media.view.Frame
    4750  *
    4751  * A frame is a composite view consisting of one or more regions and one or more
    4752  * states.
    4753  *
    4754  * @see wp.media.controller.State
    4755  * @see wp.media.controller.Region
    4756  *
    4757  * @class
    4758  * @augments wp.media.View
    4759  * @augments wp.Backbone.View
    4760  * @augments Backbone.View
    4761  * @mixes wp.media.controller.StateMachine
    4762  */
    4763 var Frame = wp.media.View.extend({
    4764         initialize: function() {
    4765                 _.defaults( this.options, {
    4766                         mode: [ 'select' ]
    4767                 });
    4768                 this._createRegions();
    4769                 this._createStates();
    4770                 this._createModes();
    4771         },
    4772 
    4773         _createRegions: function() {
    4774                 // Clone the regions array.
    4775                 this.regions = this.regions ? this.regions.slice() : [];
    4776 
    4777                 // Initialize regions.
    4778                 _.each( this.regions, function( region ) {
    4779                         this[ region ] = new wp.media.controller.Region({
    4780                                 view:     this,
    4781                                 id:       region,
    4782                                 selector: '.media-frame-' + region
    4783                         });
    4784                 }, this );
    4785         },
    4786         /**
    4787          * Create the frame's states.
    4788          *
    4789          * @see wp.media.controller.State
    4790          * @see wp.media.controller.StateMachine
    4791          *
    4792          * @fires wp.media.controller.State#ready
    4793          */
    4794         _createStates: function() {
    4795                 // Create the default `states` collection.
    4796                 this.states = new Backbone.Collection( null, {
    4797                         model: wp.media.controller.State
    4798                 });
    4799 
    4800                 // Ensure states have a reference to the frame.
    4801                 this.states.on( 'add', function( model ) {
    4802                         model.frame = this;
    4803                         model.trigger('ready');
    4804                 }, this );
    4805 
    4806                 if ( this.options.states ) {
    4807                         this.states.add( this.options.states );
    4808                 }
    4809         },
    4810 
    4811         /**
    4812          * A frame can be in a mode or multiple modes at one time.
    4813          *
    4814          * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
    4815          */
    4816         _createModes: function() {
    4817                 // Store active "modes" that the frame is in. Unrelated to region modes.
    4818                 this.activeModes = new Backbone.Collection();
    4819                 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
    4820 
    4821                 _.each( this.options.mode, function( mode ) {
    4822                         this.activateMode( mode );
    4823                 }, this );
    4824         },
    4825         /**
    4826          * Reset all states on the frame to their defaults.
    4827          *
    4828          * @returns {wp.media.view.Frame} Returns itself to allow chaining
    4829          */
    4830         reset: function() {
    4831                 this.states.invoke( 'trigger', 'reset' );
    4832                 return this;
    4833         },
    4834         /**
    4835          * Map activeMode collection events to the frame.
    4836          */
    4837         triggerModeEvents: function( model, collection, options ) {
    4838                 var collectionEvent,
    4839                         modeEventMap = {
    4840                                 add: 'activate',
    4841                                 remove: 'deactivate'
    4842                         },
    4843                         eventToTrigger;
    4844                 // Probably a better way to do this.
    4845                 _.each( options, function( value, key ) {
    4846                         if ( value ) {
    4847                                 collectionEvent = key;
    4848                         }
    4849                 } );
    4850 
    4851                 if ( ! _.has( modeEventMap, collectionEvent ) ) {
    4852                         return;
    4853                 }
    4854 
    4855                 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
    4856                 this.trigger( eventToTrigger );
    4857         },
    4858         /**
    4859          * Activate a mode on the frame.
    4860          *
    4861          * @param string mode Mode ID.
    4862          * @returns {this} Returns itself to allow chaining.
    4863          */
    4864         activateMode: function( mode ) {
    4865                 // Bail if the mode is already active.
    4866                 if ( this.isModeActive( mode ) ) {
    4867                         return;
    4868                 }
    4869                 this.activeModes.add( [ { id: mode } ] );
    4870                 // Add a CSS class to the frame so elements can be styled for the mode.
    4871                 this.$el.addClass( 'mode-' + mode );
    4872 
    4873                 return this;
    4874         },
    4875         /**
    4876          * Deactivate a mode on the frame.
    4877          *
    4878          * @param string mode Mode ID.
    4879          * @returns {this} Returns itself to allow chaining.
    4880          */
    4881         deactivateMode: function( mode ) {
    4882                 // Bail if the mode isn't active.
    4883                 if ( ! this.isModeActive( mode ) ) {
    4884                         return this;
    4885                 }
    4886                 this.activeModes.remove( this.activeModes.where( { id: mode } ) );
    4887                 this.$el.removeClass( 'mode-' + mode );
    4888                 /**
    4889                  * Frame mode deactivation event.
    4890                  *
    4891                  * @event this#{mode}:deactivate
    4892                  */
    4893                 this.trigger( mode + ':deactivate' );
    4894 
    4895                 return this;
    4896         },
    4897         /**
    4898          * Check if a mode is enabled on the frame.
    4899          *
    4900          * @param  string mode Mode ID.
    4901          * @return bool
    4902          */
    4903         isModeActive: function( mode ) {
    4904                 return Boolean( this.activeModes.where( { id: mode } ).length );
    4905         }
    4906 });
    4907 
    4908 // Make the `Frame` a `StateMachine`.
    4909 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
    4910 
    4911 module.exports = Frame;
    4912 
    4913 },{}],42:[function(require,module,exports){
    4914 /*globals wp */
    4915 
    4916 /**
    4917  * wp.media.view.MediaFrame.ImageDetails
    4918  *
    4919  * A media frame for manipulating an image that's already been inserted
    4920  * into a post.
    4921  *
    4922  * @class
    4923  * @augments wp.media.view.MediaFrame.Select
    4924  * @augments wp.media.view.MediaFrame
    4925  * @augments wp.media.view.Frame
    4926  * @augments wp.media.View
    4927  * @augments wp.Backbone.View
    4928  * @augments Backbone.View
    4929  * @mixes wp.media.controller.StateMachine
    4930  */
    4931 var Select = wp.media.view.MediaFrame.Select,
    4932         l10n = wp.media.view.l10n,
    4933         ImageDetails;
    4934 
    4935 ImageDetails = Select.extend({
    4936         defaults: {
    4937                 id:      'image',
    4938                 url:     '',
    4939                 menu:    'image-details',
    4940                 content: 'image-details',
    4941                 toolbar: 'image-details',
    4942                 type:    'link',
    4943                 title:    l10n.imageDetailsTitle,
    4944                 priority: 120
    4945         },
    4946 
    4947         initialize: function( options ) {
    4948                 this.image = new wp.media.model.PostImage( options.metadata );
    4949                 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
    4950                 Select.prototype.initialize.apply( this, arguments );
    4951         },
    4952 
    4953         bindHandlers: function() {
    4954                 Select.prototype.bindHandlers.apply( this, arguments );
    4955                 this.on( 'menu:create:image-details', this.createMenu, this );
    4956                 this.on( 'content:create:image-details', this.imageDetailsContent, this );
    4957                 this.on( 'content:render:edit-image', this.editImageContent, this );
    4958                 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
    4959                 // override the select toolbar
    4960                 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
    4961         },
    4962 
    4963         createStates: function() {
    4964                 this.states.add([
    4965                         new wp.media.controller.ImageDetails({
    4966                                 image: this.image,
    4967                                 editable: false
    4968                         }),
    4969                         new wp.media.controller.ReplaceImage({
    4970                                 id: 'replace-image',
    4971                                 library: wp.media.query( { type: 'image' } ),
    4972                                 image: this.image,
    4973                                 multiple:  false,
    4974                                 title:     l10n.imageReplaceTitle,
    4975                                 toolbar: 'replace',
    4976                                 priority:  80,
    4977                                 displaySettings: true
    4978                         }),
    4979                         new wp.media.controller.EditImage( {
    4980                                 image: this.image,
    4981                                 selection: this.options.selection
    4982                         } )
    4983                 ]);
    4984         },
    4985 
    4986         imageDetailsContent: function( options ) {
    4987                 options.view = new wp.media.view.ImageDetails({
    4988                         controller: this,
    4989                         model: this.state().image,
    4990                         attachment: this.state().image.attachment
    4991                 });
    4992         },
    4993 
    4994         editImageContent: function() {
    4995                 var state = this.state(),
    4996                         model = state.get('image'),
    4997                         view;
    4998 
    4999                 if ( ! model ) {
    5000                         return;
    5001                 }
    5002 
    5003                 view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
    5004 
    5005                 this.content.set( view );
    5006 
    5007                 // after bringing in the frame, load the actual editor via an ajax call
    5008                 view.loadEditor();
    5009 
    5010         },
    5011 
    5012         renderImageDetailsToolbar: function() {
    5013                 this.toolbar.set( new wp.media.view.Toolbar({
    5014                         controller: this,
    5015                         items: {
    5016                                 select: {
    5017                                         style:    'primary',
    5018                                         text:     l10n.update,
    5019                                         priority: 80,
    5020 
    5021                                         click: function() {
    5022                                                 var controller = this.controller,
    5023                                                         state = controller.state();
    5024 
    5025                                                 controller.close();
    5026 
    5027                                                 // not sure if we want to use wp.media.string.image which will create a shortcode or
    5028                                                 // perhaps wp.html.string to at least to build the <img />
    5029                                                 state.trigger( 'update', controller.image.toJSON() );
    5030 
    5031                                                 // Restore and reset the default state.
    5032                                                 controller.setState( controller.options.state );
    5033                                                 controller.reset();
    5034                                         }
    5035                                 }
    5036                         }
    5037                 }) );
    5038         },
    5039 
    5040         renderReplaceImageToolbar: function() {
    5041                 var frame = this,
    5042                         lastState = frame.lastState(),
    5043                         previous = lastState && lastState.id;
    5044 
    5045                 this.toolbar.set( new wp.media.view.Toolbar({
    5046                         controller: this,
    5047                         items: {
    5048                                 back: {
    5049                                         text:     l10n.back,
    5050                                         priority: 20,
    5051                                         click:    function() {
    5052                                                 if ( previous ) {
    5053                                                         frame.setState( previous );
    5054                                                 } else {
    5055                                                         frame.close();
    5056                                                 }
    5057                                         }
    5058                                 },
    5059 
    5060                                 replace: {
    5061                                         style:    'primary',
    5062                                         text:     l10n.replace,
    5063                                         priority: 80,
    5064 
    5065                                         click: function() {
    5066                                                 var controller = this.controller,
    5067                                                         state = controller.state(),
    5068                                                         selection = state.get( 'selection' ),
    5069                                                         attachment = selection.single();
    5070 
    5071                                                 controller.close();
    5072 
    5073                                                 controller.image.changeAttachment( attachment, state.display( attachment ) );
    5074 
    5075                                                 // not sure if we want to use wp.media.string.image which will create a shortcode or
    5076                                                 // perhaps wp.html.string to at least to build the <img />
    5077                                                 state.trigger( 'replace', controller.image.toJSON() );
    5078 
    5079                                                 // Restore and reset the default state.
    5080                                                 controller.setState( controller.options.state );
    5081                                                 controller.reset();
    5082                                         }
    5083                                 }
    5084                         }
    5085                 }) );
    5086         }
    5087 
    5088 });
    5089 
    5090 module.exports = ImageDetails;
    5091 
    5092 },{}],43:[function(require,module,exports){
    5093 /*globals wp, _ */
    5094 
    5095 /**
    5096  * wp.media.view.MediaFrame.Post
    5097  *
    5098  * The frame for manipulating media on the Edit Post page.
    5099  *
    5100  * @class
    5101  * @augments wp.media.view.MediaFrame.Select
    5102  * @augments wp.media.view.MediaFrame
    5103  * @augments wp.media.view.Frame
    5104  * @augments wp.media.View
    5105  * @augments wp.Backbone.View
    5106  * @augments Backbone.View
    5107  * @mixes wp.media.controller.StateMachine
    5108  */
    5109 var Select = wp.media.view.MediaFrame.Select,
    5110         Library = wp.media.controller.Library,
    5111         l10n = wp.media.view.l10n,
    5112         Post;
    5113 
    5114 Post = Select.extend({
    5115         initialize: function() {
    5116                 this.counts = {
    5117                         audio: {
    5118                                 count: wp.media.view.settings.attachmentCounts.audio,
    5119                                 state: 'playlist'
    5120                         },
    5121                         video: {
    5122                                 count: wp.media.view.settings.attachmentCounts.video,
    5123                                 state: 'video-playlist'
    5124                         }
    5125                 };
    5126 
    5127                 _.defaults( this.options, {
    5128                         multiple:  true,
    5129                         editing:   false,
    5130                         state:    'insert',
    5131                         metadata:  {}
    5132                 });
    5133 
    5134                 // Call 'initialize' directly on the parent class.
    5135                 Select.prototype.initialize.apply( this, arguments );
    5136                 this.createIframeStates();
    5137 
    5138         },
    5139 
    5140         /**
    5141          * Create the default states.
    5142          */
    5143         createStates: function() {
    5144                 var options = this.options;
    5145 
    5146                 this.states.add([
    5147                         // Main states.
    5148                         new Library({
    5149                                 id:         'insert',
    5150                                 title:      l10n.insertMediaTitle,
    5151                                 priority:   20,
    5152                                 toolbar:    'main-insert',
    5153                                 filterable: 'all',
    5154                                 library:    wp.media.query( options.library ),
    5155                                 multiple:   options.multiple ? 'reset' : false,
    5156                                 editable:   true,
    5157 
    5158                                 // If the user isn't allowed to edit fields,
    5159                                 // can they still edit it locally?
    5160                                 allowLocalEdits: true,
    5161 
    5162                                 // Show the attachment display settings.
    5163                                 displaySettings: true,
    5164                                 // Update user settings when users adjust the
    5165                                 // attachment display settings.
    5166                                 displayUserSettings: true
    5167                         }),
    5168 
    5169                         new Library({
    5170                                 id:         'gallery',
    5171                                 title:      l10n.createGalleryTitle,
    5172                                 priority:   40,
    5173                                 toolbar:    'main-gallery',
    5174                                 filterable: 'uploaded',
    5175                                 multiple:   'add',
    5176                                 editable:   false,
    5177 
    5178                                 library:  wp.media.query( _.defaults({
    5179                                         type: 'image'
    5180                                 }, options.library ) )
    5181                         }),
    5182 
    5183                         // Embed states.
    5184                         new wp.media.controller.Embed( { metadata: options.metadata } ),
    5185 
    5186                         new wp.media.controller.EditImage( { model: options.editImage } ),
    5187 
    5188                         // Gallery states.
    5189                         new wp.media.controller.GalleryEdit({
    5190                                 library: options.selection,
    5191                                 editing: options.editing,
    5192                                 menu:    'gallery'
    5193                         }),
    5194 
    5195                         new wp.media.controller.GalleryAdd(),
    5196 
    5197                         new Library({
    5198                                 id:         'playlist',
    5199                                 title:      l10n.createPlaylistTitle,
    5200                                 priority:   60,
    5201                                 toolbar:    'main-playlist',
    5202                                 filterable: 'uploaded',
    5203                                 multiple:   'add',
    5204                                 editable:   false,
    5205 
    5206                                 library:  wp.media.query( _.defaults({
    5207                                         type: 'audio'
    5208                                 }, options.library ) )
    5209                         }),
    5210 
    5211                         // Playlist states.
    5212                         new wp.media.controller.CollectionEdit({
    5213                                 type: 'audio',
    5214                                 collectionType: 'playlist',
    5215                                 title:          l10n.editPlaylistTitle,
    5216                                 SettingsView:   wp.media.view.Settings.Playlist,
    5217                                 library:        options.selection,
    5218                                 editing:        options.editing,
    5219                                 menu:           'playlist',
    5220                                 dragInfoText:   l10n.playlistDragInfo,
    5221                                 dragInfo:       false
    5222                         }),
    5223 
    5224                         new wp.media.controller.CollectionAdd({
    5225                                 type: 'audio',
    5226                                 collectionType: 'playlist',
    5227                                 title: l10n.addToPlaylistTitle
    5228                         }),
    5229 
    5230                         new Library({
    5231                                 id:         'video-playlist',
    5232                                 title:      l10n.createVideoPlaylistTitle,
    5233                                 priority:   60,
    5234                                 toolbar:    'main-video-playlist',
    5235                                 filterable: 'uploaded',
    5236                                 multiple:   'add',
    5237                                 editable:   false,
    5238 
    5239                                 library:  wp.media.query( _.defaults({
    5240                                         type: 'video'
    5241                                 }, options.library ) )
    5242                         }),
    5243 
    5244                         new wp.media.controller.CollectionEdit({
    5245                                 type: 'video',
    5246                                 collectionType: 'playlist',
    5247                                 title:          l10n.editVideoPlaylistTitle,
    5248                                 SettingsView:   wp.media.view.Settings.Playlist,
    5249                                 library:        options.selection,
    5250                                 editing:        options.editing,
    5251                                 menu:           'video-playlist',
    5252                                 dragInfoText:   l10n.videoPlaylistDragInfo,
    5253                                 dragInfo:       false
    5254                         }),
    5255 
    5256                         new wp.media.controller.CollectionAdd({
    5257                                 type: 'video',
    5258                                 collectionType: 'playlist',
    5259                                 title: l10n.addToVideoPlaylistTitle
    5260                         })
    5261                 ]);
    5262 
    5263                 if ( wp.media.view.settings.post.featuredImageId ) {
    5264                         this.states.add( new wp.media.controller.FeaturedImage() );
    5265                 }
    5266         },
    5267 
    5268         bindHandlers: function() {
    5269                 var handlers, checkCounts;
    5270 
    5271                 Select.prototype.bindHandlers.apply( this, arguments );
    5272 
    5273                 this.on( 'activate', this.activate, this );
    5274 
    5275                 // Only bother checking media type counts if one of the counts is zero
    5276                 checkCounts = _.find( this.counts, function( type ) {
    5277                         return type.count === 0;
    5278                 } );
    5279 
    5280                 if ( typeof checkCounts !== 'undefined' ) {
    5281                         this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
    5282                 }
    5283 
    5284                 this.on( 'menu:create:gallery', this.createMenu, this );
    5285                 this.on( 'menu:create:playlist', this.createMenu, this );
    5286                 this.on( 'menu:create:video-playlist', this.createMenu, this );
    5287                 this.on( 'toolbar:create:main-insert', this.createToolbar, this );
    5288                 this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
    5289                 this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
    5290                 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
    5291                 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
    5292                 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
    5293 
    5294                 handlers = {
    5295                         menu: {
    5296                                 'default': 'mainMenu',
    5297                                 'gallery': 'galleryMenu',
    5298                                 'playlist': 'playlistMenu',
    5299                                 'video-playlist': 'videoPlaylistMenu'
    5300                         },
    5301 
    5302                         content: {
    5303                                 'embed':          'embedContent',
    5304                                 'edit-image':     'editImageContent',
    5305                                 'edit-selection': 'editSelectionContent'
    5306                         },
    5307 
    5308                         toolbar: {
    5309                                 'main-insert':      'mainInsertToolbar',
    5310                                 'main-gallery':     'mainGalleryToolbar',
    5311                                 'gallery-edit':     'galleryEditToolbar',
    5312                                 'gallery-add':      'galleryAddToolbar',
    5313                                 'main-playlist':        'mainPlaylistToolbar',
    5314                                 'playlist-edit':        'playlistEditToolbar',
    5315                                 'playlist-add':         'playlistAddToolbar',
    5316                                 'main-video-playlist': 'mainVideoPlaylistToolbar',
    5317                                 'video-playlist-edit': 'videoPlaylistEditToolbar',
    5318                                 'video-playlist-add': 'videoPlaylistAddToolbar'
    5319                         }
    5320                 };
    5321 
    5322                 _.each( handlers, function( regionHandlers, region ) {
    5323                         _.each( regionHandlers, function( callback, handler ) {
    5324                                 this.on( region + ':render:' + handler, this[ callback ], this );
    5325                         }, this );
    5326                 }, this );
    5327         },
    5328 
    5329         activate: function() {
    5330                 // Hide menu items for states tied to particular media types if there are no items
    5331                 _.each( this.counts, function( type ) {
    5332                         if ( type.count < 1 ) {
    5333                                 this.menuItemVisibility( type.state, 'hide' );
    5334                         }
    5335                 }, this );
    5336         },
    5337 
    5338         mediaTypeCounts: function( model, attr ) {
    5339                 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
    5340                         this.counts[ attr ].count++;
    5341                         this.menuItemVisibility( this.counts[ attr ].state, 'show' );
    5342                 }
    5343         },
    5344 
    5345         // Menus
    5346         /**
    5347          * @param {wp.Backbone.View} view
    5348          */
    5349         mainMenu: function( view ) {
    5350                 view.set({
    5351                         'library-separator': new wp.media.View({
    5352                                 className: 'separator',
    5353                                 priority: 100
    5354                         })
    5355                 });
    5356         },
    5357 
    5358         menuItemVisibility: function( state, visibility ) {
    5359                 var menu = this.menu.get();
    5360                 if ( visibility === 'hide' ) {
    5361                         menu.hide( state );
    5362                 } else if ( visibility === 'show' ) {
    5363                         menu.show( state );
    5364                 }
    5365         },
    5366         /**
    5367          * @param {wp.Backbone.View} view
    5368          */
    5369         galleryMenu: function( view ) {
    5370                 var lastState = this.lastState(),
    5371                         previous = lastState && lastState.id,
    5372                         frame = this;
    5373 
    5374                 view.set({
    5375                         cancel: {
    5376                                 text:     l10n.cancelGalleryTitle,
    5377                                 priority: 20,
    5378                                 click:    function() {
    5379                                         if ( previous ) {
    5380                                                 frame.setState( previous );
    5381                                         } else {
    5382                                                 frame.close();
    5383                                         }
    5384 
    5385                                         // Keep focus inside media modal
    5386                                         // after canceling a gallery
    5387                                         this.controller.modal.focusManager.focus();
    5388                                 }
    5389                         },
    5390                         separateCancel: new wp.media.View({
    5391                                 className: 'separator',
    5392                                 priority: 40
    5393                         })
    5394                 });
    5395         },
    5396 
    5397         playlistMenu: function( view ) {
    5398                 var lastState = this.lastState(),
    5399                         previous = lastState && lastState.id,
    5400                         frame = this;
    5401 
    5402                 view.set({
    5403                         cancel: {
    5404                                 text:     l10n.cancelPlaylistTitle,
    5405                                 priority: 20,
    5406                                 click:    function() {
    5407                                         if ( previous ) {
    5408                                                 frame.setState( previous );
    5409                                         } else {
    5410                                                 frame.close();
    5411                                         }
    5412                                 }
    5413                         },
    5414                         separateCancel: new wp.media.View({
    5415                                 className: 'separator',
    5416                                 priority: 40
    5417                         })
    5418                 });
    5419         },
    5420 
    5421         videoPlaylistMenu: function( view ) {
    5422                 var lastState = this.lastState(),
    5423                         previous = lastState && lastState.id,
    5424                         frame = this;
    5425 
    5426                 view.set({
    5427                         cancel: {
    5428                                 text:     l10n.cancelVideoPlaylistTitle,
    5429                                 priority: 20,
    5430                                 click:    function() {
    5431                                         if ( previous ) {
    5432                                                 frame.setState( previous );
    5433                                         } else {
    5434                                                 frame.close();
    5435                                         }
    5436                                 }
    5437                         },
    5438                         separateCancel: new wp.media.View({
    5439                                 className: 'separator',
    5440                                 priority: 40
    5441                         })
    5442                 });
    5443         },
    5444 
    5445         // Content
    5446         embedContent: function() {
    5447                 var view = new wp.media.view.Embed({
    5448                         controller: this,
    5449                         model:      this.state()
    5450                 }).render();
    5451 
    5452                 this.content.set( view );
    5453 
    5454                 if ( ! wp.media.isTouchDevice ) {
    5455                         view.url.focus();
    5456                 }
    5457         },
    5458 
    5459         editSelectionContent: function() {
    5460                 var state = this.state(),
    5461                         selection = state.get('selection'),
    5462                         view;
    5463 
    5464                 view = new wp.media.view.AttachmentsBrowser({
    5465                         controller: this,
    5466                         collection: selection,
    5467                         selection:  selection,
    5468                         model:      state,
    5469                         sortable:   true,
    5470                         search:     false,
    5471                         date:       false,
    5472                         dragInfo:   true,
    5473 
    5474                         AttachmentView: wp.media.view.Attachments.EditSelection
    5475                 }).render();
    5476 
    5477                 view.toolbar.set( 'backToLibrary', {
    5478                         text:     l10n.returnToLibrary,
    5479                         priority: -100,
    5480 
    5481                         click: function() {
    5482                                 this.controller.content.mode('browse');
    5483                         }
    5484                 });
    5485 
    5486                 // Browse our library of attachments.
    5487                 this.content.set( view );
    5488 
    5489                 // Trigger the controller to set focus
    5490                 this.trigger( 'edit:selection', this );
    5491         },
    5492 
    5493         editImageContent: function() {
    5494                 var image = this.state().get('image'),
    5495                         view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
    5496 
    5497                 this.content.set( view );
    5498 
    5499                 // after creating the wrapper view, load the actual editor via an ajax call
    5500                 view.loadEditor();
    5501 
    5502         },
    5503 
    5504         // Toolbars
    5505 
    5506         /**
    5507          * @param {wp.Backbone.View} view
    5508          */
    5509         selectionStatusToolbar: function( view ) {
    5510                 var editable = this.state().get('editable');
    5511 
    5512                 view.set( 'selection', new wp.media.view.Selection({
    5513                         controller: this,
    5514                         collection: this.state().get('selection'),
    5515                         priority:   -40,
    5516 
    5517                         // If the selection is editable, pass the callback to
    5518                         // switch the content mode.
    5519                         editable: editable && function() {
    5520                                 this.controller.content.mode('edit-selection');
    5521                         }
    5522                 }).render() );
    5523         },
    5524 
    5525         /**
    5526          * @param {wp.Backbone.View} view
    5527          */
    5528         mainInsertToolbar: function( view ) {
    5529                 var controller = this;
    5530 
    5531                 this.selectionStatusToolbar( view );
    5532 
    5533                 view.set( 'insert', {
    5534                         style:    'primary',
    5535                         priority: 80,
    5536                         text:     l10n.insertIntoPost,
    5537                         requires: { selection: true },
    5538 
    5539                         /**
    5540                          * @fires wp.media.controller.State#insert
    5541                          */
    5542                         click: function() {
    5543                                 var state = controller.state(),
    5544                                         selection = state.get('selection');
    5545 
    5546                                 controller.close();
    5547                                 state.trigger( 'insert', selection ).reset();
    5548                         }
    5549                 });
    5550         },
    5551 
    5552         /**
    5553          * @param {wp.Backbone.View} view
    5554          */
    5555         mainGalleryToolbar: function( view ) {
    5556                 var controller = this;
    5557 
    5558                 this.selectionStatusToolbar( view );
    5559 
    5560                 view.set( 'gallery', {
    5561                         style:    'primary',
    5562                         text:     l10n.createNewGallery,
    5563                         priority: 60,
    5564                         requires: { selection: true },
    5565 
    5566                         click: function() {
    5567                                 var selection = controller.state().get('selection'),
    5568                                         edit = controller.state('gallery-edit'),
    5569                                         models = selection.where({ type: 'image' });
    5570 
    5571                                 edit.set( 'library', new wp.media.model.Selection( models, {
    5572                                         props:    selection.props.toJSON(),
    5573                                         multiple: true
    5574                                 }) );
    5575 
    5576                                 this.controller.setState('gallery-edit');
    5577 
    5578                                 // Keep focus inside media modal
    5579                                 // after jumping to gallery view
    5580                                 this.controller.modal.focusManager.focus();
    5581                         }
    5582                 });
    5583         },
    5584 
    5585         mainPlaylistToolbar: function( view ) {
    5586                 var controller = this;
    5587 
    5588                 this.selectionStatusToolbar( view );
    5589 
    5590                 view.set( 'playlist', {
    5591                         style:    'primary',
    5592                         text:     l10n.createNewPlaylist,
    5593                         priority: 100,
    5594                         requires: { selection: true },
    5595 
    5596                         click: function() {
    5597                                 var selection = controller.state().get('selection'),
    5598                                         edit = controller.state('playlist-edit'),
    5599                                         models = selection.where({ type: 'audio' });
    5600 
    5601                                 edit.set( 'library', new wp.media.model.Selection( models, {
    5602                                         props:    selection.props.toJSON(),
    5603                                         multiple: true
    5604                                 }) );
    5605 
    5606                                 this.controller.setState('playlist-edit');
    5607 
    5608                                 // Keep focus inside media modal
    5609                                 // after jumping to playlist view
    5610                                 this.controller.modal.focusManager.focus();
    5611                         }
    5612                 });
    5613         },
    5614 
    5615         mainVideoPlaylistToolbar: function( view ) {
    5616                 var controller = this;
    5617 
    5618                 this.selectionStatusToolbar( view );
    5619 
    5620                 view.set( 'video-playlist', {
    5621                         style:    'primary',
    5622                         text:     l10n.createNewVideoPlaylist,
    5623                         priority: 100,
    5624                         requires: { selection: true },
    5625 
    5626                         click: function() {
    5627                                 var selection = controller.state().get('selection'),
    5628                                         edit = controller.state('video-playlist-edit'),
    5629                                         models = selection.where({ type: 'video' });
    5630 
    5631                                 edit.set( 'library', new wp.media.model.Selection( models, {
    5632                                         props:    selection.props.toJSON(),
    5633                                         multiple: true
    5634                                 }) );
    5635 
    5636                                 this.controller.setState('video-playlist-edit');
    5637 
    5638                                 // Keep focus inside media modal
    5639                                 // after jumping to video playlist view
    5640                                 this.controller.modal.focusManager.focus();
    5641                         }
    5642                 });
    5643         },
    5644 
    5645         featuredImageToolbar: function( toolbar ) {
    5646                 this.createSelectToolbar( toolbar, {
    5647                         text:  l10n.setFeaturedImage,
    5648                         state: this.options.state
    5649                 });
    5650         },
    5651 
    5652         mainEmbedToolbar: function( toolbar ) {
    5653                 toolbar.view = new wp.media.view.Toolbar.Embed({
    5654                         controller: this
    5655                 });
    5656         },
    5657 
    5658         galleryEditToolbar: function() {
    5659                 var editing = this.state().get('editing');
    5660                 this.toolbar.set( new wp.media.view.Toolbar({
    5661                         controller: this,
    5662                         items: {
    5663                                 insert: {
    5664                                         style:    'primary',
    5665                                         text:     editing ? l10n.updateGallery : l10n.insertGallery,
    5666                                         priority: 80,
    5667                                         requires: { library: true },
    5668 
    5669                                         /**
    5670                                          * @fires wp.media.controller.State#update
    5671                                          */
    5672                                         click: function() {
    5673                                                 var controller = this.controller,
    5674                                                         state = controller.state();
    5675 
    5676                                                 controller.close();
    5677                                                 state.trigger( 'update', state.get('library') );
    5678 
    5679                                                 // Restore and reset the default state.
    5680                                                 controller.setState( controller.options.state );
    5681                                                 controller.reset();
    5682                                         }
    5683                                 }
    5684                         }
    5685                 }) );
    5686         },
    5687 
    5688         galleryAddToolbar: function() {
    5689                 this.toolbar.set( new wp.media.view.Toolbar({
    5690                         controller: this,
    5691                         items: {
    5692                                 insert: {
    5693                                         style:    'primary',
    5694                                         text:     l10n.addToGallery,
    5695                                         priority: 80,
    5696                                         requires: { selection: true },
    5697 
    5698                                         /**
    5699                                          * @fires wp.media.controller.State#reset
    5700                                          */
    5701                                         click: function() {
    5702                                                 var controller = this.controller,
    5703                                                         state = controller.state(),
    5704                                                         edit = controller.state('gallery-edit');
    5705 
    5706                                                 edit.get('library').add( state.get('selection').models );
    5707                                                 state.trigger('reset');
    5708                                                 controller.setState('gallery-edit');
    5709                                         }
    5710                                 }
    5711                         }
    5712                 }) );
    5713         },
    5714 
    5715         playlistEditToolbar: function() {
    5716                 var editing = this.state().get('editing');
    5717                 this.toolbar.set( new wp.media.view.Toolbar({
    5718                         controller: this,
    5719                         items: {
    5720                                 insert: {
    5721                                         style:    'primary',
    5722                                         text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
    5723                                         priority: 80,
    5724                                         requires: { library: true },
    5725 
    5726                                         /**
    5727                                          * @fires wp.media.controller.State#update
    5728                                          */
    5729                                         click: function() {
    5730                                                 var controller = this.controller,
    5731                                                         state = controller.state();
    5732 
    5733                                                 controller.close();
    5734                                                 state.trigger( 'update', state.get('library') );
    5735 
    5736                                                 // Restore and reset the default state.
    5737                                                 controller.setState( controller.options.state );
    5738                                                 controller.reset();
    5739                                         }
    5740                                 }
    5741                         }
    5742                 }) );
    5743         },
    5744 
    5745         playlistAddToolbar: function() {
    5746                 this.toolbar.set( new wp.media.view.Toolbar({
    5747                         controller: this,
    5748                         items: {
    5749                                 insert: {
    5750                                         style:    'primary',
    5751                                         text:     l10n.addToPlaylist,
    5752                                         priority: 80,
    5753                                         requires: { selection: true },
    5754 
    5755                                         /**
    5756                                          * @fires wp.media.controller.State#reset
    5757                                          */
    5758                                         click: function() {
    5759                                                 var controller = this.controller,
    5760                                                         state = controller.state(),
    5761                                                         edit = controller.state('playlist-edit');
    5762 
    5763                                                 edit.get('library').add( state.get('selection').models );
    5764                                                 state.trigger('reset');
    5765                                                 controller.setState('playlist-edit');
    5766                                         }
    5767                                 }
    5768                         }
    5769                 }) );
    5770         },
    5771 
    5772         videoPlaylistEditToolbar: function() {
    5773                 var editing = this.state().get('editing');
    5774                 this.toolbar.set( new wp.media.view.Toolbar({
    5775                         controller: this,
    5776                         items: {
    5777                                 insert: {
    5778                                         style:    'primary',
    5779                                         text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
    5780                                         priority: 140,
    5781                                         requires: { library: true },
    5782 
    5783                                         click: function() {
    5784                                                 var controller = this.controller,
    5785                                                         state = controller.state(),
    5786                                                         library = state.get('library');
    5787 
    5788                                                 library.type = 'video';
    5789 
    5790                                                 controller.close();
    5791                                                 state.trigger( 'update', library );
    5792 
    5793                                                 // Restore and reset the default state.
    5794                                                 controller.setState( controller.options.state );
    5795                                                 controller.reset();
    5796                                         }
    5797                                 }
    5798                         }
    5799                 }) );
    5800         },
    5801 
    5802         videoPlaylistAddToolbar: function() {
    5803                 this.toolbar.set( new wp.media.view.Toolbar({
    5804                         controller: this,
    5805                         items: {
    5806                                 insert: {
    5807                                         style:    'primary',
    5808                                         text:     l10n.addToVideoPlaylist,
    5809                                         priority: 140,
    5810                                         requires: { selection: true },
    5811 
    5812                                         click: function() {
    5813                                                 var controller = this.controller,
    5814                                                         state = controller.state(),
    5815                                                         edit = controller.state('video-playlist-edit');
    5816 
    5817                                                 edit.get('library').add( state.get('selection').models );
    5818                                                 state.trigger('reset');
    5819                                                 controller.setState('video-playlist-edit');
    5820                                         }
    5821                                 }
    5822                         }
    5823                 }) );
    5824         }
    5825 });
    5826 
    5827 module.exports = Post;
    5828 
    5829 },{}],44:[function(require,module,exports){
    5830 /*globals wp, _ */
    5831 
    5832 /**
    5833  * wp.media.view.MediaFrame.Select
    5834  *
    5835  * A frame for selecting an item or items from the media library.
    5836  *
    5837  * @class
    5838  * @augments wp.media.view.MediaFrame
    5839  * @augments wp.media.view.Frame
    5840  * @augments wp.media.View
    5841  * @augments wp.Backbone.View
    5842  * @augments Backbone.View
    5843  * @mixes wp.media.controller.StateMachine
    5844  */
    5845 
    5846 var MediaFrame = wp.media.view.MediaFrame,
    5847         l10n = wp.media.view.l10n,
    5848         Select;
    5849 
    5850 Select = MediaFrame.extend({
    5851         initialize: function() {
    5852                 // Call 'initialize' directly on the parent class.
    5853                 MediaFrame.prototype.initialize.apply( this, arguments );
    5854 
    5855                 _.defaults( this.options, {
    5856                         selection: [],
    5857                         library:   {},
    5858                         multiple:  false,
    5859                         state:    'library'
    5860                 });
    5861 
    5862                 this.createSelection();
    5863                 this.createStates();
    5864                 this.bindHandlers();
    5865         },
    5866 
    5867         /**
    5868          * Attach a selection collection to the frame.
    5869          *
    5870          * A selection is a collection of attachments used for a specific purpose
    5871          * by a media frame. e.g. Selecting an attachment (or many) to insert into
    5872          * post content.
    5873          *
    5874          * @see media.model.Selection
    5875          */
    5876         createSelection: function() {
    5877                 var selection = this.options.selection;
    5878 
    5879                 if ( ! (selection instanceof wp.media.model.Selection) ) {
    5880                         this.options.selection = new wp.media.model.Selection( selection, {
    5881                                 multiple: this.options.multiple
    5882                         });
    5883                 }
    5884 
    5885                 this._selection = {
    5886                         attachments: new wp.media.model.Attachments(),
    5887                         difference: []
    5888                 };
    5889         },
    5890 
    5891         /**
    5892          * Create the default states on the frame.
    5893          */
    5894         createStates: function() {
    5895                 var options = this.options;
    5896 
    5897                 if ( this.options.states ) {
    5898                         return;
    5899                 }
    5900 
    5901                 // Add the default states.
    5902                 this.states.add([
    5903                         // Main states.
    5904                         new wp.media.controller.Library({
    5905                                 library:   wp.media.query( options.library ),
    5906                                 multiple:  options.multiple,
    5907                                 title:     options.title,
    5908                                 priority:  20
    5909                         })
    5910                 ]);
    5911         },
    5912 
    5913         /**
    5914          * Bind region mode event callbacks.
    5915          *
    5916          * @see media.controller.Region.render
    5917          */
    5918         bindHandlers: function() {
    5919                 this.on( 'router:create:browse', this.createRouter, this );
    5920                 this.on( 'router:render:browse', this.browseRouter, this );
    5921                 this.on( 'content:create:browse', this.browseContent, this );
    5922                 this.on( 'content:render:upload', this.uploadContent, this );
    5923                 this.on( 'toolbar:create:select', this.createSelectToolbar, this );
    5924         },
    5925 
    5926         /**
    5927          * Render callback for the router region in the `browse` mode.
    5928          *
    5929          * @param {wp.media.view.Router} routerView
    5930          */
    5931         browseRouter: function( routerView ) {
    5932                 routerView.set({
    5933                         upload: {
    5934                                 text:     l10n.uploadFilesTitle,
    5935                                 priority: 20
    5936                         },
    5937                         browse: {
    5938                                 text:     l10n.mediaLibraryTitle,
    5939                                 priority: 40
    5940                         }
    5941                 });
    5942         },
    5943 
    5944         /**
    5945          * Render callback for the content region in the `browse` mode.
    5946          *
    5947          * @param {wp.media.controller.Region} contentRegion
    5948          */
    5949         browseContent: function( contentRegion ) {
    5950                 var state = this.state();
    5951 
    5952                 this.$el.removeClass('hide-toolbar');
    5953 
    5954                 // Browse our library of attachments.
    5955                 contentRegion.view = new wp.media.view.AttachmentsBrowser({
    5956                         controller: this,
    5957                         collection: state.get('library'),
    5958                         selection:  state.get('selection'),
    5959                         model:      state,
    5960                         sortable:   state.get('sortable'),
    5961                         search:     state.get('searchable'),
    5962                         filters:    state.get('filterable'),
    5963                         display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
    5964                         dragInfo:   state.get('dragInfo'),
    5965 
    5966                         idealColumnWidth: state.get('idealColumnWidth'),
    5967                         suggestedWidth:   state.get('suggestedWidth'),
    5968                         suggestedHeight:  state.get('suggestedHeight'),
    5969 
    5970                         AttachmentView: state.get('AttachmentView')
    5971                 });
    5972         },
    5973 
    5974         /**
    5975          * Render callback for the content region in the `upload` mode.
    5976          */
    5977         uploadContent: function() {
    5978                 this.$el.removeClass( 'hide-toolbar' );
    5979                 this.content.set( new wp.media.view.UploaderInline({
    5980                         controller: this
    5981                 }) );
    5982         },
    5983 
    5984         /**
    5985          * Toolbars
    5986          *
    5987          * @param {Object} toolbar
    5988          * @param {Object} [options={}]
    5989          * @this wp.media.controller.Region
    5990          */
    5991         createSelectToolbar: function( toolbar, options ) {
    5992                 options = options || this.options.button || {};
    5993                 options.controller = this;
    5994 
    5995                 toolbar.view = new wp.media.view.Toolbar.Select( options );
    5996         }
    5997 });
    5998 
    5999 module.exports = Select;
    6000 
    6001 },{}],45:[function(require,module,exports){
    6002 /**
    6003  * wp.media.view.Iframe
    6004  *
    6005  * @class
    6006  * @augments wp.media.View
    6007  * @augments wp.Backbone.View
    6008  * @augments Backbone.View
    6009  */
    6010 var Iframe = wp.media.View.extend({
    6011         className: 'media-iframe',
    6012         /**
    6013          * @returns {wp.media.view.Iframe} Returns itself to allow chaining
    6014          */
    6015         render: function() {
    6016                 this.views.detach();
    6017                 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
    6018                 this.views.render();
    6019                 return this;
    6020         }
    6021 });
    6022 
    6023 module.exports = Iframe;
    6024 
    6025 },{}],46:[function(require,module,exports){
    6026 /*globals wp, _, jQuery */
    6027 
    6028 /**
    6029  * wp.media.view.ImageDetails
    6030  *
    6031  * @class
    6032  * @augments wp.media.view.Settings.AttachmentDisplay
    6033  * @augments wp.media.view.Settings
    6034  * @augments wp.media.View
    6035  * @augments wp.Backbone.View
    6036  * @augments Backbone.View
    6037  */
    6038 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
    6039         $ = jQuery,
    6040         ImageDetails;
    6041 
    6042 ImageDetails = AttachmentDisplay.extend({
    6043         className: 'image-details',
    6044         template:  wp.template('image-details'),
    6045         events: _.defaults( AttachmentDisplay.prototype.events, {
    6046                 'click .edit-attachment': 'editAttachment',
    6047                 'click .replace-attachment': 'replaceAttachment',
    6048                 'click .advanced-toggle': 'onToggleAdvanced',
    6049                 'change [data-setting="customWidth"]': 'onCustomSize',
    6050                 'change [data-setting="customHeight"]': 'onCustomSize',
    6051                 'keyup [data-setting="customWidth"]': 'onCustomSize',
    6052                 'keyup [data-setting="customHeight"]': 'onCustomSize'
    6053         } ),
    6054         initialize: function() {
    6055                 // used in AttachmentDisplay.prototype.updateLinkTo
    6056                 this.options.attachment = this.model.attachment;
    6057                 this.listenTo( this.model, 'change:url', this.updateUrl );
    6058                 this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
    6059                 this.listenTo( this.model, 'change:size', this.toggleCustomSize );
    6060 
    6061                 AttachmentDisplay.prototype.initialize.apply( this, arguments );
    6062         },
    6063 
    6064         prepare: function() {
    6065                 var attachment = false;
    6066 
    6067                 if ( this.model.attachment ) {
    6068                         attachment = this.model.attachment.toJSON();
    6069                 }
    6070                 return _.defaults({
    6071                         model: this.model.toJSON(),
    6072                         attachment: attachment
    6073                 }, this.options );
    6074         },
    6075 
    6076         render: function() {
    6077                 var args = arguments;
    6078 
    6079                 if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
    6080                         this.model.dfd
    6081                                 .done( _.bind( function() {
    6082                                         AttachmentDisplay.prototype.render.apply( this, args );
    6083                                         this.postRender();
    6084                                 }, this ) )
    6085                                 .fail( _.bind( function() {
    6086                                         this.model.attachment = false;
    6087                                         AttachmentDisplay.prototype.render.apply( this, args );
    6088                                         this.postRender();
    6089                                 }, this ) );
    6090                 } else {
    6091                         AttachmentDisplay.prototype.render.apply( this, arguments );
    6092                         this.postRender();
    6093                 }
    6094 
    6095                 return this;
    6096         },
    6097 
    6098         postRender: function() {
    6099                 setTimeout( _.bind( this.resetFocus, this ), 10 );
    6100                 this.toggleLinkSettings();
    6101                 if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
    6102                         this.toggleAdvanced( true );
    6103                 }
    6104                 this.trigger( 'post-render' );
    6105         },
    6106 
    6107         resetFocus: function() {
    6108                 this.$( '.link-to-custom' ).blur();
    6109                 this.$( '.embed-media-settings' ).scrollTop( 0 );
    6110         },
    6111 
    6112         updateUrl: function() {
    6113                 this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
    6114                 this.$( '.url' ).val( this.model.get( 'url' ) );
    6115         },
    6116 
    6117         toggleLinkSettings: function() {
    6118                 if ( this.model.get( 'link' ) === 'none' ) {
    6119                         this.$( '.link-settings' ).addClass('hidden');
    6120                 } else {
    6121                         this.$( '.link-settings' ).removeClass('hidden');
    6122                 }
    6123         },
    6124 
    6125         toggleCustomSize: function() {
    6126                 if ( this.model.get( 'size' ) !== 'custom' ) {
    6127                         this.$( '.custom-size' ).addClass('hidden');
    6128                 } else {
    6129                         this.$( '.custom-size' ).removeClass('hidden');
    6130                 }
    6131         },
    6132 
    6133         onCustomSize: function( event ) {
    6134                 var dimension = $( event.target ).data('setting'),
    6135                         num = $( event.target ).val(),
    6136                         value;
    6137 
    6138                 // Ignore bogus input
    6139                 if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
    6140                         event.preventDefault();
    6141                         return;
    6142                 }
    6143 
    6144                 if ( dimension === 'customWidth' ) {
    6145                         value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );
    6146                         this.model.set( 'customHeight', value, { silent: true } );
    6147                         this.$( '[data-setting="customHeight"]' ).val( value );
    6148                 } else {
    6149                         value = Math.round( this.model.get( 'aspectRatio' ) * num );
    6150                         this.model.set( 'customWidth', value, { silent: true  } );
    6151                         this.$( '[data-setting="customWidth"]' ).val( value );
    6152                 }
    6153         },
    6154 
    6155         onToggleAdvanced: function( event ) {
    6156                 event.preventDefault();
    6157                 this.toggleAdvanced();
    6158         },
    6159 
    6160         toggleAdvanced: function( show ) {
    6161                 var $advanced = this.$el.find( '.advanced-section' ),
    6162                         mode;
    6163 
    6164                 if ( $advanced.hasClass('advanced-visible') || show === false ) {
    6165                         $advanced.removeClass('advanced-visible');
    6166                         $advanced.find('.advanced-settings').addClass('hidden');
    6167                         mode = 'hide';
    6168                 } else {
    6169                         $advanced.addClass('advanced-visible');
    6170                         $advanced.find('.advanced-settings').removeClass('hidden');
    6171                         mode = 'show';
    6172                 }
    6173 
    6174                 window.setUserSetting( 'advImgDetails', mode );
    6175         },
    6176 
    6177         editAttachment: function( event ) {
    6178                 var editState = this.controller.states.get( 'edit-image' );
    6179 
    6180                 if ( window.imageEdit && editState ) {
    6181                         event.preventDefault();
    6182                         editState.set( 'image', this.model.attachment );
    6183                         this.controller.setState( 'edit-image' );
    6184                 }
    6185         },
    6186 
    6187         replaceAttachment: function( event ) {
    6188                 event.preventDefault();
    6189                 this.controller.setState( 'replace-image' );
    6190         }
    6191 });
    6192 
    6193 module.exports = ImageDetails;
    6194 
    6195 },{}],47:[function(require,module,exports){
    6196 /**
    6197  * wp.media.view.Label
    6198  *
    6199  * @class
    6200  * @augments wp.media.View
    6201  * @augments wp.Backbone.View
    6202  * @augments Backbone.View
    6203  */
    6204 var Label = wp.media.View.extend({
    6205         tagName: 'label',
    6206         className: 'screen-reader-text',
    6207 
    6208         initialize: function() {
    6209                 this.value = this.options.value;
    6210         },
    6211 
    6212         render: function() {
    6213                 this.$el.html( this.value );
    6214 
    6215                 return this;
    6216         }
    6217 });
    6218 
    6219 module.exports = Label;
    6220 
    6221 },{}],48:[function(require,module,exports){
    6222 /*globals wp, _, jQuery */
    6223 
    6224 /**
    6225  * wp.media.view.MediaFrame
    6226  *
    6227  * The frame used to create the media modal.
    6228  *
    6229  * @class
    6230  * @augments wp.media.view.Frame
    6231  * @augments wp.media.View
    6232  * @augments wp.Backbone.View
    6233  * @augments Backbone.View
    6234  * @mixes wp.media.controller.StateMachine
    6235  */
    6236 var Frame = wp.media.view.Frame,
    6237         $ = jQuery,
    6238         MediaFrame;
    6239 
    6240 MediaFrame = Frame.extend({
    6241         className: 'media-frame',
    6242         template:  wp.template('media-frame'),
    6243         regions:   ['menu','title','content','toolbar','router'],
    6244 
    6245         events: {
    6246                 'click div.media-frame-title h1': 'toggleMenu'
    6247         },
    6248 
    6249         /**
    6250          * @global wp.Uploader
    6251          */
    6252         initialize: function() {
    6253                 Frame.prototype.initialize.apply( this, arguments );
    6254 
    6255                 _.defaults( this.options, {
    6256                         title:    '',
    6257                         modal:    true,
    6258                         uploader: true
    6259                 });
    6260 
    6261                 // Ensure core UI is enabled.
    6262                 this.$el.addClass('wp-core-ui');
    6263 
    6264                 // Initialize modal container view.
    6265                 if ( this.options.modal ) {
    6266                         this.modal = new wp.media.view.Modal({
    6267                                 controller: this,
    6268                                 title:      this.options.title
    6269                         });
    6270 
    6271                         this.modal.content( this );
    6272                 }
    6273 
    6274                 // Force the uploader off if the upload limit has been exceeded or
    6275                 // if the browser isn't supported.
    6276                 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
    6277                         this.options.uploader = false;
    6278                 }
    6279 
    6280                 // Initialize window-wide uploader.
    6281                 if ( this.options.uploader ) {
    6282                         this.uploader = new wp.media.view.UploaderWindow({
    6283                                 controller: this,
    6284                                 uploader: {
    6285                                         dropzone:  this.modal ? this.modal.$el : this.$el,
    6286                                         container: this.$el
    6287                                 }
    6288                         });
    6289                         this.views.set( '.media-frame-uploader', this.uploader );
    6290                 }
    6291 
    6292                 this.on( 'attach', _.bind( this.views.ready, this.views ), this );
    6293 
    6294                 // Bind default title creation.
    6295                 this.on( 'title:create:default', this.createTitle, this );
    6296                 this.title.mode('default');
    6297 
    6298                 this.on( 'title:render', function( view ) {
    6299                         view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
    6300                 });
    6301 
    6302                 // Bind default menu.
    6303                 this.on( 'menu:create:default', this.createMenu, this );
    6304         },
    6305         /**
    6306          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6307          */
    6308         render: function() {
    6309                 // Activate the default state if no active state exists.
    6310                 if ( ! this.state() && this.options.state ) {
    6311                         this.setState( this.options.state );
    6312                 }
    6313                 /**
    6314                  * call 'render' directly on the parent class
    6315                  */
    6316                 return Frame.prototype.render.apply( this, arguments );
    6317         },
    6318         /**
    6319          * @param {Object} title
    6320          * @this wp.media.controller.Region
    6321          */
    6322         createTitle: function( title ) {
    6323                 title.view = new wp.media.View({
    6324                         controller: this,
    6325                         tagName: 'h1'
    6326                 });
    6327         },
    6328         /**
    6329          * @param {Object} menu
    6330          * @this wp.media.controller.Region
    6331          */
    6332         createMenu: function( menu ) {
    6333                 menu.view = new wp.media.view.Menu({
    6334                         controller: this
    6335                 });
    6336         },
    6337 
    6338         toggleMenu: function() {
    6339                 this.$el.find( '.media-menu' ).toggleClass( 'visible' );
    6340         },
    6341 
    6342         /**
    6343          * @param {Object} toolbar
    6344          * @this wp.media.controller.Region
    6345          */
    6346         createToolbar: function( toolbar ) {
    6347                 toolbar.view = new wp.media.view.Toolbar({
    6348                         controller: this
    6349                 });
    6350         },
    6351         /**
    6352          * @param {Object} router
    6353          * @this wp.media.controller.Region
    6354          */
    6355         createRouter: function( router ) {
    6356                 router.view = new wp.media.view.Router({
    6357                         controller: this
    6358                 });
    6359         },
    6360         /**
    6361          * @param {Object} options
    6362          */
    6363         createIframeStates: function( options ) {
    6364                 var settings = wp.media.view.settings,
    6365                         tabs = settings.tabs,
    6366                         tabUrl = settings.tabUrl,
    6367                         $postId;
    6368 
    6369                 if ( ! tabs || ! tabUrl ) {
    6370                         return;
    6371                 }
    6372 
    6373                 // Add the post ID to the tab URL if it exists.
    6374                 $postId = $('#post_ID');
    6375                 if ( $postId.length ) {
    6376                         tabUrl += '&post_id=' + $postId.val();
    6377                 }
    6378 
    6379                 // Generate the tab states.
    6380                 _.each( tabs, function( title, id ) {
    6381                         this.state( 'iframe:' + id ).set( _.defaults({
    6382                                 tab:     id,
    6383                                 src:     tabUrl + '&tab=' + id,
    6384                                 title:   title,
    6385                                 content: 'iframe',
    6386                                 menu:    'default'
    6387                         }, options ) );
    6388                 }, this );
    6389 
    6390                 this.on( 'content:create:iframe', this.iframeContent, this );
    6391                 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
    6392                 this.on( 'menu:render:default', this.iframeMenu, this );
    6393                 this.on( 'open', this.hijackThickbox, this );
    6394                 this.on( 'close', this.restoreThickbox, this );
    6395         },
    6396 
    6397         /**
    6398          * @param {Object} content
    6399          * @this wp.media.controller.Region
    6400          */
    6401         iframeContent: function( content ) {
    6402                 this.$el.addClass('hide-toolbar');
    6403                 content.view = new wp.media.view.Iframe({
    6404                         controller: this
    6405                 });
    6406         },
    6407 
    6408         iframeContentCleanup: function() {
    6409                 this.$el.removeClass('hide-toolbar');
    6410         },
    6411 
    6412         iframeMenu: function( view ) {
    6413                 var views = {};
    6414 
    6415                 if ( ! view ) {
    6416                         return;
    6417                 }
    6418 
    6419                 _.each( wp.media.view.settings.tabs, function( title, id ) {
    6420                         views[ 'iframe:' + id ] = {
    6421                                 text: this.state( 'iframe:' + id ).get('title'),
    6422                                 priority: 200
    6423                         };
    6424                 }, this );
    6425 
    6426                 view.set( views );
    6427         },
    6428 
    6429         hijackThickbox: function() {
    6430                 var frame = this;
    6431 
    6432                 if ( ! window.tb_remove || this._tb_remove ) {
    6433                         return;
    6434                 }
    6435 
    6436                 this._tb_remove = window.tb_remove;
    6437                 window.tb_remove = function() {
    6438                         frame.close();
    6439                         frame.reset();
    6440                         frame.setState( frame.options.state );
    6441                         frame._tb_remove.call( window );
    6442                 };
    6443         },
    6444 
    6445         restoreThickbox: function() {
    6446                 if ( ! this._tb_remove ) {
    6447                         return;
    6448                 }
    6449 
    6450                 window.tb_remove = this._tb_remove;
    6451                 delete this._tb_remove;
    6452         }
    6453 });
    6454 
    6455 // Map some of the modal's methods to the frame.
    6456 _.each(['open','close','attach','detach','escape'], function( method ) {
    6457         /**
    6458          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    6459          */
    6460         MediaFrame.prototype[ method ] = function() {
    6461                 if ( this.modal ) {
    6462                         this.modal[ method ].apply( this.modal, arguments );
    6463                 }
    6464                 return this;
    6465         };
    6466 });
    6467 
    6468 module.exports = MediaFrame;
    6469 
    6470 },{}],49:[function(require,module,exports){
    6471 /*globals jQuery */
    6472 
    6473 /**
    6474  * wp.media.view.MenuItem
    6475  *
    6476  * @class
    6477  * @augments wp.media.View
    6478  * @augments wp.Backbone.View
    6479  * @augments Backbone.View
    6480  */
    6481 var $ = jQuery,
    6482         MenuItem;
    6483 
    6484 MenuItem = wp.media.View.extend({
    6485         tagName:   'a',
    6486         className: 'media-menu-item',
    6487 
    6488         attributes: {
    6489                 href: '#'
    6490         },
    6491 
    6492         events: {
    6493                 'click': '_click'
    6494         },
    6495         /**
    6496          * @param {Object} event
    6497          */
    6498         _click: function( event ) {
    6499                 var clickOverride = this.options.click;
    6500 
    6501                 if ( event ) {
    6502                         event.preventDefault();
    6503                 }
    6504 
    6505                 if ( clickOverride ) {
    6506                         clickOverride.call( this );
    6507                 } else {
    6508                         this.click();
    6509                 }
    6510 
    6511                 // When selecting a tab along the left side,
    6512                 // focus should be transferred into the main panel
    6513                 if ( ! wp.media.isTouchDevice ) {
    6514                         $('.media-frame-content input').first().focus();
    6515                 }
    6516         },
    6517 
    6518         click: function() {
    6519                 var state = this.options.state;
    6520 
    6521                 if ( state ) {
    6522                         this.controller.setState( state );
    6523                         this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
    6524                 }
    6525         },
    6526         /**
    6527          * @returns {wp.media.view.MenuItem} returns itself to allow chaining
    6528          */
    6529         render: function() {
    6530                 var options = this.options;
    6531 
    6532                 if ( options.text ) {
    6533                         this.$el.text( options.text );
    6534                 } else if ( options.html ) {
    6535                         this.$el.html( options.html );
    6536                 }
    6537 
    6538                 return this;
    6539         }
    6540 });
    6541 
    6542 module.exports = MenuItem;
    6543 
    6544 },{}],50:[function(require,module,exports){
    6545 /**
    6546  * wp.media.view.Menu
    6547  *
    6548  * @class
    6549  * @augments wp.media.view.PriorityList
    6550  * @augments wp.media.View
    6551  * @augments wp.Backbone.View
    6552  * @augments Backbone.View
    6553  */
    6554 var MenuItem = wp.media.view.MenuItem,
    6555         PriorityList = wp.media.view.PriorityList,
    6556         Menu;
    6557 
    6558 Menu = PriorityList.extend({
    6559         tagName:   'div',
    6560         className: 'media-menu',
    6561         property:  'state',
    6562         ItemView:  MenuItem,
    6563         region:    'menu',
    6564 
    6565         /* TODO: alternatively hide on any click anywhere
    6566         events: {
    6567                 'click': 'click'
    6568         },
    6569 
    6570         click: function() {
    6571                 this.$el.removeClass( 'visible' );
    6572         },
    6573         */
    6574 
    6575         /**
    6576          * @param {Object} options
    6577          * @param {string} id
    6578          * @returns {wp.media.View}
    6579          */
    6580         toView: function( options, id ) {
    6581                 options = options || {};
    6582                 options[ this.property ] = options[ this.property ] || id;
    6583                 return new this.ItemView( options ).render();
    6584         },
    6585 
    6586         ready: function() {
    6587                 /**
    6588                  * call 'ready' directly on the parent class
    6589                  */
    6590                 PriorityList.prototype.ready.apply( this, arguments );
    6591                 this.visibility();
    6592         },
    6593 
    6594         set: function() {
    6595                 /**
    6596                  * call 'set' directly on the parent class
    6597                  */
    6598                 PriorityList.prototype.set.apply( this, arguments );
    6599                 this.visibility();
    6600         },
    6601 
    6602         unset: function() {
    6603                 /**
    6604                  * call 'unset' directly on the parent class
    6605                  */
    6606                 PriorityList.prototype.unset.apply( this, arguments );
    6607                 this.visibility();
    6608         },
    6609 
    6610         visibility: function() {
    6611                 var region = this.region,
    6612                         view = this.controller[ region ].get(),
    6613                         views = this.views.get(),
    6614                         hide = ! views || views.length < 2;
    6615 
    6616                 if ( this === view ) {
    6617                         this.controller.$el.toggleClass( 'hide-' + region, hide );
    6618                 }
    6619         },
    6620         /**
    6621          * @param {string} id
    6622          */
    6623         select: function( id ) {
    6624                 var view = this.get( id );
    6625 
    6626                 if ( ! view ) {
    6627                         return;
    6628                 }
    6629 
    6630                 this.deselect();
    6631                 view.$el.addClass('active');
    6632         },
    6633 
    6634         deselect: function() {
    6635                 this.$el.children().removeClass('active');
    6636         },
    6637 
    6638         hide: function( id ) {
    6639                 var view = this.get( id );
    6640 
    6641                 if ( ! view ) {
    6642                         return;
    6643                 }
    6644 
    6645                 view.$el.addClass('hidden');
    6646         },
    6647 
    6648         show: function( id ) {
    6649                 var view = this.get( id );
    6650 
    6651                 if ( ! view ) {
    6652                         return;
    6653                 }
    6654 
    6655                 view.$el.removeClass('hidden');
    6656         }
    6657 });
    6658 
    6659 module.exports = Menu;
    6660 
    6661 },{}],51:[function(require,module,exports){
    6662 /*globals wp, _, jQuery */
    6663 
    6664 /**
    6665  * wp.media.view.Modal
    6666  *
    6667  * A modal view, which the media modal uses as its default container.
    6668  *
    6669  * @class
    6670  * @augments wp.media.View
    6671  * @augments wp.Backbone.View
    6672  * @augments Backbone.View
    6673  */
    6674 var $ = jQuery,
    6675         Modal;
    6676 
    6677 Modal = wp.media.View.extend({
    6678         tagName:  'div',
    6679         template: wp.template('media-modal'),
    6680 
    6681         attributes: {
    6682                 tabindex: 0
    6683         },
    6684 
    6685         events: {
    6686                 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
    6687                 'keydown': 'keydown'
    6688         },
    6689 
    6690         initialize: function() {
    6691                 _.defaults( this.options, {
    6692                         container: document.body,
    6693                         title:     '',
    6694                         propagate: true,
    6695                         freeze:    true
    6696                 });
    6697 
    6698                 this.focusManager = new wp.media.view.FocusManager({
    6699                         el: this.el
    6700                 });
    6701         },
    6702         /**
    6703          * @returns {Object}
    6704          */
    6705         prepare: function() {
    6706                 return {
    6707                         title: this.options.title
    6708                 };
    6709         },
    6710 
    6711         /**
    6712          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6713          */
    6714         attach: function() {
    6715                 if ( this.views.attached ) {
    6716                         return this;
    6717                 }
    6718 
    6719                 if ( ! this.views.rendered ) {
    6720                         this.render();
    6721                 }
    6722 
    6723                 this.$el.appendTo( this.options.container );
    6724 
    6725                 // Manually mark the view as attached and trigger ready.
    6726                 this.views.attached = true;
    6727                 this.views.ready();
    6728 
    6729                 return this.propagate('attach');
    6730         },
    6731 
    6732         /**
    6733          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6734          */
    6735         detach: function() {
    6736                 if ( this.$el.is(':visible') ) {
    6737                         this.close();
    6738                 }
    6739 
    6740                 this.$el.detach();
    6741                 this.views.attached = false;
    6742                 return this.propagate('detach');
    6743         },
    6744 
    6745         /**
    6746          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6747          */
    6748         open: function() {
    6749                 var $el = this.$el,
    6750                         options = this.options,
    6751                         mceEditor;
    6752 
    6753                 if ( $el.is(':visible') ) {
    6754                         return this;
    6755                 }
    6756 
    6757                 if ( ! this.views.attached ) {
    6758                         this.attach();
    6759                 }
    6760 
    6761                 // If the `freeze` option is set, record the window's scroll position.
    6762                 if ( options.freeze ) {
    6763                         this._freeze = {
    6764                                 scrollTop: $( window ).scrollTop()
    6765                         };
    6766                 }
    6767 
    6768                 // Disable page scrolling.
    6769                 $( 'body' ).addClass( 'modal-open' );
    6770 
    6771                 $el.show();
    6772 
    6773                 // Try to close the onscreen keyboard
    6774                 if ( 'ontouchend' in document ) {
    6775                         if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
    6776                                 mceEditor.iframeElement.focus();
    6777                                 mceEditor.iframeElement.blur();
    6778 
    6779                                 setTimeout( function() {
    6780                                         mceEditor.iframeElement.blur();
    6781                                 }, 100 );
    6782                         }
    6783                 }
    6784 
    6785                 this.$el.focus();
    6786 
    6787                 return this.propagate('open');
    6788         },
    6789 
    6790         /**
    6791          * @param {Object} options
    6792          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6793          */
    6794         close: function( options ) {
    6795                 var freeze = this._freeze;
    6796 
    6797                 if ( ! this.views.attached || ! this.$el.is(':visible') ) {
    6798                         return this;
    6799                 }
    6800 
    6801                 // Enable page scrolling.
    6802                 $( 'body' ).removeClass( 'modal-open' );
    6803 
    6804                 // Hide modal and remove restricted media modal tab focus once it's closed
    6805                 this.$el.hide().undelegate( 'keydown' );
    6806 
    6807                 // Put focus back in useful location once modal is closed
    6808                 $('#wpbody-content').focus();
    6809 
    6810                 this.propagate('close');
    6811 
    6812                 // If the `freeze` option is set, restore the container's scroll position.
    6813                 if ( freeze ) {
    6814                         $( window ).scrollTop( freeze.scrollTop );
    6815                 }
    6816 
    6817                 if ( options && options.escape ) {
    6818                         this.propagate('escape');
    6819                 }
    6820 
    6821                 return this;
    6822         },
    6823         /**
    6824          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6825          */
    6826         escape: function() {
    6827                 return this.close({ escape: true });
    6828         },
    6829         /**
    6830          * @param {Object} event
    6831          */
    6832         escapeHandler: function( event ) {
    6833                 event.preventDefault();
    6834                 this.escape();
    6835         },
    6836 
    6837         /**
    6838          * @param {Array|Object} content Views to register to '.media-modal-content'
    6839          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6840          */
    6841         content: function( content ) {
    6842                 this.views.set( '.media-modal-content', content );
    6843                 return this;
    6844         },
    6845 
    6846         /**
    6847          * Triggers a modal event and if the `propagate` option is set,
    6848          * forwards events to the modal's controller.
    6849          *
    6850          * @param {string} id
    6851          * @returns {wp.media.view.Modal} Returns itself to allow chaining
    6852          */
    6853         propagate: function( id ) {
    6854                 this.trigger( id );
    6855 
    6856                 if ( this.options.propagate ) {
    6857                         this.controller.trigger( id );
    6858                 }
    6859 
    6860                 return this;
    6861         },
    6862         /**
    6863          * @param {Object} event
    6864          */
    6865         keydown: function( event ) {
    6866                 // Close the modal when escape is pressed.
    6867                 if ( 27 === event.which && this.$el.is(':visible') ) {
    6868                         this.escape();
    6869                         event.stopImmediatePropagation();
    6870                 }
    6871         }
    6872 });
    6873 
    6874 module.exports = Modal;
    6875 
    6876 },{}],52:[function(require,module,exports){
    6877 /*globals _, Backbone */
    6878 
    6879 /**
    6880  * wp.media.view.PriorityList
    6881  *
    6882  * @class
    6883  * @augments wp.media.View
    6884  * @augments wp.Backbone.View
    6885  * @augments Backbone.View
    6886  */
    6887 var PriorityList = wp.media.View.extend({
    6888         tagName:   'div',
    6889 
    6890         initialize: function() {
    6891                 this._views = {};
    6892 
    6893                 this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
    6894                 delete this.options.views;
    6895 
    6896                 if ( ! this.options.silent ) {
    6897                         this.render();
    6898                 }
    6899         },
    6900         /**
    6901          * @param {string} id
    6902          * @param {wp.media.View|Object} view
    6903          * @param {Object} options
    6904          * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
    6905          */
    6906         set: function( id, view, options ) {
    6907                 var priority, views, index;
    6908 
    6909                 options = options || {};
    6910 
    6911                 // Accept an object with an `id` : `view` mapping.
    6912                 if ( _.isObject( id ) ) {
    6913                         _.each( id, function( view, id ) {
    6914                                 this.set( id, view );
    6915                         }, this );
    6916                         return this;
    6917                 }
    6918 
    6919                 if ( ! (view instanceof Backbone.View) ) {
    6920                         view = this.toView( view, id, options );
    6921                 }
    6922                 view.controller = view.controller || this.controller;
    6923 
    6924                 this.unset( id );
    6925 
    6926                 priority = view.options.priority || 10;
    6927                 views = this.views.get() || [];
    6928 
    6929                 _.find( views, function( existing, i ) {
    6930                         if ( existing.options.priority > priority ) {
    6931                                 index = i;
    6932                                 return true;
    6933                         }
    6934                 });
    6935 
    6936                 this._views[ id ] = view;
    6937                 this.views.add( view, {
    6938                         at: _.isNumber( index ) ? index : views.length || 0
    6939                 });
    6940 
    6941                 return this;
    6942         },
    6943         /**
    6944          * @param {string} id
    6945          * @returns {wp.media.View}
    6946          */
    6947         get: function( id ) {
    6948                 return this._views[ id ];
    6949         },
    6950         /**
    6951          * @param {string} id
    6952          * @returns {wp.media.view.PriorityList}
    6953          */
    6954         unset: function( id ) {
    6955                 var view = this.get( id );
    6956 
    6957                 if ( view ) {
    6958                         view.remove();
    6959                 }
    6960 
    6961                 delete this._views[ id ];
    6962                 return this;
    6963         },
    6964         /**
    6965          * @param {Object} options
    6966          * @returns {wp.media.View}
    6967          */
    6968         toView: function( options ) {
    6969                 return new wp.media.View( options );
    6970         }
    6971 });
    6972 
    6973 module.exports = PriorityList;
    6974 
    6975 },{}],53:[function(require,module,exports){
    6976 /**
    6977  * wp.media.view.RouterItem
    6978  *
    6979  * @class
    6980  * @augments wp.media.view.MenuItem
    6981  * @augments wp.media.View
    6982  * @augments wp.Backbone.View
    6983  * @augments Backbone.View
    6984  */
    6985 var RouterItem = wp.media.view.MenuItem.extend({
    6986         /**
    6987          * On click handler to activate the content region's corresponding mode.
    6988          */
    6989         click: function() {
    6990                 var contentMode = this.options.contentMode;
    6991                 if ( contentMode ) {
    6992                         this.controller.content.mode( contentMode );
    6993                 }
    6994         }
    6995 });
    6996 
    6997 module.exports = RouterItem;
    6998 
    6999 },{}],54:[function(require,module,exports){
    7000 /*globals wp */
    7001 
    7002 /**
    7003  * wp.media.view.Router
    7004  *
    7005  * @class
    7006  * @augments wp.media.view.Menu
    7007  * @augments wp.media.view.PriorityList
    7008  * @augments wp.media.View
    7009  * @augments wp.Backbone.View
    7010  * @augments Backbone.View
    7011  */
    7012 var Menu = wp.media.view.Menu,
    7013         Router;
    7014 
    7015 Router = Menu.extend({
    7016         tagName:   'div',
    7017         className: 'media-router',
    7018         property:  'contentMode',
    7019         ItemView:  wp.media.view.RouterItem,
    7020         region:    'router',
    7021 
    7022         initialize: function() {
    7023                 this.controller.on( 'content:render', this.update, this );
    7024                 // Call 'initialize' directly on the parent class.
    7025                 Menu.prototype.initialize.apply( this, arguments );
    7026         },
    7027 
    7028         update: function() {
    7029                 var mode = this.controller.content.mode();
    7030                 if ( mode ) {
    7031                         this.select( mode );
    7032                 }
    7033         }
    7034 });
    7035 
    7036 module.exports = Router;
    7037 
    7038 },{}],55:[function(require,module,exports){
    7039 /*globals wp */
    7040 
    7041 /**
    7042  * wp.media.view.Search
    7043  *
    7044  * @class
    7045  * @augments wp.media.View
    7046  * @augments wp.Backbone.View
    7047  * @augments Backbone.View
    7048  */
    7049 var l10n = wp.media.view.l10n,
    7050         Search;
    7051 
    7052 Search = wp.media.View.extend({
    7053         tagName:   'input',
    7054         className: 'search',
    7055         id:        'media-search-input',
    7056 
    7057         attributes: {
    7058                 type:        'search',
    7059                 placeholder: l10n.search
    7060         },
    7061 
    7062         events: {
    7063                 'input':  'search',
    7064                 'keyup':  'search',
    7065                 'change': 'search',
    7066                 'search': 'search'
    7067         },
    7068 
    7069         /**
    7070          * @returns {wp.media.view.Search} Returns itself to allow chaining
    7071          */
    7072         render: function() {
    7073                 this.el.value = this.model.escape('search');
    7074                 return this;
    7075         },
    7076 
    7077         search: function( event ) {
    7078                 if ( event.target.value ) {
    7079                         this.model.set( 'search', event.target.value );
    7080                 } else {
    7081                         this.model.unset('search');
    7082                 }
    7083         }
    7084 });
    7085 
    7086 module.exports = Search;
    7087 
    7088 },{}],56:[function(require,module,exports){
    7089 /*globals wp, _, Backbone */
    7090 
    7091 /**
    7092  * wp.media.view.Selection
    7093  *
    7094  * @class
    7095  * @augments wp.media.View
    7096  * @augments wp.Backbone.View
    7097  * @augments Backbone.View
    7098  */
    7099 var l10n = wp.media.view.l10n,
    7100         Selection;
    7101 
    7102 Selection = wp.media.View.extend({
    7103         tagName:   'div',
    7104         className: 'media-selection',
    7105         template:  wp.template('media-selection'),
    7106 
    7107         events: {
    7108                 'click .edit-selection':  'edit',
    7109                 'click .clear-selection': 'clear'
    7110         },
    7111 
    7112         initialize: function() {
    7113                 _.defaults( this.options, {
    7114                         editable:  false,
    7115                         clearable: true
    7116                 });
    7117 
    7118                 /**
    7119                  * @member {wp.media.view.Attachments.Selection}
    7120                  */
    7121                 this.attachments = new wp.media.view.Attachments.Selection({
    7122                         controller: this.controller,
    7123                         collection: this.collection,
    7124                         selection:  this.collection,
    7125                         model:      new Backbone.Model()
    7126                 });
    7127 
    7128                 this.views.set( '.selection-view', this.attachments );
    7129                 this.collection.on( 'add remove reset', this.refresh, this );
    7130                 this.controller.on( 'content:activate', this.refresh, this );
    7131         },
    7132 
    7133         ready: function() {
    7134                 this.refresh();
    7135         },
    7136 
    7137         refresh: function() {
    7138                 // If the selection hasn't been rendered, bail.
    7139                 if ( ! this.$el.children().length ) {
    7140                         return;
    7141                 }
    7142 
    7143                 var collection = this.collection,
    7144                         editing = 'edit-selection' === this.controller.content.mode();
    7145 
    7146                 // If nothing is selected, display nothing.
    7147                 this.$el.toggleClass( 'empty', ! collection.length );
    7148                 this.$el.toggleClass( 'one', 1 === collection.length );
    7149                 this.$el.toggleClass( 'editing', editing );
    7150 
    7151                 this.$('.count').text( l10n.selected.replace('%d', collection.length) );
    7152         },
    7153 
    7154         edit: function( event ) {
    7155                 event.preventDefault();
    7156                 if ( this.options.editable ) {
    7157                         this.options.editable.call( this, this.collection );
    7158                 }
    7159         },
    7160 
    7161         clear: function( event ) {
    7162                 event.preventDefault();
    7163                 this.collection.reset();
    7164 
    7165                 // Keep focus inside media modal
    7166                 // after clear link is selected
    7167                 this.controller.modal.focusManager.focus();
    7168         }
    7169 });
    7170 
    7171 module.exports = Selection;
    7172 
    7173 },{}],57:[function(require,module,exports){
    7174 /*globals _, Backbone */
    7175 
    7176 /**
    7177  * wp.media.view.Settings
    7178  *
    7179  * @class
    7180  * @augments wp.media.View
    7181  * @augments wp.Backbone.View
    7182  * @augments Backbone.View
    7183  */
    7184 var View = wp.media.View,
    7185         $ = Backbone.$,
    7186         Settings;
    7187 
    7188 Settings = View.extend({
    7189         events: {
    7190                 'click button':    'updateHandler',
    7191                 'change input':    'updateHandler',
    7192                 'change select':   'updateHandler',
    7193                 'change textarea': 'updateHandler'
    7194         },
    7195 
    7196         initialize: function() {
    7197                 this.model = this.model || new Backbone.Model();
    7198                 this.listenTo( this.model, 'change', this.updateChanges );
    7199         },
    7200 
    7201         prepare: function() {
    7202                 return _.defaults({
    7203                         model: this.model.toJSON()
    7204                 }, this.options );
    7205         },
    7206         /**
    7207          * @returns {wp.media.view.Settings} Returns itself to allow chaining
    7208          */
    7209         render: function() {
    7210                 View.prototype.render.apply( this, arguments );
    7211                 // Select the correct values.
    7212                 _( this.model.attributes ).chain().keys().each( this.update, this );
    7213                 return this;
    7214         },
    7215         /**
    7216          * @param {string} key
    7217          */
    7218         update: function( key ) {
    7219                 var value = this.model.get( key ),
    7220                         $setting = this.$('[data-setting="' + key + '"]'),
    7221                         $buttons, $value;
    7222 
    7223                 // Bail if we didn't find a matching setting.
    7224                 if ( ! $setting.length ) {
    7225                         return;
    7226                 }
    7227 
    7228                 // Attempt to determine how the setting is rendered and update
    7229                 // the selected value.
    7230 
    7231                 // Handle dropdowns.
    7232                 if ( $setting.is('select') ) {
    7233                         $value = $setting.find('[value="' + value + '"]');
    7234 
    7235                         if ( $value.length ) {
    7236                                 $setting.find('option').prop( 'selected', false );
    7237                                 $value.prop( 'selected', true );
    7238                         } else {
    7239                                 // If we can't find the desired value, record what *is* selected.
    7240                                 this.model.set( key, $setting.find(':selected').val() );
    7241                         }
    7242 
    7243                 // Handle button groups.
    7244                 } else if ( $setting.hasClass('button-group') ) {
    7245                         $buttons = $setting.find('button').removeClass('active');
    7246                         $buttons.filter( '[value="' + value + '"]' ).addClass('active');
    7247 
    7248                 // Handle text inputs and textareas.
    7249                 } else if ( $setting.is('input[type="text"], textarea') ) {
    7250                         if ( ! $setting.is(':focus') ) {
    7251                                 $setting.val( value );
    7252                         }
    7253                 // Handle checkboxes.
    7254                 } else if ( $setting.is('input[type="checkbox"]') ) {
    7255                         $setting.prop( 'checked', !! value && 'false' !== value );
    7256                 }
    7257         },
    7258         /**
    7259          * @param {Object} event
    7260          */
    7261         updateHandler: function( event ) {
    7262                 var $setting = $( event.target ).closest('[data-setting]'),
    7263                         value = event.target.value,
    7264                         userSetting;
    7265 
    7266                 event.preventDefault();
    7267 
    7268                 if ( ! $setting.length ) {
    7269                         return;
    7270                 }
    7271 
    7272                 // Use the correct value for checkboxes.
    7273                 if ( $setting.is('input[type="checkbox"]') ) {
    7274                         value = $setting[0].checked;
    7275                 }
    7276 
    7277                 // Update the corresponding setting.
    7278                 this.model.set( $setting.data('setting'), value );
    7279 
    7280                 // If the setting has a corresponding user setting,
    7281                 // update that as well.
    7282                 if ( userSetting = $setting.data('userSetting') ) {
    7283                         window.setUserSetting( userSetting, value );
    7284                 }
    7285         },
    7286 
    7287         updateChanges: function( model ) {
    7288                 if ( model.hasChanged() ) {
    7289                         _( model.changed ).chain().keys().each( this.update, this );
    7290                 }
    7291         }
    7292 });
    7293 
    7294 module.exports = Settings;
    7295 
    7296 },{}],58:[function(require,module,exports){
    7297 /*globals wp, _ */
    7298 
    7299 /**
    7300  * wp.media.view.Settings.AttachmentDisplay
    7301  *
    7302  * @class
    7303  * @augments wp.media.view.Settings
    7304  * @augments wp.media.View
    7305  * @augments wp.Backbone.View
    7306  * @augments Backbone.View
    7307  */
    7308 var Settings = wp.media.view.Settings,
    7309         AttachmentDisplay;
    7310 
    7311 AttachmentDisplay = Settings.extend({
    7312         className: 'attachment-display-settings',
    7313         template:  wp.template('attachment-display-settings'),
    7314 
    7315         initialize: function() {
    7316                 var attachment = this.options.attachment;
    7317 
    7318                 _.defaults( this.options, {
    7319                         userSettings: false
    7320                 });
    7321                 // Call 'initialize' directly on the parent class.
    7322                 Settings.prototype.initialize.apply( this, arguments );
    7323                 this.listenTo( this.model, 'change:link', this.updateLinkTo );
    7324 
    7325                 if ( attachment ) {
    7326                         attachment.on( 'change:uploading', this.render, this );
    7327                 }
    7328         },
    7329 
    7330         dispose: function() {
    7331                 var attachment = this.options.attachment;
    7332                 if ( attachment ) {
    7333                         attachment.off( null, null, this );
    7334                 }
    7335                 /**
    7336                  * call 'dispose' directly on the parent class
    7337                  */
    7338                 Settings.prototype.dispose.apply( this, arguments );
    7339         },
    7340         /**
    7341          * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
    7342          */
    7343         render: function() {
    7344                 var attachment = this.options.attachment;
    7345                 if ( attachment ) {
    7346                         _.extend( this.options, {
    7347                                 sizes: attachment.get('sizes'),
    7348                                 type:  attachment.get('type')
    7349                         });
    7350                 }
    7351                 /**
    7352                  * call 'render' directly on the parent class
    7353                  */
    7354                 Settings.prototype.render.call( this );
    7355                 this.updateLinkTo();
    7356                 return this;
    7357         },
    7358 
    7359         updateLinkTo: function() {
    7360                 var linkTo = this.model.get('link'),
    7361                         $input = this.$('.link-to-custom'),
    7362                         attachment = this.options.attachment;
    7363 
    7364                 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
    7365                         $input.addClass( 'hidden' );
    7366                         return;
    7367                 }
    7368 
    7369                 if ( attachment ) {
    7370                         if ( 'post' === linkTo ) {
    7371                                 $input.val( attachment.get('link') );
    7372                         } else if ( 'file' === linkTo ) {
    7373                                 $input.val( attachment.get('url') );
    7374                         } else if ( ! this.model.get('linkUrl') ) {
    7375                                 $input.val('http://');
    7376                         }
    7377 
    7378                         $input.prop( 'readonly', 'custom' !== linkTo );
    7379                 }
    7380 
    7381                 $input.removeClass( 'hidden' );
    7382 
    7383                 // If the input is visible, focus and select its contents.
    7384                 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
    7385                         $input.focus()[0].select();
    7386                 }
    7387         }
    7388 });
    7389 
    7390 module.exports = AttachmentDisplay;
    7391 
    7392 },{}],59:[function(require,module,exports){
    7393 /*globals wp */
    7394 
    7395 /**
    7396  * wp.media.view.Settings.Gallery
    7397  *
    7398  * @class
    7399  * @augments wp.media.view.Settings
    7400  * @augments wp.media.View
    7401  * @augments wp.Backbone.View
    7402  * @augments Backbone.View
    7403  */
    7404 var Gallery = wp.media.view.Settings.extend({
    7405         className: 'collection-settings gallery-settings',
    7406         template:  wp.template('gallery-settings')
    7407 });
    7408 
    7409 module.exports = Gallery;
    7410 
    7411 },{}],60:[function(require,module,exports){
    7412 /*globals wp */
    7413 
    7414 /**
    7415  * wp.media.view.Settings.Playlist
    7416  *
    7417  * @class
    7418  * @augments wp.media.view.Settings
    7419  * @augments wp.media.View
    7420  * @augments wp.Backbone.View
    7421  * @augments Backbone.View
    7422  */
    7423 var Playlist = wp.media.view.Settings.extend({
    7424         className: 'collection-settings playlist-settings',
    7425         template:  wp.template('playlist-settings')
    7426 });
    7427 
    7428 module.exports = Playlist;
    7429 
    7430 },{}],61:[function(require,module,exports){
    7431 /**
    7432  * wp.media.view.Sidebar
    7433  *
    7434  * @class
    7435  * @augments wp.media.view.PriorityList
    7436  * @augments wp.media.View
    7437  * @augments wp.Backbone.View
    7438  * @augments Backbone.View
    7439  */
    7440 var Sidebar = wp.media.view.PriorityList.extend({
    7441         className: 'media-sidebar'
    7442 });
    7443 
    7444 module.exports = Sidebar;
    7445 
    7446 },{}],62:[function(require,module,exports){
    7447 /*globals _ */
    7448 
    7449 /**
    7450  * wp.media.view.Spinner
    7451  *
    7452  * @class
    7453  * @augments wp.media.View
    7454  * @augments wp.Backbone.View
    7455  * @augments Backbone.View
    7456  */
    7457 var Spinner = wp.media.View.extend({
    7458         tagName:   'span',
    7459         className: 'spinner',
    7460         spinnerTimeout: false,
    7461         delay: 400,
    7462 
    7463         show: function() {
    7464                 if ( ! this.spinnerTimeout ) {
    7465                         this.spinnerTimeout = _.delay(function( $el ) {
    7466                                 $el.show();
    7467                         }, this.delay, this.$el );
    7468                 }
    7469 
    7470                 return this;
    7471         },
    7472 
    7473         hide: function() {
    7474                 this.$el.hide();
    7475                 this.spinnerTimeout = clearTimeout( this.spinnerTimeout );
    7476 
    7477                 return this;
    7478         }
    7479 });
    7480 
    7481 module.exports = Spinner;
    7482 
    7483 },{}],63:[function(require,module,exports){
    7484 /*globals _, Backbone */
    7485 
    7486 /**
    7487  * wp.media.view.Toolbar
    7488  *
    7489  * A toolbar which consists of a primary and a secondary section. Each sections
    7490  * can be filled with views.
    7491  *
    7492  * @class
    7493  * @augments wp.media.View
    7494  * @augments wp.Backbone.View
    7495  * @augments Backbone.View
    7496  */
    7497 var View = wp.media.View,
    7498         Toolbar;
    7499 
    7500 Toolbar = View.extend({
    7501         tagName:   'div',
    7502         className: 'media-toolbar',
    7503 
    7504         initialize: function() {
    7505                 var state = this.controller.state(),
    7506                         selection = this.selection = state.get('selection'),
    7507                         library = this.library = state.get('library');
    7508 
    7509                 this._views = {};
    7510 
    7511                 // The toolbar is composed of two `PriorityList` views.
    7512                 this.primary   = new wp.media.view.PriorityList();
    7513                 this.secondary = new wp.media.view.PriorityList();
    7514                 this.primary.$el.addClass('media-toolbar-primary search-form');
    7515                 this.secondary.$el.addClass('media-toolbar-secondary');
    7516 
    7517                 this.views.set([ this.secondary, this.primary ]);
    7518 
    7519                 if ( this.options.items ) {
    7520                         this.set( this.options.items, { silent: true });
    7521                 }
    7522 
    7523                 if ( ! this.options.silent ) {
    7524                         this.render();
    7525                 }
    7526 
    7527                 if ( selection ) {
    7528                         selection.on( 'add remove reset', this.refresh, this );
    7529                 }
    7530 
    7531                 if ( library ) {
    7532                         library.on( 'add remove reset', this.refresh, this );
    7533                 }
    7534         },
    7535         /**
    7536          * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
    7537          */
    7538         dispose: function() {
    7539                 if ( this.selection ) {
    7540                         this.selection.off( null, null, this );
    7541                 }
    7542 
    7543                 if ( this.library ) {
    7544                         this.library.off( null, null, this );
    7545                 }
    7546                 /**
    7547                  * call 'dispose' directly on the parent class
    7548                  */
    7549                 return View.prototype.dispose.apply( this, arguments );
    7550         },
    7551 
    7552         ready: function() {
    7553                 this.refresh();
    7554         },
    7555 
    7556         /**
    7557          * @param {string} id
    7558          * @param {Backbone.View|Object} view
    7559          * @param {Object} [options={}]
    7560          * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
    7561          */
    7562         set: function( id, view, options ) {
    7563                 var list;
    7564                 options = options || {};
    7565 
    7566                 // Accept an object with an `id` : `view` mapping.
    7567                 if ( _.isObject( id ) ) {
    7568                         _.each( id, function( view, id ) {
    7569                                 this.set( id, view, { silent: true });
    7570                         }, this );
    7571 
    7572                 } else {
    7573                         if ( ! ( view instanceof Backbone.View ) ) {
    7574                                 view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
    7575                                 view = new wp.media.view.Button( view ).render();
    7576                         }
    7577 
    7578                         view.controller = view.controller || this.controller;
    7579 
    7580                         this._views[ id ] = view;
    7581 
    7582                         list = view.options.priority < 0 ? 'secondary' : 'primary';
    7583                         this[ list ].set( id, view, options );
    7584                 }
    7585 
    7586                 if ( ! options.silent ) {
    7587                         this.refresh();
    7588                 }
    7589 
    7590                 return this;
    7591         },
    7592         /**
    7593          * @param {string} id
    7594          * @returns {wp.media.view.Button}
    7595          */
    7596         get: function( id ) {
    7597                 return this._views[ id ];
    7598         },
    7599         /**
    7600          * @param {string} id
    7601          * @param {Object} options
    7602          * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
    7603          */
    7604         unset: function( id, options ) {
    7605                 delete this._views[ id ];
    7606                 this.primary.unset( id, options );
    7607                 this.secondary.unset( id, options );
    7608 
    7609                 if ( ! options || ! options.silent ) {
    7610                         this.refresh();
    7611                 }
    7612                 return this;
    7613         },
    7614 
    7615         refresh: function() {
    7616                 var state = this.controller.state(),
    7617                         library = state.get('library'),
    7618                         selection = state.get('selection');
    7619 
    7620                 _.each( this._views, function( button ) {
    7621                         if ( ! button.model || ! button.options || ! button.options.requires ) {
    7622                                 return;
    7623                         }
    7624 
    7625                         var requires = button.options.requires,
    7626                                 disabled = false;
    7627 
    7628                         // Prevent insertion of attachments if any of them are still uploading
    7629                         disabled = _.some( selection.models, function( attachment ) {
    7630                                 return attachment.get('uploading') === true;
    7631                         });
    7632 
    7633                         if ( requires.selection && selection && ! selection.length ) {
    7634                                 disabled = true;
    7635                         } else if ( requires.library && library && ! library.length ) {
    7636                                 disabled = true;
    7637                         }
    7638                         button.model.set( 'disabled', disabled );
    7639                 });
    7640         }
    7641 });
    7642 
    7643 module.exports = Toolbar;
    7644 
    7645 },{}],64:[function(require,module,exports){
    7646 /*globals wp, _ */
    7647 
    7648 /**
    7649  * wp.media.view.Toolbar.Embed
    7650  *
    7651  * @class
    7652  * @augments wp.media.view.Toolbar.Select
    7653  * @augments wp.media.view.Toolbar
    7654  * @augments wp.media.View
    7655  * @augments wp.Backbone.View
    7656  * @augments Backbone.View
    7657  */
    7658 var Select = wp.media.view.Toolbar.Select,
    7659         l10n = wp.media.view.l10n,
    7660         Embed;
    7661 
    7662 Embed = Select.extend({
    7663         initialize: function() {
    7664                 _.defaults( this.options, {
    7665                         text: l10n.insertIntoPost,
    7666                         requires: false
    7667                 });
    7668                 // Call 'initialize' directly on the parent class.
    7669                 Select.prototype.initialize.apply( this, arguments );
    7670         },
    7671 
    7672         refresh: function() {
    7673                 var url = this.controller.state().props.get('url');
    7674                 this.get('select').model.set( 'disabled', ! url || url === 'http://' );
    7675                 /**
    7676                  * call 'refresh' directly on the parent class
    7677                  */
    7678                 Select.prototype.refresh.apply( this, arguments );
    7679         }
    7680 });
    7681 
    7682 module.exports = Embed;
    7683 
    7684 },{}],65:[function(require,module,exports){
    7685 /*globals wp, _ */
    7686 
    7687 /**
    7688  * wp.media.view.Toolbar.Select
    7689  *
    7690  * @class
    7691  * @augments wp.media.view.Toolbar
    7692  * @augments wp.media.View
    7693  * @augments wp.Backbone.View
    7694  * @augments Backbone.View
    7695  */
    7696 var Toolbar = wp.media.view.Toolbar,
    7697         l10n = wp.media.view.l10n,
    7698         Select;
    7699 
    7700 Select = Toolbar.extend({
    7701         initialize: function() {
    7702                 var options = this.options;
    7703 
    7704                 _.bindAll( this, 'clickSelect' );
    7705 
    7706                 _.defaults( options, {
    7707                         event: 'select',
    7708                         state: false,
    7709                         reset: true,
    7710                         close: true,
    7711                         text:  l10n.select,
    7712 
    7713                         // Does the button rely on the selection?
    7714                         requires: {
    7715                                 selection: true
    7716                         }
    7717                 });
    7718 
    7719                 options.items = _.defaults( options.items || {}, {
    7720                         select: {
    7721                                 style:    'primary',
    7722                                 text:     options.text,
    7723                                 priority: 80,
    7724                                 click:    this.clickSelect,
    7725                                 requires: options.requires
    7726                         }
    7727                 });
    7728                 // Call 'initialize' directly on the parent class.
    7729                 Toolbar.prototype.initialize.apply( this, arguments );
    7730         },
    7731 
    7732         clickSelect: function() {
    7733                 var options = this.options,
    7734                         controller = this.controller;
    7735 
    7736                 if ( options.close ) {
    7737                         controller.close();
    7738                 }
    7739 
    7740                 if ( options.event ) {
    7741                         controller.state().trigger( options.event );
    7742                 }
    7743 
    7744                 if ( options.state ) {
    7745                         controller.setState( options.state );
    7746                 }
    7747 
    7748                 if ( options.reset ) {
    7749                         controller.reset();
    7750                 }
    7751         }
    7752 });
    7753 
    7754 module.exports = Select;
    7755 
    7756 },{}],66:[function(require,module,exports){
    7757 /*globals wp, _, jQuery */
    7758 
    7759 /**
    7760  * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap
    7761  * or #wp-fullscreen-body) and relays drag'n'dropped files to a media workflow.
    7762  *
    7763  * wp.media.view.EditorUploader
    7764  *
    7765  * @class
    7766  * @augments wp.media.View
    7767  * @augments wp.Backbone.View
    7768  * @augments Backbone.View
    7769  */
    7770 var View = wp.media.View,
    7771         l10n = wp.media.view.l10n,
    7772         $ = jQuery,
    7773         EditorUploader;
    7774 
    7775 EditorUploader = View.extend({
    7776         tagName:   'div',
    7777         className: 'uploader-editor',
    7778         template:  wp.template( 'uploader-editor' ),
    7779 
    7780         localDrag: false,
    7781         overContainer: false,
    7782         overDropzone: false,
    7783         draggingFile: null,
    7784 
    7785         /**
    7786          * Bind drag'n'drop events to callbacks.
    7787          */
    7788         initialize: function() {
    7789                 this.initialized = false;
    7790 
    7791                 // Bail if not enabled or UA does not support drag'n'drop or File API.
    7792                 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
    7793                         return this;
    7794                 }
    7795 
    7796                 this.$document = $(document);
    7797                 this.dropzones = [];
    7798                 this.files = [];
    7799 
    7800                 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
    7801                 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
    7802                 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
    7803                 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
    7804 
    7805                 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
    7806                 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
    7807 
    7808                 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
    7809                         this.localDrag = event.type === 'dragstart';
    7810                 }, this ) );
    7811 
    7812                 this.initialized = true;
    7813                 return this;
    7814         },
    7815 
    7816         /**
    7817          * Check browser support for drag'n'drop.
    7818          *
    7819          * @return Boolean
    7820          */
    7821         browserSupport: function() {
    7822                 var supports = false, div = document.createElement('div');
    7823 
    7824                 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
    7825                 supports = supports && !! ( window.File && window.FileList && window.FileReader );
    7826                 return supports;
    7827         },
    7828 
    7829         isDraggingFile: function( event ) {
    7830                 if ( this.draggingFile !== null ) {
    7831                         return this.draggingFile;
    7832                 }
    7833 
    7834                 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
    7835                         return false;
    7836                 }
    7837 
    7838                 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
    7839                         _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
    7840 
    7841                 return this.draggingFile;
    7842         },
    7843 
    7844         refresh: function( e ) {
    7845                 var dropzone_id;
    7846                 for ( dropzone_id in this.dropzones ) {
    7847                         // Hide the dropzones only if dragging has left the screen.
    7848                         this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
    7849                 }
    7850 
    7851                 if ( ! _.isUndefined( e ) ) {
    7852                         $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
    7853                 }
    7854 
    7855                 if ( ! this.overContainer && ! this.overDropzone ) {
    7856                         this.draggingFile = null;
    7857                 }
    7858 
    7859                 return this;
    7860         },
    7861 
    7862         render: function() {
    7863                 if ( ! this.initialized ) {
    7864                         return this;
    7865                 }
    7866 
    7867                 View.prototype.render.apply( this, arguments );
    7868                 $( '.wp-editor-wrap, #wp-fullscreen-body' ).each( _.bind( this.attach, this ) );
    7869                 return this;
    7870         },
    7871 
    7872         attach: function( index, editor ) {
    7873                 // Attach a dropzone to an editor.
    7874                 var dropzone = this.$el.clone();
    7875                 this.dropzones.push( dropzone );
    7876                 $( editor ).append( dropzone );
    7877                 return this;
    7878         },
    7879 
    7880         /**
    7881          * When a file is dropped on the editor uploader, open up an editor media workflow
    7882          * and upload the file immediately.
    7883          *
    7884          * @param  {jQuery.Event} event The 'drop' event.
    7885          */
    7886         drop: function( event ) {
    7887                 var $wrap = null, uploadView;
    7888 
    7889                 this.containerDragleave( event );
    7890                 this.dropzoneDragleave( event );
    7891 
    7892                 this.files = event.originalEvent.dataTransfer.files;
    7893                 if ( this.files.length < 1 ) {
    7894                         return;
    7895                 }
    7896 
    7897                 // Set the active editor to the drop target.
    7898                 $wrap = $( event.target ).parents( '.wp-editor-wrap' );
    7899                 if ( $wrap.length > 0 && $wrap[0].id ) {
    7900                         window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
    7901                 }
    7902 
    7903                 if ( ! this.workflow ) {
    7904                         this.workflow = wp.media.editor.open( 'content', {
    7905                                 frame:    'post',
    7906                                 state:    'insert',
    7907                                 title:    l10n.addMedia,
    7908                                 multiple: true
    7909                         });
    7910                         uploadView = this.workflow.uploader;
    7911                         if ( uploadView.uploader && uploadView.uploader.ready ) {
    7912                                 this.addFiles.apply( this );
    7913                         } else {
    7914                                 this.workflow.on( 'uploader:ready', this.addFiles, this );
    7915                         }
    7916                 } else {
    7917                         this.workflow.state().reset();
    7918                         this.addFiles.apply( this );
    7919                         this.workflow.open();
    7920                 }
    7921 
    7922                 return false;
    7923         },
    7924 
    7925         /**
    7926          * Add the files to the uploader.
    7927          */
    7928         addFiles: function() {
    7929                 if ( this.files.length ) {
    7930                         this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
    7931                         this.files = [];
    7932                 }
    7933                 return this;
    7934         },
    7935 
    7936         containerDragover: function( event ) {
    7937                 if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    7938                         return;
    7939                 }
    7940 
    7941                 this.overContainer = true;
    7942                 this.refresh();
    7943         },
    7944 
    7945         containerDragleave: function() {
    7946                 this.overContainer = false;
    7947 
    7948                 // Throttle dragleave because it's called when bouncing from some elements to others.
    7949                 _.delay( _.bind( this.refresh, this ), 50 );
    7950         },
    7951 
    7952         dropzoneDragover: function( event ) {
    7953                 if ( this.localDrag || ! this.isDraggingFile( event ) ) {
    7954                         return;
    7955                 }
    7956 
    7957                 this.overDropzone = true;
    7958                 this.refresh( event );
    7959                 return false;
    7960         },
    7961 
    7962         dropzoneDragleave: function( e ) {
    7963                 this.overDropzone = false;
    7964                 _.delay( _.bind( this.refresh, this, e ), 50 );
    7965         },
    7966 
    7967         click: function( e ) {
    7968                 // In the rare case where the dropzone gets stuck, hide it on click.
    7969                 this.containerDragleave( e );
    7970                 this.dropzoneDragleave( e );
    7971                 this.localDrag = false;
    7972         }
    7973 });
    7974 
    7975 module.exports = EditorUploader;
    7976 
    7977 },{}],67:[function(require,module,exports){
    7978 /*globals wp, _ */
    7979 
    7980 /**
    7981  * wp.media.view.UploaderInline
    7982  *
    7983  * The inline uploader that shows up in the 'Upload Files' tab.
    7984  *
    7985  * @class
    7986  * @augments wp.media.View
    7987  * @augments wp.Backbone.View
    7988  * @augments Backbone.View
    7989  */
    7990 var View = wp.media.View,
    7991         UploaderInline;
    7992 
    7993 UploaderInline = View.extend({
    7994         tagName:   'div',
    7995         className: 'uploader-inline',
    7996         template:  wp.template('uploader-inline'),
    7997 
    7998         events: {
    7999                 'click .close': 'hide'
    8000         },
    8001 
    8002         initialize: function() {
    8003                 _.defaults( this.options, {
    8004                         message: '',
    8005                         status:  true,
    8006                         canClose: false
    8007                 });
    8008 
    8009                 if ( ! this.options.$browser && this.controller.uploader ) {
    8010                         this.options.$browser = this.controller.uploader.$browser;
    8011                 }
    8012 
    8013                 if ( _.isUndefined( this.options.postId ) ) {
    8014                         this.options.postId = wp.media.view.settings.post.id;
    8015                 }
    8016 
    8017                 if ( this.options.status ) {
    8018                         this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
    8019                                 controller: this.controller
    8020                         }) );
    8021                 }
    8022         },
    8023 
    8024         prepare: function() {
    8025                 var suggestedWidth = this.controller.state().get('suggestedWidth'),
    8026                         suggestedHeight = this.controller.state().get('suggestedHeight'),
    8027                         data = {};
    8028 
    8029                 data.message = this.options.message;
    8030                 data.canClose = this.options.canClose;
    8031 
    8032                 if ( suggestedWidth && suggestedHeight ) {
    8033                         data.suggestedWidth = suggestedWidth;
    8034                         data.suggestedHeight = suggestedHeight;
    8035                 }
    8036 
    8037                 return data;
    8038         },
    8039         /**
    8040          * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    8041          */
    8042         dispose: function() {
    8043                 if ( this.disposing ) {
    8044                         /**
    8045                          * call 'dispose' directly on the parent class
    8046                          */
    8047                         return View.prototype.dispose.apply( this, arguments );
    8048                 }
    8049 
    8050                 // Run remove on `dispose`, so we can be sure to refresh the
    8051                 // uploader with a view-less DOM. Track whether we're disposing
    8052                 // so we don't trigger an infinite loop.
    8053                 this.disposing = true;
    8054                 return this.remove();
    8055         },
    8056         /**
    8057          * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
    8058          */
    8059         remove: function() {
    8060                 /**
    8061                  * call 'remove' directly on the parent class
    8062                  */
    8063                 var result = View.prototype.remove.apply( this, arguments );
    8064 
    8065                 _.defer( _.bind( this.refresh, this ) );
    8066                 return result;
    8067         },
    8068 
    8069         refresh: function() {
    8070                 var uploader = this.controller.uploader;
    8071 
    8072                 if ( uploader ) {
    8073                         uploader.refresh();
    8074                 }
    8075         },
    8076         /**
    8077          * @returns {wp.media.view.UploaderInline}
    8078          */
    8079         ready: function() {
    8080                 var $browser = this.options.$browser,
    8081                         $placeholder;
    8082 
    8083                 if ( this.controller.uploader ) {
    8084                         $placeholder = this.$('.browser');
    8085 
    8086                         // Check if we've already replaced the placeholder.
    8087                         if ( $placeholder[0] === $browser[0] ) {
    8088                                 return;
    8089                         }
    8090 
    8091                         $browser.detach().text( $placeholder.text() );
    8092                         $browser[0].className = $placeholder[0].className;
    8093                         $placeholder.replaceWith( $browser.show() );
    8094                 }
    8095 
    8096                 this.refresh();
    8097                 return this;
    8098         },
    8099         show: function() {
    8100                 this.$el.removeClass( 'hidden' );
    8101         },
    8102         hide: function() {
    8103                 this.$el.addClass( 'hidden' );
    8104         }
    8105 
    8106 });
    8107 
    8108 module.exports = UploaderInline;
    8109 
    8110 },{}],68:[function(require,module,exports){
    8111 /*globals wp */
    8112 
    8113 /**
    8114  * wp.media.view.UploaderStatusError
    8115  *
    8116  * @class
    8117  * @augments wp.media.View
    8118  * @augments wp.Backbone.View
    8119  * @augments Backbone.View
    8120  */
    8121 var UploaderStatusError = wp.media.View.extend({
    8122         className: 'upload-error',
    8123         template:  wp.template('uploader-status-error')
    8124 });
    8125 
    8126 module.exports = UploaderStatusError;
    8127 
    8128 },{}],69:[function(require,module,exports){
    8129 /*globals wp, _ */
    8130 
    8131 /**
    8132  * wp.media.view.UploaderStatus
    8133  *
    8134  * An uploader status for on-going uploads.
    8135  *
    8136  * @class
    8137  * @augments wp.media.View
    8138  * @augments wp.Backbone.View
    8139  * @augments Backbone.View
    8140  */
    8141 var View = wp.media.View,
    8142         UploaderStatus;
    8143 
    8144 UploaderStatus = View.extend({
    8145         className: 'media-uploader-status',
    8146         template:  wp.template('uploader-status'),
    8147 
    8148         events: {
    8149                 'click .upload-dismiss-errors': 'dismiss'
    8150         },
    8151 
    8152         initialize: function() {
    8153                 this.queue = wp.Uploader.queue;
    8154                 this.queue.on( 'add remove reset', this.visibility, this );
    8155                 this.queue.on( 'add remove reset change:percent', this.progress, this );
    8156                 this.queue.on( 'add remove reset change:uploading', this.info, this );
    8157 
    8158                 this.errors = wp.Uploader.errors;
    8159                 this.errors.reset();
    8160                 this.errors.on( 'add remove reset', this.visibility, this );
    8161                 this.errors.on( 'add', this.error, this );
    8162         },
    8163         /**
    8164          * @global wp.Uploader
    8165          * @returns {wp.media.view.UploaderStatus}
    8166          */
    8167         dispose: function() {
    8168                 wp.Uploader.queue.off( null, null, this );
    8169                 /**
    8170                  * call 'dispose' directly on the parent class
    8171                  */
    8172                 View.prototype.dispose.apply( this, arguments );
    8173                 return this;
    8174         },
    8175 
    8176         visibility: function() {
    8177                 this.$el.toggleClass( 'uploading', !! this.queue.length );
    8178                 this.$el.toggleClass( 'errors', !! this.errors.length );
    8179                 this.$el.toggle( !! this.queue.length || !! this.errors.length );
    8180         },
    8181 
    8182         ready: function() {
    8183                 _.each({
    8184                         '$bar':      '.media-progress-bar div',
    8185                         '$index':    '.upload-index',
    8186                         '$total':    '.upload-total',
    8187                         '$filename': '.upload-filename'
    8188                 }, function( selector, key ) {
    8189                         this[ key ] = this.$( selector );
    8190                 }, this );
    8191 
    8192                 this.visibility();
    8193                 this.progress();
    8194                 this.info();
    8195         },
    8196 
    8197         progress: function() {
    8198                 var queue = this.queue,
    8199                         $bar = this.$bar;
    8200 
    8201                 if ( ! $bar || ! queue.length ) {
    8202                         return;
    8203                 }
    8204 
    8205                 $bar.width( ( queue.reduce( function( memo, attachment ) {
    8206                         if ( ! attachment.get('uploading') ) {
    8207                                 return memo + 100;
    8208                         }
    8209 
    8210                         var percent = attachment.get('percent');
    8211                         return memo + ( _.isNumber( percent ) ? percent : 100 );
    8212                 }, 0 ) / queue.length ) + '%' );
    8213         },
    8214 
    8215         info: function() {
    8216                 var queue = this.queue,
    8217                         index = 0, active;
    8218 
    8219                 if ( ! queue.length ) {
    8220                         return;
    8221                 }
    8222 
    8223                 active = this.queue.find( function( attachment, i ) {
    8224                         index = i;
    8225                         return attachment.get('uploading');
    8226                 });
    8227 
    8228                 this.$index.text( index + 1 );
    8229                 this.$total.text( queue.length );
    8230                 this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
    8231         },
    8232         /**
    8233          * @param {string} filename
    8234          * @returns {string}
    8235          */
    8236         filename: function( filename ) {
    8237                 return wp.media.truncate( _.escape( filename ), 24 );
    8238         },
    8239         /**
    8240          * @param {Backbone.Model} error
    8241          */
    8242         error: function( error ) {
    8243                 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
    8244                         filename: this.filename( error.get('file').name ),
    8245                         message:  error.get('message')
    8246                 }), { at: 0 });
    8247         },
    8248 
    8249         /**
    8250          * @global wp.Uploader
    8251          *
    8252          * @param {Object} event
    8253          */
    8254         dismiss: function( event ) {
    8255                 var errors = this.views.get('.upload-errors');
    8256 
    8257                 event.preventDefault();
    8258 
    8259                 if ( errors ) {
    8260                         _.invoke( errors, 'remove' );
    8261                 }
    8262                 wp.Uploader.errors.reset();
    8263         }
    8264 });
    8265 
    8266 module.exports = UploaderStatus;
    8267 
    8268 },{}],70:[function(require,module,exports){
    8269 /*globals wp, _, jQuery */
    8270 
    8271 /**
    8272  * wp.media.view.UploaderWindow
    8273  *
    8274  * An uploader window that allows for dragging and dropping media.
    8275  *
    8276  * @class
    8277  * @augments wp.media.View
    8278  * @augments wp.Backbone.View
    8279  * @augments Backbone.View
    8280  *
    8281  * @param {object} [options]                   Options hash passed to the view.
    8282  * @param {object} [options.uploader]          Uploader properties.
    8283  * @param {jQuery} [options.uploader.browser]
    8284  * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
    8285  * @param {object} [options.uploader.params]
    8286  */
    8287 var $ = jQuery,
    8288         UploaderWindow;
    8289 
    8290 UploaderWindow = wp.media.View.extend({
    8291         tagName:   'div',
    8292         className: 'uploader-window',
    8293         template:  wp.template('uploader-window'),
    8294 
    8295         initialize: function() {
    8296                 var uploader;
    8297 
    8298                 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');
    8299 
    8300                 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
    8301                         dropzone:  this.$el,
    8302                         browser:   this.$browser,
    8303                         params:    {}
    8304                 });
    8305 
    8306                 // Ensure the dropzone is a jQuery collection.
    8307                 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
    8308                         uploader.dropzone = $( uploader.dropzone );
    8309                 }
    8310 
    8311                 this.controller.on( 'activate', this.refresh, this );
    8312 
    8313                 this.controller.on( 'detach', function() {
    8314                         this.$browser.remove();
    8315                 }, this );
    8316         },
    8317 
    8318         refresh: function() {
    8319                 if ( this.uploader ) {
    8320                         this.uploader.refresh();
    8321                 }
    8322         },
    8323 
    8324         ready: function() {
    8325                 var postId = wp.media.view.settings.post.id,
    8326                         dropzone;
    8327 
    8328                 // If the uploader already exists, bail.
    8329                 if ( this.uploader ) {
    8330                         return;
    8331                 }
    8332 
    8333                 if ( postId ) {
    8334                         this.options.uploader.params.post_id = postId;
    8335                 }
    8336                 this.uploader = new wp.Uploader( this.options.uploader );
    8337 
    8338                 dropzone = this.uploader.dropzone;
    8339                 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
    8340                 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
    8341 
    8342                 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
    8343         },
    8344 
    8345         _ready: function() {
    8346                 this.controller.trigger( 'uploader:ready' );
    8347         },
    8348 
    8349         show: function() {
    8350                 var $el = this.$el.show();
    8351 
    8352                 // Ensure that the animation is triggered by waiting until
    8353                 // the transparent element is painted into the DOM.
    8354                 _.defer( function() {
    8355                         $el.css({ opacity: 1 });
    8356                 });
    8357         },
    8358 
    8359         hide: function() {
    8360                 var $el = this.$el.css({ opacity: 0 });
    8361 
    8362                 wp.media.transition( $el ).done( function() {
    8363                         // Transition end events are subject to race conditions.
    8364                         // Make sure that the value is set as intended.
    8365                         if ( '0' === $el.css('opacity') ) {
    8366                                 $el.hide();
    8367                         }
    8368                 });
    8369 
    8370                 // https://core.trac.wordpress.org/ticket/27341
    8371                 _.delay( function() {
    8372                         if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
    8373                                 $el.hide();
    8374                         }
    8375                 }, 500 );
    8376         }
    8377 });
    8378 
    8379 module.exports = UploaderWindow;
    8380 
    8381 },{}],71:[function(require,module,exports){
    8382 /*globals wp */
    8383 
    8384 /**
    8385  * wp.media.View
    8386  *
    8387  * The base view class for media.
    8388  *
    8389  * Undelegating events, removing events from the model, and
    8390  * removing events from the controller mirror the code for
    8391  * `Backbone.View.dispose` in Backbone 0.9.8 development.
    8392  *
    8393  * This behavior has since been removed, and should not be used
    8394  * outside of the media manager.
    8395  *
    8396  * @class
    8397  * @augments wp.Backbone.View
    8398  * @augments Backbone.View
    8399  */
    8400 var View = wp.Backbone.View.extend({
    8401         constructor: function( options ) {
    8402                 if ( options && options.controller ) {
    8403                         this.controller = options.controller;
    8404                 }
    8405                 wp.Backbone.View.apply( this, arguments );
    8406         },
    8407         /**
    8408          * @todo The internal comment mentions this might have been a stop-gap
    8409          *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
    8410          *       care of this in Backbone.View now.
    8411          *
    8412          * @returns {wp.media.View} Returns itself to allow chaining
    8413          */
    8414         dispose: function() {
    8415                 // Undelegating events, removing events from the model, and
    8416                 // removing events from the controller mirror the code for
    8417                 // `Backbone.View.dispose` in Backbone 0.9.8 development.
    8418                 this.undelegateEvents();
    8419 
    8420                 if ( this.model && this.model.off ) {
    8421                         this.model.off( null, null, this );
    8422                 }
    8423 
    8424                 if ( this.collection && this.collection.off ) {
    8425                         this.collection.off( null, null, this );
    8426                 }
    8427 
    8428                 // Unbind controller events.
    8429                 if ( this.controller && this.controller.off ) {
    8430                         this.controller.off( null, null, this );
    8431                 }
    8432 
    8433                 return this;
    8434         },
    8435         /**
    8436          * @returns {wp.media.View} Returns itself to allow chaining
    8437          */
    8438         remove: function() {
    8439                 this.dispose();
    8440                 /**
    8441                  * call 'remove' directly on the parent class
    8442                  */
    8443                 return wp.Backbone.View.prototype.remove.apply( this, arguments );
    8444         }
    8445 });
    8446 
    8447 module.exports = View;
    8448 
    8449 },{}]},{},[17]);
  • src/wp-includes/js/media-audiovideo.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/*globals wp, _ */
     3
     4var media = wp.media,
     5        baseSettings = window._wpmejsSettings || {},
     6        l10n = window._wpMediaViewsL10n || {};
     7
     8/**
     9 * @mixin
     10 */
     11wp.media.mixin = {
     12        mejsSettings: baseSettings,
     13
     14        removeAllPlayers: function() {
     15                var p;
     16
     17                if ( window.mejs && window.mejs.players ) {
     18                        for ( p in window.mejs.players ) {
     19                                window.mejs.players[p].pause();
     20                                this.removePlayer( window.mejs.players[p] );
     21                        }
     22                }
     23        },
     24
     25        /**
     26         * Override the MediaElement method for removing a player.
     27         *      MediaElement tries to pull the audio/video tag out of
     28         *      its container and re-add it to the DOM.
     29         */
     30        removePlayer: function(t) {
     31                var featureIndex, feature;
     32
     33                if ( ! t.options ) {
     34                        return;
     35                }
     36
     37                // invoke features cleanup
     38                for ( featureIndex in t.options.features ) {
     39                        feature = t.options.features[featureIndex];
     40                        if ( t['clean' + feature] ) {
     41                                try {
     42                                        t['clean' + feature](t);
     43                                } catch (e) {}
     44                        }
     45                }
     46
     47                if ( ! t.isDynamic ) {
     48                        t.$node.remove();
     49                }
     50
     51                if ( 'native' !== t.media.pluginType ) {
     52                        t.$media.remove();
     53                }
     54
     55                delete window.mejs.players[t.id];
     56
     57                t.container.remove();
     58                t.globalUnbind();
     59                delete t.node.player;
     60        },
     61
     62        /**
     63         * Allows any class that has set 'player' to a MediaElementPlayer
     64         *  instance to remove the player when listening to events.
     65         *
     66         *  Examples: modal closes, shortcode properties are removed, etc.
     67         */
     68        unsetPlayers : function() {
     69                if ( this.players && this.players.length ) {
     70                        _.each( this.players, function (player) {
     71                                player.pause();
     72                                wp.media.mixin.removePlayer( player );
     73                        } );
     74                        this.players = [];
     75                }
     76        }
     77};
     78
     79/**
     80 * Autowire "collection"-type shortcodes
     81 */
     82wp.media.playlist = new wp.media.collection({
     83        tag: 'playlist',
     84        editTitle : l10n.editPlaylistTitle,
     85        defaults : {
     86                id: wp.media.view.settings.post.id,
     87                style: 'light',
     88                tracklist: true,
     89                tracknumbers: true,
     90                images: true,
     91                artists: true,
     92                type: 'audio'
     93        }
     94});
     95
     96/**
     97 * Shortcode modeling for audio
     98 *  `edit()` prepares the shortcode for the media modal
     99 *  `shortcode()` builds the new shortcode after update
     100 *
     101 * @namespace
     102 */
     103wp.media.audio = {
     104        coerce : wp.media.coerce,
     105
     106        defaults : {
     107                id : wp.media.view.settings.post.id,
     108                src : '',
     109                loop : false,
     110                autoplay : false,
     111                preload : 'none',
     112                width : 400
     113        },
     114
     115        edit : function( data ) {
     116                var frame, shortcode = wp.shortcode.next( 'audio', data ).shortcode;
     117
     118                frame = wp.media({
     119                        frame: 'audio',
     120                        state: 'audio-details',
     121                        metadata: _.defaults( shortcode.attrs.named, this.defaults )
     122                });
     123
     124                return frame;
     125        },
     126
     127        shortcode : function( model ) {
     128                var content;
     129
     130                _.each( this.defaults, function( value, key ) {
     131                        model[ key ] = this.coerce( model, key );
     132
     133                        if ( value === model[ key ] ) {
     134                                delete model[ key ];
     135                        }
     136                }, this );
     137
     138                content = model.content;
     139                delete model.content;
     140
     141                return new wp.shortcode({
     142                        tag: 'audio',
     143                        attrs: model,
     144                        content: content
     145                });
     146        }
     147};
     148
     149/**
     150 * Shortcode modeling for video
     151 *  `edit()` prepares the shortcode for the media modal
     152 *  `shortcode()` builds the new shortcode after update
     153 *
     154 * @namespace
     155 */
     156wp.media.video = {
     157        coerce : wp.media.coerce,
     158
     159        defaults : {
     160                id : wp.media.view.settings.post.id,
     161                src : '',
     162                poster : '',
     163                loop : false,
     164                autoplay : false,
     165                preload : 'metadata',
     166                content : '',
     167                width : 640,
     168                height : 360
     169        },
     170
     171        edit : function( data ) {
     172                var frame,
     173                        shortcode = wp.shortcode.next( 'video', data ).shortcode,
     174                        attrs;
     175
     176                attrs = shortcode.attrs.named;
     177                attrs.content = shortcode.content;
     178
     179                frame = wp.media({
     180                        frame: 'video',
     181                        state: 'video-details',
     182                        metadata: _.defaults( attrs, this.defaults )
     183                });
     184
     185                return frame;
     186        },
     187
     188        shortcode : function( model ) {
     189                var content;
     190
     191                _.each( this.defaults, function( value, key ) {
     192                        model[ key ] = this.coerce( model, key );
     193
     194                        if ( value === model[ key ] ) {
     195                                delete model[ key ];
     196                        }
     197                }, this );
     198
     199                content = model.content;
     200                delete model.content;
     201
     202                return new wp.shortcode({
     203                        tag: 'video',
     204                        attrs: model,
     205                        content: content
     206                });
     207        }
     208};
     209
     210media.model.PostMedia = require( './models/post-media.js' );
     211media.controller.AudioDetails = require( './controllers/audio-details.js' );
     212media.controller.VideoDetails = require( './controllers/video-details.js' );
     213media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' );
     214media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' );
     215media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' );
     216media.view.MediaDetails = require( './views/media-details.js' );
     217media.view.AudioDetails = require( './views/audio-details.js' );
     218media.view.VideoDetails = require( './views/video-details.js' );
     219
     220},{"./controllers/audio-details.js":2,"./controllers/video-details.js":3,"./models/post-media.js":4,"./views/audio-details.js":5,"./views/frame/audio-details.js":6,"./views/frame/media-details.js":7,"./views/frame/video-details.js":8,"./views/media-details.js":9,"./views/video-details.js":10}],2:[function(require,module,exports){
     221/*globals wp */
     222
     223/**
     224 * wp.media.controller.AudioDetails
     225 *
     226 * The controller for the Audio Details state
     227 *
     228 * @class
     229 * @augments wp.media.controller.State
     230 * @augments Backbone.Model
     231 */
     232var State = wp.media.controller.State,
     233        l10n = wp.media.view.l10n,
     234        AudioDetails;
     235
     236AudioDetails = State.extend({
     237        defaults: {
     238                id: 'audio-details',
     239                toolbar: 'audio-details',
     240                title: l10n.audioDetailsTitle,
     241                content: 'audio-details',
     242                menu: 'audio-details',
     243                router: false,
     244                priority: 60
     245        },
     246
     247        initialize: function( options ) {
     248                this.media = options.media;
     249                State.prototype.initialize.apply( this, arguments );
     250        }
     251});
     252
     253module.exports = AudioDetails;
     254
     255},{}],3:[function(require,module,exports){
     256/*globals wp */
     257
     258/**
     259 * wp.media.controller.VideoDetails
     260 *
     261 * The controller for the Video Details state
     262 *
     263 * @class
     264 * @augments wp.media.controller.State
     265 * @augments Backbone.Model
     266 */
     267var State = wp.media.controller.State,
     268        l10n = wp.media.view.l10n,
     269        VideoDetails;
     270
     271VideoDetails = State.extend({
     272        defaults: {
     273                id: 'video-details',
     274                toolbar: 'video-details',
     275                title: l10n.videoDetailsTitle,
     276                content: 'video-details',
     277                menu: 'video-details',
     278                router: false,
     279                priority: 60
     280        },
     281
     282        initialize: function( options ) {
     283                this.media = options.media;
     284                State.prototype.initialize.apply( this, arguments );
     285        }
     286});
     287
     288module.exports = VideoDetails;
     289
     290},{}],4:[function(require,module,exports){
     291/*globals wp, Backbone, _ */
     292
     293/**
     294 * wp.media.model.PostMedia
     295 *
     296 * Shared model class for audio and video. Updates the model after
     297 *   "Add Audio|Video Source" and "Replace Audio|Video" states return
     298 *
     299 * @class
     300 * @augments Backbone.Model
     301 */
     302var PostMedia = Backbone.Model.extend({
     303        initialize: function() {
     304                this.attachment = false;
     305        },
     306
     307        setSource: function( attachment ) {
     308                this.attachment = attachment;
     309                this.extension = attachment.get( 'filename' ).split('.').pop();
     310
     311                if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
     312                        this.unset( 'src' );
     313                }
     314
     315                if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
     316                        this.set( this.extension, this.attachment.get( 'url' ) );
     317                } else {
     318                        this.unset( this.extension );
     319                }
     320        },
     321
     322        changeAttachment: function( attachment ) {
     323                this.setSource( attachment );
     324
     325                this.unset( 'src' );
     326                _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
     327                        this.unset( ext );
     328                }, this );
     329        }
     330});
     331
     332module.exports = PostMedia;
     333
     334},{}],5:[function(require,module,exports){
     335/*globals wp */
     336
     337/**
     338 * wp.media.view.AudioDetails
     339 *
     340 * @class
     341 * @augments wp.media.view.MediaDetails
     342 * @augments wp.media.view.Settings.AttachmentDisplay
     343 * @augments wp.media.view.Settings
     344 * @augments wp.media.View
     345 * @augments wp.Backbone.View
     346 * @augments Backbone.View
     347 */
     348var MediaDetails = wp.media.view.MediaDetails,
     349        AudioDetails;
     350
     351AudioDetails = MediaDetails.extend({
     352        className: 'audio-details',
     353        template:  wp.template('audio-details'),
     354
     355        setMedia: function() {
     356                var audio = this.$('.wp-audio-shortcode');
     357
     358                if ( audio.find( 'source' ).length ) {
     359                        if ( audio.is(':hidden') ) {
     360                                audio.show();
     361                        }
     362                        this.media = MediaDetails.prepareSrc( audio.get(0) );
     363                } else {
     364                        audio.hide();
     365                        this.media = false;
     366                }
     367
     368                return this;
     369        }
     370});
     371
     372module.exports = AudioDetails;
     373
     374},{}],6:[function(require,module,exports){
     375/*globals wp */
     376
     377/**
     378 * wp.media.view.MediaFrame.AudioDetails
     379 *
     380 * @class
     381 * @augments wp.media.view.MediaFrame.MediaDetails
     382 * @augments wp.media.view.MediaFrame.Select
     383 * @augments wp.media.view.MediaFrame
     384 * @augments wp.media.view.Frame
     385 * @augments wp.media.View
     386 * @augments wp.Backbone.View
     387 * @augments Backbone.View
     388 * @mixes wp.media.controller.StateMachine
     389 */
     390var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
     391        MediaLibrary = wp.media.controller.MediaLibrary,
     392
     393        l10n = wp.media.view.l10n,
     394        AudioDetails;
     395
     396AudioDetails = MediaDetails.extend({
     397        defaults: {
     398                id:      'audio',
     399                url:     '',
     400                menu:    'audio-details',
     401                content: 'audio-details',
     402                toolbar: 'audio-details',
     403                type:    'link',
     404                title:    l10n.audioDetailsTitle,
     405                priority: 120
     406        },
     407
     408        initialize: function( options ) {
     409                options.DetailsView = wp.media.view.AudioDetails;
     410                options.cancelText = l10n.audioDetailsCancel;
     411                options.addText = l10n.audioAddSourceTitle;
     412
     413                MediaDetails.prototype.initialize.call( this, options );
     414        },
     415
     416        bindHandlers: function() {
     417                MediaDetails.prototype.bindHandlers.apply( this, arguments );
     418
     419                this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
     420                this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
     421        },
     422
     423        createStates: function() {
     424                this.states.add([
     425                        new wp.media.controller.AudioDetails( {
     426                                media: this.media
     427                        } ),
     428
     429                        new MediaLibrary( {
     430                                type: 'audio',
     431                                id: 'replace-audio',
     432                                title: l10n.audioReplaceTitle,
     433                                toolbar: 'replace-audio',
     434                                media: this.media,
     435                                menu: 'audio-details'
     436                        } ),
     437
     438                        new MediaLibrary( {
     439                                type: 'audio',
     440                                id: 'add-audio-source',
     441                                title: l10n.audioAddSourceTitle,
     442                                toolbar: 'add-audio-source',
     443                                media: this.media,
     444                                menu: false
     445                        } )
     446                ]);
     447        }
     448});
     449
     450module.exports = AudioDetails;
     451
     452},{}],7:[function(require,module,exports){
     453/*globals wp */
     454
     455/**
     456 * wp.media.view.MediaFrame.MediaDetails
     457 *
     458 * @class
     459 * @augments wp.media.view.MediaFrame.Select
     460 * @augments wp.media.view.MediaFrame
     461 * @augments wp.media.view.Frame
     462 * @augments wp.media.View
     463 * @augments wp.Backbone.View
     464 * @augments Backbone.View
     465 * @mixes wp.media.controller.StateMachine
     466 */
     467var Select = wp.media.view.MediaFrame.Select,
     468        l10n = wp.media.view.l10n,
     469        MediaDetails;
     470
     471MediaDetails = Select.extend({
     472        defaults: {
     473                id:      'media',
     474                url:     '',
     475                menu:    'media-details',
     476                content: 'media-details',
     477                toolbar: 'media-details',
     478                type:    'link',
     479                priority: 120
     480        },
     481
     482        initialize: function( options ) {
     483                this.DetailsView = options.DetailsView;
     484                this.cancelText = options.cancelText;
     485                this.addText = options.addText;
     486
     487                this.media = new wp.media.model.PostMedia( options.metadata );
     488                this.options.selection = new wp.media.model.Selection( this.media.attachment, { multiple: false } );
     489                Select.prototype.initialize.apply( this, arguments );
     490        },
     491
     492        bindHandlers: function() {
     493                var menu = this.defaults.menu;
     494
     495                Select.prototype.bindHandlers.apply( this, arguments );
     496
     497                this.on( 'menu:create:' + menu, this.createMenu, this );
     498                this.on( 'content:render:' + menu, this.renderDetailsContent, this );
     499                this.on( 'menu:render:' + menu, this.renderMenu, this );
     500                this.on( 'toolbar:render:' + menu, this.renderDetailsToolbar, this );
     501        },
     502
     503        renderDetailsContent: function() {
     504                var view = new this.DetailsView({
     505                        controller: this,
     506                        model: this.state().media,
     507                        attachment: this.state().media.attachment
     508                }).render();
     509
     510                this.content.set( view );
     511        },
     512
     513        renderMenu: function( view ) {
     514                var lastState = this.lastState(),
     515                        previous = lastState && lastState.id,
     516                        frame = this;
     517
     518                view.set({
     519                        cancel: {
     520                                text:     this.cancelText,
     521                                priority: 20,
     522                                click:    function() {
     523                                        if ( previous ) {
     524                                                frame.setState( previous );
     525                                        } else {
     526                                                frame.close();
     527                                        }
     528                                }
     529                        },
     530                        separateCancel: new wp.media.View({
     531                                className: 'separator',
     532                                priority: 40
     533                        })
     534                });
     535
     536        },
     537
     538        setPrimaryButton: function(text, handler) {
     539                this.toolbar.set( new wp.media.view.Toolbar({
     540                        controller: this,
     541                        items: {
     542                                button: {
     543                                        style:    'primary',
     544                                        text:     text,
     545                                        priority: 80,
     546                                        click:    function() {
     547                                                var controller = this.controller;
     548                                                handler.call( this, controller, controller.state() );
     549                                                // Restore and reset the default state.
     550                                                controller.setState( controller.options.state );
     551                                                controller.reset();
     552                                        }
     553                                }
     554                        }
     555                }) );
     556        },
     557
     558        renderDetailsToolbar: function() {
     559                this.setPrimaryButton( l10n.update, function( controller, state ) {
     560                        controller.close();
     561                        state.trigger( 'update', controller.media.toJSON() );
     562                } );
     563        },
     564
     565        renderReplaceToolbar: function() {
     566                this.setPrimaryButton( l10n.replace, function( controller, state ) {
     567                        var attachment = state.get( 'selection' ).single();
     568                        controller.media.changeAttachment( attachment );
     569                        state.trigger( 'replace', controller.media.toJSON() );
     570                } );
     571        },
     572
     573        renderAddSourceToolbar: function() {
     574                this.setPrimaryButton( this.addText, function( controller, state ) {
     575                        var attachment = state.get( 'selection' ).single();
     576                        controller.media.setSource( attachment );
     577                        state.trigger( 'add-source', controller.media.toJSON() );
     578                } );
     579        }
     580});
     581
     582module.exports = MediaDetails;
     583
     584},{}],8:[function(require,module,exports){
     585/*globals wp, _ */
     586
     587/**
     588 * wp.media.view.MediaFrame.VideoDetails
     589 *
     590 * @class
     591 * @augments wp.media.view.MediaFrame.MediaDetails
     592 * @augments wp.media.view.MediaFrame.Select
     593 * @augments wp.media.view.MediaFrame
     594 * @augments wp.media.view.Frame
     595 * @augments wp.media.View
     596 * @augments wp.Backbone.View
     597 * @augments Backbone.View
     598 * @mixes wp.media.controller.StateMachine
     599 */
     600var MediaDetails = wp.media.view.MediaFrame.MediaDetails,
     601        MediaLibrary = wp.media.controller.MediaLibrary,
     602        l10n = wp.media.view.l10n,
     603        VideoDetails;
     604
     605VideoDetails = MediaDetails.extend({
     606        defaults: {
     607                id:      'video',
     608                url:     '',
     609                menu:    'video-details',
     610                content: 'video-details',
     611                toolbar: 'video-details',
     612                type:    'link',
     613                title:    l10n.videoDetailsTitle,
     614                priority: 120
     615        },
     616
     617        initialize: function( options ) {
     618                options.DetailsView = wp.media.view.VideoDetails;
     619                options.cancelText = l10n.videoDetailsCancel;
     620                options.addText = l10n.videoAddSourceTitle;
     621
     622                MediaDetails.prototype.initialize.call( this, options );
     623        },
     624
     625        bindHandlers: function() {
     626                MediaDetails.prototype.bindHandlers.apply( this, arguments );
     627
     628                this.on( 'toolbar:render:replace-video', this.renderReplaceToolbar, this );
     629                this.on( 'toolbar:render:add-video-source', this.renderAddSourceToolbar, this );
     630                this.on( 'toolbar:render:select-poster-image', this.renderSelectPosterImageToolbar, this );
     631                this.on( 'toolbar:render:add-track', this.renderAddTrackToolbar, this );
     632        },
     633
     634        createStates: function() {
     635                this.states.add([
     636                        new wp.media.controller.VideoDetails({
     637                                media: this.media
     638                        }),
     639
     640                        new MediaLibrary( {
     641                                type: 'video',
     642                                id: 'replace-video',
     643                                title: l10n.videoReplaceTitle,
     644                                toolbar: 'replace-video',
     645                                media: this.media,
     646                                menu: 'video-details'
     647                        } ),
     648
     649                        new MediaLibrary( {
     650                                type: 'video',
     651                                id: 'add-video-source',
     652                                title: l10n.videoAddSourceTitle,
     653                                toolbar: 'add-video-source',
     654                                media: this.media,
     655                                menu: false
     656                        } ),
     657
     658                        new MediaLibrary( {
     659                                type: 'image',
     660                                id: 'select-poster-image',
     661                                title: l10n.videoSelectPosterImageTitle,
     662                                toolbar: 'select-poster-image',
     663                                media: this.media,
     664                                menu: 'video-details'
     665                        } ),
     666
     667                        new MediaLibrary( {
     668                                type: 'text',
     669                                id: 'add-track',
     670                                title: l10n.videoAddTrackTitle,
     671                                toolbar: 'add-track',
     672                                media: this.media,
     673                                menu: 'video-details'
     674                        } )
     675                ]);
     676        },
     677
     678        renderSelectPosterImageToolbar: function() {
     679                this.setPrimaryButton( l10n.videoSelectPosterImageTitle, function( controller, state ) {
     680                        var urls = [], attachment = state.get( 'selection' ).single();
     681
     682                        controller.media.set( 'poster', attachment.get( 'url' ) );
     683                        state.trigger( 'set-poster-image', controller.media.toJSON() );
     684
     685                        _.each( wp.media.view.settings.embedExts, function (ext) {
     686                                if ( controller.media.get( ext ) ) {
     687                                        urls.push( controller.media.get( ext ) );
     688                                }
     689                        } );
     690
     691                        wp.ajax.send( 'set-attachment-thumbnail', {
     692                                data : {
     693                                        urls: urls,
     694                                        thumbnail_id: attachment.get( 'id' )
     695                                }
     696                        } );
     697                } );
     698        },
     699
     700        renderAddTrackToolbar: function() {
     701                this.setPrimaryButton( l10n.videoAddTrackTitle, function( controller, state ) {
     702                        var attachment = state.get( 'selection' ).single(),
     703                                content = controller.media.get( 'content' );
     704
     705                        if ( -1 === content.indexOf( attachment.get( 'url' ) ) ) {
     706                                content += [
     707                                        '<track srclang="en" label="English"kind="subtitles" src="',
     708                                        attachment.get( 'url' ),
     709                                        '" />'
     710                                ].join('');
     711
     712                                controller.media.set( 'content', content );
     713                        }
     714                        state.trigger( 'add-track', controller.media.toJSON() );
     715                } );
     716        }
     717});
     718
     719module.exports = VideoDetails;
     720
     721},{}],9:[function(require,module,exports){
     722/*global wp, jQuery, _, MediaElementPlayer */
     723
     724/**
     725 * wp.media.view.MediaDetails
     726 *
     727 * @class
     728 * @augments wp.media.view.Settings.AttachmentDisplay
     729 * @augments wp.media.view.Settings
     730 * @augments wp.media.View
     731 * @augments wp.Backbone.View
     732 * @augments Backbone.View
     733 */
     734var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     735        $ = jQuery,
     736        MediaDetails;
     737
     738MediaDetails = AttachmentDisplay.extend({
     739        initialize: function() {
     740                _.bindAll(this, 'success');
     741                this.players = [];
     742                this.listenTo( this.controller, 'close', wp.media.mixin.unsetPlayers );
     743                this.on( 'ready', this.setPlayer );
     744                this.on( 'media:setting:remove', wp.media.mixin.unsetPlayers, this );
     745                this.on( 'media:setting:remove', this.render );
     746                this.on( 'media:setting:remove', this.setPlayer );
     747                this.events = _.extend( this.events, {
     748                        'click .remove-setting' : 'removeSetting',
     749                        'change .content-track' : 'setTracks',
     750                        'click .remove-track' : 'setTracks',
     751                        'click .add-media-source' : 'addSource'
     752                } );
     753
     754                AttachmentDisplay.prototype.initialize.apply( this, arguments );
     755        },
     756
     757        prepare: function() {
     758                return _.defaults({
     759                        model: this.model.toJSON()
     760                }, this.options );
     761        },
     762
     763        /**
     764         * Remove a setting's UI when the model unsets it
     765         *
     766         * @fires wp.media.view.MediaDetails#media:setting:remove
     767         *
     768         * @param {Event} e
     769         */
     770        removeSetting : function(e) {
     771                var wrap = $( e.currentTarget ).parent(), setting;
     772                setting = wrap.find( 'input' ).data( 'setting' );
     773
     774                if ( setting ) {
     775                        this.model.unset( setting );
     776                        this.trigger( 'media:setting:remove', this );
     777                }
     778
     779                wrap.remove();
     780        },
     781
     782        /**
     783         *
     784         * @fires wp.media.view.MediaDetails#media:setting:remove
     785         */
     786        setTracks : function() {
     787                var tracks = '';
     788
     789                _.each( this.$('.content-track'), function(track) {
     790                        tracks += $( track ).val();
     791                } );
     792
     793                this.model.set( 'content', tracks );
     794                this.trigger( 'media:setting:remove', this );
     795        },
     796
     797        addSource : function( e ) {
     798                this.controller.lastMime = $( e.currentTarget ).data( 'mime' );
     799                this.controller.setState( 'add-' + this.controller.defaults.id + '-source' );
     800        },
     801
     802        loadPlayer: function () {
     803                this.players.push( new MediaElementPlayer( this.media, this.settings ) );
     804                this.scriptXhr = false;
     805        },
     806
     807        /**
     808         * @global MediaElementPlayer
     809         */
     810        setPlayer : function() {
     811                var baseSettings;
     812
     813                if ( this.players.length || ! this.media || this.scriptXhr ) {
     814                        return;
     815                }
     816
     817                if ( this.model.get( 'src' ).indexOf( 'vimeo' ) > -1 && ! ( 'Froogaloop' in window ) ) {
     818                        baseSettings = wp.media.mixin.mejsSettings;
     819                        this.scriptXhr = $.getScript( baseSettings.pluginPath + 'froogaloop.min.js', _.bind( this.loadPlayer, this ) );
     820                } else {
     821                        this.loadPlayer();
     822                }
     823        },
     824
     825        /**
     826         * @abstract
     827         */
     828        setMedia : function() {
     829                return this;
     830        },
     831
     832        success : function(mejs) {
     833                var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay;
     834
     835                if ( 'flash' === mejs.pluginType && autoplay ) {
     836                        mejs.addEventListener( 'canplay', function() {
     837                                mejs.play();
     838                        }, false );
     839                }
     840
     841                this.mejs = mejs;
     842        },
     843
     844        /**
     845         * @returns {media.view.MediaDetails} Returns itself to allow chaining
     846         */
     847        render: function() {
     848                AttachmentDisplay.prototype.render.apply( this, arguments );
     849
     850                setTimeout( _.bind( function() {
     851                        this.resetFocus();
     852                }, this ), 10 );
     853
     854                this.settings = _.defaults( {
     855                        success : this.success
     856                }, wp.media.mixin.mejsSettings );
     857
     858                return this.setMedia();
     859        },
     860
     861        resetFocus: function() {
     862                this.$( '.embed-media-settings' ).scrollTop( 0 );
     863        }
     864}, {
     865        instances : 0,
     866        /**
     867         * When multiple players in the DOM contain the same src, things get weird.
     868         *
     869         * @param {HTMLElement} elem
     870         * @returns {HTMLElement}
     871         */
     872        prepareSrc : function( elem ) {
     873                var i = MediaDetails.instances++;
     874                _.each( $( elem ).find( 'source' ), function( source ) {
     875                        source.src = [
     876                                source.src,
     877                                source.src.indexOf('?') > -1 ? '&' : '?',
     878                                '_=',
     879                                i
     880                        ].join('');
     881                } );
     882
     883                return elem;
     884        }
     885});
     886
     887module.exports = MediaDetails;
     888
     889},{}],10:[function(require,module,exports){
     890/*globals wp */
     891
     892/**
     893 * wp.media.view.VideoDetails
     894 *
     895 * @class
     896 * @augments wp.media.view.MediaDetails
     897 * @augments wp.media.view.Settings.AttachmentDisplay
     898 * @augments wp.media.view.Settings
     899 * @augments wp.media.View
     900 * @augments wp.Backbone.View
     901 * @augments Backbone.View
     902 */
     903var MediaDetails = wp.media.view.MediaDetails,
     904        VideoDetails;
     905
     906VideoDetails = MediaDetails.extend({
     907        className: 'video-details',
     908        template:  wp.template('video-details'),
     909
     910        setMedia: function() {
     911                var video = this.$('.wp-video-shortcode');
     912
     913                if ( video.find( 'source' ).length ) {
     914                        if ( video.is(':hidden') ) {
     915                                video.show();
     916                        }
     917
     918                        if ( ! video.hasClass( 'youtube-video' ) && ! video.hasClass( 'vimeo-video' ) ) {
     919                                this.media = MediaDetails.prepareSrc( video.get(0) );
     920                        } else {
     921                                this.media = video.get(0);
     922                        }
     923                } else {
     924                        video.hide();
     925                        this.media = false;
     926                }
     927
     928                return this;
     929        }
     930});
     931
     932module.exports = VideoDetails;
     933
     934},{}]},{},[1]);
  • src/wp-includes/js/media-grid.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/*globals wp */
     3
     4/**
     5 * wp.media.controller.EditAttachmentMetadata
     6 *
     7 * A state for editing an attachment's metadata.
     8 *
     9 * @class
     10 * @augments wp.media.controller.State
     11 * @augments Backbone.Model
     12 */
     13var l10n = wp.media.view.l10n,
     14        EditAttachmentMetadata;
     15
     16EditAttachmentMetadata = wp.media.controller.State.extend({
     17        defaults: {
     18                id:      'edit-attachment',
     19                // Title string passed to the frame's title region view.
     20                title:   l10n.attachmentDetails,
     21                // Region mode defaults.
     22                content: 'edit-metadata',
     23                menu:    false,
     24                toolbar: false,
     25                router:  false
     26        }
     27});
     28
     29module.exports = EditAttachmentMetadata;
     30
     31},{}],2:[function(require,module,exports){
     32/*globals wp */
     33
     34var media = wp.media;
     35
     36media.controller.EditAttachmentMetadata = require( './controllers/edit-attachment-metadata.js' );
     37media.view.MediaFrame.Manage = require( './views/frame/manage.js' );
     38media.view.Attachment.Details.TwoColumn = require( './views/attachment/details-two-column.js' );
     39media.view.MediaFrame.Manage.Router = require( './routers/manage.js' );
     40media.view.EditImage.Details = require( './views/edit-image-details.js' );
     41media.view.MediaFrame.EditAttachments = require( './views/frame/edit-attachments.js' );
     42media.view.SelectModeToggleButton = require( './views/button/select-mode-toggle.js' );
     43media.view.DeleteSelectedButton = require( './views/button/delete-selected.js' );
     44media.view.DeleteSelectedPermanentlyButton = require( './views/button/delete-selected-permanently.js' );
     45
     46},{"./controllers/edit-attachment-metadata.js":1,"./routers/manage.js":3,"./views/attachment/details-two-column.js":4,"./views/button/delete-selected-permanently.js":5,"./views/button/delete-selected.js":6,"./views/button/select-mode-toggle.js":7,"./views/edit-image-details.js":8,"./views/frame/edit-attachments.js":9,"./views/frame/manage.js":10}],3:[function(require,module,exports){
     47/*globals wp, Backbone */
     48
     49/**
     50 * wp.media.view.MediaFrame.Manage.Router
     51 *
     52 * A router for handling the browser history and application state.
     53 *
     54 * @class
     55 * @augments Backbone.Router
     56 */
     57var Router = Backbone.Router.extend({
     58        routes: {
     59                'upload.php?item=:slug':    'showItem',
     60                'upload.php?search=:query': 'search'
     61        },
     62
     63        // Map routes against the page URL
     64        baseUrl: function( url ) {
     65                return 'upload.php' + url;
     66        },
     67
     68        // Respond to the search route by filling the search field and trigggering the input event
     69        search: function( query ) {
     70                jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
     71        },
     72
     73        // Show the modal with a specific item
     74        showItem: function( query ) {
     75                var media = wp.media,
     76                        library = media.frame.state().get('library'),
     77                        item;
     78
     79                // Trigger the media frame to open the correct item
     80                item = library.findWhere( { id: parseInt( query, 10 ) } );
     81                if ( item ) {
     82                        media.frame.trigger( 'edit:attachment', item );
     83                } else {
     84                        item = media.attachment( query );
     85                        media.frame.listenTo( item, 'change', function( model ) {
     86                                media.frame.stopListening( item );
     87                                media.frame.trigger( 'edit:attachment', model );
     88                        } );
     89                        item.fetch();
     90                }
     91        }
     92});
     93
     94module.exports = Router;
     95
     96},{}],4:[function(require,module,exports){
     97/*globals wp */
     98
     99/**
     100 * wp.media.view.Attachment.Details.TwoColumn
     101 *
     102 * A similar view to media.view.Attachment.Details
     103 * for use in the Edit Attachment modal.
     104 *
     105 * @class
     106 * @augments wp.media.view.Attachment.Details
     107 * @augments wp.media.view.Attachment
     108 * @augments wp.media.View
     109 * @augments wp.Backbone.View
     110 * @augments Backbone.View
     111 */
     112var Details = wp.media.view.Attachment.Details,
     113        TwoColumn;
     114
     115TwoColumn = Details.extend({
     116        template: wp.template( 'attachment-details-two-column' ),
     117
     118        editAttachment: function( event ) {
     119                event.preventDefault();
     120                this.controller.content.mode( 'edit-image' );
     121        },
     122
     123        /**
     124         * Noop this from parent class, doesn't apply here.
     125         */
     126        toggleSelectionHandler: function() {},
     127
     128        render: function() {
     129                Details.prototype.render.apply( this, arguments );
     130
     131                wp.media.mixin.removeAllPlayers();
     132                this.$( 'audio, video' ).each( function (i, elem) {
     133                        var el = wp.media.view.MediaDetails.prepareSrc( elem );
     134                        new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
     135                } );
     136        }
     137});
     138
     139module.exports = TwoColumn;
     140
     141},{}],5:[function(require,module,exports){
     142/*globals wp */
     143
     144/**
     145 * wp.media.view.DeleteSelectedPermanentlyButton
     146 *
     147 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
     148 *
     149 * @class
     150 * @augments wp.media.view.DeleteSelectedButton
     151 * @augments wp.media.view.Button
     152 * @augments wp.media.View
     153 * @augments wp.Backbone.View
     154 * @augments Backbone.View
     155 */
     156var Button = wp.media.view.Button,
     157        DeleteSelected = wp.media.view.DeleteSelectedButton,
     158        DeleteSelectedPermanently;
     159
     160DeleteSelectedPermanently = DeleteSelected.extend({
     161        initialize: function() {
     162                DeleteSelected.prototype.initialize.apply( this, arguments );
     163                this.listenTo( this.controller, 'select:activate', this.selectActivate );
     164                this.listenTo( this.controller, 'select:deactivate', this.selectDeactivate );
     165        },
     166
     167        filterChange: function( model ) {
     168                this.canShow = ( 'trash' === model.get( 'status' ) );
     169        },
     170
     171        selectActivate: function() {
     172                this.toggleDisabled();
     173                this.$el.toggleClass( 'hidden', ! this.canShow );
     174        },
     175
     176        selectDeactivate: function() {
     177                this.toggleDisabled();
     178                this.$el.addClass( 'hidden' );
     179        },
     180
     181        render: function() {
     182                Button.prototype.render.apply( this, arguments );
     183                this.selectActivate();
     184                return this;
     185        }
     186});
     187
     188module.exports = DeleteSelectedPermanently;
     189
     190},{}],6:[function(require,module,exports){
     191/*globals wp */
     192
     193/**
     194 * wp.media.view.DeleteSelectedButton
     195 *
     196 * A button that handles bulk Delete/Trash logic
     197 *
     198 * @class
     199 * @augments wp.media.view.Button
     200 * @augments wp.media.View
     201 * @augments wp.Backbone.View
     202 * @augments Backbone.View
     203 */
     204var Button = wp.media.view.Button,
     205        l10n = wp.media.view.l10n,
     206        DeleteSelected;
     207
     208DeleteSelected = Button.extend({
     209        initialize: function() {
     210                Button.prototype.initialize.apply( this, arguments );
     211                if ( this.options.filters ) {
     212                        this.listenTo( this.options.filters.model, 'change', this.filterChange );
     213                }
     214                this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled );
     215        },
     216
     217        filterChange: function( model ) {
     218                if ( 'trash' === model.get( 'status' ) ) {
     219                        this.model.set( 'text', l10n.untrashSelected );
     220                } else if ( wp.media.view.settings.mediaTrash ) {
     221                        this.model.set( 'text', l10n.trashSelected );
     222                } else {
     223                        this.model.set( 'text', l10n.deleteSelected );
     224                }
     225        },
     226
     227        toggleDisabled: function() {
     228                this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
     229        },
     230
     231        render: function() {
     232                Button.prototype.render.apply( this, arguments );
     233                if ( this.controller.isModeActive( 'select' ) ) {
     234                        this.$el.addClass( 'delete-selected-button' );
     235                } else {
     236                        this.$el.addClass( 'delete-selected-button hidden' );
     237                }
     238                this.toggleDisabled();
     239                return this;
     240        }
     241});
     242
     243module.exports = DeleteSelected;
     244
     245},{}],7:[function(require,module,exports){
     246/*globals wp */
     247
     248/**
     249 * wp.media.view.SelectModeToggleButton
     250 *
     251 * @class
     252 * @augments wp.media.view.Button
     253 * @augments wp.media.View
     254 * @augments wp.Backbone.View
     255 * @augments Backbone.View
     256 */
     257var Button = wp.media.view.Button,
     258        l10n = wp.media.view.l10n,
     259        SelectModeToggle;
     260
     261SelectModeToggle = Button.extend({
     262        initialize: function() {
     263                Button.prototype.initialize.apply( this, arguments );
     264                this.listenTo( this.controller, 'select:activate select:deactivate', this.toggleBulkEditHandler );
     265                this.listenTo( this.controller, 'selection:action:done', this.back );
     266        },
     267
     268        back: function () {
     269                this.controller.deactivateMode( 'select' ).activateMode( 'edit' );
     270        },
     271
     272        click: function() {
     273                Button.prototype.click.apply( this, arguments );
     274                if ( this.controller.isModeActive( 'select' ) ) {
     275                        this.back();
     276                } else {
     277                        this.controller.deactivateMode( 'edit' ).activateMode( 'select' );
     278                }
     279        },
     280
     281        render: function() {
     282                Button.prototype.render.apply( this, arguments );
     283                this.$el.addClass( 'select-mode-toggle-button' );
     284                return this;
     285        },
     286
     287        toggleBulkEditHandler: function() {
     288                var toolbar = this.controller.content.get().toolbar, children;
     289
     290                children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' );
     291
     292                // TODO: the Frame should be doing all of this.
     293                if ( this.controller.isModeActive( 'select' ) ) {
     294                        this.model.set( 'text', l10n.cancelSelection );
     295                        children.not( '.media-button' ).hide();
     296                        this.$el.show();
     297                        toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' );
     298                } else {
     299                        this.model.set( 'text', l10n.bulkSelect );
     300                        this.controller.content.get().$el.removeClass( 'fixed' );
     301                        toolbar.$el.css( 'width', '' );
     302                        toolbar.$( '.delete-selected-button' ).addClass( 'hidden' );
     303                        children.not( '.spinner, .media-button' ).show();
     304                        this.controller.state().get( 'selection' ).reset();
     305                }
     306        }
     307});
     308
     309module.exports = SelectModeToggle;
     310
     311},{}],8:[function(require,module,exports){
     312/*globals wp, _ */
     313
     314/**
     315 * wp.media.view.EditImage.Details
     316 *
     317 * @class
     318 * @augments wp.media.view.EditImage
     319 * @augments wp.media.View
     320 * @augments wp.Backbone.View
     321 * @augments Backbone.View
     322 */
     323var View = wp.media.View,
     324        EditImage = wp.media.view.EditImage,
     325        Details;
     326
     327Details = EditImage.extend({
     328        initialize: function( options ) {
     329                this.editor = window.imageEdit;
     330                this.frame = options.frame;
     331                this.controller = options.controller;
     332                View.prototype.initialize.apply( this, arguments );
     333        },
     334
     335        back: function() {
     336                this.frame.content.mode( 'edit-metadata' );
     337        },
     338
     339        save: function() {
     340                this.model.fetch().done( _.bind( function() {
     341                        this.frame.content.mode( 'edit-metadata' );
     342                }, this ) );
     343        }
     344});
     345
     346module.exports = Details;
     347
     348},{}],9:[function(require,module,exports){
     349/*globals wp, _, jQuery */
     350
     351/**
     352 * wp.media.view.MediaFrame.EditAttachments
     353 *
     354 * A frame for editing the details of a specific media item.
     355 *
     356 * Opens in a modal by default.
     357 *
     358 * Requires an attachment model to be passed in the options hash under `model`.
     359 *
     360 * @class
     361 * @augments wp.media.view.Frame
     362 * @augments wp.media.View
     363 * @augments wp.Backbone.View
     364 * @augments Backbone.View
     365 * @mixes wp.media.controller.StateMachine
     366 */
     367var Frame = wp.media.view.Frame,
     368        MediaFrame = wp.media.view.MediaFrame,
     369
     370        $ = jQuery,
     371        EditAttachments;
     372
     373EditAttachments = MediaFrame.extend({
     374
     375        className: 'edit-attachment-frame',
     376        template:  wp.template( 'edit-attachment-frame' ),
     377        regions:   [ 'title', 'content' ],
     378
     379        events: {
     380                'click .left':  'previousMediaItem',
     381                'click .right': 'nextMediaItem'
     382        },
     383
     384        initialize: function() {
     385                Frame.prototype.initialize.apply( this, arguments );
     386
     387                _.defaults( this.options, {
     388                        modal: true,
     389                        state: 'edit-attachment'
     390                });
     391
     392                this.controller = this.options.controller;
     393                this.gridRouter = this.controller.gridRouter;
     394                this.library = this.options.library;
     395
     396                if ( this.options.model ) {
     397                        this.model = this.options.model;
     398                }
     399
     400                this.bindHandlers();
     401                this.createStates();
     402                this.createModal();
     403
     404                this.title.mode( 'default' );
     405                this.toggleNav();
     406        },
     407
     408        bindHandlers: function() {
     409                // Bind default title creation.
     410                this.on( 'title:create:default', this.createTitle, this );
     411
     412                // Close the modal if the attachment is deleted.
     413                this.listenTo( this.model, 'change:status destroy', this.close, this );
     414
     415                this.on( 'content:create:edit-metadata', this.editMetadataMode, this );
     416                this.on( 'content:create:edit-image', this.editImageMode, this );
     417                this.on( 'content:render:edit-image', this.editImageModeRender, this );
     418                this.on( 'close', this.detach );
     419        },
     420
     421        createModal: function() {
     422                // Initialize modal container view.
     423                if ( this.options.modal ) {
     424                        this.modal = new wp.media.view.Modal({
     425                                controller: this,
     426                                title:      this.options.title
     427                        });
     428
     429                        this.modal.on( 'open', _.bind( function () {
     430                                $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) );
     431                        }, this ) );
     432
     433                        // Completely destroy the modal DOM element when closing it.
     434                        this.modal.on( 'close', _.bind( function() {
     435                                this.modal.remove();
     436                                $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
     437                                // Restore the original focus item if possible
     438                                $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus();
     439                                this.resetRoute();
     440                        }, this ) );
     441
     442                        // Set this frame as the modal's content.
     443                        this.modal.content( this );
     444                        this.modal.open();
     445                }
     446        },
     447
     448        /**
     449         * Add the default states to the frame.
     450         */
     451        createStates: function() {
     452                this.states.add([
     453                        new wp.media.controller.EditAttachmentMetadata( { model: this.model } )
     454                ]);
     455        },
     456
     457        /**
     458         * Content region rendering callback for the `edit-metadata` mode.
     459         *
     460         * @param {Object} contentRegion Basic object with a `view` property, which
     461         *                               should be set with the proper region view.
     462         */
     463        editMetadataMode: function( contentRegion ) {
     464                contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({
     465                        controller: this,
     466                        model:      this.model
     467                });
     468
     469                /**
     470                 * Attach a subview to display fields added via the
     471                 * `attachment_fields_to_edit` filter.
     472                 */
     473                contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({
     474                        controller: this,
     475                        model:      this.model
     476                }) );
     477
     478                // Update browser url when navigating media details
     479                if ( this.model ) {
     480                        this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
     481                }
     482        },
     483
     484        /**
     485         * Render the EditImage view into the frame's content region.
     486         *
     487         * @param {Object} contentRegion Basic object with a `view` property, which
     488         *                               should be set with the proper region view.
     489         */
     490        editImageMode: function( contentRegion ) {
     491                var editImageController = new wp.media.controller.EditImage( {
     492                        model: this.model,
     493                        frame: this
     494                } );
     495                // Noop some methods.
     496                editImageController._toolbar = function() {};
     497                editImageController._router = function() {};
     498                editImageController._menu = function() {};
     499
     500                contentRegion.view = new wp.media.view.EditImage.Details( {
     501                        model: this.model,
     502                        frame: this,
     503                        controller: editImageController
     504                } );
     505        },
     506
     507        editImageModeRender: function( view ) {
     508                view.on( 'ready', view.loadEditor );
     509        },
     510
     511        toggleNav: function() {
     512                this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() );
     513                this.$('.right').toggleClass( 'disabled', ! this.hasNext() );
     514        },
     515
     516        /**
     517         * Rerender the view.
     518         */
     519        rerender: function() {
     520                // Only rerender the `content` region.
     521                if ( this.content.mode() !== 'edit-metadata' ) {
     522                        this.content.mode( 'edit-metadata' );
     523                } else {
     524                        this.content.render();
     525                }
     526
     527                this.toggleNav();
     528        },
     529
     530        /**
     531         * Click handler to switch to the previous media item.
     532         */
     533        previousMediaItem: function() {
     534                if ( ! this.hasPrevious() ) {
     535                        this.$( '.left' ).blur();
     536                        return;
     537                }
     538                this.model = this.library.at( this.getCurrentIndex() - 1 );
     539                this.rerender();
     540                this.$( '.left' ).focus();
     541        },
     542
     543        /**
     544         * Click handler to switch to the next media item.
     545         */
     546        nextMediaItem: function() {
     547                if ( ! this.hasNext() ) {
     548                        this.$( '.right' ).blur();
     549                        return;
     550                }
     551                this.model = this.library.at( this.getCurrentIndex() + 1 );
     552                this.rerender();
     553                this.$( '.right' ).focus();
     554        },
     555
     556        getCurrentIndex: function() {
     557                return this.library.indexOf( this.model );
     558        },
     559
     560        hasNext: function() {
     561                return ( this.getCurrentIndex() + 1 ) < this.library.length;
     562        },
     563
     564        hasPrevious: function() {
     565                return ( this.getCurrentIndex() - 1 ) > -1;
     566        },
     567        /**
     568         * Respond to the keyboard events: right arrow, left arrow, except when
     569         * focus is in a textarea or input field.
     570         */
     571        keyEvent: function( event ) {
     572                if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) {
     573                        return;
     574                }
     575
     576                // The right arrow key
     577                if ( 39 === event.keyCode ) {
     578                        this.nextMediaItem();
     579                }
     580                // The left arrow key
     581                if ( 37 === event.keyCode ) {
     582                        this.previousMediaItem();
     583                }
     584        },
     585
     586        resetRoute: function() {
     587                this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) );
     588        }
     589});
     590
     591module.exports = EditAttachments;
     592
     593},{}],10:[function(require,module,exports){
     594/*globals wp, _, Backbone */
     595
     596/**
     597 * wp.media.view.MediaFrame.Manage
     598 *
     599 * A generic management frame workflow.
     600 *
     601 * Used in the media grid view.
     602 *
     603 * @class
     604 * @augments wp.media.view.MediaFrame
     605 * @augments wp.media.view.Frame
     606 * @augments wp.media.View
     607 * @augments wp.Backbone.View
     608 * @augments Backbone.View
     609 * @mixes wp.media.controller.StateMachine
     610 */
     611var MediaFrame = wp.media.view.MediaFrame,
     612        Library = wp.media.controller.Library,
     613
     614        $ = Backbone.$,
     615        Manage;
     616
     617Manage = MediaFrame.extend({
     618        /**
     619         * @global wp.Uploader
     620         */
     621        initialize: function() {
     622                _.defaults( this.options, {
     623                        title:     '',
     624                        modal:     false,
     625                        selection: [],
     626                        library:   {}, // Options hash for the query to the media library.
     627                        multiple:  'add',
     628                        state:     'library',
     629                        uploader:  true,
     630                        mode:      [ 'grid', 'edit' ]
     631                });
     632
     633                this.$body = $( document.body );
     634                this.$window = $( window );
     635                this.$adminBar = $( '#wpadminbar' );
     636                this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) );
     637                $( document ).on( 'click', '.add-new-h2', _.bind( this.addNewClickHandler, this ) );
     638
     639                // Ensure core and media grid view UI is enabled.
     640                this.$el.addClass('wp-core-ui');
     641
     642                // Force the uploader off if the upload limit has been exceeded or
     643                // if the browser isn't supported.
     644                if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     645                        this.options.uploader = false;
     646                }
     647
     648                // Initialize a window-wide uploader.
     649                if ( this.options.uploader ) {
     650                        this.uploader = new wp.media.view.UploaderWindow({
     651                                controller: this,
     652                                uploader: {
     653                                        dropzone:  document.body,
     654                                        container: document.body
     655                                }
     656                        }).render();
     657                        this.uploader.ready();
     658                        $('body').append( this.uploader.el );
     659
     660                        this.options.uploader = false;
     661                }
     662
     663                this.gridRouter = new wp.media.view.MediaFrame.Manage.Router();
     664
     665                // Call 'initialize' directly on the parent class.
     666                MediaFrame.prototype.initialize.apply( this, arguments );
     667
     668                // Append the frame view directly the supplied container.
     669                this.$el.appendTo( this.options.container );
     670
     671                this.createStates();
     672                this.bindRegionModeHandlers();
     673                this.render();
     674                this.bindSearchHandler();
     675        },
     676
     677        bindSearchHandler: function() {
     678                var search = this.$( '#media-search-input' ),
     679                        currentSearch = this.options.container.data( 'search' ),
     680                        searchView = this.browserView.toolbar.get( 'search' ).$el,
     681                        listMode = this.$( '.view-list' ),
     682
     683                        input  = _.debounce( function (e) {
     684                                var val = $( e.currentTarget ).val(),
     685                                        url = '';
     686
     687                                if ( val ) {
     688                                        url += '?search=' + val;
     689                                }
     690                                this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
     691                        }, 1000 );
     692
     693                // Update the URL when entering search string (at most once per second)
     694                search.on( 'input', _.bind( input, this ) );
     695                searchView.val( currentSearch ).trigger( 'input' );
     696
     697                this.gridRouter.on( 'route:search', function () {
     698                        var href = window.location.href;
     699                        if ( href.indexOf( 'mode=' ) > -1 ) {
     700                                href = href.replace( /mode=[^&]+/g, 'mode=list' );
     701                        } else {
     702                                href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
     703                        }
     704                        href = href.replace( 'search=', 's=' );
     705                        listMode.prop( 'href', href );
     706                } );
     707        },
     708
     709        /**
     710         * Create the default states for the frame.
     711         */
     712        createStates: function() {
     713                var options = this.options;
     714
     715                if ( this.options.states ) {
     716                        return;
     717                }
     718
     719                // Add the default states.
     720                this.states.add([
     721                        new Library({
     722                                library:            wp.media.query( options.library ),
     723                                multiple:           options.multiple,
     724                                title:              options.title,
     725                                content:            'browse',
     726                                toolbar:            'select',
     727                                contentUserSetting: false,
     728                                filterable:         'all',
     729                                autoSelect:         false
     730                        })
     731                ]);
     732        },
     733
     734        /**
     735         * Bind region mode activation events to proper handlers.
     736         */
     737        bindRegionModeHandlers: function() {
     738                this.on( 'content:create:browse', this.browseContent, this );
     739
     740                // Handle a frame-level event for editing an attachment.
     741                this.on( 'edit:attachment', this.openEditAttachmentModal, this );
     742
     743                this.on( 'select:activate', this.bindKeydown, this );
     744                this.on( 'select:deactivate', this.unbindKeydown, this );
     745        },
     746
     747        handleKeydown: function( e ) {
     748                if ( 27 === e.which ) {
     749                        e.preventDefault();
     750                        this.deactivateMode( 'select' ).activateMode( 'edit' );
     751                }
     752        },
     753
     754        bindKeydown: function() {
     755                this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) );
     756        },
     757
     758        unbindKeydown: function() {
     759                this.$body.off( 'keydown.select' );
     760        },
     761
     762        fixPosition: function() {
     763                var $browser, $toolbar;
     764                if ( ! this.isModeActive( 'select' ) ) {
     765                        return;
     766                }
     767
     768                $browser = this.$('.attachments-browser');
     769                $toolbar = $browser.find('.media-toolbar');
     770
     771                // Offset doesn't appear to take top margin into account, hence +16
     772                if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) {
     773                        $browser.addClass( 'fixed' );
     774                        $toolbar.css('width', $browser.width() + 'px');
     775                } else {
     776                        $browser.removeClass( 'fixed' );
     777                        $toolbar.css('width', '');
     778                }
     779        },
     780
     781        /**
     782         * Click handler for the `Add New` button.
     783         */
     784        addNewClickHandler: function( event ) {
     785                event.preventDefault();
     786                this.trigger( 'toggle:upload:attachment' );
     787        },
     788
     789        /**
     790         * Open the Edit Attachment modal.
     791         */
     792        openEditAttachmentModal: function( model ) {
     793                // Create a new EditAttachment frame, passing along the library and the attachment model.
     794                wp.media( {
     795                        frame:       'edit-attachments',
     796                        controller:  this,
     797                        library:     this.state().get('library'),
     798                        model:       model
     799                } );
     800        },
     801
     802        /**
     803         * Create an attachments browser view within the content region.
     804         *
     805         * @param {Object} contentRegion Basic object with a `view` property, which
     806         *                               should be set with the proper region view.
     807         * @this wp.media.controller.Region
     808         */
     809        browseContent: function( contentRegion ) {
     810                var state = this.state();
     811
     812                // Browse our library of attachments.
     813                this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({
     814                        controller: this,
     815                        collection: state.get('library'),
     816                        selection:  state.get('selection'),
     817                        model:      state,
     818                        sortable:   state.get('sortable'),
     819                        search:     state.get('searchable'),
     820                        filters:    state.get('filterable'),
     821                        date:       state.get('date'),
     822                        display:    state.get('displaySettings'),
     823                        dragInfo:   state.get('dragInfo'),
     824                        sidebar:    'errors',
     825
     826                        suggestedWidth:  state.get('suggestedWidth'),
     827                        suggestedHeight: state.get('suggestedHeight'),
     828
     829                        AttachmentView: state.get('AttachmentView'),
     830
     831                        scrollElement: document
     832                });
     833                this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) );
     834
     835                this.errors = wp.Uploader.errors;
     836                this.errors.on( 'add remove reset', this.sidebarVisibility, this );
     837        },
     838
     839        sidebarVisibility: function() {
     840                this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length );
     841        },
     842
     843        bindDeferred: function() {
     844                if ( ! this.browserView.dfd ) {
     845                        return;
     846                }
     847                this.browserView.dfd.done( _.bind( this.startHistory, this ) );
     848        },
     849
     850        startHistory: function() {
     851                // Verify pushState support and activate
     852                if ( window.history && window.history.pushState ) {
     853                        Backbone.history.start( {
     854                                root: window._wpMediaGridSettings.adminUrl,
     855                                pushState: true
     856                        } );
     857                }
     858        }
     859});
     860
     861module.exports = Manage;
     862
     863},{}]},{},[2]);
  • src/wp-includes/js/media-models.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/*globals wp, _, jQuery */
     3
     4var $ = jQuery,
     5        Attachment, Attachments, l10n, media;
     6
     7window.wp = window.wp || {};
     8
     9/**
     10 * Create and return a media frame.
     11 *
     12 * Handles the default media experience.
     13 *
     14 * @param  {object} attributes The properties passed to the main media controller.
     15 * @return {wp.media.view.MediaFrame} A media workflow.
     16 */
     17media = wp.media = function( attributes ) {
     18        var MediaFrame = media.view.MediaFrame,
     19                frame;
     20
     21        if ( ! MediaFrame ) {
     22                return;
     23        }
     24
     25        attributes = _.defaults( attributes || {}, {
     26                frame: 'select'
     27        });
     28
     29        if ( 'select' === attributes.frame && MediaFrame.Select ) {
     30                frame = new MediaFrame.Select( attributes );
     31        } else if ( 'post' === attributes.frame && MediaFrame.Post ) {
     32                frame = new MediaFrame.Post( attributes );
     33        } else if ( 'manage' === attributes.frame && MediaFrame.Manage ) {
     34                frame = new MediaFrame.Manage( attributes );
     35        } else if ( 'image' === attributes.frame && MediaFrame.ImageDetails ) {
     36                frame = new MediaFrame.ImageDetails( attributes );
     37        } else if ( 'audio' === attributes.frame && MediaFrame.AudioDetails ) {
     38                frame = new MediaFrame.AudioDetails( attributes );
     39        } else if ( 'video' === attributes.frame && MediaFrame.VideoDetails ) {
     40                frame = new MediaFrame.VideoDetails( attributes );
     41        } else if ( 'edit-attachments' === attributes.frame && MediaFrame.EditAttachments ) {
     42                frame = new MediaFrame.EditAttachments( attributes );
     43        }
     44
     45        delete attributes.frame;
     46
     47        media.frame = frame;
     48
     49        return frame;
     50};
     51
     52_.extend( media, { model: {}, view: {}, controller: {}, frames: {} });
     53
     54// Link any localized strings.
     55l10n = media.model.l10n = window._wpMediaModelsL10n || {};
     56
     57// Link any settings.
     58media.model.settings = l10n.settings || {};
     59delete l10n.settings;
     60
     61Attachment = media.model.Attachment = require( './models/attachment.js' );
     62Attachments = media.model.Attachments = require( './models/attachments.js' );
     63
     64media.model.Query = require( './models/query.js' );
     65media.model.PostImage = require( './models/post-image.js' );
     66media.model.Selection = require( './models/selection.js' );
     67
     68/**
     69 * ========================================================================
     70 * UTILITIES
     71 * ========================================================================
     72 */
     73
     74/**
     75 * A basic equality comparator for Backbone models.
     76 *
     77 * Used to order models within a collection - @see wp.media.model.Attachments.comparator().
     78 *
     79 * @param  {mixed}  a  The primary parameter to compare.
     80 * @param  {mixed}  b  The primary parameter to compare.
     81 * @param  {string} ac The fallback parameter to compare, a's cid.
     82 * @param  {string} bc The fallback parameter to compare, b's cid.
     83 * @return {number}    -1: a should come before b.
     84 *                      0: a and b are of the same rank.
     85 *                      1: b should come before a.
     86 */
     87media.compare = function( a, b, ac, bc ) {
     88        if ( _.isEqual( a, b ) ) {
     89                return ac === bc ? 0 : (ac > bc ? -1 : 1);
     90        } else {
     91                return a > b ? -1 : 1;
     92        }
     93};
     94
     95_.extend( media, {
     96        /**
     97         * media.template( id )
     98         *
     99         * Fetch a JavaScript template for an id, and return a templating function for it.
     100         *
     101         * See wp.template() in `wp-includes/js/wp-util.js`.
     102         *
     103         * @borrows wp.template as template
     104         */
     105        template: wp.template,
     106
     107        /**
     108         * media.post( [action], [data] )
     109         *
     110         * Sends a POST request to WordPress.
     111         * See wp.ajax.post() in `wp-includes/js/wp-util.js`.
     112         *
     113         * @borrows wp.ajax.post as post
     114         */
     115        post: wp.ajax.post,
     116
     117        /**
     118         * media.ajax( [action], [options] )
     119         *
     120         * Sends an XHR request to WordPress.
     121         * See wp.ajax.send() in `wp-includes/js/wp-util.js`.
     122         *
     123         * @borrows wp.ajax.send as ajax
     124         */
     125        ajax: wp.ajax.send,
     126
     127        /**
     128         * Scales a set of dimensions to fit within bounding dimensions.
     129         *
     130         * @param {Object} dimensions
     131         * @returns {Object}
     132         */
     133        fit: function( dimensions ) {
     134                var width     = dimensions.width,
     135                        height    = dimensions.height,
     136                        maxWidth  = dimensions.maxWidth,
     137                        maxHeight = dimensions.maxHeight,
     138                        constraint;
     139
     140                // Compare ratios between the two values to determine which
     141                // max to constrain by. If a max value doesn't exist, then the
     142                // opposite side is the constraint.
     143                if ( ! _.isUndefined( maxWidth ) && ! _.isUndefined( maxHeight ) ) {
     144                        constraint = ( width / height > maxWidth / maxHeight ) ? 'width' : 'height';
     145                } else if ( _.isUndefined( maxHeight ) ) {
     146                        constraint = 'width';
     147                } else if (  _.isUndefined( maxWidth ) && height > maxHeight ) {
     148                        constraint = 'height';
     149                }
     150
     151                // If the value of the constrained side is larger than the max,
     152                // then scale the values. Otherwise return the originals; they fit.
     153                if ( 'width' === constraint && width > maxWidth ) {
     154                        return {
     155                                width : maxWidth,
     156                                height: Math.round( maxWidth * height / width )
     157                        };
     158                } else if ( 'height' === constraint && height > maxHeight ) {
     159                        return {
     160                                width : Math.round( maxHeight * width / height ),
     161                                height: maxHeight
     162                        };
     163                } else {
     164                        return {
     165                                width : width,
     166                                height: height
     167                        };
     168                }
     169        },
     170        /**
     171         * Truncates a string by injecting an ellipsis into the middle.
     172         * Useful for filenames.
     173         *
     174         * @param {String} string
     175         * @param {Number} [length=30]
     176         * @param {String} [replacement=&hellip;]
     177         * @returns {String} The string, unless length is greater than string.length.
     178         */
     179        truncate: function( string, length, replacement ) {
     180                length = length || 30;
     181                replacement = replacement || '&hellip;';
     182
     183                if ( string.length <= length ) {
     184                        return string;
     185                }
     186
     187                return string.substr( 0, length / 2 ) + replacement + string.substr( -1 * length / 2 );
     188        }
     189});
     190
     191/**
     192 * ========================================================================
     193 * MODELS
     194 * ========================================================================
     195 */
     196/**
     197 * wp.media.attachment
     198 *
     199 * @static
     200 * @param {String} id A string used to identify a model.
     201 * @returns {wp.media.model.Attachment}
     202 */
     203media.attachment = function( id ) {
     204        return Attachment.get( id );
     205};
     206
     207/**
     208 * A collection of all attachments that have been fetched from the server.
     209 *
     210 * @static
     211 * @member {wp.media.model.Attachments}
     212 */
     213Attachments.all = new Attachments();
     214
     215/**
     216 * wp.media.query
     217 *
     218 * Shorthand for creating a new Attachments Query.
     219 *
     220 * @param {object} [props]
     221 * @returns {wp.media.model.Attachments}
     222 */
     223media.query = function( props ) {
     224        return new Attachments( null, {
     225                props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } )
     226        });
     227};
     228
     229// Clean up. Prevents mobile browsers caching
     230$(window).on('unload', function(){
     231        window.wp = null;
     232});
     233
     234},{"./models/attachment.js":2,"./models/attachments.js":3,"./models/post-image.js":4,"./models/query.js":5,"./models/selection.js":6}],2:[function(require,module,exports){
     235/*globals wp, _, Backbone */
     236
     237/**
     238 * wp.media.model.Attachment
     239 *
     240 * @class
     241 * @augments Backbone.Model
     242 */
     243var $ = Backbone.$,
     244        Attachment;
     245
     246Attachment = Backbone.Model.extend({
     247        /**
     248         * Triggered when attachment details change
     249         * Overrides Backbone.Model.sync
     250         *
     251         * @param {string} method
     252         * @param {wp.media.model.Attachment} model
     253         * @param {Object} [options={}]
     254         *
     255         * @returns {Promise}
     256         */
     257        sync: function( method, model, options ) {
     258                // If the attachment does not yet have an `id`, return an instantly
     259                // rejected promise. Otherwise, all of our requests will fail.
     260                if ( _.isUndefined( this.id ) ) {
     261                        return $.Deferred().rejectWith( this ).promise();
     262                }
     263
     264                // Overload the `read` request so Attachment.fetch() functions correctly.
     265                if ( 'read' === method ) {
     266                        options = options || {};
     267                        options.context = this;
     268                        options.data = _.extend( options.data || {}, {
     269                                action: 'get-attachment',
     270                                id: this.id
     271                        });
     272                        return wp.media.ajax( options );
     273
     274                // Overload the `update` request so properties can be saved.
     275                } else if ( 'update' === method ) {
     276                        // If we do not have the necessary nonce, fail immeditately.
     277                        if ( ! this.get('nonces') || ! this.get('nonces').update ) {
     278                                return $.Deferred().rejectWith( this ).promise();
     279                        }
     280
     281                        options = options || {};
     282                        options.context = this;
     283
     284                        // Set the action and ID.
     285                        options.data = _.extend( options.data || {}, {
     286                                action:  'save-attachment',
     287                                id:      this.id,
     288                                nonce:   this.get('nonces').update,
     289                                post_id: wp.media.model.settings.post.id
     290                        });
     291
     292                        // Record the values of the changed attributes.
     293                        if ( model.hasChanged() ) {
     294                                options.data.changes = {};
     295
     296                                _.each( model.changed, function( value, key ) {
     297                                        options.data.changes[ key ] = this.get( key );
     298                                }, this );
     299                        }
     300
     301                        return wp.media.ajax( options );
     302
     303                // Overload the `delete` request so attachments can be removed.
     304                // This will permanently delete an attachment.
     305                } else if ( 'delete' === method ) {
     306                        options = options || {};
     307
     308                        if ( ! options.wait ) {
     309                                this.destroyed = true;
     310                        }
     311
     312                        options.context = this;
     313                        options.data = _.extend( options.data || {}, {
     314                                action:   'delete-post',
     315                                id:       this.id,
     316                                _wpnonce: this.get('nonces')['delete']
     317                        });
     318
     319                        return wp.media.ajax( options ).done( function() {
     320                                this.destroyed = true;
     321                        }).fail( function() {
     322                                this.destroyed = false;
     323                        });
     324
     325                // Otherwise, fall back to `Backbone.sync()`.
     326                } else {
     327                        /**
     328                         * Call `sync` directly on Backbone.Model
     329                         */
     330                        return Backbone.Model.prototype.sync.apply( this, arguments );
     331                }
     332        },
     333        /**
     334         * Convert date strings into Date objects.
     335         *
     336         * @param {Object} resp The raw response object, typically returned by fetch()
     337         * @returns {Object} The modified response object, which is the attributes hash
     338         *    to be set on the model.
     339         */
     340        parse: function( resp ) {
     341                if ( ! resp ) {
     342                        return resp;
     343                }
     344
     345                resp.date = new Date( resp.date );
     346                resp.modified = new Date( resp.modified );
     347                return resp;
     348        },
     349        /**
     350         * @param {Object} data The properties to be saved.
     351         * @param {Object} options Sync options. e.g. patch, wait, success, error.
     352         *
     353         * @this Backbone.Model
     354         *
     355         * @returns {Promise}
     356         */
     357        saveCompat: function( data, options ) {
     358                var model = this;
     359
     360                // If we do not have the necessary nonce, fail immeditately.
     361                if ( ! this.get('nonces') || ! this.get('nonces').update ) {
     362                        return $.Deferred().rejectWith( this ).promise();
     363                }
     364
     365                return wp.media.post( 'save-attachment-compat', _.defaults({
     366                        id:      this.id,
     367                        nonce:   this.get('nonces').update,
     368                        post_id: wp.media.model.settings.post.id
     369                }, data ) ).done( function( resp, status, xhr ) {
     370                        model.set( model.parse( resp, xhr ), options );
     371                });
     372        }
     373}, {
     374        /**
     375         * Create a new model on the static 'all' attachments collection and return it.
     376         *
     377         * @static
     378         * @param {Object} attrs
     379         * @returns {wp.media.model.Attachment}
     380         */
     381        create: function( attrs ) {
     382                var Attachments = wp.media.model.Attachments;
     383                return Attachments.all.push( attrs );
     384        },
     385        /**
     386         * Create a new model on the static 'all' attachments collection and return it.
     387         *
     388         * If this function has already been called for the id,
     389         * it returns the specified attachment.
     390         *
     391         * @static
     392         * @param {string} id A string used to identify a model.
     393         * @param {Backbone.Model|undefined} attachment
     394         * @returns {wp.media.model.Attachment}
     395         */
     396        get: _.memoize( function( id, attachment ) {
     397                var Attachments = wp.media.model.Attachments;
     398                return Attachments.all.push( attachment || { id: id } );
     399        })
     400});
     401
     402module.exports = Attachment;
     403
     404},{}],3:[function(require,module,exports){
     405/*globals wp, _, Backbone */
     406
     407/**
     408 * wp.media.model.Attachments
     409 *
     410 * A collection of attachments.
     411 *
     412 * This collection has no persistence with the server without supplying
     413 * 'options.props.query = true', which will mirror the collection
     414 * to an Attachments Query collection - @see wp.media.model.Attachments.mirror().
     415 *
     416 * @class
     417 * @augments Backbone.Collection
     418 *
     419 * @param {array}  [models]                Models to initialize with the collection.
     420 * @param {object} [options]               Options hash for the collection.
     421 * @param {string} [options.props]         Options hash for the initial query properties.
     422 * @param {string} [options.props.order]   Initial order (ASC or DESC) for the collection.
     423 * @param {string} [options.props.orderby] Initial attribute key to order the collection by.
     424 * @param {string} [options.props.query]   Whether the collection is linked to an attachments query.
     425 * @param {string} [options.observe]
     426 * @param {string} [options.filters]
     427 *
     428 */
     429var Attachments = Backbone.Collection.extend({
     430        /**
     431         * @type {wp.media.model.Attachment}
     432         */
     433        model: wp.media.model.Attachment,
     434        /**
     435         * @param {Array} [models=[]] Array of models used to populate the collection.
     436         * @param {Object} [options={}]
     437         */
     438        initialize: function( models, options ) {
     439                options = options || {};
     440
     441                this.props   = new Backbone.Model();
     442                this.filters = options.filters || {};
     443
     444                // Bind default `change` events to the `props` model.
     445                this.props.on( 'change', this._changeFilteredProps, this );
     446
     447                this.props.on( 'change:order',   this._changeOrder,   this );
     448                this.props.on( 'change:orderby', this._changeOrderby, this );
     449                this.props.on( 'change:query',   this._changeQuery,   this );
     450
     451                this.props.set( _.defaults( options.props || {} ) );
     452
     453                if ( options.observe ) {
     454                        this.observe( options.observe );
     455                }
     456        },
     457        /**
     458         * Sort the collection when the order attribute changes.
     459         *
     460         * @access private
     461         */
     462        _changeOrder: function() {
     463                if ( this.comparator ) {
     464                        this.sort();
     465                }
     466        },
     467        /**
     468         * Set the default comparator only when the `orderby` property is set.
     469         *
     470         * @access private
     471         *
     472         * @param {Backbone.Model} model
     473         * @param {string} orderby
     474         */
     475        _changeOrderby: function( model, orderby ) {
     476                // If a different comparator is defined, bail.
     477                if ( this.comparator && this.comparator !== Attachments.comparator ) {
     478                        return;
     479                }
     480
     481                if ( orderby && 'post__in' !== orderby ) {
     482                        this.comparator = Attachments.comparator;
     483                } else {
     484                        delete this.comparator;
     485                }
     486        },
     487        /**
     488         * If the `query` property is set to true, query the server using
     489         * the `props` values, and sync the results to this collection.
     490         *
     491         * @access private
     492         *
     493         * @param {Backbone.Model} model
     494         * @param {Boolean} query
     495         */
     496        _changeQuery: function( model, query ) {
     497                if ( query ) {
     498                        this.props.on( 'change', this._requery, this );
     499                        this._requery();
     500                } else {
     501                        this.props.off( 'change', this._requery, this );
     502                }
     503        },
     504        /**
     505         * @access private
     506         *
     507         * @param {Backbone.Model} model
     508         */
     509        _changeFilteredProps: function( model ) {
     510                // If this is a query, updating the collection will be handled by
     511                // `this._requery()`.
     512                if ( this.props.get('query') ) {
     513                        return;
     514                }
     515
     516                var changed = _.chain( model.changed ).map( function( t, prop ) {
     517                        var filter = Attachments.filters[ prop ],
     518                                term = model.get( prop );
     519
     520                        if ( ! filter ) {
     521                                return;
     522                        }
     523
     524                        if ( term && ! this.filters[ prop ] ) {
     525                                this.filters[ prop ] = filter;
     526                        } else if ( ! term && this.filters[ prop ] === filter ) {
     527                                delete this.filters[ prop ];
     528                        } else {
     529                                return;
     530                        }
     531
     532                        // Record the change.
     533                        return true;
     534                }, this ).any().value();
     535
     536                if ( ! changed ) {
     537                        return;
     538                }
     539
     540                // If no `Attachments` model is provided to source the searches
     541                // from, then automatically generate a source from the existing
     542                // models.
     543                if ( ! this._source ) {
     544                        this._source = new Attachments( this.models );
     545                }
     546
     547                this.reset( this._source.filter( this.validator, this ) );
     548        },
     549
     550        validateDestroyed: false,
     551        /**
     552         * Checks whether an attachment is valid.
     553         *
     554         * @param {wp.media.model.Attachment} attachment
     555         * @returns {Boolean}
     556         */
     557        validator: function( attachment ) {
     558                if ( ! this.validateDestroyed && attachment.destroyed ) {
     559                        return false;
     560                }
     561                return _.all( this.filters, function( filter ) {
     562                        return !! filter.call( this, attachment );
     563                }, this );
     564        },
     565        /**
     566         * Add or remove an attachment to the collection depending on its validity.
     567         *
     568         * @param {wp.media.model.Attachment} attachment
     569         * @param {Object} options
     570         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     571         */
     572        validate: function( attachment, options ) {
     573                var valid = this.validator( attachment ),
     574                        hasAttachment = !! this.get( attachment.cid );
     575
     576                if ( ! valid && hasAttachment ) {
     577                        this.remove( attachment, options );
     578                } else if ( valid && ! hasAttachment ) {
     579                        this.add( attachment, options );
     580                }
     581
     582                return this;
     583        },
     584
     585        /**
     586         * Add or remove all attachments from another collection depending on each one's validity.
     587         *
     588         * @param {wp.media.model.Attachments} attachments
     589         * @param {object} [options={}]
     590         *
     591         * @fires wp.media.model.Attachments#reset
     592         *
     593         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     594         */
     595        validateAll: function( attachments, options ) {
     596                options = options || {};
     597
     598                _.each( attachments.models, function( attachment ) {
     599                        this.validate( attachment, { silent: true });
     600                }, this );
     601
     602                if ( ! options.silent ) {
     603                        this.trigger( 'reset', this, options );
     604                }
     605                return this;
     606        },
     607        /**
     608         * Start observing another attachments collection change events
     609         * and replicate them on this collection.
     610         *
     611         * @param {wp.media.model.Attachments} The attachments collection to observe.
     612         * @returns {wp.media.model.Attachments} Returns itself to allow chaining.
     613         */
     614        observe: function( attachments ) {
     615                this.observers = this.observers || [];
     616                this.observers.push( attachments );
     617
     618                attachments.on( 'add change remove', this._validateHandler, this );
     619                attachments.on( 'reset', this._validateAllHandler, this );
     620                this.validateAll( attachments );
     621                return this;
     622        },
     623        /**
     624         * Stop replicating collection change events from another attachments collection.
     625         *
     626         * @param {wp.media.model.Attachments} The attachments collection to stop observing.
     627         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     628         */
     629        unobserve: function( attachments ) {
     630                if ( attachments ) {
     631                        attachments.off( null, null, this );
     632                        this.observers = _.without( this.observers, attachments );
     633
     634                } else {
     635                        _.each( this.observers, function( attachments ) {
     636                                attachments.off( null, null, this );
     637                        }, this );
     638                        delete this.observers;
     639                }
     640
     641                return this;
     642        },
     643        /**
     644         * @access private
     645         *
     646         * @param {wp.media.model.Attachments} attachment
     647         * @param {wp.media.model.Attachments} attachments
     648         * @param {Object} options
     649         *
     650         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     651         */
     652        _validateHandler: function( attachment, attachments, options ) {
     653                // If we're not mirroring this `attachments` collection,
     654                // only retain the `silent` option.
     655                options = attachments === this.mirroring ? options : {
     656                        silent: options && options.silent
     657                };
     658
     659                return this.validate( attachment, options );
     660        },
     661        /**
     662         * @access private
     663         *
     664         * @param {wp.media.model.Attachments} attachments
     665         * @param {Object} options
     666         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     667         */
     668        _validateAllHandler: function( attachments, options ) {
     669                return this.validateAll( attachments, options );
     670        },
     671        /**
     672         * Start mirroring another attachments collection, clearing out any models already
     673         * in the collection.
     674         *
     675         * @param {wp.media.model.Attachments} The attachments collection to mirror.
     676         * @returns {wp.media.model.Attachments} Returns itself to allow chaining
     677         */
     678        mirror: function( attachments ) {
     679                if ( this.mirroring && this.mirroring === attachments ) {
     680                        return this;
     681                }
     682
     683                this.unmirror();
     684                this.mirroring = attachments;
     685
     686                // Clear the collection silently. A `reset` event will be fired
     687                // when `observe()` calls `validateAll()`.
     688                this.reset( [], { silent: true } );
     689                this.observe( attachments );
     690
     691                return this;
     692        },
     693        /**
     694         * Stop mirroring another attachments collection.
     695         */
     696        unmirror: function() {
     697                if ( ! this.mirroring ) {
     698                        return;
     699                }
     700
     701                this.unobserve( this.mirroring );
     702                delete this.mirroring;
     703        },
     704        /**
     705         * Retrive more attachments from the server for the collection.
     706         *
     707         * Only works if the collection is mirroring a Query Attachments collection,
     708         * and forwards to its `more` method. This collection class doesn't have
     709         * server persistence by itself.
     710         *
     711         * @param {object} options
     712         * @returns {Promise}
     713         */
     714        more: function( options ) {
     715                var deferred = jQuery.Deferred(),
     716                        mirroring = this.mirroring,
     717                        attachments = this;
     718
     719                if ( ! mirroring || ! mirroring.more ) {
     720                        return deferred.resolveWith( this ).promise();
     721                }
     722                // If we're mirroring another collection, forward `more` to
     723                // the mirrored collection. Account for a race condition by
     724                // checking if we're still mirroring that collection when
     725                // the request resolves.
     726                mirroring.more( options ).done( function() {
     727                        if ( this === attachments.mirroring ) {
     728                                deferred.resolveWith( this );
     729                        }
     730                });
     731
     732                return deferred.promise();
     733        },
     734        /**
     735         * Whether there are more attachments that haven't been sync'd from the server
     736         * that match the collection's query.
     737         *
     738         * Only works if the collection is mirroring a Query Attachments collection,
     739         * and forwards to its `hasMore` method. This collection class doesn't have
     740         * server persistence by itself.
     741         *
     742         * @returns {boolean}
     743         */
     744        hasMore: function() {
     745                return this.mirroring ? this.mirroring.hasMore() : false;
     746        },
     747        /**
     748         * A custom AJAX-response parser.
     749         *
     750         * See trac ticket #24753
     751         *
     752         * @param {Object|Array} resp The raw response Object/Array.
     753         * @param {Object} xhr
     754         * @returns {Array} The array of model attributes to be added to the collection
     755         */
     756        parse: function( resp, xhr ) {
     757                if ( ! _.isArray( resp ) ) {
     758                        resp = [resp];
     759                }
     760
     761                return _.map( resp, function( attrs ) {
     762                        var id, attachment, newAttributes;
     763
     764                        if ( attrs instanceof Backbone.Model ) {
     765                                id = attrs.get( 'id' );
     766                                attrs = attrs.attributes;
     767                        } else {
     768                                id = attrs.id;
     769                        }
     770
     771                        attachment = wp.media.model.Attachment.get( id );
     772                        newAttributes = attachment.parse( attrs, xhr );
     773
     774                        if ( ! _.isEqual( attachment.attributes, newAttributes ) ) {
     775                                attachment.set( newAttributes );
     776                        }
     777
     778                        return attachment;
     779                });
     780        },
     781        /**
     782         * If the collection is a query, create and mirror an Attachments Query collection.
     783         *
     784         * @access private
     785         */
     786        _requery: function( refresh ) {
     787                var props;
     788                if ( this.props.get('query') ) {
     789                        props = this.props.toJSON();
     790                        props.cache = ( true !== refresh );
     791                        this.mirror( wp.media.model.Query.get( props ) );
     792                }
     793        },
     794        /**
     795         * If this collection is sorted by `menuOrder`, recalculates and saves
     796         * the menu order to the database.
     797         *
     798         * @returns {undefined|Promise}
     799         */
     800        saveMenuOrder: function() {
     801                if ( 'menuOrder' !== this.props.get('orderby') ) {
     802                        return;
     803                }
     804
     805                // Removes any uploading attachments, updates each attachment's
     806                // menu order, and returns an object with an { id: menuOrder }
     807                // mapping to pass to the request.
     808                var attachments = this.chain().filter( function( attachment ) {
     809                        return ! _.isUndefined( attachment.id );
     810                }).map( function( attachment, index ) {
     811                        // Indices start at 1.
     812                        index = index + 1;
     813                        attachment.set( 'menuOrder', index );
     814                        return [ attachment.id, index ];
     815                }).object().value();
     816
     817                if ( _.isEmpty( attachments ) ) {
     818                        return;
     819                }
     820
     821                return wp.media.post( 'save-attachment-order', {
     822                        nonce:       wp.media.model.settings.post.nonce,
     823                        post_id:     wp.media.model.settings.post.id,
     824                        attachments: attachments
     825                });
     826        }
     827}, {
     828        /**
     829         * A function to compare two attachment models in an attachments collection.
     830         *
     831         * Used as the default comparator for instances of wp.media.model.Attachments
     832         * and its subclasses. @see wp.media.model.Attachments._changeOrderby().
     833         *
     834         * @static
     835         *
     836         * @param {Backbone.Model} a
     837         * @param {Backbone.Model} b
     838         * @param {Object} options
     839         * @returns {Number} -1 if the first model should come before the second,
     840         *    0 if they are of the same rank and
     841         *    1 if the first model should come after.
     842         */
     843        comparator: function( a, b, options ) {
     844                var key   = this.props.get('orderby'),
     845                        order = this.props.get('order') || 'DESC',
     846                        ac    = a.cid,
     847                        bc    = b.cid;
     848
     849                a = a.get( key );
     850                b = b.get( key );
     851
     852                if ( 'date' === key || 'modified' === key ) {
     853                        a = a || new Date();
     854                        b = b || new Date();
     855                }
     856
     857                // If `options.ties` is set, don't enforce the `cid` tiebreaker.
     858                if ( options && options.ties ) {
     859                        ac = bc = null;
     860                }
     861
     862                return ( 'DESC' === order ) ? wp.media.compare( a, b, ac, bc ) : wp.media.compare( b, a, bc, ac );
     863        },
     864        /**
     865         * @namespace
     866         */
     867        filters: {
     868                /**
     869                 * @static
     870                 * Note that this client-side searching is *not* equivalent
     871                 * to our server-side searching.
     872                 *
     873                 * @param {wp.media.model.Attachment} attachment
     874                 *
     875                 * @this wp.media.model.Attachments
     876                 *
     877                 * @returns {Boolean}
     878                 */
     879                search: function( attachment ) {
     880                        if ( ! this.props.get('search') ) {
     881                                return true;
     882                        }
     883
     884                        return _.any(['title','filename','description','caption','name'], function( key ) {
     885                                var value = attachment.get( key );
     886                                return value && -1 !== value.search( this.props.get('search') );
     887                        }, this );
     888                },
     889                /**
     890                 * @static
     891                 * @param {wp.media.model.Attachment} attachment
     892                 *
     893                 * @this wp.media.model.Attachments
     894                 *
     895                 * @returns {Boolean}
     896                 */
     897                type: function( attachment ) {
     898                        var type = this.props.get('type');
     899                        return ! type || -1 !== type.indexOf( attachment.get('type') );
     900                },
     901                /**
     902                 * @static
     903                 * @param {wp.media.model.Attachment} attachment
     904                 *
     905                 * @this wp.media.model.Attachments
     906                 *
     907                 * @returns {Boolean}
     908                 */
     909                uploadedTo: function( attachment ) {
     910                        var uploadedTo = this.props.get('uploadedTo');
     911                        if ( _.isUndefined( uploadedTo ) ) {
     912                                return true;
     913                        }
     914
     915                        return uploadedTo === attachment.get('uploadedTo');
     916                },
     917                /**
     918                 * @static
     919                 * @param {wp.media.model.Attachment} attachment
     920                 *
     921                 * @this wp.media.model.Attachments
     922                 *
     923                 * @returns {Boolean}
     924                 */
     925                status: function( attachment ) {
     926                        var status = this.props.get('status');
     927                        if ( _.isUndefined( status ) ) {
     928                                return true;
     929                        }
     930
     931                        return status === attachment.get('status');
     932                }
     933        }
     934});
     935
     936module.exports = Attachments;
     937
     938},{}],4:[function(require,module,exports){
     939/*globals Backbone */
     940
     941/**
     942 * wp.media.model.PostImage
     943 *
     944 * An instance of an image that's been embedded into a post.
     945 *
     946 * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails.
     947 *
     948 * @class
     949 * @augments Backbone.Model
     950 *
     951 * @param {int} [attributes]               Initial model attributes.
     952 * @param {int} [attributes.attachment_id] ID of the attachment.
     953 **/
     954var PostImage = Backbone.Model.extend({
     955
     956        initialize: function( attributes ) {
     957                var Attachment = wp.media.model.Attachment;
     958                this.attachment = false;
     959
     960                if ( attributes.attachment_id ) {
     961                        this.attachment = Attachment.get( attributes.attachment_id );
     962                        if ( this.attachment.get( 'url' ) ) {
     963                                this.dfd = jQuery.Deferred();
     964                                this.dfd.resolve();
     965                        } else {
     966                                this.dfd = this.attachment.fetch();
     967                        }
     968                        this.bindAttachmentListeners();
     969                }
     970
     971                // keep url in sync with changes to the type of link
     972                this.on( 'change:link', this.updateLinkUrl, this );
     973                this.on( 'change:size', this.updateSize, this );
     974
     975                this.setLinkTypeFromUrl();
     976                this.setAspectRatio();
     977
     978                this.set( 'originalUrl', attributes.url );
     979        },
     980
     981        bindAttachmentListeners: function() {
     982                this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );
     983                this.listenTo( this.attachment, 'sync', this.setAspectRatio );
     984                this.listenTo( this.attachment, 'change', this.updateSize );
     985        },
     986
     987        changeAttachment: function( attachment, props ) {
     988                this.stopListening( this.attachment );
     989                this.attachment = attachment;
     990                this.bindAttachmentListeners();
     991
     992                this.set( 'attachment_id', this.attachment.get( 'id' ) );
     993                this.set( 'caption', this.attachment.get( 'caption' ) );
     994                this.set( 'alt', this.attachment.get( 'alt' ) );
     995                this.set( 'size', props.get( 'size' ) );
     996                this.set( 'align', props.get( 'align' ) );
     997                this.set( 'link', props.get( 'link' ) );
     998                this.updateLinkUrl();
     999                this.updateSize();
     1000        },
     1001
     1002        setLinkTypeFromUrl: function() {
     1003                var linkUrl = this.get( 'linkUrl' ),
     1004                        type;
     1005
     1006                if ( ! linkUrl ) {
     1007                        this.set( 'link', 'none' );
     1008                        return;
     1009                }
     1010
     1011                // default to custom if there is a linkUrl
     1012                type = 'custom';
     1013
     1014                if ( this.attachment ) {
     1015                        if ( this.attachment.get( 'url' ) === linkUrl ) {
     1016                                type = 'file';
     1017                        } else if ( this.attachment.get( 'link' ) === linkUrl ) {
     1018                                type = 'post';
     1019                        }
     1020                } else {
     1021                        if ( this.get( 'url' ) === linkUrl ) {
     1022                                type = 'file';
     1023                        }
     1024                }
     1025
     1026                this.set( 'link', type );
     1027        },
     1028
     1029        updateLinkUrl: function() {
     1030                var link = this.get( 'link' ),
     1031                        url;
     1032
     1033                switch( link ) {
     1034                        case 'file':
     1035                                if ( this.attachment ) {
     1036                                        url = this.attachment.get( 'url' );
     1037                                } else {
     1038                                        url = this.get( 'url' );
     1039                                }
     1040                                this.set( 'linkUrl', url );
     1041                                break;
     1042                        case 'post':
     1043                                this.set( 'linkUrl', this.attachment.get( 'link' ) );
     1044                                break;
     1045                        case 'none':
     1046                                this.set( 'linkUrl', '' );
     1047                                break;
     1048                }
     1049        },
     1050
     1051        updateSize: function() {
     1052                var size;
     1053
     1054                if ( ! this.attachment ) {
     1055                        return;
     1056                }
     1057
     1058                if ( this.get( 'size' ) === 'custom' ) {
     1059                        this.set( 'width', this.get( 'customWidth' ) );
     1060                        this.set( 'height', this.get( 'customHeight' ) );
     1061                        this.set( 'url', this.get( 'originalUrl' ) );
     1062                        return;
     1063                }
     1064
     1065                size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ];
     1066
     1067                if ( ! size ) {
     1068                        return;
     1069                }
     1070
     1071                this.set( 'url', size.url );
     1072                this.set( 'width', size.width );
     1073                this.set( 'height', size.height );
     1074        },
     1075
     1076        setAspectRatio: function() {
     1077                var full;
     1078
     1079                if ( this.attachment && this.attachment.get( 'sizes' ) ) {
     1080                        full = this.attachment.get( 'sizes' ).full;
     1081
     1082                        if ( full ) {
     1083                                this.set( 'aspectRatio', full.width / full.height );
     1084                                return;
     1085                        }
     1086                }
     1087
     1088                this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) );
     1089        }
     1090});
     1091
     1092module.exports = PostImage;
     1093
     1094},{}],5:[function(require,module,exports){
     1095/*globals wp, _ */
     1096
     1097/**
     1098 * wp.media.model.Query
     1099 *
     1100 * A collection of attachments that match the supplied query arguments.
     1101 *
     1102 * Note: Do NOT change this.args after the query has been initialized.
     1103 *       Things will break.
     1104 *
     1105 * @class
     1106 * @augments wp.media.model.Attachments
     1107 * @augments Backbone.Collection
     1108 *
     1109 * @param {array}  [models]                      Models to initialize with the collection.
     1110 * @param {object} [options]                     Options hash.
     1111 * @param {object} [options.args]                Attachments query arguments.
     1112 * @param {object} [options.args.posts_per_page]
     1113 */
     1114var Attachments = wp.media.model.Attachments,
     1115        Query;
     1116
     1117Query = Attachments.extend({
     1118        /**
     1119         * @global wp.Uploader
     1120         *
     1121         * @param {array}  [models=[]]  Array of initial models to populate the collection.
     1122         * @param {object} [options={}]
     1123         */
     1124        initialize: function( models, options ) {
     1125                var allowed;
     1126
     1127                options = options || {};
     1128                Attachments.prototype.initialize.apply( this, arguments );
     1129
     1130                this.args     = options.args;
     1131                this._hasMore = true;
     1132                this.created  = new Date();
     1133
     1134                this.filters.order = function( attachment ) {
     1135                        var orderby = this.props.get('orderby'),
     1136                                order = this.props.get('order');
     1137
     1138                        if ( ! this.comparator ) {
     1139                                return true;
     1140                        }
     1141
     1142                        // We want any items that can be placed before the last
     1143                        // item in the set. If we add any items after the last
     1144                        // item, then we can't guarantee the set is complete.
     1145                        if ( this.length ) {
     1146                                return 1 !== this.comparator( attachment, this.last(), { ties: true });
     1147
     1148                        // Handle the case where there are no items yet and
     1149                        // we're sorting for recent items. In that case, we want
     1150                        // changes that occurred after we created the query.
     1151                        } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) {
     1152                                return attachment.get( orderby ) >= this.created;
     1153
     1154                        // If we're sorting by menu order and we have no items,
     1155                        // accept any items that have the default menu order (0).
     1156                        } else if ( 'ASC' === order && 'menuOrder' === orderby ) {
     1157                                return attachment.get( orderby ) === 0;
     1158                        }
     1159
     1160                        // Otherwise, we don't want any items yet.
     1161                        return false;
     1162                };
     1163
     1164                // Observe the central `wp.Uploader.queue` collection to watch for
     1165                // new matches for the query.
     1166                //
     1167                // Only observe when a limited number of query args are set. There
     1168                // are no filters for other properties, so observing will result in
     1169                // false positives in those queries.
     1170                allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ];
     1171                if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) {
     1172                        this.observe( wp.Uploader.queue );
     1173                }
     1174        },
     1175        /**
     1176         * Whether there are more attachments that haven't been sync'd from the server
     1177         * that match the collection's query.
     1178         *
     1179         * @returns {boolean}
     1180         */
     1181        hasMore: function() {
     1182                return this._hasMore;
     1183        },
     1184        /**
     1185         * Fetch more attachments from the server for the collection.
     1186         *
     1187         * @param   {object}  [options={}]
     1188         * @returns {Promise}
     1189         */
     1190        more: function( options ) {
     1191                var query = this;
     1192
     1193                // If there is already a request pending, return early with the Deferred object.
     1194                if ( this._more && 'pending' === this._more.state() ) {
     1195                        return this._more;
     1196                }
     1197
     1198                if ( ! this.hasMore() ) {
     1199                        return jQuery.Deferred().resolveWith( this ).promise();
     1200                }
     1201
     1202                options = options || {};
     1203                options.remove = false;
     1204
     1205                return this._more = this.fetch( options ).done( function( resp ) {
     1206                        if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) {
     1207                                query._hasMore = false;
     1208                        }
     1209                });
     1210        },
     1211        /**
     1212         * Overrides Backbone.Collection.sync
     1213         * Overrides wp.media.model.Attachments.sync
     1214         *
     1215         * @param {String} method
     1216         * @param {Backbone.Model} model
     1217         * @param {Object} [options={}]
     1218         * @returns {Promise}
     1219         */
     1220        sync: function( method, model, options ) {
     1221                var args, fallback;
     1222
     1223                // Overload the read method so Attachment.fetch() functions correctly.
     1224                if ( 'read' === method ) {
     1225                        options = options || {};
     1226                        options.context = this;
     1227                        options.data = _.extend( options.data || {}, {
     1228                                action:  'query-attachments',
     1229                                post_id: wp.media.model.settings.post.id
     1230                        });
     1231
     1232                        // Clone the args so manipulation is non-destructive.
     1233                        args = _.clone( this.args );
     1234
     1235                        // Determine which page to query.
     1236                        if ( -1 !== args.posts_per_page ) {
     1237                                args.paged = Math.round( this.length / args.posts_per_page ) + 1;
     1238                        }
     1239
     1240                        options.data.query = args;
     1241                        return wp.media.ajax( options );
     1242
     1243                // Otherwise, fall back to Backbone.sync()
     1244                } else {
     1245                        /**
     1246                         * Call wp.media.model.Attachments.sync or Backbone.sync
     1247                         */
     1248                        fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone;
     1249                        return fallback.sync.apply( this, arguments );
     1250                }
     1251        }
     1252}, {
     1253        /**
     1254         * @readonly
     1255         */
     1256        defaultProps: {
     1257                orderby: 'date',
     1258                order:   'DESC'
     1259        },
     1260        /**
     1261         * @readonly
     1262         */
     1263        defaultArgs: {
     1264                posts_per_page: 40
     1265        },
     1266        /**
     1267         * @readonly
     1268         */
     1269        orderby: {
     1270                allowed:  [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ],
     1271                /**
     1272                 * A map of JavaScript orderby values to their WP_Query equivalents.
     1273                 * @type {Object}
     1274                 */
     1275                valuemap: {
     1276                        'id':         'ID',
     1277                        'uploadedTo': 'parent',
     1278                        'menuOrder':  'menu_order ID'
     1279                }
     1280        },
     1281        /**
     1282         * A map of JavaScript query properties to their WP_Query equivalents.
     1283         *
     1284         * @readonly
     1285         */
     1286        propmap: {
     1287                'search':    's',
     1288                'type':      'post_mime_type',
     1289                'perPage':   'posts_per_page',
     1290                'menuOrder': 'menu_order',
     1291                'uploadedTo': 'post_parent',
     1292                'status':     'post_status',
     1293                'include':    'post__in',
     1294                'exclude':    'post__not_in'
     1295        },
     1296        /**
     1297         * Creates and returns an Attachments Query collection given the properties.
     1298         *
     1299         * Caches query objects and reuses where possible.
     1300         *
     1301         * @static
     1302         * @method
     1303         *
     1304         * @param {object} [props]
     1305         * @param {Object} [props.cache=true]   Whether to use the query cache or not.
     1306         * @param {Object} [props.order]
     1307         * @param {Object} [props.orderby]
     1308         * @param {Object} [props.include]
     1309         * @param {Object} [props.exclude]
     1310         * @param {Object} [props.s]
     1311         * @param {Object} [props.post_mime_type]
     1312         * @param {Object} [props.posts_per_page]
     1313         * @param {Object} [props.menu_order]
     1314         * @param {Object} [props.post_parent]
     1315         * @param {Object} [props.post_status]
     1316         * @param {Object} [options]
     1317         *
     1318         * @returns {wp.media.model.Query} A new Attachments Query collection.
     1319         */
     1320        get: (function(){
     1321                /**
     1322                 * @static
     1323                 * @type Array
     1324                 */
     1325                var queries = [];
     1326
     1327                /**
     1328                 * @returns {Query}
     1329                 */
     1330                return function( props, options ) {
     1331                        var args     = {},
     1332                                orderby  = Query.orderby,
     1333                                defaults = Query.defaultProps,
     1334                                query,
     1335                                cache    = !! props.cache || _.isUndefined( props.cache );
     1336
     1337                        // Remove the `query` property. This isn't linked to a query,
     1338                        // this *is* the query.
     1339                        delete props.query;
     1340                        delete props.cache;
     1341
     1342                        // Fill default args.
     1343                        _.defaults( props, defaults );
     1344
     1345                        // Normalize the order.
     1346                        props.order = props.order.toUpperCase();
     1347                        if ( 'DESC' !== props.order && 'ASC' !== props.order ) {
     1348                                props.order = defaults.order.toUpperCase();
     1349                        }
     1350
     1351                        // Ensure we have a valid orderby value.
     1352                        if ( ! _.contains( orderby.allowed, props.orderby ) ) {
     1353                                props.orderby = defaults.orderby;
     1354                        }
     1355
     1356                        _.each( [ 'include', 'exclude' ], function( prop ) {
     1357                                if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) {
     1358                                        props[ prop ] = [ props[ prop ] ];
     1359                                }
     1360                        } );
     1361
     1362                        // Generate the query `args` object.
     1363                        // Correct any differing property names.
     1364                        _.each( props, function( value, prop ) {
     1365                                if ( _.isNull( value ) ) {
     1366                                        return;
     1367                                }
     1368
     1369                                args[ Query.propmap[ prop ] || prop ] = value;
     1370                        });
     1371
     1372                        // Fill any other default query args.
     1373                        _.defaults( args, Query.defaultArgs );
     1374
     1375                        // `props.orderby` does not always map directly to `args.orderby`.
     1376                        // Substitute exceptions specified in orderby.keymap.
     1377                        args.orderby = orderby.valuemap[ props.orderby ] || props.orderby;
     1378
     1379                        // Search the query cache for a matching query.
     1380                        if ( cache ) {
     1381                                query = _.find( queries, function( query ) {
     1382                                        return _.isEqual( query.args, args );
     1383                                });
     1384                        } else {
     1385                                queries = [];
     1386                        }
     1387
     1388                        // Otherwise, create a new query and add it to the cache.
     1389                        if ( ! query ) {
     1390                                query = new Query( [], _.extend( options || {}, {
     1391                                        props: props,
     1392                                        args:  args
     1393                                } ) );
     1394                                queries.push( query );
     1395                        }
     1396
     1397                        return query;
     1398                };
     1399        }())
     1400});
     1401
     1402module.exports = Query;
     1403
     1404},{}],6:[function(require,module,exports){
     1405/*globals wp, _ */
     1406
     1407/**
     1408 * wp.media.model.Selection
     1409 *
     1410 * A selection of attachments.
     1411 *
     1412 * @class
     1413 * @augments wp.media.model.Attachments
     1414 * @augments Backbone.Collection
     1415 */
     1416var Attachments = wp.media.model.Attachments,
     1417        Selection;
     1418
     1419Selection = Attachments.extend({
     1420        /**
     1421         * Refresh the `single` model whenever the selection changes.
     1422         * Binds `single` instead of using the context argument to ensure
     1423         * it receives no parameters.
     1424         *
     1425         * @param {Array} [models=[]] Array of models used to populate the collection.
     1426         * @param {Object} [options={}]
     1427         */
     1428        initialize: function( models, options ) {
     1429                /**
     1430                 * call 'initialize' directly on the parent class
     1431                 */
     1432                Attachments.prototype.initialize.apply( this, arguments );
     1433                this.multiple = options && options.multiple;
     1434
     1435                this.on( 'add remove reset', _.bind( this.single, this, false ) );
     1436        },
     1437
     1438        /**
     1439         * If the workflow does not support multi-select, clear out the selection
     1440         * before adding a new attachment to it.
     1441         *
     1442         * @param {Array} models
     1443         * @param {Object} options
     1444         * @returns {wp.media.model.Attachment[]}
     1445         */
     1446        add: function( models, options ) {
     1447                if ( ! this.multiple ) {
     1448                        this.remove( this.models );
     1449                }
     1450                /**
     1451                 * call 'add' directly on the parent class
     1452                 */
     1453                return Attachments.prototype.add.call( this, models, options );
     1454        },
     1455
     1456        /**
     1457         * Fired when toggling (clicking on) an attachment in the modal.
     1458         *
     1459         * @param {undefined|boolean|wp.media.model.Attachment} model
     1460         *
     1461         * @fires wp.media.model.Selection#selection:single
     1462         * @fires wp.media.model.Selection#selection:unsingle
     1463         *
     1464         * @returns {Backbone.Model}
     1465         */
     1466        single: function( model ) {
     1467                var previous = this._single;
     1468
     1469                // If a `model` is provided, use it as the single model.
     1470                if ( model ) {
     1471                        this._single = model;
     1472                }
     1473                // If the single model isn't in the selection, remove it.
     1474                if ( this._single && ! this.get( this._single.cid ) ) {
     1475                        delete this._single;
     1476                }
     1477
     1478                this._single = this._single || this.last();
     1479
     1480                // If single has changed, fire an event.
     1481                if ( this._single !== previous ) {
     1482                        if ( previous ) {
     1483                                previous.trigger( 'selection:unsingle', previous, this );
     1484
     1485                                // If the model was already removed, trigger the collection
     1486                                // event manually.
     1487                                if ( ! this.get( previous.cid ) ) {
     1488                                        this.trigger( 'selection:unsingle', previous, this );
     1489                                }
     1490                        }
     1491                        if ( this._single ) {
     1492                                this._single.trigger( 'selection:single', this._single, this );
     1493                        }
     1494                }
     1495
     1496                // Return the single model, or the last model as a fallback.
     1497                return this._single;
     1498        }
     1499});
     1500
     1501module.exports = Selection;
     1502
     1503},{}]},{},[1]);
  • src/wp-includes/js/media-views.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/*globals wp, _ */
     3
     4/**
     5 * wp.media.controller.CollectionAdd
     6 *
     7 * A state for adding attachments to a collection (e.g. video playlist).
     8 *
     9 * @class
     10 * @augments wp.media.controller.Library
     11 * @augments wp.media.controller.State
     12 * @augments Backbone.Model
     13 *
     14 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     15 * @param {string}                     [attributes.id=library]      Unique identifier.
     16 * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
     17 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     18 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     19 *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
     20 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     21 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     22 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     23 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     24 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     25 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     26 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     27 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     28 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     29 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     30 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     31 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     32 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     33 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     34 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     35 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     36 */
     37var Selection = wp.media.model.Selection,
     38        Library = wp.media.controller.Library,
     39        CollectionAdd;
     40
     41CollectionAdd = Library.extend({
     42        defaults: _.defaults( {
     43                // Selection defaults. @see media.model.Selection
     44                multiple:      'add',
     45                // Attachments browser defaults. @see media.view.AttachmentsBrowser
     46                filterable:    'uploaded',
     47
     48                priority:      100,
     49                syncSelection: false
     50        }, Library.prototype.defaults ),
     51
     52        /**
     53         * @since 3.9.0
     54         */
     55        initialize: function() {
     56                var collectionType = this.get('collectionType');
     57
     58                if ( 'video' === this.get( 'type' ) ) {
     59                        collectionType = 'video-' + collectionType;
     60                }
     61
     62                this.set( 'id', collectionType + '-library' );
     63                this.set( 'toolbar', collectionType + '-add' );
     64                this.set( 'menu', collectionType );
     65
     66                // If we haven't been provided a `library`, create a `Selection`.
     67                if ( ! this.get('library') ) {
     68                        this.set( 'library', wp.media.query({ type: this.get('type') }) );
     69                }
     70                Library.prototype.initialize.apply( this, arguments );
     71        },
     72
     73        /**
     74         * @since 3.9.0
     75         */
     76        activate: function() {
     77                var library = this.get('library'),
     78                        editLibrary = this.get('editLibrary'),
     79                        edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
     80
     81                if ( editLibrary && editLibrary !== edit ) {
     82                        library.unobserve( editLibrary );
     83                }
     84
     85                // Accepts attachments that exist in the original library and
     86                // that do not exist in gallery's library.
     87                library.validator = function( attachment ) {
     88                        return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     89                };
     90
     91                // Reset the library to ensure that all attachments are re-added
     92                // to the collection. Do so silently, as calling `observe` will
     93                // trigger the `reset` event.
     94                library.reset( library.mirroring.models, { silent: true });
     95                library.observe( edit );
     96                this.set('editLibrary', edit);
     97
     98                Library.prototype.activate.apply( this, arguments );
     99        }
     100});
     101
     102module.exports = CollectionAdd;
     103
     104},{}],2:[function(require,module,exports){
     105/*globals wp, Backbone */
     106
     107/**
     108 * wp.media.controller.CollectionEdit
     109 *
     110 * A state for editing a collection, which is used by audio and video playlists,
     111 * and can be used for other collections.
     112 *
     113 * @class
     114 * @augments wp.media.controller.Library
     115 * @augments wp.media.controller.State
     116 * @augments Backbone.Model
     117 *
     118 * @param {object}                     [attributes]                      The attributes hash passed to the state.
     119 * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
     120 * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
     121 *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
     122 * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
     123 * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
     124 * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
     125 * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
     126 * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     127 * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
     128 * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
     129 * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
     130 * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
     131 * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
     132 * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
     133 * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
     134 *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
     135 * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
     136 * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
     137 *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     138 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
     139 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
     140 */
     141var Library = wp.media.controller.Library,
     142        l10n = wp.media.view.l10n,
     143        $ = jQuery,
     144        CollectionEdit;
     145
     146CollectionEdit = Library.extend({
     147        defaults: {
     148                multiple:         false,
     149                sortable:         true,
     150                searchable:       false,
     151                content:          'browse',
     152                describe:         true,
     153                dragInfo:         true,
     154                idealColumnWidth: 170,
     155                editing:          false,
     156                priority:         60,
     157                SettingsView:     false,
     158                syncSelection:    false
     159        },
     160
     161        /**
     162         * @since 3.9.0
     163         */
     164        initialize: function() {
     165                var collectionType = this.get('collectionType');
     166
     167                if ( 'video' === this.get( 'type' ) ) {
     168                        collectionType = 'video-' + collectionType;
     169                }
     170
     171                this.set( 'id', collectionType + '-edit' );
     172                this.set( 'toolbar', collectionType + '-edit' );
     173
     174                // If we haven't been provided a `library`, create a `Selection`.
     175                if ( ! this.get('library') ) {
     176                        this.set( 'library', new wp.media.model.Selection() );
     177                }
     178                // The single `Attachment` view to be used in the `Attachments` view.
     179                if ( ! this.get('AttachmentView') ) {
     180                        this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     181                }
     182                Library.prototype.initialize.apply( this, arguments );
     183        },
     184
     185        /**
     186         * @since 3.9.0
     187         */
     188        activate: function() {
     189                var library = this.get('library');
     190
     191                // Limit the library to images only.
     192                library.props.set( 'type', this.get( 'type' ) );
     193
     194                // Watch for uploaded attachments.
     195                this.get('library').observe( wp.Uploader.queue );
     196
     197                this.frame.on( 'content:render:browse', this.renderSettings, this );
     198
     199                Library.prototype.activate.apply( this, arguments );
     200        },
     201
     202        /**
     203         * @since 3.9.0
     204         */
     205        deactivate: function() {
     206                // Stop watching for uploaded attachments.
     207                this.get('library').unobserve( wp.Uploader.queue );
     208
     209                this.frame.off( 'content:render:browse', this.renderSettings, this );
     210
     211                Library.prototype.deactivate.apply( this, arguments );
     212        },
     213
     214        /**
     215         * Render the collection embed settings view in the browser sidebar.
     216         *
     217         * @todo This is against the pattern elsewhere in media. Typically the frame
     218         *       is responsible for adding region mode callbacks. Explain.
     219         *
     220         * @since 3.9.0
     221         *
     222         * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
     223         */
     224        renderSettings: function( attachmentsBrowserView ) {
     225                var library = this.get('library'),
     226                        collectionType = this.get('collectionType'),
     227                        dragInfoText = this.get('dragInfoText'),
     228                        SettingsView = this.get('SettingsView'),
     229                        obj = {};
     230
     231                if ( ! library || ! attachmentsBrowserView ) {
     232                        return;
     233                }
     234
     235                library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
     236
     237                obj[ collectionType ] = new SettingsView({
     238                        controller: this,
     239                        model:      library[ collectionType ],
     240                        priority:   40
     241                });
     242
     243                attachmentsBrowserView.sidebar.set( obj );
     244
     245                if ( dragInfoText ) {
     246                        attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
     247                                el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
     248                                priority: -40
     249                        }) );
     250                }
     251
     252                // Add the 'Reverse order' button to the toolbar.
     253                attachmentsBrowserView.toolbar.set( 'reverse', {
     254                        text:     l10n.reverseOrder,
     255                        priority: 80,
     256
     257                        click: function() {
     258                                library.reset( library.toArray().reverse() );
     259                        }
     260                });
     261        }
     262});
     263
     264module.exports = CollectionEdit;
     265
     266},{}],3:[function(require,module,exports){
     267/*globals wp, _, Backbone */
     268
     269/**
     270 * wp.media.controller.Cropper
     271 *
     272 * A state for cropping an image.
     273 *
     274 * @class
     275 * @augments wp.media.controller.State
     276 * @augments Backbone.Model
     277 */
     278var l10n = wp.media.view.l10n,
     279        Cropper;
     280
     281Cropper = wp.media.controller.State.extend({
     282        defaults: {
     283                id:          'cropper',
     284                title:       l10n.cropImage,
     285                // Region mode defaults.
     286                toolbar:     'crop',
     287                content:     'crop',
     288                router:      false,
     289
     290                canSkipCrop: false
     291        },
     292
     293        activate: function() {
     294                this.frame.on( 'content:create:crop', this.createCropContent, this );
     295                this.frame.on( 'close', this.removeCropper, this );
     296                this.set('selection', new Backbone.Collection(this.frame._selection.single));
     297        },
     298
     299        deactivate: function() {
     300                this.frame.toolbar.mode('browse');
     301        },
     302
     303        createCropContent: function() {
     304                this.cropperView = new wp.media.view.Cropper({
     305                        controller: this,
     306                        attachment: this.get('selection').first()
     307                });
     308                this.cropperView.on('image-loaded', this.createCropToolbar, this);
     309                this.frame.content.set(this.cropperView);
     310
     311        },
     312        removeCropper: function() {
     313                this.imgSelect.cancelSelection();
     314                this.imgSelect.setOptions({remove: true});
     315                this.imgSelect.update();
     316                this.cropperView.remove();
     317        },
     318        createCropToolbar: function() {
     319                var canSkipCrop, toolbarOptions;
     320
     321                canSkipCrop = this.get('canSkipCrop') || false;
     322
     323                toolbarOptions = {
     324                        controller: this.frame,
     325                        items: {
     326                                insert: {
     327                                        style:    'primary',
     328                                        text:     l10n.cropImage,
     329                                        priority: 80,
     330                                        requires: { library: false, selection: false },
     331
     332                                        click: function() {
     333                                                var controller = this.controller,
     334                                                        selection;
     335
     336                                                selection = controller.state().get('selection').first();
     337                                                selection.set({cropDetails: controller.state().imgSelect.getSelection()});
     338
     339                                                this.$el.text(l10n.cropping);
     340                                                this.$el.attr('disabled', true);
     341
     342                                                controller.state().doCrop( selection ).done( function( croppedImage ) {
     343                                                        controller.trigger('cropped', croppedImage );
     344                                                        controller.close();
     345                                                }).fail( function() {
     346                                                        controller.trigger('content:error:crop');
     347                                                });
     348                                        }
     349                                }
     350                        }
     351                };
     352
     353                if ( canSkipCrop ) {
     354                        _.extend( toolbarOptions.items, {
     355                                skip: {
     356                                        style:      'secondary',
     357                                        text:       l10n.skipCropping,
     358                                        priority:   70,
     359                                        requires:   { library: false, selection: false },
     360                                        click:      function() {
     361                                                var selection = this.controller.state().get('selection').first();
     362                                                this.controller.state().cropperView.remove();
     363                                                this.controller.trigger('skippedcrop', selection);
     364                                                this.controller.close();
     365                                        }
     366                                }
     367                        });
     368                }
     369
     370                this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
     371        },
     372
     373        doCrop: function( attachment ) {
     374                return wp.ajax.post( 'custom-header-crop', {
     375                        nonce: attachment.get('nonces').edit,
     376                        id: attachment.get('id'),
     377                        cropDetails: attachment.get('cropDetails')
     378                } );
     379        }
     380});
     381
     382module.exports = Cropper;
     383
     384},{}],4:[function(require,module,exports){
     385/*globals wp */
     386
     387/**
     388 * wp.media.controller.EditImage
     389 *
     390 * A state for editing (cropping, etc.) an image.
     391 *
     392 * @class
     393 * @augments wp.media.controller.State
     394 * @augments Backbone.Model
     395 *
     396 * @param {object}                    attributes                      The attributes hash passed to the state.
     397 * @param {wp.media.model.Attachment} attributes.model                The attachment.
     398 * @param {string}                    [attributes.id=edit-image]      Unique identifier.
     399 * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
     400 * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
     401 * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
     402 * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
     403 * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
     404 */
     405var l10n = wp.media.view.l10n,
     406        EditImage;
     407
     408EditImage = wp.media.controller.State.extend({
     409        defaults: {
     410                id:      'edit-image',
     411                title:   l10n.editImage,
     412                menu:    false,
     413                toolbar: 'edit-image',
     414                content: 'edit-image',
     415                url:     ''
     416        },
     417
     418        /**
     419         * @since 3.9.0
     420         */
     421        activate: function() {
     422                this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
     423        },
     424
     425        /**
     426         * @since 3.9.0
     427         */
     428        deactivate: function() {
     429                this.stopListening( this.frame );
     430        },
     431
     432        /**
     433         * @since 3.9.0
     434         */
     435        toolbar: function() {
     436                var frame = this.frame,
     437                        lastState = frame.lastState(),
     438                        previous = lastState && lastState.id;
     439
     440                frame.toolbar.set( new wp.media.view.Toolbar({
     441                        controller: frame,
     442                        items: {
     443                                back: {
     444                                        style: 'primary',
     445                                        text:     l10n.back,
     446                                        priority: 20,
     447                                        click:    function() {
     448                                                if ( previous ) {
     449                                                        frame.setState( previous );
     450                                                } else {
     451                                                        frame.close();
     452                                                }
     453                                        }
     454                                }
     455                        }
     456                }) );
     457        }
     458});
     459
     460module.exports = EditImage;
     461
     462},{}],5:[function(require,module,exports){
     463/*globals wp, _, Backbone */
     464
     465/**
     466 * wp.media.controller.Embed
     467 *
     468 * A state for embedding media from a URL.
     469 *
     470 * @class
     471 * @augments wp.media.controller.State
     472 * @augments Backbone.Model
     473 *
     474 * @param {object} attributes                         The attributes hash passed to the state.
     475 * @param {string} [attributes.id=embed]              Unique identifier.
     476 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
     477 * @param {string} [attributes.content=embed]         Initial mode for the content region.
     478 * @param {string} [attributes.menu=default]          Initial mode for the menu region.
     479 * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
     480 * @param {string} [attributes.menu=false]            Initial mode for the menu region.
     481 * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
     482 * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
     483 * @param {string} [attributes.url]                   The embed URL.
     484 * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
     485 */
     486var l10n = wp.media.view.l10n,
     487        $ = Backbone.$,
     488        Embed;
     489
     490Embed = wp.media.controller.State.extend({
     491        defaults: {
     492                id:       'embed',
     493                title:    l10n.insertFromUrlTitle,
     494                content:  'embed',
     495                menu:     'default',
     496                toolbar:  'main-embed',
     497                priority: 120,
     498                type:     'link',
     499                url:      '',
     500                metadata: {}
     501        },
     502
     503        // The amount of time used when debouncing the scan.
     504        sensitivity: 200,
     505
     506        initialize: function(options) {
     507                this.metadata = options.metadata;
     508                this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
     509                this.props = new Backbone.Model( this.metadata || { url: '' });
     510                this.props.on( 'change:url', this.debouncedScan, this );
     511                this.props.on( 'change:url', this.refresh, this );
     512                this.on( 'scan', this.scanImage, this );
     513        },
     514
     515        /**
     516         * Trigger a scan of the embedded URL's content for metadata required to embed.
     517         *
     518         * @fires wp.media.controller.Embed#scan
     519         */
     520        scan: function() {
     521                var scanners,
     522                        embed = this,
     523                        attributes = {
     524                                type: 'link',
     525                                scanners: []
     526                        };
     527
     528                // Scan is triggered with the list of `attributes` to set on the
     529                // state, useful for the 'type' attribute and 'scanners' attribute,
     530                // an array of promise objects for asynchronous scan operations.
     531                if ( this.props.get('url') ) {
     532                        this.trigger( 'scan', attributes );
     533                }
     534
     535                if ( attributes.scanners.length ) {
     536                        scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
     537                        scanners.always( function() {
     538                                if ( embed.get('scanners') === scanners ) {
     539                                        embed.set( 'loading', false );
     540                                }
     541                        });
     542                } else {
     543                        attributes.scanners = null;
     544                }
     545
     546                attributes.loading = !! attributes.scanners;
     547                this.set( attributes );
     548        },
     549        /**
     550         * Try scanning the embed as an image to discover its dimensions.
     551         *
     552         * @param {Object} attributes
     553         */
     554        scanImage: function( attributes ) {
     555                var frame = this.frame,
     556                        state = this,
     557                        url = this.props.get('url'),
     558                        image = new Image(),
     559                        deferred = $.Deferred();
     560
     561                attributes.scanners.push( deferred.promise() );
     562
     563                // Try to load the image and find its width/height.
     564                image.onload = function() {
     565                        deferred.resolve();
     566
     567                        if ( state !== frame.state() || url !== state.props.get('url') ) {
     568                                return;
     569                        }
     570
     571                        state.set({
     572                                type: 'image'
     573                        });
     574
     575                        state.props.set({
     576                                width:  image.width,
     577                                height: image.height
     578                        });
     579                };
     580
     581                image.onerror = deferred.reject;
     582                image.src = url;
     583        },
     584
     585        refresh: function() {
     586                this.frame.toolbar.get().refresh();
     587        },
     588
     589        reset: function() {
     590                this.props.clear().set({ url: '' });
     591
     592                if ( this.active ) {
     593                        this.refresh();
     594                }
     595        }
     596});
     597
     598module.exports = Embed;
     599
     600},{}],6:[function(require,module,exports){
     601/*globals wp, _ */
     602
     603/**
     604 * wp.media.controller.FeaturedImage
     605 *
     606 * A state for selecting a featured image for a post.
     607 *
     608 * @class
     609 * @augments wp.media.controller.Library
     610 * @augments wp.media.controller.State
     611 * @augments Backbone.Model
     612 *
     613 * @param {object}                     [attributes]                          The attributes hash passed to the state.
     614 * @param {string}                     [attributes.id=featured-image]        Unique identifier.
     615 * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
     616 * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
     617 *                                                                           If one is not supplied, a collection of all images will be created.
     618 * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
     619 * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
     620 *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
     621 * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
     622 * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
     623 * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
     624 * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
     625 * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
     626 * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
     627 *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
     628 * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     629 * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
     630 * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     631 * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
     632 * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
     633 */
     634var Attachment = wp.media.model.Attachment,
     635        Library = wp.media.controller.Library,
     636        l10n = wp.media.view.l10n,
     637        FeaturedImage;
     638
     639FeaturedImage = Library.extend({
     640        defaults: _.defaults({
     641                id:            'featured-image',
     642                title:         l10n.setFeaturedImageTitle,
     643                multiple:      false,
     644                filterable:    'uploaded',
     645                toolbar:       'featured-image',
     646                priority:      60,
     647                syncSelection: true
     648        }, Library.prototype.defaults ),
     649
     650        /**
     651         * @since 3.5.0
     652         */
     653        initialize: function() {
     654                var library, comparator;
     655
     656                // If we haven't been provided a `library`, create a `Selection`.
     657                if ( ! this.get('library') ) {
     658                        this.set( 'library', wp.media.query({ type: 'image' }) );
     659                }
     660
     661                Library.prototype.initialize.apply( this, arguments );
     662
     663                library    = this.get('library');
     664                comparator = library.comparator;
     665
     666                // Overload the library's comparator to push items that are not in
     667                // the mirrored query to the front of the aggregate collection.
     668                library.comparator = function( a, b ) {
     669                        var aInQuery = !! this.mirroring.get( a.cid ),
     670                                bInQuery = !! this.mirroring.get( b.cid );
     671
     672                        if ( ! aInQuery && bInQuery ) {
     673                                return -1;
     674                        } else if ( aInQuery && ! bInQuery ) {
     675                                return 1;
     676                        } else {
     677                                return comparator.apply( this, arguments );
     678                        }
     679                };
     680
     681                // Add all items in the selection to the library, so any featured
     682                // images that are not initially loaded still appear.
     683                library.observe( this.get('selection') );
     684        },
     685
     686        /**
     687         * @since 3.5.0
     688         */
     689        activate: function() {
     690                this.updateSelection();
     691                this.frame.on( 'open', this.updateSelection, this );
     692
     693                Library.prototype.activate.apply( this, arguments );
     694        },
     695
     696        /**
     697         * @since 3.5.0
     698         */
     699        deactivate: function() {
     700                this.frame.off( 'open', this.updateSelection, this );
     701
     702                Library.prototype.deactivate.apply( this, arguments );
     703        },
     704
     705        /**
     706         * @since 3.5.0
     707         */
     708        updateSelection: function() {
     709                var selection = this.get('selection'),
     710                        id = wp.media.view.settings.post.featuredImageId,
     711                        attachment;
     712
     713                if ( '' !== id && -1 !== id ) {
     714                        attachment = Attachment.get( id );
     715                        attachment.fetch();
     716                }
     717
     718                selection.reset( attachment ? [ attachment ] : [] );
     719        }
     720});
     721
     722module.exports = FeaturedImage;
     723
     724},{}],7:[function(require,module,exports){
     725/*globals wp, _ */
     726
     727/**
     728 * wp.media.controller.GalleryAdd
     729 *
     730 * A state for selecting more images to add to a gallery.
     731 *
     732 * @class
     733 * @augments wp.media.controller.Library
     734 * @augments wp.media.controller.State
     735 * @augments Backbone.Model
     736 *
     737 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     738 * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
     739 * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
     740 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
     741 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     742 *                                                                          If one is not supplied, a collection of all images will be created.
     743 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     744 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     745 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
     746 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     747 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     748 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     749 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
     750 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     751 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     752 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     753 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     754 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
     755 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
     756 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
     757 */
     758var Selection = wp.media.model.Selection,
     759        Library = wp.media.controller.Library,
     760        l10n = wp.media.view.l10n,
     761        GalleryAdd;
     762
     763GalleryAdd = Library.extend({
     764        defaults: _.defaults({
     765                id:            'gallery-library',
     766                title:         l10n.addToGalleryTitle,
     767                multiple:      'add',
     768                filterable:    'uploaded',
     769                menu:          'gallery',
     770                toolbar:       'gallery-add',
     771                priority:      100,
     772                syncSelection: false
     773        }, Library.prototype.defaults ),
     774
     775        /**
     776         * @since 3.5.0
     777         */
     778        initialize: function() {
     779                // If a library wasn't supplied, create a library of images.
     780                if ( ! this.get('library') ) {
     781                        this.set( 'library', wp.media.query({ type: 'image' }) );
     782                }
     783
     784                Library.prototype.initialize.apply( this, arguments );
     785        },
     786
     787        /**
     788         * @since 3.5.0
     789         */
     790        activate: function() {
     791                var library = this.get('library'),
     792                        edit    = this.frame.state('gallery-edit').get('library');
     793
     794                if ( this.editLibrary && this.editLibrary !== edit ) {
     795                        library.unobserve( this.editLibrary );
     796                }
     797
     798                // Accepts attachments that exist in the original library and
     799                // that do not exist in gallery's library.
     800                library.validator = function( attachment ) {
     801                        return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
     802                };
     803
     804                // Reset the library to ensure that all attachments are re-added
     805                // to the collection. Do so silently, as calling `observe` will
     806                // trigger the `reset` event.
     807                library.reset( library.mirroring.models, { silent: true });
     808                library.observe( edit );
     809                this.editLibrary = edit;
     810
     811                Library.prototype.activate.apply( this, arguments );
     812        }
     813});
     814
     815module.exports = GalleryAdd;
     816
     817},{}],8:[function(require,module,exports){
     818/*globals wp */
     819
     820/**
     821 * wp.media.controller.GalleryEdit
     822 *
     823 * A state for editing a gallery's images and settings.
     824 *
     825 * @class
     826 * @augments wp.media.controller.Library
     827 * @augments wp.media.controller.State
     828 * @augments Backbone.Model
     829 *
     830 * @param {object}                     [attributes]                       The attributes hash passed to the state.
     831 * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
     832 * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
     833 * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
     834 *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
     835 * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
     836 * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
     837 * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     838 * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
     839 * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
     840 * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     841 * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
     842 * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
     843 * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
     844 * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
     845 * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
     846 * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
     847 *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
     848 * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
     849 *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
     850 */
     851var Library = wp.media.controller.Library,
     852        l10n = wp.media.view.l10n,
     853        GalleryEdit;
     854
     855GalleryEdit = Library.extend({
     856        defaults: {
     857                id:               'gallery-edit',
     858                title:            l10n.editGalleryTitle,
     859                multiple:         false,
     860                searchable:       false,
     861                sortable:         true,
     862                display:          false,
     863                content:          'browse',
     864                toolbar:          'gallery-edit',
     865                describe:         true,
     866                displaySettings:  true,
     867                dragInfo:         true,
     868                idealColumnWidth: 170,
     869                editing:          false,
     870                priority:         60,
     871                syncSelection:    false
     872        },
     873
     874        /**
     875         * @since 3.5.0
     876         */
     877        initialize: function() {
     878                // If we haven't been provided a `library`, create a `Selection`.
     879                if ( ! this.get('library') ) {
     880                        this.set( 'library', new wp.media.model.Selection() );
     881                }
     882
     883                // The single `Attachment` view to be used in the `Attachments` view.
     884                if ( ! this.get('AttachmentView') ) {
     885                        this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
     886                }
     887
     888                Library.prototype.initialize.apply( this, arguments );
     889        },
     890
     891        /**
     892         * @since 3.5.0
     893         */
     894        activate: function() {
     895                var library = this.get('library');
     896
     897                // Limit the library to images only.
     898                library.props.set( 'type', 'image' );
     899
     900                // Watch for uploaded attachments.
     901                this.get('library').observe( wp.Uploader.queue );
     902
     903                this.frame.on( 'content:render:browse', this.gallerySettings, this );
     904
     905                Library.prototype.activate.apply( this, arguments );
     906        },
     907
     908        /**
     909         * @since 3.5.0
     910         */
     911        deactivate: function() {
     912                // Stop watching for uploaded attachments.
     913                this.get('library').unobserve( wp.Uploader.queue );
     914
     915                this.frame.off( 'content:render:browse', this.gallerySettings, this );
     916
     917                Library.prototype.deactivate.apply( this, arguments );
     918        },
     919
     920        /**
     921         * @since 3.5.0
     922         *
     923         * @param browser
     924         */
     925        gallerySettings: function( browser ) {
     926                if ( ! this.get('displaySettings') ) {
     927                        return;
     928                }
     929
     930                var library = this.get('library');
     931
     932                if ( ! library || ! browser ) {
     933                        return;
     934                }
     935
     936                library.gallery = library.gallery || new Backbone.Model();
     937
     938                browser.sidebar.set({
     939                        gallery: new wp.media.view.Settings.Gallery({
     940                                controller: this,
     941                                model:      library.gallery,
     942                                priority:   40
     943                        })
     944                });
     945
     946                browser.toolbar.set( 'reverse', {
     947                        text:     l10n.reverseOrder,
     948                        priority: 80,
     949
     950                        click: function() {
     951                                library.reset( library.toArray().reverse() );
     952                        }
     953                });
     954        }
     955});
     956
     957module.exports = GalleryEdit;
     958
     959},{}],9:[function(require,module,exports){
     960/*globals wp, _ */
     961
     962/**
     963 * wp.media.controller.ImageDetails
     964 *
     965 * A state for editing the attachment display settings of an image that's been
     966 * inserted into the editor.
     967 *
     968 * @class
     969 * @augments wp.media.controller.State
     970 * @augments Backbone.Model
     971 *
     972 * @param {object}                    [attributes]                       The attributes hash passed to the state.
     973 * @param {string}                    [attributes.id=image-details]      Unique identifier.
     974 * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
     975 * @param {wp.media.model.Attachment} attributes.image                   The image's model.
     976 * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
     977 * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
     978 * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
     979 * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
     980 * @param {boolean}                   [attributes.editing=false]         Unused.
     981 * @param {int}                       [attributes.priority=60]           Unused.
     982 *
     983 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
     984 *       however this may not do anything.
     985 */
     986var State = wp.media.controller.State,
     987        Library = wp.media.controller.Library,
     988        l10n = wp.media.view.l10n,
     989        ImageDetails;
     990
     991ImageDetails = State.extend({
     992        defaults: _.defaults({
     993                id:       'image-details',
     994                title:    l10n.imageDetailsTitle,
     995                content:  'image-details',
     996                menu:     false,
     997                router:   false,
     998                toolbar:  'image-details',
     999                editing:  false,
     1000                priority: 60
     1001        }, Library.prototype.defaults ),
     1002
     1003        /**
     1004         * @since 3.9.0
     1005         *
     1006         * @param options Attributes
     1007         */
     1008        initialize: function( options ) {
     1009                this.image = options.image;
     1010                State.prototype.initialize.apply( this, arguments );
     1011        },
     1012
     1013        /**
     1014         * @since 3.9.0
     1015         */
     1016        activate: function() {
     1017                this.frame.modal.$el.addClass('image-details');
     1018        }
     1019});
     1020
     1021module.exports = ImageDetails;
     1022
     1023},{}],10:[function(require,module,exports){
     1024/*globals wp, _, Backbone */
     1025
     1026/**
     1027 * wp.media.controller.Library
     1028 *
     1029 * A state for choosing an attachment or group of attachments from the media library.
     1030 *
     1031 * @class
     1032 * @augments wp.media.controller.State
     1033 * @augments Backbone.Model
     1034 * @mixes media.selectionSync
     1035 *
     1036 * @param {object}                          [attributes]                         The attributes hash passed to the state.
     1037 * @param {string}                          [attributes.id=library]              Unique identifier.
     1038 * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
     1039 * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
     1040 *                                                                               If one is not supplied, a collection of all attachments will be created.
     1041 * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
     1042 *                                                                               If the 'selection' attribute is a plain JS object,
     1043 *                                                                               a Selection will be created using its values as the selection instance's `props` model.
     1044 *                                                                               Otherwise, it will copy the library's `props` model.
     1045 * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
     1046 * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
     1047 *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
     1048 * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
     1049 * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
     1050 * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
     1051 * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
     1052 * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
     1053 *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
     1054 * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1055 * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     1056 * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     1057 * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     1058 * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
     1059 */
     1060var l10n = wp.media.view.l10n,
     1061        getUserSetting = window.getUserSetting,
     1062        setUserSetting = window.setUserSetting,
     1063        Library;
     1064
     1065Library = wp.media.controller.State.extend({
     1066        defaults: {
     1067                id:                 'library',
     1068                title:              l10n.mediaLibraryTitle,
     1069                multiple:           false,
     1070                content:            'upload',
     1071                menu:               'default',
     1072                router:             'browse',
     1073                toolbar:            'select',
     1074                searchable:         true,
     1075                filterable:         false,
     1076                sortable:           true,
     1077                autoSelect:         true,
     1078                describe:           false,
     1079                contentUserSetting: true,
     1080                syncSelection:      true
     1081        },
     1082
     1083        /**
     1084         * If a library isn't provided, query all media items.
     1085         * If a selection instance isn't provided, create one.
     1086         *
     1087         * @since 3.5.0
     1088         */
     1089        initialize: function() {
     1090                var selection = this.get('selection'),
     1091                        props;
     1092
     1093                if ( ! this.get('library') ) {
     1094                        this.set( 'library', wp.media.query() );
     1095                }
     1096
     1097                if ( ! ( selection instanceof wp.media.model.Selection ) ) {
     1098                        props = selection;
     1099
     1100                        if ( ! props ) {
     1101                                props = this.get('library').props.toJSON();
     1102                                props = _.omit( props, 'orderby', 'query' );
     1103                        }
     1104
     1105                        this.set( 'selection', new wp.media.model.Selection( null, {
     1106                                multiple: this.get('multiple'),
     1107                                props: props
     1108                        }) );
     1109                }
     1110
     1111                this.resetDisplays();
     1112        },
     1113
     1114        /**
     1115         * @since 3.5.0
     1116         */
     1117        activate: function() {
     1118                this.syncSelection();
     1119
     1120                wp.Uploader.queue.on( 'add', this.uploading, this );
     1121
     1122                this.get('selection').on( 'add remove reset', this.refreshContent, this );
     1123
     1124                if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
     1125                        this.frame.on( 'content:activate', this.saveContentMode, this );
     1126                        this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
     1127                }
     1128        },
     1129
     1130        /**
     1131         * @since 3.5.0
     1132         */
     1133        deactivate: function() {
     1134                this.recordSelection();
     1135
     1136                this.frame.off( 'content:activate', this.saveContentMode, this );
     1137
     1138                // Unbind all event handlers that use this state as the context
     1139                // from the selection.
     1140                this.get('selection').off( null, null, this );
     1141
     1142                wp.Uploader.queue.off( null, null, this );
     1143        },
     1144
     1145        /**
     1146         * Reset the library to its initial state.
     1147         *
     1148         * @since 3.5.0
     1149         */
     1150        reset: function() {
     1151                this.get('selection').reset();
     1152                this.resetDisplays();
     1153                this.refreshContent();
     1154        },
     1155
     1156        /**
     1157         * Reset the attachment display settings defaults to the site options.
     1158         *
     1159         * If site options don't define them, fall back to a persistent user setting.
     1160         *
     1161         * @since 3.5.0
     1162         */
     1163        resetDisplays: function() {
     1164                var defaultProps = wp.media.view.settings.defaultProps;
     1165                this._displays = [];
     1166                this._defaultDisplaySettings = {
     1167                        align: defaultProps.align || getUserSetting( 'align', 'none' ),
     1168                        size:  defaultProps.size  || getUserSetting( 'imgsize', 'medium' ),
     1169                        link:  defaultProps.link  || getUserSetting( 'urlbutton', 'file' )
     1170                };
     1171        },
     1172
     1173        /**
     1174         * Create a model to represent display settings (alignment, etc.) for an attachment.
     1175         *
     1176         * @since 3.5.0
     1177         *
     1178         * @param {wp.media.model.Attachment} attachment
     1179         * @returns {Backbone.Model}
     1180         */
     1181        display: function( attachment ) {
     1182                var displays = this._displays;
     1183
     1184                if ( ! displays[ attachment.cid ] ) {
     1185                        displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
     1186                }
     1187                return displays[ attachment.cid ];
     1188        },
     1189
     1190        /**
     1191         * Given an attachment, create attachment display settings properties.
     1192         *
     1193         * @since 3.6.0
     1194         *
     1195         * @param {wp.media.model.Attachment} attachment
     1196         * @returns {Object}
     1197         */
     1198        defaultDisplaySettings: function( attachment ) {
     1199                var settings = this._defaultDisplaySettings;
     1200                if ( settings.canEmbed = this.canEmbed( attachment ) ) {
     1201                        settings.link = 'embed';
     1202                }
     1203                return settings;
     1204        },
     1205
     1206        /**
     1207         * Whether an attachment can be embedded (audio or video).
     1208         *
     1209         * @since 3.6.0
     1210         *
     1211         * @param {wp.media.model.Attachment} attachment
     1212         * @returns {Boolean}
     1213         */
     1214        canEmbed: function( attachment ) {
     1215                // If uploading, we know the filename but not the mime type.
     1216                if ( ! attachment.get('uploading') ) {
     1217                        var type = attachment.get('type');
     1218                        if ( type !== 'audio' && type !== 'video' ) {
     1219                                return false;
     1220                        }
     1221                }
     1222
     1223                return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
     1224        },
     1225
     1226
     1227        /**
     1228         * If the state is active, no items are selected, and the current
     1229         * content mode is not an option in the state's router (provided
     1230         * the state has a router), reset the content mode to the default.
     1231         *
     1232         * @since 3.5.0
     1233         */
     1234        refreshContent: function() {
     1235                var selection = this.get('selection'),
     1236                        frame = this.frame,
     1237                        router = frame.router.get(),
     1238                        mode = frame.content.mode();
     1239
     1240                if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
     1241                        this.frame.content.render( this.get('content') );
     1242                }
     1243        },
     1244
     1245        /**
     1246         * Callback handler when an attachment is uploaded.
     1247         *
     1248         * Switch to the Media Library if uploaded from the 'Upload Files' tab.
     1249         *
     1250         * Adds any uploading attachments to the selection.
     1251         *
     1252         * If the state only supports one attachment to be selected and multiple
     1253         * attachments are uploaded, the last attachment in the upload queue will
     1254         * be selected.
     1255         *
     1256         * @since 3.5.0
     1257         *
     1258         * @param {wp.media.model.Attachment} attachment
     1259         */
     1260        uploading: function( attachment ) {
     1261                var content = this.frame.content;
     1262
     1263                if ( 'upload' === content.mode() ) {
     1264                        this.frame.content.mode('browse');
     1265                }
     1266
     1267                if ( this.get( 'autoSelect' ) ) {
     1268                        this.get('selection').add( attachment );
     1269                        this.frame.trigger( 'library:selection:add' );
     1270                }
     1271        },
     1272
     1273        /**
     1274         * Persist the mode of the content region as a user setting.
     1275         *
     1276         * @since 3.5.0
     1277         */
     1278        saveContentMode: function() {
     1279                if ( 'browse' !== this.get('router') ) {
     1280                        return;
     1281                }
     1282
     1283                var mode = this.frame.content.mode(),
     1284                        view = this.frame.router.get();
     1285
     1286                if ( view && view.get( mode ) ) {
     1287                        setUserSetting( 'libraryContent', mode );
     1288                }
     1289        }
     1290});
     1291
     1292// Make selectionSync available on any Media Library state.
     1293_.extend( Library.prototype, wp.media.selectionSync );
     1294
     1295module.exports = Library;
     1296
     1297},{}],11:[function(require,module,exports){
     1298/*globals wp, _ */
     1299
     1300/**
     1301 * wp.media.controller.MediaLibrary
     1302 *
     1303 * @class
     1304 * @augments wp.media.controller.Library
     1305 * @augments wp.media.controller.State
     1306 * @augments Backbone.Model
     1307 */
     1308var Library = wp.media.controller.Library,
     1309        MediaLibrary;
     1310
     1311MediaLibrary = Library.extend({
     1312        defaults: _.defaults({
     1313                // Attachments browser defaults. @see media.view.AttachmentsBrowser
     1314                filterable:      'uploaded',
     1315
     1316                displaySettings: false,
     1317                priority:        80,
     1318                syncSelection:   false
     1319        }, Library.prototype.defaults ),
     1320
     1321        /**
     1322         * @since 3.9.0
     1323         *
     1324         * @param options
     1325         */
     1326        initialize: function( options ) {
     1327                this.media = options.media;
     1328                this.type = options.type;
     1329                this.set( 'library', wp.media.query({ type: this.type }) );
     1330
     1331                Library.prototype.initialize.apply( this, arguments );
     1332        },
     1333
     1334        /**
     1335         * @since 3.9.0
     1336         */
     1337        activate: function() {
     1338                // @todo this should use this.frame.
     1339                if ( wp.media.frame.lastMime ) {
     1340                        this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
     1341                        delete wp.media.frame.lastMime;
     1342                }
     1343                Library.prototype.activate.apply( this, arguments );
     1344        }
     1345});
     1346
     1347module.exports = MediaLibrary;
     1348
     1349},{}],12:[function(require,module,exports){
     1350/*globals Backbone, _ */
     1351
     1352/**
     1353 * wp.media.controller.Region
     1354 *
     1355 * A region is a persistent application layout area.
     1356 *
     1357 * A region assumes one mode at any time, and can be switched to another.
     1358 *
     1359 * When mode changes, events are triggered on the region's parent view.
     1360 * The parent view will listen to specific events and fill the region with an
     1361 * appropriate view depending on mode. For example, a frame listens for the
     1362 * 'browse' mode t be activated on the 'content' view and then fills the region
     1363 * with an AttachmentsBrowser view.
     1364 *
     1365 * @class
     1366 *
     1367 * @param {object}        options          Options hash for the region.
     1368 * @param {string}        options.id       Unique identifier for the region.
     1369 * @param {Backbone.View} options.view     A parent view the region exists within.
     1370 * @param {string}        options.selector jQuery selector for the region within the parent view.
     1371 */
     1372var Region = function( options ) {
     1373        _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
     1374};
     1375
     1376// Use Backbone's self-propagating `extend` inheritance method.
     1377Region.extend = Backbone.Model.extend;
     1378
     1379_.extend( Region.prototype, {
     1380        /**
     1381         * Activate a mode.
     1382         *
     1383         * @since 3.5.0
     1384         *
     1385         * @param {string} mode
     1386         *
     1387         * @fires this.view#{this.id}:activate:{this._mode}
     1388         * @fires this.view#{this.id}:activate
     1389         * @fires this.view#{this.id}:deactivate:{this._mode}
     1390         * @fires this.view#{this.id}:deactivate
     1391         *
     1392         * @returns {wp.media.controller.Region} Returns itself to allow chaining.
     1393         */
     1394        mode: function( mode ) {
     1395                if ( ! mode ) {
     1396                        return this._mode;
     1397                }
     1398                // Bail if we're trying to change to the current mode.
     1399                if ( mode === this._mode ) {
     1400                        return this;
     1401                }
     1402
     1403                /**
     1404                 * Region mode deactivation event.
     1405                 *
     1406                 * @event this.view#{this.id}:deactivate:{this._mode}
     1407                 * @event this.view#{this.id}:deactivate
     1408                 */
     1409                this.trigger('deactivate');
     1410
     1411                this._mode = mode;
     1412                this.render( mode );
     1413
     1414                /**
     1415                 * Region mode activation event.
     1416                 *
     1417                 * @event this.view#{this.id}:activate:{this._mode}
     1418                 * @event this.view#{this.id}:activate
     1419                 */
     1420                this.trigger('activate');
     1421                return this;
     1422        },
     1423        /**
     1424         * Render a mode.
     1425         *
     1426         * @since 3.5.0
     1427         *
     1428         * @param {string} mode
     1429         *
     1430         * @fires this.view#{this.id}:create:{this._mode}
     1431         * @fires this.view#{this.id}:create
     1432         * @fires this.view#{this.id}:render:{this._mode}
     1433         * @fires this.view#{this.id}:render
     1434         *
     1435         * @returns {wp.media.controller.Region} Returns itself to allow chaining
     1436         */
     1437        render: function( mode ) {
     1438                // If the mode isn't active, activate it.
     1439                if ( mode && mode !== this._mode ) {
     1440                        return this.mode( mode );
     1441                }
     1442
     1443                var set = { view: null },
     1444                        view;
     1445
     1446                /**
     1447                 * Create region view event.
     1448                 *
     1449                 * Region view creation takes place in an event callback on the frame.
     1450                 *
     1451                 * @event this.view#{this.id}:create:{this._mode}
     1452                 * @event this.view#{this.id}:create
     1453                 */
     1454                this.trigger( 'create', set );
     1455                view = set.view;
     1456
     1457                /**
     1458                 * Render region view event.
     1459                 *
     1460                 * Region view creation takes place in an event callback on the frame.
     1461                 *
     1462                 * @event this.view#{this.id}:create:{this._mode}
     1463                 * @event this.view#{this.id}:create
     1464                 */
     1465                this.trigger( 'render', view );
     1466                if ( view ) {
     1467                        this.set( view );
     1468                }
     1469                return this;
     1470        },
     1471
     1472        /**
     1473         * Get the region's view.
     1474         *
     1475         * @since 3.5.0
     1476         *
     1477         * @returns {wp.media.View}
     1478         */
     1479        get: function() {
     1480                return this.view.views.first( this.selector );
     1481        },
     1482
     1483        /**
     1484         * Set the region's view as a subview of the frame.
     1485         *
     1486         * @since 3.5.0
     1487         *
     1488         * @param {Array|Object} views
     1489         * @param {Object} [options={}]
     1490         * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
     1491         */
     1492        set: function( views, options ) {
     1493                if ( options ) {
     1494                        options.add = false;
     1495                }
     1496                return this.view.views.set( this.selector, views, options );
     1497        },
     1498
     1499        /**
     1500         * Trigger regional view events on the frame.
     1501         *
     1502         * @since 3.5.0
     1503         *
     1504         * @param {string} event
     1505         * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
     1506         */
     1507        trigger: function( event ) {
     1508                var base, args;
     1509
     1510                if ( ! this._mode ) {
     1511                        return;
     1512                }
     1513
     1514                args = _.toArray( arguments );
     1515                base = this.id + ':' + event;
     1516
     1517                // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
     1518                args[0] = base + ':' + this._mode;
     1519                this.view.trigger.apply( this.view, args );
     1520
     1521                // Trigger `{this.id}:{event}` event on the frame.
     1522                args[0] = base;
     1523                this.view.trigger.apply( this.view, args );
     1524                return this;
     1525        }
     1526});
     1527
     1528module.exports = Region;
     1529
     1530},{}],13:[function(require,module,exports){
     1531/*globals wp, _ */
     1532
     1533/**
     1534 * wp.media.controller.ReplaceImage
     1535 *
     1536 * A state for replacing an image.
     1537 *
     1538 * @class
     1539 * @augments wp.media.controller.Library
     1540 * @augments wp.media.controller.State
     1541 * @augments Backbone.Model
     1542 *
     1543 * @param {object}                     [attributes]                         The attributes hash passed to the state.
     1544 * @param {string}                     [attributes.id=replace-image]        Unique identifier.
     1545 * @param {string}                     [attributes.title=Replace Image]     Title for the state. Displays in the media menu and the frame's title region.
     1546 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
     1547 *                                                                          If one is not supplied, a collection of all images will be created.
     1548 * @param {boolean}                    [attributes.multiple=false]          Whether multi-select is enabled.
     1549 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
     1550 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
     1551 * @param {string}                     [attributes.menu=default]            Initial mode for the menu region.
     1552 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
     1553 * @param {string}                     [attributes.toolbar=replace]         Initial mode for the toolbar region.
     1554 * @param {int}                        [attributes.priority=60]             The priority for the state link in the media menu.
     1555 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
     1556 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
     1557 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
     1558 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
     1559 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
     1560 * @param {boolean}                    [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
     1561 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
     1562 * @param {boolean}                    [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
     1563 */
     1564var Library = wp.media.controller.Library,
     1565        l10n = wp.media.view.l10n,
     1566        ReplaceImage;
     1567
     1568ReplaceImage = Library.extend({
     1569        defaults: _.defaults({
     1570                id:            'replace-image',
     1571                title:         l10n.replaceImageTitle,
     1572                multiple:      false,
     1573                filterable:    'uploaded',
     1574                toolbar:       'replace',
     1575                menu:          false,
     1576                priority:      60,
     1577                syncSelection: true
     1578        }, Library.prototype.defaults ),
     1579
     1580        /**
     1581         * @since 3.9.0
     1582         *
     1583         * @param options
     1584         */
     1585        initialize: function( options ) {
     1586                var library, comparator;
     1587
     1588                this.image = options.image;
     1589                // If we haven't been provided a `library`, create a `Selection`.
     1590                if ( ! this.get('library') ) {
     1591                        this.set( 'library', wp.media.query({ type: 'image' }) );
     1592                }
     1593
     1594                Library.prototype.initialize.apply( this, arguments );
     1595
     1596                library    = this.get('library');
     1597                comparator = library.comparator;
     1598
     1599                // Overload the library's comparator to push items that are not in
     1600                // the mirrored query to the front of the aggregate collection.
     1601                library.comparator = function( a, b ) {
     1602                        var aInQuery = !! this.mirroring.get( a.cid ),
     1603                                bInQuery = !! this.mirroring.get( b.cid );
     1604
     1605                        if ( ! aInQuery && bInQuery ) {
     1606                                return -1;
     1607                        } else if ( aInQuery && ! bInQuery ) {
     1608                                return 1;
     1609                        } else {
     1610                                return comparator.apply( this, arguments );
     1611                        }
     1612                };
     1613
     1614                // Add all items in the selection to the library, so any featured
     1615                // images that are not initially loaded still appear.
     1616                library.observe( this.get('selection') );
     1617        },
     1618
     1619        /**
     1620         * @since 3.9.0
     1621         */
     1622        activate: function() {
     1623                this.updateSelection();
     1624                Library.prototype.activate.apply( this, arguments );
     1625        },
     1626
     1627        /**
     1628         * @since 3.9.0
     1629         */
     1630        updateSelection: function() {
     1631                var selection = this.get('selection'),
     1632                        attachment = this.image.attachment;
     1633
     1634                selection.reset( attachment ? [ attachment ] : [] );
     1635        }
     1636});
     1637
     1638module.exports = ReplaceImage;
     1639
     1640},{}],14:[function(require,module,exports){
     1641/*globals _, Backbone */
     1642
     1643/**
     1644 * wp.media.controller.StateMachine
     1645 *
     1646 * A state machine keeps track of state. It is in one state at a time,
     1647 * and can change from one state to another.
     1648 *
     1649 * States are stored as models in a Backbone collection.
     1650 *
     1651 * @since 3.5.0
     1652 *
     1653 * @class
     1654 * @augments Backbone.Model
     1655 * @mixin
     1656 * @mixes Backbone.Events
     1657 *
     1658 * @param {Array} states
     1659 */
     1660var StateMachine = function( states ) {
     1661        // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
     1662        this.states = new Backbone.Collection( states );
     1663};
     1664
     1665// Use Backbone's self-propagating `extend` inheritance method.
     1666StateMachine.extend = Backbone.Model.extend;
     1667
     1668_.extend( StateMachine.prototype, Backbone.Events, {
     1669        /**
     1670         * Fetch a state.
     1671         *
     1672         * If no `id` is provided, returns the active state.
     1673         *
     1674         * Implicitly creates states.
     1675         *
     1676         * Ensure that the `states` collection exists so the `StateMachine`
     1677         *   can be used as a mixin.
     1678         *
     1679         * @since 3.5.0
     1680         *
     1681         * @param {string} id
     1682         * @returns {wp.media.controller.State} Returns a State model
     1683         *   from the StateMachine collection
     1684         */
     1685        state: function( id ) {
     1686                this.states = this.states || new Backbone.Collection();
     1687
     1688                // Default to the active state.
     1689                id = id || this._state;
     1690
     1691                if ( id && ! this.states.get( id ) ) {
     1692                        this.states.add({ id: id });
     1693                }
     1694                return this.states.get( id );
     1695        },
     1696
     1697        /**
     1698         * Sets the active state.
     1699         *
     1700         * Bail if we're trying to select the current state, if we haven't
     1701         * created the `states` collection, or are trying to select a state
     1702         * that does not exist.
     1703         *
     1704         * @since 3.5.0
     1705         *
     1706         * @param {string} id
     1707         *
     1708         * @fires wp.media.controller.State#deactivate
     1709         * @fires wp.media.controller.State#activate
     1710         *
     1711         * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
     1712         */
     1713        setState: function( id ) {
     1714                var previous = this.state();
     1715
     1716                if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
     1717                        return this;
     1718                }
     1719
     1720                if ( previous ) {
     1721                        previous.trigger('deactivate');
     1722                        this._lastState = previous.id;
     1723                }
     1724
     1725                this._state = id;
     1726                this.state().trigger('activate');
     1727
     1728                return this;
     1729        },
     1730
     1731        /**
     1732         * Returns the previous active state.
     1733         *
     1734         * Call the `state()` method with no parameters to retrieve the current
     1735         * active state.
     1736         *
     1737         * @since 3.5.0
     1738         *
     1739         * @returns {wp.media.controller.State} Returns a State model
     1740         *    from the StateMachine collection
     1741         */
     1742        lastState: function() {
     1743                if ( this._lastState ) {
     1744                        return this.state( this._lastState );
     1745                }
     1746        }
     1747});
     1748
     1749// Map all event binding and triggering on a StateMachine to its `states` collection.
     1750_.each([ 'on', 'off', 'trigger' ], function( method ) {
     1751        /**
     1752         * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
     1753         */
     1754        StateMachine.prototype[ method ] = function() {
     1755                // Ensure that the `states` collection exists so the `StateMachine`
     1756                // can be used as a mixin.
     1757                this.states = this.states || new Backbone.Collection();
     1758                // Forward the method to the `states` collection.
     1759                this.states[ method ].apply( this.states, arguments );
     1760                return this;
     1761        };
     1762});
     1763
     1764module.exports = StateMachine;
     1765
     1766},{}],15:[function(require,module,exports){
     1767/*globals _, Backbone */
     1768
     1769/**
     1770 * wp.media.controller.State
     1771 *
     1772 * A state is a step in a workflow that when set will trigger the controllers
     1773 * for the regions to be updated as specified in the frame.
     1774 *
     1775 * A state has an event-driven lifecycle:
     1776 *
     1777 *     'ready'      triggers when a state is added to a state machine's collection.
     1778 *     'activate'   triggers when a state is activated by a state machine.
     1779 *     'deactivate' triggers when a state is deactivated by a state machine.
     1780 *     'reset'      is not triggered automatically. It should be invoked by the
     1781 *                  proper controller to reset the state to its default.
     1782 *
     1783 * @class
     1784 * @augments Backbone.Model
     1785 */
     1786var State = Backbone.Model.extend({
     1787        /**
     1788         * Constructor.
     1789         *
     1790         * @since 3.5.0
     1791         */
     1792        constructor: function() {
     1793                this.on( 'activate', this._preActivate, this );
     1794                this.on( 'activate', this.activate, this );
     1795                this.on( 'activate', this._postActivate, this );
     1796                this.on( 'deactivate', this._deactivate, this );
     1797                this.on( 'deactivate', this.deactivate, this );
     1798                this.on( 'reset', this.reset, this );
     1799                this.on( 'ready', this._ready, this );
     1800                this.on( 'ready', this.ready, this );
     1801                /**
     1802                 * Call parent constructor with passed arguments
     1803                 */
     1804                Backbone.Model.apply( this, arguments );
     1805                this.on( 'change:menu', this._updateMenu, this );
     1806        },
     1807        /**
     1808         * Ready event callback.
     1809         *
     1810         * @abstract
     1811         * @since 3.5.0
     1812         */
     1813        ready: function() {},
     1814
     1815        /**
     1816         * Activate event callback.
     1817         *
     1818         * @abstract
     1819         * @since 3.5.0
     1820         */
     1821        activate: function() {},
     1822
     1823        /**
     1824         * Deactivate event callback.
     1825         *
     1826         * @abstract
     1827         * @since 3.5.0
     1828         */
     1829        deactivate: function() {},
     1830
     1831        /**
     1832         * Reset event callback.
     1833         *
     1834         * @abstract
     1835         * @since 3.5.0
     1836         */
     1837        reset: function() {},
     1838
     1839        /**
     1840         * @access private
     1841         * @since 3.5.0
     1842         */
     1843        _ready: function() {
     1844                this._updateMenu();
     1845        },
     1846
     1847        /**
     1848         * @access private
     1849         * @since 3.5.0
     1850        */
     1851        _preActivate: function() {
     1852                this.active = true;
     1853        },
     1854
     1855        /**
     1856         * @access private
     1857         * @since 3.5.0
     1858         */
     1859        _postActivate: function() {
     1860                this.on( 'change:menu', this._menu, this );
     1861                this.on( 'change:titleMode', this._title, this );
     1862                this.on( 'change:content', this._content, this );
     1863                this.on( 'change:toolbar', this._toolbar, this );
     1864
     1865                this.frame.on( 'title:render:default', this._renderTitle, this );
     1866
     1867                this._title();
     1868                this._menu();
     1869                this._toolbar();
     1870                this._content();
     1871                this._router();
     1872        },
     1873
     1874        /**
     1875         * @access private
     1876         * @since 3.5.0
     1877         */
     1878        _deactivate: function() {
     1879                this.active = false;
     1880
     1881                this.frame.off( 'title:render:default', this._renderTitle, this );
     1882
     1883                this.off( 'change:menu', this._menu, this );
     1884                this.off( 'change:titleMode', this._title, this );
     1885                this.off( 'change:content', this._content, this );
     1886                this.off( 'change:toolbar', this._toolbar, this );
     1887        },
     1888
     1889        /**
     1890         * @access private
     1891         * @since 3.5.0
     1892         */
     1893        _title: function() {
     1894                this.frame.title.render( this.get('titleMode') || 'default' );
     1895        },
     1896
     1897        /**
     1898         * @access private
     1899         * @since 3.5.0
     1900         */
     1901        _renderTitle: function( view ) {
     1902                view.$el.text( this.get('title') || '' );
     1903        },
     1904
     1905        /**
     1906         * @access private
     1907         * @since 3.5.0
     1908         */
     1909        _router: function() {
     1910                var router = this.frame.router,
     1911                        mode = this.get('router'),
     1912                        view;
     1913
     1914                this.frame.$el.toggleClass( 'hide-router', ! mode );
     1915                if ( ! mode ) {
     1916                        return;
     1917                }
     1918
     1919                this.frame.router.render( mode );
     1920
     1921                view = router.get();
     1922                if ( view && view.select ) {
     1923                        view.select( this.frame.content.mode() );
     1924                }
     1925        },
     1926
     1927        /**
     1928         * @access private
     1929         * @since 3.5.0
     1930         */
     1931        _menu: function() {
     1932                var menu = this.frame.menu,
     1933                        mode = this.get('menu'),
     1934                        view;
     1935
     1936                this.frame.$el.toggleClass( 'hide-menu', ! mode );
     1937                if ( ! mode ) {
     1938                        return;
     1939                }
     1940
     1941                menu.mode( mode );
     1942
     1943                view = menu.get();
     1944                if ( view && view.select ) {
     1945                        view.select( this.id );
     1946                }
     1947        },
     1948
     1949        /**
     1950         * @access private
     1951         * @since 3.5.0
     1952         */
     1953        _updateMenu: function() {
     1954                var previous = this.previous('menu'),
     1955                        menu = this.get('menu');
     1956
     1957                if ( previous ) {
     1958                        this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
     1959                }
     1960
     1961                if ( menu ) {
     1962                        this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
     1963                }
     1964        },
     1965
     1966        /**
     1967         * Create a view in the media menu for the state.
     1968         *
     1969         * @access private
     1970         * @since 3.5.0
     1971         *
     1972         * @param {media.view.Menu} view The menu view.
     1973         */
     1974        _renderMenu: function( view ) {
     1975                var menuItem = this.get('menuItem'),
     1976                        title = this.get('title'),
     1977                        priority = this.get('priority');
     1978
     1979                if ( ! menuItem && title ) {
     1980                        menuItem = { text: title };
     1981
     1982                        if ( priority ) {
     1983                                menuItem.priority = priority;
     1984                        }
     1985                }
     1986
     1987                if ( ! menuItem ) {
     1988                        return;
     1989                }
     1990
     1991                view.set( this.id, menuItem );
     1992        }
     1993});
     1994
     1995_.each(['toolbar','content'], function( region ) {
     1996        /**
     1997         * @access private
     1998         */
     1999        State.prototype[ '_' + region ] = function() {
     2000                var mode = this.get( region );
     2001                if ( mode ) {
     2002                        this.frame[ region ].render( mode );
     2003                }
     2004        };
     2005});
     2006
     2007module.exports = State;
     2008
     2009},{}],16:[function(require,module,exports){
     2010/*globals _ */
     2011
     2012/**
     2013 * wp.media.selectionSync
     2014 *
     2015 * Sync an attachments selection in a state with another state.
     2016 *
     2017 * Allows for selecting multiple images in the Insert Media workflow, and then
     2018 * switching to the Insert Gallery workflow while preserving the attachments selection.
     2019 *
     2020 * @mixin
     2021 */
     2022var selectionSync = {
     2023        /**
     2024         * @since 3.5.0
     2025         */
     2026        syncSelection: function() {
     2027                var selection = this.get('selection'),
     2028                        manager = this.frame._selection;
     2029
     2030                if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     2031                        return;
     2032                }
     2033
     2034                // If the selection supports multiple items, validate the stored
     2035                // attachments based on the new selection's conditions. Record
     2036                // the attachments that are not included; we'll maintain a
     2037                // reference to those. Other attachments are considered in flux.
     2038                if ( selection.multiple ) {
     2039                        selection.reset( [], { silent: true });
     2040                        selection.validateAll( manager.attachments );
     2041                        manager.difference = _.difference( manager.attachments.models, selection.models );
     2042                }
     2043
     2044                // Sync the selection's single item with the master.
     2045                selection.single( manager.single );
     2046        },
     2047
     2048        /**
     2049         * Record the currently active attachments, which is a combination
     2050         * of the selection's attachments and the set of selected
     2051         * attachments that this specific selection considered invalid.
     2052         * Reset the difference and record the single attachment.
     2053         *
     2054         * @since 3.5.0
     2055         */
     2056        recordSelection: function() {
     2057                var selection = this.get('selection'),
     2058                        manager = this.frame._selection;
     2059
     2060                if ( ! this.get('syncSelection') || ! manager || ! selection ) {
     2061                        return;
     2062                }
     2063
     2064                if ( selection.multiple ) {
     2065                        manager.attachments.reset( selection.toArray().concat( manager.difference ) );
     2066                        manager.difference = [];
     2067                } else {
     2068                        manager.attachments.add( selection.toArray() );
     2069                }
     2070
     2071                manager.single = selection._single;
     2072        }
     2073};
     2074
     2075module.exports = selectionSync;
     2076
     2077},{}],17:[function(require,module,exports){
     2078/*globals wp, jQuery, _, Backbone */
     2079
     2080var media = wp.media,
     2081        $ = jQuery,
     2082        l10n;
     2083
     2084media.isTouchDevice = ( 'ontouchend' in document );
     2085
     2086// Link any localized strings.
     2087l10n = media.view.l10n = window._wpMediaViewsL10n || {};
     2088
     2089// Link any settings.
     2090media.view.settings = l10n.settings || {};
     2091delete l10n.settings;
     2092
     2093// Copy the `post` setting over to the model settings.
     2094media.model.settings.post = media.view.settings.post;
     2095
     2096// Check if the browser supports CSS 3.0 transitions
     2097$.support.transition = (function(){
     2098        var style = document.documentElement.style,
     2099                transitions = {
     2100                        WebkitTransition: 'webkitTransitionEnd',
     2101                        MozTransition:    'transitionend',
     2102                        OTransition:      'oTransitionEnd otransitionend',
     2103                        transition:       'transitionend'
     2104                }, transition;
     2105
     2106        transition = _.find( _.keys( transitions ), function( transition ) {
     2107                return ! _.isUndefined( style[ transition ] );
     2108        });
     2109
     2110        return transition && {
     2111                end: transitions[ transition ]
     2112        };
     2113}());
     2114
     2115/**
     2116 * A shared event bus used to provide events into
     2117 * the media workflows that 3rd-party devs can use to hook
     2118 * in.
     2119 */
     2120media.events = _.extend( {}, Backbone.Events );
     2121
     2122/**
     2123 * Makes it easier to bind events using transitions.
     2124 *
     2125 * @param {string} selector
     2126 * @param {Number} sensitivity
     2127 * @returns {Promise}
     2128 */
     2129media.transition = function( selector, sensitivity ) {
     2130        var deferred = $.Deferred();
     2131
     2132        sensitivity = sensitivity || 2000;
     2133
     2134        if ( $.support.transition ) {
     2135                if ( ! (selector instanceof $) ) {
     2136                        selector = $( selector );
     2137                }
     2138
     2139                // Resolve the deferred when the first element finishes animating.
     2140                selector.first().one( $.support.transition.end, deferred.resolve );
     2141
     2142                // Just in case the event doesn't trigger, fire a callback.
     2143                _.delay( deferred.resolve, sensitivity );
     2144
     2145        // Otherwise, execute on the spot.
     2146        } else {
     2147                deferred.resolve();
     2148        }
     2149
     2150        return deferred.promise();
     2151};
     2152
     2153media.controller.Region = require( './controllers/region.js' );
     2154media.controller.StateMachine = require( './controllers/state-machine.js' );
     2155media.controller.State = require( './controllers/state.js' );
     2156
     2157media.selectionSync = require( './utils/selection-sync.js' );
     2158media.controller.Library = require( './controllers/library.js' );
     2159media.controller.ImageDetails = require( './controllers/image-details.js' );
     2160media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
     2161media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
     2162media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
     2163media.controller.CollectionAdd = require( './controllers/collection-add.js' );
     2164media.controller.FeaturedImage = require( './controllers/featured-image.js' );
     2165media.controller.ReplaceImage = require( './controllers/replace-image.js' );
     2166media.controller.EditImage = require( './controllers/edit-image.js' );
     2167media.controller.MediaLibrary = require( './controllers/media-library.js' );
     2168media.controller.Embed = require( './controllers/embed.js' );
     2169media.controller.Cropper = require( './controllers/cropper.js' );
     2170
     2171media.View = require( './views/view.js' );
     2172media.view.Frame = require( './views/frame.js' );
     2173media.view.MediaFrame = require( './views/media-frame.js' );
     2174media.view.MediaFrame.Select = require( './views/frame/select.js' );
     2175media.view.MediaFrame.Post = require( './views/frame/post.js' );
     2176media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
     2177media.view.Modal = require( './views/modal.js' );
     2178media.view.FocusManager = require( './views/focus-manager.js' );
     2179media.view.UploaderWindow = require( './views/uploader/window.js' );
     2180media.view.EditorUploader = require( './views/uploader/editor.js' );
     2181media.view.UploaderInline = require( './views/uploader/inline.js' );
     2182media.view.UploaderStatus = require( './views/uploader/status.js' );
     2183media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
     2184media.view.Toolbar = require( './views/toolbar.js' );
     2185media.view.Toolbar.Select = require( './views/toolbar/select.js' );
     2186media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
     2187media.view.Button = require( './views/button.js' );
     2188media.view.ButtonGroup = require( './views/button-group.js' );
     2189media.view.PriorityList = require( './views/priority-list.js' );
     2190media.view.MenuItem = require( './views/menu-item.js' );
     2191media.view.Menu = require( './views/menu.js' );
     2192media.view.RouterItem = require( './views/router-item.js' );
     2193media.view.Router = require( './views/router.js' );
     2194media.view.Sidebar = require( './views/sidebar.js' );
     2195media.view.Attachment = require( './views/attachment.js' );
     2196media.view.Attachment.Library = require( './views/attachment/library.js' );
     2197media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
     2198media.view.Attachments = require( './views/attachments.js' );
     2199media.view.Search = require( './views/search.js' );
     2200media.view.AttachmentFilters = require( './views/attachment-filters.js' );
     2201media.view.DateFilter = require( './views/attachment-filters/date.js' );
     2202media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
     2203media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
     2204media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
     2205media.view.Selection = require( './views/selection.js' );
     2206media.view.Attachment.Selection = require( './views/attachment/selection.js' );
     2207media.view.Attachments.Selection = require( './views/attachments/selection.js' );
     2208media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
     2209media.view.Settings = require( './views/settings.js' );
     2210media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
     2211media.view.Settings.Gallery = require( './views/settings/gallery.js' );
     2212media.view.Settings.Playlist = require( './views/settings/playlist.js' );
     2213media.view.Attachment.Details = require( './views/attachment/details.js' );
     2214media.view.AttachmentCompat = require( './views/attachment-compat.js' );
     2215media.view.Iframe = require( './views/iframe.js' );
     2216media.view.Embed = require( './views/embed.js' );
     2217media.view.Label = require( './views/label.js' );
     2218media.view.EmbedUrl = require( './views/embed/url.js' );
     2219media.view.EmbedLink = require( './views/embed/link.js' );
     2220media.view.EmbedImage = require( './views/embed/image.js' );
     2221media.view.ImageDetails = require( './views/image-details.js' );
     2222media.view.Cropper = require( './views/cropper.js' );
     2223media.view.EditImage = require( './views/edit-image.js' );
     2224media.view.Spinner = require( './views/spinner.js' );
     2225
     2226},{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/edit-image.js":4,"./controllers/embed.js":5,"./controllers/featured-image.js":6,"./controllers/gallery-add.js":7,"./controllers/gallery-edit.js":8,"./controllers/image-details.js":9,"./controllers/library.js":10,"./controllers/media-library.js":11,"./controllers/region.js":12,"./controllers/replace-image.js":13,"./controllers/state-machine.js":14,"./controllers/state.js":15,"./utils/selection-sync.js":16,"./views/attachment-compat.js":18,"./views/attachment-filters.js":19,"./views/attachment-filters/all.js":20,"./views/attachment-filters/date.js":21,"./views/attachment-filters/uploaded.js":22,"./views/attachment.js":23,"./views/attachment/details.js":24,"./views/attachment/edit-library.js":25,"./views/attachment/edit-selection.js":26,"./views/attachment/library.js":27,"./views/attachment/selection.js":28,"./views/attachments.js":29,"./views/attachments/browser.js":30,"./views/attachments/selection.js":31,"./views/button-group.js":32,"./views/button.js":33,"./views/cropper.js":34,"./views/edit-image.js":35,"./views/embed.js":36,"./views/embed/image.js":37,"./views/embed/link.js":38,"./views/embed/url.js":39,"./views/focus-manager.js":40,"./views/frame.js":41,"./views/frame/image-details.js":42,"./views/frame/post.js":43,"./views/frame/select.js":44,"./views/iframe.js":45,"./views/image-details.js":46,"./views/label.js":47,"./views/media-frame.js":48,"./views/menu-item.js":49,"./views/menu.js":50,"./views/modal.js":51,"./views/priority-list.js":52,"./views/router-item.js":53,"./views/router.js":54,"./views/search.js":55,"./views/selection.js":56,"./views/settings.js":57,"./views/settings/attachment-display.js":58,"./views/settings/gallery.js":59,"./views/settings/playlist.js":60,"./views/sidebar.js":61,"./views/spinner.js":62,"./views/toolbar.js":63,"./views/toolbar/embed.js":64,"./views/toolbar/select.js":65,"./views/uploader/editor.js":66,"./views/uploader/inline.js":67,"./views/uploader/status-error.js":68,"./views/uploader/status.js":69,"./views/uploader/window.js":70,"./views/view.js":71}],18:[function(require,module,exports){
     2227/*globals _ */
     2228
     2229/**
     2230 * wp.media.view.AttachmentCompat
     2231 *
     2232 * A view to display fields added via the `attachment_fields_to_edit` filter.
     2233 *
     2234 * @class
     2235 * @augments wp.media.View
     2236 * @augments wp.Backbone.View
     2237 * @augments Backbone.View
     2238 */
     2239var View = wp.media.View,
     2240        AttachmentCompat;
     2241
     2242AttachmentCompat = View.extend({
     2243        tagName:   'form',
     2244        className: 'compat-item',
     2245
     2246        events: {
     2247                'submit':          'preventDefault',
     2248                'change input':    'save',
     2249                'change select':   'save',
     2250                'change textarea': 'save'
     2251        },
     2252
     2253        initialize: function() {
     2254                this.listenTo( this.model, 'change:compat', this.render );
     2255        },
     2256        /**
     2257         * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     2258         */
     2259        dispose: function() {
     2260                if ( this.$(':focus').length ) {
     2261                        this.save();
     2262                }
     2263                /**
     2264                 * call 'dispose' directly on the parent class
     2265                 */
     2266                return View.prototype.dispose.apply( this, arguments );
     2267        },
     2268        /**
     2269         * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
     2270         */
     2271        render: function() {
     2272                var compat = this.model.get('compat');
     2273                if ( ! compat || ! compat.item ) {
     2274                        return;
     2275                }
     2276
     2277                this.views.detach();
     2278                this.$el.html( compat.item );
     2279                this.views.render();
     2280                return this;
     2281        },
     2282        /**
     2283         * @param {Object} event
     2284         */
     2285        preventDefault: function( event ) {
     2286                event.preventDefault();
     2287        },
     2288        /**
     2289         * @param {Object} event
     2290         */
     2291        save: function( event ) {
     2292                var data = {};
     2293
     2294                if ( event ) {
     2295                        event.preventDefault();
     2296                }
     2297
     2298                _.each( this.$el.serializeArray(), function( pair ) {
     2299                        data[ pair.name ] = pair.value;
     2300                });
     2301
     2302                this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
     2303                this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
     2304        },
     2305
     2306        postSave: function() {
     2307                this.controller.trigger( 'attachment:compat:ready', ['ready'] );
     2308        }
     2309});
     2310
     2311module.exports = AttachmentCompat;
     2312
     2313},{}],19:[function(require,module,exports){
     2314/*globals _, jQuery */
     2315
     2316/**
     2317 * wp.media.view.AttachmentFilters
     2318 *
     2319 * @class
     2320 * @augments wp.media.View
     2321 * @augments wp.Backbone.View
     2322 * @augments Backbone.View
     2323 */
     2324var $ = jQuery,
     2325        AttachmentFilters;
     2326
     2327AttachmentFilters = wp.media.View.extend({
     2328        tagName:   'select',
     2329        className: 'attachment-filters',
     2330        id:        'media-attachment-filters',
     2331
     2332        events: {
     2333                change: 'change'
     2334        },
     2335
     2336        keys: [],
     2337
     2338        initialize: function() {
     2339                this.createFilters();
     2340                _.extend( this.filters, this.options.filters );
     2341
     2342                // Build `<option>` elements.
     2343                this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
     2344                        return {
     2345                                el: $( '<option></option>' ).val( value ).html( filter.text )[0],
     2346                                priority: filter.priority || 50
     2347                        };
     2348                }, this ).sortBy('priority').pluck('el').value() );
     2349
     2350                this.listenTo( this.model, 'change', this.select );
     2351                this.select();
     2352        },
     2353
     2354        /**
     2355         * @abstract
     2356         */
     2357        createFilters: function() {
     2358                this.filters = {};
     2359        },
     2360
     2361        /**
     2362         * When the selected filter changes, update the Attachment Query properties to match.
     2363         */
     2364        change: function() {
     2365                var filter = this.filters[ this.el.value ];
     2366                if ( filter ) {
     2367                        this.model.set( filter.props );
     2368                }
     2369        },
     2370
     2371        select: function() {
     2372                var model = this.model,
     2373                        value = 'all',
     2374                        props = model.toJSON();
     2375
     2376                _.find( this.filters, function( filter, id ) {
     2377                        var equal = _.all( filter.props, function( prop, key ) {
     2378                                return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
     2379                        });
     2380
     2381                        if ( equal ) {
     2382                                return value = id;
     2383                        }
     2384                });
     2385
     2386                this.$el.val( value );
     2387        }
     2388});
     2389
     2390module.exports = AttachmentFilters;
     2391
     2392},{}],20:[function(require,module,exports){
     2393/*globals wp */
     2394
     2395/**
     2396 * wp.media.view.AttachmentFilters.All
     2397 *
     2398 * @class
     2399 * @augments wp.media.view.AttachmentFilters
     2400 * @augments wp.media.View
     2401 * @augments wp.Backbone.View
     2402 * @augments Backbone.View
     2403 */
     2404var l10n = wp.media.view.l10n,
     2405        All;
     2406
     2407All = wp.media.view.AttachmentFilters.extend({
     2408        createFilters: function() {
     2409                var filters = {};
     2410
     2411                _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
     2412                        filters[ key ] = {
     2413                                text: text,
     2414                                props: {
     2415                                        status:  null,
     2416                                        type:    key,
     2417                                        uploadedTo: null,
     2418                                        orderby: 'date',
     2419                                        order:   'DESC'
     2420                                }
     2421                        };
     2422                });
     2423
     2424                filters.all = {
     2425                        text:  l10n.allMediaItems,
     2426                        props: {
     2427                                status:  null,
     2428                                type:    null,
     2429                                uploadedTo: null,
     2430                                orderby: 'date',
     2431                                order:   'DESC'
     2432                        },
     2433                        priority: 10
     2434                };
     2435
     2436                if ( wp.media.view.settings.post.id ) {
     2437                        filters.uploaded = {
     2438                                text:  l10n.uploadedToThisPost,
     2439                                props: {
     2440                                        status:  null,
     2441                                        type:    null,
     2442                                        uploadedTo: wp.media.view.settings.post.id,
     2443                                        orderby: 'menuOrder',
     2444                                        order:   'ASC'
     2445                                },
     2446                                priority: 20
     2447                        };
     2448                }
     2449
     2450                filters.unattached = {
     2451                        text:  l10n.unattached,
     2452                        props: {
     2453                                status:     null,
     2454                                uploadedTo: 0,
     2455                                type:       null,
     2456                                orderby:    'menuOrder',
     2457                                order:      'ASC'
     2458                        },
     2459                        priority: 50
     2460                };
     2461
     2462                if ( wp.media.view.settings.mediaTrash &&
     2463                        this.controller.isModeActive( 'grid' ) ) {
     2464
     2465                        filters.trash = {
     2466                                text:  l10n.trash,
     2467                                props: {
     2468                                        uploadedTo: null,
     2469                                        status:     'trash',
     2470                                        type:       null,
     2471                                        orderby:    'date',
     2472                                        order:      'DESC'
     2473                                },
     2474                                priority: 50
     2475                        };
     2476                }
     2477
     2478                this.filters = filters;
     2479        }
     2480});
     2481
     2482module.exports = All;
     2483
     2484},{}],21:[function(require,module,exports){
     2485/*globals wp, _ */
     2486
     2487/**
     2488 * A filter dropdown for month/dates.
     2489 *
     2490 * @class
     2491 * @augments wp.media.view.AttachmentFilters
     2492 * @augments wp.media.View
     2493 * @augments wp.Backbone.View
     2494 * @augments Backbone.View
     2495 */
     2496var l10n = wp.media.view.l10n,
     2497        DateFilter;
     2498
     2499DateFilter = wp.media.view.AttachmentFilters.extend({
     2500        id: 'media-attachment-date-filters',
     2501
     2502        createFilters: function() {
     2503                var filters = {};
     2504                _.each( wp.media.view.settings.months || {}, function( value, index ) {
     2505                        filters[ index ] = {
     2506                                text: value.text,
     2507                                props: {
     2508                                        year: value.year,
     2509                                        monthnum: value.month
     2510                                }
     2511                        };
     2512                });
     2513                filters.all = {
     2514                        text:  l10n.allDates,
     2515                        props: {
     2516                                monthnum: false,
     2517                                year:  false
     2518                        },
     2519                        priority: 10
     2520                };
     2521                this.filters = filters;
     2522        }
     2523});
     2524
     2525module.exports = DateFilter;
     2526
     2527},{}],22:[function(require,module,exports){
     2528/*globals wp */
     2529
     2530/**
     2531 * wp.media.view.AttachmentFilters.Uploaded
     2532 *
     2533 * @class
     2534 * @augments wp.media.view.AttachmentFilters
     2535 * @augments wp.media.View
     2536 * @augments wp.Backbone.View
     2537 * @augments Backbone.View
     2538 */
     2539var l10n = wp.media.view.l10n,
     2540        Uploaded;
     2541
     2542Uploaded = wp.media.view.AttachmentFilters.extend({
     2543        createFilters: function() {
     2544                var type = this.model.get('type'),
     2545                        types = wp.media.view.settings.mimeTypes,
     2546                        text;
     2547
     2548                if ( types && type ) {
     2549                        text = types[ type ];
     2550                }
     2551
     2552                this.filters = {
     2553                        all: {
     2554                                text:  text || l10n.allMediaItems,
     2555                                props: {
     2556                                        uploadedTo: null,
     2557                                        orderby: 'date',
     2558                                        order:   'DESC'
     2559                                },
     2560                                priority: 10
     2561                        },
     2562
     2563                        uploaded: {
     2564                                text:  l10n.uploadedToThisPost,
     2565                                props: {
     2566                                        uploadedTo: wp.media.view.settings.post.id,
     2567                                        orderby: 'menuOrder',
     2568                                        order:   'ASC'
     2569                                },
     2570                                priority: 20
     2571                        },
     2572
     2573                        unattached: {
     2574                                text:  l10n.unattached,
     2575                                props: {
     2576                                        uploadedTo: 0,
     2577                                        orderby: 'menuOrder',
     2578                                        order:   'ASC'
     2579                                },
     2580                                priority: 50
     2581                        }
     2582                };
     2583        }
     2584});
     2585
     2586module.exports = Uploaded;
     2587
     2588},{}],23:[function(require,module,exports){
     2589/*globals wp, _, jQuery */
     2590
     2591/**
     2592 * wp.media.view.Attachment
     2593 *
     2594 * @class
     2595 * @augments wp.media.View
     2596 * @augments wp.Backbone.View
     2597 * @augments Backbone.View
     2598 */
     2599var View = wp.media.View,
     2600        $ = jQuery,
     2601        Attachment;
     2602
     2603Attachment = View.extend({
     2604        tagName:   'li',
     2605        className: 'attachment',
     2606        template:  wp.template('attachment'),
     2607
     2608        attributes: function() {
     2609                return {
     2610                        'tabIndex':     0,
     2611                        'role':         'checkbox',
     2612                        'aria-label':   this.model.get( 'title' ),
     2613                        'aria-checked': false,
     2614                        'data-id':      this.model.get( 'id' )
     2615                };
     2616        },
     2617
     2618        events: {
     2619                'click .js--select-attachment':   'toggleSelectionHandler',
     2620                'change [data-setting]':          'updateSetting',
     2621                'change [data-setting] input':    'updateSetting',
     2622                'change [data-setting] select':   'updateSetting',
     2623                'change [data-setting] textarea': 'updateSetting',
     2624                'click .close':                   'removeFromLibrary',
     2625                'click .check':                   'checkClickHandler',
     2626                'click a':                        'preventDefault',
     2627                'keydown .close':                 'removeFromLibrary',
     2628                'keydown':                        'toggleSelectionHandler'
     2629        },
     2630
     2631        buttons: {},
     2632
     2633        initialize: function() {
     2634                var selection = this.options.selection,
     2635                        options = _.defaults( this.options, {
     2636                                rerenderOnModelChange: true
     2637                        } );
     2638
     2639                if ( options.rerenderOnModelChange ) {
     2640                        this.listenTo( this.model, 'change', this.render );
     2641                } else {
     2642                        this.listenTo( this.model, 'change:percent', this.progress );
     2643                }
     2644                this.listenTo( this.model, 'change:title', this._syncTitle );
     2645                this.listenTo( this.model, 'change:caption', this._syncCaption );
     2646                this.listenTo( this.model, 'change:artist', this._syncArtist );
     2647                this.listenTo( this.model, 'change:album', this._syncAlbum );
     2648
     2649                // Update the selection.
     2650                this.listenTo( this.model, 'add', this.select );
     2651                this.listenTo( this.model, 'remove', this.deselect );
     2652                if ( selection ) {
     2653                        selection.on( 'reset', this.updateSelect, this );
     2654                        // Update the model's details view.
     2655                        this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
     2656                        this.details( this.model, this.controller.state().get('selection') );
     2657                }
     2658
     2659                this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
     2660        },
     2661        /**
     2662         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     2663         */
     2664        dispose: function() {
     2665                var selection = this.options.selection;
     2666
     2667                // Make sure all settings are saved before removing the view.
     2668                this.updateAll();
     2669
     2670                if ( selection ) {
     2671                        selection.off( null, null, this );
     2672                }
     2673                /**
     2674                 * call 'dispose' directly on the parent class
     2675                 */
     2676                View.prototype.dispose.apply( this, arguments );
     2677                return this;
     2678        },
     2679        /**
     2680         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     2681         */
     2682        render: function() {
     2683                var options = _.defaults( this.model.toJSON(), {
     2684                                orientation:   'landscape',
     2685                                uploading:     false,
     2686                                type:          '',
     2687                                subtype:       '',
     2688                                icon:          '',
     2689                                filename:      '',
     2690                                caption:       '',
     2691                                title:         '',
     2692                                dateFormatted: '',
     2693                                width:         '',
     2694                                height:        '',
     2695                                compat:        false,
     2696                                alt:           '',
     2697                                description:   ''
     2698                        }, this.options );
     2699
     2700                options.buttons  = this.buttons;
     2701                options.describe = this.controller.state().get('describe');
     2702
     2703                if ( 'image' === options.type ) {
     2704                        options.size = this.imageSize();
     2705                }
     2706
     2707                options.can = {};
     2708                if ( options.nonces ) {
     2709                        options.can.remove = !! options.nonces['delete'];
     2710                        options.can.save = !! options.nonces.update;
     2711                }
     2712
     2713                if ( this.controller.state().get('allowLocalEdits') ) {
     2714                        options.allowLocalEdits = true;
     2715                }
     2716
     2717                if ( options.uploading && ! options.percent ) {
     2718                        options.percent = 0;
     2719                }
     2720
     2721                this.views.detach();
     2722                this.$el.html( this.template( options ) );
     2723
     2724                this.$el.toggleClass( 'uploading', options.uploading );
     2725
     2726                if ( options.uploading ) {
     2727                        this.$bar = this.$('.media-progress-bar div');
     2728                } else {
     2729                        delete this.$bar;
     2730                }
     2731
     2732                // Check if the model is selected.
     2733                this.updateSelect();
     2734
     2735                // Update the save status.
     2736                this.updateSave();
     2737
     2738                this.views.render();
     2739
     2740                return this;
     2741        },
     2742
     2743        progress: function() {
     2744                if ( this.$bar && this.$bar.length ) {
     2745                        this.$bar.width( this.model.get('percent') + '%' );
     2746                }
     2747        },
     2748
     2749        /**
     2750         * @param {Object} event
     2751         */
     2752        toggleSelectionHandler: function( event ) {
     2753                var method;
     2754
     2755                // Don't do anything inside inputs.
     2756                if ( 'INPUT' === event.target.nodeName ) {
     2757                        return;
     2758                }
     2759
     2760                // Catch arrow events
     2761                if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     2762                        this.controller.trigger( 'attachment:keydown:arrow', event );
     2763                        return;
     2764                }
     2765
     2766                // Catch enter and space events
     2767                if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     2768                        return;
     2769                }
     2770
     2771                event.preventDefault();
     2772
     2773                // In the grid view, bubble up an edit:attachment event to the controller.
     2774                if ( this.controller.isModeActive( 'grid' ) ) {
     2775                        if ( this.controller.isModeActive( 'edit' ) ) {
     2776                                // Pass the current target to restore focus when closing
     2777                                this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
     2778                                return;
     2779                        }
     2780
     2781                        if ( this.controller.isModeActive( 'select' ) ) {
     2782                                method = 'toggle';
     2783                        }
     2784                }
     2785
     2786                if ( event.shiftKey ) {
     2787                        method = 'between';
     2788                } else if ( event.ctrlKey || event.metaKey ) {
     2789                        method = 'toggle';
     2790                }
     2791
     2792                this.toggleSelection({
     2793                        method: method
     2794                });
     2795
     2796                this.controller.trigger( 'selection:toggle' );
     2797        },
     2798        /**
     2799         * @param {Object} options
     2800         */
     2801        toggleSelection: function( options ) {
     2802                var collection = this.collection,
     2803                        selection = this.options.selection,
     2804                        model = this.model,
     2805                        method = options && options.method,
     2806                        single, models, singleIndex, modelIndex;
     2807
     2808                if ( ! selection ) {
     2809                        return;
     2810                }
     2811
     2812                single = selection.single();
     2813                method = _.isUndefined( method ) ? selection.multiple : method;
     2814
     2815                // If the `method` is set to `between`, select all models that
     2816                // exist between the current and the selected model.
     2817                if ( 'between' === method && single && selection.multiple ) {
     2818                        // If the models are the same, short-circuit.
     2819                        if ( single === model ) {
     2820                                return;
     2821                        }
     2822
     2823                        singleIndex = collection.indexOf( single );
     2824                        modelIndex  = collection.indexOf( this.model );
     2825
     2826                        if ( singleIndex < modelIndex ) {
     2827                                models = collection.models.slice( singleIndex, modelIndex + 1 );
     2828                        } else {
     2829                                models = collection.models.slice( modelIndex, singleIndex + 1 );
     2830                        }
     2831
     2832                        selection.add( models );
     2833                        selection.single( model );
     2834                        return;
     2835
     2836                // If the `method` is set to `toggle`, just flip the selection
     2837                // status, regardless of whether the model is the single model.
     2838                } else if ( 'toggle' === method ) {
     2839                        selection[ this.selected() ? 'remove' : 'add' ]( model );
     2840                        selection.single( model );
     2841                        return;
     2842                } else if ( 'add' === method ) {
     2843                        selection.add( model );
     2844                        selection.single( model );
     2845                        return;
     2846                }
     2847
     2848                // Fixes bug that loses focus when selecting a featured image
     2849                if ( ! method ) {
     2850                        method = 'add';
     2851                }
     2852
     2853                if ( method !== 'add' ) {
     2854                        method = 'reset';
     2855                }
     2856
     2857                if ( this.selected() ) {
     2858                        // If the model is the single model, remove it.
     2859                        // If it is not the same as the single model,
     2860                        // it now becomes the single model.
     2861                        selection[ single === model ? 'remove' : 'single' ]( model );
     2862                } else {
     2863                        // If the model is not selected, run the `method` on the
     2864                        // selection. By default, we `reset` the selection, but the
     2865                        // `method` can be set to `add` the model to the selection.
     2866                        selection[ method ]( model );
     2867                        selection.single( model );
     2868                }
     2869        },
     2870
     2871        updateSelect: function() {
     2872                this[ this.selected() ? 'select' : 'deselect' ]();
     2873        },
     2874        /**
     2875         * @returns {unresolved|Boolean}
     2876         */
     2877        selected: function() {
     2878                var selection = this.options.selection;
     2879                if ( selection ) {
     2880                        return !! selection.get( this.model.cid );
     2881                }
     2882        },
     2883        /**
     2884         * @param {Backbone.Model} model
     2885         * @param {Backbone.Collection} collection
     2886         */
     2887        select: function( model, collection ) {
     2888                var selection = this.options.selection,
     2889                        controller = this.controller;
     2890
     2891                // Check if a selection exists and if it's the collection provided.
     2892                // If they're not the same collection, bail; we're in another
     2893                // selection's event loop.
     2894                if ( ! selection || ( collection && collection !== selection ) ) {
     2895                        return;
     2896                }
     2897
     2898                // Bail if the model is already selected.
     2899                if ( this.$el.hasClass( 'selected' ) ) {
     2900                        return;
     2901                }
     2902
     2903                // Add 'selected' class to model, set aria-checked to true.
     2904                this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
     2905                //  Make the checkbox tabable, except in media grid (bulk select mode).
     2906                if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
     2907                        this.$( '.check' ).attr( 'tabindex', '0' );
     2908                }
     2909        },
     2910        /**
     2911         * @param {Backbone.Model} model
     2912         * @param {Backbone.Collection} collection
     2913         */
     2914        deselect: function( model, collection ) {
     2915                var selection = this.options.selection;
     2916
     2917                // Check if a selection exists and if it's the collection provided.
     2918                // If they're not the same collection, bail; we're in another
     2919                // selection's event loop.
     2920                if ( ! selection || ( collection && collection !== selection ) ) {
     2921                        return;
     2922                }
     2923                this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
     2924                        .find( '.check' ).attr( 'tabindex', '-1' );
     2925        },
     2926        /**
     2927         * @param {Backbone.Model} model
     2928         * @param {Backbone.Collection} collection
     2929         */
     2930        details: function( model, collection ) {
     2931                var selection = this.options.selection,
     2932                        details;
     2933
     2934                if ( selection !== collection ) {
     2935                        return;
     2936                }
     2937
     2938                details = selection.single();
     2939                this.$el.toggleClass( 'details', details === this.model );
     2940        },
     2941        /**
     2942         * @param {Object} event
     2943         */
     2944        preventDefault: function( event ) {
     2945                event.preventDefault();
     2946        },
     2947        /**
     2948         * @param {string} size
     2949         * @returns {Object}
     2950         */
     2951        imageSize: function( size ) {
     2952                var sizes = this.model.get('sizes'), matched = false;
     2953
     2954                size = size || 'medium';
     2955
     2956                // Use the provided image size if possible.
     2957                if ( sizes ) {
     2958                        if ( sizes[ size ] ) {
     2959                                matched = sizes[ size ];
     2960                        } else if ( sizes.large ) {
     2961                                matched = sizes.large;
     2962                        } else if ( sizes.thumbnail ) {
     2963                                matched = sizes.thumbnail;
     2964                        } else if ( sizes.full ) {
     2965                                matched = sizes.full;
     2966                        }
     2967
     2968                        if ( matched ) {
     2969                                return _.clone( matched );
     2970                        }
     2971                }
     2972
     2973                return {
     2974                        url:         this.model.get('url'),
     2975                        width:       this.model.get('width'),
     2976                        height:      this.model.get('height'),
     2977                        orientation: this.model.get('orientation')
     2978                };
     2979        },
     2980        /**
     2981         * @param {Object} event
     2982         */
     2983        updateSetting: function( event ) {
     2984                var $setting = $( event.target ).closest('[data-setting]'),
     2985                        setting, value;
     2986
     2987                if ( ! $setting.length ) {
     2988                        return;
     2989                }
     2990
     2991                setting = $setting.data('setting');
     2992                value   = event.target.value;
     2993
     2994                if ( this.model.get( setting ) !== value ) {
     2995                        this.save( setting, value );
     2996                }
     2997        },
     2998
     2999        /**
     3000         * Pass all the arguments to the model's save method.
     3001         *
     3002         * Records the aggregate status of all save requests and updates the
     3003         * view's classes accordingly.
     3004         */
     3005        save: function() {
     3006                var view = this,
     3007                        save = this._save = this._save || { status: 'ready' },
     3008                        request = this.model.save.apply( this.model, arguments ),
     3009                        requests = save.requests ? $.when( request, save.requests ) : request;
     3010
     3011                // If we're waiting to remove 'Saved.', stop.
     3012                if ( save.savedTimer ) {
     3013                        clearTimeout( save.savedTimer );
     3014                }
     3015
     3016                this.updateSave('waiting');
     3017                save.requests = requests;
     3018                requests.always( function() {
     3019                        // If we've performed another request since this one, bail.
     3020                        if ( save.requests !== requests ) {
     3021                                return;
     3022                        }
     3023
     3024                        view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
     3025                        save.savedTimer = setTimeout( function() {
     3026                                view.updateSave('ready');
     3027                                delete save.savedTimer;
     3028                        }, 2000 );
     3029                });
     3030        },
     3031        /**
     3032         * @param {string} status
     3033         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     3034         */
     3035        updateSave: function( status ) {
     3036                var save = this._save = this._save || { status: 'ready' };
     3037
     3038                if ( status && status !== save.status ) {
     3039                        this.$el.removeClass( 'save-' + save.status );
     3040                        save.status = status;
     3041                }
     3042
     3043                this.$el.addClass( 'save-' + save.status );
     3044                return this;
     3045        },
     3046
     3047        updateAll: function() {
     3048                var $settings = this.$('[data-setting]'),
     3049                        model = this.model,
     3050                        changed;
     3051
     3052                changed = _.chain( $settings ).map( function( el ) {
     3053                        var $input = $('input, textarea, select, [value]', el ),
     3054                                setting, value;
     3055
     3056                        if ( ! $input.length ) {
     3057                                return;
     3058                        }
     3059
     3060                        setting = $(el).data('setting');
     3061                        value = $input.val();
     3062
     3063                        // Record the value if it changed.
     3064                        if ( model.get( setting ) !== value ) {
     3065                                return [ setting, value ];
     3066                        }
     3067                }).compact().object().value();
     3068
     3069                if ( ! _.isEmpty( changed ) ) {
     3070                        model.save( changed );
     3071                }
     3072        },
     3073        /**
     3074         * @param {Object} event
     3075         */
     3076        removeFromLibrary: function( event ) {
     3077                // Catch enter and space events
     3078                if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
     3079                        return;
     3080                }
     3081
     3082                // Stop propagation so the model isn't selected.
     3083                event.stopPropagation();
     3084
     3085                this.collection.remove( this.model );
     3086        },
     3087
     3088        /**
     3089         * Add the model if it isn't in the selection, if it is in the selection,
     3090         * remove it.
     3091         *
     3092         * @param  {[type]} event [description]
     3093         * @return {[type]}       [description]
     3094         */
     3095        checkClickHandler: function ( event ) {
     3096                var selection = this.options.selection;
     3097                if ( ! selection ) {
     3098                        return;
     3099                }
     3100                event.stopPropagation();
     3101                if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
     3102                        selection.remove( this.model );
     3103                        // Move focus back to the attachment tile (from the check).
     3104                        this.$el.focus();
     3105                } else {
     3106                        selection.add( this.model );
     3107                }
     3108        }
     3109});
     3110
     3111// Ensure settings remain in sync between attachment views.
     3112_.each({
     3113        caption: '_syncCaption',
     3114        title:   '_syncTitle',
     3115        artist:  '_syncArtist',
     3116        album:   '_syncAlbum'
     3117}, function( method, setting ) {
     3118        /**
     3119         * @param {Backbone.Model} model
     3120         * @param {string} value
     3121         * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     3122         */
     3123        Attachment.prototype[ method ] = function( model, value ) {
     3124                var $setting = this.$('[data-setting="' + setting + '"]');
     3125
     3126                if ( ! $setting.length ) {
     3127                        return this;
     3128                }
     3129
     3130                // If the updated value is in sync with the value in the DOM, there
     3131                // is no need to re-render. If we're currently editing the value,
     3132                // it will automatically be in sync, suppressing the re-render for
     3133                // the view we're editing, while updating any others.
     3134                if ( value === $setting.find('input, textarea, select, [value]').val() ) {
     3135                        return this;
     3136                }
     3137
     3138                return this.render();
     3139        };
     3140});
     3141
     3142module.exports = Attachment;
     3143
     3144},{}],24:[function(require,module,exports){
     3145/*globals wp, _ */
     3146
     3147/**
     3148 * wp.media.view.Attachment.Details
     3149 *
     3150 * @class
     3151 * @augments wp.media.view.Attachment
     3152 * @augments wp.media.View
     3153 * @augments wp.Backbone.View
     3154 * @augments Backbone.View
     3155 */
     3156var Attachment = wp.media.view.Attachment,
     3157        l10n = wp.media.view.l10n,
     3158        Details;
     3159
     3160Details = Attachment.extend({
     3161        tagName:   'div',
     3162        className: 'attachment-details',
     3163        template:  wp.template('attachment-details'),
     3164
     3165        attributes: function() {
     3166                return {
     3167                        'tabIndex':     0,
     3168                        'data-id':      this.model.get( 'id' )
     3169                };
     3170        },
     3171
     3172        events: {
     3173                'change [data-setting]':          'updateSetting',
     3174                'change [data-setting] input':    'updateSetting',
     3175                'change [data-setting] select':   'updateSetting',
     3176                'change [data-setting] textarea': 'updateSetting',
     3177                'click .delete-attachment':       'deleteAttachment',
     3178                'click .trash-attachment':        'trashAttachment',
     3179                'click .untrash-attachment':      'untrashAttachment',
     3180                'click .edit-attachment':         'editAttachment',
     3181                'click .refresh-attachment':      'refreshAttachment',
     3182                'keydown':                        'toggleSelectionHandler'
     3183        },
     3184
     3185        initialize: function() {
     3186                this.options = _.defaults( this.options, {
     3187                        rerenderOnModelChange: false
     3188                });
     3189
     3190                this.on( 'ready', this.initialFocus );
     3191                // Call 'initialize' directly on the parent class.
     3192                Attachment.prototype.initialize.apply( this, arguments );
     3193        },
     3194
     3195        initialFocus: function() {
     3196                if ( ! wp.media.isTouchDevice ) {
     3197                        this.$( ':input' ).eq( 0 ).focus();
     3198                }
     3199        },
     3200        /**
     3201         * @param {Object} event
     3202         */
     3203        deleteAttachment: function( event ) {
     3204                event.preventDefault();
     3205
     3206                if ( window.confirm( l10n.warnDelete ) ) {
     3207                        this.model.destroy();
     3208                        // Keep focus inside media modal
     3209                        // after image is deleted
     3210                        this.controller.modal.focusManager.focus();
     3211                }
     3212        },
     3213        /**
     3214         * @param {Object} event
     3215         */
     3216        trashAttachment: function( event ) {
     3217                var library = this.controller.library;
     3218                event.preventDefault();
     3219
     3220                if ( wp.media.view.settings.mediaTrash &&
     3221                        'edit-metadata' === this.controller.content.mode() ) {
     3222
     3223                        this.model.set( 'status', 'trash' );
     3224                        this.model.save().done( function() {
     3225                                library._requery( true );
     3226                        } );
     3227                }  else {
     3228                        this.model.destroy();
     3229                }
     3230        },
     3231        /**
     3232         * @param {Object} event
     3233         */
     3234        untrashAttachment: function( event ) {
     3235                var library = this.controller.library;
     3236                event.preventDefault();
     3237
     3238                this.model.set( 'status', 'inherit' );
     3239                this.model.save().done( function() {
     3240                        library._requery( true );
     3241                } );
     3242        },
     3243        /**
     3244         * @param {Object} event
     3245         */
     3246        editAttachment: function( event ) {
     3247                var editState = this.controller.states.get( 'edit-image' );
     3248                if ( window.imageEdit && editState ) {
     3249                        event.preventDefault();
     3250
     3251                        editState.set( 'image', this.model );
     3252                        this.controller.setState( 'edit-image' );
     3253                } else {
     3254                        this.$el.addClass('needs-refresh');
     3255                }
     3256        },
     3257        /**
     3258         * @param {Object} event
     3259         */
     3260        refreshAttachment: function( event ) {
     3261                this.$el.removeClass('needs-refresh');
     3262                event.preventDefault();
     3263                this.model.fetch();
     3264        },
     3265        /**
     3266         * When reverse tabbing(shift+tab) out of the right details panel, deliver
     3267         * the focus to the item in the list that was being edited.
     3268         *
     3269         * @param {Object} event
     3270         */
     3271        toggleSelectionHandler: function( event ) {
     3272                if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
     3273                        this.controller.trigger( 'attachment:details:shift-tab', event );
     3274                        return false;
     3275                }
     3276
     3277                if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
     3278                        this.controller.trigger( 'attachment:keydown:arrow', event );
     3279                        return;
     3280                }
     3281        }
     3282});
     3283
     3284module.exports = Details;
     3285
     3286},{}],25:[function(require,module,exports){
     3287/*globals wp */
     3288
     3289/**
     3290 * wp.media.view.Attachment.EditLibrary
     3291 *
     3292 * @class
     3293 * @augments wp.media.view.Attachment
     3294 * @augments wp.media.View
     3295 * @augments wp.Backbone.View
     3296 * @augments Backbone.View
     3297 */
     3298var EditLibrary = wp.media.view.Attachment.extend({
     3299        buttons: {
     3300                close: true
     3301        }
     3302});
     3303
     3304module.exports = EditLibrary;
     3305
     3306},{}],26:[function(require,module,exports){
     3307/*globals wp */
     3308
     3309/**
     3310 * wp.media.view.Attachments.EditSelection
     3311 *
     3312 * @class
     3313 * @augments wp.media.view.Attachment.Selection
     3314 * @augments wp.media.view.Attachment
     3315 * @augments wp.media.View
     3316 * @augments wp.Backbone.View
     3317 * @augments Backbone.View
     3318 */
     3319var EditSelection = wp.media.view.Attachment.Selection.extend({
     3320        buttons: {
     3321                close: true
     3322        }
     3323});
     3324
     3325module.exports = EditSelection;
     3326
     3327},{}],27:[function(require,module,exports){
     3328/*globals wp */
     3329
     3330/**
     3331 * wp.media.view.Attachment.Library
     3332 *
     3333 * @class
     3334 * @augments wp.media.view.Attachment
     3335 * @augments wp.media.View
     3336 * @augments wp.Backbone.View
     3337 * @augments Backbone.View
     3338 */
     3339var Library = wp.media.view.Attachment.extend({
     3340        buttons: {
     3341                check: true
     3342        }
     3343});
     3344
     3345module.exports = Library;
     3346
     3347},{}],28:[function(require,module,exports){
     3348/*globals wp */
     3349
     3350/**
     3351 * wp.media.view.Attachment.Selection
     3352 *
     3353 * @class
     3354 * @augments wp.media.view.Attachment
     3355 * @augments wp.media.View
     3356 * @augments wp.Backbone.View
     3357 * @augments Backbone.View
     3358 */
     3359var Selection = wp.media.view.Attachment.extend({
     3360        className: 'attachment selection',
     3361
     3362        // On click, just select the model, instead of removing the model from
     3363        // the selection.
     3364        toggleSelection: function() {
     3365                this.options.selection.single( this.model );
     3366        }
     3367});
     3368
     3369module.exports = Selection;
     3370
     3371},{}],29:[function(require,module,exports){
     3372/*globals wp, _, jQuery */
     3373
     3374/**
     3375 * wp.media.view.Attachments
     3376 *
     3377 * @class
     3378 * @augments wp.media.View
     3379 * @augments wp.Backbone.View
     3380 * @augments Backbone.View
     3381 */
     3382var View = wp.media.View,
     3383        $ = jQuery,
     3384        Attachments;
     3385
     3386Attachments = View.extend({
     3387        tagName:   'ul',
     3388        className: 'attachments',
     3389
     3390        attributes: {
     3391                tabIndex: -1
     3392        },
     3393
     3394        initialize: function() {
     3395                this.el.id = _.uniqueId('__attachments-view-');
     3396
     3397                _.defaults( this.options, {
     3398                        refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
     3399                        refreshThreshold:   3,
     3400                        AttachmentView:     wp.media.view.Attachment,
     3401                        sortable:           false,
     3402                        resize:             true,
     3403                        idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
     3404                });
     3405
     3406                this._viewsByCid = {};
     3407                this.$window = $( window );
     3408                this.resizeEvent = 'resize.media-modal-columns';
     3409
     3410                this.collection.on( 'add', function( attachment ) {
     3411                        this.views.add( this.createAttachmentView( attachment ), {
     3412                                at: this.collection.indexOf( attachment )
     3413                        });
     3414                }, this );
     3415
     3416                this.collection.on( 'remove', function( attachment ) {
     3417                        var view = this._viewsByCid[ attachment.cid ];
     3418                        delete this._viewsByCid[ attachment.cid ];
     3419
     3420                        if ( view ) {
     3421                                view.remove();
     3422                        }
     3423                }, this );
     3424
     3425                this.collection.on( 'reset', this.render, this );
     3426
     3427                this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
     3428
     3429                // Throttle the scroll handler and bind this.
     3430                this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
     3431
     3432                this.options.scrollElement = this.options.scrollElement || this.el;
     3433                $( this.options.scrollElement ).on( 'scroll', this.scroll );
     3434
     3435                this.initSortable();
     3436
     3437                _.bindAll( this, 'setColumns' );
     3438
     3439                if ( this.options.resize ) {
     3440                        this.on( 'ready', this.bindEvents );
     3441                        this.controller.on( 'open', this.setColumns );
     3442
     3443                        // Call this.setColumns() after this view has been rendered in the DOM so
     3444                        // attachments get proper width applied.
     3445                        _.defer( this.setColumns, this );
     3446                }
     3447        },
     3448
     3449        bindEvents: function() {
     3450                this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
     3451        },
     3452
     3453        attachmentFocus: function() {
     3454                this.$( 'li:first' ).focus();
     3455        },
     3456
     3457        restoreFocus: function() {
     3458                this.$( 'li.selected:first' ).focus();
     3459        },
     3460
     3461        arrowEvent: function( event ) {
     3462                var attachments = this.$el.children( 'li' ),
     3463                        perRow = this.columns,
     3464                        index = attachments.filter( ':focus' ).index(),
     3465                        row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
     3466
     3467                if ( index === -1 ) {
     3468                        return;
     3469                }
     3470
     3471                // Left arrow
     3472                if ( 37 === event.keyCode ) {
     3473                        if ( 0 === index ) {
     3474                                return;
     3475                        }
     3476                        attachments.eq( index - 1 ).focus();
     3477                }
     3478
     3479                // Up arrow
     3480                if ( 38 === event.keyCode ) {
     3481                        if ( 1 === row ) {
     3482                                return;
     3483                        }
     3484                        attachments.eq( index - perRow ).focus();
     3485                }
     3486
     3487                // Right arrow
     3488                if ( 39 === event.keyCode ) {
     3489                        if ( attachments.length === index ) {
     3490                                return;
     3491                        }
     3492                        attachments.eq( index + 1 ).focus();
     3493                }
     3494
     3495                // Down arrow
     3496                if ( 40 === event.keyCode ) {
     3497                        if ( Math.ceil( attachments.length / perRow ) === row ) {
     3498                                return;
     3499                        }
     3500                        attachments.eq( index + perRow ).focus();
     3501                }
     3502        },
     3503
     3504        dispose: function() {
     3505                this.collection.props.off( null, null, this );
     3506                if ( this.options.resize ) {
     3507                        this.$window.off( this.resizeEvent );
     3508                }
     3509
     3510                /**
     3511                 * call 'dispose' directly on the parent class
     3512                 */
     3513                View.prototype.dispose.apply( this, arguments );
     3514        },
     3515
     3516        setColumns: function() {
     3517                var prev = this.columns,
     3518                        width = this.$el.width();
     3519
     3520                if ( width ) {
     3521                        this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
     3522
     3523                        if ( ! prev || prev !== this.columns ) {
     3524                                this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
     3525                        }
     3526                }
     3527        },
     3528
     3529        initSortable: function() {
     3530                var collection = this.collection;
     3531
     3532                if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
     3533                        return;
     3534                }
     3535
     3536                this.$el.sortable( _.extend({
     3537                        // If the `collection` has a `comparator`, disable sorting.
     3538                        disabled: !! collection.comparator,
     3539
     3540                        // Change the position of the attachment as soon as the
     3541                        // mouse pointer overlaps a thumbnail.
     3542                        tolerance: 'pointer',
     3543
     3544                        // Record the initial `index` of the dragged model.
     3545                        start: function( event, ui ) {
     3546                                ui.item.data('sortableIndexStart', ui.item.index());
     3547                        },
     3548
     3549                        // Update the model's index in the collection.
     3550                        // Do so silently, as the view is already accurate.
     3551                        update: function( event, ui ) {
     3552                                var model = collection.at( ui.item.data('sortableIndexStart') ),
     3553                                        comparator = collection.comparator;
     3554
     3555                                // Temporarily disable the comparator to prevent `add`
     3556                                // from re-sorting.
     3557                                delete collection.comparator;
     3558
     3559                                // Silently shift the model to its new index.
     3560                                collection.remove( model, {
     3561                                        silent: true
     3562                                });
     3563                                collection.add( model, {
     3564                                        silent: true,
     3565                                        at:     ui.item.index()
     3566                                });
     3567
     3568                                // Restore the comparator.
     3569                                collection.comparator = comparator;
     3570
     3571                                // Fire the `reset` event to ensure other collections sync.
     3572                                collection.trigger( 'reset', collection );
     3573
     3574                                // If the collection is sorted by menu order,
     3575                                // update the menu order.
     3576                                collection.saveMenuOrder();
     3577                        }
     3578                }, this.options.sortable ) );
     3579
     3580                // If the `orderby` property is changed on the `collection`,
     3581                // check to see if we have a `comparator`. If so, disable sorting.
     3582                collection.props.on( 'change:orderby', function() {
     3583                        this.$el.sortable( 'option', 'disabled', !! collection.comparator );
     3584                }, this );
     3585
     3586                this.collection.props.on( 'change:orderby', this.refreshSortable, this );
     3587                this.refreshSortable();
     3588        },
     3589
     3590        refreshSortable: function() {
     3591                if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
     3592                        return;
     3593                }
     3594
     3595                // If the `collection` has a `comparator`, disable sorting.
     3596                var collection = this.collection,
     3597                        orderby = collection.props.get('orderby'),
     3598                        enabled = 'menuOrder' === orderby || ! collection.comparator;
     3599
     3600                this.$el.sortable( 'option', 'disabled', ! enabled );
     3601        },
     3602
     3603        /**
     3604         * @param {wp.media.model.Attachment} attachment
     3605         * @returns {wp.media.View}
     3606         */
     3607        createAttachmentView: function( attachment ) {
     3608                var view = new this.options.AttachmentView({
     3609                        controller:           this.controller,
     3610                        model:                attachment,
     3611                        collection:           this.collection,
     3612                        selection:            this.options.selection
     3613                });
     3614
     3615                return this._viewsByCid[ attachment.cid ] = view;
     3616        },
     3617
     3618        prepare: function() {
     3619                // Create all of the Attachment views, and replace
     3620                // the list in a single DOM operation.
     3621                if ( this.collection.length ) {
     3622                        this.views.set( this.collection.map( this.createAttachmentView, this ) );
     3623
     3624                // If there are no elements, clear the views and load some.
     3625                } else {
     3626                        this.views.unset();
     3627                        this.collection.more().done( this.scroll );
     3628                }
     3629        },
     3630
     3631        ready: function() {
     3632                // Trigger the scroll event to check if we're within the
     3633                // threshold to query for additional attachments.
     3634                this.scroll();
     3635        },
     3636
     3637        scroll: function() {
     3638                var view = this,
     3639                        el = this.options.scrollElement,
     3640                        scrollTop = el.scrollTop,
     3641                        toolbar;
     3642
     3643                // The scroll event occurs on the document, but the element
     3644                // that should be checked is the document body.
     3645                if ( el === document ) {
     3646                        el = document.body;
     3647                        scrollTop = $(document).scrollTop();
     3648                }
     3649
     3650                if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
     3651                        return;
     3652                }
     3653
     3654                toolbar = this.views.parent.toolbar;
     3655
     3656                // Show the spinner only if we are close to the bottom.
     3657                if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
     3658                        toolbar.get('spinner').show();
     3659                }
     3660
     3661                if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
     3662                        this.collection.more().done(function() {
     3663                                view.scroll();
     3664                                toolbar.get('spinner').hide();
     3665                        });
     3666                }
     3667        }
     3668});
     3669
     3670module.exports = Attachments;
     3671
     3672},{}],30:[function(require,module,exports){
     3673/*globals wp, _, jQuery */
     3674
     3675/**
     3676 * wp.media.view.AttachmentsBrowser
     3677 *
     3678 * @class
     3679 * @augments wp.media.View
     3680 * @augments wp.Backbone.View
     3681 * @augments Backbone.View
     3682 *
     3683 * @param {object}      options
     3684 * @param {object}      [options.filters=false] Which filters to show in the browser's toolbar.
     3685 *                                              Accepts 'uploaded' and 'all'.
     3686 * @param {object}      [options.search=true]   Whether to show the search interface in the
     3687 *                                              browser's toolbar.
     3688 * @param {object}      [options.date=true]     Whether to show the date filter in the
     3689 *                                              browser's toolbar.
     3690 * @param {object}      [options.display=false] Whether to show the attachments display settings
     3691 *                                              view in the sidebar.
     3692 * @param {bool|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
     3693 *                                              Accepts true, false, and 'errors'.
     3694 */
     3695var View = wp.media.View,
     3696        mediaTrash = wp.media.view.settings.mediaTrash,
     3697        l10n = wp.media.view.l10n,
     3698        $ = jQuery,
     3699        AttachmentsBrowser;
     3700
     3701AttachmentsBrowser = View.extend({
     3702        tagName:   'div',
     3703        className: 'attachments-browser',
     3704
     3705        initialize: function() {
     3706                _.defaults( this.options, {
     3707                        filters: false,
     3708                        search:  true,
     3709                        date:    true,
     3710                        display: false,
     3711                        sidebar: true,
     3712                        AttachmentView: wp.media.view.Attachment.Library
     3713                });
     3714
     3715                this.listenTo( this.controller, 'toggle:upload:attachment', _.bind( this.toggleUploader, this ) );
     3716                this.controller.on( 'edit:selection', this.editSelection );
     3717                this.createToolbar();
     3718                if ( this.options.sidebar ) {
     3719                        this.createSidebar();
     3720                }
     3721                this.createUploader();
     3722                this.createAttachments();
     3723                this.updateContent();
     3724
     3725                if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
     3726                        this.$el.addClass( 'hide-sidebar' );
     3727
     3728                        if ( 'errors' === this.options.sidebar ) {
     3729                                this.$el.addClass( 'sidebar-for-errors' );
     3730                        }
     3731                }
     3732
     3733                this.collection.on( 'add remove reset', this.updateContent, this );
     3734        },
     3735
     3736        editSelection: function( modal ) {
     3737                modal.$( '.media-button-backToLibrary' ).focus();
     3738        },
     3739
     3740        /**
     3741         * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
     3742         */
     3743        dispose: function() {
     3744                this.options.selection.off( null, null, this );
     3745                View.prototype.dispose.apply( this, arguments );
     3746                return this;
     3747        },
     3748
     3749        createToolbar: function() {
     3750                var LibraryViewSwitcher, Filters, toolbarOptions;
     3751
     3752                toolbarOptions = {
     3753                        controller: this.controller
     3754                };
     3755
     3756                if ( this.controller.isModeActive( 'grid' ) ) {
     3757                        toolbarOptions.className = 'media-toolbar wp-filter';
     3758                }
     3759
     3760                /**
     3761                * @member {wp.media.view.Toolbar}
     3762                */
     3763                this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
     3764
     3765                this.views.add( this.toolbar );
     3766
     3767                this.toolbar.set( 'spinner', new wp.media.view.Spinner({
     3768                        priority: -60
     3769                }) );
     3770
     3771                if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
     3772                        // "Filters" will return a <select>, need to render
     3773                        // screen reader text before
     3774                        this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
     3775                                value: l10n.filterByType,
     3776                                attributes: {
     3777                                        'for':  'media-attachment-filters'
     3778                                },
     3779                                priority:   -80
     3780                        }).render() );
     3781
     3782                        if ( 'uploaded' === this.options.filters ) {
     3783                                this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({
     3784                                        controller: this.controller,
     3785                                        model:      this.collection.props,
     3786                                        priority:   -80
     3787                                }).render() );
     3788                        } else {
     3789                                Filters = new wp.media.view.AttachmentFilters.All({
     3790                                        controller: this.controller,
     3791                                        model:      this.collection.props,
     3792                                        priority:   -80
     3793                                });
     3794
     3795                                this.toolbar.set( 'filters', Filters.render() );
     3796                        }
     3797                }
     3798
     3799                // Feels odd to bring the global media library switcher into the Attachment
     3800                // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
     3801                // which the controller can tap into and add this view?
     3802                if ( this.controller.isModeActive( 'grid' ) ) {
     3803                        LibraryViewSwitcher = View.extend({
     3804                                className: 'view-switch media-grid-view-switch',
     3805                                template: wp.template( 'media-library-view-switcher')
     3806                        });
     3807
     3808                        this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
     3809                                controller: this.controller,
     3810                                priority: -90
     3811                        }).render() );
     3812
     3813                        // DateFilter is a <select>, screen reader text needs to be rendered before
     3814                        this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
     3815                                value: l10n.filterByDate,
     3816                                attributes: {
     3817                                        'for': 'media-attachment-date-filters'
     3818                                },
     3819                                priority: -75
     3820                        }).render() );
     3821                        this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
     3822                                controller: this.controller,
     3823                                model:      this.collection.props,
     3824                                priority: -75
     3825                        }).render() );
     3826
     3827                        // BulkSelection is a <div> with subviews, including screen reader text
     3828                        this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
     3829                                text: l10n.bulkSelect,
     3830                                controller: this.controller,
     3831                                priority: -70
     3832                        }).render() );
     3833
     3834                        this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
     3835                                filters: Filters,
     3836                                style: 'primary',
     3837                                disabled: true,
     3838                                text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
     3839                                controller: this.controller,
     3840                                priority: -60,
     3841                                click: function() {
     3842                                        var changed = [], removed = [],
     3843                                                selection = this.controller.state().get( 'selection' ),
     3844                                                library = this.controller.state().get( 'library' );
     3845
     3846                                        if ( ! selection.length ) {
     3847                                                return;
     3848                                        }
     3849
     3850                                        if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
     3851                                                return;
     3852                                        }
     3853
     3854                                        if ( mediaTrash &&
     3855                                                'trash' !== selection.at( 0 ).get( 'status' ) &&
     3856                                                ! window.confirm( l10n.warnBulkTrash ) ) {
     3857
     3858                                                return;
     3859                                        }
     3860
     3861                                        selection.each( function( model ) {
     3862                                                if ( ! model.get( 'nonces' )['delete'] ) {
     3863                                                        removed.push( model );
     3864                                                        return;
     3865                                                }
     3866
     3867                                                if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
     3868                                                        model.set( 'status', 'inherit' );
     3869                                                        changed.push( model.save() );
     3870                                                        removed.push( model );
     3871                                                } else if ( mediaTrash ) {
     3872                                                        model.set( 'status', 'trash' );
     3873                                                        changed.push( model.save() );
     3874                                                        removed.push( model );
     3875                                                } else {
     3876                                                        model.destroy({wait: true});
     3877                                                }
     3878                                        } );
     3879
     3880                                        if ( changed.length ) {
     3881                                                selection.remove( removed );
     3882
     3883                                                $.when.apply( null, changed ).then( _.bind( function() {
     3884                                                        library._requery( true );
     3885                                                        this.controller.trigger( 'selection:action:done' );
     3886                                                }, this ) );
     3887                                        } else {
     3888                                                this.controller.trigger( 'selection:action:done' );
     3889                                        }
     3890                                }
     3891                        }).render() );
     3892
     3893                        if ( mediaTrash ) {
     3894                                this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
     3895                                        filters: Filters,
     3896                                        style: 'primary',
     3897                                        disabled: true,
     3898                                        text: l10n.deleteSelected,
     3899                                        controller: this.controller,
     3900                                        priority: -55,
     3901                                        click: function() {
     3902                                                var removed = [], selection = this.controller.state().get( 'selection' );
     3903
     3904                                                if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
     3905                                                        return;
     3906                                                }
     3907
     3908                                                selection.each( function( model ) {
     3909                                                        if ( ! model.get( 'nonces' )['delete'] ) {
     3910                                                                removed.push( model );
     3911                                                                return;
     3912                                                        }
     3913
     3914                                                        model.destroy();
     3915                                                } );
     3916
     3917                                                selection.remove( removed );
     3918                                                this.controller.trigger( 'selection:action:done' );
     3919                                        }
     3920                                }).render() );
     3921                        }
     3922
     3923                } else if ( this.options.date ) {
     3924                        // DateFilter is a <select>, screen reader text needs to be rendered before
     3925                        this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
     3926                                value: l10n.filterByDate,
     3927                                attributes: {
     3928                                        'for': 'media-attachment-date-filters'
     3929                                },
     3930                                priority: -75
     3931                        }).render() );
     3932                        this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
     3933                                controller: this.controller,
     3934                                model:      this.collection.props,
     3935                                priority: -75
     3936                        }).render() );
     3937                }
     3938
     3939                if ( this.options.search ) {
     3940                        // Search is an input, screen reader text needs to be rendered before
     3941                        this.toolbar.set( 'searchLabel', new wp.media.view.Label({
     3942                                value: l10n.searchMediaLabel,
     3943                                attributes: {
     3944                                        'for': 'media-search-input'
     3945                                },
     3946                                priority:   60
     3947                        }).render() );
     3948                        this.toolbar.set( 'search', new wp.media.view.Search({
     3949                                controller: this.controller,
     3950                                model:      this.collection.props,
     3951                                priority:   60
     3952                        }).render() );
     3953                }
     3954
     3955                if ( this.options.dragInfo ) {
     3956                        this.toolbar.set( 'dragInfo', new View({
     3957                                el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
     3958                                priority: -40
     3959                        }) );
     3960                }
     3961
     3962                if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
     3963                        this.toolbar.set( 'suggestedDimensions', new View({
     3964                                el: $( '<div class="instructions">' + l10n.suggestedDimensions + ' ' + this.options.suggestedWidth + ' &times; ' + this.options.suggestedHeight + '</div>' )[0],
     3965                                priority: -40
     3966                        }) );
     3967                }
     3968        },
     3969
     3970        updateContent: function() {
     3971                var view = this,
     3972                        noItemsView;
     3973
     3974                if ( this.controller.isModeActive( 'grid' ) ) {
     3975                        noItemsView = view.attachmentsNoResults;
     3976                } else {
     3977                        noItemsView = view.uploader;
     3978                }
     3979
     3980                if ( ! this.collection.length ) {
     3981                        this.toolbar.get( 'spinner' ).show();
     3982                        this.dfd = this.collection.more().done( function() {
     3983                                if ( ! view.collection.length ) {
     3984                                        noItemsView.$el.removeClass( 'hidden' );
     3985                                } else {
     3986                                        noItemsView.$el.addClass( 'hidden' );
     3987                                }
     3988                                view.toolbar.get( 'spinner' ).hide();
     3989                        } );
     3990                } else {
     3991                        noItemsView.$el.addClass( 'hidden' );
     3992                        view.toolbar.get( 'spinner' ).hide();
     3993                }
     3994        },
     3995
     3996        createUploader: function() {
     3997                this.uploader = new wp.media.view.UploaderInline({
     3998                        controller: this.controller,
     3999                        status:     false,
     4000                        message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
     4001                        canClose:   this.controller.isModeActive( 'grid' )
     4002                });
     4003
     4004                this.uploader.hide();
     4005                this.views.add( this.uploader );
     4006        },
     4007
     4008        toggleUploader: function() {
     4009                if ( this.uploader.$el.hasClass( 'hidden' ) ) {
     4010                        this.uploader.show();
     4011                } else {
     4012                        this.uploader.hide();
     4013                }
     4014        },
     4015
     4016        createAttachments: function() {
     4017                this.attachments = new wp.media.view.Attachments({
     4018                        controller:           this.controller,
     4019                        collection:           this.collection,
     4020                        selection:            this.options.selection,
     4021                        model:                this.model,
     4022                        sortable:             this.options.sortable,
     4023                        scrollElement:        this.options.scrollElement,
     4024                        idealColumnWidth:     this.options.idealColumnWidth,
     4025
     4026                        // The single `Attachment` view to be used in the `Attachments` view.
     4027                        AttachmentView: this.options.AttachmentView
     4028                });
     4029
     4030                // Add keydown listener to the instance of the Attachments view
     4031                this.attachments.listenTo( this.controller, 'attachment:keydown:arrow',     this.attachments.arrowEvent );
     4032                this.attachments.listenTo( this.controller, 'attachment:details:shift-tab', this.attachments.restoreFocus );
     4033
     4034                this.views.add( this.attachments );
     4035
     4036
     4037                if ( this.controller.isModeActive( 'grid' ) ) {
     4038                        this.attachmentsNoResults = new View({
     4039                                controller: this.controller,
     4040                                tagName: 'p'
     4041                        });
     4042
     4043                        this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
     4044                        this.attachmentsNoResults.$el.html( l10n.noMedia );
     4045
     4046                        this.views.add( this.attachmentsNoResults );
     4047                }
     4048        },
     4049
     4050        createSidebar: function() {
     4051                var options = this.options,
     4052                        selection = options.selection,
     4053                        sidebar = this.sidebar = new wp.media.view.Sidebar({
     4054                                controller: this.controller
     4055                        });
     4056
     4057                this.views.add( sidebar );
     4058
     4059                if ( this.controller.uploader ) {
     4060                        sidebar.set( 'uploads', new wp.media.view.UploaderStatus({
     4061                                controller: this.controller,
     4062                                priority:   40
     4063                        }) );
     4064                }
     4065
     4066                selection.on( 'selection:single', this.createSingle, this );
     4067                selection.on( 'selection:unsingle', this.disposeSingle, this );
     4068
     4069                if ( selection.single() ) {
     4070                        this.createSingle();
     4071                }
     4072        },
     4073
     4074        createSingle: function() {
     4075                var sidebar = this.sidebar,
     4076                        single = this.options.selection.single();
     4077
     4078                sidebar.set( 'details', new wp.media.view.Attachment.Details({
     4079                        controller: this.controller,
     4080                        model:      single,
     4081                        priority:   80
     4082                }) );
     4083
     4084                sidebar.set( 'compat', new wp.media.view.AttachmentCompat({
     4085                        controller: this.controller,
     4086                        model:      single,
     4087                        priority:   120
     4088                }) );
     4089
     4090                if ( this.options.display ) {
     4091                        sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({
     4092                                controller:   this.controller,
     4093                                model:        this.model.display( single ),
     4094                                attachment:   single,
     4095                                priority:     160,
     4096                                userSettings: this.model.get('displayUserSettings')
     4097                        }) );
     4098                }
     4099
     4100                // Show the sidebar on mobile
     4101                if ( this.model.id === 'insert' ) {
     4102                        sidebar.$el.addClass( 'visible' );
     4103                }
     4104        },
     4105
     4106        disposeSingle: function() {
     4107                var sidebar = this.sidebar;
     4108                sidebar.unset('details');
     4109                sidebar.unset('compat');
     4110                sidebar.unset('display');
     4111                // Hide the sidebar on mobile
     4112                sidebar.$el.removeClass( 'visible' );
     4113        }
     4114});
     4115
     4116module.exports = AttachmentsBrowser;
     4117
     4118},{}],31:[function(require,module,exports){
     4119/*globals wp, _ */
     4120
     4121/**
     4122 * wp.media.view.Attachments.Selection
     4123 *
     4124 * @class
     4125 * @augments wp.media.view.Attachments
     4126 * @augments wp.media.View
     4127 * @augments wp.Backbone.View
     4128 * @augments Backbone.View
     4129 */
     4130var Attachments = wp.media.view.Attachments,
     4131        Selection;
     4132
     4133Selection = Attachments.extend({
     4134        events: {},
     4135        initialize: function() {
     4136                _.defaults( this.options, {
     4137                        sortable:   false,
     4138                        resize:     false,
     4139
     4140                        // The single `Attachment` view to be used in the `Attachments` view.
     4141                        AttachmentView: wp.media.view.Attachment.Selection
     4142                });
     4143                // Call 'initialize' directly on the parent class.
     4144                return Attachments.prototype.initialize.apply( this, arguments );
     4145        }
     4146});
     4147
     4148module.exports = Selection;
     4149
     4150},{}],32:[function(require,module,exports){
     4151/*globals _, Backbone */
     4152
     4153/**
     4154 * wp.media.view.ButtonGroup
     4155 *
     4156 * @class
     4157 * @augments wp.media.View
     4158 * @augments wp.Backbone.View
     4159 * @augments Backbone.View
     4160 */
     4161var $ = Backbone.$,
     4162        ButtonGroup;
     4163
     4164ButtonGroup = wp.media.View.extend({
     4165        tagName:   'div',
     4166        className: 'button-group button-large media-button-group',
     4167
     4168        initialize: function() {
     4169                /**
     4170                 * @member {wp.media.view.Button[]}
     4171                 */
     4172                this.buttons = _.map( this.options.buttons || [], function( button ) {
     4173                        if ( button instanceof Backbone.View ) {
     4174                                return button;
     4175                        } else {
     4176                                return new wp.media.view.Button( button ).render();
     4177                        }
     4178                });
     4179
     4180                delete this.options.buttons;
     4181
     4182                if ( this.options.classes ) {
     4183                        this.$el.addClass( this.options.classes );
     4184                }
     4185        },
     4186
     4187        /**
     4188         * @returns {wp.media.view.ButtonGroup}
     4189         */
     4190        render: function() {
     4191                this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
     4192                return this;
     4193        }
     4194});
     4195
     4196module.exports = ButtonGroup;
     4197
     4198},{}],33:[function(require,module,exports){
     4199/*globals _, Backbone */
     4200
     4201/**
     4202 * wp.media.view.Button
     4203 *
     4204 * @class
     4205 * @augments wp.media.View
     4206 * @augments wp.Backbone.View
     4207 * @augments Backbone.View
     4208 */
     4209var Button = wp.media.View.extend({
     4210        tagName:    'a',
     4211        className:  'media-button',
     4212        attributes: { href: '#' },
     4213
     4214        events: {
     4215                'click': 'click'
     4216        },
     4217
     4218        defaults: {
     4219                text:     '',
     4220                style:    '',
     4221                size:     'large',
     4222                disabled: false
     4223        },
     4224
     4225        initialize: function() {
     4226                /**
     4227                 * Create a model with the provided `defaults`.
     4228                 *
     4229                 * @member {Backbone.Model}
     4230                 */
     4231                this.model = new Backbone.Model( this.defaults );
     4232
     4233                // If any of the `options` have a key from `defaults`, apply its
     4234                // value to the `model` and remove it from the `options object.
     4235                _.each( this.defaults, function( def, key ) {
     4236                        var value = this.options[ key ];
     4237                        if ( _.isUndefined( value ) ) {
     4238                                return;
     4239                        }
     4240
     4241                        this.model.set( key, value );
     4242                        delete this.options[ key ];
     4243                }, this );
     4244
     4245                this.listenTo( this.model, 'change', this.render );
     4246        },
     4247        /**
     4248         * @returns {wp.media.view.Button} Returns itself to allow chaining
     4249         */
     4250        render: function() {
     4251                var classes = [ 'button', this.className ],
     4252                        model = this.model.toJSON();
     4253
     4254                if ( model.style ) {
     4255                        classes.push( 'button-' + model.style );
     4256                }
     4257
     4258                if ( model.size ) {
     4259                        classes.push( 'button-' + model.size );
     4260                }
     4261
     4262                classes = _.uniq( classes.concat( this.options.classes ) );
     4263                this.el.className = classes.join(' ');
     4264
     4265                this.$el.attr( 'disabled', model.disabled );
     4266                this.$el.text( this.model.get('text') );
     4267
     4268                return this;
     4269        },
     4270        /**
     4271         * @param {Object} event
     4272         */
     4273        click: function( event ) {
     4274                if ( '#' === this.attributes.href ) {
     4275                        event.preventDefault();
     4276                }
     4277
     4278                if ( this.options.click && ! this.model.get('disabled') ) {
     4279                        this.options.click.apply( this, arguments );
     4280                }
     4281        }
     4282});
     4283
     4284module.exports = Button;
     4285
     4286},{}],34:[function(require,module,exports){
     4287/*globals wp, _, jQuery */
     4288
     4289/**
     4290 * wp.media.view.Cropper
     4291 *
     4292 * Uses the imgAreaSelect plugin to allow a user to crop an image.
     4293 *
     4294 * Takes imgAreaSelect options from
     4295 * wp.customize.HeaderControl.calculateImageSelectOptions via
     4296 * wp.customize.HeaderControl.openMM.
     4297 *
     4298 * @class
     4299 * @augments wp.media.View
     4300 * @augments wp.Backbone.View
     4301 * @augments Backbone.View
     4302 */
     4303var View = wp.media.View,
     4304        UploaderStatus = wp.media.view.UploaderStatus,
     4305        l10n = wp.media.view.l10n,
     4306        $ = jQuery,
     4307        Cropper;
     4308
     4309Cropper = View.extend({
     4310        className: 'crop-content',
     4311        template: wp.template('crop-content'),
     4312        initialize: function() {
     4313                _.bindAll(this, 'onImageLoad');
     4314        },
     4315        ready: function() {
     4316                this.controller.frame.on('content:error:crop', this.onError, this);
     4317                this.$image = this.$el.find('.crop-image');
     4318                this.$image.on('load', this.onImageLoad);
     4319                $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
     4320        },
     4321        remove: function() {
     4322                $(window).off('resize.cropper');
     4323                this.$el.remove();
     4324                this.$el.off();
     4325                View.prototype.remove.apply(this, arguments);
     4326        },
     4327        prepare: function() {
     4328                return {
     4329                        title: l10n.cropYourImage,
     4330                        url: this.options.attachment.get('url')
     4331                };
     4332        },
     4333        onImageLoad: function() {
     4334                var imgOptions = this.controller.get('imgSelectOptions');
     4335                if (typeof imgOptions === 'function') {
     4336                        imgOptions = imgOptions(this.options.attachment, this.controller);
     4337                }
     4338
     4339                imgOptions = _.extend(imgOptions, {parent: this.$el});
     4340                this.trigger('image-loaded');
     4341                this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
     4342        },
     4343        onError: function() {
     4344                var filename = this.options.attachment.get('filename');
     4345
     4346                this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     4347                        filename: UploaderStatus.prototype.filename(filename),
     4348                        message: window._wpMediaViewsL10n.cropError
     4349                }), { at: 0 });
     4350        }
     4351});
     4352
     4353module.exports = Cropper;
     4354
     4355},{}],35:[function(require,module,exports){
     4356/*globals wp, _ */
     4357
     4358/**
     4359 * wp.media.view.EditImage
     4360 *
     4361 * @class
     4362 * @augments wp.media.View
     4363 * @augments wp.Backbone.View
     4364 * @augments Backbone.View
     4365 */
     4366var View = wp.media.View,
     4367        EditImage;
     4368
     4369EditImage = View.extend({
     4370        className: 'image-editor',
     4371        template: wp.template('image-editor'),
     4372
     4373        initialize: function( options ) {
     4374                this.editor = window.imageEdit;
     4375                this.controller = options.controller;
     4376                View.prototype.initialize.apply( this, arguments );
     4377        },
     4378
     4379        prepare: function() {
     4380                return this.model.toJSON();
     4381        },
     4382
     4383        loadEditor: function() {
     4384                var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
     4385                dfd.done( _.bind( this.focus, this ) );
     4386        },
     4387
     4388        focus: function() {
     4389                this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
     4390        },
     4391
     4392        back: function() {
     4393                var lastState = this.controller.lastState();
     4394                this.controller.setState( lastState );
     4395        },
     4396
     4397        refresh: function() {
     4398                this.model.fetch();
     4399        },
     4400
     4401        save: function() {
     4402                var lastState = this.controller.lastState();
     4403
     4404                this.model.fetch().done( _.bind( function() {
     4405                        this.controller.setState( lastState );
     4406                }, this ) );
     4407        }
     4408
     4409});
     4410
     4411module.exports = EditImage;
     4412
     4413},{}],36:[function(require,module,exports){
     4414/**
     4415 * wp.media.view.Embed
     4416 *
     4417 * @class
     4418 * @augments wp.media.View
     4419 * @augments wp.Backbone.View
     4420 * @augments Backbone.View
     4421 */
     4422var Embed = wp.media.View.extend({
     4423        className: 'media-embed',
     4424
     4425        initialize: function() {
     4426                /**
     4427                 * @member {wp.media.view.EmbedUrl}
     4428                 */
     4429                this.url = new wp.media.view.EmbedUrl({
     4430                        controller: this.controller,
     4431                        model:      this.model.props
     4432                }).render();
     4433
     4434                this.views.set([ this.url ]);
     4435                this.refresh();
     4436                this.listenTo( this.model, 'change:type', this.refresh );
     4437                this.listenTo( this.model, 'change:loading', this.loading );
     4438        },
     4439
     4440        /**
     4441         * @param {Object} view
     4442         */
     4443        settings: function( view ) {
     4444                if ( this._settings ) {
     4445                        this._settings.remove();
     4446                }
     4447                this._settings = view;
     4448                this.views.add( view );
     4449        },
     4450
     4451        refresh: function() {
     4452                var type = this.model.get('type'),
     4453                        constructor;
     4454
     4455                if ( 'image' === type ) {
     4456                        constructor = wp.media.view.EmbedImage;
     4457                } else if ( 'link' === type ) {
     4458                        constructor = wp.media.view.EmbedLink;
     4459                } else {
     4460                        return;
     4461                }
     4462
     4463                this.settings( new constructor({
     4464                        controller: this.controller,
     4465                        model:      this.model.props,
     4466                        priority:   40
     4467                }) );
     4468        },
     4469
     4470        loading: function() {
     4471                this.$el.toggleClass( 'embed-loading', this.model.get('loading') );
     4472        }
     4473});
     4474
     4475module.exports = Embed;
     4476
     4477},{}],37:[function(require,module,exports){
     4478/*globals wp */
     4479
     4480/**
     4481 * wp.media.view.EmbedImage
     4482 *
     4483 * @class
     4484 * @augments wp.media.view.Settings.AttachmentDisplay
     4485 * @augments wp.media.view.Settings
     4486 * @augments wp.media.View
     4487 * @augments wp.Backbone.View
     4488 * @augments Backbone.View
     4489 */
     4490var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     4491        EmbedImage;
     4492
     4493EmbedImage = AttachmentDisplay.extend({
     4494        className: 'embed-media-settings',
     4495        template:  wp.template('embed-image-settings'),
     4496
     4497        initialize: function() {
     4498                /**
     4499                 * Call `initialize` directly on parent class with passed arguments
     4500                 */
     4501                AttachmentDisplay.prototype.initialize.apply( this, arguments );
     4502                this.listenTo( this.model, 'change:url', this.updateImage );
     4503        },
     4504
     4505        updateImage: function() {
     4506                this.$('img').attr( 'src', this.model.get('url') );
     4507        }
     4508});
     4509
     4510module.exports = EmbedImage;
     4511
     4512},{}],38:[function(require,module,exports){
     4513/*globals wp, _, jQuery */
     4514
     4515/**
     4516 * wp.media.view.EmbedLink
     4517 *
     4518 * @class
     4519 * @augments wp.media.view.Settings
     4520 * @augments wp.media.View
     4521 * @augments wp.Backbone.View
     4522 * @augments Backbone.View
     4523 */
     4524var $ = jQuery,
     4525        EmbedLink;
     4526
     4527EmbedLink = wp.media.view.Settings.extend({
     4528        className: 'embed-link-settings',
     4529        template:  wp.template('embed-link-settings'),
     4530
     4531        initialize: function() {
     4532                this.spinner = $('<span class="spinner" />');
     4533                this.$el.append( this.spinner[0] );
     4534                this.listenTo( this.model, 'change:url change:width change:height', this.updateoEmbed );
     4535        },
     4536
     4537        updateoEmbed: _.debounce( function() {
     4538                var url = this.model.get( 'url' );
     4539
     4540                // clear out previous results
     4541                this.$('.embed-container').hide().find('.embed-preview').empty();
     4542                this.$( '.setting' ).hide();
     4543
     4544                // only proceed with embed if the field contains more than 6 characters
     4545                if ( url && url.length < 6 ) {
     4546                        return;
     4547                }
     4548
     4549                this.spinner.show();
     4550
     4551                this.fetch();
     4552        }, 600 ),
     4553
     4554        fetch: function() {
     4555                var embed;
     4556
     4557                // check if they haven't typed in 500 ms
     4558                if ( $('#embed-url-field').val() !== this.model.get('url') ) {
     4559                        return;
     4560                }
     4561
     4562                embed = new wp.shortcode({
     4563                        tag: 'embed',
     4564                        attrs: _.pick( this.model.attributes, [ 'width', 'height', 'src' ] ),
     4565                        content: this.model.get('url')
     4566                });
     4567
     4568                wp.ajax.send( 'parse-embed', {
     4569                        data : {
     4570                                post_ID: wp.media.view.settings.post.id,
     4571                                shortcode: embed.string()
     4572                        }
     4573                } )
     4574                        .done( _.bind( this.renderoEmbed, this ) )
     4575                        .fail( _.bind( this.renderFail, this ) );
     4576        },
     4577
     4578        renderFail: function () {
     4579                this.$( '.setting' ).hide().filter( '.link-text' ).show();
     4580        },
     4581
     4582        renderoEmbed: function( response ) {
     4583                var html = ( response && response.body ) || '',
     4584                        attr = {},
     4585                        opts = { silent: true };
     4586
     4587                this.$( '.setting' ).hide()
     4588                        .filter( '.link-text' )[ html ? 'hide' : 'show' ]();
     4589
     4590                if ( response && response.attr ) {
     4591                        attr = response.attr;
     4592
     4593                        _.each( [ 'width', 'height' ], function ( key ) {
     4594                                var $el = this.$( '.setting.' + key ),
     4595                                        value = attr[ key ];
     4596
     4597                                if ( value ) {
     4598                                        this.model.set( key, value, opts );
     4599                                        $el.show().find( 'input' ).val( value );
     4600                                } else {
     4601                                        this.model.unset( key, opts );
     4602                                        $el.hide().find( 'input' ).val( '' );
     4603                                }
     4604                        }, this );
     4605                } else {
     4606                        this.model.unset( 'height', opts );
     4607                        this.model.unset( 'width', opts );
     4608                }
     4609
     4610                this.spinner.hide();
     4611
     4612                this.$('.embed-container').show().find('.embed-preview').html( html );
     4613        }
     4614});
     4615
     4616module.exports = EmbedLink;
     4617
     4618},{}],39:[function(require,module,exports){
     4619/*globals wp, _, jQuery */
     4620
     4621/**
     4622 * wp.media.view.EmbedUrl
     4623 *
     4624 * @class
     4625 * @augments wp.media.View
     4626 * @augments wp.Backbone.View
     4627 * @augments Backbone.View
     4628 */
     4629var View = wp.media.View,
     4630        $ = jQuery,
     4631        EmbedUrl;
     4632
     4633EmbedUrl = View.extend({
     4634        tagName:   'label',
     4635        className: 'embed-url',
     4636
     4637        events: {
     4638                'input':  'url',
     4639                'keyup':  'url',
     4640                'change': 'url'
     4641        },
     4642
     4643        initialize: function() {
     4644                this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
     4645                this.input = this.$input[0];
     4646
     4647                this.spinner = $('<span class="spinner" />')[0];
     4648                this.$el.append([ this.input, this.spinner ]);
     4649
     4650                this.listenTo( this.model, 'change:url', this.render );
     4651
     4652                if ( this.model.get( 'url' ) ) {
     4653                        _.delay( _.bind( function () {
     4654                                this.model.trigger( 'change:url' );
     4655                        }, this ), 500 );
     4656                }
     4657        },
     4658        /**
     4659         * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
     4660         */
     4661        render: function() {
     4662                var $input = this.$input;
     4663
     4664                if ( $input.is(':focus') ) {
     4665                        return;
     4666                }
     4667
     4668                this.input.value = this.model.get('url') || 'http://';
     4669                /**
     4670                 * Call `render` directly on parent class with passed arguments
     4671                 */
     4672                View.prototype.render.apply( this, arguments );
     4673                return this;
     4674        },
     4675
     4676        ready: function() {
     4677                if ( ! wp.media.isTouchDevice ) {
     4678                        this.focus();
     4679                }
     4680        },
     4681
     4682        url: function( event ) {
     4683                this.model.set( 'url', event.target.value );
     4684        },
     4685
     4686        /**
     4687         * If the input is visible, focus and select its contents.
     4688         */
     4689        focus: function() {
     4690                var $input = this.$input;
     4691                if ( $input.is(':visible') ) {
     4692                        $input.focus()[0].select();
     4693                }
     4694        }
     4695});
     4696
     4697module.exports = EmbedUrl;
     4698
     4699},{}],40:[function(require,module,exports){
     4700/**
     4701 * wp.media.view.FocusManager
     4702 *
     4703 * @class
     4704 * @augments wp.media.View
     4705 * @augments wp.Backbone.View
     4706 * @augments Backbone.View
     4707 */
     4708var FocusManager = wp.media.View.extend({
     4709
     4710        events: {
     4711                'keydown': 'constrainTabbing'
     4712        },
     4713
     4714        focus: function() { // Reset focus on first left menu item
     4715                this.$('.media-menu-item').first().focus();
     4716        },
     4717        /**
     4718         * @param {Object} event
     4719         */
     4720        constrainTabbing: function( event ) {
     4721                var tabbables;
     4722
     4723                // Look for the tab key.
     4724                if ( 9 !== event.keyCode ) {
     4725                        return;
     4726                }
     4727
     4728                // Skip the file input added by Plupload.
     4729                tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
     4730
     4731                // Keep tab focus within media modal while it's open
     4732                if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
     4733                        tabbables.first().focus();
     4734                        return false;
     4735                } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
     4736                        tabbables.last().focus();
     4737                        return false;
     4738                }
     4739        }
     4740
     4741});
     4742
     4743module.exports = FocusManager;
     4744
     4745},{}],41:[function(require,module,exports){
     4746/*globals _, Backbone */
     4747
     4748/**
     4749 * wp.media.view.Frame
     4750 *
     4751 * A frame is a composite view consisting of one or more regions and one or more
     4752 * states.
     4753 *
     4754 * @see wp.media.controller.State
     4755 * @see wp.media.controller.Region
     4756 *
     4757 * @class
     4758 * @augments wp.media.View
     4759 * @augments wp.Backbone.View
     4760 * @augments Backbone.View
     4761 * @mixes wp.media.controller.StateMachine
     4762 */
     4763var Frame = wp.media.View.extend({
     4764        initialize: function() {
     4765                _.defaults( this.options, {
     4766                        mode: [ 'select' ]
     4767                });
     4768                this._createRegions();
     4769                this._createStates();
     4770                this._createModes();
     4771        },
     4772
     4773        _createRegions: function() {
     4774                // Clone the regions array.
     4775                this.regions = this.regions ? this.regions.slice() : [];
     4776
     4777                // Initialize regions.
     4778                _.each( this.regions, function( region ) {
     4779                        this[ region ] = new wp.media.controller.Region({
     4780                                view:     this,
     4781                                id:       region,
     4782                                selector: '.media-frame-' + region
     4783                        });
     4784                }, this );
     4785        },
     4786        /**
     4787         * Create the frame's states.
     4788         *
     4789         * @see wp.media.controller.State
     4790         * @see wp.media.controller.StateMachine
     4791         *
     4792         * @fires wp.media.controller.State#ready
     4793         */
     4794        _createStates: function() {
     4795                // Create the default `states` collection.
     4796                this.states = new Backbone.Collection( null, {
     4797                        model: wp.media.controller.State
     4798                });
     4799
     4800                // Ensure states have a reference to the frame.
     4801                this.states.on( 'add', function( model ) {
     4802                        model.frame = this;
     4803                        model.trigger('ready');
     4804                }, this );
     4805
     4806                if ( this.options.states ) {
     4807                        this.states.add( this.options.states );
     4808                }
     4809        },
     4810
     4811        /**
     4812         * A frame can be in a mode or multiple modes at one time.
     4813         *
     4814         * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
     4815         */
     4816        _createModes: function() {
     4817                // Store active "modes" that the frame is in. Unrelated to region modes.
     4818                this.activeModes = new Backbone.Collection();
     4819                this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
     4820
     4821                _.each( this.options.mode, function( mode ) {
     4822                        this.activateMode( mode );
     4823                }, this );
     4824        },
     4825        /**
     4826         * Reset all states on the frame to their defaults.
     4827         *
     4828         * @returns {wp.media.view.Frame} Returns itself to allow chaining
     4829         */
     4830        reset: function() {
     4831                this.states.invoke( 'trigger', 'reset' );
     4832                return this;
     4833        },
     4834        /**
     4835         * Map activeMode collection events to the frame.
     4836         */
     4837        triggerModeEvents: function( model, collection, options ) {
     4838                var collectionEvent,
     4839                        modeEventMap = {
     4840                                add: 'activate',
     4841                                remove: 'deactivate'
     4842                        },
     4843                        eventToTrigger;
     4844                // Probably a better way to do this.
     4845                _.each( options, function( value, key ) {
     4846                        if ( value ) {
     4847                                collectionEvent = key;
     4848                        }
     4849                } );
     4850
     4851                if ( ! _.has( modeEventMap, collectionEvent ) ) {
     4852                        return;
     4853                }
     4854
     4855                eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
     4856                this.trigger( eventToTrigger );
     4857        },
     4858        /**
     4859         * Activate a mode on the frame.
     4860         *
     4861         * @param string mode Mode ID.
     4862         * @returns {this} Returns itself to allow chaining.
     4863         */
     4864        activateMode: function( mode ) {
     4865                // Bail if the mode is already active.
     4866                if ( this.isModeActive( mode ) ) {
     4867                        return;
     4868                }
     4869                this.activeModes.add( [ { id: mode } ] );
     4870                // Add a CSS class to the frame so elements can be styled for the mode.
     4871                this.$el.addClass( 'mode-' + mode );
     4872
     4873                return this;
     4874        },
     4875        /**
     4876         * Deactivate a mode on the frame.
     4877         *
     4878         * @param string mode Mode ID.
     4879         * @returns {this} Returns itself to allow chaining.
     4880         */
     4881        deactivateMode: function( mode ) {
     4882                // Bail if the mode isn't active.
     4883                if ( ! this.isModeActive( mode ) ) {
     4884                        return this;
     4885                }
     4886                this.activeModes.remove( this.activeModes.where( { id: mode } ) );
     4887                this.$el.removeClass( 'mode-' + mode );
     4888                /**
     4889                 * Frame mode deactivation event.
     4890                 *
     4891                 * @event this#{mode}:deactivate
     4892                 */
     4893                this.trigger( mode + ':deactivate' );
     4894
     4895                return this;
     4896        },
     4897        /**
     4898         * Check if a mode is enabled on the frame.
     4899         *
     4900         * @param  string mode Mode ID.
     4901         * @return bool
     4902         */
     4903        isModeActive: function( mode ) {
     4904                return Boolean( this.activeModes.where( { id: mode } ).length );
     4905        }
     4906});
     4907
     4908// Make the `Frame` a `StateMachine`.
     4909_.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
     4910
     4911module.exports = Frame;
     4912
     4913},{}],42:[function(require,module,exports){
     4914/*globals wp */
     4915
     4916/**
     4917 * wp.media.view.MediaFrame.ImageDetails
     4918 *
     4919 * A media frame for manipulating an image that's already been inserted
     4920 * into a post.
     4921 *
     4922 * @class
     4923 * @augments wp.media.view.MediaFrame.Select
     4924 * @augments wp.media.view.MediaFrame
     4925 * @augments wp.media.view.Frame
     4926 * @augments wp.media.View
     4927 * @augments wp.Backbone.View
     4928 * @augments Backbone.View
     4929 * @mixes wp.media.controller.StateMachine
     4930 */
     4931var Select = wp.media.view.MediaFrame.Select,
     4932        l10n = wp.media.view.l10n,
     4933        ImageDetails;
     4934
     4935ImageDetails = Select.extend({
     4936        defaults: {
     4937                id:      'image',
     4938                url:     '',
     4939                menu:    'image-details',
     4940                content: 'image-details',
     4941                toolbar: 'image-details',
     4942                type:    'link',
     4943                title:    l10n.imageDetailsTitle,
     4944                priority: 120
     4945        },
     4946
     4947        initialize: function( options ) {
     4948                this.image = new wp.media.model.PostImage( options.metadata );
     4949                this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
     4950                Select.prototype.initialize.apply( this, arguments );
     4951        },
     4952
     4953        bindHandlers: function() {
     4954                Select.prototype.bindHandlers.apply( this, arguments );
     4955                this.on( 'menu:create:image-details', this.createMenu, this );
     4956                this.on( 'content:create:image-details', this.imageDetailsContent, this );
     4957                this.on( 'content:render:edit-image', this.editImageContent, this );
     4958                this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
     4959                // override the select toolbar
     4960                this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
     4961        },
     4962
     4963        createStates: function() {
     4964                this.states.add([
     4965                        new wp.media.controller.ImageDetails({
     4966                                image: this.image,
     4967                                editable: false
     4968                        }),
     4969                        new wp.media.controller.ReplaceImage({
     4970                                id: 'replace-image',
     4971                                library: wp.media.query( { type: 'image' } ),
     4972                                image: this.image,
     4973                                multiple:  false,
     4974                                title:     l10n.imageReplaceTitle,
     4975                                toolbar: 'replace',
     4976                                priority:  80,
     4977                                displaySettings: true
     4978                        }),
     4979                        new wp.media.controller.EditImage( {
     4980                                image: this.image,
     4981                                selection: this.options.selection
     4982                        } )
     4983                ]);
     4984        },
     4985
     4986        imageDetailsContent: function( options ) {
     4987                options.view = new wp.media.view.ImageDetails({
     4988                        controller: this,
     4989                        model: this.state().image,
     4990                        attachment: this.state().image.attachment
     4991                });
     4992        },
     4993
     4994        editImageContent: function() {
     4995                var state = this.state(),
     4996                        model = state.get('image'),
     4997                        view;
     4998
     4999                if ( ! model ) {
     5000                        return;
     5001                }
     5002
     5003                view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
     5004
     5005                this.content.set( view );
     5006
     5007                // after bringing in the frame, load the actual editor via an ajax call
     5008                view.loadEditor();
     5009
     5010        },
     5011
     5012        renderImageDetailsToolbar: function() {
     5013                this.toolbar.set( new wp.media.view.Toolbar({
     5014                        controller: this,
     5015                        items: {
     5016                                select: {
     5017                                        style:    'primary',
     5018                                        text:     l10n.update,
     5019                                        priority: 80,
     5020
     5021                                        click: function() {
     5022                                                var controller = this.controller,
     5023                                                        state = controller.state();
     5024
     5025                                                controller.close();
     5026
     5027                                                // not sure if we want to use wp.media.string.image which will create a shortcode or
     5028                                                // perhaps wp.html.string to at least to build the <img />
     5029                                                state.trigger( 'update', controller.image.toJSON() );
     5030
     5031                                                // Restore and reset the default state.
     5032                                                controller.setState( controller.options.state );
     5033                                                controller.reset();
     5034                                        }
     5035                                }
     5036                        }
     5037                }) );
     5038        },
     5039
     5040        renderReplaceImageToolbar: function() {
     5041                var frame = this,
     5042                        lastState = frame.lastState(),
     5043                        previous = lastState && lastState.id;
     5044
     5045                this.toolbar.set( new wp.media.view.Toolbar({
     5046                        controller: this,
     5047                        items: {
     5048                                back: {
     5049                                        text:     l10n.back,
     5050                                        priority: 20,
     5051                                        click:    function() {
     5052                                                if ( previous ) {
     5053                                                        frame.setState( previous );
     5054                                                } else {
     5055                                                        frame.close();
     5056                                                }
     5057                                        }
     5058                                },
     5059
     5060                                replace: {
     5061                                        style:    'primary',
     5062                                        text:     l10n.replace,
     5063                                        priority: 80,
     5064
     5065                                        click: function() {
     5066                                                var controller = this.controller,
     5067                                                        state = controller.state(),
     5068                                                        selection = state.get( 'selection' ),
     5069                                                        attachment = selection.single();
     5070
     5071                                                controller.close();
     5072
     5073                                                controller.image.changeAttachment( attachment, state.display( attachment ) );
     5074
     5075                                                // not sure if we want to use wp.media.string.image which will create a shortcode or
     5076                                                // perhaps wp.html.string to at least to build the <img />
     5077                                                state.trigger( 'replace', controller.image.toJSON() );
     5078
     5079                                                // Restore and reset the default state.
     5080                                                controller.setState( controller.options.state );
     5081                                                controller.reset();
     5082                                        }
     5083                                }
     5084                        }
     5085                }) );
     5086        }
     5087
     5088});
     5089
     5090module.exports = ImageDetails;
     5091
     5092},{}],43:[function(require,module,exports){
     5093/*globals wp, _ */
     5094
     5095/**
     5096 * wp.media.view.MediaFrame.Post
     5097 *
     5098 * The frame for manipulating media on the Edit Post page.
     5099 *
     5100 * @class
     5101 * @augments wp.media.view.MediaFrame.Select
     5102 * @augments wp.media.view.MediaFrame
     5103 * @augments wp.media.view.Frame
     5104 * @augments wp.media.View
     5105 * @augments wp.Backbone.View
     5106 * @augments Backbone.View
     5107 * @mixes wp.media.controller.StateMachine
     5108 */
     5109var Select = wp.media.view.MediaFrame.Select,
     5110        Library = wp.media.controller.Library,
     5111        l10n = wp.media.view.l10n,
     5112        Post;
     5113
     5114Post = Select.extend({
     5115        initialize: function() {
     5116                this.counts = {
     5117                        audio: {
     5118                                count: wp.media.view.settings.attachmentCounts.audio,
     5119                                state: 'playlist'
     5120                        },
     5121                        video: {
     5122                                count: wp.media.view.settings.attachmentCounts.video,
     5123                                state: 'video-playlist'
     5124                        }
     5125                };
     5126
     5127                _.defaults( this.options, {
     5128                        multiple:  true,
     5129                        editing:   false,
     5130                        state:    'insert',
     5131                        metadata:  {}
     5132                });
     5133
     5134                // Call 'initialize' directly on the parent class.
     5135                Select.prototype.initialize.apply( this, arguments );
     5136                this.createIframeStates();
     5137
     5138        },
     5139
     5140        /**
     5141         * Create the default states.
     5142         */
     5143        createStates: function() {
     5144                var options = this.options;
     5145
     5146                this.states.add([
     5147                        // Main states.
     5148                        new Library({
     5149                                id:         'insert',
     5150                                title:      l10n.insertMediaTitle,
     5151                                priority:   20,
     5152                                toolbar:    'main-insert',
     5153                                filterable: 'all',
     5154                                library:    wp.media.query( options.library ),
     5155                                multiple:   options.multiple ? 'reset' : false,
     5156                                editable:   true,
     5157
     5158                                // If the user isn't allowed to edit fields,
     5159                                // can they still edit it locally?
     5160                                allowLocalEdits: true,
     5161
     5162                                // Show the attachment display settings.
     5163                                displaySettings: true,
     5164                                // Update user settings when users adjust the
     5165                                // attachment display settings.
     5166                                displayUserSettings: true
     5167                        }),
     5168
     5169                        new Library({
     5170                                id:         'gallery',
     5171                                title:      l10n.createGalleryTitle,
     5172                                priority:   40,
     5173                                toolbar:    'main-gallery',
     5174                                filterable: 'uploaded',
     5175                                multiple:   'add',
     5176                                editable:   false,
     5177
     5178                                library:  wp.media.query( _.defaults({
     5179                                        type: 'image'
     5180                                }, options.library ) )
     5181                        }),
     5182
     5183                        // Embed states.
     5184                        new wp.media.controller.Embed( { metadata: options.metadata } ),
     5185
     5186                        new wp.media.controller.EditImage( { model: options.editImage } ),
     5187
     5188                        // Gallery states.
     5189                        new wp.media.controller.GalleryEdit({
     5190                                library: options.selection,
     5191                                editing: options.editing,
     5192                                menu:    'gallery'
     5193                        }),
     5194
     5195                        new wp.media.controller.GalleryAdd(),
     5196
     5197                        new Library({
     5198                                id:         'playlist',
     5199                                title:      l10n.createPlaylistTitle,
     5200                                priority:   60,
     5201                                toolbar:    'main-playlist',
     5202                                filterable: 'uploaded',
     5203                                multiple:   'add',
     5204                                editable:   false,
     5205
     5206                                library:  wp.media.query( _.defaults({
     5207                                        type: 'audio'
     5208                                }, options.library ) )
     5209                        }),
     5210
     5211                        // Playlist states.
     5212                        new wp.media.controller.CollectionEdit({
     5213                                type: 'audio',
     5214                                collectionType: 'playlist',
     5215                                title:          l10n.editPlaylistTitle,
     5216                                SettingsView:   wp.media.view.Settings.Playlist,
     5217                                library:        options.selection,
     5218                                editing:        options.editing,
     5219                                menu:           'playlist',
     5220                                dragInfoText:   l10n.playlistDragInfo,
     5221                                dragInfo:       false
     5222                        }),
     5223
     5224                        new wp.media.controller.CollectionAdd({
     5225                                type: 'audio',
     5226                                collectionType: 'playlist',
     5227                                title: l10n.addToPlaylistTitle
     5228                        }),
     5229
     5230                        new Library({
     5231                                id:         'video-playlist',
     5232                                title:      l10n.createVideoPlaylistTitle,
     5233                                priority:   60,
     5234                                toolbar:    'main-video-playlist',
     5235                                filterable: 'uploaded',
     5236                                multiple:   'add',
     5237                                editable:   false,
     5238
     5239                                library:  wp.media.query( _.defaults({
     5240                                        type: 'video'
     5241                                }, options.library ) )
     5242                        }),
     5243
     5244                        new wp.media.controller.CollectionEdit({
     5245                                type: 'video',
     5246                                collectionType: 'playlist',
     5247                                title:          l10n.editVideoPlaylistTitle,
     5248                                SettingsView:   wp.media.view.Settings.Playlist,
     5249                                library:        options.selection,
     5250                                editing:        options.editing,
     5251                                menu:           'video-playlist',
     5252                                dragInfoText:   l10n.videoPlaylistDragInfo,
     5253                                dragInfo:       false
     5254                        }),
     5255
     5256                        new wp.media.controller.CollectionAdd({
     5257                                type: 'video',
     5258                                collectionType: 'playlist',
     5259                                title: l10n.addToVideoPlaylistTitle
     5260                        })
     5261                ]);
     5262
     5263                if ( wp.media.view.settings.post.featuredImageId ) {
     5264                        this.states.add( new wp.media.controller.FeaturedImage() );
     5265                }
     5266        },
     5267
     5268        bindHandlers: function() {
     5269                var handlers, checkCounts;
     5270
     5271                Select.prototype.bindHandlers.apply( this, arguments );
     5272
     5273                this.on( 'activate', this.activate, this );
     5274
     5275                // Only bother checking media type counts if one of the counts is zero
     5276                checkCounts = _.find( this.counts, function( type ) {
     5277                        return type.count === 0;
     5278                } );
     5279
     5280                if ( typeof checkCounts !== 'undefined' ) {
     5281                        this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
     5282                }
     5283
     5284                this.on( 'menu:create:gallery', this.createMenu, this );
     5285                this.on( 'menu:create:playlist', this.createMenu, this );
     5286                this.on( 'menu:create:video-playlist', this.createMenu, this );
     5287                this.on( 'toolbar:create:main-insert', this.createToolbar, this );
     5288                this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
     5289                this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
     5290                this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
     5291                this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
     5292                this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
     5293
     5294                handlers = {
     5295                        menu: {
     5296                                'default': 'mainMenu',
     5297                                'gallery': 'galleryMenu',
     5298                                'playlist': 'playlistMenu',
     5299                                'video-playlist': 'videoPlaylistMenu'
     5300                        },
     5301
     5302                        content: {
     5303                                'embed':          'embedContent',
     5304                                'edit-image':     'editImageContent',
     5305                                'edit-selection': 'editSelectionContent'
     5306                        },
     5307
     5308                        toolbar: {
     5309                                'main-insert':      'mainInsertToolbar',
     5310                                'main-gallery':     'mainGalleryToolbar',
     5311                                'gallery-edit':     'galleryEditToolbar',
     5312                                'gallery-add':      'galleryAddToolbar',
     5313                                'main-playlist':        'mainPlaylistToolbar',
     5314                                'playlist-edit':        'playlistEditToolbar',
     5315                                'playlist-add':         'playlistAddToolbar',
     5316                                'main-video-playlist': 'mainVideoPlaylistToolbar',
     5317                                'video-playlist-edit': 'videoPlaylistEditToolbar',
     5318                                'video-playlist-add': 'videoPlaylistAddToolbar'
     5319                        }
     5320                };
     5321
     5322                _.each( handlers, function( regionHandlers, region ) {
     5323                        _.each( regionHandlers, function( callback, handler ) {
     5324                                this.on( region + ':render:' + handler, this[ callback ], this );
     5325                        }, this );
     5326                }, this );
     5327        },
     5328
     5329        activate: function() {
     5330                // Hide menu items for states tied to particular media types if there are no items
     5331                _.each( this.counts, function( type ) {
     5332                        if ( type.count < 1 ) {
     5333                                this.menuItemVisibility( type.state, 'hide' );
     5334                        }
     5335                }, this );
     5336        },
     5337
     5338        mediaTypeCounts: function( model, attr ) {
     5339                if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
     5340                        this.counts[ attr ].count++;
     5341                        this.menuItemVisibility( this.counts[ attr ].state, 'show' );
     5342                }
     5343        },
     5344
     5345        // Menus
     5346        /**
     5347         * @param {wp.Backbone.View} view
     5348         */
     5349        mainMenu: function( view ) {
     5350                view.set({
     5351                        'library-separator': new wp.media.View({
     5352                                className: 'separator',
     5353                                priority: 100
     5354                        })
     5355                });
     5356        },
     5357
     5358        menuItemVisibility: function( state, visibility ) {
     5359                var menu = this.menu.get();
     5360                if ( visibility === 'hide' ) {
     5361                        menu.hide( state );
     5362                } else if ( visibility === 'show' ) {
     5363                        menu.show( state );
     5364                }
     5365        },
     5366        /**
     5367         * @param {wp.Backbone.View} view
     5368         */
     5369        galleryMenu: function( view ) {
     5370                var lastState = this.lastState(),
     5371                        previous = lastState && lastState.id,
     5372                        frame = this;
     5373
     5374                view.set({
     5375                        cancel: {
     5376                                text:     l10n.cancelGalleryTitle,
     5377                                priority: 20,
     5378                                click:    function() {
     5379                                        if ( previous ) {
     5380                                                frame.setState( previous );
     5381                                        } else {
     5382                                                frame.close();
     5383                                        }
     5384
     5385                                        // Keep focus inside media modal
     5386                                        // after canceling a gallery
     5387                                        this.controller.modal.focusManager.focus();
     5388                                }
     5389                        },
     5390                        separateCancel: new wp.media.View({
     5391                                className: 'separator',
     5392                                priority: 40
     5393                        })
     5394                });
     5395        },
     5396
     5397        playlistMenu: function( view ) {
     5398                var lastState = this.lastState(),
     5399                        previous = lastState && lastState.id,
     5400                        frame = this;
     5401
     5402                view.set({
     5403                        cancel: {
     5404                                text:     l10n.cancelPlaylistTitle,
     5405                                priority: 20,
     5406                                click:    function() {
     5407                                        if ( previous ) {
     5408                                                frame.setState( previous );
     5409                                        } else {
     5410                                                frame.close();
     5411                                        }
     5412                                }
     5413                        },
     5414                        separateCancel: new wp.media.View({
     5415                                className: 'separator',
     5416                                priority: 40
     5417                        })
     5418                });
     5419        },
     5420
     5421        videoPlaylistMenu: function( view ) {
     5422                var lastState = this.lastState(),
     5423                        previous = lastState && lastState.id,
     5424                        frame = this;
     5425
     5426                view.set({
     5427                        cancel: {
     5428                                text:     l10n.cancelVideoPlaylistTitle,
     5429                                priority: 20,
     5430                                click:    function() {
     5431                                        if ( previous ) {
     5432                                                frame.setState( previous );
     5433                                        } else {
     5434                                                frame.close();
     5435                                        }
     5436                                }
     5437                        },
     5438                        separateCancel: new wp.media.View({
     5439                                className: 'separator',
     5440                                priority: 40
     5441                        })
     5442                });
     5443        },
     5444
     5445        // Content
     5446        embedContent: function() {
     5447                var view = new wp.media.view.Embed({
     5448                        controller: this,
     5449                        model:      this.state()
     5450                }).render();
     5451
     5452                this.content.set( view );
     5453
     5454                if ( ! wp.media.isTouchDevice ) {
     5455                        view.url.focus();
     5456                }
     5457        },
     5458
     5459        editSelectionContent: function() {
     5460                var state = this.state(),
     5461                        selection = state.get('selection'),
     5462                        view;
     5463
     5464                view = new wp.media.view.AttachmentsBrowser({
     5465                        controller: this,
     5466                        collection: selection,
     5467                        selection:  selection,
     5468                        model:      state,
     5469                        sortable:   true,
     5470                        search:     false,
     5471                        date:       false,
     5472                        dragInfo:   true,
     5473
     5474                        AttachmentView: wp.media.view.Attachments.EditSelection
     5475                }).render();
     5476
     5477                view.toolbar.set( 'backToLibrary', {
     5478                        text:     l10n.returnToLibrary,
     5479                        priority: -100,
     5480
     5481                        click: function() {
     5482                                this.controller.content.mode('browse');
     5483                        }
     5484                });
     5485
     5486                // Browse our library of attachments.
     5487                this.content.set( view );
     5488
     5489                // Trigger the controller to set focus
     5490                this.trigger( 'edit:selection', this );
     5491        },
     5492
     5493        editImageContent: function() {
     5494                var image = this.state().get('image'),
     5495                        view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
     5496
     5497                this.content.set( view );
     5498
     5499                // after creating the wrapper view, load the actual editor via an ajax call
     5500                view.loadEditor();
     5501
     5502        },
     5503
     5504        // Toolbars
     5505
     5506        /**
     5507         * @param {wp.Backbone.View} view
     5508         */
     5509        selectionStatusToolbar: function( view ) {
     5510                var editable = this.state().get('editable');
     5511
     5512                view.set( 'selection', new wp.media.view.Selection({
     5513                        controller: this,
     5514                        collection: this.state().get('selection'),
     5515                        priority:   -40,
     5516
     5517                        // If the selection is editable, pass the callback to
     5518                        // switch the content mode.
     5519                        editable: editable && function() {
     5520                                this.controller.content.mode('edit-selection');
     5521                        }
     5522                }).render() );
     5523        },
     5524
     5525        /**
     5526         * @param {wp.Backbone.View} view
     5527         */
     5528        mainInsertToolbar: function( view ) {
     5529                var controller = this;
     5530
     5531                this.selectionStatusToolbar( view );
     5532
     5533                view.set( 'insert', {
     5534                        style:    'primary',
     5535                        priority: 80,
     5536                        text:     l10n.insertIntoPost,
     5537                        requires: { selection: true },
     5538
     5539                        /**
     5540                         * @fires wp.media.controller.State#insert
     5541                         */
     5542                        click: function() {
     5543                                var state = controller.state(),
     5544                                        selection = state.get('selection');
     5545
     5546                                controller.close();
     5547                                state.trigger( 'insert', selection ).reset();
     5548                        }
     5549                });
     5550        },
     5551
     5552        /**
     5553         * @param {wp.Backbone.View} view
     5554         */
     5555        mainGalleryToolbar: function( view ) {
     5556                var controller = this;
     5557
     5558                this.selectionStatusToolbar( view );
     5559
     5560                view.set( 'gallery', {
     5561                        style:    'primary',
     5562                        text:     l10n.createNewGallery,
     5563                        priority: 60,
     5564                        requires: { selection: true },
     5565
     5566                        click: function() {
     5567                                var selection = controller.state().get('selection'),
     5568                                        edit = controller.state('gallery-edit'),
     5569                                        models = selection.where({ type: 'image' });
     5570
     5571                                edit.set( 'library', new wp.media.model.Selection( models, {
     5572                                        props:    selection.props.toJSON(),
     5573                                        multiple: true
     5574                                }) );
     5575
     5576                                this.controller.setState('gallery-edit');
     5577
     5578                                // Keep focus inside media modal
     5579                                // after jumping to gallery view
     5580                                this.controller.modal.focusManager.focus();
     5581                        }
     5582                });
     5583        },
     5584
     5585        mainPlaylistToolbar: function( view ) {
     5586                var controller = this;
     5587
     5588                this.selectionStatusToolbar( view );
     5589
     5590                view.set( 'playlist', {
     5591                        style:    'primary',
     5592                        text:     l10n.createNewPlaylist,
     5593                        priority: 100,
     5594                        requires: { selection: true },
     5595
     5596                        click: function() {
     5597                                var selection = controller.state().get('selection'),
     5598                                        edit = controller.state('playlist-edit'),
     5599                                        models = selection.where({ type: 'audio' });
     5600
     5601                                edit.set( 'library', new wp.media.model.Selection( models, {
     5602                                        props:    selection.props.toJSON(),
     5603                                        multiple: true
     5604                                }) );
     5605
     5606                                this.controller.setState('playlist-edit');
     5607
     5608                                // Keep focus inside media modal
     5609                                // after jumping to playlist view
     5610                                this.controller.modal.focusManager.focus();
     5611                        }
     5612                });
     5613        },
     5614
     5615        mainVideoPlaylistToolbar: function( view ) {
     5616                var controller = this;
     5617
     5618                this.selectionStatusToolbar( view );
     5619
     5620                view.set( 'video-playlist', {
     5621                        style:    'primary',
     5622                        text:     l10n.createNewVideoPlaylist,
     5623                        priority: 100,
     5624                        requires: { selection: true },
     5625
     5626                        click: function() {
     5627                                var selection = controller.state().get('selection'),
     5628                                        edit = controller.state('video-playlist-edit'),
     5629                                        models = selection.where({ type: 'video' });
     5630
     5631                                edit.set( 'library', new wp.media.model.Selection( models, {
     5632                                        props:    selection.props.toJSON(),
     5633                                        multiple: true
     5634                                }) );
     5635
     5636                                this.controller.setState('video-playlist-edit');
     5637
     5638                                // Keep focus inside media modal
     5639                                // after jumping to video playlist view
     5640                                this.controller.modal.focusManager.focus();
     5641                        }
     5642                });
     5643        },
     5644
     5645        featuredImageToolbar: function( toolbar ) {
     5646                this.createSelectToolbar( toolbar, {
     5647                        text:  l10n.setFeaturedImage,
     5648                        state: this.options.state
     5649                });
     5650        },
     5651
     5652        mainEmbedToolbar: function( toolbar ) {
     5653                toolbar.view = new wp.media.view.Toolbar.Embed({
     5654                        controller: this
     5655                });
     5656        },
     5657
     5658        galleryEditToolbar: function() {
     5659                var editing = this.state().get('editing');
     5660                this.toolbar.set( new wp.media.view.Toolbar({
     5661                        controller: this,
     5662                        items: {
     5663                                insert: {
     5664                                        style:    'primary',
     5665                                        text:     editing ? l10n.updateGallery : l10n.insertGallery,
     5666                                        priority: 80,
     5667                                        requires: { library: true },
     5668
     5669                                        /**
     5670                                         * @fires wp.media.controller.State#update
     5671                                         */
     5672                                        click: function() {
     5673                                                var controller = this.controller,
     5674                                                        state = controller.state();
     5675
     5676                                                controller.close();
     5677                                                state.trigger( 'update', state.get('library') );
     5678
     5679                                                // Restore and reset the default state.
     5680                                                controller.setState( controller.options.state );
     5681                                                controller.reset();
     5682                                        }
     5683                                }
     5684                        }
     5685                }) );
     5686        },
     5687
     5688        galleryAddToolbar: function() {
     5689                this.toolbar.set( new wp.media.view.Toolbar({
     5690                        controller: this,
     5691                        items: {
     5692                                insert: {
     5693                                        style:    'primary',
     5694                                        text:     l10n.addToGallery,
     5695                                        priority: 80,
     5696                                        requires: { selection: true },
     5697
     5698                                        /**
     5699                                         * @fires wp.media.controller.State#reset
     5700                                         */
     5701                                        click: function() {
     5702                                                var controller = this.controller,
     5703                                                        state = controller.state(),
     5704                                                        edit = controller.state('gallery-edit');
     5705
     5706                                                edit.get('library').add( state.get('selection').models );
     5707                                                state.trigger('reset');
     5708                                                controller.setState('gallery-edit');
     5709                                        }
     5710                                }
     5711                        }
     5712                }) );
     5713        },
     5714
     5715        playlistEditToolbar: function() {
     5716                var editing = this.state().get('editing');
     5717                this.toolbar.set( new wp.media.view.Toolbar({
     5718                        controller: this,
     5719                        items: {
     5720                                insert: {
     5721                                        style:    'primary',
     5722                                        text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
     5723                                        priority: 80,
     5724                                        requires: { library: true },
     5725
     5726                                        /**
     5727                                         * @fires wp.media.controller.State#update
     5728                                         */
     5729                                        click: function() {
     5730                                                var controller = this.controller,
     5731                                                        state = controller.state();
     5732
     5733                                                controller.close();
     5734                                                state.trigger( 'update', state.get('library') );
     5735
     5736                                                // Restore and reset the default state.
     5737                                                controller.setState( controller.options.state );
     5738                                                controller.reset();
     5739                                        }
     5740                                }
     5741                        }
     5742                }) );
     5743        },
     5744
     5745        playlistAddToolbar: function() {
     5746                this.toolbar.set( new wp.media.view.Toolbar({
     5747                        controller: this,
     5748                        items: {
     5749                                insert: {
     5750                                        style:    'primary',
     5751                                        text:     l10n.addToPlaylist,
     5752                                        priority: 80,
     5753                                        requires: { selection: true },
     5754
     5755                                        /**
     5756                                         * @fires wp.media.controller.State#reset
     5757                                         */
     5758                                        click: function() {
     5759                                                var controller = this.controller,
     5760                                                        state = controller.state(),
     5761                                                        edit = controller.state('playlist-edit');
     5762
     5763                                                edit.get('library').add( state.get('selection').models );
     5764                                                state.trigger('reset');
     5765                                                controller.setState('playlist-edit');
     5766                                        }
     5767                                }
     5768                        }
     5769                }) );
     5770        },
     5771
     5772        videoPlaylistEditToolbar: function() {
     5773                var editing = this.state().get('editing');
     5774                this.toolbar.set( new wp.media.view.Toolbar({
     5775                        controller: this,
     5776                        items: {
     5777                                insert: {
     5778                                        style:    'primary',
     5779                                        text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
     5780                                        priority: 140,
     5781                                        requires: { library: true },
     5782
     5783                                        click: function() {
     5784                                                var controller = this.controller,
     5785                                                        state = controller.state(),
     5786                                                        library = state.get('library');
     5787
     5788                                                library.type = 'video';
     5789
     5790                                                controller.close();
     5791                                                state.trigger( 'update', library );
     5792
     5793                                                // Restore and reset the default state.
     5794                                                controller.setState( controller.options.state );
     5795                                                controller.reset();
     5796                                        }
     5797                                }
     5798                        }
     5799                }) );
     5800        },
     5801
     5802        videoPlaylistAddToolbar: function() {
     5803                this.toolbar.set( new wp.media.view.Toolbar({
     5804                        controller: this,
     5805                        items: {
     5806                                insert: {
     5807                                        style:    'primary',
     5808                                        text:     l10n.addToVideoPlaylist,
     5809                                        priority: 140,
     5810                                        requires: { selection: true },
     5811
     5812                                        click: function() {
     5813                                                var controller = this.controller,
     5814                                                        state = controller.state(),
     5815                                                        edit = controller.state('video-playlist-edit');
     5816
     5817                                                edit.get('library').add( state.get('selection').models );
     5818                                                state.trigger('reset');
     5819                                                controller.setState('video-playlist-edit');
     5820                                        }
     5821                                }
     5822                        }
     5823                }) );
     5824        }
     5825});
     5826
     5827module.exports = Post;
     5828
     5829},{}],44:[function(require,module,exports){
     5830/*globals wp, _ */
     5831
     5832/**
     5833 * wp.media.view.MediaFrame.Select
     5834 *
     5835 * A frame for selecting an item or items from the media library.
     5836 *
     5837 * @class
     5838 * @augments wp.media.view.MediaFrame
     5839 * @augments wp.media.view.Frame
     5840 * @augments wp.media.View
     5841 * @augments wp.Backbone.View
     5842 * @augments Backbone.View
     5843 * @mixes wp.media.controller.StateMachine
     5844 */
     5845
     5846var MediaFrame = wp.media.view.MediaFrame,
     5847        l10n = wp.media.view.l10n,
     5848        Select;
     5849
     5850Select = MediaFrame.extend({
     5851        initialize: function() {
     5852                // Call 'initialize' directly on the parent class.
     5853                MediaFrame.prototype.initialize.apply( this, arguments );
     5854
     5855                _.defaults( this.options, {
     5856                        selection: [],
     5857                        library:   {},
     5858                        multiple:  false,
     5859                        state:    'library'
     5860                });
     5861
     5862                this.createSelection();
     5863                this.createStates();
     5864                this.bindHandlers();
     5865        },
     5866
     5867        /**
     5868         * Attach a selection collection to the frame.
     5869         *
     5870         * A selection is a collection of attachments used for a specific purpose
     5871         * by a media frame. e.g. Selecting an attachment (or many) to insert into
     5872         * post content.
     5873         *
     5874         * @see media.model.Selection
     5875         */
     5876        createSelection: function() {
     5877                var selection = this.options.selection;
     5878
     5879                if ( ! (selection instanceof wp.media.model.Selection) ) {
     5880                        this.options.selection = new wp.media.model.Selection( selection, {
     5881                                multiple: this.options.multiple
     5882                        });
     5883                }
     5884
     5885                this._selection = {
     5886                        attachments: new wp.media.model.Attachments(),
     5887                        difference: []
     5888                };
     5889        },
     5890
     5891        /**
     5892         * Create the default states on the frame.
     5893         */
     5894        createStates: function() {
     5895                var options = this.options;
     5896
     5897                if ( this.options.states ) {
     5898                        return;
     5899                }
     5900
     5901                // Add the default states.
     5902                this.states.add([
     5903                        // Main states.
     5904                        new wp.media.controller.Library({
     5905                                library:   wp.media.query( options.library ),
     5906                                multiple:  options.multiple,
     5907                                title:     options.title,
     5908                                priority:  20
     5909                        })
     5910                ]);
     5911        },
     5912
     5913        /**
     5914         * Bind region mode event callbacks.
     5915         *
     5916         * @see media.controller.Region.render
     5917         */
     5918        bindHandlers: function() {
     5919                this.on( 'router:create:browse', this.createRouter, this );
     5920                this.on( 'router:render:browse', this.browseRouter, this );
     5921                this.on( 'content:create:browse', this.browseContent, this );
     5922                this.on( 'content:render:upload', this.uploadContent, this );
     5923                this.on( 'toolbar:create:select', this.createSelectToolbar, this );
     5924        },
     5925
     5926        /**
     5927         * Render callback for the router region in the `browse` mode.
     5928         *
     5929         * @param {wp.media.view.Router} routerView
     5930         */
     5931        browseRouter: function( routerView ) {
     5932                routerView.set({
     5933                        upload: {
     5934                                text:     l10n.uploadFilesTitle,
     5935                                priority: 20
     5936                        },
     5937                        browse: {
     5938                                text:     l10n.mediaLibraryTitle,
     5939                                priority: 40
     5940                        }
     5941                });
     5942        },
     5943
     5944        /**
     5945         * Render callback for the content region in the `browse` mode.
     5946         *
     5947         * @param {wp.media.controller.Region} contentRegion
     5948         */
     5949        browseContent: function( contentRegion ) {
     5950                var state = this.state();
     5951
     5952                this.$el.removeClass('hide-toolbar');
     5953
     5954                // Browse our library of attachments.
     5955                contentRegion.view = new wp.media.view.AttachmentsBrowser({
     5956                        controller: this,
     5957                        collection: state.get('library'),
     5958                        selection:  state.get('selection'),
     5959                        model:      state,
     5960                        sortable:   state.get('sortable'),
     5961                        search:     state.get('searchable'),
     5962                        filters:    state.get('filterable'),
     5963                        display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
     5964                        dragInfo:   state.get('dragInfo'),
     5965
     5966                        idealColumnWidth: state.get('idealColumnWidth'),
     5967                        suggestedWidth:   state.get('suggestedWidth'),
     5968                        suggestedHeight:  state.get('suggestedHeight'),
     5969
     5970                        AttachmentView: state.get('AttachmentView')
     5971                });
     5972        },
     5973
     5974        /**
     5975         * Render callback for the content region in the `upload` mode.
     5976         */
     5977        uploadContent: function() {
     5978                this.$el.removeClass( 'hide-toolbar' );
     5979                this.content.set( new wp.media.view.UploaderInline({
     5980                        controller: this
     5981                }) );
     5982        },
     5983
     5984        /**
     5985         * Toolbars
     5986         *
     5987         * @param {Object} toolbar
     5988         * @param {Object} [options={}]
     5989         * @this wp.media.controller.Region
     5990         */
     5991        createSelectToolbar: function( toolbar, options ) {
     5992                options = options || this.options.button || {};
     5993                options.controller = this;
     5994
     5995                toolbar.view = new wp.media.view.Toolbar.Select( options );
     5996        }
     5997});
     5998
     5999module.exports = Select;
     6000
     6001},{}],45:[function(require,module,exports){
     6002/**
     6003 * wp.media.view.Iframe
     6004 *
     6005 * @class
     6006 * @augments wp.media.View
     6007 * @augments wp.Backbone.View
     6008 * @augments Backbone.View
     6009 */
     6010var Iframe = wp.media.View.extend({
     6011        className: 'media-iframe',
     6012        /**
     6013         * @returns {wp.media.view.Iframe} Returns itself to allow chaining
     6014         */
     6015        render: function() {
     6016                this.views.detach();
     6017                this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
     6018                this.views.render();
     6019                return this;
     6020        }
     6021});
     6022
     6023module.exports = Iframe;
     6024
     6025},{}],46:[function(require,module,exports){
     6026/*globals wp, _, jQuery */
     6027
     6028/**
     6029 * wp.media.view.ImageDetails
     6030 *
     6031 * @class
     6032 * @augments wp.media.view.Settings.AttachmentDisplay
     6033 * @augments wp.media.view.Settings
     6034 * @augments wp.media.View
     6035 * @augments wp.Backbone.View
     6036 * @augments Backbone.View
     6037 */
     6038var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
     6039        $ = jQuery,
     6040        ImageDetails;
     6041
     6042ImageDetails = AttachmentDisplay.extend({
     6043        className: 'image-details',
     6044        template:  wp.template('image-details'),
     6045        events: _.defaults( AttachmentDisplay.prototype.events, {
     6046                'click .edit-attachment': 'editAttachment',
     6047                'click .replace-attachment': 'replaceAttachment',
     6048                'click .advanced-toggle': 'onToggleAdvanced',
     6049                'change [data-setting="customWidth"]': 'onCustomSize',
     6050                'change [data-setting="customHeight"]': 'onCustomSize',
     6051                'keyup [data-setting="customWidth"]': 'onCustomSize',
     6052                'keyup [data-setting="customHeight"]': 'onCustomSize'
     6053        } ),
     6054        initialize: function() {
     6055                // used in AttachmentDisplay.prototype.updateLinkTo
     6056                this.options.attachment = this.model.attachment;
     6057                this.listenTo( this.model, 'change:url', this.updateUrl );
     6058                this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
     6059                this.listenTo( this.model, 'change:size', this.toggleCustomSize );
     6060
     6061                AttachmentDisplay.prototype.initialize.apply( this, arguments );
     6062        },
     6063
     6064        prepare: function() {
     6065                var attachment = false;
     6066
     6067                if ( this.model.attachment ) {
     6068                        attachment = this.model.attachment.toJSON();
     6069                }
     6070                return _.defaults({
     6071                        model: this.model.toJSON(),
     6072                        attachment: attachment
     6073                }, this.options );
     6074        },
     6075
     6076        render: function() {
     6077                var args = arguments;
     6078
     6079                if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
     6080                        this.model.dfd
     6081                                .done( _.bind( function() {
     6082                                        AttachmentDisplay.prototype.render.apply( this, args );
     6083                                        this.postRender();
     6084                                }, this ) )
     6085                                .fail( _.bind( function() {
     6086                                        this.model.attachment = false;
     6087                                        AttachmentDisplay.prototype.render.apply( this, args );
     6088                                        this.postRender();
     6089                                }, this ) );
     6090                } else {
     6091                        AttachmentDisplay.prototype.render.apply( this, arguments );
     6092                        this.postRender();
     6093                }
     6094
     6095                return this;
     6096        },
     6097
     6098        postRender: function() {
     6099                setTimeout( _.bind( this.resetFocus, this ), 10 );
     6100                this.toggleLinkSettings();
     6101                if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
     6102                        this.toggleAdvanced( true );
     6103                }
     6104                this.trigger( 'post-render' );
     6105        },
     6106
     6107        resetFocus: function() {
     6108                this.$( '.link-to-custom' ).blur();
     6109                this.$( '.embed-media-settings' ).scrollTop( 0 );
     6110        },
     6111
     6112        updateUrl: function() {
     6113                this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
     6114                this.$( '.url' ).val( this.model.get( 'url' ) );
     6115        },
     6116
     6117        toggleLinkSettings: function() {
     6118                if ( this.model.get( 'link' ) === 'none' ) {
     6119                        this.$( '.link-settings' ).addClass('hidden');
     6120                } else {
     6121                        this.$( '.link-settings' ).removeClass('hidden');
     6122                }
     6123        },
     6124
     6125        toggleCustomSize: function() {
     6126                if ( this.model.get( 'size' ) !== 'custom' ) {
     6127                        this.$( '.custom-size' ).addClass('hidden');
     6128                } else {
     6129                        this.$( '.custom-size' ).removeClass('hidden');
     6130                }
     6131        },
     6132
     6133        onCustomSize: function( event ) {
     6134                var dimension = $( event.target ).data('setting'),
     6135                        num = $( event.target ).val(),
     6136                        value;
     6137
     6138                // Ignore bogus input
     6139                if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
     6140                        event.preventDefault();
     6141                        return;
     6142                }
     6143
     6144                if ( dimension === 'customWidth' ) {
     6145                        value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );
     6146                        this.model.set( 'customHeight', value, { silent: true } );
     6147                        this.$( '[data-setting="customHeight"]' ).val( value );
     6148                } else {
     6149                        value = Math.round( this.model.get( 'aspectRatio' ) * num );
     6150                        this.model.set( 'customWidth', value, { silent: true  } );
     6151                        this.$( '[data-setting="customWidth"]' ).val( value );
     6152                }
     6153        },
     6154
     6155        onToggleAdvanced: function( event ) {
     6156                event.preventDefault();
     6157                this.toggleAdvanced();
     6158        },
     6159
     6160        toggleAdvanced: function( show ) {
     6161                var $advanced = this.$el.find( '.advanced-section' ),
     6162                        mode;
     6163
     6164                if ( $advanced.hasClass('advanced-visible') || show === false ) {
     6165                        $advanced.removeClass('advanced-visible');
     6166                        $advanced.find('.advanced-settings').addClass('hidden');
     6167                        mode = 'hide';
     6168                } else {
     6169                        $advanced.addClass('advanced-visible');
     6170                        $advanced.find('.advanced-settings').removeClass('hidden');
     6171                        mode = 'show';
     6172                }
     6173
     6174                window.setUserSetting( 'advImgDetails', mode );
     6175        },
     6176
     6177        editAttachment: function( event ) {
     6178                var editState = this.controller.states.get( 'edit-image' );
     6179
     6180                if ( window.imageEdit && editState ) {
     6181                        event.preventDefault();
     6182                        editState.set( 'image', this.model.attachment );
     6183                        this.controller.setState( 'edit-image' );
     6184                }
     6185        },
     6186
     6187        replaceAttachment: function( event ) {
     6188                event.preventDefault();
     6189                this.controller.setState( 'replace-image' );
     6190        }
     6191});
     6192
     6193module.exports = ImageDetails;
     6194
     6195},{}],47:[function(require,module,exports){
     6196/**
     6197 * wp.media.view.Label
     6198 *
     6199 * @class
     6200 * @augments wp.media.View
     6201 * @augments wp.Backbone.View
     6202 * @augments Backbone.View
     6203 */
     6204var Label = wp.media.View.extend({
     6205        tagName: 'label',
     6206        className: 'screen-reader-text',
     6207
     6208        initialize: function() {
     6209                this.value = this.options.value;
     6210        },
     6211
     6212        render: function() {
     6213                this.$el.html( this.value );
     6214
     6215                return this;
     6216        }
     6217});
     6218
     6219module.exports = Label;
     6220
     6221},{}],48:[function(require,module,exports){
     6222/*globals wp, _, jQuery */
     6223
     6224/**
     6225 * wp.media.view.MediaFrame
     6226 *
     6227 * The frame used to create the media modal.
     6228 *
     6229 * @class
     6230 * @augments wp.media.view.Frame
     6231 * @augments wp.media.View
     6232 * @augments wp.Backbone.View
     6233 * @augments Backbone.View
     6234 * @mixes wp.media.controller.StateMachine
     6235 */
     6236var Frame = wp.media.view.Frame,
     6237        $ = jQuery,
     6238        MediaFrame;
     6239
     6240MediaFrame = Frame.extend({
     6241        className: 'media-frame',
     6242        template:  wp.template('media-frame'),
     6243        regions:   ['menu','title','content','toolbar','router'],
     6244
     6245        events: {
     6246                'click div.media-frame-title h1': 'toggleMenu'
     6247        },
     6248
     6249        /**
     6250         * @global wp.Uploader
     6251         */
     6252        initialize: function() {
     6253                Frame.prototype.initialize.apply( this, arguments );
     6254
     6255                _.defaults( this.options, {
     6256                        title:    '',
     6257                        modal:    true,
     6258                        uploader: true
     6259                });
     6260
     6261                // Ensure core UI is enabled.
     6262                this.$el.addClass('wp-core-ui');
     6263
     6264                // Initialize modal container view.
     6265                if ( this.options.modal ) {
     6266                        this.modal = new wp.media.view.Modal({
     6267                                controller: this,
     6268                                title:      this.options.title
     6269                        });
     6270
     6271                        this.modal.content( this );
     6272                }
     6273
     6274                // Force the uploader off if the upload limit has been exceeded or
     6275                // if the browser isn't supported.
     6276                if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     6277                        this.options.uploader = false;
     6278                }
     6279
     6280                // Initialize window-wide uploader.
     6281                if ( this.options.uploader ) {
     6282                        this.uploader = new wp.media.view.UploaderWindow({
     6283                                controller: this,
     6284                                uploader: {
     6285                                        dropzone:  this.modal ? this.modal.$el : this.$el,
     6286                                        container: this.$el
     6287                                }
     6288                        });
     6289                        this.views.set( '.media-frame-uploader', this.uploader );
     6290                }
     6291
     6292                this.on( 'attach', _.bind( this.views.ready, this.views ), this );
     6293
     6294                // Bind default title creation.
     6295                this.on( 'title:create:default', this.createTitle, this );
     6296                this.title.mode('default');
     6297
     6298                this.on( 'title:render', function( view ) {
     6299                        view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
     6300                });
     6301
     6302                // Bind default menu.
     6303                this.on( 'menu:create:default', this.createMenu, this );
     6304        },
     6305        /**
     6306         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     6307         */
     6308        render: function() {
     6309                // Activate the default state if no active state exists.
     6310                if ( ! this.state() && this.options.state ) {
     6311                        this.setState( this.options.state );
     6312                }
     6313                /**
     6314                 * call 'render' directly on the parent class
     6315                 */
     6316                return Frame.prototype.render.apply( this, arguments );
     6317        },
     6318        /**
     6319         * @param {Object} title
     6320         * @this wp.media.controller.Region
     6321         */
     6322        createTitle: function( title ) {
     6323                title.view = new wp.media.View({
     6324                        controller: this,
     6325                        tagName: 'h1'
     6326                });
     6327        },
     6328        /**
     6329         * @param {Object} menu
     6330         * @this wp.media.controller.Region
     6331         */
     6332        createMenu: function( menu ) {
     6333                menu.view = new wp.media.view.Menu({
     6334                        controller: this
     6335                });
     6336        },
     6337
     6338        toggleMenu: function() {
     6339                this.$el.find( '.media-menu' ).toggleClass( 'visible' );
     6340        },
     6341
     6342        /**
     6343         * @param {Object} toolbar
     6344         * @this wp.media.controller.Region
     6345         */
     6346        createToolbar: function( toolbar ) {
     6347                toolbar.view = new wp.media.view.Toolbar({
     6348                        controller: this
     6349                });
     6350        },
     6351        /**
     6352         * @param {Object} router
     6353         * @this wp.media.controller.Region
     6354         */
     6355        createRouter: function( router ) {
     6356                router.view = new wp.media.view.Router({
     6357                        controller: this
     6358                });
     6359        },
     6360        /**
     6361         * @param {Object} options
     6362         */
     6363        createIframeStates: function( options ) {
     6364                var settings = wp.media.view.settings,
     6365                        tabs = settings.tabs,
     6366                        tabUrl = settings.tabUrl,
     6367                        $postId;
     6368
     6369                if ( ! tabs || ! tabUrl ) {
     6370                        return;
     6371                }
     6372
     6373                // Add the post ID to the tab URL if it exists.
     6374                $postId = $('#post_ID');
     6375                if ( $postId.length ) {
     6376                        tabUrl += '&post_id=' + $postId.val();
     6377                }
     6378
     6379                // Generate the tab states.
     6380                _.each( tabs, function( title, id ) {
     6381                        this.state( 'iframe:' + id ).set( _.defaults({
     6382                                tab:     id,
     6383                                src:     tabUrl + '&tab=' + id,
     6384                                title:   title,
     6385                                content: 'iframe',
     6386                                menu:    'default'
     6387                        }, options ) );
     6388                }, this );
     6389
     6390                this.on( 'content:create:iframe', this.iframeContent, this );
     6391                this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
     6392                this.on( 'menu:render:default', this.iframeMenu, this );
     6393                this.on( 'open', this.hijackThickbox, this );
     6394                this.on( 'close', this.restoreThickbox, this );
     6395        },
     6396
     6397        /**
     6398         * @param {Object} content
     6399         * @this wp.media.controller.Region
     6400         */
     6401        iframeContent: function( content ) {
     6402                this.$el.addClass('hide-toolbar');
     6403                content.view = new wp.media.view.Iframe({
     6404                        controller: this
     6405                });
     6406        },
     6407
     6408        iframeContentCleanup: function() {
     6409                this.$el.removeClass('hide-toolbar');
     6410        },
     6411
     6412        iframeMenu: function( view ) {
     6413                var views = {};
     6414
     6415                if ( ! view ) {
     6416                        return;
     6417                }
     6418
     6419                _.each( wp.media.view.settings.tabs, function( title, id ) {
     6420                        views[ 'iframe:' + id ] = {
     6421                                text: this.state( 'iframe:' + id ).get('title'),
     6422                                priority: 200
     6423                        };
     6424                }, this );
     6425
     6426                view.set( views );
     6427        },
     6428
     6429        hijackThickbox: function() {
     6430                var frame = this;
     6431
     6432                if ( ! window.tb_remove || this._tb_remove ) {
     6433                        return;
     6434                }
     6435
     6436                this._tb_remove = window.tb_remove;
     6437                window.tb_remove = function() {
     6438                        frame.close();
     6439                        frame.reset();
     6440                        frame.setState( frame.options.state );
     6441                        frame._tb_remove.call( window );
     6442                };
     6443        },
     6444
     6445        restoreThickbox: function() {
     6446                if ( ! this._tb_remove ) {
     6447                        return;
     6448                }
     6449
     6450                window.tb_remove = this._tb_remove;
     6451                delete this._tb_remove;
     6452        }
     6453});
     6454
     6455// Map some of the modal's methods to the frame.
     6456_.each(['open','close','attach','detach','escape'], function( method ) {
     6457        /**
     6458         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     6459         */
     6460        MediaFrame.prototype[ method ] = function() {
     6461                if ( this.modal ) {
     6462                        this.modal[ method ].apply( this.modal, arguments );
     6463                }
     6464                return this;
     6465        };
     6466});
     6467
     6468module.exports = MediaFrame;
     6469
     6470},{}],49:[function(require,module,exports){
     6471/*globals jQuery */
     6472
     6473/**
     6474 * wp.media.view.MenuItem
     6475 *
     6476 * @class
     6477 * @augments wp.media.View
     6478 * @augments wp.Backbone.View
     6479 * @augments Backbone.View
     6480 */
     6481var $ = jQuery,
     6482        MenuItem;
     6483
     6484MenuItem = wp.media.View.extend({
     6485        tagName:   'a',
     6486        className: 'media-menu-item',
     6487
     6488        attributes: {
     6489                href: '#'
     6490        },
     6491
     6492        events: {
     6493                'click': '_click'
     6494        },
     6495        /**
     6496         * @param {Object} event
     6497         */
     6498        _click: function( event ) {
     6499                var clickOverride = this.options.click;
     6500
     6501                if ( event ) {
     6502                        event.preventDefault();
     6503                }
     6504
     6505                if ( clickOverride ) {
     6506                        clickOverride.call( this );
     6507                } else {
     6508                        this.click();
     6509                }
     6510
     6511                // When selecting a tab along the left side,
     6512                // focus should be transferred into the main panel
     6513                if ( ! wp.media.isTouchDevice ) {
     6514                        $('.media-frame-content input').first().focus();
     6515                }
     6516        },
     6517
     6518        click: function() {
     6519                var state = this.options.state;
     6520
     6521                if ( state ) {
     6522                        this.controller.setState( state );
     6523                        this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
     6524                }
     6525        },
     6526        /**
     6527         * @returns {wp.media.view.MenuItem} returns itself to allow chaining
     6528         */
     6529        render: function() {
     6530                var options = this.options;
     6531
     6532                if ( options.text ) {
     6533                        this.$el.text( options.text );
     6534                } else if ( options.html ) {
     6535                        this.$el.html( options.html );
     6536                }
     6537
     6538                return this;
     6539        }
     6540});
     6541
     6542module.exports = MenuItem;
     6543
     6544},{}],50:[function(require,module,exports){
     6545/**
     6546 * wp.media.view.Menu
     6547 *
     6548 * @class
     6549 * @augments wp.media.view.PriorityList
     6550 * @augments wp.media.View
     6551 * @augments wp.Backbone.View
     6552 * @augments Backbone.View
     6553 */
     6554var MenuItem = wp.media.view.MenuItem,
     6555        PriorityList = wp.media.view.PriorityList,
     6556        Menu;
     6557
     6558Menu = PriorityList.extend({
     6559        tagName:   'div',
     6560        className: 'media-menu',
     6561        property:  'state',
     6562        ItemView:  MenuItem,
     6563        region:    'menu',
     6564
     6565        /* TODO: alternatively hide on any click anywhere
     6566        events: {
     6567                'click': 'click'
     6568        },
     6569
     6570        click: function() {
     6571                this.$el.removeClass( 'visible' );
     6572        },
     6573        */
     6574
     6575        /**
     6576         * @param {Object} options
     6577         * @param {string} id
     6578         * @returns {wp.media.View}
     6579         */
     6580        toView: function( options, id ) {
     6581                options = options || {};
     6582                options[ this.property ] = options[ this.property ] || id;
     6583                return new this.ItemView( options ).render();
     6584        },
     6585
     6586        ready: function() {
     6587                /**
     6588                 * call 'ready' directly on the parent class
     6589                 */
     6590                PriorityList.prototype.ready.apply( this, arguments );
     6591                this.visibility();
     6592        },
     6593
     6594        set: function() {
     6595                /**
     6596                 * call 'set' directly on the parent class
     6597                 */
     6598                PriorityList.prototype.set.apply( this, arguments );
     6599                this.visibility();
     6600        },
     6601
     6602        unset: function() {
     6603                /**
     6604                 * call 'unset' directly on the parent class
     6605                 */
     6606                PriorityList.prototype.unset.apply( this, arguments );
     6607                this.visibility();
     6608        },
     6609
     6610        visibility: function() {
     6611                var region = this.region,
     6612                        view = this.controller[ region ].get(),
     6613                        views = this.views.get(),
     6614                        hide = ! views || views.length < 2;
     6615
     6616                if ( this === view ) {
     6617                        this.controller.$el.toggleClass( 'hide-' + region, hide );
     6618                }
     6619        },
     6620        /**
     6621         * @param {string} id
     6622         */
     6623        select: function( id ) {
     6624                var view = this.get( id );
     6625
     6626                if ( ! view ) {
     6627                        return;
     6628                }
     6629
     6630                this.deselect();
     6631                view.$el.addClass('active');
     6632        },
     6633
     6634        deselect: function() {
     6635                this.$el.children().removeClass('active');
     6636        },
     6637
     6638        hide: function( id ) {
     6639                var view = this.get( id );
     6640
     6641                if ( ! view ) {
     6642                        return;
     6643                }
     6644
     6645                view.$el.addClass('hidden');
     6646        },
     6647
     6648        show: function( id ) {
     6649                var view = this.get( id );
     6650
     6651                if ( ! view ) {
     6652                        return;
     6653                }
     6654
     6655                view.$el.removeClass('hidden');
     6656        }
     6657});
     6658
     6659module.exports = Menu;
     6660
     6661},{}],51:[function(require,module,exports){
     6662/*globals wp, _, jQuery */
     6663
     6664/**
     6665 * wp.media.view.Modal
     6666 *
     6667 * A modal view, which the media modal uses as its default container.
     6668 *
     6669 * @class
     6670 * @augments wp.media.View
     6671 * @augments wp.Backbone.View
     6672 * @augments Backbone.View
     6673 */
     6674var $ = jQuery,
     6675        Modal;
     6676
     6677Modal = wp.media.View.extend({
     6678        tagName:  'div',
     6679        template: wp.template('media-modal'),
     6680
     6681        attributes: {
     6682                tabindex: 0
     6683        },
     6684
     6685        events: {
     6686                'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
     6687                'keydown': 'keydown'
     6688        },
     6689
     6690        initialize: function() {
     6691                _.defaults( this.options, {
     6692                        container: document.body,
     6693                        title:     '',
     6694                        propagate: true,
     6695                        freeze:    true
     6696                });
     6697
     6698                this.focusManager = new wp.media.view.FocusManager({
     6699                        el: this.el
     6700                });
     6701        },
     6702        /**
     6703         * @returns {Object}
     6704         */
     6705        prepare: function() {
     6706                return {
     6707                        title: this.options.title
     6708                };
     6709        },
     6710
     6711        /**
     6712         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6713         */
     6714        attach: function() {
     6715                if ( this.views.attached ) {
     6716                        return this;
     6717                }
     6718
     6719                if ( ! this.views.rendered ) {
     6720                        this.render();
     6721                }
     6722
     6723                this.$el.appendTo( this.options.container );
     6724
     6725                // Manually mark the view as attached and trigger ready.
     6726                this.views.attached = true;
     6727                this.views.ready();
     6728
     6729                return this.propagate('attach');
     6730        },
     6731
     6732        /**
     6733         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6734         */
     6735        detach: function() {
     6736                if ( this.$el.is(':visible') ) {
     6737                        this.close();
     6738                }
     6739
     6740                this.$el.detach();
     6741                this.views.attached = false;
     6742                return this.propagate('detach');
     6743        },
     6744
     6745        /**
     6746         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6747         */
     6748        open: function() {
     6749                var $el = this.$el,
     6750                        options = this.options,
     6751                        mceEditor;
     6752
     6753                if ( $el.is(':visible') ) {
     6754                        return this;
     6755                }
     6756
     6757                if ( ! this.views.attached ) {
     6758                        this.attach();
     6759                }
     6760
     6761                // If the `freeze` option is set, record the window's scroll position.
     6762                if ( options.freeze ) {
     6763                        this._freeze = {
     6764                                scrollTop: $( window ).scrollTop()
     6765                        };
     6766                }
     6767
     6768                // Disable page scrolling.
     6769                $( 'body' ).addClass( 'modal-open' );
     6770
     6771                $el.show();
     6772
     6773                // Try to close the onscreen keyboard
     6774                if ( 'ontouchend' in document ) {
     6775                        if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
     6776                                mceEditor.iframeElement.focus();
     6777                                mceEditor.iframeElement.blur();
     6778
     6779                                setTimeout( function() {
     6780                                        mceEditor.iframeElement.blur();
     6781                                }, 100 );
     6782                        }
     6783                }
     6784
     6785                this.$el.focus();
     6786
     6787                return this.propagate('open');
     6788        },
     6789
     6790        /**
     6791         * @param {Object} options
     6792         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6793         */
     6794        close: function( options ) {
     6795                var freeze = this._freeze;
     6796
     6797                if ( ! this.views.attached || ! this.$el.is(':visible') ) {
     6798                        return this;
     6799                }
     6800
     6801                // Enable page scrolling.
     6802                $( 'body' ).removeClass( 'modal-open' );
     6803
     6804                // Hide modal and remove restricted media modal tab focus once it's closed
     6805                this.$el.hide().undelegate( 'keydown' );
     6806
     6807                // Put focus back in useful location once modal is closed
     6808                $('#wpbody-content').focus();
     6809
     6810                this.propagate('close');
     6811
     6812                // If the `freeze` option is set, restore the container's scroll position.
     6813                if ( freeze ) {
     6814                        $( window ).scrollTop( freeze.scrollTop );
     6815                }
     6816
     6817                if ( options && options.escape ) {
     6818                        this.propagate('escape');
     6819                }
     6820
     6821                return this;
     6822        },
     6823        /**
     6824         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6825         */
     6826        escape: function() {
     6827                return this.close({ escape: true });
     6828        },
     6829        /**
     6830         * @param {Object} event
     6831         */
     6832        escapeHandler: function( event ) {
     6833                event.preventDefault();
     6834                this.escape();
     6835        },
     6836
     6837        /**
     6838         * @param {Array|Object} content Views to register to '.media-modal-content'
     6839         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6840         */
     6841        content: function( content ) {
     6842                this.views.set( '.media-modal-content', content );
     6843                return this;
     6844        },
     6845
     6846        /**
     6847         * Triggers a modal event and if the `propagate` option is set,
     6848         * forwards events to the modal's controller.
     6849         *
     6850         * @param {string} id
     6851         * @returns {wp.media.view.Modal} Returns itself to allow chaining
     6852         */
     6853        propagate: function( id ) {
     6854                this.trigger( id );
     6855
     6856                if ( this.options.propagate ) {
     6857                        this.controller.trigger( id );
     6858                }
     6859
     6860                return this;
     6861        },
     6862        /**
     6863         * @param {Object} event
     6864         */
     6865        keydown: function( event ) {
     6866                // Close the modal when escape is pressed.
     6867                if ( 27 === event.which && this.$el.is(':visible') ) {
     6868                        this.escape();
     6869                        event.stopImmediatePropagation();
     6870                }
     6871        }
     6872});
     6873
     6874module.exports = Modal;
     6875
     6876},{}],52:[function(require,module,exports){
     6877/*globals _, Backbone */
     6878
     6879/**
     6880 * wp.media.view.PriorityList
     6881 *
     6882 * @class
     6883 * @augments wp.media.View
     6884 * @augments wp.Backbone.View
     6885 * @augments Backbone.View
     6886 */
     6887var PriorityList = wp.media.View.extend({
     6888        tagName:   'div',
     6889
     6890        initialize: function() {
     6891                this._views = {};
     6892
     6893                this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
     6894                delete this.options.views;
     6895
     6896                if ( ! this.options.silent ) {
     6897                        this.render();
     6898                }
     6899        },
     6900        /**
     6901         * @param {string} id
     6902         * @param {wp.media.View|Object} view
     6903         * @param {Object} options
     6904         * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
     6905         */
     6906        set: function( id, view, options ) {
     6907                var priority, views, index;
     6908
     6909                options = options || {};
     6910
     6911                // Accept an object with an `id` : `view` mapping.
     6912                if ( _.isObject( id ) ) {
     6913                        _.each( id, function( view, id ) {
     6914                                this.set( id, view );
     6915                        }, this );
     6916                        return this;
     6917                }
     6918
     6919                if ( ! (view instanceof Backbone.View) ) {
     6920                        view = this.toView( view, id, options );
     6921                }
     6922                view.controller = view.controller || this.controller;
     6923
     6924                this.unset( id );
     6925
     6926                priority = view.options.priority || 10;
     6927                views = this.views.get() || [];
     6928
     6929                _.find( views, function( existing, i ) {
     6930                        if ( existing.options.priority > priority ) {
     6931                                index = i;
     6932                                return true;
     6933                        }
     6934                });
     6935
     6936                this._views[ id ] = view;
     6937                this.views.add( view, {
     6938                        at: _.isNumber( index ) ? index : views.length || 0
     6939                });
     6940
     6941                return this;
     6942        },
     6943        /**
     6944         * @param {string} id
     6945         * @returns {wp.media.View}
     6946         */
     6947        get: function( id ) {
     6948                return this._views[ id ];
     6949        },
     6950        /**
     6951         * @param {string} id
     6952         * @returns {wp.media.view.PriorityList}
     6953         */
     6954        unset: function( id ) {
     6955                var view = this.get( id );
     6956
     6957                if ( view ) {
     6958                        view.remove();
     6959                }
     6960
     6961                delete this._views[ id ];
     6962                return this;
     6963        },
     6964        /**
     6965         * @param {Object} options
     6966         * @returns {wp.media.View}
     6967         */
     6968        toView: function( options ) {
     6969                return new wp.media.View( options );
     6970        }
     6971});
     6972
     6973module.exports = PriorityList;
     6974
     6975},{}],53:[function(require,module,exports){
     6976/**
     6977 * wp.media.view.RouterItem
     6978 *
     6979 * @class
     6980 * @augments wp.media.view.MenuItem
     6981 * @augments wp.media.View
     6982 * @augments wp.Backbone.View
     6983 * @augments Backbone.View
     6984 */
     6985var RouterItem = wp.media.view.MenuItem.extend({
     6986        /**
     6987         * On click handler to activate the content region's corresponding mode.
     6988         */
     6989        click: function() {
     6990                var contentMode = this.options.contentMode;
     6991                if ( contentMode ) {
     6992                        this.controller.content.mode( contentMode );
     6993                }
     6994        }
     6995});
     6996
     6997module.exports = RouterItem;
     6998
     6999},{}],54:[function(require,module,exports){
     7000/*globals wp */
     7001
     7002/**
     7003 * wp.media.view.Router
     7004 *
     7005 * @class
     7006 * @augments wp.media.view.Menu
     7007 * @augments wp.media.view.PriorityList
     7008 * @augments wp.media.View
     7009 * @augments wp.Backbone.View
     7010 * @augments Backbone.View
     7011 */
     7012var Menu = wp.media.view.Menu,
     7013        Router;
     7014
     7015Router = Menu.extend({
     7016        tagName:   'div',
     7017        className: 'media-router',
     7018        property:  'contentMode',
     7019        ItemView:  wp.media.view.RouterItem,
     7020        region:    'router',
     7021
     7022        initialize: function() {
     7023                this.controller.on( 'content:render', this.update, this );
     7024                // Call 'initialize' directly on the parent class.
     7025                Menu.prototype.initialize.apply( this, arguments );
     7026        },
     7027
     7028        update: function() {
     7029                var mode = this.controller.content.mode();
     7030                if ( mode ) {
     7031                        this.select( mode );
     7032                }
     7033        }
     7034});
     7035
     7036module.exports = Router;
     7037
     7038},{}],55:[function(require,module,exports){
     7039/*globals wp */
     7040
     7041/**
     7042 * wp.media.view.Search
     7043 *
     7044 * @class
     7045 * @augments wp.media.View
     7046 * @augments wp.Backbone.View
     7047 * @augments Backbone.View
     7048 */
     7049var l10n = wp.media.view.l10n,
     7050        Search;
     7051
     7052Search = wp.media.View.extend({
     7053        tagName:   'input',
     7054        className: 'search',
     7055        id:        'media-search-input',
     7056
     7057        attributes: {
     7058                type:        'search',
     7059                placeholder: l10n.search
     7060        },
     7061
     7062        events: {
     7063                'input':  'search',
     7064                'keyup':  'search',
     7065                'change': 'search',
     7066                'search': 'search'
     7067        },
     7068
     7069        /**
     7070         * @returns {wp.media.view.Search} Returns itself to allow chaining
     7071         */
     7072        render: function() {
     7073                this.el.value = this.model.escape('search');
     7074                return this;
     7075        },
     7076
     7077        search: function( event ) {
     7078                if ( event.target.value ) {
     7079                        this.model.set( 'search', event.target.value );
     7080                } else {
     7081                        this.model.unset('search');
     7082                }
     7083        }
     7084});
     7085
     7086module.exports = Search;
     7087
     7088},{}],56:[function(require,module,exports){
     7089/*globals wp, _, Backbone */
     7090
     7091/**
     7092 * wp.media.view.Selection
     7093 *
     7094 * @class
     7095 * @augments wp.media.View
     7096 * @augments wp.Backbone.View
     7097 * @augments Backbone.View
     7098 */
     7099var l10n = wp.media.view.l10n,
     7100        Selection;
     7101
     7102Selection = wp.media.View.extend({
     7103        tagName:   'div',
     7104        className: 'media-selection',
     7105        template:  wp.template('media-selection'),
     7106
     7107        events: {
     7108                'click .edit-selection':  'edit',
     7109                'click .clear-selection': 'clear'
     7110        },
     7111
     7112        initialize: function() {
     7113                _.defaults( this.options, {
     7114                        editable:  false,
     7115                        clearable: true
     7116                });
     7117
     7118                /**
     7119                 * @member {wp.media.view.Attachments.Selection}
     7120                 */
     7121                this.attachments = new wp.media.view.Attachments.Selection({
     7122                        controller: this.controller,
     7123                        collection: this.collection,
     7124                        selection:  this.collection,
     7125                        model:      new Backbone.Model()
     7126                });
     7127
     7128                this.views.set( '.selection-view', this.attachments );
     7129                this.collection.on( 'add remove reset', this.refresh, this );
     7130                this.controller.on( 'content:activate', this.refresh, this );
     7131        },
     7132
     7133        ready: function() {
     7134                this.refresh();
     7135        },
     7136
     7137        refresh: function() {
     7138                // If the selection hasn't been rendered, bail.
     7139                if ( ! this.$el.children().length ) {
     7140                        return;
     7141                }
     7142
     7143                var collection = this.collection,
     7144                        editing = 'edit-selection' === this.controller.content.mode();
     7145
     7146                // If nothing is selected, display nothing.
     7147                this.$el.toggleClass( 'empty', ! collection.length );
     7148                this.$el.toggleClass( 'one', 1 === collection.length );
     7149                this.$el.toggleClass( 'editing', editing );
     7150
     7151                this.$('.count').text( l10n.selected.replace('%d', collection.length) );
     7152        },
     7153
     7154        edit: function( event ) {
     7155                event.preventDefault();
     7156                if ( this.options.editable ) {
     7157                        this.options.editable.call( this, this.collection );
     7158                }
     7159        },
     7160
     7161        clear: function( event ) {
     7162                event.preventDefault();
     7163                this.collection.reset();
     7164
     7165                // Keep focus inside media modal
     7166                // after clear link is selected
     7167                this.controller.modal.focusManager.focus();
     7168        }
     7169});
     7170
     7171module.exports = Selection;
     7172
     7173},{}],57:[function(require,module,exports){
     7174/*globals _, Backbone */
     7175
     7176/**
     7177 * wp.media.view.Settings
     7178 *
     7179 * @class
     7180 * @augments wp.media.View
     7181 * @augments wp.Backbone.View
     7182 * @augments Backbone.View
     7183 */
     7184var View = wp.media.View,
     7185        $ = Backbone.$,
     7186        Settings;
     7187
     7188Settings = View.extend({
     7189        events: {
     7190                'click button':    'updateHandler',
     7191                'change input':    'updateHandler',
     7192                'change select':   'updateHandler',
     7193                'change textarea': 'updateHandler'
     7194        },
     7195
     7196        initialize: function() {
     7197                this.model = this.model || new Backbone.Model();
     7198                this.listenTo( this.model, 'change', this.updateChanges );
     7199        },
     7200
     7201        prepare: function() {
     7202                return _.defaults({
     7203                        model: this.model.toJSON()
     7204                }, this.options );
     7205        },
     7206        /**
     7207         * @returns {wp.media.view.Settings} Returns itself to allow chaining
     7208         */
     7209        render: function() {
     7210                View.prototype.render.apply( this, arguments );
     7211                // Select the correct values.
     7212                _( this.model.attributes ).chain().keys().each( this.update, this );
     7213                return this;
     7214        },
     7215        /**
     7216         * @param {string} key
     7217         */
     7218        update: function( key ) {
     7219                var value = this.model.get( key ),
     7220                        $setting = this.$('[data-setting="' + key + '"]'),
     7221                        $buttons, $value;
     7222
     7223                // Bail if we didn't find a matching setting.
     7224                if ( ! $setting.length ) {
     7225                        return;
     7226                }
     7227
     7228                // Attempt to determine how the setting is rendered and update
     7229                // the selected value.
     7230
     7231                // Handle dropdowns.
     7232                if ( $setting.is('select') ) {
     7233                        $value = $setting.find('[value="' + value + '"]');
     7234
     7235                        if ( $value.length ) {
     7236                                $setting.find('option').prop( 'selected', false );
     7237                                $value.prop( 'selected', true );
     7238                        } else {
     7239                                // If we can't find the desired value, record what *is* selected.
     7240                                this.model.set( key, $setting.find(':selected').val() );
     7241                        }
     7242
     7243                // Handle button groups.
     7244                } else if ( $setting.hasClass('button-group') ) {
     7245                        $buttons = $setting.find('button').removeClass('active');
     7246                        $buttons.filter( '[value="' + value + '"]' ).addClass('active');
     7247
     7248                // Handle text inputs and textareas.
     7249                } else if ( $setting.is('input[type="text"], textarea') ) {
     7250                        if ( ! $setting.is(':focus') ) {
     7251                                $setting.val( value );
     7252                        }
     7253                // Handle checkboxes.
     7254                } else if ( $setting.is('input[type="checkbox"]') ) {
     7255                        $setting.prop( 'checked', !! value && 'false' !== value );
     7256                }
     7257        },
     7258        /**
     7259         * @param {Object} event
     7260         */
     7261        updateHandler: function( event ) {
     7262                var $setting = $( event.target ).closest('[data-setting]'),
     7263                        value = event.target.value,
     7264                        userSetting;
     7265
     7266                event.preventDefault();
     7267
     7268                if ( ! $setting.length ) {
     7269                        return;
     7270                }
     7271
     7272                // Use the correct value for checkboxes.
     7273                if ( $setting.is('input[type="checkbox"]') ) {
     7274                        value = $setting[0].checked;
     7275                }
     7276
     7277                // Update the corresponding setting.
     7278                this.model.set( $setting.data('setting'), value );
     7279
     7280                // If the setting has a corresponding user setting,
     7281                // update that as well.
     7282                if ( userSetting = $setting.data('userSetting') ) {
     7283                        window.setUserSetting( userSetting, value );
     7284                }
     7285        },
     7286
     7287        updateChanges: function( model ) {
     7288                if ( model.hasChanged() ) {
     7289                        _( model.changed ).chain().keys().each( this.update, this );
     7290                }
     7291        }
     7292});
     7293
     7294module.exports = Settings;
     7295
     7296},{}],58:[function(require,module,exports){
     7297/*globals wp, _ */
     7298
     7299/**
     7300 * wp.media.view.Settings.AttachmentDisplay
     7301 *
     7302 * @class
     7303 * @augments wp.media.view.Settings
     7304 * @augments wp.media.View
     7305 * @augments wp.Backbone.View
     7306 * @augments Backbone.View
     7307 */
     7308var Settings = wp.media.view.Settings,
     7309        AttachmentDisplay;
     7310
     7311AttachmentDisplay = Settings.extend({
     7312        className: 'attachment-display-settings',
     7313        template:  wp.template('attachment-display-settings'),
     7314
     7315        initialize: function() {
     7316                var attachment = this.options.attachment;
     7317
     7318                _.defaults( this.options, {
     7319                        userSettings: false
     7320                });
     7321                // Call 'initialize' directly on the parent class.
     7322                Settings.prototype.initialize.apply( this, arguments );
     7323                this.listenTo( this.model, 'change:link', this.updateLinkTo );
     7324
     7325                if ( attachment ) {
     7326                        attachment.on( 'change:uploading', this.render, this );
     7327                }
     7328        },
     7329
     7330        dispose: function() {
     7331                var attachment = this.options.attachment;
     7332                if ( attachment ) {
     7333                        attachment.off( null, null, this );
     7334                }
     7335                /**
     7336                 * call 'dispose' directly on the parent class
     7337                 */
     7338                Settings.prototype.dispose.apply( this, arguments );
     7339        },
     7340        /**
     7341         * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
     7342         */
     7343        render: function() {
     7344                var attachment = this.options.attachment;
     7345                if ( attachment ) {
     7346                        _.extend( this.options, {
     7347                                sizes: attachment.get('sizes'),
     7348                                type:  attachment.get('type')
     7349                        });
     7350                }
     7351                /**
     7352                 * call 'render' directly on the parent class
     7353                 */
     7354                Settings.prototype.render.call( this );
     7355                this.updateLinkTo();
     7356                return this;
     7357        },
     7358
     7359        updateLinkTo: function() {
     7360                var linkTo = this.model.get('link'),
     7361                        $input = this.$('.link-to-custom'),
     7362                        attachment = this.options.attachment;
     7363
     7364                if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
     7365                        $input.addClass( 'hidden' );
     7366                        return;
     7367                }
     7368
     7369                if ( attachment ) {
     7370                        if ( 'post' === linkTo ) {
     7371                                $input.val( attachment.get('link') );
     7372                        } else if ( 'file' === linkTo ) {
     7373                                $input.val( attachment.get('url') );
     7374                        } else if ( ! this.model.get('linkUrl') ) {
     7375                                $input.val('http://');
     7376                        }
     7377
     7378                        $input.prop( 'readonly', 'custom' !== linkTo );
     7379                }
     7380
     7381                $input.removeClass( 'hidden' );
     7382
     7383                // If the input is visible, focus and select its contents.
     7384                if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
     7385                        $input.focus()[0].select();
     7386                }
     7387        }
     7388});
     7389
     7390module.exports = AttachmentDisplay;
     7391
     7392},{}],59:[function(require,module,exports){
     7393/*globals wp */
     7394
     7395/**
     7396 * wp.media.view.Settings.Gallery
     7397 *
     7398 * @class
     7399 * @augments wp.media.view.Settings
     7400 * @augments wp.media.View
     7401 * @augments wp.Backbone.View
     7402 * @augments Backbone.View
     7403 */
     7404var Gallery = wp.media.view.Settings.extend({
     7405        className: 'collection-settings gallery-settings',
     7406        template:  wp.template('gallery-settings')
     7407});
     7408
     7409module.exports = Gallery;
     7410
     7411},{}],60:[function(require,module,exports){
     7412/*globals wp */
     7413
     7414/**
     7415 * wp.media.view.Settings.Playlist
     7416 *
     7417 * @class
     7418 * @augments wp.media.view.Settings
     7419 * @augments wp.media.View
     7420 * @augments wp.Backbone.View
     7421 * @augments Backbone.View
     7422 */
     7423var Playlist = wp.media.view.Settings.extend({
     7424        className: 'collection-settings playlist-settings',
     7425        template:  wp.template('playlist-settings')
     7426});
     7427
     7428module.exports = Playlist;
     7429
     7430},{}],61:[function(require,module,exports){
     7431/**
     7432 * wp.media.view.Sidebar
     7433 *
     7434 * @class
     7435 * @augments wp.media.view.PriorityList
     7436 * @augments wp.media.View
     7437 * @augments wp.Backbone.View
     7438 * @augments Backbone.View
     7439 */
     7440var Sidebar = wp.media.view.PriorityList.extend({
     7441        className: 'media-sidebar'
     7442});
     7443
     7444module.exports = Sidebar;
     7445
     7446},{}],62:[function(require,module,exports){
     7447/*globals _ */
     7448
     7449/**
     7450 * wp.media.view.Spinner
     7451 *
     7452 * @class
     7453 * @augments wp.media.View
     7454 * @augments wp.Backbone.View
     7455 * @augments Backbone.View
     7456 */
     7457var Spinner = wp.media.View.extend({
     7458        tagName:   'span',
     7459        className: 'spinner',
     7460        spinnerTimeout: false,
     7461        delay: 400,
     7462
     7463        show: function() {
     7464                if ( ! this.spinnerTimeout ) {
     7465                        this.spinnerTimeout = _.delay(function( $el ) {
     7466                                $el.show();
     7467                        }, this.delay, this.$el );
     7468                }
     7469
     7470                return this;
     7471        },
     7472
     7473        hide: function() {
     7474                this.$el.hide();
     7475                this.spinnerTimeout = clearTimeout( this.spinnerTimeout );
     7476
     7477                return this;
     7478        }
     7479});
     7480
     7481module.exports = Spinner;
     7482
     7483},{}],63:[function(require,module,exports){
     7484/*globals _, Backbone */
     7485
     7486/**
     7487 * wp.media.view.Toolbar
     7488 *
     7489 * A toolbar which consists of a primary and a secondary section. Each sections
     7490 * can be filled with views.
     7491 *
     7492 * @class
     7493 * @augments wp.media.View
     7494 * @augments wp.Backbone.View
     7495 * @augments Backbone.View
     7496 */
     7497var View = wp.media.View,
     7498        Toolbar;
     7499
     7500Toolbar = View.extend({
     7501        tagName:   'div',
     7502        className: 'media-toolbar',
     7503
     7504        initialize: function() {
     7505                var state = this.controller.state(),
     7506                        selection = this.selection = state.get('selection'),
     7507                        library = this.library = state.get('library');
     7508
     7509                this._views = {};
     7510
     7511                // The toolbar is composed of two `PriorityList` views.
     7512                this.primary   = new wp.media.view.PriorityList();
     7513                this.secondary = new wp.media.view.PriorityList();
     7514                this.primary.$el.addClass('media-toolbar-primary search-form');
     7515                this.secondary.$el.addClass('media-toolbar-secondary');
     7516
     7517                this.views.set([ this.secondary, this.primary ]);
     7518
     7519                if ( this.options.items ) {
     7520                        this.set( this.options.items, { silent: true });
     7521                }
     7522
     7523                if ( ! this.options.silent ) {
     7524                        this.render();
     7525                }
     7526
     7527                if ( selection ) {
     7528                        selection.on( 'add remove reset', this.refresh, this );
     7529                }
     7530
     7531                if ( library ) {
     7532                        library.on( 'add remove reset', this.refresh, this );
     7533                }
     7534        },
     7535        /**
     7536         * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
     7537         */
     7538        dispose: function() {
     7539                if ( this.selection ) {
     7540                        this.selection.off( null, null, this );
     7541                }
     7542
     7543                if ( this.library ) {
     7544                        this.library.off( null, null, this );
     7545                }
     7546                /**
     7547                 * call 'dispose' directly on the parent class
     7548                 */
     7549                return View.prototype.dispose.apply( this, arguments );
     7550        },
     7551
     7552        ready: function() {
     7553                this.refresh();
     7554        },
     7555
     7556        /**
     7557         * @param {string} id
     7558         * @param {Backbone.View|Object} view
     7559         * @param {Object} [options={}]
     7560         * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     7561         */
     7562        set: function( id, view, options ) {
     7563                var list;
     7564                options = options || {};
     7565
     7566                // Accept an object with an `id` : `view` mapping.
     7567                if ( _.isObject( id ) ) {
     7568                        _.each( id, function( view, id ) {
     7569                                this.set( id, view, { silent: true });
     7570                        }, this );
     7571
     7572                } else {
     7573                        if ( ! ( view instanceof Backbone.View ) ) {
     7574                                view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
     7575                                view = new wp.media.view.Button( view ).render();
     7576                        }
     7577
     7578                        view.controller = view.controller || this.controller;
     7579
     7580                        this._views[ id ] = view;
     7581
     7582                        list = view.options.priority < 0 ? 'secondary' : 'primary';
     7583                        this[ list ].set( id, view, options );
     7584                }
     7585
     7586                if ( ! options.silent ) {
     7587                        this.refresh();
     7588                }
     7589
     7590                return this;
     7591        },
     7592        /**
     7593         * @param {string} id
     7594         * @returns {wp.media.view.Button}
     7595         */
     7596        get: function( id ) {
     7597                return this._views[ id ];
     7598        },
     7599        /**
     7600         * @param {string} id
     7601         * @param {Object} options
     7602         * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
     7603         */
     7604        unset: function( id, options ) {
     7605                delete this._views[ id ];
     7606                this.primary.unset( id, options );
     7607                this.secondary.unset( id, options );
     7608
     7609                if ( ! options || ! options.silent ) {
     7610                        this.refresh();
     7611                }
     7612                return this;
     7613        },
     7614
     7615        refresh: function() {
     7616                var state = this.controller.state(),
     7617                        library = state.get('library'),
     7618                        selection = state.get('selection');
     7619
     7620                _.each( this._views, function( button ) {
     7621                        if ( ! button.model || ! button.options || ! button.options.requires ) {
     7622                                return;
     7623                        }
     7624
     7625                        var requires = button.options.requires,
     7626                                disabled = false;
     7627
     7628                        // Prevent insertion of attachments if any of them are still uploading
     7629                        disabled = _.some( selection.models, function( attachment ) {
     7630                                return attachment.get('uploading') === true;
     7631                        });
     7632
     7633                        if ( requires.selection && selection && ! selection.length ) {
     7634                                disabled = true;
     7635                        } else if ( requires.library && library && ! library.length ) {
     7636                                disabled = true;
     7637                        }
     7638                        button.model.set( 'disabled', disabled );
     7639                });
     7640        }
     7641});
     7642
     7643module.exports = Toolbar;
     7644
     7645},{}],64:[function(require,module,exports){
     7646/*globals wp, _ */
     7647
     7648/**
     7649 * wp.media.view.Toolbar.Embed
     7650 *
     7651 * @class
     7652 * @augments wp.media.view.Toolbar.Select
     7653 * @augments wp.media.view.Toolbar
     7654 * @augments wp.media.View
     7655 * @augments wp.Backbone.View
     7656 * @augments Backbone.View
     7657 */
     7658var Select = wp.media.view.Toolbar.Select,
     7659        l10n = wp.media.view.l10n,
     7660        Embed;
     7661
     7662Embed = Select.extend({
     7663        initialize: function() {
     7664                _.defaults( this.options, {
     7665                        text: l10n.insertIntoPost,
     7666                        requires: false
     7667                });
     7668                // Call 'initialize' directly on the parent class.
     7669                Select.prototype.initialize.apply( this, arguments );
     7670        },
     7671
     7672        refresh: function() {
     7673                var url = this.controller.state().props.get('url');
     7674                this.get('select').model.set( 'disabled', ! url || url === 'http://' );
     7675                /**
     7676                 * call 'refresh' directly on the parent class
     7677                 */
     7678                Select.prototype.refresh.apply( this, arguments );
     7679        }
     7680});
     7681
     7682module.exports = Embed;
     7683
     7684},{}],65:[function(require,module,exports){
     7685/*globals wp, _ */
     7686
     7687/**
     7688 * wp.media.view.Toolbar.Select
     7689 *
     7690 * @class
     7691 * @augments wp.media.view.Toolbar
     7692 * @augments wp.media.View
     7693 * @augments wp.Backbone.View
     7694 * @augments Backbone.View
     7695 */
     7696var Toolbar = wp.media.view.Toolbar,
     7697        l10n = wp.media.view.l10n,
     7698        Select;
     7699
     7700Select = Toolbar.extend({
     7701        initialize: function() {
     7702                var options = this.options;
     7703
     7704                _.bindAll( this, 'clickSelect' );
     7705
     7706                _.defaults( options, {
     7707                        event: 'select',
     7708                        state: false,
     7709                        reset: true,
     7710                        close: true,
     7711                        text:  l10n.select,
     7712
     7713                        // Does the button rely on the selection?
     7714                        requires: {
     7715                                selection: true
     7716                        }
     7717                });
     7718
     7719                options.items = _.defaults( options.items || {}, {
     7720                        select: {
     7721                                style:    'primary',
     7722                                text:     options.text,
     7723                                priority: 80,
     7724                                click:    this.clickSelect,
     7725                                requires: options.requires
     7726                        }
     7727                });
     7728                // Call 'initialize' directly on the parent class.
     7729                Toolbar.prototype.initialize.apply( this, arguments );
     7730        },
     7731
     7732        clickSelect: function() {
     7733                var options = this.options,
     7734                        controller = this.controller;
     7735
     7736                if ( options.close ) {
     7737                        controller.close();
     7738                }
     7739
     7740                if ( options.event ) {
     7741                        controller.state().trigger( options.event );
     7742                }
     7743
     7744                if ( options.state ) {
     7745                        controller.setState( options.state );
     7746                }
     7747
     7748                if ( options.reset ) {
     7749                        controller.reset();
     7750                }
     7751        }
     7752});
     7753
     7754module.exports = Select;
     7755
     7756},{}],66:[function(require,module,exports){
     7757/*globals wp, _, jQuery */
     7758
     7759/**
     7760 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap
     7761 * or #wp-fullscreen-body) and relays drag'n'dropped files to a media workflow.
     7762 *
     7763 * wp.media.view.EditorUploader
     7764 *
     7765 * @class
     7766 * @augments wp.media.View
     7767 * @augments wp.Backbone.View
     7768 * @augments Backbone.View
     7769 */
     7770var View = wp.media.View,
     7771        l10n = wp.media.view.l10n,
     7772        $ = jQuery,
     7773        EditorUploader;
     7774
     7775EditorUploader = View.extend({
     7776        tagName:   'div',
     7777        className: 'uploader-editor',
     7778        template:  wp.template( 'uploader-editor' ),
     7779
     7780        localDrag: false,
     7781        overContainer: false,
     7782        overDropzone: false,
     7783        draggingFile: null,
     7784
     7785        /**
     7786         * Bind drag'n'drop events to callbacks.
     7787         */
     7788        initialize: function() {
     7789                this.initialized = false;
     7790
     7791                // Bail if not enabled or UA does not support drag'n'drop or File API.
     7792                if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
     7793                        return this;
     7794                }
     7795
     7796                this.$document = $(document);
     7797                this.dropzones = [];
     7798                this.files = [];
     7799
     7800                this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
     7801                this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
     7802                this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
     7803                this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
     7804
     7805                this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
     7806                this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
     7807
     7808                this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
     7809                        this.localDrag = event.type === 'dragstart';
     7810                }, this ) );
     7811
     7812                this.initialized = true;
     7813                return this;
     7814        },
     7815
     7816        /**
     7817         * Check browser support for drag'n'drop.
     7818         *
     7819         * @return Boolean
     7820         */
     7821        browserSupport: function() {
     7822                var supports = false, div = document.createElement('div');
     7823
     7824                supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
     7825                supports = supports && !! ( window.File && window.FileList && window.FileReader );
     7826                return supports;
     7827        },
     7828
     7829        isDraggingFile: function( event ) {
     7830                if ( this.draggingFile !== null ) {
     7831                        return this.draggingFile;
     7832                }
     7833
     7834                if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
     7835                        return false;
     7836                }
     7837
     7838                this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
     7839                        _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
     7840
     7841                return this.draggingFile;
     7842        },
     7843
     7844        refresh: function( e ) {
     7845                var dropzone_id;
     7846                for ( dropzone_id in this.dropzones ) {
     7847                        // Hide the dropzones only if dragging has left the screen.
     7848                        this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
     7849                }
     7850
     7851                if ( ! _.isUndefined( e ) ) {
     7852                        $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
     7853                }
     7854
     7855                if ( ! this.overContainer && ! this.overDropzone ) {
     7856                        this.draggingFile = null;
     7857                }
     7858
     7859                return this;
     7860        },
     7861
     7862        render: function() {
     7863                if ( ! this.initialized ) {
     7864                        return this;
     7865                }
     7866
     7867                View.prototype.render.apply( this, arguments );
     7868                $( '.wp-editor-wrap, #wp-fullscreen-body' ).each( _.bind( this.attach, this ) );
     7869                return this;
     7870        },
     7871
     7872        attach: function( index, editor ) {
     7873                // Attach a dropzone to an editor.
     7874                var dropzone = this.$el.clone();
     7875                this.dropzones.push( dropzone );
     7876                $( editor ).append( dropzone );
     7877                return this;
     7878        },
     7879
     7880        /**
     7881         * When a file is dropped on the editor uploader, open up an editor media workflow
     7882         * and upload the file immediately.
     7883         *
     7884         * @param  {jQuery.Event} event The 'drop' event.
     7885         */
     7886        drop: function( event ) {
     7887                var $wrap = null, uploadView;
     7888
     7889                this.containerDragleave( event );
     7890                this.dropzoneDragleave( event );
     7891
     7892                this.files = event.originalEvent.dataTransfer.files;
     7893                if ( this.files.length < 1 ) {
     7894                        return;
     7895                }
     7896
     7897                // Set the active editor to the drop target.
     7898                $wrap = $( event.target ).parents( '.wp-editor-wrap' );
     7899                if ( $wrap.length > 0 && $wrap[0].id ) {
     7900                        window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
     7901                }
     7902
     7903                if ( ! this.workflow ) {
     7904                        this.workflow = wp.media.editor.open( 'content', {
     7905                                frame:    'post',
     7906                                state:    'insert',
     7907                                title:    l10n.addMedia,
     7908                                multiple: true
     7909                        });
     7910                        uploadView = this.workflow.uploader;
     7911                        if ( uploadView.uploader && uploadView.uploader.ready ) {
     7912                                this.addFiles.apply( this );
     7913                        } else {
     7914                                this.workflow.on( 'uploader:ready', this.addFiles, this );
     7915                        }
     7916                } else {
     7917                        this.workflow.state().reset();
     7918                        this.addFiles.apply( this );
     7919                        this.workflow.open();
     7920                }
     7921
     7922                return false;
     7923        },
     7924
     7925        /**
     7926         * Add the files to the uploader.
     7927         */
     7928        addFiles: function() {
     7929                if ( this.files.length ) {
     7930                        this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
     7931                        this.files = [];
     7932                }
     7933                return this;
     7934        },
     7935
     7936        containerDragover: function( event ) {
     7937                if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     7938                        return;
     7939                }
     7940
     7941                this.overContainer = true;
     7942                this.refresh();
     7943        },
     7944
     7945        containerDragleave: function() {
     7946                this.overContainer = false;
     7947
     7948                // Throttle dragleave because it's called when bouncing from some elements to others.
     7949                _.delay( _.bind( this.refresh, this ), 50 );
     7950        },
     7951
     7952        dropzoneDragover: function( event ) {
     7953                if ( this.localDrag || ! this.isDraggingFile( event ) ) {
     7954                        return;
     7955                }
     7956
     7957                this.overDropzone = true;
     7958                this.refresh( event );
     7959                return false;
     7960        },
     7961
     7962        dropzoneDragleave: function( e ) {
     7963                this.overDropzone = false;
     7964                _.delay( _.bind( this.refresh, this, e ), 50 );
     7965        },
     7966
     7967        click: function( e ) {
     7968                // In the rare case where the dropzone gets stuck, hide it on click.
     7969                this.containerDragleave( e );
     7970                this.dropzoneDragleave( e );
     7971                this.localDrag = false;
     7972        }
     7973});
     7974
     7975module.exports = EditorUploader;
     7976
     7977},{}],67:[function(require,module,exports){
     7978/*globals wp, _ */
     7979
     7980/**
     7981 * wp.media.view.UploaderInline
     7982 *
     7983 * The inline uploader that shows up in the 'Upload Files' tab.
     7984 *
     7985 * @class
     7986 * @augments wp.media.View
     7987 * @augments wp.Backbone.View
     7988 * @augments Backbone.View
     7989 */
     7990var View = wp.media.View,
     7991        UploaderInline;
     7992
     7993UploaderInline = View.extend({
     7994        tagName:   'div',
     7995        className: 'uploader-inline',
     7996        template:  wp.template('uploader-inline'),
     7997
     7998        events: {
     7999                'click .close': 'hide'
     8000        },
     8001
     8002        initialize: function() {
     8003                _.defaults( this.options, {
     8004                        message: '',
     8005                        status:  true,
     8006                        canClose: false
     8007                });
     8008
     8009                if ( ! this.options.$browser && this.controller.uploader ) {
     8010                        this.options.$browser = this.controller.uploader.$browser;
     8011                }
     8012
     8013                if ( _.isUndefined( this.options.postId ) ) {
     8014                        this.options.postId = wp.media.view.settings.post.id;
     8015                }
     8016
     8017                if ( this.options.status ) {
     8018                        this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
     8019                                controller: this.controller
     8020                        }) );
     8021                }
     8022        },
     8023
     8024        prepare: function() {
     8025                var suggestedWidth = this.controller.state().get('suggestedWidth'),
     8026                        suggestedHeight = this.controller.state().get('suggestedHeight'),
     8027                        data = {};
     8028
     8029                data.message = this.options.message;
     8030                data.canClose = this.options.canClose;
     8031
     8032                if ( suggestedWidth && suggestedHeight ) {
     8033                        data.suggestedWidth = suggestedWidth;
     8034                        data.suggestedHeight = suggestedHeight;
     8035                }
     8036
     8037                return data;
     8038        },
     8039        /**
     8040         * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
     8041         */
     8042        dispose: function() {
     8043                if ( this.disposing ) {
     8044                        /**
     8045                         * call 'dispose' directly on the parent class
     8046                         */
     8047                        return View.prototype.dispose.apply( this, arguments );
     8048                }
     8049
     8050                // Run remove on `dispose`, so we can be sure to refresh the
     8051                // uploader with a view-less DOM. Track whether we're disposing
     8052                // so we don't trigger an infinite loop.
     8053                this.disposing = true;
     8054                return this.remove();
     8055        },
     8056        /**
     8057         * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
     8058         */
     8059        remove: function() {
     8060                /**
     8061                 * call 'remove' directly on the parent class
     8062                 */
     8063                var result = View.prototype.remove.apply( this, arguments );
     8064
     8065                _.defer( _.bind( this.refresh, this ) );
     8066                return result;
     8067        },
     8068
     8069        refresh: function() {
     8070                var uploader = this.controller.uploader;
     8071
     8072                if ( uploader ) {
     8073                        uploader.refresh();
     8074                }
     8075        },
     8076        /**
     8077         * @returns {wp.media.view.UploaderInline}
     8078         */
     8079        ready: function() {
     8080                var $browser = this.options.$browser,
     8081                        $placeholder;
     8082
     8083                if ( this.controller.uploader ) {
     8084                        $placeholder = this.$('.browser');
     8085
     8086                        // Check if we've already replaced the placeholder.
     8087                        if ( $placeholder[0] === $browser[0] ) {
     8088                                return;
     8089                        }
     8090
     8091                        $browser.detach().text( $placeholder.text() );
     8092                        $browser[0].className = $placeholder[0].className;
     8093                        $placeholder.replaceWith( $browser.show() );
     8094                }
     8095
     8096                this.refresh();
     8097                return this;
     8098        },
     8099        show: function() {
     8100                this.$el.removeClass( 'hidden' );
     8101        },
     8102        hide: function() {
     8103                this.$el.addClass( 'hidden' );
     8104        }
     8105
     8106});
     8107
     8108module.exports = UploaderInline;
     8109
     8110},{}],68:[function(require,module,exports){
     8111/*globals wp */
     8112
     8113/**
     8114 * wp.media.view.UploaderStatusError
     8115 *
     8116 * @class
     8117 * @augments wp.media.View
     8118 * @augments wp.Backbone.View
     8119 * @augments Backbone.View
     8120 */
     8121var UploaderStatusError = wp.media.View.extend({
     8122        className: 'upload-error',
     8123        template:  wp.template('uploader-status-error')
     8124});
     8125
     8126module.exports = UploaderStatusError;
     8127
     8128},{}],69:[function(require,module,exports){
     8129/*globals wp, _ */
     8130
     8131/**
     8132 * wp.media.view.UploaderStatus
     8133 *
     8134 * An uploader status for on-going uploads.
     8135 *
     8136 * @class
     8137 * @augments wp.media.View
     8138 * @augments wp.Backbone.View
     8139 * @augments Backbone.View
     8140 */
     8141var View = wp.media.View,
     8142        UploaderStatus;
     8143
     8144UploaderStatus = View.extend({
     8145        className: 'media-uploader-status',
     8146        template:  wp.template('uploader-status'),
     8147
     8148        events: {
     8149                'click .upload-dismiss-errors': 'dismiss'
     8150        },
     8151
     8152        initialize: function() {
     8153                this.queue = wp.Uploader.queue;
     8154                this.queue.on( 'add remove reset', this.visibility, this );
     8155                this.queue.on( 'add remove reset change:percent', this.progress, this );
     8156                this.queue.on( 'add remove reset change:uploading', this.info, this );
     8157
     8158                this.errors = wp.Uploader.errors;
     8159                this.errors.reset();
     8160                this.errors.on( 'add remove reset', this.visibility, this );
     8161                this.errors.on( 'add', this.error, this );
     8162        },
     8163        /**
     8164         * @global wp.Uploader
     8165         * @returns {wp.media.view.UploaderStatus}
     8166         */
     8167        dispose: function() {
     8168                wp.Uploader.queue.off( null, null, this );
     8169                /**
     8170                 * call 'dispose' directly on the parent class
     8171                 */
     8172                View.prototype.dispose.apply( this, arguments );
     8173                return this;
     8174        },
     8175
     8176        visibility: function() {
     8177                this.$el.toggleClass( 'uploading', !! this.queue.length );
     8178                this.$el.toggleClass( 'errors', !! this.errors.length );
     8179                this.$el.toggle( !! this.queue.length || !! this.errors.length );
     8180        },
     8181
     8182        ready: function() {
     8183                _.each({
     8184                        '$bar':      '.media-progress-bar div',
     8185                        '$index':    '.upload-index',
     8186                        '$total':    '.upload-total',
     8187                        '$filename': '.upload-filename'
     8188                }, function( selector, key ) {
     8189                        this[ key ] = this.$( selector );
     8190                }, this );
     8191
     8192                this.visibility();
     8193                this.progress();
     8194                this.info();
     8195        },
     8196
     8197        progress: function() {
     8198                var queue = this.queue,
     8199                        $bar = this.$bar;
     8200
     8201                if ( ! $bar || ! queue.length ) {
     8202                        return;
     8203                }
     8204
     8205                $bar.width( ( queue.reduce( function( memo, attachment ) {
     8206                        if ( ! attachment.get('uploading') ) {
     8207                                return memo + 100;
     8208                        }
     8209
     8210                        var percent = attachment.get('percent');
     8211                        return memo + ( _.isNumber( percent ) ? percent : 100 );
     8212                }, 0 ) / queue.length ) + '%' );
     8213        },
     8214
     8215        info: function() {
     8216                var queue = this.queue,
     8217                        index = 0, active;
     8218
     8219                if ( ! queue.length ) {
     8220                        return;
     8221                }
     8222
     8223                active = this.queue.find( function( attachment, i ) {
     8224                        index = i;
     8225                        return attachment.get('uploading');
     8226                });
     8227
     8228                this.$index.text( index + 1 );
     8229                this.$total.text( queue.length );
     8230                this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
     8231        },
     8232        /**
     8233         * @param {string} filename
     8234         * @returns {string}
     8235         */
     8236        filename: function( filename ) {
     8237                return wp.media.truncate( _.escape( filename ), 24 );
     8238        },
     8239        /**
     8240         * @param {Backbone.Model} error
     8241         */
     8242        error: function( error ) {
     8243                this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
     8244                        filename: this.filename( error.get('file').name ),
     8245                        message:  error.get('message')
     8246                }), { at: 0 });
     8247        },
     8248
     8249        /**
     8250         * @global wp.Uploader
     8251         *
     8252         * @param {Object} event
     8253         */
     8254        dismiss: function( event ) {
     8255                var errors = this.views.get('.upload-errors');
     8256
     8257                event.preventDefault();
     8258
     8259                if ( errors ) {
     8260                        _.invoke( errors, 'remove' );
     8261                }
     8262                wp.Uploader.errors.reset();
     8263        }
     8264});
     8265
     8266module.exports = UploaderStatus;
     8267
     8268},{}],70:[function(require,module,exports){
     8269/*globals wp, _, jQuery */
     8270
     8271/**
     8272 * wp.media.view.UploaderWindow
     8273 *
     8274 * An uploader window that allows for dragging and dropping media.
     8275 *
     8276 * @class
     8277 * @augments wp.media.View
     8278 * @augments wp.Backbone.View
     8279 * @augments Backbone.View
     8280 *
     8281 * @param {object} [options]                   Options hash passed to the view.
     8282 * @param {object} [options.uploader]          Uploader properties.
     8283 * @param {jQuery} [options.uploader.browser]
     8284 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
     8285 * @param {object} [options.uploader.params]
     8286 */
     8287var $ = jQuery,
     8288        UploaderWindow;
     8289
     8290UploaderWindow = wp.media.View.extend({
     8291        tagName:   'div',
     8292        className: 'uploader-window',
     8293        template:  wp.template('uploader-window'),
     8294
     8295        initialize: function() {
     8296                var uploader;
     8297
     8298                this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');
     8299
     8300                uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
     8301                        dropzone:  this.$el,
     8302                        browser:   this.$browser,
     8303                        params:    {}
     8304                });
     8305
     8306                // Ensure the dropzone is a jQuery collection.
     8307                if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
     8308                        uploader.dropzone = $( uploader.dropzone );
     8309                }
     8310
     8311                this.controller.on( 'activate', this.refresh, this );
     8312
     8313                this.controller.on( 'detach', function() {
     8314                        this.$browser.remove();
     8315                }, this );
     8316        },
     8317
     8318        refresh: function() {
     8319                if ( this.uploader ) {
     8320                        this.uploader.refresh();
     8321                }
     8322        },
     8323
     8324        ready: function() {
     8325                var postId = wp.media.view.settings.post.id,
     8326                        dropzone;
     8327
     8328                // If the uploader already exists, bail.
     8329                if ( this.uploader ) {
     8330                        return;
     8331                }
     8332
     8333                if ( postId ) {
     8334                        this.options.uploader.params.post_id = postId;
     8335                }
     8336                this.uploader = new wp.Uploader( this.options.uploader );
     8337
     8338                dropzone = this.uploader.dropzone;
     8339                dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
     8340                dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
     8341
     8342                $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
     8343        },
     8344
     8345        _ready: function() {
     8346                this.controller.trigger( 'uploader:ready' );
     8347        },
     8348
     8349        show: function() {
     8350                var $el = this.$el.show();
     8351
     8352                // Ensure that the animation is triggered by waiting until
     8353                // the transparent element is painted into the DOM.
     8354                _.defer( function() {
     8355                        $el.css({ opacity: 1 });
     8356                });
     8357        },
     8358
     8359        hide: function() {
     8360                var $el = this.$el.css({ opacity: 0 });
     8361
     8362                wp.media.transition( $el ).done( function() {
     8363                        // Transition end events are subject to race conditions.
     8364                        // Make sure that the value is set as intended.
     8365                        if ( '0' === $el.css('opacity') ) {
     8366                                $el.hide();
     8367                        }
     8368                });
     8369
     8370                // https://core.trac.wordpress.org/ticket/27341
     8371                _.delay( function() {
     8372                        if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
     8373                                $el.hide();
     8374                        }
     8375                }, 500 );
     8376        }
     8377});
     8378
     8379module.exports = UploaderWindow;
     8380
     8381},{}],71:[function(require,module,exports){
     8382/*globals wp */
     8383
     8384/**
     8385 * wp.media.View
     8386 *
     8387 * The base view class for media.
     8388 *
     8389 * Undelegating events, removing events from the model, and
     8390 * removing events from the controller mirror the code for
     8391 * `Backbone.View.dispose` in Backbone 0.9.8 development.
     8392 *
     8393 * This behavior has since been removed, and should not be used
     8394 * outside of the media manager.
     8395 *
     8396 * @class
     8397 * @augments wp.Backbone.View
     8398 * @augments Backbone.View
     8399 */
     8400var View = wp.Backbone.View.extend({
     8401        constructor: function( options ) {
     8402                if ( options && options.controller ) {
     8403                        this.controller = options.controller;
     8404                }
     8405                wp.Backbone.View.apply( this, arguments );
     8406        },
     8407        /**
     8408         * @todo The internal comment mentions this might have been a stop-gap
     8409         *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
     8410         *       care of this in Backbone.View now.
     8411         *
     8412         * @returns {wp.media.View} Returns itself to allow chaining
     8413         */
     8414        dispose: function() {
     8415                // Undelegating events, removing events from the model, and
     8416                // removing events from the controller mirror the code for
     8417                // `Backbone.View.dispose` in Backbone 0.9.8 development.
     8418                this.undelegateEvents();
     8419
     8420                if ( this.model && this.model.off ) {
     8421                        this.model.off( null, null, this );
     8422                }
     8423
     8424                if ( this.collection && this.collection.off ) {
     8425                        this.collection.off( null, null, this );
     8426                }
     8427
     8428                // Unbind controller events.
     8429                if ( this.controller && this.controller.off ) {
     8430                        this.controller.off( null, null, this );
     8431                }
     8432
     8433                return this;
     8434        },
     8435        /**
     8436         * @returns {wp.media.View} Returns itself to allow chaining
     8437         */
     8438        remove: function() {
     8439                this.dispose();
     8440                /**
     8441                 * call 'remove' directly on the parent class
     8442                 */
     8443                return wp.Backbone.View.prototype.remove.apply( this, arguments );
     8444        }
     8445});
     8446
     8447module.exports = View;
     8448
     8449},{}]},{},[17]);
  • src/wp-includes/script-loader.php

     
    410410        $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
    411411
    412412        $scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );
    413         $scripts->add( 'media-models', "/wp-includes/js/media/models$suffix.js", array( 'wp-backbone' ), false, 1 );
     413        $scripts->add( 'media-models', "/wp-includes/js/media-models$suffix.js", array( 'wp-backbone' ), false, 1 );
    414414        did_action( 'init' ) && $scripts->localize( 'media-models', '_wpMediaModelsL10n', array(
    415415                'settings' => array(
    416416                        'ajaxurl' => admin_url( 'admin-ajax.php', 'relative' ),
     
    420420
    421421        // To enqueue media-views or media-editor, call wp_enqueue_media().
    422422        // Both rely on numerous settings, styles, and templates to operate correctly.
    423         $scripts->add( 'media-views',  "/wp-includes/js/media/views$suffix.js",  array( 'utils', 'media-models', 'wp-plupload', 'jquery-ui-sortable', 'wp-mediaelement' ), false, 1 );
     423        $scripts->add( 'media-views',  "/wp-includes/js/media-views$suffix.js",  array( 'utils', 'media-models', 'wp-plupload', 'jquery-ui-sortable', 'wp-mediaelement' ), false, 1 );
    424424        $scripts->add( 'media-editor', "/wp-includes/js/media-editor$suffix.js", array( 'shortcode', 'media-views' ), false, 1 );
    425         $scripts->add( 'media-audiovideo', "/wp-includes/js/media/audio-video$suffix.js", array( 'media-editor' ), false, 1 );
     425        $scripts->add( 'media-audiovideo', "/wp-includes/js/media-audiovideo$suffix.js", array( 'media-editor' ), false, 1 );
    426426        $scripts->add( 'mce-view', "/wp-includes/js/mce-view$suffix.js", array( 'shortcode', 'media-models', 'media-audiovideo', 'wp-playlist' ), false, 1 );
    427427
    428428        if ( is_admin() ) {
     
    556556
    557557                $scripts->add( 'list-revisions', "/wp-includes/js/wp-list-revisions$suffix.js" );
    558558
    559                 $scripts->add( 'media-grid', "/wp-includes/js/media/grid$suffix.js", array( 'media-editor' ), false, 1 );
     559                $scripts->add( 'media-grid', "/wp-includes/js/media-grid$suffix.js", array( 'media-editor' ), false, 1 );
    560560                $scripts->add( 'media', "/wp-admin/js/media$suffix.js", array( 'jquery' ), false, 1 );
    561561                did_action( 'init' ) && $scripts->localize( 'media', 'attachMediaBoxL10n', array(
    562562                        'error' => __( 'An error has occurred. Please reload the page and try again.' ),