WordPress.org

Make WordPress Core

Ticket #31912: 31912.patch

File 31912.patch, 649.8 KB (added by iseulde, 5 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             &nb