Ticket #31912: 31912.patch
| File 31912.patch, 649.8 KB (added by , 11 years ago) |
|---|
-
Gruntfile.js
4 4 SOURCE_DIR = 'src/', 5 5 BUILD_DIR = 'build/', 6 6 mediaConfig = {}, 7 mediaBuilds = ['audio -video', 'grid', 'models', 'views'];7 mediaBuilds = ['audiovideo', 'grid', 'models', 'views']; 8 8 9 9 // Load tasks. 10 10 require('matchdep').filterDev(['grunt-*', '!grunt-legacy-util']).forEach( grunt.loadNpmTasks ); … … 12 12 grunt.util = require('grunt-legacy-util'); 13 13 14 14 mediaBuilds.forEach( function ( build ) { 15 var path = SOURCE_DIR + 'wp-includes/js/media /';15 var path = SOURCE_DIR + 'wp-includes/js/media'; 16 16 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' ]; 18 18 } ); 19 19 20 20 // Project configuration. … … 62 62 cwd: SOURCE_DIR, 63 63 src: [ 64 64 '**', 65 '!wp-includes/js/media/**', 65 66 '!**/.{svn,git}/**', // Ignore version control directories. 66 67 // Ignore unminified versions of external libs we don't ship: 67 68 '!wp-includes/js/backbone.js', … … 272 273 options: { 273 274 browserify: true 274 275 }, 275 expand: true,276 cwd: SOURCE_DIR,277 276 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' 281 278 ] 282 279 }, 283 280 core: { … … 286 283 src: [ 287 284 'wp-admin/js/*.js', 288 285 'wp-includes/js/*.js', 286 // Built scripts. 287 '!wp-includes/js/media-*', 289 288 // WordPress scripts inside directories 290 289 'wp-includes/js/jquery/jquery.table-hotkeys.js', 291 290 'wp-includes/js/mediaelement/wp-mediaelement.js', … … 627 626 'rtl', 628 627 'cssmin:rtl', 629 628 'cssmin:colors', 630 'browserify',631 629 'uglify:core', 632 'uglify:media',633 630 'uglify:jqueryui', 634 631 'concat:tinymce', 635 632 'compress:tinymce', -
src/wp-admin/includes/update-core.php
694 694 'wp-includes/js/jquery/ui/jquery.ui.tabs.min.js', 695 695 'wp-includes/js/jquery/ui/jquery.ui.tooltip.min.js', 696 696 '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' 707 698 ); 708 699 709 700 /** -
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 * @mixin10 */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 of28 * 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 cleanup38 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 MediaElementPlayer64 * 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 shortcodes81 */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 audio98 * `edit()` prepares the shortcode for the media modal99 * `shortcode()` builds the new shortcode after update100 *101 * @namespace102 */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 : 400113 },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: content145 });146 }147 };148 149 /**150 * Shortcode modeling for video151 * `edit()` prepares the shortcode for the media modal152 * `shortcode()` builds the new shortcode after update153 *154 * @namespace155 */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 : 360169 },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: content206 });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.AudioDetails225 *226 * The controller for the Audio Details state227 *228 * @class229 * @augments wp.media.controller.State230 * @augments Backbone.Model231 */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: 60245 },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.VideoDetails260 *261 * The controller for the Video Details state262 *263 * @class264 * @augments wp.media.controller.State265 * @augments Backbone.Model266 */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: 60280 },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.PostMedia295 *296 * Shared model class for audio and video. Updates the model after297 * "Add Audio|Video Source" and "Replace Audio|Video" states return298 *299 * @class300 * @augments Backbone.Model301 */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.AudioDetails339 *340 * @class341 * @augments wp.media.view.MediaDetails342 * @augments wp.media.view.Settings.AttachmentDisplay343 * @augments wp.media.view.Settings344 * @augments wp.media.View345 * @augments wp.Backbone.View346 * @augments Backbone.View347 */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.AudioDetails379 *380 * @class381 * @augments wp.media.view.MediaFrame.MediaDetails382 * @augments wp.media.view.MediaFrame.Select383 * @augments wp.media.view.MediaFrame384 * @augments wp.media.view.Frame385 * @augments wp.media.View386 * @augments wp.Backbone.View387 * @augments Backbone.View388 * @mixes wp.media.controller.StateMachine389 */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: 120406 },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.media427 } ),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: false445 } )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.MediaDetails457 *458 * @class459 * @augments wp.media.view.MediaFrame.Select460 * @augments wp.media.view.MediaFrame461 * @augments wp.media.view.Frame462 * @augments wp.media.View463 * @augments wp.Backbone.View464 * @augments Backbone.View465 * @mixes wp.media.controller.StateMachine466 */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: 120480 },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.attachment508 }).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: 40533 })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.VideoDetails589 *590 * @class591 * @augments wp.media.view.MediaFrame.MediaDetails592 * @augments wp.media.view.MediaFrame.Select593 * @augments wp.media.view.MediaFrame594 * @augments wp.media.view.Frame595 * @augments wp.media.View596 * @augments wp.Backbone.View597 * @augments Backbone.View598 * @mixes wp.media.controller.StateMachine599 */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: 120615 },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.media638 }),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: false656 } ),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.MediaDetails726 *727 * @class728 * @augments wp.media.view.Settings.AttachmentDisplay729 * @augments wp.media.view.Settings730 * @augments wp.media.View731 * @augments wp.Backbone.View732 * @augments Backbone.View733 */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 it765 *766 * @fires wp.media.view.MediaDetails#media:setting:remove767 *768 * @param {Event} e769 */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:remove785 */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 MediaElementPlayer809 */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 * @abstract827 */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 chaining846 */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.success856 }, 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} elem870 * @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 i880 ].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.VideoDetails894 *895 * @class896 * @augments wp.media.view.MediaDetails897 * @augments wp.media.view.Settings.AttachmentDisplay898 * @augments wp.media.view.Settings899 * @augments wp.media.View900 * @augments wp.Backbone.View901 * @augments Backbone.View902 */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 * @mixin9 */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 of27 * 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 cleanup37 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 MediaElementPlayer63 * 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 shortcodes80 */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 audio97 * `edit()` prepares the shortcode for the media modal98 * `shortcode()` builds the new shortcode after update99 *100 * @namespace101 */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 : 400112 },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: content144 });145 }146 };147 148 /**149 * Shortcode modeling for video150 * `edit()` prepares the shortcode for the media modal151 * `shortcode()` builds the new shortcode after update152 *153 * @namespace154 */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 : 360168 },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: content205 });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 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/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.EditAttachmentMetadata6 *7 * A state for editing an attachment's metadata.8 *9 * @class10 * @augments wp.media.controller.State11 * @augments Backbone.Model12 */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: false26 }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.Router51 *52 * A router for handling the browser history and application state.53 *54 * @class55 * @augments Backbone.Router56 */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 URL64 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 event69 search: function( query ) {70 jQuery( '#media-search-input' ).val( query ).trigger( 'input' );71 },72 73 // Show the modal with a specific item74 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 item80 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.TwoColumn101 *102 * A similar view to media.view.Attachment.Details103 * for use in the Edit Attachment modal.104 *105 * @class106 * @augments wp.media.view.Attachment.Details107 * @augments wp.media.view.Attachment108 * @augments wp.media.View109 * @augments wp.Backbone.View110 * @augments Backbone.View111 */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.DeleteSelectedPermanentlyButton146 *147 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic148 *149 * @class150 * @augments wp.media.view.DeleteSelectedButton151 * @augments wp.media.view.Button152 * @augments wp.media.View153 * @augments wp.Backbone.View154 * @augments Backbone.View155 */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.DeleteSelectedButton195 *196 * A button that handles bulk Delete/Trash logic197 *198 * @class199 * @augments wp.media.view.Button200 * @augments wp.media.View201 * @augments wp.Backbone.View202 * @augments Backbone.View203 */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.SelectModeToggleButton250 *251 * @class252 * @augments wp.media.view.Button253 * @augments wp.media.View254 * @augments wp.Backbone.View255 * @augments Backbone.View256 */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.Details316 *317 * @class318 * @augments wp.media.view.EditImage319 * @augments wp.media.View320 * @augments wp.Backbone.View321 * @augments Backbone.View322 */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.EditAttachments353 *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 * @class361 * @augments wp.media.view.Frame362 * @augments wp.media.View363 * @augments wp.Backbone.View364 * @augments Backbone.View365 * @mixes wp.media.controller.StateMachine366 */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.title427 });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 possible438 $( '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, which461 * 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.model467 });468 469 /**470 * Attach a subview to display fields added via the471 * `attachment_fields_to_edit` filter.472 */473 contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({474 controller: this,475 model: this.model476 }) );477 478 // Update browser url when navigating media details479 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, which488 * 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: this494 } );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: editImageController504 } );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 when569 * 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 key577 if ( 39 === event.keyCode ) {578 this.nextMediaItem();579 }580 // The left arrow key581 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.Manage598 *599 * A generic management frame workflow.600 *601 * Used in the media grid view.602 *603 * @class604 * @augments wp.media.view.MediaFrame605 * @augments wp.media.view.Frame606 * @augments wp.media.View607 * @augments wp.Backbone.View608 * @augments Backbone.View609 * @mixes wp.media.controller.StateMachine610 */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.Uploader620 */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 or643 // 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.body655 }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: false730 })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 +16772 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: model799 } );800 },801 802 /**803 * Create an attachments browser view within the content region.804 *805 * @param {Object} contentRegion Basic object with a `view` property, which806 * should be set with the proper region view.807 * @this wp.media.controller.Region808 */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: document832 });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 activate852 if ( window.history && window.history.pushState ) {853 Backbone.history.start( {854 root: window._wpMediaGridSettings.adminUrl,855 pushState: true856 } );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 * UTILITIES71 * ========================================================================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 template104 */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 post114 */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 ajax124 */125 ajax: wp.ajax.send,126 127 /**128 * Scales a set of dimensions to fit within bounding dimensions.129 *130 * @param {Object} dimensions131 * @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 which141 // max to constrain by. If a max value doesn't exist, then the142 // 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: maxHeight162 };163 } else {164 return {165 width : width,166 height: height167 };168 }169 },170 /**171 * Truncates a string by injecting an ellipsis into the middle.172 * Useful for filenames.173 *174 * @param {String} string175 * @param {Number} [length=30]176 * @param {String} [replacement=…]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 || '…';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 * MODELS194 * ========================================================================195 */196 /**197 * wp.media.attachment198 *199 * @static200 * @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 * @static211 * @member {wp.media.model.Attachments}212 */213 Attachments.all = new Attachments();214 215 /**216 * wp.media.query217 *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 caching230 $(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.Attachment239 *240 * @class241 * @augments Backbone.Model242 */243 var $ = Backbone.$,244 Attachment;245 246 Attachment = Backbone.Model.extend({247 /**248 * Triggered when attachment details change249 * Overrides Backbone.Model.sync250 *251 * @param {string} method252 * @param {wp.media.model.Attachment} model253 * @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 instantly259 // 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.id271 });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.id290 });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.Model329 */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 hash338 * 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.Model354 *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.id369 }, 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 * @static378 * @param {Object} attrs379 * @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 * @static392 * @param {string} id A string used to identify a model.393 * @param {Backbone.Model|undefined} attachment394 * @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.Attachments409 *410 * A collection of attachments.411 *412 * This collection has no persistence with the server without supplying413 * 'options.props.query = true', which will mirror the collection414 * to an Attachments Query collection - @see wp.media.model.Attachments.mirror().415 *416 * @class417 * @augments Backbone.Collection418 *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 private461 */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 private471 *472 * @param {Backbone.Model} model473 * @param {string} orderby474 */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 using489 * the `props` values, and sync the results to this collection.490 *491 * @access private492 *493 * @param {Backbone.Model} model494 * @param {Boolean} query495 */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 private506 *507 * @param {Backbone.Model} model508 */509 _changeFilteredProps: function( model ) {510 // If this is a query, updating the collection will be handled by511 // `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 searches541 // from, then automatically generate a source from the existing542 // 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} attachment555 * @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} attachment569 * @param {Object} options570 * @returns {wp.media.model.Attachments} Returns itself to allow chaining571 */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} attachments589 * @param {object} [options={}]590 *591 * @fires wp.media.model.Attachments#reset592 *593 * @returns {wp.media.model.Attachments} Returns itself to allow chaining594 */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 events609 * 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 chaining628 */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 private645 *646 * @param {wp.media.model.Attachments} attachment647 * @param {wp.media.model.Attachments} attachments648 * @param {Object} options649 *650 * @returns {wp.media.model.Attachments} Returns itself to allow chaining651 */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.silent657 };658 659 return this.validate( attachment, options );660 },661 /**662 * @access private663 *664 * @param {wp.media.model.Attachments} attachments665 * @param {Object} options666 * @returns {wp.media.model.Attachments} Returns itself to allow chaining667 */668 _validateAllHandler: function( attachments, options ) {669 return this.validateAll( attachments, options );670 },671 /**672 * Start mirroring another attachments collection, clearing out any models already673 * 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 chaining677 */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 fired687 // 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 have709 * server persistence by itself.710 *711 * @param {object} options712 * @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` to723 // the mirrored collection. Account for a race condition by724 // checking if we're still mirroring that collection when725 // 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 server736 * 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 have740 * 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 #24753751 *752 * @param {Object|Array} resp The raw response Object/Array.753 * @param {Object} xhr754 * @returns {Array} The array of model attributes to be added to the collection755 */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 private785 */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 saves796 * 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's806 // 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: attachments825 });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.Attachments832 * and its subclasses. @see wp.media.model.Attachments._changeOrderby().833 *834 * @static835 *836 * @param {Backbone.Model} a837 * @param {Backbone.Model} b838 * @param {Object} options839 * @returns {Number} -1 if the first model should come before the second,840 * 0 if they are of the same rank and841 * 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 * @namespace866 */867 filters: {868 /**869 * @static870 * Note that this client-side searching is *not* equivalent871 * to our server-side searching.872 *873 * @param {wp.media.model.Attachment} attachment874 *875 * @this wp.media.model.Attachments876 *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 * @static891 * @param {wp.media.model.Attachment} attachment892 *893 * @this wp.media.model.Attachments894 *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 * @static903 * @param {wp.media.model.Attachment} attachment904 *905 * @this wp.media.model.Attachments906 *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 * @static919 * @param {wp.media.model.Attachment} attachment920 *921 * @this wp.media.model.Attachments922 *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.PostImage943 *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 * @class949 * @augments Backbone.Model950 *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 link972 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 linkUrl1012 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.Query1099 *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 * @class1106 * @augments wp.media.model.Attachments1107 * @augments Backbone.Collection1108 *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.Uploader1120 *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 last1143 // item in the set. If we add any items after the last1144 // 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 and1149 // we're sorting for recent items. In that case, we want1150 // 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 for1165 // new matches for the query.1166 //1167 // Only observe when a limited number of query args are set. There1168 // are no filters for other properties, so observing will result in1169 // 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 server1177 * 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.sync1213 * Overrides wp.media.model.Attachments.sync1214 *1215 * @param {String} method1216 * @param {Backbone.Model} model1217 * @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.id1230 });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.sync1247 */1248 fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone;1249 return fallback.sync.apply( this, arguments );1250 }1251 }1252 }, {1253 /**1254 * @readonly1255 */1256 defaultProps: {1257 orderby: 'date',1258 order: 'DESC'1259 },1260 /**1261 * @readonly1262 */1263 defaultArgs: {1264 posts_per_page: 401265 },1266 /**1267 * @readonly1268 */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 * @readonly1285 */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 * @static1302 * @method1303 *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 * @static1323 * @type Array1324 */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: args1393 } ) );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.Selection1409 *1410 * A selection of attachments.1411 *1412 * @class1413 * @augments wp.media.model.Attachments1414 * @augments Backbone.Collection1415 */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 ensure1423 * 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 class1431 */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 selection1440 * before adding a new attachment to it.1441 *1442 * @param {Array} models1443 * @param {Object} options1444 * @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 class1452 */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} model1460 *1461 * @fires wp.media.model.Selection#selection:single1462 * @fires wp.media.model.Selection#selection:unsingle1463 *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 collection1486 // 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.CollectionAdd6 *7 * A state for adding attachments to a collection (e.g. video playlist).8 *9 * @class10 * @augments wp.media.controller.Library11 * @augments wp.media.controller.State12 * @augments Backbone.Model13 *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.Selection44 multiple: 'add',45 // Attachments browser defaults. @see media.view.AttachmentsBrowser46 filterable: 'uploaded',47 48 priority: 100,49 syncSelection: false50 }, Library.prototype.defaults ),51 52 /**53 * @since 3.9.054 */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.075 */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 and86 // 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-added92 // to the collection. Do so silently, as calling `observe` will93 // 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.CollectionEdit109 *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 * @class114 * @augments wp.media.controller.Library115 * @augments wp.media.controller.State116 * @augments Backbone.Model117 *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: false159 },160 161 /**162 * @since 3.9.0163 */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.0187 */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.0204 */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 frame218 * is responsible for adding region mode callbacks. Explain.219 *220 * @since 3.9.0221 *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: 40241 });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: -40249 }) );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.Cropper271 *272 * A state for cropping an image.273 *274 * @class275 * @augments wp.media.controller.State276 * @augments Backbone.Model277 */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: false291 },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.EditImage389 *390 * A state for editing (cropping, etc.) an image.391 *392 * @class393 * @augments wp.media.controller.State394 * @augments Backbone.Model395 *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.0420 */421 activate: function() {422 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );423 },424 425 /**426 * @since 3.9.0427 */428 deactivate: function() {429 this.stopListening( this.frame );430 },431 432 /**433 * @since 3.9.0434 */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.Embed467 *468 * A state for embedding media from a URL.469 *470 * @class471 * @augments wp.media.controller.State472 * @augments Backbone.Model473 *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#scan519 */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 the529 // 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} attributes553 */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.height578 });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.FeaturedImage605 *606 * A state for selecting a featured image for a post.607 *608 * @class609 * @augments wp.media.controller.Library610 * @augments wp.media.controller.State611 * @augments Backbone.Model612 *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: true648 }, Library.prototype.defaults ),649 650 /**651 * @since 3.5.0652 */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 in667 // 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 featured682 // images that are not initially loaded still appear.683 library.observe( this.get('selection') );684 },685 686 /**687 * @since 3.5.0688 */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.0698 */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.0707 */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.GalleryAdd729 *730 * A state for selecting more images to add to a gallery.731 *732 * @class733 * @augments wp.media.controller.Library734 * @augments wp.media.controller.State735 * @augments Backbone.Model736 *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: false773 }, Library.prototype.defaults ),774 775 /**776 * @since 3.5.0777 */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.0789 */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 and799 // 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-added805 // to the collection. Do so silently, as calling `observe` will806 // 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.GalleryEdit822 *823 * A state for editing a gallery's images and settings.824 *825 * @class826 * @augments wp.media.controller.Library827 * @augments wp.media.controller.State828 * @augments Backbone.Model829 *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: false872 },873 874 /**875 * @since 3.5.0876 */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.0893 */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.0910 */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.0922 *923 * @param browser924 */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: 40943 })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.ImageDetails964 *965 * A state for editing the attachment display settings of an image that's been966 * inserted into the editor.967 *968 * @class969 * @augments wp.media.controller.State970 * @augments Backbone.Model971 *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: 601001 }, Library.prototype.defaults ),1002 1003 /**1004 * @since 3.9.01005 *1006 * @param options Attributes1007 */1008 initialize: function( options ) {1009 this.image = options.image;1010 State.prototype.initialize.apply( this, arguments );1011 },1012 1013 /**1014 * @since 3.9.01015 */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.Library1028 *1029 * A state for choosing an attachment or group of attachments from the media library.1030 *1031 * @class1032 * @augments wp.media.controller.State1033 * @augments Backbone.Model1034 * @mixes media.selectionSync1035 *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: true1081 },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.01088 */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: props1108 }) );1109 }1110 1111 this.resetDisplays();1112 },1113 1114 /**1115 * @since 3.5.01116 */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.01132 */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 context1139 // 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.01149 */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.01162 */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.01177 *1178 * @param {wp.media.model.Attachment} attachment1179 * @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.01194 *1195 * @param {wp.media.model.Attachment} attachment1196 * @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.01210 *1211 * @param {wp.media.model.Attachment} attachment1212 * @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 current1229 * content mode is not an option in the state's router (provided1230 * the state has a router), reset the content mode to the default.1231 *1232 * @since 3.5.01233 */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 multiple1253 * attachments are uploaded, the last attachment in the upload queue will1254 * be selected.1255 *1256 * @since 3.5.01257 *1258 * @param {wp.media.model.Attachment} attachment1259 */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.01277 */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.MediaLibrary1302 *1303 * @class1304 * @augments wp.media.controller.Library1305 * @augments wp.media.controller.State1306 * @augments Backbone.Model1307 */1308 var Library = wp.media.controller.Library,1309 MediaLibrary;1310 1311 MediaLibrary = Library.extend({1312 defaults: _.defaults({1313 // Attachments browser defaults. @see media.view.AttachmentsBrowser1314 filterable: 'uploaded',1315 1316 displaySettings: false,1317 priority: 80,1318 syncSelection: false1319 }, Library.prototype.defaults ),1320 1321 /**1322 * @since 3.9.01323 *1324 * @param options1325 */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.01336 */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.Region1354 *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 an1361 * appropriate view depending on mode. For example, a frame listens for the1362 * 'browse' mode t be activated on the 'content' view and then fills the region1363 * with an AttachmentsBrowser view.1364 *1365 * @class1366 *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.01384 *1385 * @param {string} mode1386 *1387 * @fires this.view#{this.id}:activate:{this._mode}1388 * @fires this.view#{this.id}:activate1389 * @fires this.view#{this.id}:deactivate:{this._mode}1390 * @fires this.view#{this.id}:deactivate1391 *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}:deactivate1408 */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}:activate1419 */1420 this.trigger('activate');1421 return this;1422 },1423 /**1424 * Render a mode.1425 *1426 * @since 3.5.01427 *1428 * @param {string} mode1429 *1430 * @fires this.view#{this.id}:create:{this._mode}1431 * @fires this.view#{this.id}:create1432 * @fires this.view#{this.id}:render:{this._mode}1433 * @fires this.view#{this.id}:render1434 *1435 * @returns {wp.media.controller.Region} Returns itself to allow chaining1436 */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}:create1453 */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}:create1464 */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.01476 *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.01487 *1488 * @param {Array|Object} views1489 * @param {Object} [options={}]1490 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining1491 */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.01503 *1504 * @param {string} event1505 * @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.ReplaceImage1535 *1536 * A state for replacing an image.1537 *1538 * @class1539 * @augments wp.media.controller.Library1540 * @augments wp.media.controller.State1541 * @augments Backbone.Model1542 *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: true1578 }, Library.prototype.defaults ),1579 1580 /**1581 * @since 3.9.01582 *1583 * @param options1584 */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 in1600 // 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 featured1615 // images that are not initially loaded still appear.1616 library.observe( this.get('selection') );1617 },1618 1619 /**1620 * @since 3.9.01621 */1622 activate: function() {1623 this.updateSelection();1624 Library.prototype.activate.apply( this, arguments );1625 },1626 1627 /**1628 * @since 3.9.01629 */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.StateMachine1645 *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.01652 *1653 * @class1654 * @augments Backbone.Model1655 * @mixin1656 * @mixes Backbone.Events1657 *1658 * @param {Array} states1659 */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.01680 *1681 * @param {string} id1682 * @returns {wp.media.controller.State} Returns a State model1683 * from the StateMachine collection1684 */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't1701 * created the `states` collection, or are trying to select a state1702 * that does not exist.1703 *1704 * @since 3.5.01705 *1706 * @param {string} id1707 *1708 * @fires wp.media.controller.State#deactivate1709 * @fires wp.media.controller.State#activate1710 *1711 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining1712 */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 current1735 * active state.1736 *1737 * @since 3.5.01738 *1739 * @returns {wp.media.controller.State} Returns a State model1740 * from the StateMachine collection1741 */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.State1771 *1772 * A state is a step in a workflow that when set will trigger the controllers1773 * 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 the1781 * proper controller to reset the state to its default.1782 *1783 * @class1784 * @augments Backbone.Model1785 */1786 var State = Backbone.Model.extend({1787 /**1788 * Constructor.1789 *1790 * @since 3.5.01791 */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 arguments1803 */1804 Backbone.Model.apply( this, arguments );1805 this.on( 'change:menu', this._updateMenu, this );1806 },1807 /**1808 * Ready event callback.1809 *1810 * @abstract1811 * @since 3.5.01812 */1813 ready: function() {},1814 1815 /**1816 * Activate event callback.1817 *1818 * @abstract1819 * @since 3.5.01820 */1821 activate: function() {},1822 1823 /**1824 * Deactivate event callback.1825 *1826 * @abstract1827 * @since 3.5.01828 */1829 deactivate: function() {},1830 1831 /**1832 * Reset event callback.1833 *1834 * @abstract1835 * @since 3.5.01836 */1837 reset: function() {},1838 1839 /**1840 * @access private1841 * @since 3.5.01842 */1843 _ready: function() {1844 this._updateMenu();1845 },1846 1847 /**1848 * @access private1849 * @since 3.5.01850 */1851 _preActivate: function() {1852 this.active = true;1853 },1854 1855 /**1856 * @access private1857 * @since 3.5.01858 */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 private1876 * @since 3.5.01877 */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 private1891 * @since 3.5.01892 */1893 _title: function() {1894 this.frame.title.render( this.get('titleMode') || 'default' );1895 },1896 1897 /**1898 * @access private1899 * @since 3.5.01900 */1901 _renderTitle: function( view ) {1902 view.$el.text( this.get('title') || '' );1903 },1904 1905 /**1906 * @access private1907 * @since 3.5.01908 */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 private1929 * @since 3.5.01930 */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 private1951 * @since 3.5.01952 */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 private1970 * @since 3.5.01971 *1972 * @param {media.view.Menu} view The menu view.1973 */1974 _renderMenu: function( view ) {1975 var menuItem = this.get('menuItem'),1976 title = this.get('title'),1977 priority = this.get('priority');1978 1979 if ( ! menuItem && title ) {1980 menuItem = { text: title };1981 1982 if ( priority ) {1983 menuItem.priority = priority;1984 }1985 }1986 1987 if ( ! menuItem ) {1988 return;1989 }1990 1991 view.set( this.id, menuItem );1992 }1993 });1994 1995 _.each(['toolbar','content'], function( region ) {1996 /**1997 * @access private1998 */1999 State.prototype[ '_' + region ] = function() {2000 var mode = this.get( region );2001 if ( mode ) {2002 this.frame[ region ].render( mode );2003 }2004 };2005 });2006 2007 module.exports = State;2008 2009 },{}],16:[function(require,module,exports){2010 /*globals _ */2011 2012 /**2013 * wp.media.selectionSync2014 *2015 * Sync an attachments selection in a state with another state.2016 *2017 * Allows for selecting multiple images in the Insert Media workflow, and then2018 * switching to the Insert Gallery workflow while preserving the attachments selection.2019 *2020 * @mixin2021 */2022 var selectionSync = {2023 /**2024 * @since 3.5.02025 */2026 syncSelection: function() {2027 var selection = this.get('selection'),2028 manager = this.frame._selection;2029 2030 if ( ! this.get('syncSelection') || ! manager || ! selection ) {2031 return;2032 }2033 2034 // If the selection supports multiple items, validate the stored2035 // attachments based on the new selection's conditions. Record2036 // the attachments that are not included; we'll maintain a2037 // reference to those. Other attachments are considered in flux.2038 if ( selection.multiple ) {2039 selection.reset( [], { silent: true });2040 selection.validateAll( manager.attachments );2041 manager.difference = _.difference( manager.attachments.models, selection.models );2042 }2043 2044 // Sync the selection's single item with the master.2045 selection.single( manager.single );2046 },2047 2048 /**2049 * Record the currently active attachments, which is a combination2050 * of the selection's attachments and the set of selected2051 * attachments that this specific selection considered invalid.2052 * Reset the difference and record the single attachment.2053 *2054 * @since 3.5.02055 */2056 recordSelection: function() {2057 var selection = this.get('selection'),2058 manager = this.frame._selection;2059 2060 if ( ! this.get('syncSelection') || ! manager || ! selection ) {2061 return;2062 }2063 2064 if ( selection.multiple ) {2065 manager.attachments.reset( selection.toArray().concat( manager.difference ) );2066 manager.difference = [];2067 } else {2068 manager.attachments.add( selection.toArray() );2069 }2070 2071 manager.single = selection._single;2072 }2073 };2074 2075 module.exports = selectionSync;2076 2077 },{}],17:[function(require,module,exports){2078 /*globals wp, jQuery, _, Backbone */2079 2080 var media = wp.media,2081 $ = jQuery,2082 l10n;2083 2084 media.isTouchDevice = ( 'ontouchend' in document );2085 2086 // Link any localized strings.2087 l10n = media.view.l10n = window._wpMediaViewsL10n || {};2088 2089 // Link any settings.2090 media.view.settings = l10n.settings || {};2091 delete l10n.settings;2092 2093 // Copy the `post` setting over to the model settings.2094 media.model.settings.post = media.view.settings.post;2095 2096 // Check if the browser supports CSS 3.0 transitions2097 $.support.transition = (function(){2098 var style = document.documentElement.style,2099 transitions = {2100 WebkitTransition: 'webkitTransitionEnd',2101 MozTransition: 'transitionend',2102 OTransition: 'oTransitionEnd otransitionend',2103 transition: 'transitionend'2104 }, transition;2105 2106 transition = _.find( _.keys( transitions ), function( transition ) {2107 return ! _.isUndefined( style[ transition ] );2108 });2109 2110 return transition && {2111 end: transitions[ transition ]2112 };2113 }());2114 2115 /**2116 * A shared event bus used to provide events into2117 * the media workflows that 3rd-party devs can use to hook2118 * in.2119 */2120 media.events = _.extend( {}, Backbone.Events );2121 2122 /**2123 * Makes it easier to bind events using transitions.2124 *2125 * @param {string} selector2126 * @param {Number} sensitivity2127 * @returns {Promise}2128 */2129 media.transition = function( selector, sensitivity ) {2130 var deferred = $.Deferred();2131 2132 sensitivity = sensitivity || 2000;2133 2134 if ( $.support.transition ) {2135 if ( ! (selector instanceof $) ) {2136 selector = $( selector );2137 }2138 2139 // Resolve the deferred when the first element finishes animating.2140 selector.first().one( $.support.transition.end, deferred.resolve );2141 2142 // Just in case the event doesn't trigger, fire a callback.2143 _.delay( deferred.resolve, sensitivity );2144 2145 // Otherwise, execute on the spot.2146 } else {2147 deferred.resolve();2148 }2149 2150 return deferred.promise();2151 };2152 2153 media.controller.Region = require( './controllers/region.js' );2154 media.controller.StateMachine = require( './controllers/state-machine.js' );2155 media.controller.State = require( './controllers/state.js' );2156 2157 media.selectionSync = require( './utils/selection-sync.js' );2158 media.controller.Library = require( './controllers/library.js' );2159 media.controller.ImageDetails = require( './controllers/image-details.js' );2160 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );2161 media.controller.GalleryAdd = require( './controllers/gallery-add.js' );2162 media.controller.CollectionEdit = require( './controllers/collection-edit.js' );2163 media.controller.CollectionAdd = require( './controllers/collection-add.js' );2164 media.controller.FeaturedImage = require( './controllers/featured-image.js' );2165 media.controller.ReplaceImage = require( './controllers/replace-image.js' );2166 media.controller.EditImage = require( './controllers/edit-image.js' );2167 media.controller.MediaLibrary = require( './controllers/media-library.js' );2168 media.controller.Embed = require( './controllers/embed.js' );2169 media.controller.Cropper = require( './controllers/cropper.js' );2170 2171 media.View = require( './views/view.js' );2172 media.view.Frame = require( './views/frame.js' );2173 media.view.MediaFrame = require( './views/media-frame.js' );2174 media.view.MediaFrame.Select = require( './views/frame/select.js' );2175 media.view.MediaFrame.Post = require( './views/frame/post.js' );2176 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );2177 media.view.Modal = require( './views/modal.js' );2178 media.view.FocusManager = require( './views/focus-manager.js' );2179 media.view.UploaderWindow = require( './views/uploader/window.js' );2180 media.view.EditorUploader = require( './views/uploader/editor.js' );2181 media.view.UploaderInline = require( './views/uploader/inline.js' );2182 media.view.UploaderStatus = require( './views/uploader/status.js' );2183 media.view.UploaderStatusError = require( './views/uploader/status-error.js' );2184 media.view.Toolbar = require( './views/toolbar.js' );2185 media.view.Toolbar.Select = require( './views/toolbar/select.js' );2186 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );2187 media.view.Button = require( './views/button.js' );2188 media.view.ButtonGroup = require( './views/button-group.js' );2189 media.view.PriorityList = require( './views/priority-list.js' );2190 media.view.MenuItem = require( './views/menu-item.js' );2191 media.view.Menu = require( './views/menu.js' );2192 media.view.RouterItem = require( './views/router-item.js' );2193 media.view.Router = require( './views/router.js' );2194 media.view.Sidebar = require( './views/sidebar.js' );2195 media.view.Attachment = require( './views/attachment.js' );2196 media.view.Attachment.Library = require( './views/attachment/library.js' );2197 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );2198 media.view.Attachments = require( './views/attachments.js' );2199 media.view.Search = require( './views/search.js' );2200 media.view.AttachmentFilters = require( './views/attachment-filters.js' );2201 media.view.DateFilter = require( './views/attachment-filters/date.js' );2202 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );2203 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );2204 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );2205 media.view.Selection = require( './views/selection.js' );2206 media.view.Attachment.Selection = require( './views/attachment/selection.js' );2207 media.view.Attachments.Selection = require( './views/attachments/selection.js' );2208 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );2209 media.view.Settings = require( './views/settings.js' );2210 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );2211 media.view.Settings.Gallery = require( './views/settings/gallery.js' );2212 media.view.Settings.Playlist = require( './views/settings/playlist.js' );2213 media.view.Attachment.Details = require( './views/attachment/details.js' );2214 media.view.AttachmentCompat = require( './views/attachment-compat.js' );2215 media.view.Iframe = require( './views/iframe.js' );2216 media.view.Embed = require( './views/embed.js' );2217 media.view.Label = require( './views/label.js' );2218 media.view.EmbedUrl = require( './views/embed/url.js' );2219 media.view.EmbedLink = require( './views/embed/link.js' );2220 media.view.EmbedImage = require( './views/embed/image.js' );2221 media.view.ImageDetails = require( './views/image-details.js' );2222 media.view.Cropper = require( './views/cropper.js' );2223 media.view.EditImage = require( './views/edit-image.js' );2224 media.view.Spinner = require( './views/spinner.js' );2225 2226 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/edit-image.js":4,"./controllers/embed.js":5,"./controllers/featured-image.js":6,"./controllers/gallery-add.js":7,"./controllers/gallery-edit.js":8,"./controllers/image-details.js":9,"./controllers/library.js":10,"./controllers/media-library.js":11,"./controllers/region.js":12,"./controllers/replace-image.js":13,"./controllers/state-machine.js":14,"./controllers/state.js":15,"./utils/selection-sync.js":16,"./views/attachment-compat.js":18,"./views/attachment-filters.js":19,"./views/attachment-filters/all.js":20,"./views/attachment-filters/date.js":21,"./views/attachment-filters/uploaded.js":22,"./views/attachment.js":23,"./views/attachment/details.js":24,"./views/attachment/edit-library.js":25,"./views/attachment/edit-selection.js":26,"./views/attachment/library.js":27,"./views/attachment/selection.js":28,"./views/attachments.js":29,"./views/attachments/browser.js":30,"./views/attachments/selection.js":31,"./views/button-group.js":32,"./views/button.js":33,"./views/cropper.js":34,"./views/edit-image.js":35,"./views/embed.js":36,"./views/embed/image.js":37,"./views/embed/link.js":38,"./views/embed/url.js":39,"./views/focus-manager.js":40,"./views/frame.js":41,"./views/frame/image-details.js":42,"./views/frame/post.js":43,"./views/frame/select.js":44,"./views/iframe.js":45,"./views/image-details.js":46,"./views/label.js":47,"./views/media-frame.js":48,"./views/menu-item.js":49,"./views/menu.js":50,"./views/modal.js":51,"./views/priority-list.js":52,"./views/router-item.js":53,"./views/router.js":54,"./views/search.js":55,"./views/selection.js":56,"./views/settings.js":57,"./views/settings/attachment-display.js":58,"./views/settings/gallery.js":59,"./views/settings/playlist.js":60,"./views/sidebar.js":61,"./views/spinner.js":62,"./views/toolbar.js":63,"./views/toolbar/embed.js":64,"./views/toolbar/select.js":65,"./views/uploader/editor.js":66,"./views/uploader/inline.js":67,"./views/uploader/status-error.js":68,"./views/uploader/status.js":69,"./views/uploader/window.js":70,"./views/view.js":71}],18:[function(require,module,exports){2227 /*globals _ */2228 2229 /**2230 * wp.media.view.AttachmentCompat2231 *2232 * A view to display fields added via the `attachment_fields_to_edit` filter.2233 *2234 * @class2235 * @augments wp.media.View2236 * @augments wp.Backbone.View2237 * @augments Backbone.View2238 */2239 var View = wp.media.View,2240 AttachmentCompat;2241 2242 AttachmentCompat = View.extend({2243 tagName: 'form',2244 className: 'compat-item',2245 2246 events: {2247 'submit': 'preventDefault',2248 'change input': 'save',2249 'change select': 'save',2250 'change textarea': 'save'2251 },2252 2253 initialize: function() {2254 this.listenTo( this.model, 'change:compat', this.render );2255 },2256 /**2257 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining2258 */2259 dispose: function() {2260 if ( this.$(':focus').length ) {2261 this.save();2262 }2263 /**2264 * call 'dispose' directly on the parent class2265 */2266 return View.prototype.dispose.apply( this, arguments );2267 },2268 /**2269 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining2270 */2271 render: function() {2272 var compat = this.model.get('compat');2273 if ( ! compat || ! compat.item ) {2274 return;2275 }2276 2277 this.views.detach();2278 this.$el.html( compat.item );2279 this.views.render();2280 return this;2281 },2282 /**2283 * @param {Object} event2284 */2285 preventDefault: function( event ) {2286 event.preventDefault();2287 },2288 /**2289 * @param {Object} event2290 */2291 save: function( event ) {2292 var data = {};2293 2294 if ( event ) {2295 event.preventDefault();2296 }2297 2298 _.each( this.$el.serializeArray(), function( pair ) {2299 data[ pair.name ] = pair.value;2300 });2301 2302 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );2303 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );2304 },2305 2306 postSave: function() {2307 this.controller.trigger( 'attachment:compat:ready', ['ready'] );2308 }2309 });2310 2311 module.exports = AttachmentCompat;2312 2313 },{}],19:[function(require,module,exports){2314 /*globals _, jQuery */2315 2316 /**2317 * wp.media.view.AttachmentFilters2318 *2319 * @class2320 * @augments wp.media.View2321 * @augments wp.Backbone.View2322 * @augments Backbone.View2323 */2324 var $ = jQuery,2325 AttachmentFilters;2326 2327 AttachmentFilters = wp.media.View.extend({2328 tagName: 'select',2329 className: 'attachment-filters',2330 id: 'media-attachment-filters',2331 2332 events: {2333 change: 'change'2334 },2335 2336 keys: [],2337 2338 initialize: function() {2339 this.createFilters();2340 _.extend( this.filters, this.options.filters );2341 2342 // Build `<option>` elements.2343 this.$el.html( _.chain( this.filters ).map( function( filter, value ) {2344 return {2345 el: $( '<option></option>' ).val( value ).html( filter.text )[0],2346 priority: filter.priority || 502347 };2348 }, this ).sortBy('priority').pluck('el').value() );2349 2350 this.listenTo( this.model, 'change', this.select );2351 this.select();2352 },2353 2354 /**2355 * @abstract2356 */2357 createFilters: function() {2358 this.filters = {};2359 },2360 2361 /**2362 * When the selected filter changes, update the Attachment Query properties to match.2363 */2364 change: function() {2365 var filter = this.filters[ this.el.value ];2366 if ( filter ) {2367 this.model.set( filter.props );2368 }2369 },2370 2371 select: function() {2372 var model = this.model,2373 value = 'all',2374 props = model.toJSON();2375 2376 _.find( this.filters, function( filter, id ) {2377 var equal = _.all( filter.props, function( prop, key ) {2378 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );2379 });2380 2381 if ( equal ) {2382 return value = id;2383 }2384 });2385 2386 this.$el.val( value );2387 }2388 });2389 2390 module.exports = AttachmentFilters;2391 2392 },{}],20:[function(require,module,exports){2393 /*globals wp */2394 2395 /**2396 * wp.media.view.AttachmentFilters.All2397 *2398 * @class2399 * @augments wp.media.view.AttachmentFilters2400 * @augments wp.media.View2401 * @augments wp.Backbone.View2402 * @augments Backbone.View2403 */2404 var l10n = wp.media.view.l10n,2405 All;2406 2407 All = wp.media.view.AttachmentFilters.extend({2408 createFilters: function() {2409 var filters = {};2410 2411 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {2412 filters[ key ] = {2413 text: text,2414 props: {2415 status: null,2416 type: key,2417 uploadedTo: null,2418 orderby: 'date',2419 order: 'DESC'2420 }2421 };2422 });2423 2424 filters.all = {2425 text: l10n.allMediaItems,2426 props: {2427 status: null,2428 type: null,2429 uploadedTo: null,2430 orderby: 'date',2431 order: 'DESC'2432 },2433 priority: 102434 };2435 2436 if ( wp.media.view.settings.post.id ) {2437 filters.uploaded = {2438 text: l10n.uploadedToThisPost,2439 props: {2440 status: null,2441 type: null,2442 uploadedTo: wp.media.view.settings.post.id,2443 orderby: 'menuOrder',2444 order: 'ASC'2445 },2446 priority: 202447 };2448 }2449 2450 filters.unattached = {2451 text: l10n.unattached,2452 props: {2453 status: null,2454 uploadedTo: 0,2455 type: null,2456 orderby: 'menuOrder',2457 order: 'ASC'2458 },2459 priority: 502460 };2461 2462 if ( wp.media.view.settings.mediaTrash &&2463 this.controller.isModeActive( 'grid' ) ) {2464 2465 filters.trash = {2466 text: l10n.trash,2467 props: {2468 uploadedTo: null,2469 status: 'trash',2470 type: null,2471 orderby: 'date',2472 order: 'DESC'2473 },2474 priority: 502475 };2476 }2477 2478 this.filters = filters;2479 }2480 });2481 2482 module.exports = All;2483 2484 },{}],21:[function(require,module,exports){2485 /*globals wp, _ */2486 2487 /**2488 * A filter dropdown for month/dates.2489 *2490 * @class2491 * @augments wp.media.view.AttachmentFilters2492 * @augments wp.media.View2493 * @augments wp.Backbone.View2494 * @augments Backbone.View2495 */2496 var l10n = wp.media.view.l10n,2497 DateFilter;2498 2499 DateFilter = wp.media.view.AttachmentFilters.extend({2500 id: 'media-attachment-date-filters',2501 2502 createFilters: function() {2503 var filters = {};2504 _.each( wp.media.view.settings.months || {}, function( value, index ) {2505 filters[ index ] = {2506 text: value.text,2507 props: {2508 year: value.year,2509 monthnum: value.month2510 }2511 };2512 });2513 filters.all = {2514 text: l10n.allDates,2515 props: {2516 monthnum: false,2517 year: false2518 },2519 priority: 102520 };2521 this.filters = filters;2522 }2523 });2524 2525 module.exports = DateFilter;2526 2527 },{}],22:[function(require,module,exports){2528 /*globals wp */2529 2530 /**2531 * wp.media.view.AttachmentFilters.Uploaded2532 *2533 * @class2534 * @augments wp.media.view.AttachmentFilters2535 * @augments wp.media.View2536 * @augments wp.Backbone.View2537 * @augments Backbone.View2538 */2539 var l10n = wp.media.view.l10n,2540 Uploaded;2541 2542 Uploaded = wp.media.view.AttachmentFilters.extend({2543 createFilters: function() {2544 var type = this.model.get('type'),2545 types = wp.media.view.settings.mimeTypes,2546 text;2547 2548 if ( types && type ) {2549 text = types[ type ];2550 }2551 2552 this.filters = {2553 all: {2554 text: text || l10n.allMediaItems,2555 props: {2556 uploadedTo: null,2557 orderby: 'date',2558 order: 'DESC'2559 },2560 priority: 102561 },2562 2563 uploaded: {2564 text: l10n.uploadedToThisPost,2565 props: {2566 uploadedTo: wp.media.view.settings.post.id,2567 orderby: 'menuOrder',2568 order: 'ASC'2569 },2570 priority: 202571 },2572 2573 unattached: {2574 text: l10n.unattached,2575 props: {2576 uploadedTo: 0,2577 orderby: 'menuOrder',2578 order: 'ASC'2579 },2580 priority: 502581 }2582 };2583 }2584 });2585 2586 module.exports = Uploaded;2587 2588 },{}],23:[function(require,module,exports){2589 /*globals wp, _, jQuery */2590 2591 /**2592 * wp.media.view.Attachment2593 *2594 * @class2595 * @augments wp.media.View2596 * @augments wp.Backbone.View2597 * @augments Backbone.View2598 */2599 var View = wp.media.View,2600 $ = jQuery,2601 Attachment;2602 2603 Attachment = View.extend({2604 tagName: 'li',2605 className: 'attachment',2606 template: wp.template('attachment'),2607 2608 attributes: function() {2609 return {2610 'tabIndex': 0,2611 'role': 'checkbox',2612 'aria-label': this.model.get( 'title' ),2613 'aria-checked': false,2614 'data-id': this.model.get( 'id' )2615 };2616 },2617 2618 events: {2619 'click .js--select-attachment': 'toggleSelectionHandler',2620 'change [data-setting]': 'updateSetting',2621 'change [data-setting] input': 'updateSetting',2622 'change [data-setting] select': 'updateSetting',2623 'change [data-setting] textarea': 'updateSetting',2624 'click .close': 'removeFromLibrary',2625 'click .check': 'checkClickHandler',2626 'click a': 'preventDefault',2627 'keydown .close': 'removeFromLibrary',2628 'keydown': 'toggleSelectionHandler'2629 },2630 2631 buttons: {},2632 2633 initialize: function() {2634 var selection = this.options.selection,2635 options = _.defaults( this.options, {2636 rerenderOnModelChange: true2637 } );2638 2639 if ( options.rerenderOnModelChange ) {2640 this.listenTo( this.model, 'change', this.render );2641 } else {2642 this.listenTo( this.model, 'change:percent', this.progress );2643 }2644 this.listenTo( this.model, 'change:title', this._syncTitle );2645 this.listenTo( this.model, 'change:caption', this._syncCaption );2646 this.listenTo( this.model, 'change:artist', this._syncArtist );2647 this.listenTo( this.model, 'change:album', this._syncAlbum );2648 2649 // Update the selection.2650 this.listenTo( this.model, 'add', this.select );2651 this.listenTo( this.model, 'remove', this.deselect );2652 if ( selection ) {2653 selection.on( 'reset', this.updateSelect, this );2654 // Update the model's details view.2655 this.listenTo( this.model, 'selection:single selection:unsingle', this.details );2656 this.details( this.model, this.controller.state().get('selection') );2657 }2658 2659 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );2660 },2661 /**2662 * @returns {wp.media.view.Attachment} Returns itself to allow chaining2663 */2664 dispose: function() {2665 var selection = this.options.selection;2666 2667 // Make sure all settings are saved before removing the view.2668 this.updateAll();2669 2670 if ( selection ) {2671 selection.off( null, null, this );2672 }2673 /**2674 * call 'dispose' directly on the parent class2675 */2676 View.prototype.dispose.apply( this, arguments );2677 return this;2678 },2679 /**2680 * @returns {wp.media.view.Attachment} Returns itself to allow chaining2681 */2682 render: function() {2683 var options = _.defaults( this.model.toJSON(), {2684 orientation: 'landscape',2685 uploading: false,2686 type: '',2687 subtype: '',2688 icon: '',2689 filename: '',2690 caption: '',2691 title: '',2692 dateFormatted: '',2693 width: '',2694 height: '',2695 compat: false,2696 alt: '',2697 description: ''2698 }, this.options );2699 2700 options.buttons = this.buttons;2701 options.describe = this.controller.state().get('describe');2702 2703 if ( 'image' === options.type ) {2704 options.size = this.imageSize();2705 }2706 2707 options.can = {};2708 if ( options.nonces ) {2709 options.can.remove = !! options.nonces['delete'];2710 options.can.save = !! options.nonces.update;2711 }2712 2713 if ( this.controller.state().get('allowLocalEdits') ) {2714 options.allowLocalEdits = true;2715 }2716 2717 if ( options.uploading && ! options.percent ) {2718 options.percent = 0;2719 }2720 2721 this.views.detach();2722 this.$el.html( this.template( options ) );2723 2724 this.$el.toggleClass( 'uploading', options.uploading );2725 2726 if ( options.uploading ) {2727 this.$bar = this.$('.media-progress-bar div');2728 } else {2729 delete this.$bar;2730 }2731 2732 // Check if the model is selected.2733 this.updateSelect();2734 2735 // Update the save status.2736 this.updateSave();2737 2738 this.views.render();2739 2740 return this;2741 },2742 2743 progress: function() {2744 if ( this.$bar && this.$bar.length ) {2745 this.$bar.width( this.model.get('percent') + '%' );2746 }2747 },2748 2749 /**2750 * @param {Object} event2751 */2752 toggleSelectionHandler: function( event ) {2753 var method;2754 2755 // Don't do anything inside inputs.2756 if ( 'INPUT' === event.target.nodeName ) {2757 return;2758 }2759 2760 // Catch arrow events2761 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {2762 this.controller.trigger( 'attachment:keydown:arrow', event );2763 return;2764 }2765 2766 // Catch enter and space events2767 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {2768 return;2769 }2770 2771 event.preventDefault();2772 2773 // In the grid view, bubble up an edit:attachment event to the controller.2774 if ( this.controller.isModeActive( 'grid' ) ) {2775 if ( this.controller.isModeActive( 'edit' ) ) {2776 // Pass the current target to restore focus when closing2777 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );2778 return;2779 }2780 2781 if ( this.controller.isModeActive( 'select' ) ) {2782 method = 'toggle';2783 }2784 }2785 2786 if ( event.shiftKey ) {2787 method = 'between';2788 } else if ( event.ctrlKey || event.metaKey ) {2789 method = 'toggle';2790 }2791 2792 this.toggleSelection({2793 method: method2794 });2795 2796 this.controller.trigger( 'selection:toggle' );2797 },2798 /**2799 * @param {Object} options2800 */2801 toggleSelection: function( options ) {2802 var collection = this.collection,2803 selection = this.options.selection,2804 model = this.model,2805 method = options && options.method,2806 single, models, singleIndex, modelIndex;2807 2808 if ( ! selection ) {2809 return;2810 }2811 2812 single = selection.single();2813 method = _.isUndefined( method ) ? selection.multiple : method;2814 2815 // If the `method` is set to `between`, select all models that2816 // exist between the current and the selected model.2817 if ( 'between' === method && single && selection.multiple ) {2818 // If the models are the same, short-circuit.2819 if ( single === model ) {2820 return;2821 }2822 2823 singleIndex = collection.indexOf( single );2824 modelIndex = collection.indexOf( this.model );2825 2826 if ( singleIndex < modelIndex ) {2827 models = collection.models.slice( singleIndex, modelIndex + 1 );2828 } else {2829 models = collection.models.slice( modelIndex, singleIndex + 1 );2830 }2831 2832 selection.add( models );2833 selection.single( model );2834 return;2835 2836 // If the `method` is set to `toggle`, just flip the selection2837 // status, regardless of whether the model is the single model.2838 } else if ( 'toggle' === method ) {2839 selection[ this.selected() ? 'remove' : 'add' ]( model );2840 selection.single( model );2841 return;2842 } else if ( 'add' === method ) {2843 selection.add( model );2844 selection.single( model );2845 return;2846 }2847 2848 // Fixes bug that loses focus when selecting a featured image2849 if ( ! method ) {2850 method = 'add';2851 }2852 2853 if ( method !== 'add' ) {2854 method = 'reset';2855 }2856 2857 if ( this.selected() ) {2858 // If the model is the single model, remove it.2859 // If it is not the same as the single model,2860 // it now becomes the single model.2861 selection[ single === model ? 'remove' : 'single' ]( model );2862 } else {2863 // If the model is not selected, run the `method` on the2864 // selection. By default, we `reset` the selection, but the2865 // `method` can be set to `add` the model to the selection.2866 selection[ method ]( model );2867 selection.single( model );2868 }2869 },2870 2871 updateSelect: function() {2872 this[ this.selected() ? 'select' : 'deselect' ]();2873 },2874 /**2875 * @returns {unresolved|Boolean}2876 */2877 selected: function() {2878 var selection = this.options.selection;2879 if ( selection ) {2880 return !! selection.get( this.model.cid );2881 }2882 },2883 /**2884 * @param {Backbone.Model} model2885 * @param {Backbone.Collection} collection2886 */2887 select: function( model, collection ) {2888 var selection = this.options.selection,2889 controller = this.controller;2890 2891 // Check if a selection exists and if it's the collection provided.2892 // If they're not the same collection, bail; we're in another2893 // selection's event loop.2894 if ( ! selection || ( collection && collection !== selection ) ) {2895 return;2896 }2897 2898 // Bail if the model is already selected.2899 if ( this.$el.hasClass( 'selected' ) ) {2900 return;2901 }2902 2903 // Add 'selected' class to model, set aria-checked to true.2904 this.$el.addClass( 'selected' ).attr( 'aria-checked', true );2905 // Make the checkbox tabable, except in media grid (bulk select mode).2906 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {2907 this.$( '.check' ).attr( 'tabindex', '0' );2908 }2909 },2910 /**2911 * @param {Backbone.Model} model2912 * @param {Backbone.Collection} collection2913 */2914 deselect: function( model, collection ) {2915 var selection = this.options.selection;2916 2917 // Check if a selection exists and if it's the collection provided.2918 // If they're not the same collection, bail; we're in another2919 // selection's event loop.2920 if ( ! selection || ( collection && collection !== selection ) ) {2921 return;2922 }2923 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )2924 .find( '.check' ).attr( 'tabindex', '-1' );2925 },2926 /**2927 * @param {Backbone.Model} model2928 * @param {Backbone.Collection} collection2929 */2930 details: function( model, collection ) {2931 var selection = this.options.selection,2932 details;2933 2934 if ( selection !== collection ) {2935 return;2936 }2937 2938 details = selection.single();2939 this.$el.toggleClass( 'details', details === this.model );2940 },2941 /**2942 * @param {Object} event2943 */2944 preventDefault: function( event ) {2945 event.preventDefault();2946 },2947 /**2948 * @param {string} size2949 * @returns {Object}2950 */2951 imageSize: function( size ) {2952 var sizes = this.model.get('sizes'), matched = false;2953 2954 size = size || 'medium';2955 2956 // Use the provided image size if possible.2957 if ( sizes ) {2958 if ( sizes[ size ] ) {2959 matched = sizes[ size ];2960 } else if ( sizes.large ) {2961 matched = sizes.large;2962 } else if ( sizes.thumbnail ) {2963 matched = sizes.thumbnail;2964 } else if ( sizes.full ) {2965 matched = sizes.full;2966 }2967 2968 if ( matched ) {2969 return _.clone( matched );2970 }2971 }2972 2973 return {2974 url: this.model.get('url'),2975 width: this.model.get('width'),2976 height: this.model.get('height'),2977 orientation: this.model.get('orientation')2978 };2979 },2980 /**2981 * @param {Object} event2982 */2983 updateSetting: function( event ) {2984 var $setting = $( event.target ).closest('[data-setting]'),2985 setting, value;2986 2987 if ( ! $setting.length ) {2988 return;2989 }2990 2991 setting = $setting.data('setting');2992 value = event.target.value;2993 2994 if ( this.model.get( setting ) !== value ) {2995 this.save( setting, value );2996 }2997 },2998 2999 /**3000 * Pass all the arguments to the model's save method.3001 *3002 * Records the aggregate status of all save requests and updates the3003 * view's classes accordingly.3004 */3005 save: function() {3006 var view = this,3007 save = this._save = this._save || { status: 'ready' },3008 request = this.model.save.apply( this.model, arguments ),3009 requests = save.requests ? $.when( request, save.requests ) : request;3010 3011 // If we're waiting to remove 'Saved.', stop.3012 if ( save.savedTimer ) {3013 clearTimeout( save.savedTimer );3014 }3015 3016 this.updateSave('waiting');3017 save.requests = requests;3018 requests.always( function() {3019 // If we've performed another request since this one, bail.3020 if ( save.requests !== requests ) {3021 return;3022 }3023 3024 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );3025 save.savedTimer = setTimeout( function() {3026 view.updateSave('ready');3027 delete save.savedTimer;3028 }, 2000 );3029 });3030 },3031 /**3032 * @param {string} status3033 * @returns {wp.media.view.Attachment} Returns itself to allow chaining3034 */3035 updateSave: function( status ) {3036 var save = this._save = this._save || { status: 'ready' };3037 3038 if ( status && status !== save.status ) {3039 this.$el.removeClass( 'save-' + save.status );3040 save.status = status;3041 }3042 3043 this.$el.addClass( 'save-' + save.status );3044 return this;3045 },3046 3047 updateAll: function() {3048 var $settings = this.$('[data-setting]'),3049 model = this.model,3050 changed;3051 3052 changed = _.chain( $settings ).map( function( el ) {3053 var $input = $('input, textarea, select, [value]', el ),3054 setting, value;3055 3056 if ( ! $input.length ) {3057 return;3058 }3059 3060 setting = $(el).data('setting');3061 value = $input.val();3062 3063 // Record the value if it changed.3064 if ( model.get( setting ) !== value ) {3065 return [ setting, value ];3066 }3067 }).compact().object().value();3068 3069 if ( ! _.isEmpty( changed ) ) {3070 model.save( changed );3071 }3072 },3073 /**3074 * @param {Object} event3075 */3076 removeFromLibrary: function( event ) {3077 // Catch enter and space events3078 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {3079 return;3080 }3081 3082 // Stop propagation so the model isn't selected.3083 event.stopPropagation();3084 3085 this.collection.remove( this.model );3086 },3087 3088 /**3089 * Add the model if it isn't in the selection, if it is in the selection,3090 * remove it.3091 *3092 * @param {[type]} event [description]3093 * @return {[type]} [description]3094 */3095 checkClickHandler: function ( event ) {3096 var selection = this.options.selection;3097 if ( ! selection ) {3098 return;3099 }3100 event.stopPropagation();3101 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {3102 selection.remove( this.model );3103 // Move focus back to the attachment tile (from the check).3104 this.$el.focus();3105 } else {3106 selection.add( this.model );3107 }3108 }3109 });3110 3111 // Ensure settings remain in sync between attachment views.3112 _.each({3113 caption: '_syncCaption',3114 title: '_syncTitle',3115 artist: '_syncArtist',3116 album: '_syncAlbum'3117 }, function( method, setting ) {3118 /**3119 * @param {Backbone.Model} model3120 * @param {string} value3121 * @returns {wp.media.view.Attachment} Returns itself to allow chaining3122 */3123 Attachment.prototype[ method ] = function( model, value ) {3124 var $setting = this.$('[data-setting="' + setting + '"]');3125 3126 if ( ! $setting.length ) {3127 return this;3128 }3129 3130 // If the updated value is in sync with the value in the DOM, there3131 // is no need to re-render. If we're currently editing the value,3132 // it will automatically be in sync, suppressing the re-render for3133 // the view we're editing, while updating any others.3134 if ( value === $setting.find('input, textarea, select, [value]').val() ) {3135 return this;3136 }3137 3138 return this.render();3139 };3140 });3141 3142 module.exports = Attachment;3143 3144 },{}],24:[function(require,module,exports){3145 /*globals wp, _ */3146 3147 /**3148 * wp.media.view.Attachment.Details3149 *3150 * @class3151 * @augments wp.media.view.Attachment3152 * @augments wp.media.View3153 * @augments wp.Backbone.View3154 * @augments Backbone.View3155 */3156 var Attachment = wp.media.view.Attachment,3157 l10n = wp.media.view.l10n,3158 Details;3159 3160 Details = Attachment.extend({3161 tagName: 'div',3162 className: 'attachment-details',3163 template: wp.template('attachment-details'),3164 3165 attributes: function() {3166 return {3167 'tabIndex': 0,3168 'data-id': this.model.get( 'id' )3169 };3170 },3171 3172 events: {3173 'change [data-setting]': 'updateSetting',3174 'change [data-setting] input': 'updateSetting',3175 'change [data-setting] select': 'updateSetting',3176 'change [data-setting] textarea': 'updateSetting',3177 'click .delete-attachment': 'deleteAttachment',3178 'click .trash-attachment': 'trashAttachment',3179 'click .untrash-attachment': 'untrashAttachment',3180 'click .edit-attachment': 'editAttachment',3181 'click .refresh-attachment': 'refreshAttachment',3182 'keydown': 'toggleSelectionHandler'3183 },3184 3185 initialize: function() {3186 this.options = _.defaults( this.options, {3187 rerenderOnModelChange: false3188 });3189 3190 this.on( 'ready', this.initialFocus );3191 // Call 'initialize' directly on the parent class.3192 Attachment.prototype.initialize.apply( this, arguments );3193 },3194 3195 initialFocus: function() {3196 if ( ! wp.media.isTouchDevice ) {3197 this.$( ':input' ).eq( 0 ).focus();3198 }3199 },3200 /**3201 * @param {Object} event3202 */3203 deleteAttachment: function( event ) {3204 event.preventDefault();3205 3206 if ( window.confirm( l10n.warnDelete ) ) {3207 this.model.destroy();3208 // Keep focus inside media modal3209 // after image is deleted3210 this.controller.modal.focusManager.focus();3211 }3212 },3213 /**3214 * @param {Object} event3215 */3216 trashAttachment: function( event ) {3217 var library = this.controller.library;3218 event.preventDefault();3219 3220 if ( wp.media.view.settings.mediaTrash &&3221 'edit-metadata' === this.controller.content.mode() ) {3222 3223 this.model.set( 'status', 'trash' );3224 this.model.save().done( function() {3225 library._requery( true );3226 } );3227 } else {3228 this.model.destroy();3229 }3230 },3231 /**3232 * @param {Object} event3233 */3234 untrashAttachment: function( event ) {3235 var library = this.controller.library;3236 event.preventDefault();3237 3238 this.model.set( 'status', 'inherit' );3239 this.model.save().done( function() {3240 library._requery( true );3241 } );3242 },3243 /**3244 * @param {Object} event3245 */3246 editAttachment: function( event ) {3247 var editState = this.controller.states.get( 'edit-image' );3248 if ( window.imageEdit && editState ) {3249 event.preventDefault();3250 3251 editState.set( 'image', this.model );3252 this.controller.setState( 'edit-image' );3253 } else {3254 this.$el.addClass('needs-refresh');3255 }3256 },3257 /**3258 * @param {Object} event3259 */3260 refreshAttachment: function( event ) {3261 this.$el.removeClass('needs-refresh');3262 event.preventDefault();3263 this.model.fetch();3264 },3265 /**3266 * When reverse tabbing(shift+tab) out of the right details panel, deliver3267 * the focus to the item in the list that was being edited.3268 *3269 * @param {Object} event3270 */3271 toggleSelectionHandler: function( event ) {3272 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {3273 this.controller.trigger( 'attachment:details:shift-tab', event );3274 return false;3275 }3276 3277 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {3278 this.controller.trigger( 'attachment:keydown:arrow', event );3279 return;3280 }3281 }3282 });3283 3284 module.exports = Details;3285 3286 },{}],25:[function(require,module,exports){3287 /*globals wp */3288 3289 /**3290 * wp.media.view.Attachment.EditLibrary3291 *3292 * @class3293 * @augments wp.media.view.Attachment3294 * @augments wp.media.View3295 * @augments wp.Backbone.View3296 * @augments Backbone.View3297 */3298 var EditLibrary = wp.media.view.Attachment.extend({3299 buttons: {3300 close: true3301 }3302 });3303 3304 module.exports = EditLibrary;3305 3306 },{}],26:[function(require,module,exports){3307 /*globals wp */3308 3309 /**3310 * wp.media.view.Attachments.EditSelection3311 *3312 * @class3313 * @augments wp.media.view.Attachment.Selection3314 * @augments wp.media.view.Attachment3315 * @augments wp.media.View3316 * @augments wp.Backbone.View3317 * @augments Backbone.View3318 */3319 var EditSelection = wp.media.view.Attachment.Selection.extend({3320 buttons: {3321 close: true3322 }3323 });3324 3325 module.exports = EditSelection;3326 3327 },{}],27:[function(require,module,exports){3328 /*globals wp */3329 3330 /**3331 * wp.media.view.Attachment.Library3332 *3333 * @class3334 * @augments wp.media.view.Attachment3335 * @augments wp.media.View3336 * @augments wp.Backbone.View3337 * @augments Backbone.View3338 */3339 var Library = wp.media.view.Attachment.extend({3340 buttons: {3341 check: true3342 }3343 });3344 3345 module.exports = Library;3346 3347 },{}],28:[function(require,module,exports){3348 /*globals wp */3349 3350 /**3351 * wp.media.view.Attachment.Selection3352 *3353 * @class3354 * @augments wp.media.view.Attachment3355 * @augments wp.media.View3356 * @augments wp.Backbone.View3357 * @augments Backbone.View3358 */3359 var Selection = wp.media.view.Attachment.extend({3360 className: 'attachment selection',3361 3362 // On click, just select the model, instead of removing the model from3363 // the selection.3364 toggleSelection: function() {3365 this.options.selection.single( this.model );3366 }3367 });3368 3369 module.exports = Selection;3370 3371 },{}],29:[function(require,module,exports){3372 /*globals wp, _, jQuery */3373 3374 /**3375 * wp.media.view.Attachments3376 *3377 * @class3378 * @augments wp.media.View3379 * @augments wp.Backbone.View3380 * @augments Backbone.View3381 */3382 var View = wp.media.View,3383 $ = jQuery,3384 Attachments;3385 3386 Attachments = View.extend({3387 tagName: 'ul',3388 className: 'attachments',3389 3390 attributes: {3391 tabIndex: -13392 },3393 3394 initialize: function() {3395 this.el.id = _.uniqueId('__attachments-view-');3396 3397 _.defaults( this.options, {3398 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,3399 refreshThreshold: 3,3400 AttachmentView: wp.media.view.Attachment,3401 sortable: false,3402 resize: true,3403 idealColumnWidth: $( window ).width() < 640 ? 135 : 1503404 });3405 3406 this._viewsByCid = {};3407 this.$window = $( window );3408 this.resizeEvent = 'resize.media-modal-columns';3409 3410 this.collection.on( 'add', function( attachment ) {3411 this.views.add( this.createAttachmentView( attachment ), {3412 at: this.collection.indexOf( attachment )3413 });3414 }, this );3415 3416 this.collection.on( 'remove', function( attachment ) {3417 var view = this._viewsByCid[ attachment.cid ];3418 delete this._viewsByCid[ attachment.cid ];3419 3420 if ( view ) {3421 view.remove();3422 }3423 }, this );3424 3425 this.collection.on( 'reset', this.render, this );3426 3427 this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus );3428 3429 // Throttle the scroll handler and bind this.3430 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();3431 3432 this.options.scrollElement = this.options.scrollElement || this.el;3433 $( this.options.scrollElement ).on( 'scroll', this.scroll );3434 3435 this.initSortable();3436 3437 _.bindAll( this, 'setColumns' );3438 3439 if ( this.options.resize ) {3440 this.on( 'ready', this.bindEvents );3441 this.controller.on( 'open', this.setColumns );3442 3443 // Call this.setColumns() after this view has been rendered in the DOM so3444 // attachments get proper width applied.3445 _.defer( this.setColumns, this );3446 }3447 },3448 3449 bindEvents: function() {3450 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );3451 },3452 3453 attachmentFocus: function() {3454 this.$( 'li:first' ).focus();3455 },3456 3457 restoreFocus: function() {3458 this.$( 'li.selected:first' ).focus();3459 },3460 3461 arrowEvent: function( event ) {3462 var attachments = this.$el.children( 'li' ),3463 perRow = this.columns,3464 index = attachments.filter( ':focus' ).index(),3465 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );3466 3467 if ( index === -1 ) {3468 return;3469 }3470 3471 // Left arrow3472 if ( 37 === event.keyCode ) {3473 if ( 0 === index ) {3474 return;3475 }3476 attachments.eq( index - 1 ).focus();3477 }3478 3479 // Up arrow3480 if ( 38 === event.keyCode ) {3481 if ( 1 === row ) {3482 return;3483 }3484 attachments.eq( index - perRow ).focus();3485 }3486 3487 // Right arrow3488 if ( 39 === event.keyCode ) {3489 if ( attachments.length === index ) {3490 return;3491 }3492 attachments.eq( index + 1 ).focus();3493 }3494 3495 // Down arrow3496 if ( 40 === event.keyCode ) {3497 if ( Math.ceil( attachments.length / perRow ) === row ) {3498 return;3499 }3500 attachments.eq( index + perRow ).focus();3501 }3502 },3503 3504 dispose: function() {3505 this.collection.props.off( null, null, this );3506 if ( this.options.resize ) {3507 this.$window.off( this.resizeEvent );3508 }3509 3510 /**3511 * call 'dispose' directly on the parent class3512 */3513 View.prototype.dispose.apply( this, arguments );3514 },3515 3516 setColumns: function() {3517 var prev = this.columns,3518 width = this.$el.width();3519 3520 if ( width ) {3521 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;3522 3523 if ( ! prev || prev !== this.columns ) {3524 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );3525 }3526 }3527 },3528 3529 initSortable: function() {3530 var collection = this.collection;3531 3532 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {3533 return;3534 }3535 3536 this.$el.sortable( _.extend({3537 // If the `collection` has a `comparator`, disable sorting.3538 disabled: !! collection.comparator,3539 3540 // Change the position of the attachment as soon as the3541 // mouse pointer overlaps a thumbnail.3542 tolerance: 'pointer',3543 3544 // Record the initial `index` of the dragged model.3545 start: function( event, ui ) {3546 ui.item.data('sortableIndexStart', ui.item.index());3547 },3548 3549 // Update the model's index in the collection.3550 // Do so silently, as the view is already accurate.3551 update: function( event, ui ) {3552 var model = collection.at( ui.item.data('sortableIndexStart') ),3553 comparator = collection.comparator;3554 3555 // Temporarily disable the comparator to prevent `add`3556 // from re-sorting.3557 delete collection.comparator;3558 3559 // Silently shift the model to its new index.3560 collection.remove( model, {3561 silent: true3562 });3563 collection.add( model, {3564 silent: true,3565 at: ui.item.index()3566 });3567 3568 // Restore the comparator.3569 collection.comparator = comparator;3570 3571 // Fire the `reset` event to ensure other collections sync.3572 collection.trigger( 'reset', collection );3573 3574 // If the collection is sorted by menu order,3575 // update the menu order.3576 collection.saveMenuOrder();3577 }3578 }, this.options.sortable ) );3579 3580 // If the `orderby` property is changed on the `collection`,3581 // check to see if we have a `comparator`. If so, disable sorting.3582 collection.props.on( 'change:orderby', function() {3583 this.$el.sortable( 'option', 'disabled', !! collection.comparator );3584 }, this );3585 3586 this.collection.props.on( 'change:orderby', this.refreshSortable, this );3587 this.refreshSortable();3588 },3589 3590 refreshSortable: function() {3591 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {3592 return;3593 }3594 3595 // If the `collection` has a `comparator`, disable sorting.3596 var collection = this.collection,3597 orderby = collection.props.get('orderby'),3598 enabled = 'menuOrder' === orderby || ! collection.comparator;3599 3600 this.$el.sortable( 'option', 'disabled', ! enabled );3601 },3602 3603 /**3604 * @param {wp.media.model.Attachment} attachment3605 * @returns {wp.media.View}3606 */3607 createAttachmentView: function( attachment ) {3608 var view = new this.options.AttachmentView({3609 controller: this.controller,3610 model: attachment,3611 collection: this.collection,3612 selection: this.options.selection3613 });3614 3615 return this._viewsByCid[ attachment.cid ] = view;3616 },3617 3618 prepare: function() {3619 // Create all of the Attachment views, and replace3620 // the list in a single DOM operation.3621 if ( this.collection.length ) {3622 this.views.set( this.collection.map( this.createAttachmentView, this ) );3623 3624 // If there are no elements, clear the views and load some.3625 } else {3626 this.views.unset();3627 this.collection.more().done( this.scroll );3628 }3629 },3630 3631 ready: function() {3632 // Trigger the scroll event to check if we're within the3633 // threshold to query for additional attachments.3634 this.scroll();3635 },3636 3637 scroll: function() {3638 var view = this,3639 el = this.options.scrollElement,3640 scrollTop = el.scrollTop,3641 toolbar;3642 3643 // The scroll event occurs on the document, but the element3644 // that should be checked is the document body.3645 if ( el === document ) {3646 el = document.body;3647 scrollTop = $(document).scrollTop();3648 }3649 3650 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {3651 return;3652 }3653 3654 toolbar = this.views.parent.toolbar;3655 3656 // Show the spinner only if we are close to the bottom.3657 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {3658 toolbar.get('spinner').show();3659 }3660 3661 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {3662 this.collection.more().done(function() {3663 view.scroll();3664 toolbar.get('spinner').hide();3665 });3666 }3667 }3668 });3669 3670 module.exports = Attachments;3671 3672 },{}],30:[function(require,module,exports){3673 /*globals wp, _, jQuery */3674 3675 /**3676 * wp.media.view.AttachmentsBrowser3677 *3678 * @class3679 * @augments wp.media.View3680 * @augments wp.Backbone.View3681 * @augments Backbone.View3682 *3683 * @param {object} options3684 * @param {object} [options.filters=false] Which filters to show in the browser's toolbar.3685 * Accepts 'uploaded' and 'all'.3686 * @param {object} [options.search=true] Whether to show the search interface in the3687 * browser's toolbar.3688 * @param {object} [options.date=true] Whether to show the date filter in the3689 * browser's toolbar.3690 * @param {object} [options.display=false] Whether to show the attachments display settings3691 * view in the sidebar.3692 * @param {bool|string} [options.sidebar=true] Whether to create a sidebar for the browser.3693 * Accepts true, false, and 'errors'.3694 */3695 var View = wp.media.View,3696 mediaTrash = wp.media.view.settings.mediaTrash,3697 l10n = wp.media.view.l10n,3698 $ = jQuery,3699 AttachmentsBrowser;3700 3701 AttachmentsBrowser = View.extend({3702 tagName: 'div',3703 className: 'attachments-browser',3704 3705 initialize: function() {3706 _.defaults( this.options, {3707 filters: false,3708 search: true,3709 date: true,3710 display: false,3711 sidebar: true,3712 AttachmentView: wp.media.view.Attachment.Library3713 });3714 3715 this.listenTo( this.controller, 'toggle:upload:attachment', _.bind( this.toggleUploader, this ) );3716 this.controller.on( 'edit:selection', this.editSelection );3717 this.createToolbar();3718 if ( this.options.sidebar ) {3719 this.createSidebar();3720 }3721 this.createUploader();3722 this.createAttachments();3723 this.updateContent();3724 3725 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {3726 this.$el.addClass( 'hide-sidebar' );3727 3728 if ( 'errors' === this.options.sidebar ) {3729 this.$el.addClass( 'sidebar-for-errors' );3730 }3731 }3732 3733 this.collection.on( 'add remove reset', this.updateContent, this );3734 },3735 3736 editSelection: function( modal ) {3737 modal.$( '.media-button-backToLibrary' ).focus();3738 },3739 3740 /**3741 * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining3742 */3743 dispose: function() {3744 this.options.selection.off( null, null, this );3745 View.prototype.dispose.apply( this, arguments );3746 return this;3747 },3748 3749 createToolbar: function() {3750 var LibraryViewSwitcher, Filters, toolbarOptions;3751 3752 toolbarOptions = {3753 controller: this.controller3754 };3755 3756 if ( this.controller.isModeActive( 'grid' ) ) {3757 toolbarOptions.className = 'media-toolbar wp-filter';3758 }3759 3760 /**3761 * @member {wp.media.view.Toolbar}3762 */3763 this.toolbar = new wp.media.view.Toolbar( toolbarOptions );3764 3765 this.views.add( this.toolbar );3766 3767 this.toolbar.set( 'spinner', new wp.media.view.Spinner({3768 priority: -603769 }) );3770 3771 if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {3772 // "Filters" will return a <select>, need to render3773 // screen reader text before3774 this.toolbar.set( 'filtersLabel', new wp.media.view.Label({3775 value: l10n.filterByType,3776 attributes: {3777 'for': 'media-attachment-filters'3778 },3779 priority: -803780 }).render() );3781 3782 if ( 'uploaded' === this.options.filters ) {3783 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({3784 controller: this.controller,3785 model: this.collection.props,3786 priority: -803787 }).render() );3788 } else {3789 Filters = new wp.media.view.AttachmentFilters.All({3790 controller: this.controller,3791 model: this.collection.props,3792 priority: -803793 });3794 3795 this.toolbar.set( 'filters', Filters.render() );3796 }3797 }3798 3799 // Feels odd to bring the global media library switcher into the Attachment3800 // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );3801 // which the controller can tap into and add this view?3802 if ( this.controller.isModeActive( 'grid' ) ) {3803 LibraryViewSwitcher = View.extend({3804 className: 'view-switch media-grid-view-switch',3805 template: wp.template( 'media-library-view-switcher')3806 });3807 3808 this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({3809 controller: this.controller,3810 priority: -903811 }).render() );3812 3813 // DateFilter is a <select>, screen reader text needs to be rendered before3814 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({3815 value: l10n.filterByDate,3816 attributes: {3817 'for': 'media-attachment-date-filters'3818 },3819 priority: -753820 }).render() );3821 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({3822 controller: this.controller,3823 model: this.collection.props,3824 priority: -753825 }).render() );3826 3827 // BulkSelection is a <div> with subviews, including screen reader text3828 this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({3829 text: l10n.bulkSelect,3830 controller: this.controller,3831 priority: -703832 }).render() );3833 3834 this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({3835 filters: Filters,3836 style: 'primary',3837 disabled: true,3838 text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,3839 controller: this.controller,3840 priority: -60,3841 click: function() {3842 var changed = [], removed = [],3843 selection = this.controller.state().get( 'selection' ),3844 library = this.controller.state().get( 'library' );3845 3846 if ( ! selection.length ) {3847 return;3848 }3849 3850 if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {3851 return;3852 }3853 3854 if ( mediaTrash &&3855 'trash' !== selection.at( 0 ).get( 'status' ) &&3856 ! window.confirm( l10n.warnBulkTrash ) ) {3857 3858 return;3859 }3860 3861 selection.each( function( model ) {3862 if ( ! model.get( 'nonces' )['delete'] ) {3863 removed.push( model );3864 return;3865 }3866 3867 if ( mediaTrash && 'trash' === model.get( 'status' ) ) {3868 model.set( 'status', 'inherit' );3869 changed.push( model.save() );3870 removed.push( model );3871 } else if ( mediaTrash ) {3872 model.set( 'status', 'trash' );3873 changed.push( model.save() );3874 removed.push( model );3875 } else {3876 model.destroy({wait: true});3877 }3878 } );3879 3880 if ( changed.length ) {3881 selection.remove( removed );3882 3883 $.when.apply( null, changed ).then( _.bind( function() {3884 library._requery( true );3885 this.controller.trigger( 'selection:action:done' );3886 }, this ) );3887 } else {3888 this.controller.trigger( 'selection:action:done' );3889 }3890 }3891 }).render() );3892 3893 if ( mediaTrash ) {3894 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({3895 filters: Filters,3896 style: 'primary',3897 disabled: true,3898 text: l10n.deleteSelected,3899 controller: this.controller,3900 priority: -55,3901 click: function() {3902 var removed = [], selection = this.controller.state().get( 'selection' );3903 3904 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {3905 return;3906 }3907 3908 selection.each( function( model ) {3909 if ( ! model.get( 'nonces' )['delete'] ) {3910 removed.push( model );3911 return;3912 }3913 3914 model.destroy();3915 } );3916 3917 selection.remove( removed );3918 this.controller.trigger( 'selection:action:done' );3919 }3920 }).render() );3921 }3922 3923 } else if ( this.options.date ) {3924 // DateFilter is a <select>, screen reader text needs to be rendered before3925 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({3926 value: l10n.filterByDate,3927 attributes: {3928 'for': 'media-attachment-date-filters'3929 },3930 priority: -753931 }).render() );3932 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({3933 controller: this.controller,3934 model: this.collection.props,3935 priority: -753936 }).render() );3937 }3938 3939 if ( this.options.search ) {3940 // Search is an input, screen reader text needs to be rendered before3941 this.toolbar.set( 'searchLabel', new wp.media.view.Label({3942 value: l10n.searchMediaLabel,3943 attributes: {3944 'for': 'media-search-input'3945 },3946 priority: 603947 }).render() );3948 this.toolbar.set( 'search', new wp.media.view.Search({3949 controller: this.controller,3950 model: this.collection.props,3951 priority: 603952 }).render() );3953 }3954 3955 if ( this.options.dragInfo ) {3956 this.toolbar.set( 'dragInfo', new View({3957 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],3958 priority: -403959 }) );3960 }3961 3962 if ( this.options.suggestedWidth && this.options.suggestedHeight ) {3963 this.toolbar.set( 'suggestedDimensions', new View({3964 el: $( '<div class="instructions">' + l10n.suggestedDimensions + ' ' + this.options.suggestedWidth + ' × ' + this.options.suggestedHeight + '</div>' )[0],3965 priority: -403966 }) );3967 }3968 },3969 3970 updateContent: function() {3971 var view = this,3972 noItemsView;3973 3974 if ( this.controller.isModeActive( 'grid' ) ) {3975 noItemsView = view.attachmentsNoResults;3976 } else {3977 noItemsView = view.uploader;3978 }3979 3980 if ( ! this.collection.length ) {3981 this.toolbar.get( 'spinner' ).show();3982 this.dfd = this.collection.more().done( function() {3983 if ( ! view.collection.length ) {3984 noItemsView.$el.removeClass( 'hidden' );3985 } else {3986 noItemsView.$el.addClass( 'hidden' );3987 }3988 view.toolbar.get( 'spinner' ).hide();3989 } );3990 } else {3991 noItemsView.$el.addClass( 'hidden' );3992 view.toolbar.get( 'spinner' ).hide();3993 }3994 },3995 3996 createUploader: function() {3997 this.uploader = new wp.media.view.UploaderInline({3998 controller: this.controller,3999 status: false,4000 message: this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,4001 canClose: this.controller.isModeActive( 'grid' )4002 });4003 4004 this.uploader.hide();4005 this.views.add( this.uploader );4006 },4007 4008 toggleUploader: function() {4009 if ( this.uploader.$el.hasClass( 'hidden' ) ) {4010 this.uploader.show();4011 } else {4012 this.uploader.hide();4013 }4014 },4015 4016 createAttachments: function() {4017 this.attachments = new wp.media.view.Attachments({4018 controller: this.controller,4019 collection: this.collection,4020 selection: this.options.selection,4021 model: this.model,4022 sortable: this.options.sortable,4023 scrollElement: this.options.scrollElement,4024 idealColumnWidth: this.options.idealColumnWidth,4025 4026 // The single `Attachment` view to be used in the `Attachments` view.4027 AttachmentView: this.options.AttachmentView4028 });4029 4030 // Add keydown listener to the instance of the Attachments view4031 this.attachments.listenTo( this.controller, 'attachment:keydown:arrow', this.attachments.arrowEvent );4032 this.attachments.listenTo( this.controller, 'attachment:details:shift-tab', this.attachments.restoreFocus );4033 4034 this.views.add( this.attachments );4035 4036 4037 if ( this.controller.isModeActive( 'grid' ) ) {4038 this.attachmentsNoResults = new View({4039 controller: this.controller,4040 tagName: 'p'4041 });4042 4043 this.attachmentsNoResults.$el.addClass( 'hidden no-media' );4044 this.attachmentsNoResults.$el.html( l10n.noMedia );4045 4046 this.views.add( this.attachmentsNoResults );4047 }4048 },4049 4050 createSidebar: function() {4051 var options = this.options,4052 selection = options.selection,4053 sidebar = this.sidebar = new wp.media.view.Sidebar({4054 controller: this.controller4055 });4056 4057 this.views.add( sidebar );4058 4059 if ( this.controller.uploader ) {4060 sidebar.set( 'uploads', new wp.media.view.UploaderStatus({4061 controller: this.controller,4062 priority: 404063 }) );4064 }4065 4066 selection.on( 'selection:single', this.createSingle, this );4067 selection.on( 'selection:unsingle', this.disposeSingle, this );4068 4069 if ( selection.single() ) {4070 this.createSingle();4071 }4072 },4073 4074 createSingle: function() {4075 var sidebar = this.sidebar,4076 single = this.options.selection.single();4077 4078 sidebar.set( 'details', new wp.media.view.Attachment.Details({4079 controller: this.controller,4080 model: single,4081 priority: 804082 }) );4083 4084 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({4085 controller: this.controller,4086 model: single,4087 priority: 1204088 }) );4089 4090 if ( this.options.display ) {4091 sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({4092 controller: this.controller,4093 model: this.model.display( single ),4094 attachment: single,4095 priority: 160,4096 userSettings: this.model.get('displayUserSettings')4097 }) );4098 }4099 4100 // Show the sidebar on mobile4101 if ( this.model.id === 'insert' ) {4102 sidebar.$el.addClass( 'visible' );4103 }4104 },4105 4106 disposeSingle: function() {4107 var sidebar = this.sidebar;4108 sidebar.unset('details');4109 sidebar.unset('compat');4110 sidebar.unset('display');4111 // Hide the sidebar on mobile4112 sidebar.$el.removeClass( 'visible' );4113 }4114 });4115 4116 module.exports = AttachmentsBrowser;4117 4118 },{}],31:[function(require,module,exports){4119 /*globals wp, _ */4120 4121 /**4122 * wp.media.view.Attachments.Selection4123 *4124 * @class4125 * @augments wp.media.view.Attachments4126 * @augments wp.media.View4127 * @augments wp.Backbone.View4128 * @augments Backbone.View4129 */4130 var Attachments = wp.media.view.Attachments,4131 Selection;4132 4133 Selection = Attachments.extend({4134 events: {},4135 initialize: function() {4136 _.defaults( this.options, {4137 sortable: false,4138 resize: false,4139 4140 // The single `Attachment` view to be used in the `Attachments` view.4141 AttachmentView: wp.media.view.Attachment.Selection4142 });4143 // Call 'initialize' directly on the parent class.4144 return Attachments.prototype.initialize.apply( this, arguments );4145 }4146 });4147 4148 module.exports = Selection;4149 4150 },{}],32:[function(require,module,exports){4151 /*globals _, Backbone */4152 4153 /**4154 * wp.media.view.ButtonGroup4155 *4156 * @class4157 * @augments wp.media.View4158 * @augments wp.Backbone.View4159 * @augments Backbone.View4160 */4161 var $ = Backbone.$,4162 ButtonGroup;4163 4164 ButtonGroup = wp.media.View.extend({4165 tagName: 'div',4166 className: 'button-group button-large media-button-group',4167 4168 initialize: function() {4169 /**4170 * @member {wp.media.view.Button[]}4171 */4172 this.buttons = _.map( this.options.buttons || [], function( button ) {4173 if ( button instanceof Backbone.View ) {4174 return button;4175 } else {4176 return new wp.media.view.Button( button ).render();4177 }4178 });4179 4180 delete this.options.buttons;4181 4182 if ( this.options.classes ) {4183 this.$el.addClass( this.options.classes );4184 }4185 },4186 4187 /**4188 * @returns {wp.media.view.ButtonGroup}4189 */4190 render: function() {4191 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );4192 return this;4193 }4194 });4195 4196 module.exports = ButtonGroup;4197 4198 },{}],33:[function(require,module,exports){4199 /*globals _, Backbone */4200 4201 /**4202 * wp.media.view.Button4203 *4204 * @class4205 * @augments wp.media.View4206 * @augments wp.Backbone.View4207 * @augments Backbone.View4208 */4209 var Button = wp.media.View.extend({4210 tagName: 'a',4211 className: 'media-button',4212 attributes: { href: '#' },4213 4214 events: {4215 'click': 'click'4216 },4217 4218 defaults: {4219 text: '',4220 style: '',4221 size: 'large',4222 disabled: false4223 },4224 4225 initialize: function() {4226 /**4227 * Create a model with the provided `defaults`.4228 *4229 * @member {Backbone.Model}4230 */4231 this.model = new Backbone.Model( this.defaults );4232 4233 // If any of the `options` have a key from `defaults`, apply its4234 // value to the `model` and remove it from the `options object.4235 _.each( this.defaults, function( def, key ) {4236 var value = this.options[ key ];4237 if ( _.isUndefined( value ) ) {4238 return;4239 }4240 4241 this.model.set( key, value );4242 delete this.options[ key ];4243 }, this );4244 4245 this.listenTo( this.model, 'change', this.render );4246 },4247 /**4248 * @returns {wp.media.view.Button} Returns itself to allow chaining4249 */4250 render: function() {4251 var classes = [ 'button', this.className ],4252 model = this.model.toJSON();4253 4254 if ( model.style ) {4255 classes.push( 'button-' + model.style );4256 }4257 4258 if ( model.size ) {4259 classes.push( 'button-' + model.size );4260 }4261 4262 classes = _.uniq( classes.concat( this.options.classes ) );4263 this.el.className = classes.join(' ');4264 4265 this.$el.attr( 'disabled', model.disabled );4266 this.$el.text( this.model.get('text') );4267 4268 return this;4269 },4270 /**4271 * @param {Object} event4272 */4273 click: function( event ) {4274 if ( '#' === this.attributes.href ) {4275 event.preventDefault();4276 }4277 4278 if ( this.options.click && ! this.model.get('disabled') ) {4279 this.options.click.apply( this, arguments );4280 }4281 }4282 });4283 4284 module.exports = Button;4285 4286 },{}],34:[function(require,module,exports){4287 /*globals wp, _, jQuery */4288 4289 /**4290 * wp.media.view.Cropper4291 *4292 * Uses the imgAreaSelect plugin to allow a user to crop an image.4293 *4294 * Takes imgAreaSelect options from4295 * wp.customize.HeaderControl.calculateImageSelectOptions via4296 * wp.customize.HeaderControl.openMM.4297 *4298 * @class4299 * @augments wp.media.View4300 * @augments wp.Backbone.View4301 * @augments Backbone.View4302 */4303 var View = wp.media.View,4304 UploaderStatus = wp.media.view.UploaderStatus,4305 l10n = wp.media.view.l10n,4306 $ = jQuery,4307 Cropper;4308 4309 Cropper = View.extend({4310 className: 'crop-content',4311 template: wp.template('crop-content'),4312 initialize: function() {4313 _.bindAll(this, 'onImageLoad');4314 },4315 ready: function() {4316 this.controller.frame.on('content:error:crop', this.onError, this);4317 this.$image = this.$el.find('.crop-image');4318 this.$image.on('load', this.onImageLoad);4319 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));4320 },4321 remove: function() {4322 $(window).off('resize.cropper');4323 this.$el.remove();4324 this.$el.off();4325 View.prototype.remove.apply(this, arguments);4326 },4327 prepare: function() {4328 return {4329 title: l10n.cropYourImage,4330 url: this.options.attachment.get('url')4331 };4332 },4333 onImageLoad: function() {4334 var imgOptions = this.controller.get('imgSelectOptions');4335 if (typeof imgOptions === 'function') {4336 imgOptions = imgOptions(this.options.attachment, this.controller);4337 }4338 4339 imgOptions = _.extend(imgOptions, {parent: this.$el});4340 this.trigger('image-loaded');4341 this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);4342 },4343 onError: function() {4344 var filename = this.options.attachment.get('filename');4345 4346 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({4347 filename: UploaderStatus.prototype.filename(filename),4348 message: window._wpMediaViewsL10n.cropError4349 }), { at: 0 });4350 }4351 });4352 4353 module.exports = Cropper;4354 4355 },{}],35:[function(require,module,exports){4356 /*globals wp, _ */4357 4358 /**4359 * wp.media.view.EditImage4360 *4361 * @class4362 * @augments wp.media.View4363 * @augments wp.Backbone.View4364 * @augments Backbone.View4365 */4366 var View = wp.media.View,4367 EditImage;4368 4369 EditImage = View.extend({4370 className: 'image-editor',4371 template: wp.template('image-editor'),4372 4373 initialize: function( options ) {4374 this.editor = window.imageEdit;4375 this.controller = options.controller;4376 View.prototype.initialize.apply( this, arguments );4377 },4378 4379 prepare: function() {4380 return this.model.toJSON();4381 },4382 4383 loadEditor: function() {4384 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );4385 dfd.done( _.bind( this.focus, this ) );4386 },4387 4388 focus: function() {4389 this.$( '.imgedit-submit .button' ).eq( 0 ).focus();4390 },4391 4392 back: function() {4393 var lastState = this.controller.lastState();4394 this.controller.setState( lastState );4395 },4396 4397 refresh: function() {4398 this.model.fetch();4399 },4400 4401 save: function() {4402 var lastState = this.controller.lastState();4403 4404 this.model.fetch().done( _.bind( function() {4405 this.controller.setState( lastState );4406 }, this ) );4407 }4408 4409 });4410 4411 module.exports = EditImage;4412 4413 },{}],36:[function(require,module,exports){4414 /**4415 * wp.media.view.Embed4416 *4417 * @class4418 * @augments wp.media.View4419 * @augments wp.Backbone.View4420 * @augments Backbone.View4421 */4422 var Embed = wp.media.View.extend({4423 className: 'media-embed',4424 4425 initialize: function() {4426 /**4427 * @member {wp.media.view.EmbedUrl}4428 */4429 this.url = new wp.media.view.EmbedUrl({4430 controller: this.controller,4431 model: this.model.props4432 }).render();4433 4434 this.views.set([ this.url ]);4435 this.refresh();4436 this.listenTo( this.model, 'change:type', this.refresh );4437 this.listenTo( this.model, 'change:loading', this.loading );4438 },4439 4440 /**4441 * @param {Object} view4442 */4443 settings: function( view ) {4444 if ( this._settings ) {4445 this._settings.remove();4446 }4447 this._settings = view;4448 this.views.add( view );4449 },4450 4451 refresh: function() {4452 var type = this.model.get('type'),4453 constructor;4454 4455 if ( 'image' === type ) {4456 constructor = wp.media.view.EmbedImage;4457 } else if ( 'link' === type ) {4458 constructor = wp.media.view.EmbedLink;4459 } else {4460 return;4461 }4462 4463 this.settings( new constructor({4464 controller: this.controller,4465 model: this.model.props,4466 priority: 404467 }) );4468 },4469 4470 loading: function() {4471 this.$el.toggleClass( 'embed-loading', this.model.get('loading') );4472 }4473 });4474 4475 module.exports = Embed;4476 4477 },{}],37:[function(require,module,exports){4478 /*globals wp */4479 4480 /**4481 * wp.media.view.EmbedImage4482 *4483 * @class4484 * @augments wp.media.view.Settings.AttachmentDisplay4485 * @augments wp.media.view.Settings4486 * @augments wp.media.View4487 * @augments wp.Backbone.View4488 * @augments Backbone.View4489 */4490 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,4491 EmbedImage;4492 4493 EmbedImage = AttachmentDisplay.extend({4494 className: 'embed-media-settings',4495 template: wp.template('embed-image-settings'),4496 4497 initialize: function() {4498 /**4499 * Call `initialize` directly on parent class with passed arguments4500 */4501 AttachmentDisplay.prototype.initialize.apply( this, arguments );4502 this.listenTo( this.model, 'change:url', this.updateImage );4503 },4504 4505 updateImage: function() {4506 this.$('img').attr( 'src', this.model.get('url') );4507 }4508 });4509 4510 module.exports = EmbedImage;4511 4512 },{}],38:[function(require,module,exports){4513 /*globals wp, _, jQuery */4514 4515 /**4516 * wp.media.view.EmbedLink4517 *4518 * @class4519 * @augments wp.media.view.Settings4520 * @augments wp.media.View4521 * @augments wp.Backbone.View4522 * @augments Backbone.View4523 */4524 var $ = jQuery,4525 EmbedLink;4526 4527 EmbedLink = wp.media.view.Settings.extend({4528 className: 'embed-link-settings',4529 template: wp.template('embed-link-settings'),4530 4531 initialize: function() {4532 this.spinner = $('<span class="spinner" />');4533 this.$el.append( this.spinner[0] );4534 this.listenTo( this.model, 'change:url change:width change:height', this.updateoEmbed );4535 },4536 4537 updateoEmbed: _.debounce( function() {4538 var url = this.model.get( 'url' );4539 4540 // clear out previous results4541 this.$('.embed-container').hide().find('.embed-preview').empty();4542 this.$( '.setting' ).hide();4543 4544 // only proceed with embed if the field contains more than 6 characters4545 if ( url && url.length < 6 ) {4546 return;4547 }4548 4549 this.spinner.show();4550 4551 this.fetch();4552 }, 600 ),4553 4554 fetch: function() {4555 var embed;4556 4557 // check if they haven't typed in 500 ms4558 if ( $('#embed-url-field').val() !== this.model.get('url') ) {4559 return;4560 }4561 4562 embed = new wp.shortcode({4563 tag: 'embed',4564 attrs: _.pick( this.model.attributes, [ 'width', 'height', 'src' ] ),4565 content: this.model.get('url')4566 });4567 4568 wp.ajax.send( 'parse-embed', {4569 data : {4570 post_ID: wp.media.view.settings.post.id,4571 shortcode: embed.string()4572 }4573 } )4574 .done( _.bind( this.renderoEmbed, this ) )4575 .fail( _.bind( this.renderFail, this ) );4576 },4577 4578 renderFail: function () {4579 this.$( '.setting' ).hide().filter( '.link-text' ).show();4580 },4581 4582 renderoEmbed: function( response ) {4583 var html = ( response && response.body ) || '',4584 attr = {},4585 opts = { silent: true };4586 4587 this.$( '.setting' ).hide()4588 .filter( '.link-text' )[ html ? 'hide' : 'show' ]();4589 4590 if ( response && response.attr ) {4591 attr = response.attr;4592 4593 _.each( [ 'width', 'height' ], function ( key ) {4594 var $el = this.$( '.setting.' + key ),4595 value = attr[ key ];4596 4597 if ( value ) {4598 this.model.set( key, value, opts );4599 $el.show().find( 'input' ).val( value );4600 } else {4601 this.model.unset( key, opts );4602 $el.hide().find( 'input' ).val( '' );4603 }4604 }, this );4605 } else {4606 this.model.unset( 'height', opts );4607 this.model.unset( 'width', opts );4608 }4609 4610 this.spinner.hide();4611 4612 this.$('.embed-container').show().find('.embed-preview').html( html );4613 }4614 });4615 4616 module.exports = EmbedLink;4617 4618 },{}],39:[function(require,module,exports){4619 /*globals wp, _, jQuery */4620 4621 /**4622 * wp.media.view.EmbedUrl4623 *4624 * @class4625 * @augments wp.media.View4626 * @augments wp.Backbone.View4627 * @augments Backbone.View4628 */4629 var View = wp.media.View,4630 $ = jQuery,4631 EmbedUrl;4632 4633 EmbedUrl = View.extend({4634 tagName: 'label',4635 className: 'embed-url',4636 4637 events: {4638 'input': 'url',4639 'keyup': 'url',4640 'change': 'url'4641 },4642 4643 initialize: function() {4644 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );4645 this.input = this.$input[0];4646 4647 this.spinner = $('<span class="spinner" />')[0];4648 this.$el.append([ this.input, this.spinner ]);4649 4650 this.listenTo( this.model, 'change:url', this.render );4651 4652 if ( this.model.get( 'url' ) ) {4653 _.delay( _.bind( function () {4654 this.model.trigger( 'change:url' );4655 }, this ), 500 );4656 }4657 },4658 /**4659 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining4660 */4661 render: function() {4662 var $input = this.$input;4663 4664 if ( $input.is(':focus') ) {4665 return;4666 }4667 4668 this.input.value = this.model.get('url') || 'http://';4669 /**4670 * Call `render` directly on parent class with passed arguments4671 */4672 View.prototype.render.apply( this, arguments );4673 return this;4674 },4675 4676 ready: function() {4677 if ( ! wp.media.isTouchDevice ) {4678 this.focus();4679 }4680 },4681 4682 url: function( event ) {4683 this.model.set( 'url', event.target.value );4684 },4685 4686 /**4687 * If the input is visible, focus and select its contents.4688 */4689 focus: function() {4690 var $input = this.$input;4691 if ( $input.is(':visible') ) {4692 $input.focus()[0].select();4693 }4694 }4695 });4696 4697 module.exports = EmbedUrl;4698 4699 },{}],40:[function(require,module,exports){4700 /**4701 * wp.media.view.FocusManager4702 *4703 * @class4704 * @augments wp.media.View4705 * @augments wp.Backbone.View4706 * @augments Backbone.View4707 */4708 var FocusManager = wp.media.View.extend({4709 4710 events: {4711 'keydown': 'constrainTabbing'4712 },4713 4714 focus: function() { // Reset focus on first left menu item4715 this.$('.media-menu-item').first().focus();4716 },4717 /**4718 * @param {Object} event4719 */4720 constrainTabbing: function( event ) {4721 var tabbables;4722 4723 // Look for the tab key.4724 if ( 9 !== event.keyCode ) {4725 return;4726 }4727 4728 // Skip the file input added by Plupload.4729 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );4730 4731 // Keep tab focus within media modal while it's open4732 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {4733 tabbables.first().focus();4734 return false;4735 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {4736 tabbables.last().focus();4737 return false;4738 }4739 }4740 4741 });4742 4743 module.exports = FocusManager;4744 4745 },{}],41:[function(require,module,exports){4746 /*globals _, Backbone */4747 4748 /**4749 * wp.media.view.Frame4750 *4751 * A frame is a composite view consisting of one or more regions and one or more4752 * states.4753 *4754 * @see wp.media.controller.State4755 * @see wp.media.controller.Region4756 *4757 * @class4758 * @augments wp.media.View4759 * @augments wp.Backbone.View4760 * @augments Backbone.View4761 * @mixes wp.media.controller.StateMachine4762 */4763 var Frame = wp.media.View.extend({4764 initialize: function() {4765 _.defaults( this.options, {4766 mode: [ 'select' ]4767 });4768 this._createRegions();4769 this._createStates();4770 this._createModes();4771 },4772 4773 _createRegions: function() {4774 // Clone the regions array.4775 this.regions = this.regions ? this.regions.slice() : [];4776 4777 // Initialize regions.4778 _.each( this.regions, function( region ) {4779 this[ region ] = new wp.media.controller.Region({4780 view: this,4781 id: region,4782 selector: '.media-frame-' + region4783 });4784 }, this );4785 },4786 /**4787 * Create the frame's states.4788 *4789 * @see wp.media.controller.State4790 * @see wp.media.controller.StateMachine4791 *4792 * @fires wp.media.controller.State#ready4793 */4794 _createStates: function() {4795 // Create the default `states` collection.4796 this.states = new Backbone.Collection( null, {4797 model: wp.media.controller.State4798 });4799 4800 // Ensure states have a reference to the frame.4801 this.states.on( 'add', function( model ) {4802 model.frame = this;4803 model.trigger('ready');4804 }, this );4805 4806 if ( this.options.states ) {4807 this.states.add( this.options.states );4808 }4809 },4810 4811 /**4812 * A frame can be in a mode or multiple modes at one time.4813 *4814 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.4815 */4816 _createModes: function() {4817 // Store active "modes" that the frame is in. Unrelated to region modes.4818 this.activeModes = new Backbone.Collection();4819 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );4820 4821 _.each( this.options.mode, function( mode ) {4822 this.activateMode( mode );4823 }, this );4824 },4825 /**4826 * Reset all states on the frame to their defaults.4827 *4828 * @returns {wp.media.view.Frame} Returns itself to allow chaining4829 */4830 reset: function() {4831 this.states.invoke( 'trigger', 'reset' );4832 return this;4833 },4834 /**4835 * Map activeMode collection events to the frame.4836 */4837 triggerModeEvents: function( model, collection, options ) {4838 var collectionEvent,4839 modeEventMap = {4840 add: 'activate',4841 remove: 'deactivate'4842 },4843 eventToTrigger;4844 // Probably a better way to do this.4845 _.each( options, function( value, key ) {4846 if ( value ) {4847 collectionEvent = key;4848 }4849 } );4850 4851 if ( ! _.has( modeEventMap, collectionEvent ) ) {4852 return;4853 }4854 4855 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];4856 this.trigger( eventToTrigger );4857 },4858 /**4859 * Activate a mode on the frame.4860 *4861 * @param string mode Mode ID.4862 * @returns {this} Returns itself to allow chaining.4863 */4864 activateMode: function( mode ) {4865 // Bail if the mode is already active.4866 if ( this.isModeActive( mode ) ) {4867 return;4868 }4869 this.activeModes.add( [ { id: mode } ] );4870 // Add a CSS class to the frame so elements can be styled for the mode.4871 this.$el.addClass( 'mode-' + mode );4872 4873 return this;4874 },4875 /**4876 * Deactivate a mode on the frame.4877 *4878 * @param string mode Mode ID.4879 * @returns {this} Returns itself to allow chaining.4880 */4881 deactivateMode: function( mode ) {4882 // Bail if the mode isn't active.4883 if ( ! this.isModeActive( mode ) ) {4884 return this;4885 }4886 this.activeModes.remove( this.activeModes.where( { id: mode } ) );4887 this.$el.removeClass( 'mode-' + mode );4888 /**4889 * Frame mode deactivation event.4890 *4891 * @event this#{mode}:deactivate4892 */4893 this.trigger( mode + ':deactivate' );4894 4895 return this;4896 },4897 /**4898 * Check if a mode is enabled on the frame.4899 *4900 * @param string mode Mode ID.4901 * @return bool4902 */4903 isModeActive: function( mode ) {4904 return Boolean( this.activeModes.where( { id: mode } ).length );4905 }4906 });4907 4908 // Make the `Frame` a `StateMachine`.4909 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );4910 4911 module.exports = Frame;4912 4913 },{}],42:[function(require,module,exports){4914 /*globals wp */4915 4916 /**4917 * wp.media.view.MediaFrame.ImageDetails4918 *4919 * A media frame for manipulating an image that's already been inserted4920 * into a post.4921 *4922 * @class4923 * @augments wp.media.view.MediaFrame.Select4924 * @augments wp.media.view.MediaFrame4925 * @augments wp.media.view.Frame4926 * @augments wp.media.View4927 * @augments wp.Backbone.View4928 * @augments Backbone.View4929 * @mixes wp.media.controller.StateMachine4930 */4931 var Select = wp.media.view.MediaFrame.Select,4932 l10n = wp.media.view.l10n,4933 ImageDetails;4934 4935 ImageDetails = Select.extend({4936 defaults: {4937 id: 'image',4938 url: '',4939 menu: 'image-details',4940 content: 'image-details',4941 toolbar: 'image-details',4942 type: 'link',4943 title: l10n.imageDetailsTitle,4944 priority: 1204945 },4946 4947 initialize: function( options ) {4948 this.image = new wp.media.model.PostImage( options.metadata );4949 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );4950 Select.prototype.initialize.apply( this, arguments );4951 },4952 4953 bindHandlers: function() {4954 Select.prototype.bindHandlers.apply( this, arguments );4955 this.on( 'menu:create:image-details', this.createMenu, this );4956 this.on( 'content:create:image-details', this.imageDetailsContent, this );4957 this.on( 'content:render:edit-image', this.editImageContent, this );4958 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );4959 // override the select toolbar4960 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );4961 },4962 4963 createStates: function() {4964 this.states.add([4965 new wp.media.controller.ImageDetails({4966 image: this.image,4967 editable: false4968 }),4969 new wp.media.controller.ReplaceImage({4970 id: 'replace-image',4971 library: wp.media.query( { type: 'image' } ),4972 image: this.image,4973 multiple: false,4974 title: l10n.imageReplaceTitle,4975 toolbar: 'replace',4976 priority: 80,4977 displaySettings: true4978 }),4979 new wp.media.controller.EditImage( {4980 image: this.image,4981 selection: this.options.selection4982 } )4983 ]);4984 },4985 4986 imageDetailsContent: function( options ) {4987 options.view = new wp.media.view.ImageDetails({4988 controller: this,4989 model: this.state().image,4990 attachment: this.state().image.attachment4991 });4992 },4993 4994 editImageContent: function() {4995 var state = this.state(),4996 model = state.get('image'),4997 view;4998 4999 if ( ! model ) {5000 return;5001 }5002 5003 view = new wp.media.view.EditImage( { model: model, controller: this } ).render();5004 5005 this.content.set( view );5006 5007 // after bringing in the frame, load the actual editor via an ajax call5008 view.loadEditor();5009 5010 },5011 5012 renderImageDetailsToolbar: function() {5013 this.toolbar.set( new wp.media.view.Toolbar({5014 controller: this,5015 items: {5016 select: {5017 style: 'primary',5018 text: l10n.update,5019 priority: 80,5020 5021 click: function() {5022 var controller = this.controller,5023 state = controller.state();5024 5025 controller.close();5026 5027 // not sure if we want to use wp.media.string.image which will create a shortcode or5028 // perhaps wp.html.string to at least to build the <img />5029 state.trigger( 'update', controller.image.toJSON() );5030 5031 // Restore and reset the default state.5032 controller.setState( controller.options.state );5033 controller.reset();5034 }5035 }5036 }5037 }) );5038 },5039 5040 renderReplaceImageToolbar: function() {5041 var frame = this,5042 lastState = frame.lastState(),5043 previous = lastState && lastState.id;5044 5045 this.toolbar.set( new wp.media.view.Toolbar({5046 controller: this,5047 items: {5048 back: {5049 text: l10n.back,5050 priority: 20,5051 click: function() {5052 if ( previous ) {5053 frame.setState( previous );5054 } else {5055 frame.close();5056 }5057 }5058 },5059 5060 replace: {5061 style: 'primary',5062 text: l10n.replace,5063 priority: 80,5064 5065 click: function() {5066 var controller = this.controller,5067 state = controller.state(),5068 selection = state.get( 'selection' ),5069 attachment = selection.single();5070 5071 controller.close();5072 5073 controller.image.changeAttachment( attachment, state.display( attachment ) );5074 5075 // not sure if we want to use wp.media.string.image which will create a shortcode or5076 // perhaps wp.html.string to at least to build the <img />5077 state.trigger( 'replace', controller.image.toJSON() );5078 5079 // Restore and reset the default state.5080 controller.setState( controller.options.state );5081 controller.reset();5082 }5083 }5084 }5085 }) );5086 }5087 5088 });5089 5090 module.exports = ImageDetails;5091 5092 },{}],43:[function(require,module,exports){5093 /*globals wp, _ */5094 5095 /**5096 * wp.media.view.MediaFrame.Post5097 *5098 * The frame for manipulating media on the Edit Post page.5099 *5100 * @class5101 * @augments wp.media.view.MediaFrame.Select5102 * @augments wp.media.view.MediaFrame5103 * @augments wp.media.view.Frame5104 * @augments wp.media.View5105 * @augments wp.Backbone.View5106 * @augments Backbone.View5107 * @mixes wp.media.controller.StateMachine5108 */5109 var Select = wp.media.view.MediaFrame.Select,5110 Library = wp.media.controller.Library,5111 l10n = wp.media.view.l10n,5112 Post;5113 5114 Post = Select.extend({5115 initialize: function() {5116 this.counts = {5117 audio: {5118 count: wp.media.view.settings.attachmentCounts.audio,5119 state: 'playlist'5120 },5121 video: {5122 count: wp.media.view.settings.attachmentCounts.video,5123 state: 'video-playlist'5124 }5125 };5126 5127 _.defaults( this.options, {5128 multiple: true,5129 editing: false,5130 state: 'insert',5131 metadata: {}5132 });5133 5134 // Call 'initialize' directly on the parent class.5135 Select.prototype.initialize.apply( this, arguments );5136 this.createIframeStates();5137 5138 },5139 5140 /**5141 * Create the default states.5142 */5143 createStates: function() {5144 var options = this.options;5145 5146 this.states.add([5147 // Main states.5148 new Library({5149 id: 'insert',5150 title: l10n.insertMediaTitle,5151 priority: 20,5152 toolbar: 'main-insert',5153 filterable: 'all',5154 library: wp.media.query( options.library ),5155 multiple: options.multiple ? 'reset' : false,5156 editable: true,5157 5158 // If the user isn't allowed to edit fields,5159 // can they still edit it locally?5160 allowLocalEdits: true,5161 5162 // Show the attachment display settings.5163 displaySettings: true,5164 // Update user settings when users adjust the5165 // attachment display settings.5166 displayUserSettings: true5167 }),5168 5169 new Library({5170 id: 'gallery',5171 title: l10n.createGalleryTitle,5172 priority: 40,5173 toolbar: 'main-gallery',5174 filterable: 'uploaded',5175 multiple: 'add',5176 editable: false,5177 5178 library: wp.media.query( _.defaults({5179 type: 'image'5180 }, options.library ) )5181 }),5182 5183 // Embed states.5184 new wp.media.controller.Embed( { metadata: options.metadata } ),5185 5186 new wp.media.controller.EditImage( { model: options.editImage } ),5187 5188 // Gallery states.5189 new wp.media.controller.GalleryEdit({5190 library: options.selection,5191 editing: options.editing,5192 menu: 'gallery'5193 }),5194 5195 new wp.media.controller.GalleryAdd(),5196 5197 new Library({5198 id: 'playlist',5199 title: l10n.createPlaylistTitle,5200 priority: 60,5201 toolbar: 'main-playlist',5202 filterable: 'uploaded',5203 multiple: 'add',5204 editable: false,5205 5206 library: wp.media.query( _.defaults({5207 type: 'audio'5208 }, options.library ) )5209 }),5210 5211 // Playlist states.5212 new wp.media.controller.CollectionEdit({5213 type: 'audio',5214 collectionType: 'playlist',5215 title: l10n.editPlaylistTitle,5216 SettingsView: wp.media.view.Settings.Playlist,5217 library: options.selection,5218 editing: options.editing,5219 menu: 'playlist',5220 dragInfoText: l10n.playlistDragInfo,5221 dragInfo: false5222 }),5223 5224 new wp.media.controller.CollectionAdd({5225 type: 'audio',5226 collectionType: 'playlist',5227 title: l10n.addToPlaylistTitle5228 }),5229 5230 new Library({5231 id: 'video-playlist',5232 title: l10n.createVideoPlaylistTitle,5233 priority: 60,5234 toolbar: 'main-video-playlist',5235 filterable: 'uploaded',5236 multiple: 'add',5237 editable: false,5238 5239 library: wp.media.query( _.defaults({5240 type: 'video'5241 }, options.library ) )5242 }),5243 5244 new wp.media.controller.CollectionEdit({5245 type: 'video',5246 collectionType: 'playlist',5247 title: l10n.editVideoPlaylistTitle,5248 SettingsView: wp.media.view.Settings.Playlist,5249 library: options.selection,5250 editing: options.editing,5251 menu: 'video-playlist',5252 dragInfoText: l10n.videoPlaylistDragInfo,5253 dragInfo: false5254 }),5255 5256 new wp.media.controller.CollectionAdd({5257 type: 'video',5258 collectionType: 'playlist',5259 title: l10n.addToVideoPlaylistTitle5260 })5261 ]);5262 5263 if ( wp.media.view.settings.post.featuredImageId ) {5264 this.states.add( new wp.media.controller.FeaturedImage() );5265 }5266 },5267 5268 bindHandlers: function() {5269 var handlers, checkCounts;5270 5271 Select.prototype.bindHandlers.apply( this, arguments );5272 5273 this.on( 'activate', this.activate, this );5274 5275 // Only bother checking media type counts if one of the counts is zero5276 checkCounts = _.find( this.counts, function( type ) {5277 return type.count === 0;5278 } );5279 5280 if ( typeof checkCounts !== 'undefined' ) {5281 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );5282 }5283 5284 this.on( 'menu:create:gallery', this.createMenu, this );5285 this.on( 'menu:create:playlist', this.createMenu, this );5286 this.on( 'menu:create:video-playlist', this.createMenu, this );5287 this.on( 'toolbar:create:main-insert', this.createToolbar, this );5288 this.on( 'toolbar:create:main-gallery', this.createToolbar, this );5289 this.on( 'toolbar:create:main-playlist', this.createToolbar, this );5290 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );5291 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );5292 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );5293 5294 handlers = {5295 menu: {5296 'default': 'mainMenu',5297 'gallery': 'galleryMenu',5298 'playlist': 'playlistMenu',5299 'video-playlist': 'videoPlaylistMenu'5300 },5301 5302 content: {5303 'embed': 'embedContent',5304 'edit-image': 'editImageContent',5305 'edit-selection': 'editSelectionContent'5306 },5307 5308 toolbar: {5309 'main-insert': 'mainInsertToolbar',5310 'main-gallery': 'mainGalleryToolbar',5311 'gallery-edit': 'galleryEditToolbar',5312 'gallery-add': 'galleryAddToolbar',5313 'main-playlist': 'mainPlaylistToolbar',5314 'playlist-edit': 'playlistEditToolbar',5315 'playlist-add': 'playlistAddToolbar',5316 'main-video-playlist': 'mainVideoPlaylistToolbar',5317 'video-playlist-edit': 'videoPlaylistEditToolbar',5318 'video-playlist-add': 'videoPlaylistAddToolbar'5319 }5320 };5321 5322 _.each( handlers, function( regionHandlers, region ) {5323 _.each( regionHandlers, function( callback, handler ) {5324 this.on( region + ':render:' + handler, this[ callback ], this );5325 }, this );5326 }, this );5327 },5328 5329 activate: function() {5330 // Hide menu items for states tied to particular media types if there are no items5331 _.each( this.counts, function( type ) {5332 if ( type.count < 1 ) {5333 this.menuItemVisibility( type.state, 'hide' );5334 }5335 }, this );5336 },5337 5338 mediaTypeCounts: function( model, attr ) {5339 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {5340 this.counts[ attr ].count++;5341 this.menuItemVisibility( this.counts[ attr ].state, 'show' );5342 }5343 },5344 5345 // Menus5346 /**5347 * @param {wp.Backbone.View} view5348 */5349 mainMenu: function( view ) {5350 view.set({5351 'library-separator': new wp.media.View({5352 className: 'separator',5353 priority: 1005354 })5355 });5356 },5357 5358 menuItemVisibility: function( state, visibility ) {5359 var menu = this.menu.get();5360 if ( visibility === 'hide' ) {5361 menu.hide( state );5362 } else if ( visibility === 'show' ) {5363 menu.show( state );5364 }5365 },5366 /**5367 * @param {wp.Backbone.View} view5368 */5369 galleryMenu: function( view ) {5370 var lastState = this.lastState(),5371 previous = lastState && lastState.id,5372 frame = this;5373 5374 view.set({5375 cancel: {5376 text: l10n.cancelGalleryTitle,5377 priority: 20,5378 click: function() {5379 if ( previous ) {5380 frame.setState( previous );5381 } else {5382 frame.close();5383 }5384 5385 // Keep focus inside media modal5386 // after canceling a gallery5387 this.controller.modal.focusManager.focus();5388 }5389 },5390 separateCancel: new wp.media.View({5391 className: 'separator',5392 priority: 405393 })5394 });5395 },5396 5397 playlistMenu: function( view ) {5398 var lastState = this.lastState(),5399 previous = lastState && lastState.id,5400 frame = this;5401 5402 view.set({5403 cancel: {5404 text: l10n.cancelPlaylistTitle,5405 priority: 20,5406 click: function() {5407 if ( previous ) {5408 frame.setState( previous );5409 } else {5410 frame.close();5411 }5412 }5413 },5414 separateCancel: new wp.media.View({5415 className: 'separator',5416 priority: 405417 })5418 });5419 },5420 5421 videoPlaylistMenu: function( view ) {5422 var lastState = this.lastState(),5423 previous = lastState && lastState.id,5424 frame = this;5425 5426 view.set({5427 cancel: {5428 text: l10n.cancelVideoPlaylistTitle,5429 priority: 20,5430 click: function() {5431 if ( previous ) {5432 frame.setState( previous );5433 } else {5434 frame.close();5435 }5436 }5437 },5438 separateCancel: new wp.media.View({5439 className: 'separator',5440 priority: 405441 })5442 });5443 },5444 5445 // Content5446 embedContent: function() {5447 var view = new wp.media.view.Embed({5448 controller: this,5449 model: this.state()5450 }).render();5451 5452 this.content.set( view );5453 5454 if ( ! wp.media.isTouchDevice ) {5455 view.url.focus();5456 }5457 },5458 5459 editSelectionContent: function() {5460 var state = this.state(),5461 selection = state.get('selection'),5462 view;5463 5464 view = new wp.media.view.AttachmentsBrowser({5465 controller: this,5466 collection: selection,5467 selection: selection,5468 model: state,5469 sortable: true,5470 search: false,5471 date: false,5472 dragInfo: true,5473 5474 AttachmentView: wp.media.view.Attachments.EditSelection5475 }).render();5476 5477 view.toolbar.set( 'backToLibrary', {5478 text: l10n.returnToLibrary,5479 priority: -100,5480 5481 click: function() {5482 this.controller.content.mode('browse');5483 }5484 });5485 5486 // Browse our library of attachments.5487 this.content.set( view );5488 5489 // Trigger the controller to set focus5490 this.trigger( 'edit:selection', this );5491 },5492 5493 editImageContent: function() {5494 var image = this.state().get('image'),5495 view = new wp.media.view.EditImage( { model: image, controller: this } ).render();5496 5497 this.content.set( view );5498 5499 // after creating the wrapper view, load the actual editor via an ajax call5500 view.loadEditor();5501 5502 },5503 5504 // Toolbars5505 5506 /**5507 * @param {wp.Backbone.View} view5508 */5509 selectionStatusToolbar: function( view ) {5510 var editable = this.state().get('editable');5511 5512 view.set( 'selection', new wp.media.view.Selection({5513 controller: this,5514 collection: this.state().get('selection'),5515 priority: -40,5516 5517 // If the selection is editable, pass the callback to5518 // switch the content mode.5519 editable: editable && function() {5520 this.controller.content.mode('edit-selection');5521 }5522 }).render() );5523 },5524 5525 /**5526 * @param {wp.Backbone.View} view5527 */5528 mainInsertToolbar: function( view ) {5529 var controller = this;5530 5531 this.selectionStatusToolbar( view );5532 5533 view.set( 'insert', {5534 style: 'primary',5535 priority: 80,5536 text: l10n.insertIntoPost,5537 requires: { selection: true },5538 5539 /**5540 * @fires wp.media.controller.State#insert5541 */5542 click: function() {5543 var state = controller.state(),5544 selection = state.get('selection');5545 5546 controller.close();5547 state.trigger( 'insert', selection ).reset();5548 }5549 });5550 },5551 5552 /**5553 * @param {wp.Backbone.View} view5554 */5555 mainGalleryToolbar: function( view ) {5556 var controller = this;5557 5558 this.selectionStatusToolbar( view );5559 5560 view.set( 'gallery', {5561 style: 'primary',5562 text: l10n.createNewGallery,5563 priority: 60,5564 requires: { selection: true },5565 5566 click: function() {5567 var selection = controller.state().get('selection'),5568 edit = controller.state('gallery-edit'),5569 models = selection.where({ type: 'image' });5570 5571 edit.set( 'library', new wp.media.model.Selection( models, {5572 props: selection.props.toJSON(),5573 multiple: true5574 }) );5575 5576 this.controller.setState('gallery-edit');5577 5578 // Keep focus inside media modal5579 // after jumping to gallery view5580 this.controller.modal.focusManager.focus();5581 }5582 });5583 },5584 5585 mainPlaylistToolbar: function( view ) {5586 var controller = this;5587 5588 this.selectionStatusToolbar( view );5589 5590 view.set( 'playlist', {5591 style: 'primary',5592 text: l10n.createNewPlaylist,5593 priority: 100,5594 requires: { selection: true },5595 5596 click: function() {5597 var selection = controller.state().get('selection'),5598 edit = controller.state('playlist-edit'),5599 models = selection.where({ type: 'audio' });5600 5601 edit.set( 'library', new wp.media.model.Selection( models, {5602 props: selection.props.toJSON(),5603 multiple: true5604 }) );5605 5606 this.controller.setState('playlist-edit');5607 5608 // Keep focus inside media modal5609 // after jumping to playlist view5610 this.controller.modal.focusManager.focus();5611 }5612 });5613 },5614 5615 mainVideoPlaylistToolbar: function( view ) {5616 var controller = this;5617 5618 this.selectionStatusToolbar( view );5619 5620 view.set( 'video-playlist', {5621 style: 'primary',5622 text: l10n.createNewVideoPlaylist,5623 priority: 100,5624 requires: { selection: true },5625 5626 click: function() {5627 var selection = controller.state().get('selection'),5628 edit = controller.state('video-playlist-edit'),5629 models = selection.where({ type: 'video' });5630 5631 edit.set( 'library', new wp.media.model.Selection( models, {5632 props: selection.props.toJSON(),5633 multiple: true5634 }) );5635 5636 this.controller.setState('video-playlist-edit');5637 5638 // Keep focus inside media modal5639 // after jumping to video playlist view5640 this.controller.modal.focusManager.focus();5641 }5642 });5643 },5644 5645 featuredImageToolbar: function( toolbar ) {5646 this.createSelectToolbar( toolbar, {5647 text: l10n.setFeaturedImage,5648 state: this.options.state5649 });5650 },5651 5652 mainEmbedToolbar: function( toolbar ) {5653 toolbar.view = new wp.media.view.Toolbar.Embed({5654 controller: this5655 });5656 },5657 5658 galleryEditToolbar: function() {5659 var editing = this.state().get('editing');5660 this.toolbar.set( new wp.media.view.Toolbar({5661 controller: this,5662 items: {5663 insert: {5664 style: 'primary',5665 text: editing ? l10n.updateGallery : l10n.insertGallery,5666 priority: 80,5667 requires: { library: true },5668 5669 /**5670 * @fires wp.media.controller.State#update5671 */5672 click: function() {5673 var controller = this.controller,5674 state = controller.state();5675 5676 controller.close();5677 state.trigger( 'update', state.get('library') );5678 5679 // Restore and reset the default state.5680 controller.setState( controller.options.state );5681 controller.reset();5682 }5683 }5684 }5685 }) );5686 },5687 5688 galleryAddToolbar: function() {5689 this.toolbar.set( new wp.media.view.Toolbar({5690 controller: this,5691 items: {5692 insert: {5693 style: 'primary',5694 text: l10n.addToGallery,5695 priority: 80,5696 requires: { selection: true },5697 5698 /**5699 * @fires wp.media.controller.State#reset5700 */5701 click: function() {5702 var controller = this.controller,5703 state = controller.state(),5704 edit = controller.state('gallery-edit');5705 5706 edit.get('library').add( state.get('selection').models );5707 state.trigger('reset');5708 controller.setState('gallery-edit');5709 }5710 }5711 }5712 }) );5713 },5714 5715 playlistEditToolbar: function() {5716 var editing = this.state().get('editing');5717 this.toolbar.set( new wp.media.view.Toolbar({5718 controller: this,5719 items: {5720 insert: {5721 style: 'primary',5722 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist,5723 priority: 80,5724 requires: { library: true },5725 5726 /**5727 * @fires wp.media.controller.State#update5728 */5729 click: function() {5730 var controller = this.controller,5731 state = controller.state();5732 5733 controller.close();5734 state.trigger( 'update', state.get('library') );5735 5736 // Restore and reset the default state.5737 controller.setState( controller.options.state );5738 controller.reset();5739 }5740 }5741 }5742 }) );5743 },5744 5745 playlistAddToolbar: function() {5746 this.toolbar.set( new wp.media.view.Toolbar({5747 controller: this,5748 items: {5749 insert: {5750 style: 'primary',5751 text: l10n.addToPlaylist,5752 priority: 80,5753 requires: { selection: true },5754 5755 /**5756 * @fires wp.media.controller.State#reset5757 */5758 click: function() {5759 var controller = this.controller,5760 state = controller.state(),5761 edit = controller.state('playlist-edit');5762 5763 edit.get('library').add( state.get('selection').models );5764 state.trigger('reset');5765 controller.setState('playlist-edit');5766 }5767 }5768 }5769 }) );5770 },5771 5772 videoPlaylistEditToolbar: function() {5773 var editing = this.state().get('editing');5774 this.toolbar.set( new wp.media.view.Toolbar({5775 controller: this,5776 items: {5777 insert: {5778 style: 'primary',5779 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,5780 priority: 140,5781 requires: { library: true },5782 5783 click: function() {5784 var controller = this.controller,5785 state = controller.state(),5786 library = state.get('library');5787 5788 library.type = 'video';5789 5790 controller.close();5791 state.trigger( 'update', library );5792 5793 // Restore and reset the default state.5794 controller.setState( controller.options.state );5795 controller.reset();5796 }5797 }5798 }5799 }) );5800 },5801 5802 videoPlaylistAddToolbar: function() {5803 this.toolbar.set( new wp.media.view.Toolbar({5804 controller: this,5805 items: {5806 insert: {5807 style: 'primary',5808 text: l10n.addToVideoPlaylist,5809 priority: 140,5810 requires: { selection: true },5811 5812 click: function() {5813 var controller = this.controller,5814 state = controller.state(),5815 edit = controller.state('video-playlist-edit');5816 5817 edit.get('library').add( state.get('selection').models );5818 state.trigger('reset');5819 controller.setState('video-playlist-edit');5820 }5821 }5822 }5823 }) );5824 }5825 });5826 5827 module.exports = Post;5828 5829 },{}],44:[function(require,module,exports){5830 /*globals wp, _ */5831 5832 /**5833 * wp.media.view.MediaFrame.Select5834 *5835 * A frame for selecting an item or items from the media library.5836 *5837 * @class5838 * @augments wp.media.view.MediaFrame5839 * @augments wp.media.view.Frame5840 * @augments wp.media.View5841 * @augments wp.Backbone.View5842 * @augments Backbone.View5843 * @mixes wp.media.controller.StateMachine5844 */5845 5846 var MediaFrame = wp.media.view.MediaFrame,5847 l10n = wp.media.view.l10n,5848 Select;5849 5850 Select = MediaFrame.extend({5851 initialize: function() {5852 // Call 'initialize' directly on the parent class.5853 MediaFrame.prototype.initialize.apply( this, arguments );5854 5855 _.defaults( this.options, {5856 selection: [],5857 library: {},5858 multiple: false,5859 state: 'library'5860 });5861 5862 this.createSelection();5863 this.createStates();5864 this.bindHandlers();5865 },5866 5867 /**5868 * Attach a selection collection to the frame.5869 *5870 * A selection is a collection of attachments used for a specific purpose5871 * by a media frame. e.g. Selecting an attachment (or many) to insert into5872 * post content.5873 *5874 * @see media.model.Selection5875 */5876 createSelection: function() {5877 var selection = this.options.selection;5878 5879 if ( ! (selection instanceof wp.media.model.Selection) ) {5880 this.options.selection = new wp.media.model.Selection( selection, {5881 multiple: this.options.multiple5882 });5883 }5884 5885 this._selection = {5886 attachments: new wp.media.model.Attachments(),5887 difference: []5888 };5889 },5890 5891 /**5892 * Create the default states on the frame.5893 */5894 createStates: function() {5895 var options = this.options;5896 5897 if ( this.options.states ) {5898 return;5899 }5900 5901 // Add the default states.5902 this.states.add([5903 // Main states.5904 new wp.media.controller.Library({5905 library: wp.media.query( options.library ),5906 multiple: options.multiple,5907 title: options.title,5908 priority: 205909 })5910 ]);5911 },5912 5913 /**5914 * Bind region mode event callbacks.5915 *5916 * @see media.controller.Region.render5917 */5918 bindHandlers: function() {5919 this.on( 'router:create:browse', this.createRouter, this );5920 this.on( 'router:render:browse', this.browseRouter, this );5921 this.on( 'content:create:browse', this.browseContent, this );5922 this.on( 'content:render:upload', this.uploadContent, this );5923 this.on( 'toolbar:create:select', this.createSelectToolbar, this );5924 },5925 5926 /**5927 * Render callback for the router region in the `browse` mode.5928 *5929 * @param {wp.media.view.Router} routerView5930 */5931 browseRouter: function( routerView ) {5932 routerView.set({5933 upload: {5934 text: l10n.uploadFilesTitle,5935 priority: 205936 },5937 browse: {5938 text: l10n.mediaLibraryTitle,5939 priority: 405940 }5941 });5942 },5943 5944 /**5945 * Render callback for the content region in the `browse` mode.5946 *5947 * @param {wp.media.controller.Region} contentRegion5948 */5949 browseContent: function( contentRegion ) {5950 var state = this.state();5951 5952 this.$el.removeClass('hide-toolbar');5953 5954 // Browse our library of attachments.5955 contentRegion.view = new wp.media.view.AttachmentsBrowser({5956 controller: this,5957 collection: state.get('library'),5958 selection: state.get('selection'),5959 model: state,5960 sortable: state.get('sortable'),5961 search: state.get('searchable'),5962 filters: state.get('filterable'),5963 display: state.has('display') ? state.get('display') : state.get('displaySettings'),5964 dragInfo: state.get('dragInfo'),5965 5966 idealColumnWidth: state.get('idealColumnWidth'),5967 suggestedWidth: state.get('suggestedWidth'),5968 suggestedHeight: state.get('suggestedHeight'),5969 5970 AttachmentView: state.get('AttachmentView')5971 });5972 },5973 5974 /**5975 * Render callback for the content region in the `upload` mode.5976 */5977 uploadContent: function() {5978 this.$el.removeClass( 'hide-toolbar' );5979 this.content.set( new wp.media.view.UploaderInline({5980 controller: this5981 }) );5982 },5983 5984 /**5985 * Toolbars5986 *5987 * @param {Object} toolbar5988 * @param {Object} [options={}]5989 * @this wp.media.controller.Region5990 */5991 createSelectToolbar: function( toolbar, options ) {5992 options = options || this.options.button || {};5993 options.controller = this;5994 5995 toolbar.view = new wp.media.view.Toolbar.Select( options );5996 }5997 });5998 5999 module.exports = Select;6000 6001 },{}],45:[function(require,module,exports){6002 /**6003 * wp.media.view.Iframe6004 *6005 * @class6006 * @augments wp.media.View6007 * @augments wp.Backbone.View6008 * @augments Backbone.View6009 */6010 var Iframe = wp.media.View.extend({6011 className: 'media-iframe',6012 /**6013 * @returns {wp.media.view.Iframe} Returns itself to allow chaining6014 */6015 render: function() {6016 this.views.detach();6017 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );6018 this.views.render();6019 return this;6020 }6021 });6022 6023 module.exports = Iframe;6024 6025 },{}],46:[function(require,module,exports){6026 /*globals wp, _, jQuery */6027 6028 /**6029 * wp.media.view.ImageDetails6030 *6031 * @class6032 * @augments wp.media.view.Settings.AttachmentDisplay6033 * @augments wp.media.view.Settings6034 * @augments wp.media.View6035 * @augments wp.Backbone.View6036 * @augments Backbone.View6037 */6038 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,6039 $ = jQuery,6040 ImageDetails;6041 6042 ImageDetails = AttachmentDisplay.extend({6043 className: 'image-details',6044 template: wp.template('image-details'),6045 events: _.defaults( AttachmentDisplay.prototype.events, {6046 'click .edit-attachment': 'editAttachment',6047 'click .replace-attachment': 'replaceAttachment',6048 'click .advanced-toggle': 'onToggleAdvanced',6049 'change [data-setting="customWidth"]': 'onCustomSize',6050 'change [data-setting="customHeight"]': 'onCustomSize',6051 'keyup [data-setting="customWidth"]': 'onCustomSize',6052 'keyup [data-setting="customHeight"]': 'onCustomSize'6053 } ),6054 initialize: function() {6055 // used in AttachmentDisplay.prototype.updateLinkTo6056 this.options.attachment = this.model.attachment;6057 this.listenTo( this.model, 'change:url', this.updateUrl );6058 this.listenTo( this.model, 'change:link', this.toggleLinkSettings );6059 this.listenTo( this.model, 'change:size', this.toggleCustomSize );6060 6061 AttachmentDisplay.prototype.initialize.apply( this, arguments );6062 },6063 6064 prepare: function() {6065 var attachment = false;6066 6067 if ( this.model.attachment ) {6068 attachment = this.model.attachment.toJSON();6069 }6070 return _.defaults({6071 model: this.model.toJSON(),6072 attachment: attachment6073 }, this.options );6074 },6075 6076 render: function() {6077 var args = arguments;6078 6079 if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {6080 this.model.dfd6081 .done( _.bind( function() {6082 AttachmentDisplay.prototype.render.apply( this, args );6083 this.postRender();6084 }, this ) )6085 .fail( _.bind( function() {6086 this.model.attachment = false;6087 AttachmentDisplay.prototype.render.apply( this, args );6088 this.postRender();6089 }, this ) );6090 } else {6091 AttachmentDisplay.prototype.render.apply( this, arguments );6092 this.postRender();6093 }6094 6095 return this;6096 },6097 6098 postRender: function() {6099 setTimeout( _.bind( this.resetFocus, this ), 10 );6100 this.toggleLinkSettings();6101 if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {6102 this.toggleAdvanced( true );6103 }6104 this.trigger( 'post-render' );6105 },6106 6107 resetFocus: function() {6108 this.$( '.link-to-custom' ).blur();6109 this.$( '.embed-media-settings' ).scrollTop( 0 );6110 },6111 6112 updateUrl: function() {6113 this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );6114 this.$( '.url' ).val( this.model.get( 'url' ) );6115 },6116 6117 toggleLinkSettings: function() {6118 if ( this.model.get( 'link' ) === 'none' ) {6119 this.$( '.link-settings' ).addClass('hidden');6120 } else {6121 this.$( '.link-settings' ).removeClass('hidden');6122 }6123 },6124 6125 toggleCustomSize: function() {6126 if ( this.model.get( 'size' ) !== 'custom' ) {6127 this.$( '.custom-size' ).addClass('hidden');6128 } else {6129 this.$( '.custom-size' ).removeClass('hidden');6130 }6131 },6132 6133 onCustomSize: function( event ) {6134 var dimension = $( event.target ).data('setting'),6135 num = $( event.target ).val(),6136 value;6137 6138 // Ignore bogus input6139 if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {6140 event.preventDefault();6141 return;6142 }6143 6144 if ( dimension === 'customWidth' ) {6145 value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );6146 this.model.set( 'customHeight', value, { silent: true } );6147 this.$( '[data-setting="customHeight"]' ).val( value );6148 } else {6149 value = Math.round( this.model.get( 'aspectRatio' ) * num );6150 this.model.set( 'customWidth', value, { silent: true } );6151 this.$( '[data-setting="customWidth"]' ).val( value );6152 }6153 },6154 6155 onToggleAdvanced: function( event ) {6156 event.preventDefault();6157 this.toggleAdvanced();6158 },6159 6160 toggleAdvanced: function( show ) {6161 var $advanced = this.$el.find( '.advanced-section' ),6162 mode;6163 6164 if ( $advanced.hasClass('advanced-visible') || show === false ) {6165 $advanced.removeClass('advanced-visible');6166 $advanced.find('.advanced-settings').addClass('hidden');6167 mode = 'hide';6168 } else {6169 $advanced.addClass('advanced-visible');6170 $advanced.find('.advanced-settings').removeClass('hidden');6171 mode = 'show';6172 }6173 6174 window.setUserSetting( 'advImgDetails', mode );6175 },6176 6177 editAttachment: function( event ) {6178 var editState = this.controller.states.get( 'edit-image' );6179 6180 if ( window.imageEdit && editState ) {6181 event.preventDefault();6182 editState.set( 'image', this.model.attachment );6183 this.controller.setState( 'edit-image' );6184 }6185 },6186 6187 replaceAttachment: function( event ) {6188 event.preventDefault();6189 this.controller.setState( 'replace-image' );6190 }6191 });6192 6193 module.exports = ImageDetails;6194 6195 },{}],47:[function(require,module,exports){6196 /**6197 * wp.media.view.Label6198 *6199 * @class6200 * @augments wp.media.View6201 * @augments wp.Backbone.View6202 * @augments Backbone.View6203 */6204 var Label = wp.media.View.extend({6205 tagName: 'label',6206 className: 'screen-reader-text',6207 6208 initialize: function() {6209 this.value = this.options.value;6210 },6211 6212 render: function() {6213 this.$el.html( this.value );6214 6215 return this;6216 }6217 });6218 6219 module.exports = Label;6220 6221 },{}],48:[function(require,module,exports){6222 /*globals wp, _, jQuery */6223 6224 /**6225 * wp.media.view.MediaFrame6226 *6227 * The frame used to create the media modal.6228 *6229 * @class6230 * @augments wp.media.view.Frame6231 * @augments wp.media.View6232 * @augments wp.Backbone.View6233 * @augments Backbone.View6234 * @mixes wp.media.controller.StateMachine6235 */6236 var Frame = wp.media.view.Frame,6237 $ = jQuery,6238 MediaFrame;6239 6240 MediaFrame = Frame.extend({6241 className: 'media-frame',6242 template: wp.template('media-frame'),6243 regions: ['menu','title','content','toolbar','router'],6244 6245 events: {6246 'click div.media-frame-title h1': 'toggleMenu'6247 },6248 6249 /**6250 * @global wp.Uploader6251 */6252 initialize: function() {6253 Frame.prototype.initialize.apply( this, arguments );6254 6255 _.defaults( this.options, {6256 title: '',6257 modal: true,6258 uploader: true6259 });6260 6261 // Ensure core UI is enabled.6262 this.$el.addClass('wp-core-ui');6263 6264 // Initialize modal container view.6265 if ( this.options.modal ) {6266 this.modal = new wp.media.view.Modal({6267 controller: this,6268 title: this.options.title6269 });6270 6271 this.modal.content( this );6272 }6273 6274 // Force the uploader off if the upload limit has been exceeded or6275 // if the browser isn't supported.6276 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {6277 this.options.uploader = false;6278 }6279 6280 // Initialize window-wide uploader.6281 if ( this.options.uploader ) {6282 this.uploader = new wp.media.view.UploaderWindow({6283 controller: this,6284 uploader: {6285 dropzone: this.modal ? this.modal.$el : this.$el,6286 container: this.$el6287 }6288 });6289 this.views.set( '.media-frame-uploader', this.uploader );6290 }6291 6292 this.on( 'attach', _.bind( this.views.ready, this.views ), this );6293 6294 // Bind default title creation.6295 this.on( 'title:create:default', this.createTitle, this );6296 this.title.mode('default');6297 6298 this.on( 'title:render', function( view ) {6299 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );6300 });6301 6302 // Bind default menu.6303 this.on( 'menu:create:default', this.createMenu, this );6304 },6305 /**6306 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining6307 */6308 render: function() {6309 // Activate the default state if no active state exists.6310 if ( ! this.state() && this.options.state ) {6311 this.setState( this.options.state );6312 }6313 /**6314 * call 'render' directly on the parent class6315 */6316 return Frame.prototype.render.apply( this, arguments );6317 },6318 /**6319 * @param {Object} title6320 * @this wp.media.controller.Region6321 */6322 createTitle: function( title ) {6323 title.view = new wp.media.View({6324 controller: this,6325 tagName: 'h1'6326 });6327 },6328 /**6329 * @param {Object} menu6330 * @this wp.media.controller.Region6331 */6332 createMenu: function( menu ) {6333 menu.view = new wp.media.view.Menu({6334 controller: this6335 });6336 },6337 6338 toggleMenu: function() {6339 this.$el.find( '.media-menu' ).toggleClass( 'visible' );6340 },6341 6342 /**6343 * @param {Object} toolbar6344 * @this wp.media.controller.Region6345 */6346 createToolbar: function( toolbar ) {6347 toolbar.view = new wp.media.view.Toolbar({6348 controller: this6349 });6350 },6351 /**6352 * @param {Object} router6353 * @this wp.media.controller.Region6354 */6355 createRouter: function( router ) {6356 router.view = new wp.media.view.Router({6357 controller: this6358 });6359 },6360 /**6361 * @param {Object} options6362 */6363 createIframeStates: function( options ) {6364 var settings = wp.media.view.settings,6365 tabs = settings.tabs,6366 tabUrl = settings.tabUrl,6367 $postId;6368 6369 if ( ! tabs || ! tabUrl ) {6370 return;6371 }6372 6373 // Add the post ID to the tab URL if it exists.6374 $postId = $('#post_ID');6375 if ( $postId.length ) {6376 tabUrl += '&post_id=' + $postId.val();6377 }6378 6379 // Generate the tab states.6380 _.each( tabs, function( title, id ) {6381 this.state( 'iframe:' + id ).set( _.defaults({6382 tab: id,6383 src: tabUrl + '&tab=' + id,6384 title: title,6385 content: 'iframe',6386 menu: 'default'6387 }, options ) );6388 }, this );6389 6390 this.on( 'content:create:iframe', this.iframeContent, this );6391 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );6392 this.on( 'menu:render:default', this.iframeMenu, this );6393 this.on( 'open', this.hijackThickbox, this );6394 this.on( 'close', this.restoreThickbox, this );6395 },6396 6397 /**6398 * @param {Object} content6399 * @this wp.media.controller.Region6400 */6401 iframeContent: function( content ) {6402 this.$el.addClass('hide-toolbar');6403 content.view = new wp.media.view.Iframe({6404 controller: this6405 });6406 },6407 6408 iframeContentCleanup: function() {6409 this.$el.removeClass('hide-toolbar');6410 },6411 6412 iframeMenu: function( view ) {6413 var views = {};6414 6415 if ( ! view ) {6416 return;6417 }6418 6419 _.each( wp.media.view.settings.tabs, function( title, id ) {6420 views[ 'iframe:' + id ] = {6421 text: this.state( 'iframe:' + id ).get('title'),6422 priority: 2006423 };6424 }, this );6425 6426 view.set( views );6427 },6428 6429 hijackThickbox: function() {6430 var frame = this;6431 6432 if ( ! window.tb_remove || this._tb_remove ) {6433 return;6434 }6435 6436 this._tb_remove = window.tb_remove;6437 window.tb_remove = function() {6438 frame.close();6439 frame.reset();6440 frame.setState( frame.options.state );6441 frame._tb_remove.call( window );6442 };6443 },6444 6445 restoreThickbox: function() {6446 if ( ! this._tb_remove ) {6447 return;6448 }6449 6450 window.tb_remove = this._tb_remove;6451 delete this._tb_remove;6452 }6453 });6454 6455 // Map some of the modal's methods to the frame.6456 _.each(['open','close','attach','detach','escape'], function( method ) {6457 /**6458 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining6459 */6460 MediaFrame.prototype[ method ] = function() {6461 if ( this.modal ) {6462 this.modal[ method ].apply( this.modal, arguments );6463 }6464 return this;6465 };6466 });6467 6468 module.exports = MediaFrame;6469 6470 },{}],49:[function(require,module,exports){6471 /*globals jQuery */6472 6473 /**6474 * wp.media.view.MenuItem6475 *6476 * @class6477 * @augments wp.media.View6478 * @augments wp.Backbone.View6479 * @augments Backbone.View6480 */6481 var $ = jQuery,6482 MenuItem;6483 6484 MenuItem = wp.media.View.extend({6485 tagName: 'a',6486 className: 'media-menu-item',6487 6488 attributes: {6489 href: '#'6490 },6491 6492 events: {6493 'click': '_click'6494 },6495 /**6496 * @param {Object} event6497 */6498 _click: function( event ) {6499 var clickOverride = this.options.click;6500 6501 if ( event ) {6502 event.preventDefault();6503 }6504 6505 if ( clickOverride ) {6506 clickOverride.call( this );6507 } else {6508 this.click();6509 }6510 6511 // When selecting a tab along the left side,6512 // focus should be transferred into the main panel6513 if ( ! wp.media.isTouchDevice ) {6514 $('.media-frame-content input').first().focus();6515 }6516 },6517 6518 click: function() {6519 var state = this.options.state;6520 6521 if ( state ) {6522 this.controller.setState( state );6523 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below6524 }6525 },6526 /**6527 * @returns {wp.media.view.MenuItem} returns itself to allow chaining6528 */6529 render: function() {6530 var options = this.options;6531 6532 if ( options.text ) {6533 this.$el.text( options.text );6534 } else if ( options.html ) {6535 this.$el.html( options.html );6536 }6537 6538 return this;6539 }6540 });6541 6542 module.exports = MenuItem;6543 6544 },{}],50:[function(require,module,exports){6545 /**6546 * wp.media.view.Menu6547 *6548 * @class6549 * @augments wp.media.view.PriorityList6550 * @augments wp.media.View6551 * @augments wp.Backbone.View6552 * @augments Backbone.View6553 */6554 var MenuItem = wp.media.view.MenuItem,6555 PriorityList = wp.media.view.PriorityList,6556 Menu;6557 6558 Menu = PriorityList.extend({6559 tagName: 'div',6560 className: 'media-menu',6561 property: 'state',6562 ItemView: MenuItem,6563 region: 'menu',6564 6565 /* TODO: alternatively hide on any click anywhere6566 events: {6567 'click': 'click'6568 },6569 6570 click: function() {6571 this.$el.removeClass( 'visible' );6572 },6573 */6574 6575 /**6576 * @param {Object} options6577 * @param {string} id6578 * @returns {wp.media.View}6579 */6580 toView: function( options, id ) {6581 options = options || {};6582 options[ this.property ] = options[ this.property ] || id;6583 return new this.ItemView( options ).render();6584 },6585 6586 ready: function() {6587 /**6588 * call 'ready' directly on the parent class6589 */6590 PriorityList.prototype.ready.apply( this, arguments );6591 this.visibility();6592 },6593 6594 set: function() {6595 /**6596 * call 'set' directly on the parent class6597 */6598 PriorityList.prototype.set.apply( this, arguments );6599 this.visibility();6600 },6601 6602 unset: function() {6603 /**6604 * call 'unset' directly on the parent class6605 */6606 PriorityList.prototype.unset.apply( this, arguments );6607 this.visibility();6608 },6609 6610 visibility: function() {6611 var region = this.region,6612 view = this.controller[ region ].get(),6613 views = this.views.get(),6614 hide = ! views || views.length < 2;6615 6616 if ( this === view ) {6617 this.controller.$el.toggleClass( 'hide-' + region, hide );6618 }6619 },6620 /**6621 * @param {string} id6622 */6623 select: function( id ) {6624 var view = this.get( id );6625 6626 if ( ! view ) {6627 return;6628 }6629 6630 this.deselect();6631 view.$el.addClass('active');6632 },6633 6634 deselect: function() {6635 this.$el.children().removeClass('active');6636 },6637 6638 hide: function( id ) {6639 var view = this.get( id );6640 6641 if ( ! view ) {6642 return;6643 }6644 6645 view.$el.addClass('hidden');6646 },6647 6648 show: function( id ) {6649 var view = this.get( id );6650 6651 if ( ! view ) {6652 return;6653 }6654 6655 view.$el.removeClass('hidden');6656 }6657 });6658 6659 module.exports = Menu;6660 6661 },{}],51:[function(require,module,exports){6662 /*globals wp, _, jQuery */6663 6664 /**6665 * wp.media.view.Modal6666 *6667 * A modal view, which the media modal uses as its default container.6668 *6669 * @class6670 * @augments wp.media.View6671 * @augments wp.Backbone.View6672 * @augments Backbone.View6673 */6674 var $ = jQuery,6675 Modal;6676 6677 Modal = wp.media.View.extend({6678 tagName: 'div',6679 template: wp.template('media-modal'),6680 6681 attributes: {6682 tabindex: 06683 },6684 6685 events: {6686 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',6687 'keydown': 'keydown'6688 },6689 6690 initialize: function() {6691 _.defaults( this.options, {6692 container: document.body,6693 title: '',6694 propagate: true,6695 freeze: true6696 });6697 6698 this.focusManager = new wp.media.view.FocusManager({6699 el: this.el6700 });6701 },6702 /**6703 * @returns {Object}6704 */6705 prepare: function() {6706 return {6707 title: this.options.title6708 };6709 },6710 6711 /**6712 * @returns {wp.media.view.Modal} Returns itself to allow chaining6713 */6714 attach: function() {6715 if ( this.views.attached ) {6716 return this;6717 }6718 6719 if ( ! this.views.rendered ) {6720 this.render();6721 }6722 6723 this.$el.appendTo( this.options.container );6724 6725 // Manually mark the view as attached and trigger ready.6726 this.views.attached = true;6727 this.views.ready();6728 6729 return this.propagate('attach');6730 },6731 6732 /**6733 * @returns {wp.media.view.Modal} Returns itself to allow chaining6734 */6735 detach: function() {6736 if ( this.$el.is(':visible') ) {6737 this.close();6738 }6739 6740 this.$el.detach();6741 this.views.attached = false;6742 return this.propagate('detach');6743 },6744 6745 /**6746 * @returns {wp.media.view.Modal} Returns itself to allow chaining6747 */6748 open: function() {6749 var $el = this.$el,6750 options = this.options,6751 mceEditor;6752 6753 if ( $el.is(':visible') ) {6754 return this;6755 }6756 6757 if ( ! this.views.attached ) {6758 this.attach();6759 }6760 6761 // If the `freeze` option is set, record the window's scroll position.6762 if ( options.freeze ) {6763 this._freeze = {6764 scrollTop: $( window ).scrollTop()6765 };6766 }6767 6768 // Disable page scrolling.6769 $( 'body' ).addClass( 'modal-open' );6770 6771 $el.show();6772 6773 // Try to close the onscreen keyboard6774 if ( 'ontouchend' in document ) {6775 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) {6776 mceEditor.iframeElement.focus();6777 mceEditor.iframeElement.blur();6778 6779 setTimeout( function() {6780 mceEditor.iframeElement.blur();6781 }, 100 );6782 }6783 }6784 6785 this.$el.focus();6786 6787 return this.propagate('open');6788 },6789 6790 /**6791 * @param {Object} options6792 * @returns {wp.media.view.Modal} Returns itself to allow chaining6793 */6794 close: function( options ) {6795 var freeze = this._freeze;6796 6797 if ( ! this.views.attached || ! this.$el.is(':visible') ) {6798 return this;6799 }6800 6801 // Enable page scrolling.6802 $( 'body' ).removeClass( 'modal-open' );6803 6804 // Hide modal and remove restricted media modal tab focus once it's closed6805 this.$el.hide().undelegate( 'keydown' );6806 6807 // Put focus back in useful location once modal is closed6808 $('#wpbody-content').focus();6809 6810 this.propagate('close');6811 6812 // If the `freeze` option is set, restore the container's scroll position.6813 if ( freeze ) {6814 $( window ).scrollTop( freeze.scrollTop );6815 }6816 6817 if ( options && options.escape ) {6818 this.propagate('escape');6819 }6820 6821 return this;6822 },6823 /**6824 * @returns {wp.media.view.Modal} Returns itself to allow chaining6825 */6826 escape: function() {6827 return this.close({ escape: true });6828 },6829 /**6830 * @param {Object} event6831 */6832 escapeHandler: function( event ) {6833 event.preventDefault();6834 this.escape();6835 },6836 6837 /**6838 * @param {Array|Object} content Views to register to '.media-modal-content'6839 * @returns {wp.media.view.Modal} Returns itself to allow chaining6840 */6841 content: function( content ) {6842 this.views.set( '.media-modal-content', content );6843 return this;6844 },6845 6846 /**6847 * Triggers a modal event and if the `propagate` option is set,6848 * forwards events to the modal's controller.6849 *6850 * @param {string} id6851 * @returns {wp.media.view.Modal} Returns itself to allow chaining6852 */6853 propagate: function( id ) {6854 this.trigger( id );6855 6856 if ( this.options.propagate ) {6857 this.controller.trigger( id );6858 }6859 6860 return this;6861 },6862 /**6863 * @param {Object} event6864 */6865 keydown: function( event ) {6866 // Close the modal when escape is pressed.6867 if ( 27 === event.which && this.$el.is(':visible') ) {6868 this.escape();6869 event.stopImmediatePropagation();6870 }6871 }6872 });6873 6874 module.exports = Modal;6875 6876 },{}],52:[function(require,module,exports){6877 /*globals _, Backbone */6878 6879 /**6880 * wp.media.view.PriorityList6881 *6882 * @class6883 * @augments wp.media.View6884 * @augments wp.Backbone.View6885 * @augments Backbone.View6886 */6887 var PriorityList = wp.media.View.extend({6888 tagName: 'div',6889 6890 initialize: function() {6891 this._views = {};6892 6893 this.set( _.extend( {}, this._views, this.options.views ), { silent: true });6894 delete this.options.views;6895 6896 if ( ! this.options.silent ) {6897 this.render();6898 }6899 },6900 /**6901 * @param {string} id6902 * @param {wp.media.View|Object} view6903 * @param {Object} options6904 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining6905 */6906 set: function( id, view, options ) {6907 var priority, views, index;6908 6909 options = options || {};6910 6911 // Accept an object with an `id` : `view` mapping.6912 if ( _.isObject( id ) ) {6913 _.each( id, function( view, id ) {6914 this.set( id, view );6915 }, this );6916 return this;6917 }6918 6919 if ( ! (view instanceof Backbone.View) ) {6920 view = this.toView( view, id, options );6921 }6922 view.controller = view.controller || this.controller;6923 6924 this.unset( id );6925 6926 priority = view.options.priority || 10;6927 views = this.views.get() || [];6928 6929 _.find( views, function( existing, i ) {6930 if ( existing.options.priority > priority ) {6931 index = i;6932 return true;6933 }6934 });6935 6936 this._views[ id ] = view;6937 this.views.add( view, {6938 at: _.isNumber( index ) ? index : views.length || 06939 });6940 6941 return this;6942 },6943 /**6944 * @param {string} id6945 * @returns {wp.media.View}6946 */6947 get: function( id ) {6948 return this._views[ id ];6949 },6950 /**6951 * @param {string} id6952 * @returns {wp.media.view.PriorityList}6953 */6954 unset: function( id ) {6955 var view = this.get( id );6956 6957 if ( view ) {6958 view.remove();6959 }6960 6961 delete this._views[ id ];6962 return this;6963 },6964 /**6965 * @param {Object} options6966 * @returns {wp.media.View}6967 */6968 toView: function( options ) {6969 return new wp.media.View( options );6970 }6971 });6972 6973 module.exports = PriorityList;6974 6975 },{}],53:[function(require,module,exports){6976 /**6977 * wp.media.view.RouterItem6978 *6979 * @class6980 * @augments wp.media.view.MenuItem6981 * @augments wp.media.View6982 * @augments wp.Backbone.View6983 * @augments Backbone.View6984 */6985 var RouterItem = wp.media.view.MenuItem.extend({6986 /**6987 * On click handler to activate the content region's corresponding mode.6988 */6989 click: function() {6990 var contentMode = this.options.contentMode;6991 if ( contentMode ) {6992 this.controller.content.mode( contentMode );6993 }6994 }6995 });6996 6997 module.exports = RouterItem;6998 6999 },{}],54:[function(require,module,exports){7000 /*globals wp */7001 7002 /**7003 * wp.media.view.Router7004 *7005 * @class7006 * @augments wp.media.view.Menu7007 * @augments wp.media.view.PriorityList7008 * @augments wp.media.View7009 * @augments wp.Backbone.View7010 * @augments Backbone.View7011 */7012 var Menu = wp.media.view.Menu,7013 Router;7014 7015 Router = Menu.extend({7016 tagName: 'div',7017 className: 'media-router',7018 property: 'contentMode',7019 ItemView: wp.media.view.RouterItem,7020 region: 'router',7021 7022 initialize: function() {7023 this.controller.on( 'content:render', this.update, this );7024 // Call 'initialize' directly on the parent class.7025 Menu.prototype.initialize.apply( this, arguments );7026 },7027 7028 update: function() {7029 var mode = this.controller.content.mode();7030 if ( mode ) {7031 this.select( mode );7032 }7033 }7034 });7035 7036 module.exports = Router;7037 7038 },{}],55:[function(require,module,exports){7039 /*globals wp */7040 7041 /**7042 * wp.media.view.Search7043 *7044 * @class7045 * @augments wp.media.View7046 * @augments wp.Backbone.View7047 * @augments Backbone.View7048 */7049 var l10n = wp.media.view.l10n,7050 Search;7051 7052 Search = wp.media.View.extend({7053 tagName: 'input',7054 className: 'search',7055 id: 'media-search-input',7056 7057 attributes: {7058 type: 'search',7059 placeholder: l10n.search7060 },7061 7062 events: {7063 'input': 'search',7064 'keyup': 'search',7065 'change': 'search',7066 'search': 'search'7067 },7068 7069 /**7070 * @returns {wp.media.view.Search} Returns itself to allow chaining7071 */7072 render: function() {7073 this.el.value = this.model.escape('search');7074 return this;7075 },7076 7077 search: function( event ) {7078 if ( event.target.value ) {7079 this.model.set( 'search', event.target.value );7080 } else {7081 this.model.unset('search');7082 }7083 }7084 });7085 7086 module.exports = Search;7087 7088 },{}],56:[function(require,module,exports){7089 /*globals wp, _, Backbone */7090 7091 /**7092 * wp.media.view.Selection7093 *7094 * @class7095 * @augments wp.media.View7096 * @augments wp.Backbone.View7097 * @augments Backbone.View7098 */7099 var l10n = wp.media.view.l10n,7100 Selection;7101 7102 Selection = wp.media.View.extend({7103 tagName: 'div',7104 className: 'media-selection',7105 template: wp.template('media-selection'),7106 7107 events: {7108 'click .edit-selection': 'edit',7109 'click .clear-selection': 'clear'7110 },7111 7112 initialize: function() {7113 _.defaults( this.options, {7114 editable: false,7115 clearable: true7116 });7117 7118 /**7119 * @member {wp.media.view.Attachments.Selection}7120 */7121 this.attachments = new wp.media.view.Attachments.Selection({7122 controller: this.controller,7123 collection: this.collection,7124 selection: this.collection,7125 model: new Backbone.Model()7126 });7127 7128 this.views.set( '.selection-view', this.attachments );7129 this.collection.on( 'add remove reset', this.refresh, this );7130 this.controller.on( 'content:activate', this.refresh, this );7131 },7132 7133 ready: function() {7134 this.refresh();7135 },7136 7137 refresh: function() {7138 // If the selection hasn't been rendered, bail.7139 if ( ! this.$el.children().length ) {7140 return;7141 }7142 7143 var collection = this.collection,7144 editing = 'edit-selection' === this.controller.content.mode();7145 7146 // If nothing is selected, display nothing.7147 this.$el.toggleClass( 'empty', ! collection.length );7148 this.$el.toggleClass( 'one', 1 === collection.length );7149 this.$el.toggleClass( 'editing', editing );7150 7151 this.$('.count').text( l10n.selected.replace('%d', collection.length) );7152 },7153 7154 edit: function( event ) {7155 event.preventDefault();7156 if ( this.options.editable ) {7157 this.options.editable.call( this, this.collection );7158 }7159 },7160 7161 clear: function( event ) {7162 event.preventDefault();7163 this.collection.reset();7164 7165 // Keep focus inside media modal7166 // after clear link is selected7167 this.controller.modal.focusManager.focus();7168 }7169 });7170 7171 module.exports = Selection;7172 7173 },{}],57:[function(require,module,exports){7174 /*globals _, Backbone */7175 7176 /**7177 * wp.media.view.Settings7178 *7179 * @class7180 * @augments wp.media.View7181 * @augments wp.Backbone.View7182 * @augments Backbone.View7183 */7184 var View = wp.media.View,7185 $ = Backbone.$,7186 Settings;7187 7188 Settings = View.extend({7189 events: {7190 'click button': 'updateHandler',7191 'change input': 'updateHandler',7192 'change select': 'updateHandler',7193 'change textarea': 'updateHandler'7194 },7195 7196 initialize: function() {7197 this.model = this.model || new Backbone.Model();7198 this.listenTo( this.model, 'change', this.updateChanges );7199 },7200 7201 prepare: function() {7202 return _.defaults({7203 model: this.model.toJSON()7204 }, this.options );7205 },7206 /**7207 * @returns {wp.media.view.Settings} Returns itself to allow chaining7208 */7209 render: function() {7210 View.prototype.render.apply( this, arguments );7211 // Select the correct values.7212 _( this.model.attributes ).chain().keys().each( this.update, this );7213 return this;7214 },7215 /**7216 * @param {string} key7217 */7218 update: function( key ) {7219 var value = this.model.get( key ),7220 $setting = this.$('[data-setting="' + key + '"]'),7221 $buttons, $value;7222 7223 // Bail if we didn't find a matching setting.7224 if ( ! $setting.length ) {7225 return;7226 }7227 7228 // Attempt to determine how the setting is rendered and update7229 // the selected value.7230 7231 // Handle dropdowns.7232 if ( $setting.is('select') ) {7233 $value = $setting.find('[value="' + value + '"]');7234 7235 if ( $value.length ) {7236 $setting.find('option').prop( 'selected', false );7237 $value.prop( 'selected', true );7238 } else {7239 // If we can't find the desired value, record what *is* selected.7240 this.model.set( key, $setting.find(':selected').val() );7241 }7242 7243 // Handle button groups.7244 } else if ( $setting.hasClass('button-group') ) {7245 $buttons = $setting.find('button').removeClass('active');7246 $buttons.filter( '[value="' + value + '"]' ).addClass('active');7247 7248 // Handle text inputs and textareas.7249 } else if ( $setting.is('input[type="text"], textarea') ) {7250 if ( ! $setting.is(':focus') ) {7251 $setting.val( value );7252 }7253 // Handle checkboxes.7254 } else if ( $setting.is('input[type="checkbox"]') ) {7255 $setting.prop( 'checked', !! value && 'false' !== value );7256 }7257 },7258 /**7259 * @param {Object} event7260 */7261 updateHandler: function( event ) {7262 var $setting = $( event.target ).closest('[data-setting]'),7263 value = event.target.value,7264 userSetting;7265 7266 event.preventDefault();7267 7268 if ( ! $setting.length ) {7269 return;7270 }7271 7272 // Use the correct value for checkboxes.7273 if ( $setting.is('input[type="checkbox"]') ) {7274 value = $setting[0].checked;7275 }7276 7277 // Update the corresponding setting.7278 this.model.set( $setting.data('setting'), value );7279 7280 // If the setting has a corresponding user setting,7281 // update that as well.7282 if ( userSetting = $setting.data('userSetting') ) {7283 window.setUserSetting( userSetting, value );7284 }7285 },7286 7287 updateChanges: function( model ) {7288 if ( model.hasChanged() ) {7289 _( model.changed ).chain().keys().each( this.update, this );7290 }7291 }7292 });7293 7294 module.exports = Settings;7295 7296 },{}],58:[function(require,module,exports){7297 /*globals wp, _ */7298 7299 /**7300 * wp.media.view.Settings.AttachmentDisplay7301 *7302 * @class7303 * @augments wp.media.view.Settings7304 * @augments wp.media.View7305 * @augments wp.Backbone.View7306 * @augments Backbone.View7307 */7308 var Settings = wp.media.view.Settings,7309 AttachmentDisplay;7310 7311 AttachmentDisplay = Settings.extend({7312 className: 'attachment-display-settings',7313 template: wp.template('attachment-display-settings'),7314 7315 initialize: function() {7316 var attachment = this.options.attachment;7317 7318 _.defaults( this.options, {7319 userSettings: false7320 });7321 // Call 'initialize' directly on the parent class.7322 Settings.prototype.initialize.apply( this, arguments );7323 this.listenTo( this.model, 'change:link', this.updateLinkTo );7324 7325 if ( attachment ) {7326 attachment.on( 'change:uploading', this.render, this );7327 }7328 },7329 7330 dispose: function() {7331 var attachment = this.options.attachment;7332 if ( attachment ) {7333 attachment.off( null, null, this );7334 }7335 /**7336 * call 'dispose' directly on the parent class7337 */7338 Settings.prototype.dispose.apply( this, arguments );7339 },7340 /**7341 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining7342 */7343 render: function() {7344 var attachment = this.options.attachment;7345 if ( attachment ) {7346 _.extend( this.options, {7347 sizes: attachment.get('sizes'),7348 type: attachment.get('type')7349 });7350 }7351 /**7352 * call 'render' directly on the parent class7353 */7354 Settings.prototype.render.call( this );7355 this.updateLinkTo();7356 return this;7357 },7358 7359 updateLinkTo: function() {7360 var linkTo = this.model.get('link'),7361 $input = this.$('.link-to-custom'),7362 attachment = this.options.attachment;7363 7364 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {7365 $input.addClass( 'hidden' );7366 return;7367 }7368 7369 if ( attachment ) {7370 if ( 'post' === linkTo ) {7371 $input.val( attachment.get('link') );7372 } else if ( 'file' === linkTo ) {7373 $input.val( attachment.get('url') );7374 } else if ( ! this.model.get('linkUrl') ) {7375 $input.val('http://');7376 }7377 7378 $input.prop( 'readonly', 'custom' !== linkTo );7379 }7380 7381 $input.removeClass( 'hidden' );7382 7383 // If the input is visible, focus and select its contents.7384 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {7385 $input.focus()[0].select();7386 }7387 }7388 });7389 7390 module.exports = AttachmentDisplay;7391 7392 },{}],59:[function(require,module,exports){7393 /*globals wp */7394 7395 /**7396 * wp.media.view.Settings.Gallery7397 *7398 * @class7399 * @augments wp.media.view.Settings7400 * @augments wp.media.View7401 * @augments wp.Backbone.View7402 * @augments Backbone.View7403 */7404 var Gallery = wp.media.view.Settings.extend({7405 className: 'collection-settings gallery-settings',7406 template: wp.template('gallery-settings')7407 });7408 7409 module.exports = Gallery;7410 7411 },{}],60:[function(require,module,exports){7412 /*globals wp */7413 7414 /**7415 * wp.media.view.Settings.Playlist7416 *7417 * @class7418 * @augments wp.media.view.Settings7419 * @augments wp.media.View7420 * @augments wp.Backbone.View7421 * @augments Backbone.View7422 */7423 var Playlist = wp.media.view.Settings.extend({7424 className: 'collection-settings playlist-settings',7425 template: wp.template('playlist-settings')7426 });7427 7428 module.exports = Playlist;7429 7430 },{}],61:[function(require,module,exports){7431 /**7432 * wp.media.view.Sidebar7433 *7434 * @class7435 * @augments wp.media.view.PriorityList7436 * @augments wp.media.View7437 * @augments wp.Backbone.View7438 * @augments Backbone.View7439 */7440 var Sidebar = wp.media.view.PriorityList.extend({7441 className: 'media-sidebar'7442 });7443 7444 module.exports = Sidebar;7445 7446 },{}],62:[function(require,module,exports){7447 /*globals _ */7448 7449 /**7450 * wp.media.view.Spinner7451 *7452 * @class7453 * @augments wp.media.View7454 * @augments wp.Backbone.View7455 * @augments Backbone.View7456 */7457 var Spinner = wp.media.View.extend({7458 tagName: 'span',7459 className: 'spinner',7460 spinnerTimeout: false,7461 delay: 400,7462 7463 show: function() {7464 if ( ! this.spinnerTimeout ) {7465 this.spinnerTimeout = _.delay(function( $el ) {7466 $el.show();7467 }, this.delay, this.$el );7468 }7469 7470 return this;7471 },7472 7473 hide: function() {7474 this.$el.hide();7475 this.spinnerTimeout = clearTimeout( this.spinnerTimeout );7476 7477 return this;7478 }7479 });7480 7481 module.exports = Spinner;7482 7483 },{}],63:[function(require,module,exports){7484 /*globals _, Backbone */7485 7486 /**7487 * wp.media.view.Toolbar7488 *7489 * A toolbar which consists of a primary and a secondary section. Each sections7490 * can be filled with views.7491 *7492 * @class7493 * @augments wp.media.View7494 * @augments wp.Backbone.View7495 * @augments Backbone.View7496 */7497 var View = wp.media.View,7498 Toolbar;7499 7500 Toolbar = View.extend({7501 tagName: 'div',7502 className: 'media-toolbar',7503 7504 initialize: function() {7505 var state = this.controller.state(),7506 selection = this.selection = state.get('selection'),7507 library = this.library = state.get('library');7508 7509 this._views = {};7510 7511 // The toolbar is composed of two `PriorityList` views.7512 this.primary = new wp.media.view.PriorityList();7513 this.secondary = new wp.media.view.PriorityList();7514 this.primary.$el.addClass('media-toolbar-primary search-form');7515 this.secondary.$el.addClass('media-toolbar-secondary');7516 7517 this.views.set([ this.secondary, this.primary ]);7518 7519 if ( this.options.items ) {7520 this.set( this.options.items, { silent: true });7521 }7522 7523 if ( ! this.options.silent ) {7524 this.render();7525 }7526 7527 if ( selection ) {7528 selection.on( 'add remove reset', this.refresh, this );7529 }7530 7531 if ( library ) {7532 library.on( 'add remove reset', this.refresh, this );7533 }7534 },7535 /**7536 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining7537 */7538 dispose: function() {7539 if ( this.selection ) {7540 this.selection.off( null, null, this );7541 }7542 7543 if ( this.library ) {7544 this.library.off( null, null, this );7545 }7546 /**7547 * call 'dispose' directly on the parent class7548 */7549 return View.prototype.dispose.apply( this, arguments );7550 },7551 7552 ready: function() {7553 this.refresh();7554 },7555 7556 /**7557 * @param {string} id7558 * @param {Backbone.View|Object} view7559 * @param {Object} [options={}]7560 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining7561 */7562 set: function( id, view, options ) {7563 var list;7564 options = options || {};7565 7566 // Accept an object with an `id` : `view` mapping.7567 if ( _.isObject( id ) ) {7568 _.each( id, function( view, id ) {7569 this.set( id, view, { silent: true });7570 }, this );7571 7572 } else {7573 if ( ! ( view instanceof Backbone.View ) ) {7574 view.classes = [ 'media-button-' + id ].concat( view.classes || [] );7575 view = new wp.media.view.Button( view ).render();7576 }7577 7578 view.controller = view.controller || this.controller;7579 7580 this._views[ id ] = view;7581 7582 list = view.options.priority < 0 ? 'secondary' : 'primary';7583 this[ list ].set( id, view, options );7584 }7585 7586 if ( ! options.silent ) {7587 this.refresh();7588 }7589 7590 return this;7591 },7592 /**7593 * @param {string} id7594 * @returns {wp.media.view.Button}7595 */7596 get: function( id ) {7597 return this._views[ id ];7598 },7599 /**7600 * @param {string} id7601 * @param {Object} options7602 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining7603 */7604 unset: function( id, options ) {7605 delete this._views[ id ];7606 this.primary.unset( id, options );7607 this.secondary.unset( id, options );7608 7609 if ( ! options || ! options.silent ) {7610 this.refresh();7611 }7612 return this;7613 },7614 7615 refresh: function() {7616 var state = this.controller.state(),7617 library = state.get('library'),7618 selection = state.get('selection');7619 7620 _.each( this._views, function( button ) {7621 if ( ! button.model || ! button.options || ! button.options.requires ) {7622 return;7623 }7624 7625 var requires = button.options.requires,7626 disabled = false;7627 7628 // Prevent insertion of attachments if any of them are still uploading7629 disabled = _.some( selection.models, function( attachment ) {7630 return attachment.get('uploading') === true;7631 });7632 7633 if ( requires.selection && selection && ! selection.length ) {7634 disabled = true;7635 } else if ( requires.library && library && ! library.length ) {7636 disabled = true;7637 }7638 button.model.set( 'disabled', disabled );7639 });7640 }7641 });7642 7643 module.exports = Toolbar;7644 7645 },{}],64:[function(require,module,exports){7646 /*globals wp, _ */7647 7648 /**7649 * wp.media.view.Toolbar.Embed7650 *7651 * @class7652 * @augments wp.media.view.Toolbar.Select7653 * @augments wp.media.view.Toolbar7654 * @augments wp.media.View7655 * @augments wp.Backbone.View7656 * @augments Backbone.View7657 */7658 var Select = wp.media.view.Toolbar.Select,7659 l10n = wp.media.view.l10n,7660 Embed;7661 7662 Embed = Select.extend({7663 initialize: function() {7664 _.defaults( this.options, {7665 text: l10n.insertIntoPost,7666 requires: false7667 });7668 // Call 'initialize' directly on the parent class.7669 Select.prototype.initialize.apply( this, arguments );7670 },7671 7672 refresh: function() {7673 var url = this.controller.state().props.get('url');7674 this.get('select').model.set( 'disabled', ! url || url === 'http://' );7675 /**7676 * call 'refresh' directly on the parent class7677 */7678 Select.prototype.refresh.apply( this, arguments );7679 }7680 });7681 7682 module.exports = Embed;7683 7684 },{}],65:[function(require,module,exports){7685 /*globals wp, _ */7686 7687 /**7688 * wp.media.view.Toolbar.Select7689 *7690 * @class7691 * @augments wp.media.view.Toolbar7692 * @augments wp.media.View7693 * @augments wp.Backbone.View7694 * @augments Backbone.View7695 */7696 var Toolbar = wp.media.view.Toolbar,7697 l10n = wp.media.view.l10n,7698 Select;7699 7700 Select = Toolbar.extend({7701 initialize: function() {7702 var options = this.options;7703 7704 _.bindAll( this, 'clickSelect' );7705 7706 _.defaults( options, {7707 event: 'select',7708 state: false,7709 reset: true,7710 close: true,7711 text: l10n.select,7712 7713 // Does the button rely on the selection?7714 requires: {7715 selection: true7716 }7717 });7718 7719 options.items = _.defaults( options.items || {}, {7720 select: {7721 style: 'primary',7722 text: options.text,7723 priority: 80,7724 click: this.clickSelect,7725 requires: options.requires7726 }7727 });7728 // Call 'initialize' directly on the parent class.7729 Toolbar.prototype.initialize.apply( this, arguments );7730 },7731 7732 clickSelect: function() {7733 var options = this.options,7734 controller = this.controller;7735 7736 if ( options.close ) {7737 controller.close();7738 }7739 7740 if ( options.event ) {7741 controller.state().trigger( options.event );7742 }7743 7744 if ( options.state ) {7745 controller.setState( options.state );7746 }7747 7748 if ( options.reset ) {7749 controller.reset();7750 }7751 }7752 });7753 7754 module.exports = Select;7755 7756 },{}],66:[function(require,module,exports){7757 /*globals wp, _, jQuery */7758 7759 /**7760 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap7761 * or #wp-fullscreen-body) and relays drag'n'dropped files to a media workflow.7762 *7763 * wp.media.view.EditorUploader7764 *7765 * @class7766 * @augments wp.media.View7767 * @augments wp.Backbone.View7768 * @augments Backbone.View7769 */7770 var View = wp.media.View,7771 l10n = wp.media.view.l10n,7772 $ = jQuery,7773 EditorUploader;7774 7775 EditorUploader = View.extend({7776 tagName: 'div',7777 className: 'uploader-editor',7778 template: wp.template( 'uploader-editor' ),7779 7780 localDrag: false,7781 overContainer: false,7782 overDropzone: false,7783 draggingFile: null,7784 7785 /**7786 * Bind drag'n'drop events to callbacks.7787 */7788 initialize: function() {7789 this.initialized = false;7790 7791 // Bail if not enabled or UA does not support drag'n'drop or File API.7792 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {7793 return this;7794 }7795 7796 this.$document = $(document);7797 this.dropzones = [];7798 this.files = [];7799 7800 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );7801 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );7802 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );7803 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );7804 7805 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );7806 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );7807 7808 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {7809 this.localDrag = event.type === 'dragstart';7810 }, this ) );7811 7812 this.initialized = true;7813 return this;7814 },7815 7816 /**7817 * Check browser support for drag'n'drop.7818 *7819 * @return Boolean7820 */7821 browserSupport: function() {7822 var supports = false, div = document.createElement('div');7823 7824 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );7825 supports = supports && !! ( window.File && window.FileList && window.FileReader );7826 return supports;7827 },7828 7829 isDraggingFile: function( event ) {7830 if ( this.draggingFile !== null ) {7831 return this.draggingFile;7832 }7833 7834 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {7835 return false;7836 }7837 7838 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&7839 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;7840 7841 return this.draggingFile;7842 },7843 7844 refresh: function( e ) {7845 var dropzone_id;7846 for ( dropzone_id in this.dropzones ) {7847 // Hide the dropzones only if dragging has left the screen.7848 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );7849 }7850 7851 if ( ! _.isUndefined( e ) ) {7852 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );7853 }7854 7855 if ( ! this.overContainer && ! this.overDropzone ) {7856 this.draggingFile = null;7857 }7858 7859 return this;7860 },7861 7862 render: function() {7863 if ( ! this.initialized ) {7864 return this;7865 }7866 7867 View.prototype.render.apply( this, arguments );7868 $( '.wp-editor-wrap, #wp-fullscreen-body' ).each( _.bind( this.attach, this ) );7869 return this;7870 },7871 7872 attach: function( index, editor ) {7873 // Attach a dropzone to an editor.7874 var dropzone = this.$el.clone();7875 this.dropzones.push( dropzone );7876 $( editor ).append( dropzone );7877 return this;7878 },7879 7880 /**7881 * When a file is dropped on the editor uploader, open up an editor media workflow7882 * and upload the file immediately.7883 *7884 * @param {jQuery.Event} event The 'drop' event.7885 */7886 drop: function( event ) {7887 var $wrap = null, uploadView;7888 7889 this.containerDragleave( event );7890 this.dropzoneDragleave( event );7891 7892 this.files = event.originalEvent.dataTransfer.files;7893 if ( this.files.length < 1 ) {7894 return;7895 }7896 7897 // Set the active editor to the drop target.7898 $wrap = $( event.target ).parents( '.wp-editor-wrap' );7899 if ( $wrap.length > 0 && $wrap[0].id ) {7900 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );7901 }7902 7903 if ( ! this.workflow ) {7904 this.workflow = wp.media.editor.open( 'content', {7905 frame: 'post',7906 state: 'insert',7907 title: l10n.addMedia,7908 multiple: true7909 });7910 uploadView = this.workflow.uploader;7911 if ( uploadView.uploader && uploadView.uploader.ready ) {7912 this.addFiles.apply( this );7913 } else {7914 this.workflow.on( 'uploader:ready', this.addFiles, this );7915 }7916 } else {7917 this.workflow.state().reset();7918 this.addFiles.apply( this );7919 this.workflow.open();7920 }7921 7922 return false;7923 },7924 7925 /**7926 * Add the files to the uploader.7927 */7928 addFiles: function() {7929 if ( this.files.length ) {7930 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );7931 this.files = [];7932 }7933 return this;7934 },7935 7936 containerDragover: function( event ) {7937 if ( this.localDrag || ! this.isDraggingFile( event ) ) {7938 return;7939 }7940 7941 this.overContainer = true;7942 this.refresh();7943 },7944 7945 containerDragleave: function() {7946 this.overContainer = false;7947 7948 // Throttle dragleave because it's called when bouncing from some elements to others.7949 _.delay( _.bind( this.refresh, this ), 50 );7950 },7951 7952 dropzoneDragover: function( event ) {7953 if ( this.localDrag || ! this.isDraggingFile( event ) ) {7954 return;7955 }7956 7957 this.overDropzone = true;7958 this.refresh( event );7959 return false;7960 },7961 7962 dropzoneDragleave: function( e ) {7963 this.overDropzone = false;7964 _.delay( _.bind( this.refresh, this, e ), 50 );7965 },7966 7967 click: function( e ) {7968 // In the rare case where the dropzone gets stuck, hide it on click.7969 this.containerDragleave( e );7970 this.dropzoneDragleave( e );7971 this.localDrag = false;7972 }7973 });7974 7975 module.exports = EditorUploader;7976 7977 },{}],67:[function(require,module,exports){7978 /*globals wp, _ */7979 7980 /**7981 * wp.media.view.UploaderInline7982 *7983 * The inline uploader that shows up in the 'Upload Files' tab.7984 *7985 * @class7986 * @augments wp.media.View7987 * @augments wp.Backbone.View7988 * @augments Backbone.View7989 */7990 var View = wp.media.View,7991 UploaderInline;7992 7993 UploaderInline = View.extend({7994 tagName: 'div',7995 className: 'uploader-inline',7996 template: wp.template('uploader-inline'),7997 7998 events: {7999 'click .close': 'hide'8000 },8001 8002 initialize: function() {8003 _.defaults( this.options, {8004 message: '',8005 status: true,8006 canClose: false8007 });8008 8009 if ( ! this.options.$browser && this.controller.uploader ) {8010 this.options.$browser = this.controller.uploader.$browser;8011 }8012 8013 if ( _.isUndefined( this.options.postId ) ) {8014 this.options.postId = wp.media.view.settings.post.id;8015 }8016 8017 if ( this.options.status ) {8018 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({8019 controller: this.controller8020 }) );8021 }8022 },8023 8024 prepare: function() {8025 var suggestedWidth = this.controller.state().get('suggestedWidth'),8026 suggestedHeight = this.controller.state().get('suggestedHeight'),8027 data = {};8028 8029 data.message = this.options.message;8030 data.canClose = this.options.canClose;8031 8032 if ( suggestedWidth && suggestedHeight ) {8033 data.suggestedWidth = suggestedWidth;8034 data.suggestedHeight = suggestedHeight;8035 }8036 8037 return data;8038 },8039 /**8040 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining8041 */8042 dispose: function() {8043 if ( this.disposing ) {8044 /**8045 * call 'dispose' directly on the parent class8046 */8047 return View.prototype.dispose.apply( this, arguments );8048 }8049 8050 // Run remove on `dispose`, so we can be sure to refresh the8051 // uploader with a view-less DOM. Track whether we're disposing8052 // so we don't trigger an infinite loop.8053 this.disposing = true;8054 return this.remove();8055 },8056 /**8057 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining8058 */8059 remove: function() {8060 /**8061 * call 'remove' directly on the parent class8062 */8063 var result = View.prototype.remove.apply( this, arguments );8064 8065 _.defer( _.bind( this.refresh, this ) );8066 return result;8067 },8068 8069 refresh: function() {8070 var uploader = this.controller.uploader;8071 8072 if ( uploader ) {8073 uploader.refresh();8074 }8075 },8076 /**8077 * @returns {wp.media.view.UploaderInline}8078 */8079 ready: function() {8080 var $browser = this.options.$browser,8081 $placeholder;8082 8083 if ( this.controller.uploader ) {8084 $placeholder = this.$('.browser');8085 8086 // Check if we've already replaced the placeholder.8087 if ( $placeholder[0] === $browser[0] ) {8088 return;8089 }8090 8091 $browser.detach().text( $placeholder.text() );8092 $browser[0].className = $placeholder[0].className;8093 $placeholder.replaceWith( $browser.show() );8094 }8095 8096 this.refresh();8097 return this;8098 },8099 show: function() {8100 this.$el.removeClass( 'hidden' );8101 },8102 hide: function() {8103 this.$el.addClass( 'hidden' );8104 }8105 8106 });8107 8108 module.exports = UploaderInline;8109 8110 },{}],68:[function(require,module,exports){8111 /*globals wp */8112 8113 /**8114 * wp.media.view.UploaderStatusError8115 *8116 * @class8117 * @augments wp.media.View8118 * @augments wp.Backbone.View8119 * @augments Backbone.View8120 */8121 var UploaderStatusError = wp.media.View.extend({8122 className: 'upload-error',8123 template: wp.template('uploader-status-error')8124 });8125 8126 module.exports = UploaderStatusError;8127 8128 },{}],69:[function(require,module,exports){8129 /*globals wp, _ */8130 8131 /**8132 * wp.media.view.UploaderStatus8133 *8134 * An uploader status for on-going uploads.8135 *8136 * @class8137 * @augments wp.media.View8138 * @augments wp.Backbone.View8139 * @augments Backbone.View8140 */8141 var View = wp.media.View,8142 UploaderStatus;8143 8144 UploaderStatus = View.extend({8145 className: 'media-uploader-status',8146 template: wp.template('uploader-status'),8147 8148 events: {8149 'click .upload-dismiss-errors': 'dismiss'8150 },8151 8152 initialize: function() {8153 this.queue = wp.Uploader.queue;8154 this.queue.on( 'add remove reset', this.visibility, this );8155 this.queue.on( 'add remove reset change:percent', this.progress, this );8156 this.queue.on( 'add remove reset change:uploading', this.info, this );8157 8158 this.errors = wp.Uploader.errors;8159 this.errors.reset();8160 this.errors.on( 'add remove reset', this.visibility, this );8161 this.errors.on( 'add', this.error, this );8162 },8163 /**8164 * @global wp.Uploader8165 * @returns {wp.media.view.UploaderStatus}8166 */8167 dispose: function() {8168 wp.Uploader.queue.off( null, null, this );8169 /**8170 * call 'dispose' directly on the parent class8171 */8172 View.prototype.dispose.apply( this, arguments );8173 return this;8174 },8175 8176 visibility: function() {8177 this.$el.toggleClass( 'uploading', !! this.queue.length );8178 this.$el.toggleClass( 'errors', !! this.errors.length );8179 this.$el.toggle( !! this.queue.length || !! this.errors.length );8180 },8181 8182 ready: function() {8183 _.each({8184 '$bar': '.media-progress-bar div',8185 '$index': '.upload-index',8186 '$total': '.upload-total',8187 '$filename': '.upload-filename'8188 }, function( selector, key ) {8189 this[ key ] = this.$( selector );8190 }, this );8191 8192 this.visibility();8193 this.progress();8194 this.info();8195 },8196 8197 progress: function() {8198 var queue = this.queue,8199 $bar = this.$bar;8200 8201 if ( ! $bar || ! queue.length ) {8202 return;8203 }8204 8205 $bar.width( ( queue.reduce( function( memo, attachment ) {8206 if ( ! attachment.get('uploading') ) {8207 return memo + 100;8208 }8209 8210 var percent = attachment.get('percent');8211 return memo + ( _.isNumber( percent ) ? percent : 100 );8212 }, 0 ) / queue.length ) + '%' );8213 },8214 8215 info: function() {8216 var queue = this.queue,8217 index = 0, active;8218 8219 if ( ! queue.length ) {8220 return;8221 }8222 8223 active = this.queue.find( function( attachment, i ) {8224 index = i;8225 return attachment.get('uploading');8226 });8227 8228 this.$index.text( index + 1 );8229 this.$total.text( queue.length );8230 this.$filename.html( active ? this.filename( active.get('filename') ) : '' );8231 },8232 /**8233 * @param {string} filename8234 * @returns {string}8235 */8236 filename: function( filename ) {8237 return wp.media.truncate( _.escape( filename ), 24 );8238 },8239 /**8240 * @param {Backbone.Model} error8241 */8242 error: function( error ) {8243 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({8244 filename: this.filename( error.get('file').name ),8245 message: error.get('message')8246 }), { at: 0 });8247 },8248 8249 /**8250 * @global wp.Uploader8251 *8252 * @param {Object} event8253 */8254 dismiss: function( event ) {8255 var errors = this.views.get('.upload-errors');8256 8257 event.preventDefault();8258 8259 if ( errors ) {8260 _.invoke( errors, 'remove' );8261 }8262 wp.Uploader.errors.reset();8263 }8264 });8265 8266 module.exports = UploaderStatus;8267 8268 },{}],70:[function(require,module,exports){8269 /*globals wp, _, jQuery */8270 8271 /**8272 * wp.media.view.UploaderWindow8273 *8274 * An uploader window that allows for dragging and dropping media.8275 *8276 * @class8277 * @augments wp.media.View8278 * @augments wp.Backbone.View8279 * @augments Backbone.View8280 *8281 * @param {object} [options] Options hash passed to the view.8282 * @param {object} [options.uploader] Uploader properties.8283 * @param {jQuery} [options.uploader.browser]8284 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.8285 * @param {object} [options.uploader.params]8286 */8287 var $ = jQuery,8288 UploaderWindow;8289 8290 UploaderWindow = wp.media.View.extend({8291 tagName: 'div',8292 className: 'uploader-window',8293 template: wp.template('uploader-window'),8294 8295 initialize: function() {8296 var uploader;8297 8298 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');8299 8300 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {8301 dropzone: this.$el,8302 browser: this.$browser,8303 params: {}8304 });8305 8306 // Ensure the dropzone is a jQuery collection.8307 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {8308 uploader.dropzone = $( uploader.dropzone );8309 }8310 8311 this.controller.on( 'activate', this.refresh, this );8312 8313 this.controller.on( 'detach', function() {8314 this.$browser.remove();8315 }, this );8316 },8317 8318 refresh: function() {8319 if ( this.uploader ) {8320 this.uploader.refresh();8321 }8322 },8323 8324 ready: function() {8325 var postId = wp.media.view.settings.post.id,8326 dropzone;8327 8328 // If the uploader already exists, bail.8329 if ( this.uploader ) {8330 return;8331 }8332 8333 if ( postId ) {8334 this.options.uploader.params.post_id = postId;8335 }8336 this.uploader = new wp.Uploader( this.options.uploader );8337 8338 dropzone = this.uploader.dropzone;8339 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );8340 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );8341 8342 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );8343 },8344 8345 _ready: function() {8346 this.controller.trigger( 'uploader:ready' );8347 },8348 8349 show: function() {8350 var $el = this.$el.show();8351 8352 // Ensure that the animation is triggered by waiting until8353 // the transparent element is painted into the DOM.8354 _.defer( function() {8355 $el.css({ opacity: 1 });8356 });8357 },8358 8359 hide: function() {8360 var $el = this.$el.css({ opacity: 0 });8361 8362 wp.media.transition( $el ).done( function() {8363 // Transition end events are subject to race conditions.8364 // Make sure that the value is set as intended.8365 if ( '0' === $el.css('opacity') ) {8366 $el.hide();8367 }8368 });8369 8370 // https://core.trac.wordpress.org/ticket/273418371 _.delay( function() {8372 if ( '0' === $el.css('opacity') && $el.is(':visible') ) {8373 $el.hide();8374 }8375 }, 500 );8376 }8377 });8378 8379 module.exports = UploaderWindow;8380 8381 },{}],71:[function(require,module,exports){8382 /*globals wp */8383 8384 /**8385 * wp.media.View8386 *8387 * The base view class for media.8388 *8389 * Undelegating events, removing events from the model, and8390 * removing events from the controller mirror the code for8391 * `Backbone.View.dispose` in Backbone 0.9.8 development.8392 *8393 * This behavior has since been removed, and should not be used8394 * outside of the media manager.8395 *8396 * @class8397 * @augments wp.Backbone.View8398 * @augments Backbone.View8399 */8400 var View = wp.Backbone.View.extend({8401 constructor: function( options ) {8402 if ( options && options.controller ) {8403 this.controller = options.controller;8404 }8405 wp.Backbone.View.apply( this, arguments );8406 },8407 /**8408 * @todo The internal comment mentions this might have been a stop-gap8409 * before Backbone 0.9.8 came out. Figure out if Backbone core takes8410 * care of this in Backbone.View now.8411 *8412 * @returns {wp.media.View} Returns itself to allow chaining8413 */8414 dispose: function() {8415 // Undelegating events, removing events from the model, and8416 // removing events from the controller mirror the code for8417 // `Backbone.View.dispose` in Backbone 0.9.8 development.8418 this.undelegateEvents();8419 8420 if ( this.model && this.model.off ) {8421 this.model.off( null, null, this );8422 }8423 8424 if ( this.collection && this.collection.off ) {8425 this.collection.off( null, null, this );8426 }8427 8428 // Unbind controller events.8429 if ( this.controller && this.controller.off ) {8430 this.controller.off( null, null, this );8431 }8432 8433 return this;8434 },8435 /**8436 * @returns {wp.media.View} Returns itself to allow chaining8437 */8438 remove: function() {8439 this.dispose();8440 /**8441 * call 'remove' directly on the parent class8442 */8443 return wp.Backbone.View.prototype.remove.apply( this, arguments );8444 }8445 });8446 8447 module.exports = View;8448 8449 },{}]},{},[17]); -
src/wp-includes/js/media-audiovideo.js
1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 2 /*globals wp, _ */ 3 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-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=…] 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 || '…'; 182 183 if ( string.length <= length ) { 184 return string; 185 } 186 187 return string.substr( 0, length / 2 ) + replacement + string.substr( -1 * length / 2 ); 188 } 189 }); 190 191 /** 192 * ======================================================================== 193 * MODELS 194 * ======================================================================== 195 */ 196 /** 197 * wp.media.attachment 198 * 199 * @static 200 * @param {String} id A string used to identify a model. 201 * @returns {wp.media.model.Attachment} 202 */ 203 media.attachment = function( id ) { 204 return Attachment.get( id ); 205 }; 206 207 /** 208 * A collection of all attachments that have been fetched from the server. 209 * 210 * @static 211 * @member {wp.media.model.Attachments} 212 */ 213 Attachments.all = new Attachments(); 214 215 /** 216 * wp.media.query 217 * 218 * Shorthand for creating a new Attachments Query. 219 * 220 * @param {object} [props] 221 * @returns {wp.media.model.Attachments} 222 */ 223 media.query = function( props ) { 224 return new Attachments( null, { 225 props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } ) 226 }); 227 }; 228 229 // Clean up. Prevents mobile browsers caching 230 $(window).on('unload', function(){ 231 window.wp = null; 232 }); 233 234 },{"./models/attachment.js":2,"./models/attachments.js":3,"./models/post-image.js":4,"./models/query.js":5,"./models/selection.js":6}],2:[function(require,module,exports){ 235 /*globals wp, _, Backbone */ 236 237 /** 238 * wp.media.model.Attachment 239 * 240 * @class 241 * @augments Backbone.Model 242 */ 243 var $ = Backbone.$, 244 Attachment; 245 246 Attachment = Backbone.Model.extend({ 247 /** 248 * Triggered when attachment details change 249 * Overrides Backbone.Model.sync 250 * 251 * @param {string} method 252 * @param {wp.media.model.Attachment} model 253 * @param {Object} [options={}] 254 * 255 * @returns {Promise} 256 */ 257 sync: function( method, model, options ) { 258 // If the attachment does not yet have an `id`, return an instantly 259 // rejected promise. Otherwise, all of our requests will fail. 260 if ( _.isUndefined( this.id ) ) { 261 return $.Deferred().rejectWith( this ).promise(); 262 } 263 264 // Overload the `read` request so Attachment.fetch() functions correctly. 265 if ( 'read' === method ) { 266 options = options || {}; 267 options.context = this; 268 options.data = _.extend( options.data || {}, { 269 action: 'get-attachment', 270 id: this.id 271 }); 272 return wp.media.ajax( options ); 273 274 // Overload the `update` request so properties can be saved. 275 } else if ( 'update' === method ) { 276 // If we do not have the necessary nonce, fail immeditately. 277 if ( ! this.get('nonces') || ! this.get('nonces').update ) { 278 return $.Deferred().rejectWith( this ).promise(); 279 } 280 281 options = options || {}; 282 options.context = this; 283 284 // Set the action and ID. 285 options.data = _.extend( options.data || {}, { 286 action: 'save-attachment', 287 id: this.id, 288 nonce: this.get('nonces').update, 289 post_id: wp.media.model.settings.post.id 290 }); 291 292 // Record the values of the changed attributes. 293 if ( model.hasChanged() ) { 294 options.data.changes = {}; 295 296 _.each( model.changed, function( value, key ) { 297 options.data.changes[ key ] = this.get( key ); 298 }, this ); 299 } 300 301 return wp.media.ajax( options ); 302 303 // Overload the `delete` request so attachments can be removed. 304 // This will permanently delete an attachment. 305 } else if ( 'delete' === method ) { 306 options = options || {}; 307 308 if ( ! options.wait ) { 309 this.destroyed = true; 310 } 311 312 options.context = this; 313 options.data = _.extend( options.data || {}, { 314 action: 'delete-post', 315 id: this.id, 316 _wpnonce: this.get('nonces')['delete'] 317 }); 318 319 return wp.media.ajax( options ).done( function() { 320 this.destroyed = true; 321 }).fail( function() { 322 this.destroyed = false; 323 }); 324 325 // Otherwise, fall back to `Backbone.sync()`. 326 } else { 327 /** 328 * Call `sync` directly on Backbone.Model 329 */ 330 return Backbone.Model.prototype.sync.apply( this, arguments ); 331 } 332 }, 333 /** 334 * Convert date strings into Date objects. 335 * 336 * @param {Object} resp The raw response object, typically returned by fetch() 337 * @returns {Object} The modified response object, which is the attributes hash 338 * to be set on the model. 339 */ 340 parse: function( resp ) { 341 if ( ! resp ) { 342 return resp; 343 } 344 345 resp.date = new Date( resp.date ); 346 resp.modified = new Date( resp.modified ); 347 return resp; 348 }, 349 /** 350 * @param {Object} data The properties to be saved. 351 * @param {Object} options Sync options. e.g. patch, wait, success, error. 352 * 353 * @this Backbone.Model 354 * 355 * @returns {Promise} 356 */ 357 saveCompat: function( data, options ) { 358 var model = this; 359 360 // If we do not have the necessary nonce, fail immeditately. 361 if ( ! this.get('nonces') || ! this.get('nonces').update ) { 362 return $.Deferred().rejectWith( this ).promise(); 363 } 364 365 return wp.media.post( 'save-attachment-compat', _.defaults({ 366 id: this.id, 367 nonce: this.get('nonces').update, 368 post_id: wp.media.model.settings.post.id 369 }, data ) ).done( function( resp, status, xhr ) { 370 model.set( model.parse( resp, xhr ), options ); 371 }); 372 } 373 }, { 374 /** 375 * Create a new model on the static 'all' attachments collection and return it. 376 * 377 * @static 378 * @param {Object} attrs 379 * @returns {wp.media.model.Attachment} 380 */ 381 create: function( attrs ) { 382 var Attachments = wp.media.model.Attachments; 383 return Attachments.all.push( attrs ); 384 }, 385 /** 386 * Create a new model on the static 'all' attachments collection and return it. 387 * 388 * If this function has already been called for the id, 389 * it returns the specified attachment. 390 * 391 * @static 392 * @param {string} id A string used to identify a model. 393 * @param {Backbone.Model|undefined} attachment 394 * @returns {wp.media.model.Attachment} 395 */ 396 get: _.memoize( function( id, attachment ) { 397 var Attachments = wp.media.model.Attachments; 398 return Attachments.all.push( attachment || { id: id } ); 399 }) 400 }); 401 402 module.exports = Attachment; 403 404 },{}],3:[function(require,module,exports){ 405 /*globals wp, _, Backbone */ 406 407 /** 408 * wp.media.model.Attachments 409 * 410 * A collection of attachments. 411 * 412 * This collection has no persistence with the server without supplying 413 * 'options.props.query = true', which will mirror the collection 414 * to an Attachments Query collection - @see wp.media.model.Attachments.mirror(). 415 * 416 * @class 417 * @augments Backbone.Collection 418 * 419 * @param {array} [models] Models to initialize with the collection. 420 * @param {object} [options] Options hash for the collection. 421 * @param {string} [options.props] Options hash for the initial query properties. 422 * @param {string} [options.props.order] Initial order (ASC or DESC) for the collection. 423 * @param {string} [options.props.orderby] Initial attribute key to order the collection by. 424 * @param {string} [options.props.query] Whether the collection is linked to an attachments query. 425 * @param {string} [options.observe] 426 * @param {string} [options.filters] 427 * 428 */ 429 var Attachments = Backbone.Collection.extend({ 430 /** 431 * @type {wp.media.model.Attachment} 432 */ 433 model: wp.media.model.Attachment, 434 /** 435 * @param {Array} [models=[]] Array of models used to populate the collection. 436 * @param {Object} [options={}] 437 */ 438 initialize: function( models, options ) { 439 options = options || {}; 440 441 this.props = new Backbone.Model(); 442 this.filters = options.filters || {}; 443 444 // Bind default `change` events to the `props` model. 445 this.props.on( 'change', this._changeFilteredProps, this ); 446 447 this.props.on( 'change:order', this._changeOrder, this ); 448 this.props.on( 'change:orderby', this._changeOrderby, this ); 449 this.props.on( 'change:query', this._changeQuery, this ); 450 451 this.props.set( _.defaults( options.props || {} ) ); 452 453 if ( options.observe ) { 454 this.observe( options.observe ); 455 } 456 }, 457 /** 458 * Sort the collection when the order attribute changes. 459 * 460 * @access private 461 */ 462 _changeOrder: function() { 463 if ( this.comparator ) { 464 this.sort(); 465 } 466 }, 467 /** 468 * Set the default comparator only when the `orderby` property is set. 469 * 470 * @access private 471 * 472 * @param {Backbone.Model} model 473 * @param {string} orderby 474 */ 475 _changeOrderby: function( model, orderby ) { 476 // If a different comparator is defined, bail. 477 if ( this.comparator && this.comparator !== Attachments.comparator ) { 478 return; 479 } 480 481 if ( orderby && 'post__in' !== orderby ) { 482 this.comparator = Attachments.comparator; 483 } else { 484 delete this.comparator; 485 } 486 }, 487 /** 488 * If the `query` property is set to true, query the server using 489 * the `props` values, and sync the results to this collection. 490 * 491 * @access private 492 * 493 * @param {Backbone.Model} model 494 * @param {Boolean} query 495 */ 496 _changeQuery: function( model, query ) { 497 if ( query ) { 498 this.props.on( 'change', this._requery, this ); 499 this._requery(); 500 } else { 501 this.props.off( 'change', this._requery, this ); 502 } 503 }, 504 /** 505 * @access private 506 * 507 * @param {Backbone.Model} model 508 */ 509 _changeFilteredProps: function( model ) { 510 // If this is a query, updating the collection will be handled by 511 // `this._requery()`. 512 if ( this.props.get('query') ) { 513 return; 514 } 515 516 var changed = _.chain( model.changed ).map( function( t, prop ) { 517 var filter = Attachments.filters[ prop ], 518 term = model.get( prop ); 519 520 if ( ! filter ) { 521 return; 522 } 523 524 if ( term && ! this.filters[ prop ] ) { 525 this.filters[ prop ] = filter; 526 } else if ( ! term && this.filters[ prop ] === filter ) { 527 delete this.filters[ prop ]; 528 } else { 529 return; 530 } 531 532 // Record the change. 533 return true; 534 }, this ).any().value(); 535 536 if ( ! changed ) { 537 return; 538 } 539 540 // If no `Attachments` model is provided to source the searches 541 // from, then automatically generate a source from the existing 542 // models. 543 if ( ! this._source ) { 544 this._source = new Attachments( this.models ); 545 } 546 547 this.reset( this._source.filter( this.validator, this ) ); 548 }, 549 550 validateDestroyed: false, 551 /** 552 * Checks whether an attachment is valid. 553 * 554 * @param {wp.media.model.Attachment} attachment 555 * @returns {Boolean} 556 */ 557 validator: function( attachment ) { 558 if ( ! this.validateDestroyed && attachment.destroyed ) { 559 return false; 560 } 561 return _.all( this.filters, function( filter ) { 562 return !! filter.call( this, attachment ); 563 }, this ); 564 }, 565 /** 566 * Add or remove an attachment to the collection depending on its validity. 567 * 568 * @param {wp.media.model.Attachment} attachment 569 * @param {Object} options 570 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 571 */ 572 validate: function( attachment, options ) { 573 var valid = this.validator( attachment ), 574 hasAttachment = !! this.get( attachment.cid ); 575 576 if ( ! valid && hasAttachment ) { 577 this.remove( attachment, options ); 578 } else if ( valid && ! hasAttachment ) { 579 this.add( attachment, options ); 580 } 581 582 return this; 583 }, 584 585 /** 586 * Add or remove all attachments from another collection depending on each one's validity. 587 * 588 * @param {wp.media.model.Attachments} attachments 589 * @param {object} [options={}] 590 * 591 * @fires wp.media.model.Attachments#reset 592 * 593 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 594 */ 595 validateAll: function( attachments, options ) { 596 options = options || {}; 597 598 _.each( attachments.models, function( attachment ) { 599 this.validate( attachment, { silent: true }); 600 }, this ); 601 602 if ( ! options.silent ) { 603 this.trigger( 'reset', this, options ); 604 } 605 return this; 606 }, 607 /** 608 * Start observing another attachments collection change events 609 * and replicate them on this collection. 610 * 611 * @param {wp.media.model.Attachments} The attachments collection to observe. 612 * @returns {wp.media.model.Attachments} Returns itself to allow chaining. 613 */ 614 observe: function( attachments ) { 615 this.observers = this.observers || []; 616 this.observers.push( attachments ); 617 618 attachments.on( 'add change remove', this._validateHandler, this ); 619 attachments.on( 'reset', this._validateAllHandler, this ); 620 this.validateAll( attachments ); 621 return this; 622 }, 623 /** 624 * Stop replicating collection change events from another attachments collection. 625 * 626 * @param {wp.media.model.Attachments} The attachments collection to stop observing. 627 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 628 */ 629 unobserve: function( attachments ) { 630 if ( attachments ) { 631 attachments.off( null, null, this ); 632 this.observers = _.without( this.observers, attachments ); 633 634 } else { 635 _.each( this.observers, function( attachments ) { 636 attachments.off( null, null, this ); 637 }, this ); 638 delete this.observers; 639 } 640 641 return this; 642 }, 643 /** 644 * @access private 645 * 646 * @param {wp.media.model.Attachments} attachment 647 * @param {wp.media.model.Attachments} attachments 648 * @param {Object} options 649 * 650 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 651 */ 652 _validateHandler: function( attachment, attachments, options ) { 653 // If we're not mirroring this `attachments` collection, 654 // only retain the `silent` option. 655 options = attachments === this.mirroring ? options : { 656 silent: options && options.silent 657 }; 658 659 return this.validate( attachment, options ); 660 }, 661 /** 662 * @access private 663 * 664 * @param {wp.media.model.Attachments} attachments 665 * @param {Object} options 666 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 667 */ 668 _validateAllHandler: function( attachments, options ) { 669 return this.validateAll( attachments, options ); 670 }, 671 /** 672 * Start mirroring another attachments collection, clearing out any models already 673 * in the collection. 674 * 675 * @param {wp.media.model.Attachments} The attachments collection to mirror. 676 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 677 */ 678 mirror: function( attachments ) { 679 if ( this.mirroring && this.mirroring === attachments ) { 680 return this; 681 } 682 683 this.unmirror(); 684 this.mirroring = attachments; 685 686 // Clear the collection silently. A `reset` event will be fired 687 // when `observe()` calls `validateAll()`. 688 this.reset( [], { silent: true } ); 689 this.observe( attachments ); 690 691 return this; 692 }, 693 /** 694 * Stop mirroring another attachments collection. 695 */ 696 unmirror: function() { 697 if ( ! this.mirroring ) { 698 return; 699 } 700 701 this.unobserve( this.mirroring ); 702 delete this.mirroring; 703 }, 704 /** 705 * Retrive more attachments from the server for the collection. 706 * 707 * Only works if the collection is mirroring a Query Attachments collection, 708 * and forwards to its `more` method. This collection class doesn't have 709 * server persistence by itself. 710 * 711 * @param {object} options 712 * @returns {Promise} 713 */ 714 more: function( options ) { 715 var deferred = jQuery.Deferred(), 716 mirroring = this.mirroring, 717 attachments = this; 718 719 if ( ! mirroring || ! mirroring.more ) { 720 return deferred.resolveWith( this ).promise(); 721 } 722 // If we're mirroring another collection, forward `more` to 723 // the mirrored collection. Account for a race condition by 724 // checking if we're still mirroring that collection when 725 // the request resolves. 726 mirroring.more( options ).done( function() { 727 if ( this === attachments.mirroring ) { 728 deferred.resolveWith( this ); 729 } 730 }); 731 732 return deferred.promise(); 733 }, 734 /** 735 * Whether there are more attachments that haven't been sync'd from the server 736 * that match the collection's query. 737 * 738 * Only works if the collection is mirroring a Query Attachments collection, 739 * and forwards to its `hasMore` method. This collection class doesn't have 740 * server persistence by itself. 741 * 742 * @returns {boolean} 743 */ 744 hasMore: function() { 745 return this.mirroring ? this.mirroring.hasMore() : false; 746 }, 747 /** 748 * A custom AJAX-response parser. 749 * 750 * See trac ticket #24753 751 * 752 * @param {Object|Array} resp The raw response Object/Array. 753 * @param {Object} xhr 754 * @returns {Array} The array of model attributes to be added to the collection 755 */ 756 parse: function( resp, xhr ) { 757 if ( ! _.isArray( resp ) ) { 758 resp = [resp]; 759 } 760 761 return _.map( resp, function( attrs ) { 762 var id, attachment, newAttributes; 763 764 if ( attrs instanceof Backbone.Model ) { 765 id = attrs.get( 'id' ); 766 attrs = attrs.attributes; 767 } else { 768 id = attrs.id; 769 } 770 771 attachment = wp.media.model.Attachment.get( id ); 772 newAttributes = attachment.parse( attrs, xhr ); 773 774 if ( ! _.isEqual( attachment.attributes, newAttributes ) ) { 775 attachment.set( newAttributes ); 776 } 777 778 return attachment; 779 }); 780 }, 781 /** 782 * If the collection is a query, create and mirror an Attachments Query collection. 783 * 784 * @access private 785 */ 786 _requery: function( refresh ) { 787 var props; 788 if ( this.props.get('query') ) { 789 props = this.props.toJSON(); 790 props.cache = ( true !== refresh ); 791 this.mirror( wp.media.model.Query.get( props ) ); 792 } 793 }, 794 /** 795 * If this collection is sorted by `menuOrder`, recalculates and saves 796 * the menu order to the database. 797 * 798 * @returns {undefined|Promise} 799 */ 800 saveMenuOrder: function() { 801 if ( 'menuOrder' !== this.props.get('orderby') ) { 802 return; 803 } 804 805 // Removes any uploading attachments, updates each attachment's 806 // menu order, and returns an object with an { id: menuOrder } 807 // mapping to pass to the request. 808 var attachments = this.chain().filter( function( attachment ) { 809 return ! _.isUndefined( attachment.id ); 810 }).map( function( attachment, index ) { 811 // Indices start at 1. 812 index = index + 1; 813 attachment.set( 'menuOrder', index ); 814 return [ attachment.id, index ]; 815 }).object().value(); 816 817 if ( _.isEmpty( attachments ) ) { 818 return; 819 } 820 821 return wp.media.post( 'save-attachment-order', { 822 nonce: wp.media.model.settings.post.nonce, 823 post_id: wp.media.model.settings.post.id, 824 attachments: attachments 825 }); 826 } 827 }, { 828 /** 829 * A function to compare two attachment models in an attachments collection. 830 * 831 * Used as the default comparator for instances of wp.media.model.Attachments 832 * and its subclasses. @see wp.media.model.Attachments._changeOrderby(). 833 * 834 * @static 835 * 836 * @param {Backbone.Model} a 837 * @param {Backbone.Model} b 838 * @param {Object} options 839 * @returns {Number} -1 if the first model should come before the second, 840 * 0 if they are of the same rank and 841 * 1 if the first model should come after. 842 */ 843 comparator: function( a, b, options ) { 844 var key = this.props.get('orderby'), 845 order = this.props.get('order') || 'DESC', 846 ac = a.cid, 847 bc = b.cid; 848 849 a = a.get( key ); 850 b = b.get( key ); 851 852 if ( 'date' === key || 'modified' === key ) { 853 a = a || new Date(); 854 b = b || new Date(); 855 } 856 857 // If `options.ties` is set, don't enforce the `cid` tiebreaker. 858 if ( options && options.ties ) { 859 ac = bc = null; 860 } 861 862 return ( 'DESC' === order ) ? wp.media.compare( a, b, ac, bc ) : wp.media.compare( b, a, bc, ac ); 863 }, 864 /** 865 * @namespace 866 */ 867 filters: { 868 /** 869 * @static 870 * Note that this client-side searching is *not* equivalent 871 * to our server-side searching. 872 * 873 * @param {wp.media.model.Attachment} attachment 874 * 875 * @this wp.media.model.Attachments 876 * 877 * @returns {Boolean} 878 */ 879 search: function( attachment ) { 880 if ( ! this.props.get('search') ) { 881 return true; 882 } 883 884 return _.any(['title','filename','description','caption','name'], function( key ) { 885 var value = attachment.get( key ); 886 return value && -1 !== value.search( this.props.get('search') ); 887 }, this ); 888 }, 889 /** 890 * @static 891 * @param {wp.media.model.Attachment} attachment 892 * 893 * @this wp.media.model.Attachments 894 * 895 * @returns {Boolean} 896 */ 897 type: function( attachment ) { 898 var type = this.props.get('type'); 899 return ! type || -1 !== type.indexOf( attachment.get('type') ); 900 }, 901 /** 902 * @static 903 * @param {wp.media.model.Attachment} attachment 904 * 905 * @this wp.media.model.Attachments 906 * 907 * @returns {Boolean} 908 */ 909 uploadedTo: function( attachment ) { 910 var uploadedTo = this.props.get('uploadedTo'); 911 if ( _.isUndefined( uploadedTo ) ) { 912 return true; 913 } 914 915 return uploadedTo === attachment.get('uploadedTo'); 916 }, 917 /** 918 * @static 919 * @param {wp.media.model.Attachment} attachment 920 * 921 * @this wp.media.model.Attachments 922 * 923 * @returns {Boolean} 924 */ 925 status: function( attachment ) { 926 var status = this.props.get('status'); 927 if ( _.isUndefined( status ) ) { 928 return true; 929 } 930 931 return status === attachment.get('status'); 932 } 933 } 934 }); 935 936 module.exports = Attachments; 937 938 },{}],4:[function(require,module,exports){ 939 /*globals Backbone */ 940 941 /** 942 * wp.media.model.PostImage 943 * 944 * An instance of an image that's been embedded into a post. 945 * 946 * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails. 947 * 948 * @class 949 * @augments Backbone.Model 950 * 951 * @param {int} [attributes] Initial model attributes. 952 * @param {int} [attributes.attachment_id] ID of the attachment. 953 **/ 954 var PostImage = Backbone.Model.extend({ 955 956 initialize: function( attributes ) { 957 var Attachment = wp.media.model.Attachment; 958 this.attachment = false; 959 960 if ( attributes.attachment_id ) { 961 this.attachment = Attachment.get( attributes.attachment_id ); 962 if ( this.attachment.get( 'url' ) ) { 963 this.dfd = jQuery.Deferred(); 964 this.dfd.resolve(); 965 } else { 966 this.dfd = this.attachment.fetch(); 967 } 968 this.bindAttachmentListeners(); 969 } 970 971 // keep url in sync with changes to the type of link 972 this.on( 'change:link', this.updateLinkUrl, this ); 973 this.on( 'change:size', this.updateSize, this ); 974 975 this.setLinkTypeFromUrl(); 976 this.setAspectRatio(); 977 978 this.set( 'originalUrl', attributes.url ); 979 }, 980 981 bindAttachmentListeners: function() { 982 this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl ); 983 this.listenTo( this.attachment, 'sync', this.setAspectRatio ); 984 this.listenTo( this.attachment, 'change', this.updateSize ); 985 }, 986 987 changeAttachment: function( attachment, props ) { 988 this.stopListening( this.attachment ); 989 this.attachment = attachment; 990 this.bindAttachmentListeners(); 991 992 this.set( 'attachment_id', this.attachment.get( 'id' ) ); 993 this.set( 'caption', this.attachment.get( 'caption' ) ); 994 this.set( 'alt', this.attachment.get( 'alt' ) ); 995 this.set( 'size', props.get( 'size' ) ); 996 this.set( 'align', props.get( 'align' ) ); 997 this.set( 'link', props.get( 'link' ) ); 998 this.updateLinkUrl(); 999 this.updateSize(); 1000 }, 1001 1002 setLinkTypeFromUrl: function() { 1003 var linkUrl = this.get( 'linkUrl' ), 1004 type; 1005 1006 if ( ! linkUrl ) { 1007 this.set( 'link', 'none' ); 1008 return; 1009 } 1010 1011 // default to custom if there is a linkUrl 1012 type = 'custom'; 1013 1014 if ( this.attachment ) { 1015 if ( this.attachment.get( 'url' ) === linkUrl ) { 1016 type = 'file'; 1017 } else if ( this.attachment.get( 'link' ) === linkUrl ) { 1018 type = 'post'; 1019 } 1020 } else { 1021 if ( this.get( 'url' ) === linkUrl ) { 1022 type = 'file'; 1023 } 1024 } 1025 1026 this.set( 'link', type ); 1027 }, 1028 1029 updateLinkUrl: function() { 1030 var link = this.get( 'link' ), 1031 url; 1032 1033 switch( link ) { 1034 case 'file': 1035 if ( this.attachment ) { 1036 url = this.attachment.get( 'url' ); 1037 } else { 1038 url = this.get( 'url' ); 1039 } 1040 this.set( 'linkUrl', url ); 1041 break; 1042 case 'post': 1043 this.set( 'linkUrl', this.attachment.get( 'link' ) ); 1044 break; 1045 case 'none': 1046 this.set( 'linkUrl', '' ); 1047 break; 1048 } 1049 }, 1050 1051 updateSize: function() { 1052 var size; 1053 1054 if ( ! this.attachment ) { 1055 return; 1056 } 1057 1058 if ( this.get( 'size' ) === 'custom' ) { 1059 this.set( 'width', this.get( 'customWidth' ) ); 1060 this.set( 'height', this.get( 'customHeight' ) ); 1061 this.set( 'url', this.get( 'originalUrl' ) ); 1062 return; 1063 } 1064 1065 size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ]; 1066 1067 if ( ! size ) { 1068 return; 1069 } 1070 1071 this.set( 'url', size.url ); 1072 this.set( 'width', size.width ); 1073 this.set( 'height', size.height ); 1074 }, 1075 1076 setAspectRatio: function() { 1077 var full; 1078 1079 if ( this.attachment && this.attachment.get( 'sizes' ) ) { 1080 full = this.attachment.get( 'sizes' ).full; 1081 1082 if ( full ) { 1083 this.set( 'aspectRatio', full.width / full.height ); 1084 return; 1085 } 1086 } 1087 1088 this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) ); 1089 } 1090 }); 1091 1092 module.exports = PostImage; 1093 1094 },{}],5:[function(require,module,exports){ 1095 /*globals wp, _ */ 1096 1097 /** 1098 * wp.media.model.Query 1099 * 1100 * A collection of attachments that match the supplied query arguments. 1101 * 1102 * Note: Do NOT change this.args after the query has been initialized. 1103 * Things will break. 1104 * 1105 * @class 1106 * @augments wp.media.model.Attachments 1107 * @augments Backbone.Collection 1108 * 1109 * @param {array} [models] Models to initialize with the collection. 1110 * @param {object} [options] Options hash. 1111 * @param {object} [options.args] Attachments query arguments. 1112 * @param {object} [options.args.posts_per_page] 1113 */ 1114 var Attachments = wp.media.model.Attachments, 1115 Query; 1116 1117 Query = Attachments.extend({ 1118 /** 1119 * @global wp.Uploader 1120 * 1121 * @param {array} [models=[]] Array of initial models to populate the collection. 1122 * @param {object} [options={}] 1123 */ 1124 initialize: function( models, options ) { 1125 var allowed; 1126 1127 options = options || {}; 1128 Attachments.prototype.initialize.apply( this, arguments ); 1129 1130 this.args = options.args; 1131 this._hasMore = true; 1132 this.created = new Date(); 1133 1134 this.filters.order = function( attachment ) { 1135 var orderby = this.props.get('orderby'), 1136 order = this.props.get('order'); 1137 1138 if ( ! this.comparator ) { 1139 return true; 1140 } 1141 1142 // We want any items that can be placed before the last 1143 // item in the set. If we add any items after the last 1144 // item, then we can't guarantee the set is complete. 1145 if ( this.length ) { 1146 return 1 !== this.comparator( attachment, this.last(), { ties: true }); 1147 1148 // Handle the case where there are no items yet and 1149 // we're sorting for recent items. In that case, we want 1150 // changes that occurred after we created the query. 1151 } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) { 1152 return attachment.get( orderby ) >= this.created; 1153 1154 // If we're sorting by menu order and we have no items, 1155 // accept any items that have the default menu order (0). 1156 } else if ( 'ASC' === order && 'menuOrder' === orderby ) { 1157 return attachment.get( orderby ) === 0; 1158 } 1159 1160 // Otherwise, we don't want any items yet. 1161 return false; 1162 }; 1163 1164 // Observe the central `wp.Uploader.queue` collection to watch for 1165 // new matches for the query. 1166 // 1167 // Only observe when a limited number of query args are set. There 1168 // are no filters for other properties, so observing will result in 1169 // false positives in those queries. 1170 allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ]; 1171 if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) { 1172 this.observe( wp.Uploader.queue ); 1173 } 1174 }, 1175 /** 1176 * Whether there are more attachments that haven't been sync'd from the server 1177 * that match the collection's query. 1178 * 1179 * @returns {boolean} 1180 */ 1181 hasMore: function() { 1182 return this._hasMore; 1183 }, 1184 /** 1185 * Fetch more attachments from the server for the collection. 1186 * 1187 * @param {object} [options={}] 1188 * @returns {Promise} 1189 */ 1190 more: function( options ) { 1191 var query = this; 1192 1193 // If there is already a request pending, return early with the Deferred object. 1194 if ( this._more && 'pending' === this._more.state() ) { 1195 return this._more; 1196 } 1197 1198 if ( ! this.hasMore() ) { 1199 return jQuery.Deferred().resolveWith( this ).promise(); 1200 } 1201 1202 options = options || {}; 1203 options.remove = false; 1204 1205 return this._more = this.fetch( options ).done( function( resp ) { 1206 if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) { 1207 query._hasMore = false; 1208 } 1209 }); 1210 }, 1211 /** 1212 * Overrides Backbone.Collection.sync 1213 * Overrides wp.media.model.Attachments.sync 1214 * 1215 * @param {String} method 1216 * @param {Backbone.Model} model 1217 * @param {Object} [options={}] 1218 * @returns {Promise} 1219 */ 1220 sync: function( method, model, options ) { 1221 var args, fallback; 1222 1223 // Overload the read method so Attachment.fetch() functions correctly. 1224 if ( 'read' === method ) { 1225 options = options || {}; 1226 options.context = this; 1227 options.data = _.extend( options.data || {}, { 1228 action: 'query-attachments', 1229 post_id: wp.media.model.settings.post.id 1230 }); 1231 1232 // Clone the args so manipulation is non-destructive. 1233 args = _.clone( this.args ); 1234 1235 // Determine which page to query. 1236 if ( -1 !== args.posts_per_page ) { 1237 args.paged = Math.round( this.length / args.posts_per_page ) + 1; 1238 } 1239 1240 options.data.query = args; 1241 return wp.media.ajax( options ); 1242 1243 // Otherwise, fall back to Backbone.sync() 1244 } else { 1245 /** 1246 * Call wp.media.model.Attachments.sync or Backbone.sync 1247 */ 1248 fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone; 1249 return fallback.sync.apply( this, arguments ); 1250 } 1251 } 1252 }, { 1253 /** 1254 * @readonly 1255 */ 1256 defaultProps: { 1257 orderby: 'date', 1258 order: 'DESC' 1259 }, 1260 /** 1261 * @readonly 1262 */ 1263 defaultArgs: { 1264 posts_per_page: 40 1265 }, 1266 /** 1267 * @readonly 1268 */ 1269 orderby: { 1270 allowed: [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ], 1271 /** 1272 * A map of JavaScript orderby values to their WP_Query equivalents. 1273 * @type {Object} 1274 */ 1275 valuemap: { 1276 'id': 'ID', 1277 'uploadedTo': 'parent', 1278 'menuOrder': 'menu_order ID' 1279 } 1280 }, 1281 /** 1282 * A map of JavaScript query properties to their WP_Query equivalents. 1283 * 1284 * @readonly 1285 */ 1286 propmap: { 1287 'search': 's', 1288 'type': 'post_mime_type', 1289 'perPage': 'posts_per_page', 1290 'menuOrder': 'menu_order', 1291 'uploadedTo': 'post_parent', 1292 'status': 'post_status', 1293 'include': 'post__in', 1294 'exclude': 'post__not_in' 1295 }, 1296 /** 1297 * Creates and returns an Attachments Query collection given the properties. 1298 * 1299 * Caches query objects and reuses where possible. 1300 * 1301 * @static 1302 * @method 1303 * 1304 * @param {object} [props] 1305 * @param {Object} [props.cache=true] Whether to use the query cache or not. 1306 * @param {Object} [props.order] 1307 * @param {Object} [props.orderby] 1308 * @param {Object} [props.include] 1309 * @param {Object} [props.exclude] 1310 * @param {Object} [props.s] 1311 * @param {Object} [props.post_mime_type] 1312 * @param {Object} [props.posts_per_page] 1313 * @param {Object} [props.menu_order] 1314 * @param {Object} [props.post_parent] 1315 * @param {Object} [props.post_status] 1316 * @param {Object} [options] 1317 * 1318 * @returns {wp.media.model.Query} A new Attachments Query collection. 1319 */ 1320 get: (function(){ 1321 /** 1322 * @static 1323 * @type Array 1324 */ 1325 var queries = []; 1326 1327 /** 1328 * @returns {Query} 1329 */ 1330 return function( props, options ) { 1331 var args = {}, 1332 orderby = Query.orderby, 1333 defaults = Query.defaultProps, 1334 query, 1335 cache = !! props.cache || _.isUndefined( props.cache ); 1336 1337 // Remove the `query` property. This isn't linked to a query, 1338 // this *is* the query. 1339 delete props.query; 1340 delete props.cache; 1341 1342 // Fill default args. 1343 _.defaults( props, defaults ); 1344 1345 // Normalize the order. 1346 props.order = props.order.toUpperCase(); 1347 if ( 'DESC' !== props.order && 'ASC' !== props.order ) { 1348 props.order = defaults.order.toUpperCase(); 1349 } 1350 1351 // Ensure we have a valid orderby value. 1352 if ( ! _.contains( orderby.allowed, props.orderby ) ) { 1353 props.orderby = defaults.orderby; 1354 } 1355 1356 _.each( [ 'include', 'exclude' ], function( prop ) { 1357 if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) { 1358 props[ prop ] = [ props[ prop ] ]; 1359 } 1360 } ); 1361 1362 // Generate the query `args` object. 1363 // Correct any differing property names. 1364 _.each( props, function( value, prop ) { 1365 if ( _.isNull( value ) ) { 1366 return; 1367 } 1368 1369 args[ Query.propmap[ prop ] || prop ] = value; 1370 }); 1371 1372 // Fill any other default query args. 1373 _.defaults( args, Query.defaultArgs ); 1374 1375 // `props.orderby` does not always map directly to `args.orderby`. 1376 // Substitute exceptions specified in orderby.keymap. 1377 args.orderby = orderby.valuemap[ props.orderby ] || props.orderby; 1378 1379 // Search the query cache for a matching query. 1380 if ( cache ) { 1381 query = _.find( queries, function( query ) { 1382 return _.isEqual( query.args, args ); 1383 }); 1384 } else { 1385 queries = []; 1386 } 1387 1388 // Otherwise, create a new query and add it to the cache. 1389 if ( ! query ) { 1390 query = new Query( [], _.extend( options || {}, { 1391 props: props, 1392 args: args 1393 } ) ); 1394 queries.push( query ); 1395 } 1396 1397 return query; 1398 }; 1399 }()) 1400 }); 1401 1402 module.exports = Query; 1403 1404 },{}],6:[function(require,module,exports){ 1405 /*globals wp, _ */ 1406 1407 /** 1408 * wp.media.model.Selection 1409 * 1410 * A selection of attachments. 1411 * 1412 * @class 1413 * @augments wp.media.model.Attachments 1414 * @augments Backbone.Collection 1415 */ 1416 var Attachments = wp.media.model.Attachments, 1417 Selection; 1418 1419 Selection = Attachments.extend({ 1420 /** 1421 * Refresh the `single` model whenever the selection changes. 1422 * Binds `single` instead of using the context argument to ensure 1423 * it receives no parameters. 1424 * 1425 * @param {Array} [models=[]] Array of models used to populate the collection. 1426 * @param {Object} [options={}] 1427 */ 1428 initialize: function( models, options ) { 1429 /** 1430 * call 'initialize' directly on the parent class 1431 */ 1432 Attachments.prototype.initialize.apply( this, arguments ); 1433 this.multiple = options && options.multiple; 1434 1435 this.on( 'add remove reset', _.bind( this.single, this, false ) ); 1436 }, 1437 1438 /** 1439 * If the workflow does not support multi-select, clear out the selection 1440 * before adding a new attachment to it. 1441 * 1442 * @param {Array} models 1443 * @param {Object} options 1444 * @returns {wp.media.model.Attachment[]} 1445 */ 1446 add: function( models, options ) { 1447 if ( ! this.multiple ) { 1448 this.remove( this.models ); 1449 } 1450 /** 1451 * call 'add' directly on the parent class 1452 */ 1453 return Attachments.prototype.add.call( this, models, options ); 1454 }, 1455 1456 /** 1457 * Fired when toggling (clicking on) an attachment in the modal. 1458 * 1459 * @param {undefined|boolean|wp.media.model.Attachment} model 1460 * 1461 * @fires wp.media.model.Selection#selection:single 1462 * @fires wp.media.model.Selection#selection:unsingle 1463 * 1464 * @returns {Backbone.Model} 1465 */ 1466 single: function( model ) { 1467 var previous = this._single; 1468 1469 // If a `model` is provided, use it as the single model. 1470 if ( model ) { 1471 this._single = model; 1472 } 1473 // If the single model isn't in the selection, remove it. 1474 if ( this._single && ! this.get( this._single.cid ) ) { 1475 delete this._single; 1476 } 1477 1478 this._single = this._single || this.last(); 1479 1480 // If single has changed, fire an event. 1481 if ( this._single !== previous ) { 1482 if ( previous ) { 1483 previous.trigger( 'selection:unsingle', previous, this ); 1484 1485 // If the model was already removed, trigger the collection 1486 // event manually. 1487 if ( ! this.get( previous.cid ) ) { 1488 this.trigger( 'selection:unsingle', previous, this ); 1489 } 1490 } 1491 if ( this._single ) { 1492 this._single.trigger( 'selection:single', this._single, this ); 1493 } 1494 } 1495 1496 // Return the single model, or the last model as a fallback. 1497 return this._single; 1498 } 1499 }); 1500 1501 module.exports = Selection; 1502 1503 },{}]},{},[1]); -
src/wp-includes/js/media-views.js
1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 2 /*globals wp, _ */ 3 4 /** 5 * wp.media.controller.CollectionAdd 6 * 7 * A state for adding attachments to a collection (e.g. video playlist). 8 * 9 * @class 10 * @augments wp.media.controller.Library 11 * @augments wp.media.controller.State 12 * @augments Backbone.Model 13 * 14 * @param {object} [attributes] The attributes hash passed to the state. 15 * @param {string} [attributes.id=library] Unique identifier. 16 * @param {string} attributes.title Title for the state. Displays in the frame's title region. 17 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 18 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 19 * If one is not supplied, a collection of attachments of the specified type will be created. 20 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 21 * Accepts 'all', 'uploaded', or 'unattached'. 22 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 23 * @param {string} [attributes.content=upload] Initial mode for the content region. 24 * Overridden by persistent user setting if 'contentUserSetting' is true. 25 * @param {string} [attributes.router=browse] Initial mode for the router region. 26 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 27 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 28 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 29 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 30 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 31 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 32 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 33 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 34 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 35 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 36 */ 37 var Selection = wp.media.model.Selection, 38 Library = wp.media.controller.Library, 39 CollectionAdd; 40 41 CollectionAdd = Library.extend({ 42 defaults: _.defaults( { 43 // Selection defaults. @see media.model.Selection 44 multiple: 'add', 45 // Attachments browser defaults. @see media.view.AttachmentsBrowser 46 filterable: 'uploaded', 47 48 priority: 100, 49 syncSelection: false 50 }, Library.prototype.defaults ), 51 52 /** 53 * @since 3.9.0 54 */ 55 initialize: function() { 56 var collectionType = this.get('collectionType'); 57 58 if ( 'video' === this.get( 'type' ) ) { 59 collectionType = 'video-' + collectionType; 60 } 61 62 this.set( 'id', collectionType + '-library' ); 63 this.set( 'toolbar', collectionType + '-add' ); 64 this.set( 'menu', collectionType ); 65 66 // If we haven't been provided a `library`, create a `Selection`. 67 if ( ! this.get('library') ) { 68 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 69 } 70 Library.prototype.initialize.apply( this, arguments ); 71 }, 72 73 /** 74 * @since 3.9.0 75 */ 76 activate: function() { 77 var library = this.get('library'), 78 editLibrary = this.get('editLibrary'), 79 edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library'); 80 81 if ( editLibrary && editLibrary !== edit ) { 82 library.unobserve( editLibrary ); 83 } 84 85 // Accepts attachments that exist in the original library and 86 // that do not exist in gallery's library. 87 library.validator = function( attachment ) { 88 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 89 }; 90 91 // Reset the library to ensure that all attachments are re-added 92 // to the collection. Do so silently, as calling `observe` will 93 // trigger the `reset` event. 94 library.reset( library.mirroring.models, { silent: true }); 95 library.observe( edit ); 96 this.set('editLibrary', edit); 97 98 Library.prototype.activate.apply( this, arguments ); 99 } 100 }); 101 102 module.exports = CollectionAdd; 103 104 },{}],2:[function(require,module,exports){ 105 /*globals wp, Backbone */ 106 107 /** 108 * wp.media.controller.CollectionEdit 109 * 110 * A state for editing a collection, which is used by audio and video playlists, 111 * and can be used for other collections. 112 * 113 * @class 114 * @augments wp.media.controller.Library 115 * @augments wp.media.controller.State 116 * @augments Backbone.Model 117 * 118 * @param {object} [attributes] The attributes hash passed to the state. 119 * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. 120 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. 121 * If one is not supplied, an empty media.model.Selection collection is created. 122 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 123 * @param {string} [attributes.content=browse] Initial mode for the content region. 124 * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. 125 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 126 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 127 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. 128 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 129 * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. 130 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 131 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 132 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 133 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 134 * Defaults to false for this state, because the library passed in *is* the selection. 135 * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). 136 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 137 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 138 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 139 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 140 */ 141 var Library = wp.media.controller.Library, 142 l10n = wp.media.view.l10n, 143 $ = jQuery, 144 CollectionEdit; 145 146 CollectionEdit = Library.extend({ 147 defaults: { 148 multiple: false, 149 sortable: true, 150 searchable: false, 151 content: 'browse', 152 describe: true, 153 dragInfo: true, 154 idealColumnWidth: 170, 155 editing: false, 156 priority: 60, 157 SettingsView: false, 158 syncSelection: false 159 }, 160 161 /** 162 * @since 3.9.0 163 */ 164 initialize: function() { 165 var collectionType = this.get('collectionType'); 166 167 if ( 'video' === this.get( 'type' ) ) { 168 collectionType = 'video-' + collectionType; 169 } 170 171 this.set( 'id', collectionType + '-edit' ); 172 this.set( 'toolbar', collectionType + '-edit' ); 173 174 // If we haven't been provided a `library`, create a `Selection`. 175 if ( ! this.get('library') ) { 176 this.set( 'library', new wp.media.model.Selection() ); 177 } 178 // The single `Attachment` view to be used in the `Attachments` view. 179 if ( ! this.get('AttachmentView') ) { 180 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 181 } 182 Library.prototype.initialize.apply( this, arguments ); 183 }, 184 185 /** 186 * @since 3.9.0 187 */ 188 activate: function() { 189 var library = this.get('library'); 190 191 // Limit the library to images only. 192 library.props.set( 'type', this.get( 'type' ) ); 193 194 // Watch for uploaded attachments. 195 this.get('library').observe( wp.Uploader.queue ); 196 197 this.frame.on( 'content:render:browse', this.renderSettings, this ); 198 199 Library.prototype.activate.apply( this, arguments ); 200 }, 201 202 /** 203 * @since 3.9.0 204 */ 205 deactivate: function() { 206 // Stop watching for uploaded attachments. 207 this.get('library').unobserve( wp.Uploader.queue ); 208 209 this.frame.off( 'content:render:browse', this.renderSettings, this ); 210 211 Library.prototype.deactivate.apply( this, arguments ); 212 }, 213 214 /** 215 * Render the collection embed settings view in the browser sidebar. 216 * 217 * @todo This is against the pattern elsewhere in media. Typically the frame 218 * is responsible for adding region mode callbacks. Explain. 219 * 220 * @since 3.9.0 221 * 222 * @param {wp.media.view.attachmentsBrowser} The attachments browser view. 223 */ 224 renderSettings: function( attachmentsBrowserView ) { 225 var library = this.get('library'), 226 collectionType = this.get('collectionType'), 227 dragInfoText = this.get('dragInfoText'), 228 SettingsView = this.get('SettingsView'), 229 obj = {}; 230 231 if ( ! library || ! attachmentsBrowserView ) { 232 return; 233 } 234 235 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 236 237 obj[ collectionType ] = new SettingsView({ 238 controller: this, 239 model: library[ collectionType ], 240 priority: 40 241 }); 242 243 attachmentsBrowserView.sidebar.set( obj ); 244 245 if ( dragInfoText ) { 246 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 247 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 248 priority: -40 249 }) ); 250 } 251 252 // Add the 'Reverse order' button to the toolbar. 253 attachmentsBrowserView.toolbar.set( 'reverse', { 254 text: l10n.reverseOrder, 255 priority: 80, 256 257 click: function() { 258 library.reset( library.toArray().reverse() ); 259 } 260 }); 261 } 262 }); 263 264 module.exports = CollectionEdit; 265 266 },{}],3:[function(require,module,exports){ 267 /*globals wp, _, Backbone */ 268 269 /** 270 * wp.media.controller.Cropper 271 * 272 * A state for cropping an image. 273 * 274 * @class 275 * @augments wp.media.controller.State 276 * @augments Backbone.Model 277 */ 278 var l10n = wp.media.view.l10n, 279 Cropper; 280 281 Cropper = wp.media.controller.State.extend({ 282 defaults: { 283 id: 'cropper', 284 title: l10n.cropImage, 285 // Region mode defaults. 286 toolbar: 'crop', 287 content: 'crop', 288 router: false, 289 290 canSkipCrop: false 291 }, 292 293 activate: function() { 294 this.frame.on( 'content:create:crop', this.createCropContent, this ); 295 this.frame.on( 'close', this.removeCropper, this ); 296 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 297 }, 298 299 deactivate: function() { 300 this.frame.toolbar.mode('browse'); 301 }, 302 303 createCropContent: function() { 304 this.cropperView = new wp.media.view.Cropper({ 305 controller: this, 306 attachment: this.get('selection').first() 307 }); 308 this.cropperView.on('image-loaded', this.createCropToolbar, this); 309 this.frame.content.set(this.cropperView); 310 311 }, 312 removeCropper: function() { 313 this.imgSelect.cancelSelection(); 314 this.imgSelect.setOptions({remove: true}); 315 this.imgSelect.update(); 316 this.cropperView.remove(); 317 }, 318 createCropToolbar: function() { 319 var canSkipCrop, toolbarOptions; 320 321 canSkipCrop = this.get('canSkipCrop') || false; 322 323 toolbarOptions = { 324 controller: this.frame, 325 items: { 326 insert: { 327 style: 'primary', 328 text: l10n.cropImage, 329 priority: 80, 330 requires: { library: false, selection: false }, 331 332 click: function() { 333 var controller = this.controller, 334 selection; 335 336 selection = controller.state().get('selection').first(); 337 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 338 339 this.$el.text(l10n.cropping); 340 this.$el.attr('disabled', true); 341 342 controller.state().doCrop( selection ).done( function( croppedImage ) { 343 controller.trigger('cropped', croppedImage ); 344 controller.close(); 345 }).fail( function() { 346 controller.trigger('content:error:crop'); 347 }); 348 } 349 } 350 } 351 }; 352 353 if ( canSkipCrop ) { 354 _.extend( toolbarOptions.items, { 355 skip: { 356 style: 'secondary', 357 text: l10n.skipCropping, 358 priority: 70, 359 requires: { library: false, selection: false }, 360 click: function() { 361 var selection = this.controller.state().get('selection').first(); 362 this.controller.state().cropperView.remove(); 363 this.controller.trigger('skippedcrop', selection); 364 this.controller.close(); 365 } 366 } 367 }); 368 } 369 370 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 371 }, 372 373 doCrop: function( attachment ) { 374 return wp.ajax.post( 'custom-header-crop', { 375 nonce: attachment.get('nonces').edit, 376 id: attachment.get('id'), 377 cropDetails: attachment.get('cropDetails') 378 } ); 379 } 380 }); 381 382 module.exports = Cropper; 383 384 },{}],4:[function(require,module,exports){ 385 /*globals wp */ 386 387 /** 388 * wp.media.controller.EditImage 389 * 390 * A state for editing (cropping, etc.) an image. 391 * 392 * @class 393 * @augments wp.media.controller.State 394 * @augments Backbone.Model 395 * 396 * @param {object} attributes The attributes hash passed to the state. 397 * @param {wp.media.model.Attachment} attributes.model The attachment. 398 * @param {string} [attributes.id=edit-image] Unique identifier. 399 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 400 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 401 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 402 * @param {string} [attributes.menu=false] Initial mode for the menu region. 403 * @param {string} [attributes.url] Unused. @todo Consider removal. 404 */ 405 var l10n = wp.media.view.l10n, 406 EditImage; 407 408 EditImage = wp.media.controller.State.extend({ 409 defaults: { 410 id: 'edit-image', 411 title: l10n.editImage, 412 menu: false, 413 toolbar: 'edit-image', 414 content: 'edit-image', 415 url: '' 416 }, 417 418 /** 419 * @since 3.9.0 420 */ 421 activate: function() { 422 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar ); 423 }, 424 425 /** 426 * @since 3.9.0 427 */ 428 deactivate: function() { 429 this.stopListening( this.frame ); 430 }, 431 432 /** 433 * @since 3.9.0 434 */ 435 toolbar: function() { 436 var frame = this.frame, 437 lastState = frame.lastState(), 438 previous = lastState && lastState.id; 439 440 frame.toolbar.set( new wp.media.view.Toolbar({ 441 controller: frame, 442 items: { 443 back: { 444 style: 'primary', 445 text: l10n.back, 446 priority: 20, 447 click: function() { 448 if ( previous ) { 449 frame.setState( previous ); 450 } else { 451 frame.close(); 452 } 453 } 454 } 455 } 456 }) ); 457 } 458 }); 459 460 module.exports = EditImage; 461 462 },{}],5:[function(require,module,exports){ 463 /*globals wp, _, Backbone */ 464 465 /** 466 * wp.media.controller.Embed 467 * 468 * A state for embedding media from a URL. 469 * 470 * @class 471 * @augments wp.media.controller.State 472 * @augments Backbone.Model 473 * 474 * @param {object} attributes The attributes hash passed to the state. 475 * @param {string} [attributes.id=embed] Unique identifier. 476 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. 477 * @param {string} [attributes.content=embed] Initial mode for the content region. 478 * @param {string} [attributes.menu=default] Initial mode for the menu region. 479 * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. 480 * @param {string} [attributes.menu=false] Initial mode for the menu region. 481 * @param {int} [attributes.priority=120] The priority for the state link in the media menu. 482 * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. 483 * @param {string} [attributes.url] The embed URL. 484 * @param {object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. 485 */ 486 var l10n = wp.media.view.l10n, 487 $ = Backbone.$, 488 Embed; 489 490 Embed = wp.media.controller.State.extend({ 491 defaults: { 492 id: 'embed', 493 title: l10n.insertFromUrlTitle, 494 content: 'embed', 495 menu: 'default', 496 toolbar: 'main-embed', 497 priority: 120, 498 type: 'link', 499 url: '', 500 metadata: {} 501 }, 502 503 // The amount of time used when debouncing the scan. 504 sensitivity: 200, 505 506 initialize: function(options) { 507 this.metadata = options.metadata; 508 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity ); 509 this.props = new Backbone.Model( this.metadata || { url: '' }); 510 this.props.on( 'change:url', this.debouncedScan, this ); 511 this.props.on( 'change:url', this.refresh, this ); 512 this.on( 'scan', this.scanImage, this ); 513 }, 514 515 /** 516 * Trigger a scan of the embedded URL's content for metadata required to embed. 517 * 518 * @fires wp.media.controller.Embed#scan 519 */ 520 scan: function() { 521 var scanners, 522 embed = this, 523 attributes = { 524 type: 'link', 525 scanners: [] 526 }; 527 528 // Scan is triggered with the list of `attributes` to set on the 529 // state, useful for the 'type' attribute and 'scanners' attribute, 530 // an array of promise objects for asynchronous scan operations. 531 if ( this.props.get('url') ) { 532 this.trigger( 'scan', attributes ); 533 } 534 535 if ( attributes.scanners.length ) { 536 scanners = attributes.scanners = $.when.apply( $, attributes.scanners ); 537 scanners.always( function() { 538 if ( embed.get('scanners') === scanners ) { 539 embed.set( 'loading', false ); 540 } 541 }); 542 } else { 543 attributes.scanners = null; 544 } 545 546 attributes.loading = !! attributes.scanners; 547 this.set( attributes ); 548 }, 549 /** 550 * Try scanning the embed as an image to discover its dimensions. 551 * 552 * @param {Object} attributes 553 */ 554 scanImage: function( attributes ) { 555 var frame = this.frame, 556 state = this, 557 url = this.props.get('url'), 558 image = new Image(), 559 deferred = $.Deferred(); 560 561 attributes.scanners.push( deferred.promise() ); 562 563 // Try to load the image and find its width/height. 564 image.onload = function() { 565 deferred.resolve(); 566 567 if ( state !== frame.state() || url !== state.props.get('url') ) { 568 return; 569 } 570 571 state.set({ 572 type: 'image' 573 }); 574 575 state.props.set({ 576 width: image.width, 577 height: image.height 578 }); 579 }; 580 581 image.onerror = deferred.reject; 582 image.src = url; 583 }, 584 585 refresh: function() { 586 this.frame.toolbar.get().refresh(); 587 }, 588 589 reset: function() { 590 this.props.clear().set({ url: '' }); 591 592 if ( this.active ) { 593 this.refresh(); 594 } 595 } 596 }); 597 598 module.exports = Embed; 599 600 },{}],6:[function(require,module,exports){ 601 /*globals wp, _ */ 602 603 /** 604 * wp.media.controller.FeaturedImage 605 * 606 * A state for selecting a featured image for a post. 607 * 608 * @class 609 * @augments wp.media.controller.Library 610 * @augments wp.media.controller.State 611 * @augments Backbone.Model 612 * 613 * @param {object} [attributes] The attributes hash passed to the state. 614 * @param {string} [attributes.id=featured-image] Unique identifier. 615 * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. 616 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 617 * If one is not supplied, a collection of all images will be created. 618 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 619 * @param {string} [attributes.content=upload] Initial mode for the content region. 620 * Overridden by persistent user setting if 'contentUserSetting' is true. 621 * @param {string} [attributes.menu=default] Initial mode for the menu region. 622 * @param {string} [attributes.router=browse] Initial mode for the router region. 623 * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. 624 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 625 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 626 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 627 * Accepts 'all', 'uploaded', or 'unattached'. 628 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 629 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 630 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 631 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 632 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 633 */ 634 var Attachment = wp.media.model.Attachment, 635 Library = wp.media.controller.Library, 636 l10n = wp.media.view.l10n, 637 FeaturedImage; 638 639 FeaturedImage = Library.extend({ 640 defaults: _.defaults({ 641 id: 'featured-image', 642 title: l10n.setFeaturedImageTitle, 643 multiple: false, 644 filterable: 'uploaded', 645 toolbar: 'featured-image', 646 priority: 60, 647 syncSelection: true 648 }, Library.prototype.defaults ), 649 650 /** 651 * @since 3.5.0 652 */ 653 initialize: function() { 654 var library, comparator; 655 656 // If we haven't been provided a `library`, create a `Selection`. 657 if ( ! this.get('library') ) { 658 this.set( 'library', wp.media.query({ type: 'image' }) ); 659 } 660 661 Library.prototype.initialize.apply( this, arguments ); 662 663 library = this.get('library'); 664 comparator = library.comparator; 665 666 // Overload the library's comparator to push items that are not in 667 // the mirrored query to the front of the aggregate collection. 668 library.comparator = function( a, b ) { 669 var aInQuery = !! this.mirroring.get( a.cid ), 670 bInQuery = !! this.mirroring.get( b.cid ); 671 672 if ( ! aInQuery && bInQuery ) { 673 return -1; 674 } else if ( aInQuery && ! bInQuery ) { 675 return 1; 676 } else { 677 return comparator.apply( this, arguments ); 678 } 679 }; 680 681 // Add all items in the selection to the library, so any featured 682 // images that are not initially loaded still appear. 683 library.observe( this.get('selection') ); 684 }, 685 686 /** 687 * @since 3.5.0 688 */ 689 activate: function() { 690 this.updateSelection(); 691 this.frame.on( 'open', this.updateSelection, this ); 692 693 Library.prototype.activate.apply( this, arguments ); 694 }, 695 696 /** 697 * @since 3.5.0 698 */ 699 deactivate: function() { 700 this.frame.off( 'open', this.updateSelection, this ); 701 702 Library.prototype.deactivate.apply( this, arguments ); 703 }, 704 705 /** 706 * @since 3.5.0 707 */ 708 updateSelection: function() { 709 var selection = this.get('selection'), 710 id = wp.media.view.settings.post.featuredImageId, 711 attachment; 712 713 if ( '' !== id && -1 !== id ) { 714 attachment = Attachment.get( id ); 715 attachment.fetch(); 716 } 717 718 selection.reset( attachment ? [ attachment ] : [] ); 719 } 720 }); 721 722 module.exports = FeaturedImage; 723 724 },{}],7:[function(require,module,exports){ 725 /*globals wp, _ */ 726 727 /** 728 * wp.media.controller.GalleryAdd 729 * 730 * A state for selecting more images to add to a gallery. 731 * 732 * @class 733 * @augments wp.media.controller.Library 734 * @augments wp.media.controller.State 735 * @augments Backbone.Model 736 * 737 * @param {object} [attributes] The attributes hash passed to the state. 738 * @param {string} [attributes.id=gallery-library] Unique identifier. 739 * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. 740 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 741 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 742 * If one is not supplied, a collection of all images will be created. 743 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 744 * Accepts 'all', 'uploaded', or 'unattached'. 745 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 746 * @param {string} [attributes.content=upload] Initial mode for the content region. 747 * Overridden by persistent user setting if 'contentUserSetting' is true. 748 * @param {string} [attributes.router=browse] Initial mode for the router region. 749 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 750 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 751 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 752 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 753 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 754 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 755 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 756 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 757 */ 758 var Selection = wp.media.model.Selection, 759 Library = wp.media.controller.Library, 760 l10n = wp.media.view.l10n, 761 GalleryAdd; 762 763 GalleryAdd = Library.extend({ 764 defaults: _.defaults({ 765 id: 'gallery-library', 766 title: l10n.addToGalleryTitle, 767 multiple: 'add', 768 filterable: 'uploaded', 769 menu: 'gallery', 770 toolbar: 'gallery-add', 771 priority: 100, 772 syncSelection: false 773 }, Library.prototype.defaults ), 774 775 /** 776 * @since 3.5.0 777 */ 778 initialize: function() { 779 // If a library wasn't supplied, create a library of images. 780 if ( ! this.get('library') ) { 781 this.set( 'library', wp.media.query({ type: 'image' }) ); 782 } 783 784 Library.prototype.initialize.apply( this, arguments ); 785 }, 786 787 /** 788 * @since 3.5.0 789 */ 790 activate: function() { 791 var library = this.get('library'), 792 edit = this.frame.state('gallery-edit').get('library'); 793 794 if ( this.editLibrary && this.editLibrary !== edit ) { 795 library.unobserve( this.editLibrary ); 796 } 797 798 // Accepts attachments that exist in the original library and 799 // that do not exist in gallery's library. 800 library.validator = function( attachment ) { 801 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 802 }; 803 804 // Reset the library to ensure that all attachments are re-added 805 // to the collection. Do so silently, as calling `observe` will 806 // trigger the `reset` event. 807 library.reset( library.mirroring.models, { silent: true }); 808 library.observe( edit ); 809 this.editLibrary = edit; 810 811 Library.prototype.activate.apply( this, arguments ); 812 } 813 }); 814 815 module.exports = GalleryAdd; 816 817 },{}],8:[function(require,module,exports){ 818 /*globals wp */ 819 820 /** 821 * wp.media.controller.GalleryEdit 822 * 823 * A state for editing a gallery's images and settings. 824 * 825 * @class 826 * @augments wp.media.controller.Library 827 * @augments wp.media.controller.State 828 * @augments Backbone.Model 829 * 830 * @param {object} [attributes] The attributes hash passed to the state. 831 * @param {string} [attributes.id=gallery-edit] Unique identifier. 832 * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region. 833 * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery. 834 * If one is not supplied, an empty media.model.Selection collection is created. 835 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 836 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 837 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 838 * @param {string|false} [attributes.content=browse] Initial mode for the content region. 839 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 840 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 841 * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface. 842 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 843 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 844 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 845 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 846 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 847 * Defaults to false for this state, because the library passed in *is* the selection. 848 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 849 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 850 */ 851 var Library = wp.media.controller.Library, 852 l10n = wp.media.view.l10n, 853 GalleryEdit; 854 855 GalleryEdit = Library.extend({ 856 defaults: { 857 id: 'gallery-edit', 858 title: l10n.editGalleryTitle, 859 multiple: false, 860 searchable: false, 861 sortable: true, 862 display: false, 863 content: 'browse', 864 toolbar: 'gallery-edit', 865 describe: true, 866 displaySettings: true, 867 dragInfo: true, 868 idealColumnWidth: 170, 869 editing: false, 870 priority: 60, 871 syncSelection: false 872 }, 873 874 /** 875 * @since 3.5.0 876 */ 877 initialize: function() { 878 // If we haven't been provided a `library`, create a `Selection`. 879 if ( ! this.get('library') ) { 880 this.set( 'library', new wp.media.model.Selection() ); 881 } 882 883 // The single `Attachment` view to be used in the `Attachments` view. 884 if ( ! this.get('AttachmentView') ) { 885 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 886 } 887 888 Library.prototype.initialize.apply( this, arguments ); 889 }, 890 891 /** 892 * @since 3.5.0 893 */ 894 activate: function() { 895 var library = this.get('library'); 896 897 // Limit the library to images only. 898 library.props.set( 'type', 'image' ); 899 900 // Watch for uploaded attachments. 901 this.get('library').observe( wp.Uploader.queue ); 902 903 this.frame.on( 'content:render:browse', this.gallerySettings, this ); 904 905 Library.prototype.activate.apply( this, arguments ); 906 }, 907 908 /** 909 * @since 3.5.0 910 */ 911 deactivate: function() { 912 // Stop watching for uploaded attachments. 913 this.get('library').unobserve( wp.Uploader.queue ); 914 915 this.frame.off( 'content:render:browse', this.gallerySettings, this ); 916 917 Library.prototype.deactivate.apply( this, arguments ); 918 }, 919 920 /** 921 * @since 3.5.0 922 * 923 * @param browser 924 */ 925 gallerySettings: function( browser ) { 926 if ( ! this.get('displaySettings') ) { 927 return; 928 } 929 930 var library = this.get('library'); 931 932 if ( ! library || ! browser ) { 933 return; 934 } 935 936 library.gallery = library.gallery || new Backbone.Model(); 937 938 browser.sidebar.set({ 939 gallery: new wp.media.view.Settings.Gallery({ 940 controller: this, 941 model: library.gallery, 942 priority: 40 943 }) 944 }); 945 946 browser.toolbar.set( 'reverse', { 947 text: l10n.reverseOrder, 948 priority: 80, 949 950 click: function() { 951 library.reset( library.toArray().reverse() ); 952 } 953 }); 954 } 955 }); 956 957 module.exports = GalleryEdit; 958 959 },{}],9:[function(require,module,exports){ 960 /*globals wp, _ */ 961 962 /** 963 * wp.media.controller.ImageDetails 964 * 965 * A state for editing the attachment display settings of an image that's been 966 * inserted into the editor. 967 * 968 * @class 969 * @augments wp.media.controller.State 970 * @augments Backbone.Model 971 * 972 * @param {object} [attributes] The attributes hash passed to the state. 973 * @param {string} [attributes.id=image-details] Unique identifier. 974 * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. 975 * @param {wp.media.model.Attachment} attributes.image The image's model. 976 * @param {string|false} [attributes.content=image-details] Initial mode for the content region. 977 * @param {string|false} [attributes.menu=false] Initial mode for the menu region. 978 * @param {string|false} [attributes.router=false] Initial mode for the router region. 979 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 980 * @param {boolean} [attributes.editing=false] Unused. 981 * @param {int} [attributes.priority=60] Unused. 982 * 983 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, 984 * however this may not do anything. 985 */ 986 var State = wp.media.controller.State, 987 Library = wp.media.controller.Library, 988 l10n = wp.media.view.l10n, 989 ImageDetails; 990 991 ImageDetails = State.extend({ 992 defaults: _.defaults({ 993 id: 'image-details', 994 title: l10n.imageDetailsTitle, 995 content: 'image-details', 996 menu: false, 997 router: false, 998 toolbar: 'image-details', 999 editing: false, 1000 priority: 60 1001 }, Library.prototype.defaults ), 1002 1003 /** 1004 * @since 3.9.0 1005 * 1006 * @param options Attributes 1007 */ 1008 initialize: function( options ) { 1009 this.image = options.image; 1010 State.prototype.initialize.apply( this, arguments ); 1011 }, 1012 1013 /** 1014 * @since 3.9.0 1015 */ 1016 activate: function() { 1017 this.frame.modal.$el.addClass('image-details'); 1018 } 1019 }); 1020 1021 module.exports = ImageDetails; 1022 1023 },{}],10:[function(require,module,exports){ 1024 /*globals wp, _, Backbone */ 1025 1026 /** 1027 * wp.media.controller.Library 1028 * 1029 * A state for choosing an attachment or group of attachments from the media library. 1030 * 1031 * @class 1032 * @augments wp.media.controller.State 1033 * @augments Backbone.Model 1034 * @mixes media.selectionSync 1035 * 1036 * @param {object} [attributes] The attributes hash passed to the state. 1037 * @param {string} [attributes.id=library] Unique identifier. 1038 * @param {string} [attributes.title=Media library] Title for the state. Displays in the media menu and the frame's title region. 1039 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1040 * If one is not supplied, a collection of all attachments will be created. 1041 * @param {wp.media.model.Selection|object} [attributes.selection] A collection to contain attachment selections within the state. 1042 * If the 'selection' attribute is a plain JS object, 1043 * a Selection will be created using its values as the selection instance's `props` model. 1044 * Otherwise, it will copy the library's `props` model. 1045 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1046 * @param {string} [attributes.content=upload] Initial mode for the content region. 1047 * Overridden by persistent user setting if 'contentUserSetting' is true. 1048 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1049 * @param {string} [attributes.router=browse] Initial mode for the router region. 1050 * @param {string} [attributes.toolbar=select] Initial mode for the toolbar region. 1051 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1052 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 1053 * Accepts 'all', 'uploaded', or 'unattached'. 1054 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1055 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1056 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1057 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1058 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1059 */ 1060 var l10n = wp.media.view.l10n, 1061 getUserSetting = window.getUserSetting, 1062 setUserSetting = window.setUserSetting, 1063 Library; 1064 1065 Library = wp.media.controller.State.extend({ 1066 defaults: { 1067 id: 'library', 1068 title: l10n.mediaLibraryTitle, 1069 multiple: false, 1070 content: 'upload', 1071 menu: 'default', 1072 router: 'browse', 1073 toolbar: 'select', 1074 searchable: true, 1075 filterable: false, 1076 sortable: true, 1077 autoSelect: true, 1078 describe: false, 1079 contentUserSetting: true, 1080 syncSelection: true 1081 }, 1082 1083 /** 1084 * If a library isn't provided, query all media items. 1085 * If a selection instance isn't provided, create one. 1086 * 1087 * @since 3.5.0 1088 */ 1089 initialize: function() { 1090 var selection = this.get('selection'), 1091 props; 1092 1093 if ( ! this.get('library') ) { 1094 this.set( 'library', wp.media.query() ); 1095 } 1096 1097 if ( ! ( selection instanceof wp.media.model.Selection ) ) { 1098 props = selection; 1099 1100 if ( ! props ) { 1101 props = this.get('library').props.toJSON(); 1102 props = _.omit( props, 'orderby', 'query' ); 1103 } 1104 1105 this.set( 'selection', new wp.media.model.Selection( null, { 1106 multiple: this.get('multiple'), 1107 props: props 1108 }) ); 1109 } 1110 1111 this.resetDisplays(); 1112 }, 1113 1114 /** 1115 * @since 3.5.0 1116 */ 1117 activate: function() { 1118 this.syncSelection(); 1119 1120 wp.Uploader.queue.on( 'add', this.uploading, this ); 1121 1122 this.get('selection').on( 'add remove reset', this.refreshContent, this ); 1123 1124 if ( this.get( 'router' ) && this.get('contentUserSetting') ) { 1125 this.frame.on( 'content:activate', this.saveContentMode, this ); 1126 this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) ); 1127 } 1128 }, 1129 1130 /** 1131 * @since 3.5.0 1132 */ 1133 deactivate: function() { 1134 this.recordSelection(); 1135 1136 this.frame.off( 'content:activate', this.saveContentMode, this ); 1137 1138 // Unbind all event handlers that use this state as the context 1139 // from the selection. 1140 this.get('selection').off( null, null, this ); 1141 1142 wp.Uploader.queue.off( null, null, this ); 1143 }, 1144 1145 /** 1146 * Reset the library to its initial state. 1147 * 1148 * @since 3.5.0 1149 */ 1150 reset: function() { 1151 this.get('selection').reset(); 1152 this.resetDisplays(); 1153 this.refreshContent(); 1154 }, 1155 1156 /** 1157 * Reset the attachment display settings defaults to the site options. 1158 * 1159 * If site options don't define them, fall back to a persistent user setting. 1160 * 1161 * @since 3.5.0 1162 */ 1163 resetDisplays: function() { 1164 var defaultProps = wp.media.view.settings.defaultProps; 1165 this._displays = []; 1166 this._defaultDisplaySettings = { 1167 align: defaultProps.align || getUserSetting( 'align', 'none' ), 1168 size: defaultProps.size || getUserSetting( 'imgsize', 'medium' ), 1169 link: defaultProps.link || getUserSetting( 'urlbutton', 'file' ) 1170 }; 1171 }, 1172 1173 /** 1174 * Create a model to represent display settings (alignment, etc.) for an attachment. 1175 * 1176 * @since 3.5.0 1177 * 1178 * @param {wp.media.model.Attachment} attachment 1179 * @returns {Backbone.Model} 1180 */ 1181 display: function( attachment ) { 1182 var displays = this._displays; 1183 1184 if ( ! displays[ attachment.cid ] ) { 1185 displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) ); 1186 } 1187 return displays[ attachment.cid ]; 1188 }, 1189 1190 /** 1191 * Given an attachment, create attachment display settings properties. 1192 * 1193 * @since 3.6.0 1194 * 1195 * @param {wp.media.model.Attachment} attachment 1196 * @returns {Object} 1197 */ 1198 defaultDisplaySettings: function( attachment ) { 1199 var settings = this._defaultDisplaySettings; 1200 if ( settings.canEmbed = this.canEmbed( attachment ) ) { 1201 settings.link = 'embed'; 1202 } 1203 return settings; 1204 }, 1205 1206 /** 1207 * Whether an attachment can be embedded (audio or video). 1208 * 1209 * @since 3.6.0 1210 * 1211 * @param {wp.media.model.Attachment} attachment 1212 * @returns {Boolean} 1213 */ 1214 canEmbed: function( attachment ) { 1215 // If uploading, we know the filename but not the mime type. 1216 if ( ! attachment.get('uploading') ) { 1217 var type = attachment.get('type'); 1218 if ( type !== 'audio' && type !== 'video' ) { 1219 return false; 1220 } 1221 } 1222 1223 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() ); 1224 }, 1225 1226 1227 /** 1228 * If the state is active, no items are selected, and the current 1229 * content mode is not an option in the state's router (provided 1230 * the state has a router), reset the content mode to the default. 1231 * 1232 * @since 3.5.0 1233 */ 1234 refreshContent: function() { 1235 var selection = this.get('selection'), 1236 frame = this.frame, 1237 router = frame.router.get(), 1238 mode = frame.content.mode(); 1239 1240 if ( this.active && ! selection.length && router && ! router.get( mode ) ) { 1241 this.frame.content.render( this.get('content') ); 1242 } 1243 }, 1244 1245 /** 1246 * Callback handler when an attachment is uploaded. 1247 * 1248 * Switch to the Media Library if uploaded from the 'Upload Files' tab. 1249 * 1250 * Adds any uploading attachments to the selection. 1251 * 1252 * If the state only supports one attachment to be selected and multiple 1253 * attachments are uploaded, the last attachment in the upload queue will 1254 * be selected. 1255 * 1256 * @since 3.5.0 1257 * 1258 * @param {wp.media.model.Attachment} attachment 1259 */ 1260 uploading: function( attachment ) { 1261 var content = this.frame.content; 1262 1263 if ( 'upload' === content.mode() ) { 1264 this.frame.content.mode('browse'); 1265 } 1266 1267 if ( this.get( 'autoSelect' ) ) { 1268 this.get('selection').add( attachment ); 1269 this.frame.trigger( 'library:selection:add' ); 1270 } 1271 }, 1272 1273 /** 1274 * Persist the mode of the content region as a user setting. 1275 * 1276 * @since 3.5.0 1277 */ 1278 saveContentMode: function() { 1279 if ( 'browse' !== this.get('router') ) { 1280 return; 1281 } 1282 1283 var mode = this.frame.content.mode(), 1284 view = this.frame.router.get(); 1285 1286 if ( view && view.get( mode ) ) { 1287 setUserSetting( 'libraryContent', mode ); 1288 } 1289 } 1290 }); 1291 1292 // Make selectionSync available on any Media Library state. 1293 _.extend( Library.prototype, wp.media.selectionSync ); 1294 1295 module.exports = Library; 1296 1297 },{}],11:[function(require,module,exports){ 1298 /*globals wp, _ */ 1299 1300 /** 1301 * wp.media.controller.MediaLibrary 1302 * 1303 * @class 1304 * @augments wp.media.controller.Library 1305 * @augments wp.media.controller.State 1306 * @augments Backbone.Model 1307 */ 1308 var Library = wp.media.controller.Library, 1309 MediaLibrary; 1310 1311 MediaLibrary = Library.extend({ 1312 defaults: _.defaults({ 1313 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1314 filterable: 'uploaded', 1315 1316 displaySettings: false, 1317 priority: 80, 1318 syncSelection: false 1319 }, Library.prototype.defaults ), 1320 1321 /** 1322 * @since 3.9.0 1323 * 1324 * @param options 1325 */ 1326 initialize: function( options ) { 1327 this.media = options.media; 1328 this.type = options.type; 1329 this.set( 'library', wp.media.query({ type: this.type }) ); 1330 1331 Library.prototype.initialize.apply( this, arguments ); 1332 }, 1333 1334 /** 1335 * @since 3.9.0 1336 */ 1337 activate: function() { 1338 // @todo this should use this.frame. 1339 if ( wp.media.frame.lastMime ) { 1340 this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) ); 1341 delete wp.media.frame.lastMime; 1342 } 1343 Library.prototype.activate.apply( this, arguments ); 1344 } 1345 }); 1346 1347 module.exports = MediaLibrary; 1348 1349 },{}],12:[function(require,module,exports){ 1350 /*globals Backbone, _ */ 1351 1352 /** 1353 * wp.media.controller.Region 1354 * 1355 * A region is a persistent application layout area. 1356 * 1357 * A region assumes one mode at any time, and can be switched to another. 1358 * 1359 * When mode changes, events are triggered on the region's parent view. 1360 * The parent view will listen to specific events and fill the region with an 1361 * appropriate view depending on mode. For example, a frame listens for the 1362 * 'browse' mode t be activated on the 'content' view and then fills the region 1363 * with an AttachmentsBrowser view. 1364 * 1365 * @class 1366 * 1367 * @param {object} options Options hash for the region. 1368 * @param {string} options.id Unique identifier for the region. 1369 * @param {Backbone.View} options.view A parent view the region exists within. 1370 * @param {string} options.selector jQuery selector for the region within the parent view. 1371 */ 1372 var Region = function( options ) { 1373 _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); 1374 }; 1375 1376 // Use Backbone's self-propagating `extend` inheritance method. 1377 Region.extend = Backbone.Model.extend; 1378 1379 _.extend( Region.prototype, { 1380 /** 1381 * Activate a mode. 1382 * 1383 * @since 3.5.0 1384 * 1385 * @param {string} mode 1386 * 1387 * @fires this.view#{this.id}:activate:{this._mode} 1388 * @fires this.view#{this.id}:activate 1389 * @fires this.view#{this.id}:deactivate:{this._mode} 1390 * @fires this.view#{this.id}:deactivate 1391 * 1392 * @returns {wp.media.controller.Region} Returns itself to allow chaining. 1393 */ 1394 mode: function( mode ) { 1395 if ( ! mode ) { 1396 return this._mode; 1397 } 1398 // Bail if we're trying to change to the current mode. 1399 if ( mode === this._mode ) { 1400 return this; 1401 } 1402 1403 /** 1404 * Region mode deactivation event. 1405 * 1406 * @event this.view#{this.id}:deactivate:{this._mode} 1407 * @event this.view#{this.id}:deactivate 1408 */ 1409 this.trigger('deactivate'); 1410 1411 this._mode = mode; 1412 this.render( mode ); 1413 1414 /** 1415 * Region mode activation event. 1416 * 1417 * @event this.view#{this.id}:activate:{this._mode} 1418 * @event this.view#{this.id}:activate 1419 */ 1420 this.trigger('activate'); 1421 return this; 1422 }, 1423 /** 1424 * Render a mode. 1425 * 1426 * @since 3.5.0 1427 * 1428 * @param {string} mode 1429 * 1430 * @fires this.view#{this.id}:create:{this._mode} 1431 * @fires this.view#{this.id}:create 1432 * @fires this.view#{this.id}:render:{this._mode} 1433 * @fires this.view#{this.id}:render 1434 * 1435 * @returns {wp.media.controller.Region} Returns itself to allow chaining 1436 */ 1437 render: function( mode ) { 1438 // If the mode isn't active, activate it. 1439 if ( mode && mode !== this._mode ) { 1440 return this.mode( mode ); 1441 } 1442 1443 var set = { view: null }, 1444 view; 1445 1446 /** 1447 * Create region view event. 1448 * 1449 * Region view creation takes place in an event callback on the frame. 1450 * 1451 * @event this.view#{this.id}:create:{this._mode} 1452 * @event this.view#{this.id}:create 1453 */ 1454 this.trigger( 'create', set ); 1455 view = set.view; 1456 1457 /** 1458 * Render region view event. 1459 * 1460 * Region view creation takes place in an event callback on the frame. 1461 * 1462 * @event this.view#{this.id}:create:{this._mode} 1463 * @event this.view#{this.id}:create 1464 */ 1465 this.trigger( 'render', view ); 1466 if ( view ) { 1467 this.set( view ); 1468 } 1469 return this; 1470 }, 1471 1472 /** 1473 * Get the region's view. 1474 * 1475 * @since 3.5.0 1476 * 1477 * @returns {wp.media.View} 1478 */ 1479 get: function() { 1480 return this.view.views.first( this.selector ); 1481 }, 1482 1483 /** 1484 * Set the region's view as a subview of the frame. 1485 * 1486 * @since 3.5.0 1487 * 1488 * @param {Array|Object} views 1489 * @param {Object} [options={}] 1490 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining 1491 */ 1492 set: function( views, options ) { 1493 if ( options ) { 1494 options.add = false; 1495 } 1496 return this.view.views.set( this.selector, views, options ); 1497 }, 1498 1499 /** 1500 * Trigger regional view events on the frame. 1501 * 1502 * @since 3.5.0 1503 * 1504 * @param {string} event 1505 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining. 1506 */ 1507 trigger: function( event ) { 1508 var base, args; 1509 1510 if ( ! this._mode ) { 1511 return; 1512 } 1513 1514 args = _.toArray( arguments ); 1515 base = this.id + ':' + event; 1516 1517 // Trigger `{this.id}:{event}:{this._mode}` event on the frame. 1518 args[0] = base + ':' + this._mode; 1519 this.view.trigger.apply( this.view, args ); 1520 1521 // Trigger `{this.id}:{event}` event on the frame. 1522 args[0] = base; 1523 this.view.trigger.apply( this.view, args ); 1524 return this; 1525 } 1526 }); 1527 1528 module.exports = Region; 1529 1530 },{}],13:[function(require,module,exports){ 1531 /*globals wp, _ */ 1532 1533 /** 1534 * wp.media.controller.ReplaceImage 1535 * 1536 * A state for replacing an image. 1537 * 1538 * @class 1539 * @augments wp.media.controller.Library 1540 * @augments wp.media.controller.State 1541 * @augments Backbone.Model 1542 * 1543 * @param {object} [attributes] The attributes hash passed to the state. 1544 * @param {string} [attributes.id=replace-image] Unique identifier. 1545 * @param {string} [attributes.title=Replace Image] Title for the state. Displays in the media menu and the frame's title region. 1546 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1547 * If one is not supplied, a collection of all images will be created. 1548 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1549 * @param {string} [attributes.content=upload] Initial mode for the content region. 1550 * Overridden by persistent user setting if 'contentUserSetting' is true. 1551 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1552 * @param {string} [attributes.router=browse] Initial mode for the router region. 1553 * @param {string} [attributes.toolbar=replace] Initial mode for the toolbar region. 1554 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1555 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1556 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1557 * Accepts 'all', 'uploaded', or 'unattached'. 1558 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1559 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1560 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1561 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1562 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1563 */ 1564 var Library = wp.media.controller.Library, 1565 l10n = wp.media.view.l10n, 1566 ReplaceImage; 1567 1568 ReplaceImage = Library.extend({ 1569 defaults: _.defaults({ 1570 id: 'replace-image', 1571 title: l10n.replaceImageTitle, 1572 multiple: false, 1573 filterable: 'uploaded', 1574 toolbar: 'replace', 1575 menu: false, 1576 priority: 60, 1577 syncSelection: true 1578 }, Library.prototype.defaults ), 1579 1580 /** 1581 * @since 3.9.0 1582 * 1583 * @param options 1584 */ 1585 initialize: function( options ) { 1586 var library, comparator; 1587 1588 this.image = options.image; 1589 // If we haven't been provided a `library`, create a `Selection`. 1590 if ( ! this.get('library') ) { 1591 this.set( 'library', wp.media.query({ type: 'image' }) ); 1592 } 1593 1594 Library.prototype.initialize.apply( this, arguments ); 1595 1596 library = this.get('library'); 1597 comparator = library.comparator; 1598 1599 // Overload the library's comparator to push items that are not in 1600 // the mirrored query to the front of the aggregate collection. 1601 library.comparator = function( a, b ) { 1602 var aInQuery = !! this.mirroring.get( a.cid ), 1603 bInQuery = !! this.mirroring.get( b.cid ); 1604 1605 if ( ! aInQuery && bInQuery ) { 1606 return -1; 1607 } else if ( aInQuery && ! bInQuery ) { 1608 return 1; 1609 } else { 1610 return comparator.apply( this, arguments ); 1611 } 1612 }; 1613 1614 // Add all items in the selection to the library, so any featured 1615 // images that are not initially loaded still appear. 1616 library.observe( this.get('selection') ); 1617 }, 1618 1619 /** 1620 * @since 3.9.0 1621 */ 1622 activate: function() { 1623 this.updateSelection(); 1624 Library.prototype.activate.apply( this, arguments ); 1625 }, 1626 1627 /** 1628 * @since 3.9.0 1629 */ 1630 updateSelection: function() { 1631 var selection = this.get('selection'), 1632 attachment = this.image.attachment; 1633 1634 selection.reset( attachment ? [ attachment ] : [] ); 1635 } 1636 }); 1637 1638 module.exports = ReplaceImage; 1639 1640 },{}],14:[function(require,module,exports){ 1641 /*globals _, Backbone */ 1642 1643 /** 1644 * wp.media.controller.StateMachine 1645 * 1646 * A state machine keeps track of state. It is in one state at a time, 1647 * and can change from one state to another. 1648 * 1649 * States are stored as models in a Backbone collection. 1650 * 1651 * @since 3.5.0 1652 * 1653 * @class 1654 * @augments Backbone.Model 1655 * @mixin 1656 * @mixes Backbone.Events 1657 * 1658 * @param {Array} states 1659 */ 1660 var StateMachine = function( states ) { 1661 // @todo This is dead code. The states collection gets created in media.view.Frame._createStates. 1662 this.states = new Backbone.Collection( states ); 1663 }; 1664 1665 // Use Backbone's self-propagating `extend` inheritance method. 1666 StateMachine.extend = Backbone.Model.extend; 1667 1668 _.extend( StateMachine.prototype, Backbone.Events, { 1669 /** 1670 * Fetch a state. 1671 * 1672 * If no `id` is provided, returns the active state. 1673 * 1674 * Implicitly creates states. 1675 * 1676 * Ensure that the `states` collection exists so the `StateMachine` 1677 * can be used as a mixin. 1678 * 1679 * @since 3.5.0 1680 * 1681 * @param {string} id 1682 * @returns {wp.media.controller.State} Returns a State model 1683 * from the StateMachine collection 1684 */ 1685 state: function( id ) { 1686 this.states = this.states || new Backbone.Collection(); 1687 1688 // Default to the active state. 1689 id = id || this._state; 1690 1691 if ( id && ! this.states.get( id ) ) { 1692 this.states.add({ id: id }); 1693 } 1694 return this.states.get( id ); 1695 }, 1696 1697 /** 1698 * Sets the active state. 1699 * 1700 * Bail if we're trying to select the current state, if we haven't 1701 * created the `states` collection, or are trying to select a state 1702 * that does not exist. 1703 * 1704 * @since 3.5.0 1705 * 1706 * @param {string} id 1707 * 1708 * @fires wp.media.controller.State#deactivate 1709 * @fires wp.media.controller.State#activate 1710 * 1711 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining 1712 */ 1713 setState: function( id ) { 1714 var previous = this.state(); 1715 1716 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 1717 return this; 1718 } 1719 1720 if ( previous ) { 1721 previous.trigger('deactivate'); 1722 this._lastState = previous.id; 1723 } 1724 1725 this._state = id; 1726 this.state().trigger('activate'); 1727 1728 return this; 1729 }, 1730 1731 /** 1732 * Returns the previous active state. 1733 * 1734 * Call the `state()` method with no parameters to retrieve the current 1735 * active state. 1736 * 1737 * @since 3.5.0 1738 * 1739 * @returns {wp.media.controller.State} Returns a State model 1740 * from the StateMachine collection 1741 */ 1742 lastState: function() { 1743 if ( this._lastState ) { 1744 return this.state( this._lastState ); 1745 } 1746 } 1747 }); 1748 1749 // Map all event binding and triggering on a StateMachine to its `states` collection. 1750 _.each([ 'on', 'off', 'trigger' ], function( method ) { 1751 /** 1752 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 1753 */ 1754 StateMachine.prototype[ method ] = function() { 1755 // Ensure that the `states` collection exists so the `StateMachine` 1756 // can be used as a mixin. 1757 this.states = this.states || new Backbone.Collection(); 1758 // Forward the method to the `states` collection. 1759 this.states[ method ].apply( this.states, arguments ); 1760 return this; 1761 }; 1762 }); 1763 1764 module.exports = StateMachine; 1765 1766 },{}],15:[function(require,module,exports){ 1767 /*globals _, Backbone */ 1768 1769 /** 1770 * wp.media.controller.State 1771 * 1772 * A state is a step in a workflow that when set will trigger the controllers 1773 * for the regions to be updated as specified in the frame. 1774 * 1775 * A state has an event-driven lifecycle: 1776 * 1777 * 'ready' triggers when a state is added to a state machine's collection. 1778 * 'activate' triggers when a state is activated by a state machine. 1779 * 'deactivate' triggers when a state is deactivated by a state machine. 1780 * 'reset' is not triggered automatically. It should be invoked by the 1781 * proper controller to reset the state to its default. 1782 * 1783 * @class 1784 * @augments Backbone.Model 1785 */ 1786 var State = Backbone.Model.extend({ 1787 /** 1788 * Constructor. 1789 * 1790 * @since 3.5.0 1791 */ 1792 constructor: function() { 1793 this.on( 'activate', this._preActivate, this ); 1794 this.on( 'activate', this.activate, this ); 1795 this.on( 'activate', this._postActivate, this ); 1796 this.on( 'deactivate', this._deactivate, this ); 1797 this.on( 'deactivate', this.deactivate, this ); 1798 this.on( 'reset', this.reset, this ); 1799 this.on( 'ready', this._ready, this ); 1800 this.on( 'ready', this.ready, this ); 1801 /** 1802 * Call parent constructor with passed arguments 1803 */ 1804 Backbone.Model.apply( this, arguments ); 1805 this.on( 'change:menu', this._updateMenu, this ); 1806 }, 1807 /** 1808 * Ready event callback. 1809 * 1810 * @abstract 1811 * @since 3.5.0 1812 */ 1813 ready: function() {}, 1814 1815 /** 1816 * Activate event callback. 1817 * 1818 * @abstract 1819 * @since 3.5.0 1820 */ 1821 activate: function() {}, 1822 1823 /** 1824 * Deactivate event callback. 1825 * 1826 * @abstract 1827 * @since 3.5.0 1828 */ 1829 deactivate: function() {}, 1830 1831 /** 1832 * Reset event callback. 1833 * 1834 * @abstract 1835 * @since 3.5.0 1836 */ 1837 reset: function() {}, 1838 1839 /** 1840 * @access private 1841 * @since 3.5.0 1842 */ 1843 _ready: function() { 1844 this._updateMenu(); 1845 }, 1846 1847 /** 1848 * @access private 1849 * @since 3.5.0 1850 */ 1851 _preActivate: function() { 1852 this.active = true; 1853 }, 1854 1855 /** 1856 * @access private 1857 * @since 3.5.0 1858 */ 1859 _postActivate: function() { 1860 this.on( 'change:menu', this._menu, this ); 1861 this.on( 'change:titleMode', this._title, this ); 1862 this.on( 'change:content', this._content, this ); 1863 this.on( 'change:toolbar', this._toolbar, this ); 1864 1865 this.frame.on( 'title:render:default', this._renderTitle, this ); 1866 1867 this._title(); 1868 this._menu(); 1869 this._toolbar(); 1870 this._content(); 1871 this._router(); 1872 }, 1873 1874 /** 1875 * @access private 1876 * @since 3.5.0 1877 */ 1878 _deactivate: function() { 1879 this.active = false; 1880 1881 this.frame.off( 'title:render:default', this._renderTitle, this ); 1882 1883 this.off( 'change:menu', this._menu, this ); 1884 this.off( 'change:titleMode', this._title, this ); 1885 this.off( 'change:content', this._content, this ); 1886 this.off( 'change:toolbar', this._toolbar, this ); 1887 }, 1888 1889 /** 1890 * @access private 1891 * @since 3.5.0 1892 */ 1893 _title: function() { 1894 this.frame.title.render( this.get('titleMode') || 'default' ); 1895 }, 1896 1897 /** 1898 * @access private 1899 * @since 3.5.0 1900 */ 1901 _renderTitle: function( view ) { 1902 view.$el.text( this.get('title') || '' ); 1903 }, 1904 1905 /** 1906 * @access private 1907 * @since 3.5.0 1908 */ 1909 _router: function() { 1910 var router = this.frame.router, 1911 mode = this.get('router'), 1912 view; 1913 1914 this.frame.$el.toggleClass( 'hide-router', ! mode ); 1915 if ( ! mode ) { 1916 return; 1917 } 1918 1919 this.frame.router.render( mode ); 1920 1921 view = router.get(); 1922 if ( view && view.select ) { 1923 view.select( this.frame.content.mode() ); 1924 } 1925 }, 1926 1927 /** 1928 * @access private 1929 * @since 3.5.0 1930 */ 1931 _menu: function() { 1932 var menu = this.frame.menu, 1933 mode = this.get('menu'), 1934 view; 1935 1936 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 1937 if ( ! mode ) { 1938 return; 1939 } 1940 1941 menu.mode( mode ); 1942 1943 view = menu.get(); 1944 if ( view && view.select ) { 1945 view.select( this.id ); 1946 } 1947 }, 1948 1949 /** 1950 * @access private 1951 * @since 3.5.0 1952 */ 1953 _updateMenu: function() { 1954 var previous = this.previous('menu'), 1955 menu = this.get('menu'); 1956 1957 if ( previous ) { 1958 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 1959 } 1960 1961 if ( menu ) { 1962 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 1963 } 1964 }, 1965 1966 /** 1967 * Create a view in the media menu for the state. 1968 * 1969 * @access private 1970 * @since 3.5.0 1971 * 1972 * @param {media.view.Menu} view The menu view. 1973 */ 1974 _renderMenu: function( view ) { 1975 var menuItem = this.get('menuItem'), 1976 title = this.get('title'), 1977 priority = this.get('priority'); 1978 1979 if ( ! menuItem && title ) { 1980 menuItem = { text: title }; 1981 1982 if ( priority ) { 1983 menuItem.priority = priority; 1984 } 1985 } 1986 1987 if ( ! menuItem ) { 1988 return; 1989 } 1990 1991 view.set( this.id, menuItem ); 1992 } 1993 }); 1994 1995 _.each(['toolbar','content'], function( region ) { 1996 /** 1997 * @access private 1998 */ 1999 State.prototype[ '_' + region ] = function() { 2000 var mode = this.get( region ); 2001 if ( mode ) { 2002 this.frame[ region ].render( mode ); 2003 } 2004 }; 2005 }); 2006 2007 module.exports = State; 2008 2009 },{}],16:[function(require,module,exports){ 2010 /*globals _ */ 2011 2012 /** 2013 * wp.media.selectionSync 2014 * 2015 * Sync an attachments selection in a state with another state. 2016 * 2017 * Allows for selecting multiple images in the Insert Media workflow, and then 2018 * switching to the Insert Gallery workflow while preserving the attachments selection. 2019 * 2020 * @mixin 2021 */ 2022 var selectionSync = { 2023 /** 2024 * @since 3.5.0 2025 */ 2026 syncSelection: function() { 2027 var selection = this.get('selection'), 2028 manager = this.frame._selection; 2029 2030 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2031 return; 2032 } 2033 2034 // If the selection supports multiple items, validate the stored 2035 // attachments based on the new selection's conditions. Record 2036 // the attachments that are not included; we'll maintain a 2037 // reference to those. Other attachments are considered in flux. 2038 if ( selection.multiple ) { 2039 selection.reset( [], { silent: true }); 2040 selection.validateAll( manager.attachments ); 2041 manager.difference = _.difference( manager.attachments.models, selection.models ); 2042 } 2043 2044 // Sync the selection's single item with the master. 2045 selection.single( manager.single ); 2046 }, 2047 2048 /** 2049 * Record the currently active attachments, which is a combination 2050 * of the selection's attachments and the set of selected 2051 * attachments that this specific selection considered invalid. 2052 * Reset the difference and record the single attachment. 2053 * 2054 * @since 3.5.0 2055 */ 2056 recordSelection: function() { 2057 var selection = this.get('selection'), 2058 manager = this.frame._selection; 2059 2060 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2061 return; 2062 } 2063 2064 if ( selection.multiple ) { 2065 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 2066 manager.difference = []; 2067 } else { 2068 manager.attachments.add( selection.toArray() ); 2069 } 2070 2071 manager.single = selection._single; 2072 } 2073 }; 2074 2075 module.exports = selectionSync; 2076 2077 },{}],17:[function(require,module,exports){ 2078 /*globals wp, jQuery, _, Backbone */ 2079 2080 var media = wp.media, 2081 $ = jQuery, 2082 l10n; 2083 2084 media.isTouchDevice = ( 'ontouchend' in document ); 2085 2086 // Link any localized strings. 2087 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 2088 2089 // Link any settings. 2090 media.view.settings = l10n.settings || {}; 2091 delete l10n.settings; 2092 2093 // Copy the `post` setting over to the model settings. 2094 media.model.settings.post = media.view.settings.post; 2095 2096 // Check if the browser supports CSS 3.0 transitions 2097 $.support.transition = (function(){ 2098 var style = document.documentElement.style, 2099 transitions = { 2100 WebkitTransition: 'webkitTransitionEnd', 2101 MozTransition: 'transitionend', 2102 OTransition: 'oTransitionEnd otransitionend', 2103 transition: 'transitionend' 2104 }, transition; 2105 2106 transition = _.find( _.keys( transitions ), function( transition ) { 2107 return ! _.isUndefined( style[ transition ] ); 2108 }); 2109 2110 return transition && { 2111 end: transitions[ transition ] 2112 }; 2113 }()); 2114 2115 /** 2116 * A shared event bus used to provide events into 2117 * the media workflows that 3rd-party devs can use to hook 2118 * in. 2119 */ 2120 media.events = _.extend( {}, Backbone.Events ); 2121 2122 /** 2123 * Makes it easier to bind events using transitions. 2124 * 2125 * @param {string} selector 2126 * @param {Number} sensitivity 2127 * @returns {Promise} 2128 */ 2129 media.transition = function( selector, sensitivity ) { 2130 var deferred = $.Deferred(); 2131 2132 sensitivity = sensitivity || 2000; 2133 2134 if ( $.support.transition ) { 2135 if ( ! (selector instanceof $) ) { 2136 selector = $( selector ); 2137 } 2138 2139 // Resolve the deferred when the first element finishes animating. 2140 selector.first().one( $.support.transition.end, deferred.resolve ); 2141 2142 // Just in case the event doesn't trigger, fire a callback. 2143 _.delay( deferred.resolve, sensitivity ); 2144 2145 // Otherwise, execute on the spot. 2146 } else { 2147 deferred.resolve(); 2148 } 2149 2150 return deferred.promise(); 2151 }; 2152 2153 media.controller.Region = require( './controllers/region.js' ); 2154 media.controller.StateMachine = require( './controllers/state-machine.js' ); 2155 media.controller.State = require( './controllers/state.js' ); 2156 2157 media.selectionSync = require( './utils/selection-sync.js' ); 2158 media.controller.Library = require( './controllers/library.js' ); 2159 media.controller.ImageDetails = require( './controllers/image-details.js' ); 2160 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' ); 2161 media.controller.GalleryAdd = require( './controllers/gallery-add.js' ); 2162 media.controller.CollectionEdit = require( './controllers/collection-edit.js' ); 2163 media.controller.CollectionAdd = require( './controllers/collection-add.js' ); 2164 media.controller.FeaturedImage = require( './controllers/featured-image.js' ); 2165 media.controller.ReplaceImage = require( './controllers/replace-image.js' ); 2166 media.controller.EditImage = require( './controllers/edit-image.js' ); 2167 media.controller.MediaLibrary = require( './controllers/media-library.js' ); 2168 media.controller.Embed = require( './controllers/embed.js' ); 2169 media.controller.Cropper = require( './controllers/cropper.js' ); 2170 2171 media.View = require( './views/view.js' ); 2172 media.view.Frame = require( './views/frame.js' ); 2173 media.view.MediaFrame = require( './views/media-frame.js' ); 2174 media.view.MediaFrame.Select = require( './views/frame/select.js' ); 2175 media.view.MediaFrame.Post = require( './views/frame/post.js' ); 2176 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' ); 2177 media.view.Modal = require( './views/modal.js' ); 2178 media.view.FocusManager = require( './views/focus-manager.js' ); 2179 media.view.UploaderWindow = require( './views/uploader/window.js' ); 2180 media.view.EditorUploader = require( './views/uploader/editor.js' ); 2181 media.view.UploaderInline = require( './views/uploader/inline.js' ); 2182 media.view.UploaderStatus = require( './views/uploader/status.js' ); 2183 media.view.UploaderStatusError = require( './views/uploader/status-error.js' ); 2184 media.view.Toolbar = require( './views/toolbar.js' ); 2185 media.view.Toolbar.Select = require( './views/toolbar/select.js' ); 2186 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' ); 2187 media.view.Button = require( './views/button.js' ); 2188 media.view.ButtonGroup = require( './views/button-group.js' ); 2189 media.view.PriorityList = require( './views/priority-list.js' ); 2190 media.view.MenuItem = require( './views/menu-item.js' ); 2191 media.view.Menu = require( './views/menu.js' ); 2192 media.view.RouterItem = require( './views/router-item.js' ); 2193 media.view.Router = require( './views/router.js' ); 2194 media.view.Sidebar = require( './views/sidebar.js' ); 2195 media.view.Attachment = require( './views/attachment.js' ); 2196 media.view.Attachment.Library = require( './views/attachment/library.js' ); 2197 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' ); 2198 media.view.Attachments = require( './views/attachments.js' ); 2199 media.view.Search = require( './views/search.js' ); 2200 media.view.AttachmentFilters = require( './views/attachment-filters.js' ); 2201 media.view.DateFilter = require( './views/attachment-filters/date.js' ); 2202 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' ); 2203 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' ); 2204 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' ); 2205 media.view.Selection = require( './views/selection.js' ); 2206 media.view.Attachment.Selection = require( './views/attachment/selection.js' ); 2207 media.view.Attachments.Selection = require( './views/attachments/selection.js' ); 2208 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' ); 2209 media.view.Settings = require( './views/settings.js' ); 2210 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' ); 2211 media.view.Settings.Gallery = require( './views/settings/gallery.js' ); 2212 media.view.Settings.Playlist = require( './views/settings/playlist.js' ); 2213 media.view.Attachment.Details = require( './views/attachment/details.js' ); 2214 media.view.AttachmentCompat = require( './views/attachment-compat.js' ); 2215 media.view.Iframe = require( './views/iframe.js' ); 2216 media.view.Embed = require( './views/embed.js' ); 2217 media.view.Label = require( './views/label.js' ); 2218 media.view.EmbedUrl = require( './views/embed/url.js' ); 2219 media.view.EmbedLink = require( './views/embed/link.js' ); 2220 media.view.EmbedImage = require( './views/embed/image.js' ); 2221 media.view.ImageDetails = require( './views/image-details.js' ); 2222 media.view.Cropper = require( './views/cropper.js' ); 2223 media.view.EditImage = require( './views/edit-image.js' ); 2224 media.view.Spinner = require( './views/spinner.js' ); 2225 2226 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/edit-image.js":4,"./controllers/embed.js":5,"./controllers/featured-image.js":6,"./controllers/gallery-add.js":7,"./controllers/gallery-edit.js":8,"./controllers/image-details.js":9,"./controllers/library.js":10,"./controllers/media-library.js":11,"./controllers/region.js":12,"./controllers/replace-image.js":13,"./controllers/state-machine.js":14,"./controllers/state.js":15,"./utils/selection-sync.js":16,"./views/attachment-compat.js":18,"./views/attachment-filters.js":19,"./views/attachment-filters/all.js":20,"./views/attachment-filters/date.js":21,"./views/attachment-filters/uploaded.js":22,"./views/attachment.js":23,"./views/attachment/details.js":24,"./views/attachment/edit-library.js":25,"./views/attachment/edit-selection.js":26,"./views/attachment/library.js":27,"./views/attachment/selection.js":28,"./views/attachments.js":29,"./views/attachments/browser.js":30,"./views/attachments/selection.js":31,"./views/button-group.js":32,"./views/button.js":33,"./views/cropper.js":34,"./views/edit-image.js":35,"./views/embed.js":36,"./views/embed/image.js":37,"./views/embed/link.js":38,"./views/embed/url.js":39,"./views/focus-manager.js":40,"./views/frame.js":41,"./views/frame/image-details.js":42,"./views/frame/post.js":43,"./views/frame/select.js":44,"./views/iframe.js":45,"./views/image-details.js":46,"./views/label.js":47,"./views/media-frame.js":48,"./views/menu-item.js":49,"./views/menu.js":50,"./views/modal.js":51,"./views/priority-list.js":52,"./views/router-item.js":53,"./views/router.js":54,"./views/search.js":55,"./views/selection.js":56,"./views/settings.js":57,"./views/settings/attachment-display.js":58,"./views/settings/gallery.js":59,"./views/settings/playlist.js":60,"./views/sidebar.js":61,"./views/spinner.js":62,"./views/toolbar.js":63,"./views/toolbar/embed.js":64,"./views/toolbar/select.js":65,"./views/uploader/editor.js":66,"./views/uploader/inline.js":67,"./views/uploader/status-error.js":68,"./views/uploader/status.js":69,"./views/uploader/window.js":70,"./views/view.js":71}],18:[function(require,module,exports){ 2227 /*globals _ */ 2228 2229 /** 2230 * wp.media.view.AttachmentCompat 2231 * 2232 * A view to display fields added via the `attachment_fields_to_edit` filter. 2233 * 2234 * @class 2235 * @augments wp.media.View 2236 * @augments wp.Backbone.View 2237 * @augments Backbone.View 2238 */ 2239 var View = wp.media.View, 2240 AttachmentCompat; 2241 2242 AttachmentCompat = View.extend({ 2243 tagName: 'form', 2244 className: 'compat-item', 2245 2246 events: { 2247 'submit': 'preventDefault', 2248 'change input': 'save', 2249 'change select': 'save', 2250 'change textarea': 'save' 2251 }, 2252 2253 initialize: function() { 2254 this.listenTo( this.model, 'change:compat', this.render ); 2255 }, 2256 /** 2257 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 2258 */ 2259 dispose: function() { 2260 if ( this.$(':focus').length ) { 2261 this.save(); 2262 } 2263 /** 2264 * call 'dispose' directly on the parent class 2265 */ 2266 return View.prototype.dispose.apply( this, arguments ); 2267 }, 2268 /** 2269 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 2270 */ 2271 render: function() { 2272 var compat = this.model.get('compat'); 2273 if ( ! compat || ! compat.item ) { 2274 return; 2275 } 2276 2277 this.views.detach(); 2278 this.$el.html( compat.item ); 2279 this.views.render(); 2280 return this; 2281 }, 2282 /** 2283 * @param {Object} event 2284 */ 2285 preventDefault: function( event ) { 2286 event.preventDefault(); 2287 }, 2288 /** 2289 * @param {Object} event 2290 */ 2291 save: function( event ) { 2292 var data = {}; 2293 2294 if ( event ) { 2295 event.preventDefault(); 2296 } 2297 2298 _.each( this.$el.serializeArray(), function( pair ) { 2299 data[ pair.name ] = pair.value; 2300 }); 2301 2302 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 2303 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 2304 }, 2305 2306 postSave: function() { 2307 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 2308 } 2309 }); 2310 2311 module.exports = AttachmentCompat; 2312 2313 },{}],19:[function(require,module,exports){ 2314 /*globals _, jQuery */ 2315 2316 /** 2317 * wp.media.view.AttachmentFilters 2318 * 2319 * @class 2320 * @augments wp.media.View 2321 * @augments wp.Backbone.View 2322 * @augments Backbone.View 2323 */ 2324 var $ = jQuery, 2325 AttachmentFilters; 2326 2327 AttachmentFilters = wp.media.View.extend({ 2328 tagName: 'select', 2329 className: 'attachment-filters', 2330 id: 'media-attachment-filters', 2331 2332 events: { 2333 change: 'change' 2334 }, 2335 2336 keys: [], 2337 2338 initialize: function() { 2339 this.createFilters(); 2340 _.extend( this.filters, this.options.filters ); 2341 2342 // Build `<option>` elements. 2343 this.$el.html( _.chain( this.filters ).map( function( filter, value ) { 2344 return { 2345 el: $( '<option></option>' ).val( value ).html( filter.text )[0], 2346 priority: filter.priority || 50 2347 }; 2348 }, this ).sortBy('priority').pluck('el').value() ); 2349 2350 this.listenTo( this.model, 'change', this.select ); 2351 this.select(); 2352 }, 2353 2354 /** 2355 * @abstract 2356 */ 2357 createFilters: function() { 2358 this.filters = {}; 2359 }, 2360 2361 /** 2362 * When the selected filter changes, update the Attachment Query properties to match. 2363 */ 2364 change: function() { 2365 var filter = this.filters[ this.el.value ]; 2366 if ( filter ) { 2367 this.model.set( filter.props ); 2368 } 2369 }, 2370 2371 select: function() { 2372 var model = this.model, 2373 value = 'all', 2374 props = model.toJSON(); 2375 2376 _.find( this.filters, function( filter, id ) { 2377 var equal = _.all( filter.props, function( prop, key ) { 2378 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] ); 2379 }); 2380 2381 if ( equal ) { 2382 return value = id; 2383 } 2384 }); 2385 2386 this.$el.val( value ); 2387 } 2388 }); 2389 2390 module.exports = AttachmentFilters; 2391 2392 },{}],20:[function(require,module,exports){ 2393 /*globals wp */ 2394 2395 /** 2396 * wp.media.view.AttachmentFilters.All 2397 * 2398 * @class 2399 * @augments wp.media.view.AttachmentFilters 2400 * @augments wp.media.View 2401 * @augments wp.Backbone.View 2402 * @augments Backbone.View 2403 */ 2404 var l10n = wp.media.view.l10n, 2405 All; 2406 2407 All = wp.media.view.AttachmentFilters.extend({ 2408 createFilters: function() { 2409 var filters = {}; 2410 2411 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 2412 filters[ key ] = { 2413 text: text, 2414 props: { 2415 status: null, 2416 type: key, 2417 uploadedTo: null, 2418 orderby: 'date', 2419 order: 'DESC' 2420 } 2421 }; 2422 }); 2423 2424 filters.all = { 2425 text: l10n.allMediaItems, 2426 props: { 2427 status: null, 2428 type: null, 2429 uploadedTo: null, 2430 orderby: 'date', 2431 order: 'DESC' 2432 }, 2433 priority: 10 2434 }; 2435 2436 if ( wp.media.view.settings.post.id ) { 2437 filters.uploaded = { 2438 text: l10n.uploadedToThisPost, 2439 props: { 2440 status: null, 2441 type: null, 2442 uploadedTo: wp.media.view.settings.post.id, 2443 orderby: 'menuOrder', 2444 order: 'ASC' 2445 }, 2446 priority: 20 2447 }; 2448 } 2449 2450 filters.unattached = { 2451 text: l10n.unattached, 2452 props: { 2453 status: null, 2454 uploadedTo: 0, 2455 type: null, 2456 orderby: 'menuOrder', 2457 order: 'ASC' 2458 }, 2459 priority: 50 2460 }; 2461 2462 if ( wp.media.view.settings.mediaTrash && 2463 this.controller.isModeActive( 'grid' ) ) { 2464 2465 filters.trash = { 2466 text: l10n.trash, 2467 props: { 2468 uploadedTo: null, 2469 status: 'trash', 2470 type: null, 2471 orderby: 'date', 2472 order: 'DESC' 2473 }, 2474 priority: 50 2475 }; 2476 } 2477 2478 this.filters = filters; 2479 } 2480 }); 2481 2482 module.exports = All; 2483 2484 },{}],21:[function(require,module,exports){ 2485 /*globals wp, _ */ 2486 2487 /** 2488 * A filter dropdown for month/dates. 2489 * 2490 * @class 2491 * @augments wp.media.view.AttachmentFilters 2492 * @augments wp.media.View 2493 * @augments wp.Backbone.View 2494 * @augments Backbone.View 2495 */ 2496 var l10n = wp.media.view.l10n, 2497 DateFilter; 2498 2499 DateFilter = wp.media.view.AttachmentFilters.extend({ 2500 id: 'media-attachment-date-filters', 2501 2502 createFilters: function() { 2503 var filters = {}; 2504 _.each( wp.media.view.settings.months || {}, function( value, index ) { 2505 filters[ index ] = { 2506 text: value.text, 2507 props: { 2508 year: value.year, 2509 monthnum: value.month 2510 } 2511 }; 2512 }); 2513 filters.all = { 2514 text: l10n.allDates, 2515 props: { 2516 monthnum: false, 2517 year: false 2518 }, 2519 priority: 10 2520 }; 2521 this.filters = filters; 2522 } 2523 }); 2524 2525 module.exports = DateFilter; 2526 2527 },{}],22:[function(require,module,exports){ 2528 /*globals wp */ 2529 2530 /** 2531 * wp.media.view.AttachmentFilters.Uploaded 2532 * 2533 * @class 2534 * @augments wp.media.view.AttachmentFilters 2535 * @augments wp.media.View 2536 * @augments wp.Backbone.View 2537 * @augments Backbone.View 2538 */ 2539 var l10n = wp.media.view.l10n, 2540 Uploaded; 2541 2542 Uploaded = wp.media.view.AttachmentFilters.extend({ 2543 createFilters: function() { 2544 var type = this.model.get('type'), 2545 types = wp.media.view.settings.mimeTypes, 2546 text; 2547 2548 if ( types && type ) { 2549 text = types[ type ]; 2550 } 2551 2552 this.filters = { 2553 all: { 2554 text: text || l10n.allMediaItems, 2555 props: { 2556 uploadedTo: null, 2557 orderby: 'date', 2558 order: 'DESC' 2559 }, 2560 priority: 10 2561 }, 2562 2563 uploaded: { 2564 text: l10n.uploadedToThisPost, 2565 props: { 2566 uploadedTo: wp.media.view.settings.post.id, 2567 orderby: 'menuOrder', 2568 order: 'ASC' 2569 }, 2570 priority: 20 2571 }, 2572 2573 unattached: { 2574 text: l10n.unattached, 2575 props: { 2576 uploadedTo: 0, 2577 orderby: 'menuOrder', 2578 order: 'ASC' 2579 }, 2580 priority: 50 2581 } 2582 }; 2583 } 2584 }); 2585 2586 module.exports = Uploaded; 2587 2588 },{}],23:[function(require,module,exports){ 2589 /*globals wp, _, jQuery */ 2590 2591 /** 2592 * wp.media.view.Attachment 2593 * 2594 * @class 2595 * @augments wp.media.View 2596 * @augments wp.Backbone.View 2597 * @augments Backbone.View 2598 */ 2599 var View = wp.media.View, 2600 $ = jQuery, 2601 Attachment; 2602 2603 Attachment = View.extend({ 2604 tagName: 'li', 2605 className: 'attachment', 2606 template: wp.template('attachment'), 2607 2608 attributes: function() { 2609 return { 2610 'tabIndex': 0, 2611 'role': 'checkbox', 2612 'aria-label': this.model.get( 'title' ), 2613 'aria-checked': false, 2614 'data-id': this.model.get( 'id' ) 2615 }; 2616 }, 2617 2618 events: { 2619 'click .js--select-attachment': 'toggleSelectionHandler', 2620 'change [data-setting]': 'updateSetting', 2621 'change [data-setting] input': 'updateSetting', 2622 'change [data-setting] select': 'updateSetting', 2623 'change [data-setting] textarea': 'updateSetting', 2624 'click .close': 'removeFromLibrary', 2625 'click .check': 'checkClickHandler', 2626 'click a': 'preventDefault', 2627 'keydown .close': 'removeFromLibrary', 2628 'keydown': 'toggleSelectionHandler' 2629 }, 2630 2631 buttons: {}, 2632 2633 initialize: function() { 2634 var selection = this.options.selection, 2635 options = _.defaults( this.options, { 2636 rerenderOnModelChange: true 2637 } ); 2638 2639 if ( options.rerenderOnModelChange ) { 2640 this.listenTo( this.model, 'change', this.render ); 2641 } else { 2642 this.listenTo( this.model, 'change:percent', this.progress ); 2643 } 2644 this.listenTo( this.model, 'change:title', this._syncTitle ); 2645 this.listenTo( this.model, 'change:caption', this._syncCaption ); 2646 this.listenTo( this.model, 'change:artist', this._syncArtist ); 2647 this.listenTo( this.model, 'change:album', this._syncAlbum ); 2648 2649 // Update the selection. 2650 this.listenTo( this.model, 'add', this.select ); 2651 this.listenTo( this.model, 'remove', this.deselect ); 2652 if ( selection ) { 2653 selection.on( 'reset', this.updateSelect, this ); 2654 // Update the model's details view. 2655 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 2656 this.details( this.model, this.controller.state().get('selection') ); 2657 } 2658 2659 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 2660 }, 2661 /** 2662 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 2663 */ 2664 dispose: function() { 2665 var selection = this.options.selection; 2666 2667 // Make sure all settings are saved before removing the view. 2668 this.updateAll(); 2669 2670 if ( selection ) { 2671 selection.off( null, null, this ); 2672 } 2673 /** 2674 * call 'dispose' directly on the parent class 2675 */ 2676 View.prototype.dispose.apply( this, arguments ); 2677 return this; 2678 }, 2679 /** 2680 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 2681 */ 2682 render: function() { 2683 var options = _.defaults( this.model.toJSON(), { 2684 orientation: 'landscape', 2685 uploading: false, 2686 type: '', 2687 subtype: '', 2688 icon: '', 2689 filename: '', 2690 caption: '', 2691 title: '', 2692 dateFormatted: '', 2693 width: '', 2694 height: '', 2695 compat: false, 2696 alt: '', 2697 description: '' 2698 }, this.options ); 2699 2700 options.buttons = this.buttons; 2701 options.describe = this.controller.state().get('describe'); 2702 2703 if ( 'image' === options.type ) { 2704 options.size = this.imageSize(); 2705 } 2706 2707 options.can = {}; 2708 if ( options.nonces ) { 2709 options.can.remove = !! options.nonces['delete']; 2710 options.can.save = !! options.nonces.update; 2711 } 2712 2713 if ( this.controller.state().get('allowLocalEdits') ) { 2714 options.allowLocalEdits = true; 2715 } 2716 2717 if ( options.uploading && ! options.percent ) { 2718 options.percent = 0; 2719 } 2720 2721 this.views.detach(); 2722 this.$el.html( this.template( options ) ); 2723 2724 this.$el.toggleClass( 'uploading', options.uploading ); 2725 2726 if ( options.uploading ) { 2727 this.$bar = this.$('.media-progress-bar div'); 2728 } else { 2729 delete this.$bar; 2730 } 2731 2732 // Check if the model is selected. 2733 this.updateSelect(); 2734 2735 // Update the save status. 2736 this.updateSave(); 2737 2738 this.views.render(); 2739 2740 return this; 2741 }, 2742 2743 progress: function() { 2744 if ( this.$bar && this.$bar.length ) { 2745 this.$bar.width( this.model.get('percent') + '%' ); 2746 } 2747 }, 2748 2749 /** 2750 * @param {Object} event 2751 */ 2752 toggleSelectionHandler: function( event ) { 2753 var method; 2754 2755 // Don't do anything inside inputs. 2756 if ( 'INPUT' === event.target.nodeName ) { 2757 return; 2758 } 2759 2760 // Catch arrow events 2761 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 2762 this.controller.trigger( 'attachment:keydown:arrow', event ); 2763 return; 2764 } 2765 2766 // Catch enter and space events 2767 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 2768 return; 2769 } 2770 2771 event.preventDefault(); 2772 2773 // In the grid view, bubble up an edit:attachment event to the controller. 2774 if ( this.controller.isModeActive( 'grid' ) ) { 2775 if ( this.controller.isModeActive( 'edit' ) ) { 2776 // Pass the current target to restore focus when closing 2777 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 2778 return; 2779 } 2780 2781 if ( this.controller.isModeActive( 'select' ) ) { 2782 method = 'toggle'; 2783 } 2784 } 2785 2786 if ( event.shiftKey ) { 2787 method = 'between'; 2788 } else if ( event.ctrlKey || event.metaKey ) { 2789 method = 'toggle'; 2790 } 2791 2792 this.toggleSelection({ 2793 method: method 2794 }); 2795 2796 this.controller.trigger( 'selection:toggle' ); 2797 }, 2798 /** 2799 * @param {Object} options 2800 */ 2801 toggleSelection: function( options ) { 2802 var collection = this.collection, 2803 selection = this.options.selection, 2804 model = this.model, 2805 method = options && options.method, 2806 single, models, singleIndex, modelIndex; 2807 2808 if ( ! selection ) { 2809 return; 2810 } 2811 2812 single = selection.single(); 2813 method = _.isUndefined( method ) ? selection.multiple : method; 2814 2815 // If the `method` is set to `between`, select all models that 2816 // exist between the current and the selected model. 2817 if ( 'between' === method && single && selection.multiple ) { 2818 // If the models are the same, short-circuit. 2819 if ( single === model ) { 2820 return; 2821 } 2822 2823 singleIndex = collection.indexOf( single ); 2824 modelIndex = collection.indexOf( this.model ); 2825 2826 if ( singleIndex < modelIndex ) { 2827 models = collection.models.slice( singleIndex, modelIndex + 1 ); 2828 } else { 2829 models = collection.models.slice( modelIndex, singleIndex + 1 ); 2830 } 2831 2832 selection.add( models ); 2833 selection.single( model ); 2834 return; 2835 2836 // If the `method` is set to `toggle`, just flip the selection 2837 // status, regardless of whether the model is the single model. 2838 } else if ( 'toggle' === method ) { 2839 selection[ this.selected() ? 'remove' : 'add' ]( model ); 2840 selection.single( model ); 2841 return; 2842 } else if ( 'add' === method ) { 2843 selection.add( model ); 2844 selection.single( model ); 2845 return; 2846 } 2847 2848 // Fixes bug that loses focus when selecting a featured image 2849 if ( ! method ) { 2850 method = 'add'; 2851 } 2852 2853 if ( method !== 'add' ) { 2854 method = 'reset'; 2855 } 2856 2857 if ( this.selected() ) { 2858 // If the model is the single model, remove it. 2859 // If it is not the same as the single model, 2860 // it now becomes the single model. 2861 selection[ single === model ? 'remove' : 'single' ]( model ); 2862 } else { 2863 // If the model is not selected, run the `method` on the 2864 // selection. By default, we `reset` the selection, but the 2865 // `method` can be set to `add` the model to the selection. 2866 selection[ method ]( model ); 2867 selection.single( model ); 2868 } 2869 }, 2870 2871 updateSelect: function() { 2872 this[ this.selected() ? 'select' : 'deselect' ](); 2873 }, 2874 /** 2875 * @returns {unresolved|Boolean} 2876 */ 2877 selected: function() { 2878 var selection = this.options.selection; 2879 if ( selection ) { 2880 return !! selection.get( this.model.cid ); 2881 } 2882 }, 2883 /** 2884 * @param {Backbone.Model} model 2885 * @param {Backbone.Collection} collection 2886 */ 2887 select: function( model, collection ) { 2888 var selection = this.options.selection, 2889 controller = this.controller; 2890 2891 // Check if a selection exists and if it's the collection provided. 2892 // If they're not the same collection, bail; we're in another 2893 // selection's event loop. 2894 if ( ! selection || ( collection && collection !== selection ) ) { 2895 return; 2896 } 2897 2898 // Bail if the model is already selected. 2899 if ( this.$el.hasClass( 'selected' ) ) { 2900 return; 2901 } 2902 2903 // Add 'selected' class to model, set aria-checked to true. 2904 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 2905 // Make the checkbox tabable, except in media grid (bulk select mode). 2906 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 2907 this.$( '.check' ).attr( 'tabindex', '0' ); 2908 } 2909 }, 2910 /** 2911 * @param {Backbone.Model} model 2912 * @param {Backbone.Collection} collection 2913 */ 2914 deselect: function( model, collection ) { 2915 var selection = this.options.selection; 2916 2917 // Check if a selection exists and if it's the collection provided. 2918 // If they're not the same collection, bail; we're in another 2919 // selection's event loop. 2920 if ( ! selection || ( collection && collection !== selection ) ) { 2921 return; 2922 } 2923 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 2924 .find( '.check' ).attr( 'tabindex', '-1' ); 2925 }, 2926 /** 2927 * @param {Backbone.Model} model 2928 * @param {Backbone.Collection} collection 2929 */ 2930 details: function( model, collection ) { 2931 var selection = this.options.selection, 2932 details; 2933 2934 if ( selection !== collection ) { 2935 return; 2936 } 2937 2938 details = selection.single(); 2939 this.$el.toggleClass( 'details', details === this.model ); 2940 }, 2941 /** 2942 * @param {Object} event 2943 */ 2944 preventDefault: function( event ) { 2945 event.preventDefault(); 2946 }, 2947 /** 2948 * @param {string} size 2949 * @returns {Object} 2950 */ 2951 imageSize: function( size ) { 2952 var sizes = this.model.get('sizes'), matched = false; 2953 2954 size = size || 'medium'; 2955 2956 // Use the provided image size if possible. 2957 if ( sizes ) { 2958 if ( sizes[ size ] ) { 2959 matched = sizes[ size ]; 2960 } else if ( sizes.large ) { 2961 matched = sizes.large; 2962 } else if ( sizes.thumbnail ) { 2963 matched = sizes.thumbnail; 2964 } else if ( sizes.full ) { 2965 matched = sizes.full; 2966 } 2967 2968 if ( matched ) { 2969 return _.clone( matched ); 2970 } 2971 } 2972 2973 return { 2974 url: this.model.get('url'), 2975 width: this.model.get('width'), 2976 height: this.model.get('height'), 2977 orientation: this.model.get('orientation') 2978 }; 2979 }, 2980 /** 2981 * @param {Object} event 2982 */ 2983 updateSetting: function( event ) { 2984 var $setting = $( event.target ).closest('[data-setting]'), 2985 setting, value; 2986 2987 if ( ! $setting.length ) { 2988 return; 2989 } 2990 2991 setting = $setting.data('setting'); 2992 value = event.target.value; 2993 2994 if ( this.model.get( setting ) !== value ) { 2995 this.save( setting, value ); 2996 } 2997 }, 2998 2999 /** 3000 * Pass all the arguments to the model's save method. 3001 * 3002 * Records the aggregate status of all save requests and updates the 3003 * view's classes accordingly. 3004 */ 3005 save: function() { 3006 var view = this, 3007 save = this._save = this._save || { status: 'ready' }, 3008 request = this.model.save.apply( this.model, arguments ), 3009 requests = save.requests ? $.when( request, save.requests ) : request; 3010 3011 // If we're waiting to remove 'Saved.', stop. 3012 if ( save.savedTimer ) { 3013 clearTimeout( save.savedTimer ); 3014 } 3015 3016 this.updateSave('waiting'); 3017 save.requests = requests; 3018 requests.always( function() { 3019 // If we've performed another request since this one, bail. 3020 if ( save.requests !== requests ) { 3021 return; 3022 } 3023 3024 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 3025 save.savedTimer = setTimeout( function() { 3026 view.updateSave('ready'); 3027 delete save.savedTimer; 3028 }, 2000 ); 3029 }); 3030 }, 3031 /** 3032 * @param {string} status 3033 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3034 */ 3035 updateSave: function( status ) { 3036 var save = this._save = this._save || { status: 'ready' }; 3037 3038 if ( status && status !== save.status ) { 3039 this.$el.removeClass( 'save-' + save.status ); 3040 save.status = status; 3041 } 3042 3043 this.$el.addClass( 'save-' + save.status ); 3044 return this; 3045 }, 3046 3047 updateAll: function() { 3048 var $settings = this.$('[data-setting]'), 3049 model = this.model, 3050 changed; 3051 3052 changed = _.chain( $settings ).map( function( el ) { 3053 var $input = $('input, textarea, select, [value]', el ), 3054 setting, value; 3055 3056 if ( ! $input.length ) { 3057 return; 3058 } 3059 3060 setting = $(el).data('setting'); 3061 value = $input.val(); 3062 3063 // Record the value if it changed. 3064 if ( model.get( setting ) !== value ) { 3065 return [ setting, value ]; 3066 } 3067 }).compact().object().value(); 3068 3069 if ( ! _.isEmpty( changed ) ) { 3070 model.save( changed ); 3071 } 3072 }, 3073 /** 3074 * @param {Object} event 3075 */ 3076 removeFromLibrary: function( event ) { 3077 // Catch enter and space events 3078 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 3079 return; 3080 } 3081 3082 // Stop propagation so the model isn't selected. 3083 event.stopPropagation(); 3084 3085 this.collection.remove( this.model ); 3086 }, 3087 3088 /** 3089 * Add the model if it isn't in the selection, if it is in the selection, 3090 * remove it. 3091 * 3092 * @param {[type]} event [description] 3093 * @return {[type]} [description] 3094 */ 3095 checkClickHandler: function ( event ) { 3096 var selection = this.options.selection; 3097 if ( ! selection ) { 3098 return; 3099 } 3100 event.stopPropagation(); 3101 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 3102 selection.remove( this.model ); 3103 // Move focus back to the attachment tile (from the check). 3104 this.$el.focus(); 3105 } else { 3106 selection.add( this.model ); 3107 } 3108 } 3109 }); 3110 3111 // Ensure settings remain in sync between attachment views. 3112 _.each({ 3113 caption: '_syncCaption', 3114 title: '_syncTitle', 3115 artist: '_syncArtist', 3116 album: '_syncAlbum' 3117 }, function( method, setting ) { 3118 /** 3119 * @param {Backbone.Model} model 3120 * @param {string} value 3121 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3122 */ 3123 Attachment.prototype[ method ] = function( model, value ) { 3124 var $setting = this.$('[data-setting="' + setting + '"]'); 3125 3126 if ( ! $setting.length ) { 3127 return this; 3128 } 3129 3130 // If the updated value is in sync with the value in the DOM, there 3131 // is no need to re-render. If we're currently editing the value, 3132 // it will automatically be in sync, suppressing the re-render for 3133 // the view we're editing, while updating any others. 3134 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 3135 return this; 3136 } 3137 3138 return this.render(); 3139 }; 3140 }); 3141 3142 module.exports = Attachment; 3143 3144 },{}],24:[function(require,module,exports){ 3145 /*globals wp, _ */ 3146 3147 /** 3148 * wp.media.view.Attachment.Details 3149 * 3150 * @class 3151 * @augments wp.media.view.Attachment 3152 * @augments wp.media.View 3153 * @augments wp.Backbone.View 3154 * @augments Backbone.View 3155 */ 3156 var Attachment = wp.media.view.Attachment, 3157 l10n = wp.media.view.l10n, 3158 Details; 3159 3160 Details = Attachment.extend({ 3161 tagName: 'div', 3162 className: 'attachment-details', 3163 template: wp.template('attachment-details'), 3164 3165 attributes: function() { 3166 return { 3167 'tabIndex': 0, 3168 'data-id': this.model.get( 'id' ) 3169 }; 3170 }, 3171 3172 events: { 3173 'change [data-setting]': 'updateSetting', 3174 'change [data-setting] input': 'updateSetting', 3175 'change [data-setting] select': 'updateSetting', 3176 'change [data-setting] textarea': 'updateSetting', 3177 'click .delete-attachment': 'deleteAttachment', 3178 'click .trash-attachment': 'trashAttachment', 3179 'click .untrash-attachment': 'untrashAttachment', 3180 'click .edit-attachment': 'editAttachment', 3181 'click .refresh-attachment': 'refreshAttachment', 3182 'keydown': 'toggleSelectionHandler' 3183 }, 3184 3185 initialize: function() { 3186 this.options = _.defaults( this.options, { 3187 rerenderOnModelChange: false 3188 }); 3189 3190 this.on( 'ready', this.initialFocus ); 3191 // Call 'initialize' directly on the parent class. 3192 Attachment.prototype.initialize.apply( this, arguments ); 3193 }, 3194 3195 initialFocus: function() { 3196 if ( ! wp.media.isTouchDevice ) { 3197 this.$( ':input' ).eq( 0 ).focus(); 3198 } 3199 }, 3200 /** 3201 * @param {Object} event 3202 */ 3203 deleteAttachment: function( event ) { 3204 event.preventDefault(); 3205 3206 if ( window.confirm( l10n.warnDelete ) ) { 3207 this.model.destroy(); 3208 // Keep focus inside media modal 3209 // after image is deleted 3210 this.controller.modal.focusManager.focus(); 3211 } 3212 }, 3213 /** 3214 * @param {Object} event 3215 */ 3216 trashAttachment: function( event ) { 3217 var library = this.controller.library; 3218 event.preventDefault(); 3219 3220 if ( wp.media.view.settings.mediaTrash && 3221 'edit-metadata' === this.controller.content.mode() ) { 3222 3223 this.model.set( 'status', 'trash' ); 3224 this.model.save().done( function() { 3225 library._requery( true ); 3226 } ); 3227 } else { 3228 this.model.destroy(); 3229 } 3230 }, 3231 /** 3232 * @param {Object} event 3233 */ 3234 untrashAttachment: function( event ) { 3235 var library = this.controller.library; 3236 event.preventDefault(); 3237 3238 this.model.set( 'status', 'inherit' ); 3239 this.model.save().done( function() { 3240 library._requery( true ); 3241 } ); 3242 }, 3243 /** 3244 * @param {Object} event 3245 */ 3246 editAttachment: function( event ) { 3247 var editState = this.controller.states.get( 'edit-image' ); 3248 if ( window.imageEdit && editState ) { 3249 event.preventDefault(); 3250 3251 editState.set( 'image', this.model ); 3252 this.controller.setState( 'edit-image' ); 3253 } else { 3254 this.$el.addClass('needs-refresh'); 3255 } 3256 }, 3257 /** 3258 * @param {Object} event 3259 */ 3260 refreshAttachment: function( event ) { 3261 this.$el.removeClass('needs-refresh'); 3262 event.preventDefault(); 3263 this.model.fetch(); 3264 }, 3265 /** 3266 * When reverse tabbing(shift+tab) out of the right details panel, deliver 3267 * the focus to the item in the list that was being edited. 3268 * 3269 * @param {Object} event 3270 */ 3271 toggleSelectionHandler: function( event ) { 3272 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 3273 this.controller.trigger( 'attachment:details:shift-tab', event ); 3274 return false; 3275 } 3276 3277 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 3278 this.controller.trigger( 'attachment:keydown:arrow', event ); 3279 return; 3280 } 3281 } 3282 }); 3283 3284 module.exports = Details; 3285 3286 },{}],25:[function(require,module,exports){ 3287 /*globals wp */ 3288 3289 /** 3290 * wp.media.view.Attachment.EditLibrary 3291 * 3292 * @class 3293 * @augments wp.media.view.Attachment 3294 * @augments wp.media.View 3295 * @augments wp.Backbone.View 3296 * @augments Backbone.View 3297 */ 3298 var EditLibrary = wp.media.view.Attachment.extend({ 3299 buttons: { 3300 close: true 3301 } 3302 }); 3303 3304 module.exports = EditLibrary; 3305 3306 },{}],26:[function(require,module,exports){ 3307 /*globals wp */ 3308 3309 /** 3310 * wp.media.view.Attachments.EditSelection 3311 * 3312 * @class 3313 * @augments wp.media.view.Attachment.Selection 3314 * @augments wp.media.view.Attachment 3315 * @augments wp.media.View 3316 * @augments wp.Backbone.View 3317 * @augments Backbone.View 3318 */ 3319 var EditSelection = wp.media.view.Attachment.Selection.extend({ 3320 buttons: { 3321 close: true 3322 } 3323 }); 3324 3325 module.exports = EditSelection; 3326 3327 },{}],27:[function(require,module,exports){ 3328 /*globals wp */ 3329 3330 /** 3331 * wp.media.view.Attachment.Library 3332 * 3333 * @class 3334 * @augments wp.media.view.Attachment 3335 * @augments wp.media.View 3336 * @augments wp.Backbone.View 3337 * @augments Backbone.View 3338 */ 3339 var Library = wp.media.view.Attachment.extend({ 3340 buttons: { 3341 check: true 3342 } 3343 }); 3344 3345 module.exports = Library; 3346 3347 },{}],28:[function(require,module,exports){ 3348 /*globals wp */ 3349 3350 /** 3351 * wp.media.view.Attachment.Selection 3352 * 3353 * @class 3354 * @augments wp.media.view.Attachment 3355 * @augments wp.media.View 3356 * @augments wp.Backbone.View 3357 * @augments Backbone.View 3358 */ 3359 var Selection = wp.media.view.Attachment.extend({ 3360 className: 'attachment selection', 3361 3362 // On click, just select the model, instead of removing the model from 3363 // the selection. 3364 toggleSelection: function() { 3365 this.options.selection.single( this.model ); 3366 } 3367 }); 3368 3369 module.exports = Selection; 3370 3371 },{}],29:[function(require,module,exports){ 3372 /*globals wp, _, jQuery */ 3373 3374 /** 3375 * wp.media.view.Attachments 3376 * 3377 * @class 3378 * @augments wp.media.View 3379 * @augments wp.Backbone.View 3380 * @augments Backbone.View 3381 */ 3382 var View = wp.media.View, 3383 $ = jQuery, 3384 Attachments; 3385 3386 Attachments = View.extend({ 3387 tagName: 'ul', 3388 className: 'attachments', 3389 3390 attributes: { 3391 tabIndex: -1 3392 }, 3393 3394 initialize: function() { 3395 this.el.id = _.uniqueId('__attachments-view-'); 3396 3397 _.defaults( this.options, { 3398 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 3399 refreshThreshold: 3, 3400 AttachmentView: wp.media.view.Attachment, 3401 sortable: false, 3402 resize: true, 3403 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 3404 }); 3405 3406 this._viewsByCid = {}; 3407 this.$window = $( window ); 3408 this.resizeEvent = 'resize.media-modal-columns'; 3409 3410 this.collection.on( 'add', function( attachment ) { 3411 this.views.add( this.createAttachmentView( attachment ), { 3412 at: this.collection.indexOf( attachment ) 3413 }); 3414 }, this ); 3415 3416 this.collection.on( 'remove', function( attachment ) { 3417 var view = this._viewsByCid[ attachment.cid ]; 3418 delete this._viewsByCid[ attachment.cid ]; 3419 3420 if ( view ) { 3421 view.remove(); 3422 } 3423 }, this ); 3424 3425 this.collection.on( 'reset', this.render, this ); 3426 3427 this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus ); 3428 3429 // Throttle the scroll handler and bind this. 3430 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 3431 3432 this.options.scrollElement = this.options.scrollElement || this.el; 3433 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 3434 3435 this.initSortable(); 3436 3437 _.bindAll( this, 'setColumns' ); 3438 3439 if ( this.options.resize ) { 3440 this.on( 'ready', this.bindEvents ); 3441 this.controller.on( 'open', this.setColumns ); 3442 3443 // Call this.setColumns() after this view has been rendered in the DOM so 3444 // attachments get proper width applied. 3445 _.defer( this.setColumns, this ); 3446 } 3447 }, 3448 3449 bindEvents: function() { 3450 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 3451 }, 3452 3453 attachmentFocus: function() { 3454 this.$( 'li:first' ).focus(); 3455 }, 3456 3457 restoreFocus: function() { 3458 this.$( 'li.selected:first' ).focus(); 3459 }, 3460 3461 arrowEvent: function( event ) { 3462 var attachments = this.$el.children( 'li' ), 3463 perRow = this.columns, 3464 index = attachments.filter( ':focus' ).index(), 3465 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 3466 3467 if ( index === -1 ) { 3468 return; 3469 } 3470 3471 // Left arrow 3472 if ( 37 === event.keyCode ) { 3473 if ( 0 === index ) { 3474 return; 3475 } 3476 attachments.eq( index - 1 ).focus(); 3477 } 3478 3479 // Up arrow 3480 if ( 38 === event.keyCode ) { 3481 if ( 1 === row ) { 3482 return; 3483 } 3484 attachments.eq( index - perRow ).focus(); 3485 } 3486 3487 // Right arrow 3488 if ( 39 === event.keyCode ) { 3489 if ( attachments.length === index ) { 3490 return; 3491 } 3492 attachments.eq( index + 1 ).focus(); 3493 } 3494 3495 // Down arrow 3496 if ( 40 === event.keyCode ) { 3497 if ( Math.ceil( attachments.length / perRow ) === row ) { 3498 return; 3499 } 3500 attachments.eq( index + perRow ).focus(); 3501 } 3502 }, 3503 3504 dispose: function() { 3505 this.collection.props.off( null, null, this ); 3506 if ( this.options.resize ) { 3507 this.$window.off( this.resizeEvent ); 3508 } 3509 3510 /** 3511 * call 'dispose' directly on the parent class 3512 */ 3513 View.prototype.dispose.apply( this, arguments ); 3514 }, 3515 3516 setColumns: function() { 3517 var prev = this.columns, 3518 width = this.$el.width(); 3519 3520 if ( width ) { 3521 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 3522 3523 if ( ! prev || prev !== this.columns ) { 3524 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 3525 } 3526 } 3527 }, 3528 3529 initSortable: function() { 3530 var collection = this.collection; 3531 3532 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 3533 return; 3534 } 3535 3536 this.$el.sortable( _.extend({ 3537 // If the `collection` has a `comparator`, disable sorting. 3538 disabled: !! collection.comparator, 3539 3540 // Change the position of the attachment as soon as the 3541 // mouse pointer overlaps a thumbnail. 3542 tolerance: 'pointer', 3543 3544 // Record the initial `index` of the dragged model. 3545 start: function( event, ui ) { 3546 ui.item.data('sortableIndexStart', ui.item.index()); 3547 }, 3548 3549 // Update the model's index in the collection. 3550 // Do so silently, as the view is already accurate. 3551 update: function( event, ui ) { 3552 var model = collection.at( ui.item.data('sortableIndexStart') ), 3553 comparator = collection.comparator; 3554 3555 // Temporarily disable the comparator to prevent `add` 3556 // from re-sorting. 3557 delete collection.comparator; 3558 3559 // Silently shift the model to its new index. 3560 collection.remove( model, { 3561 silent: true 3562 }); 3563 collection.add( model, { 3564 silent: true, 3565 at: ui.item.index() 3566 }); 3567 3568 // Restore the comparator. 3569 collection.comparator = comparator; 3570 3571 // Fire the `reset` event to ensure other collections sync. 3572 collection.trigger( 'reset', collection ); 3573 3574 // If the collection is sorted by menu order, 3575 // update the menu order. 3576 collection.saveMenuOrder(); 3577 } 3578 }, this.options.sortable ) ); 3579 3580 // If the `orderby` property is changed on the `collection`, 3581 // check to see if we have a `comparator`. If so, disable sorting. 3582 collection.props.on( 'change:orderby', function() { 3583 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 3584 }, this ); 3585 3586 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 3587 this.refreshSortable(); 3588 }, 3589 3590 refreshSortable: function() { 3591 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 3592 return; 3593 } 3594 3595 // If the `collection` has a `comparator`, disable sorting. 3596 var collection = this.collection, 3597 orderby = collection.props.get('orderby'), 3598 enabled = 'menuOrder' === orderby || ! collection.comparator; 3599 3600 this.$el.sortable( 'option', 'disabled', ! enabled ); 3601 }, 3602 3603 /** 3604 * @param {wp.media.model.Attachment} attachment 3605 * @returns {wp.media.View} 3606 */ 3607 createAttachmentView: function( attachment ) { 3608 var view = new this.options.AttachmentView({ 3609 controller: this.controller, 3610 model: attachment, 3611 collection: this.collection, 3612 selection: this.options.selection 3613 }); 3614 3615 return this._viewsByCid[ attachment.cid ] = view; 3616 }, 3617 3618 prepare: function() { 3619 // Create all of the Attachment views, and replace 3620 // the list in a single DOM operation. 3621 if ( this.collection.length ) { 3622 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 3623 3624 // If there are no elements, clear the views and load some. 3625 } else { 3626 this.views.unset(); 3627 this.collection.more().done( this.scroll ); 3628 } 3629 }, 3630 3631 ready: function() { 3632 // Trigger the scroll event to check if we're within the 3633 // threshold to query for additional attachments. 3634 this.scroll(); 3635 }, 3636 3637 scroll: function() { 3638 var view = this, 3639 el = this.options.scrollElement, 3640 scrollTop = el.scrollTop, 3641 toolbar; 3642 3643 // The scroll event occurs on the document, but the element 3644 // that should be checked is the document body. 3645 if ( el === document ) { 3646 el = document.body; 3647 scrollTop = $(document).scrollTop(); 3648 } 3649 3650 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 3651 return; 3652 } 3653 3654 toolbar = this.views.parent.toolbar; 3655 3656 // Show the spinner only if we are close to the bottom. 3657 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 3658 toolbar.get('spinner').show(); 3659 } 3660 3661 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 3662 this.collection.more().done(function() { 3663 view.scroll(); 3664 toolbar.get('spinner').hide(); 3665 }); 3666 } 3667 } 3668 }); 3669 3670 module.exports = Attachments; 3671 3672 },{}],30:[function(require,module,exports){ 3673 /*globals wp, _, jQuery */ 3674 3675 /** 3676 * wp.media.view.AttachmentsBrowser 3677 * 3678 * @class 3679 * @augments wp.media.View 3680 * @augments wp.Backbone.View 3681 * @augments Backbone.View 3682 * 3683 * @param {object} options 3684 * @param {object} [options.filters=false] Which filters to show in the browser's toolbar. 3685 * Accepts 'uploaded' and 'all'. 3686 * @param {object} [options.search=true] Whether to show the search interface in the 3687 * browser's toolbar. 3688 * @param {object} [options.date=true] Whether to show the date filter in the 3689 * browser's toolbar. 3690 * @param {object} [options.display=false] Whether to show the attachments display settings 3691 * view in the sidebar. 3692 * @param {bool|string} [options.sidebar=true] Whether to create a sidebar for the browser. 3693 * Accepts true, false, and 'errors'. 3694 */ 3695 var View = wp.media.View, 3696 mediaTrash = wp.media.view.settings.mediaTrash, 3697 l10n = wp.media.view.l10n, 3698 $ = jQuery, 3699 AttachmentsBrowser; 3700 3701 AttachmentsBrowser = View.extend({ 3702 tagName: 'div', 3703 className: 'attachments-browser', 3704 3705 initialize: function() { 3706 _.defaults( this.options, { 3707 filters: false, 3708 search: true, 3709 date: true, 3710 display: false, 3711 sidebar: true, 3712 AttachmentView: wp.media.view.Attachment.Library 3713 }); 3714 3715 this.listenTo( this.controller, 'toggle:upload:attachment', _.bind( this.toggleUploader, this ) ); 3716 this.controller.on( 'edit:selection', this.editSelection ); 3717 this.createToolbar(); 3718 if ( this.options.sidebar ) { 3719 this.createSidebar(); 3720 } 3721 this.createUploader(); 3722 this.createAttachments(); 3723 this.updateContent(); 3724 3725 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) { 3726 this.$el.addClass( 'hide-sidebar' ); 3727 3728 if ( 'errors' === this.options.sidebar ) { 3729 this.$el.addClass( 'sidebar-for-errors' ); 3730 } 3731 } 3732 3733 this.collection.on( 'add remove reset', this.updateContent, this ); 3734 }, 3735 3736 editSelection: function( modal ) { 3737 modal.$( '.media-button-backToLibrary' ).focus(); 3738 }, 3739 3740 /** 3741 * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining 3742 */ 3743 dispose: function() { 3744 this.options.selection.off( null, null, this ); 3745 View.prototype.dispose.apply( this, arguments ); 3746 return this; 3747 }, 3748 3749 createToolbar: function() { 3750 var LibraryViewSwitcher, Filters, toolbarOptions; 3751 3752 toolbarOptions = { 3753 controller: this.controller 3754 }; 3755 3756 if ( this.controller.isModeActive( 'grid' ) ) { 3757 toolbarOptions.className = 'media-toolbar wp-filter'; 3758 } 3759 3760 /** 3761 * @member {wp.media.view.Toolbar} 3762 */ 3763 this.toolbar = new wp.media.view.Toolbar( toolbarOptions ); 3764 3765 this.views.add( this.toolbar ); 3766 3767 this.toolbar.set( 'spinner', new wp.media.view.Spinner({ 3768 priority: -60 3769 }) ); 3770 3771 if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) { 3772 // "Filters" will return a <select>, need to render 3773 // screen reader text before 3774 this.toolbar.set( 'filtersLabel', new wp.media.view.Label({ 3775 value: l10n.filterByType, 3776 attributes: { 3777 'for': 'media-attachment-filters' 3778 }, 3779 priority: -80 3780 }).render() ); 3781 3782 if ( 'uploaded' === this.options.filters ) { 3783 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({ 3784 controller: this.controller, 3785 model: this.collection.props, 3786 priority: -80 3787 }).render() ); 3788 } else { 3789 Filters = new wp.media.view.AttachmentFilters.All({ 3790 controller: this.controller, 3791 model: this.collection.props, 3792 priority: -80 3793 }); 3794 3795 this.toolbar.set( 'filters', Filters.render() ); 3796 } 3797 } 3798 3799 // Feels odd to bring the global media library switcher into the Attachment 3800 // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar ); 3801 // which the controller can tap into and add this view? 3802 if ( this.controller.isModeActive( 'grid' ) ) { 3803 LibraryViewSwitcher = View.extend({ 3804 className: 'view-switch media-grid-view-switch', 3805 template: wp.template( 'media-library-view-switcher') 3806 }); 3807 3808 this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({ 3809 controller: this.controller, 3810 priority: -90 3811 }).render() ); 3812 3813 // DateFilter is a <select>, screen reader text needs to be rendered before 3814 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 3815 value: l10n.filterByDate, 3816 attributes: { 3817 'for': 'media-attachment-date-filters' 3818 }, 3819 priority: -75 3820 }).render() ); 3821 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 3822 controller: this.controller, 3823 model: this.collection.props, 3824 priority: -75 3825 }).render() ); 3826 3827 // BulkSelection is a <div> with subviews, including screen reader text 3828 this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({ 3829 text: l10n.bulkSelect, 3830 controller: this.controller, 3831 priority: -70 3832 }).render() ); 3833 3834 this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({ 3835 filters: Filters, 3836 style: 'primary', 3837 disabled: true, 3838 text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected, 3839 controller: this.controller, 3840 priority: -60, 3841 click: function() { 3842 var changed = [], removed = [], 3843 selection = this.controller.state().get( 'selection' ), 3844 library = this.controller.state().get( 'library' ); 3845 3846 if ( ! selection.length ) { 3847 return; 3848 } 3849 3850 if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) { 3851 return; 3852 } 3853 3854 if ( mediaTrash && 3855 'trash' !== selection.at( 0 ).get( 'status' ) && 3856 ! window.confirm( l10n.warnBulkTrash ) ) { 3857 3858 return; 3859 } 3860 3861 selection.each( function( model ) { 3862 if ( ! model.get( 'nonces' )['delete'] ) { 3863 removed.push( model ); 3864 return; 3865 } 3866 3867 if ( mediaTrash && 'trash' === model.get( 'status' ) ) { 3868 model.set( 'status', 'inherit' ); 3869 changed.push( model.save() ); 3870 removed.push( model ); 3871 } else if ( mediaTrash ) { 3872 model.set( 'status', 'trash' ); 3873 changed.push( model.save() ); 3874 removed.push( model ); 3875 } else { 3876 model.destroy({wait: true}); 3877 } 3878 } ); 3879 3880 if ( changed.length ) { 3881 selection.remove( removed ); 3882 3883 $.when.apply( null, changed ).then( _.bind( function() { 3884 library._requery( true ); 3885 this.controller.trigger( 'selection:action:done' ); 3886 }, this ) ); 3887 } else { 3888 this.controller.trigger( 'selection:action:done' ); 3889 } 3890 } 3891 }).render() ); 3892 3893 if ( mediaTrash ) { 3894 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({ 3895 filters: Filters, 3896 style: 'primary', 3897 disabled: true, 3898 text: l10n.deleteSelected, 3899 controller: this.controller, 3900 priority: -55, 3901 click: function() { 3902 var removed = [], selection = this.controller.state().get( 'selection' ); 3903 3904 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) { 3905 return; 3906 } 3907 3908 selection.each( function( model ) { 3909 if ( ! model.get( 'nonces' )['delete'] ) { 3910 removed.push( model ); 3911 return; 3912 } 3913 3914 model.destroy(); 3915 } ); 3916 3917 selection.remove( removed ); 3918 this.controller.trigger( 'selection:action:done' ); 3919 } 3920 }).render() ); 3921 } 3922 3923 } else if ( this.options.date ) { 3924 // DateFilter is a <select>, screen reader text needs to be rendered before 3925 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 3926 value: l10n.filterByDate, 3927 attributes: { 3928 'for': 'media-attachment-date-filters' 3929 }, 3930 priority: -75 3931 }).render() ); 3932 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 3933 controller: this.controller, 3934 model: this.collection.props, 3935 priority: -75 3936 }).render() ); 3937 } 3938 3939 if ( this.options.search ) { 3940 // Search is an input, screen reader text needs to be rendered before 3941 this.toolbar.set( 'searchLabel', new wp.media.view.Label({ 3942 value: l10n.searchMediaLabel, 3943 attributes: { 3944 'for': 'media-search-input' 3945 }, 3946 priority: 60 3947 }).render() ); 3948 this.toolbar.set( 'search', new wp.media.view.Search({ 3949 controller: this.controller, 3950 model: this.collection.props, 3951 priority: 60 3952 }).render() ); 3953 } 3954 3955 if ( this.options.dragInfo ) { 3956 this.toolbar.set( 'dragInfo', new View({ 3957 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0], 3958 priority: -40 3959 }) ); 3960 } 3961 3962 if ( this.options.suggestedWidth && this.options.suggestedHeight ) { 3963 this.toolbar.set( 'suggestedDimensions', new View({ 3964 el: $( '<div class="instructions">' + l10n.suggestedDimensions + ' ' + this.options.suggestedWidth + ' × ' + this.options.suggestedHeight + '</div>' )[0], 3965 priority: -40 3966 }) ); 3967 } 3968 }, 3969 3970 updateContent: function() { 3971 var view = this, 3972 noItemsView; 3973 3974 if ( this.controller.isModeActive( 'grid' ) ) { 3975 noItemsView = view.attachmentsNoResults; 3976 } else { 3977 noItemsView = view.uploader; 3978 } 3979 3980 if ( ! this.collection.length ) { 3981 this.toolbar.get( 'spinner' ).show(); 3982 this.dfd = this.collection.more().done( function() { 3983 if ( ! view.collection.length ) { 3984 noItemsView.$el.removeClass( 'hidden' ); 3985 } else { 3986 noItemsView.$el.addClass( 'hidden' ); 3987 } 3988 view.toolbar.get( 'spinner' ).hide(); 3989 } ); 3990 } else { 3991 noItemsView.$el.addClass( 'hidden' ); 3992 view.toolbar.get( 'spinner' ).hide(); 3993 } 3994 }, 3995 3996 createUploader: function() { 3997 this.uploader = new wp.media.view.UploaderInline({ 3998 controller: this.controller, 3999 status: false, 4000 message: this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound, 4001 canClose: this.controller.isModeActive( 'grid' ) 4002 }); 4003 4004 this.uploader.hide(); 4005 this.views.add( this.uploader ); 4006 }, 4007 4008 toggleUploader: function() { 4009 if ( this.uploader.$el.hasClass( 'hidden' ) ) { 4010 this.uploader.show(); 4011 } else { 4012 this.uploader.hide(); 4013 } 4014 }, 4015 4016 createAttachments: function() { 4017 this.attachments = new wp.media.view.Attachments({ 4018 controller: this.controller, 4019 collection: this.collection, 4020 selection: this.options.selection, 4021 model: this.model, 4022 sortable: this.options.sortable, 4023 scrollElement: this.options.scrollElement, 4024 idealColumnWidth: this.options.idealColumnWidth, 4025 4026 // The single `Attachment` view to be used in the `Attachments` view. 4027 AttachmentView: this.options.AttachmentView 4028 }); 4029 4030 // Add keydown listener to the instance of the Attachments view 4031 this.attachments.listenTo( this.controller, 'attachment:keydown:arrow', this.attachments.arrowEvent ); 4032 this.attachments.listenTo( this.controller, 'attachment:details:shift-tab', this.attachments.restoreFocus ); 4033 4034 this.views.add( this.attachments ); 4035 4036 4037 if ( this.controller.isModeActive( 'grid' ) ) { 4038 this.attachmentsNoResults = new View({ 4039 controller: this.controller, 4040 tagName: 'p' 4041 }); 4042 4043 this.attachmentsNoResults.$el.addClass( 'hidden no-media' ); 4044 this.attachmentsNoResults.$el.html( l10n.noMedia ); 4045 4046 this.views.add( this.attachmentsNoResults ); 4047 } 4048 }, 4049 4050 createSidebar: function() { 4051 var options = this.options, 4052 selection = options.selection, 4053 sidebar = this.sidebar = new wp.media.view.Sidebar({ 4054 controller: this.controller 4055 }); 4056 4057 this.views.add( sidebar ); 4058 4059 if ( this.controller.uploader ) { 4060 sidebar.set( 'uploads', new wp.media.view.UploaderStatus({ 4061 controller: this.controller, 4062 priority: 40 4063 }) ); 4064 } 4065 4066 selection.on( 'selection:single', this.createSingle, this ); 4067 selection.on( 'selection:unsingle', this.disposeSingle, this ); 4068 4069 if ( selection.single() ) { 4070 this.createSingle(); 4071 } 4072 }, 4073 4074 createSingle: function() { 4075 var sidebar = this.sidebar, 4076 single = this.options.selection.single(); 4077 4078 sidebar.set( 'details', new wp.media.view.Attachment.Details({ 4079 controller: this.controller, 4080 model: single, 4081 priority: 80 4082 }) ); 4083 4084 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({ 4085 controller: this.controller, 4086 model: single, 4087 priority: 120 4088 }) ); 4089 4090 if ( this.options.display ) { 4091 sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({ 4092 controller: this.controller, 4093 model: this.model.display( single ), 4094 attachment: single, 4095 priority: 160, 4096 userSettings: this.model.get('displayUserSettings') 4097 }) ); 4098 } 4099 4100 // Show the sidebar on mobile 4101 if ( this.model.id === 'insert' ) { 4102 sidebar.$el.addClass( 'visible' ); 4103 } 4104 }, 4105 4106 disposeSingle: function() { 4107 var sidebar = this.sidebar; 4108 sidebar.unset('details'); 4109 sidebar.unset('compat'); 4110 sidebar.unset('display'); 4111 // Hide the sidebar on mobile 4112 sidebar.$el.removeClass( 'visible' ); 4113 } 4114 }); 4115 4116 module.exports = AttachmentsBrowser; 4117 4118 },{}],31:[function(require,module,exports){ 4119 /*globals wp, _ */ 4120 4121 /** 4122 * wp.media.view.Attachments.Selection 4123 * 4124 * @class 4125 * @augments wp.media.view.Attachments 4126 * @augments wp.media.View 4127 * @augments wp.Backbone.View 4128 * @augments Backbone.View 4129 */ 4130 var Attachments = wp.media.view.Attachments, 4131 Selection; 4132 4133 Selection = Attachments.extend({ 4134 events: {}, 4135 initialize: function() { 4136 _.defaults( this.options, { 4137 sortable: false, 4138 resize: false, 4139 4140 // The single `Attachment` view to be used in the `Attachments` view. 4141 AttachmentView: wp.media.view.Attachment.Selection 4142 }); 4143 // Call 'initialize' directly on the parent class. 4144 return Attachments.prototype.initialize.apply( this, arguments ); 4145 } 4146 }); 4147 4148 module.exports = Selection; 4149 4150 },{}],32:[function(require,module,exports){ 4151 /*globals _, Backbone */ 4152 4153 /** 4154 * wp.media.view.ButtonGroup 4155 * 4156 * @class 4157 * @augments wp.media.View 4158 * @augments wp.Backbone.View 4159 * @augments Backbone.View 4160 */ 4161 var $ = Backbone.$, 4162 ButtonGroup; 4163 4164 ButtonGroup = wp.media.View.extend({ 4165 tagName: 'div', 4166 className: 'button-group button-large media-button-group', 4167 4168 initialize: function() { 4169 /** 4170 * @member {wp.media.view.Button[]} 4171 */ 4172 this.buttons = _.map( this.options.buttons || [], function( button ) { 4173 if ( button instanceof Backbone.View ) { 4174 return button; 4175 } else { 4176 return new wp.media.view.Button( button ).render(); 4177 } 4178 }); 4179 4180 delete this.options.buttons; 4181 4182 if ( this.options.classes ) { 4183 this.$el.addClass( this.options.classes ); 4184 } 4185 }, 4186 4187 /** 4188 * @returns {wp.media.view.ButtonGroup} 4189 */ 4190 render: function() { 4191 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 4192 return this; 4193 } 4194 }); 4195 4196 module.exports = ButtonGroup; 4197 4198 },{}],33:[function(require,module,exports){ 4199 /*globals _, Backbone */ 4200 4201 /** 4202 * wp.media.view.Button 4203 * 4204 * @class 4205 * @augments wp.media.View 4206 * @augments wp.Backbone.View 4207 * @augments Backbone.View 4208 */ 4209 var Button = wp.media.View.extend({ 4210 tagName: 'a', 4211 className: 'media-button', 4212 attributes: { href: '#' }, 4213 4214 events: { 4215 'click': 'click' 4216 }, 4217 4218 defaults: { 4219 text: '', 4220 style: '', 4221 size: 'large', 4222 disabled: false 4223 }, 4224 4225 initialize: function() { 4226 /** 4227 * Create a model with the provided `defaults`. 4228 * 4229 * @member {Backbone.Model} 4230 */ 4231 this.model = new Backbone.Model( this.defaults ); 4232 4233 // If any of the `options` have a key from `defaults`, apply its 4234 // value to the `model` and remove it from the `options object. 4235 _.each( this.defaults, function( def, key ) { 4236 var value = this.options[ key ]; 4237 if ( _.isUndefined( value ) ) { 4238 return; 4239 } 4240 4241 this.model.set( key, value ); 4242 delete this.options[ key ]; 4243 }, this ); 4244 4245 this.listenTo( this.model, 'change', this.render ); 4246 }, 4247 /** 4248 * @returns {wp.media.view.Button} Returns itself to allow chaining 4249 */ 4250 render: function() { 4251 var classes = [ 'button', this.className ], 4252 model = this.model.toJSON(); 4253 4254 if ( model.style ) { 4255 classes.push( 'button-' + model.style ); 4256 } 4257 4258 if ( model.size ) { 4259 classes.push( 'button-' + model.size ); 4260 } 4261 4262 classes = _.uniq( classes.concat( this.options.classes ) ); 4263 this.el.className = classes.join(' '); 4264 4265 this.$el.attr( 'disabled', model.disabled ); 4266 this.$el.text( this.model.get('text') ); 4267 4268 return this; 4269 }, 4270 /** 4271 * @param {Object} event 4272 */ 4273 click: function( event ) { 4274 if ( '#' === this.attributes.href ) { 4275 event.preventDefault(); 4276 } 4277 4278 if ( this.options.click && ! this.model.get('disabled') ) { 4279 this.options.click.apply( this, arguments ); 4280 } 4281 } 4282 }); 4283 4284 module.exports = Button; 4285 4286 },{}],34:[function(require,module,exports){ 4287 /*globals wp, _, jQuery */ 4288 4289 /** 4290 * wp.media.view.Cropper 4291 * 4292 * Uses the imgAreaSelect plugin to allow a user to crop an image. 4293 * 4294 * Takes imgAreaSelect options from 4295 * wp.customize.HeaderControl.calculateImageSelectOptions via 4296 * wp.customize.HeaderControl.openMM. 4297 * 4298 * @class 4299 * @augments wp.media.View 4300 * @augments wp.Backbone.View 4301 * @augments Backbone.View 4302 */ 4303 var View = wp.media.View, 4304 UploaderStatus = wp.media.view.UploaderStatus, 4305 l10n = wp.media.view.l10n, 4306 $ = jQuery, 4307 Cropper; 4308 4309 Cropper = View.extend({ 4310 className: 'crop-content', 4311 template: wp.template('crop-content'), 4312 initialize: function() { 4313 _.bindAll(this, 'onImageLoad'); 4314 }, 4315 ready: function() { 4316 this.controller.frame.on('content:error:crop', this.onError, this); 4317 this.$image = this.$el.find('.crop-image'); 4318 this.$image.on('load', this.onImageLoad); 4319 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250)); 4320 }, 4321 remove: function() { 4322 $(window).off('resize.cropper'); 4323 this.$el.remove(); 4324 this.$el.off(); 4325 View.prototype.remove.apply(this, arguments); 4326 }, 4327 prepare: function() { 4328 return { 4329 title: l10n.cropYourImage, 4330 url: this.options.attachment.get('url') 4331 }; 4332 }, 4333 onImageLoad: function() { 4334 var imgOptions = this.controller.get('imgSelectOptions'); 4335 if (typeof imgOptions === 'function') { 4336 imgOptions = imgOptions(this.options.attachment, this.controller); 4337 } 4338 4339 imgOptions = _.extend(imgOptions, {parent: this.$el}); 4340 this.trigger('image-loaded'); 4341 this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 4342 }, 4343 onError: function() { 4344 var filename = this.options.attachment.get('filename'); 4345 4346 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 4347 filename: UploaderStatus.prototype.filename(filename), 4348 message: window._wpMediaViewsL10n.cropError 4349 }), { at: 0 }); 4350 } 4351 }); 4352 4353 module.exports = Cropper; 4354 4355 },{}],35:[function(require,module,exports){ 4356 /*globals wp, _ */ 4357 4358 /** 4359 * wp.media.view.EditImage 4360 * 4361 * @class 4362 * @augments wp.media.View 4363 * @augments wp.Backbone.View 4364 * @augments Backbone.View 4365 */ 4366 var View = wp.media.View, 4367 EditImage; 4368 4369 EditImage = View.extend({ 4370 className: 'image-editor', 4371 template: wp.template('image-editor'), 4372 4373 initialize: function( options ) { 4374 this.editor = window.imageEdit; 4375 this.controller = options.controller; 4376 View.prototype.initialize.apply( this, arguments ); 4377 }, 4378 4379 prepare: function() { 4380 return this.model.toJSON(); 4381 }, 4382 4383 loadEditor: function() { 4384 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this ); 4385 dfd.done( _.bind( this.focus, this ) ); 4386 }, 4387 4388 focus: function() { 4389 this.$( '.imgedit-submit .button' ).eq( 0 ).focus(); 4390 }, 4391 4392 back: function() { 4393 var lastState = this.controller.lastState(); 4394 this.controller.setState( lastState ); 4395 }, 4396 4397 refresh: function() { 4398 this.model.fetch(); 4399 }, 4400 4401 save: function() { 4402 var lastState = this.controller.lastState(); 4403 4404 this.model.fetch().done( _.bind( function() { 4405 this.controller.setState( lastState ); 4406 }, this ) ); 4407 } 4408 4409 }); 4410 4411 module.exports = EditImage; 4412 4413 },{}],36:[function(require,module,exports){ 4414 /** 4415 * wp.media.view.Embed 4416 * 4417 * @class 4418 * @augments wp.media.View 4419 * @augments wp.Backbone.View 4420 * @augments Backbone.View 4421 */ 4422 var Embed = wp.media.View.extend({ 4423 className: 'media-embed', 4424 4425 initialize: function() { 4426 /** 4427 * @member {wp.media.view.EmbedUrl} 4428 */ 4429 this.url = new wp.media.view.EmbedUrl({ 4430 controller: this.controller, 4431 model: this.model.props 4432 }).render(); 4433 4434 this.views.set([ this.url ]); 4435 this.refresh(); 4436 this.listenTo( this.model, 'change:type', this.refresh ); 4437 this.listenTo( this.model, 'change:loading', this.loading ); 4438 }, 4439 4440 /** 4441 * @param {Object} view 4442 */ 4443 settings: function( view ) { 4444 if ( this._settings ) { 4445 this._settings.remove(); 4446 } 4447 this._settings = view; 4448 this.views.add( view ); 4449 }, 4450 4451 refresh: function() { 4452 var type = this.model.get('type'), 4453 constructor; 4454 4455 if ( 'image' === type ) { 4456 constructor = wp.media.view.EmbedImage; 4457 } else if ( 'link' === type ) { 4458 constructor = wp.media.view.EmbedLink; 4459 } else { 4460 return; 4461 } 4462 4463 this.settings( new constructor({ 4464 controller: this.controller, 4465 model: this.model.props, 4466 priority: 40 4467 }) ); 4468 }, 4469 4470 loading: function() { 4471 this.$el.toggleClass( 'embed-loading', this.model.get('loading') ); 4472 } 4473 }); 4474 4475 module.exports = Embed; 4476 4477 },{}],37:[function(require,module,exports){ 4478 /*globals wp */ 4479 4480 /** 4481 * wp.media.view.EmbedImage 4482 * 4483 * @class 4484 * @augments wp.media.view.Settings.AttachmentDisplay 4485 * @augments wp.media.view.Settings 4486 * @augments wp.media.View 4487 * @augments wp.Backbone.View 4488 * @augments Backbone.View 4489 */ 4490 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 4491 EmbedImage; 4492 4493 EmbedImage = AttachmentDisplay.extend({ 4494 className: 'embed-media-settings', 4495 template: wp.template('embed-image-settings'), 4496 4497 initialize: function() { 4498 /** 4499 * Call `initialize` directly on parent class with passed arguments 4500 */ 4501 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 4502 this.listenTo( this.model, 'change:url', this.updateImage ); 4503 }, 4504 4505 updateImage: function() { 4506 this.$('img').attr( 'src', this.model.get('url') ); 4507 } 4508 }); 4509 4510 module.exports = EmbedImage; 4511 4512 },{}],38:[function(require,module,exports){ 4513 /*globals wp, _, jQuery */ 4514 4515 /** 4516 * wp.media.view.EmbedLink 4517 * 4518 * @class 4519 * @augments wp.media.view.Settings 4520 * @augments wp.media.View 4521 * @augments wp.Backbone.View 4522 * @augments Backbone.View 4523 */ 4524 var $ = jQuery, 4525 EmbedLink; 4526 4527 EmbedLink = wp.media.view.Settings.extend({ 4528 className: 'embed-link-settings', 4529 template: wp.template('embed-link-settings'), 4530 4531 initialize: function() { 4532 this.spinner = $('<span class="spinner" />'); 4533 this.$el.append( this.spinner[0] ); 4534 this.listenTo( this.model, 'change:url change:width change:height', this.updateoEmbed ); 4535 }, 4536 4537 updateoEmbed: _.debounce( function() { 4538 var url = this.model.get( 'url' ); 4539 4540 // clear out previous results 4541 this.$('.embed-container').hide().find('.embed-preview').empty(); 4542 this.$( '.setting' ).hide(); 4543 4544 // only proceed with embed if the field contains more than 6 characters 4545 if ( url && url.length < 6 ) { 4546 return; 4547 } 4548 4549 this.spinner.show(); 4550 4551 this.fetch(); 4552 }, 600 ), 4553 4554 fetch: function() { 4555 var embed; 4556 4557 // check if they haven't typed in 500 ms 4558 if ( $('#embed-url-field').val() !== this.model.get('url') ) { 4559 return; 4560 } 4561 4562 embed = new wp.shortcode({ 4563 tag: 'embed', 4564 attrs: _.pick( this.model.attributes, [ 'width', 'height', 'src' ] ), 4565 content: this.model.get('url') 4566 }); 4567 4568 wp.ajax.send( 'parse-embed', { 4569 data : { 4570 post_ID: wp.media.view.settings.post.id, 4571 shortcode: embed.string() 4572 } 4573 } ) 4574 .done( _.bind( this.renderoEmbed, this ) ) 4575 .fail( _.bind( this.renderFail, this ) ); 4576 }, 4577 4578 renderFail: function () { 4579 this.$( '.setting' ).hide().filter( '.link-text' ).show(); 4580 }, 4581 4582 renderoEmbed: function( response ) { 4583 var html = ( response && response.body ) || '', 4584 attr = {}, 4585 opts = { silent: true }; 4586 4587 this.$( '.setting' ).hide() 4588 .filter( '.link-text' )[ html ? 'hide' : 'show' ](); 4589 4590 if ( response && response.attr ) { 4591 attr = response.attr; 4592 4593 _.each( [ 'width', 'height' ], function ( key ) { 4594 var $el = this.$( '.setting.' + key ), 4595 value = attr[ key ]; 4596 4597 if ( value ) { 4598 this.model.set( key, value, opts ); 4599 $el.show().find( 'input' ).val( value ); 4600 } else { 4601 this.model.unset( key, opts ); 4602 $el.hide().find( 'input' ).val( '' ); 4603 } 4604 }, this ); 4605 } else { 4606 this.model.unset( 'height', opts ); 4607 this.model.unset( 'width', opts ); 4608 } 4609 4610 this.spinner.hide(); 4611 4612 this.$('.embed-container').show().find('.embed-preview').html( html ); 4613 } 4614 }); 4615 4616 module.exports = EmbedLink; 4617 4618 },{}],39:[function(require,module,exports){ 4619 /*globals wp, _, jQuery */ 4620 4621 /** 4622 * wp.media.view.EmbedUrl 4623 * 4624 * @class 4625 * @augments wp.media.View 4626 * @augments wp.Backbone.View 4627 * @augments Backbone.View 4628 */ 4629 var View = wp.media.View, 4630 $ = jQuery, 4631 EmbedUrl; 4632 4633 EmbedUrl = View.extend({ 4634 tagName: 'label', 4635 className: 'embed-url', 4636 4637 events: { 4638 'input': 'url', 4639 'keyup': 'url', 4640 'change': 'url' 4641 }, 4642 4643 initialize: function() { 4644 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') ); 4645 this.input = this.$input[0]; 4646 4647 this.spinner = $('<span class="spinner" />')[0]; 4648 this.$el.append([ this.input, this.spinner ]); 4649 4650 this.listenTo( this.model, 'change:url', this.render ); 4651 4652 if ( this.model.get( 'url' ) ) { 4653 _.delay( _.bind( function () { 4654 this.model.trigger( 'change:url' ); 4655 }, this ), 500 ); 4656 } 4657 }, 4658 /** 4659 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining 4660 */ 4661 render: function() { 4662 var $input = this.$input; 4663 4664 if ( $input.is(':focus') ) { 4665 return; 4666 } 4667 4668 this.input.value = this.model.get('url') || 'http://'; 4669 /** 4670 * Call `render` directly on parent class with passed arguments 4671 */ 4672 View.prototype.render.apply( this, arguments ); 4673 return this; 4674 }, 4675 4676 ready: function() { 4677 if ( ! wp.media.isTouchDevice ) { 4678 this.focus(); 4679 } 4680 }, 4681 4682 url: function( event ) { 4683 this.model.set( 'url', event.target.value ); 4684 }, 4685 4686 /** 4687 * If the input is visible, focus and select its contents. 4688 */ 4689 focus: function() { 4690 var $input = this.$input; 4691 if ( $input.is(':visible') ) { 4692 $input.focus()[0].select(); 4693 } 4694 } 4695 }); 4696 4697 module.exports = EmbedUrl; 4698 4699 },{}],40:[function(require,module,exports){ 4700 /** 4701 * wp.media.view.FocusManager 4702 * 4703 * @class 4704 * @augments wp.media.View 4705 * @augments wp.Backbone.View 4706 * @augments Backbone.View 4707 */ 4708 var FocusManager = wp.media.View.extend({ 4709 4710 events: { 4711 'keydown': 'constrainTabbing' 4712 }, 4713 4714 focus: function() { // Reset focus on first left menu item 4715 this.$('.media-menu-item').first().focus(); 4716 }, 4717 /** 4718 * @param {Object} event 4719 */ 4720 constrainTabbing: function( event ) { 4721 var tabbables; 4722 4723 // Look for the tab key. 4724 if ( 9 !== event.keyCode ) { 4725 return; 4726 } 4727 4728 // Skip the file input added by Plupload. 4729 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 4730 4731 // Keep tab focus within media modal while it's open 4732 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 4733 tabbables.first().focus(); 4734 return false; 4735 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 4736 tabbables.last().focus(); 4737 return false; 4738 } 4739 } 4740 4741 }); 4742 4743 module.exports = FocusManager; 4744 4745 },{}],41:[function(require,module,exports){ 4746 /*globals _, Backbone */ 4747 4748 /** 4749 * wp.media.view.Frame 4750 * 4751 * A frame is a composite view consisting of one or more regions and one or more 4752 * states. 4753 * 4754 * @see wp.media.controller.State 4755 * @see wp.media.controller.Region 4756 * 4757 * @class 4758 * @augments wp.media.View 4759 * @augments wp.Backbone.View 4760 * @augments Backbone.View 4761 * @mixes wp.media.controller.StateMachine 4762 */ 4763 var Frame = wp.media.View.extend({ 4764 initialize: function() { 4765 _.defaults( this.options, { 4766 mode: [ 'select' ] 4767 }); 4768 this._createRegions(); 4769 this._createStates(); 4770 this._createModes(); 4771 }, 4772 4773 _createRegions: function() { 4774 // Clone the regions array. 4775 this.regions = this.regions ? this.regions.slice() : []; 4776 4777 // Initialize regions. 4778 _.each( this.regions, function( region ) { 4779 this[ region ] = new wp.media.controller.Region({ 4780 view: this, 4781 id: region, 4782 selector: '.media-frame-' + region 4783 }); 4784 }, this ); 4785 }, 4786 /** 4787 * Create the frame's states. 4788 * 4789 * @see wp.media.controller.State 4790 * @see wp.media.controller.StateMachine 4791 * 4792 * @fires wp.media.controller.State#ready 4793 */ 4794 _createStates: function() { 4795 // Create the default `states` collection. 4796 this.states = new Backbone.Collection( null, { 4797 model: wp.media.controller.State 4798 }); 4799 4800 // Ensure states have a reference to the frame. 4801 this.states.on( 'add', function( model ) { 4802 model.frame = this; 4803 model.trigger('ready'); 4804 }, this ); 4805 4806 if ( this.options.states ) { 4807 this.states.add( this.options.states ); 4808 } 4809 }, 4810 4811 /** 4812 * A frame can be in a mode or multiple modes at one time. 4813 * 4814 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 4815 */ 4816 _createModes: function() { 4817 // Store active "modes" that the frame is in. Unrelated to region modes. 4818 this.activeModes = new Backbone.Collection(); 4819 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 4820 4821 _.each( this.options.mode, function( mode ) { 4822 this.activateMode( mode ); 4823 }, this ); 4824 }, 4825 /** 4826 * Reset all states on the frame to their defaults. 4827 * 4828 * @returns {wp.media.view.Frame} Returns itself to allow chaining 4829 */ 4830 reset: function() { 4831 this.states.invoke( 'trigger', 'reset' ); 4832 return this; 4833 }, 4834 /** 4835 * Map activeMode collection events to the frame. 4836 */ 4837 triggerModeEvents: function( model, collection, options ) { 4838 var collectionEvent, 4839 modeEventMap = { 4840 add: 'activate', 4841 remove: 'deactivate' 4842 }, 4843 eventToTrigger; 4844 // Probably a better way to do this. 4845 _.each( options, function( value, key ) { 4846 if ( value ) { 4847 collectionEvent = key; 4848 } 4849 } ); 4850 4851 if ( ! _.has( modeEventMap, collectionEvent ) ) { 4852 return; 4853 } 4854 4855 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 4856 this.trigger( eventToTrigger ); 4857 }, 4858 /** 4859 * Activate a mode on the frame. 4860 * 4861 * @param string mode Mode ID. 4862 * @returns {this} Returns itself to allow chaining. 4863 */ 4864 activateMode: function( mode ) { 4865 // Bail if the mode is already active. 4866 if ( this.isModeActive( mode ) ) { 4867 return; 4868 } 4869 this.activeModes.add( [ { id: mode } ] ); 4870 // Add a CSS class to the frame so elements can be styled for the mode. 4871 this.$el.addClass( 'mode-' + mode ); 4872 4873 return this; 4874 }, 4875 /** 4876 * Deactivate a mode on the frame. 4877 * 4878 * @param string mode Mode ID. 4879 * @returns {this} Returns itself to allow chaining. 4880 */ 4881 deactivateMode: function( mode ) { 4882 // Bail if the mode isn't active. 4883 if ( ! this.isModeActive( mode ) ) { 4884 return this; 4885 } 4886 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 4887 this.$el.removeClass( 'mode-' + mode ); 4888 /** 4889 * Frame mode deactivation event. 4890 * 4891 * @event this#{mode}:deactivate 4892 */ 4893 this.trigger( mode + ':deactivate' ); 4894 4895 return this; 4896 }, 4897 /** 4898 * Check if a mode is enabled on the frame. 4899 * 4900 * @param string mode Mode ID. 4901 * @return bool 4902 */ 4903 isModeActive: function( mode ) { 4904 return Boolean( this.activeModes.where( { id: mode } ).length ); 4905 } 4906 }); 4907 4908 // Make the `Frame` a `StateMachine`. 4909 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 4910 4911 module.exports = Frame; 4912 4913 },{}],42:[function(require,module,exports){ 4914 /*globals wp */ 4915 4916 /** 4917 * wp.media.view.MediaFrame.ImageDetails 4918 * 4919 * A media frame for manipulating an image that's already been inserted 4920 * into a post. 4921 * 4922 * @class 4923 * @augments wp.media.view.MediaFrame.Select 4924 * @augments wp.media.view.MediaFrame 4925 * @augments wp.media.view.Frame 4926 * @augments wp.media.View 4927 * @augments wp.Backbone.View 4928 * @augments Backbone.View 4929 * @mixes wp.media.controller.StateMachine 4930 */ 4931 var Select = wp.media.view.MediaFrame.Select, 4932 l10n = wp.media.view.l10n, 4933 ImageDetails; 4934 4935 ImageDetails = Select.extend({ 4936 defaults: { 4937 id: 'image', 4938 url: '', 4939 menu: 'image-details', 4940 content: 'image-details', 4941 toolbar: 'image-details', 4942 type: 'link', 4943 title: l10n.imageDetailsTitle, 4944 priority: 120 4945 }, 4946 4947 initialize: function( options ) { 4948 this.image = new wp.media.model.PostImage( options.metadata ); 4949 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 4950 Select.prototype.initialize.apply( this, arguments ); 4951 }, 4952 4953 bindHandlers: function() { 4954 Select.prototype.bindHandlers.apply( this, arguments ); 4955 this.on( 'menu:create:image-details', this.createMenu, this ); 4956 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 4957 this.on( 'content:render:edit-image', this.editImageContent, this ); 4958 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 4959 // override the select toolbar 4960 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 4961 }, 4962 4963 createStates: function() { 4964 this.states.add([ 4965 new wp.media.controller.ImageDetails({ 4966 image: this.image, 4967 editable: false 4968 }), 4969 new wp.media.controller.ReplaceImage({ 4970 id: 'replace-image', 4971 library: wp.media.query( { type: 'image' } ), 4972 image: this.image, 4973 multiple: false, 4974 title: l10n.imageReplaceTitle, 4975 toolbar: 'replace', 4976 priority: 80, 4977 displaySettings: true 4978 }), 4979 new wp.media.controller.EditImage( { 4980 image: this.image, 4981 selection: this.options.selection 4982 } ) 4983 ]); 4984 }, 4985 4986 imageDetailsContent: function( options ) { 4987 options.view = new wp.media.view.ImageDetails({ 4988 controller: this, 4989 model: this.state().image, 4990 attachment: this.state().image.attachment 4991 }); 4992 }, 4993 4994 editImageContent: function() { 4995 var state = this.state(), 4996 model = state.get('image'), 4997 view; 4998 4999 if ( ! model ) { 5000 return; 5001 } 5002 5003 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 5004 5005 this.content.set( view ); 5006 5007 // after bringing in the frame, load the actual editor via an ajax call 5008 view.loadEditor(); 5009 5010 }, 5011 5012 renderImageDetailsToolbar: function() { 5013 this.toolbar.set( new wp.media.view.Toolbar({ 5014 controller: this, 5015 items: { 5016 select: { 5017 style: 'primary', 5018 text: l10n.update, 5019 priority: 80, 5020 5021 click: function() { 5022 var controller = this.controller, 5023 state = controller.state(); 5024 5025 controller.close(); 5026 5027 // not sure if we want to use wp.media.string.image which will create a shortcode or 5028 // perhaps wp.html.string to at least to build the <img /> 5029 state.trigger( 'update', controller.image.toJSON() ); 5030 5031 // Restore and reset the default state. 5032 controller.setState( controller.options.state ); 5033 controller.reset(); 5034 } 5035 } 5036 } 5037 }) ); 5038 }, 5039 5040 renderReplaceImageToolbar: function() { 5041 var frame = this, 5042 lastState = frame.lastState(), 5043 previous = lastState && lastState.id; 5044 5045 this.toolbar.set( new wp.media.view.Toolbar({ 5046 controller: this, 5047 items: { 5048 back: { 5049 text: l10n.back, 5050 priority: 20, 5051 click: function() { 5052 if ( previous ) { 5053 frame.setState( previous ); 5054 } else { 5055 frame.close(); 5056 } 5057 } 5058 }, 5059 5060 replace: { 5061 style: 'primary', 5062 text: l10n.replace, 5063 priority: 80, 5064 5065 click: function() { 5066 var controller = this.controller, 5067 state = controller.state(), 5068 selection = state.get( 'selection' ), 5069 attachment = selection.single(); 5070 5071 controller.close(); 5072 5073 controller.image.changeAttachment( attachment, state.display( attachment ) ); 5074 5075 // not sure if we want to use wp.media.string.image which will create a shortcode or 5076 // perhaps wp.html.string to at least to build the <img /> 5077 state.trigger( 'replace', controller.image.toJSON() ); 5078 5079 // Restore and reset the default state. 5080 controller.setState( controller.options.state ); 5081 controller.reset(); 5082 } 5083 } 5084 } 5085 }) ); 5086 } 5087 5088 }); 5089 5090 module.exports = ImageDetails; 5091 5092 },{}],43:[function(require,module,exports){ 5093 /*globals wp, _ */ 5094 5095 /** 5096 * wp.media.view.MediaFrame.Post 5097 * 5098 * The frame for manipulating media on the Edit Post page. 5099 * 5100 * @class 5101 * @augments wp.media.view.MediaFrame.Select 5102 * @augments wp.media.view.MediaFrame 5103 * @augments wp.media.view.Frame 5104 * @augments wp.media.View 5105 * @augments wp.Backbone.View 5106 * @augments Backbone.View 5107 * @mixes wp.media.controller.StateMachine 5108 */ 5109 var Select = wp.media.view.MediaFrame.Select, 5110 Library = wp.media.controller.Library, 5111 l10n = wp.media.view.l10n, 5112 Post; 5113 5114 Post = Select.extend({ 5115 initialize: function() { 5116 this.counts = { 5117 audio: { 5118 count: wp.media.view.settings.attachmentCounts.audio, 5119 state: 'playlist' 5120 }, 5121 video: { 5122 count: wp.media.view.settings.attachmentCounts.video, 5123 state: 'video-playlist' 5124 } 5125 }; 5126 5127 _.defaults( this.options, { 5128 multiple: true, 5129 editing: false, 5130 state: 'insert', 5131 metadata: {} 5132 }); 5133 5134 // Call 'initialize' directly on the parent class. 5135 Select.prototype.initialize.apply( this, arguments ); 5136 this.createIframeStates(); 5137 5138 }, 5139 5140 /** 5141 * Create the default states. 5142 */ 5143 createStates: function() { 5144 var options = this.options; 5145 5146 this.states.add([ 5147 // Main states. 5148 new Library({ 5149 id: 'insert', 5150 title: l10n.insertMediaTitle, 5151 priority: 20, 5152 toolbar: 'main-insert', 5153 filterable: 'all', 5154 library: wp.media.query( options.library ), 5155 multiple: options.multiple ? 'reset' : false, 5156 editable: true, 5157 5158 // If the user isn't allowed to edit fields, 5159 // can they still edit it locally? 5160 allowLocalEdits: true, 5161 5162 // Show the attachment display settings. 5163 displaySettings: true, 5164 // Update user settings when users adjust the 5165 // attachment display settings. 5166 displayUserSettings: true 5167 }), 5168 5169 new Library({ 5170 id: 'gallery', 5171 title: l10n.createGalleryTitle, 5172 priority: 40, 5173 toolbar: 'main-gallery', 5174 filterable: 'uploaded', 5175 multiple: 'add', 5176 editable: false, 5177 5178 library: wp.media.query( _.defaults({ 5179 type: 'image' 5180 }, options.library ) ) 5181 }), 5182 5183 // Embed states. 5184 new wp.media.controller.Embed( { metadata: options.metadata } ), 5185 5186 new wp.media.controller.EditImage( { model: options.editImage } ), 5187 5188 // Gallery states. 5189 new wp.media.controller.GalleryEdit({ 5190 library: options.selection, 5191 editing: options.editing, 5192 menu: 'gallery' 5193 }), 5194 5195 new wp.media.controller.GalleryAdd(), 5196 5197 new Library({ 5198 id: 'playlist', 5199 title: l10n.createPlaylistTitle, 5200 priority: 60, 5201 toolbar: 'main-playlist', 5202 filterable: 'uploaded', 5203 multiple: 'add', 5204 editable: false, 5205 5206 library: wp.media.query( _.defaults({ 5207 type: 'audio' 5208 }, options.library ) ) 5209 }), 5210 5211 // Playlist states. 5212 new wp.media.controller.CollectionEdit({ 5213 type: 'audio', 5214 collectionType: 'playlist', 5215 title: l10n.editPlaylistTitle, 5216 SettingsView: wp.media.view.Settings.Playlist, 5217 library: options.selection, 5218 editing: options.editing, 5219 menu: 'playlist', 5220 dragInfoText: l10n.playlistDragInfo, 5221 dragInfo: false 5222 }), 5223 5224 new wp.media.controller.CollectionAdd({ 5225 type: 'audio', 5226 collectionType: 'playlist', 5227 title: l10n.addToPlaylistTitle 5228 }), 5229 5230 new Library({ 5231 id: 'video-playlist', 5232 title: l10n.createVideoPlaylistTitle, 5233 priority: 60, 5234 toolbar: 'main-video-playlist', 5235 filterable: 'uploaded', 5236 multiple: 'add', 5237 editable: false, 5238 5239 library: wp.media.query( _.defaults({ 5240 type: 'video' 5241 }, options.library ) ) 5242 }), 5243 5244 new wp.media.controller.CollectionEdit({ 5245 type: 'video', 5246 collectionType: 'playlist', 5247 title: l10n.editVideoPlaylistTitle, 5248 SettingsView: wp.media.view.Settings.Playlist, 5249 library: options.selection, 5250 editing: options.editing, 5251 menu: 'video-playlist', 5252 dragInfoText: l10n.videoPlaylistDragInfo, 5253 dragInfo: false 5254 }), 5255 5256 new wp.media.controller.CollectionAdd({ 5257 type: 'video', 5258 collectionType: 'playlist', 5259 title: l10n.addToVideoPlaylistTitle 5260 }) 5261 ]); 5262 5263 if ( wp.media.view.settings.post.featuredImageId ) { 5264 this.states.add( new wp.media.controller.FeaturedImage() ); 5265 } 5266 }, 5267 5268 bindHandlers: function() { 5269 var handlers, checkCounts; 5270 5271 Select.prototype.bindHandlers.apply( this, arguments ); 5272 5273 this.on( 'activate', this.activate, this ); 5274 5275 // Only bother checking media type counts if one of the counts is zero 5276 checkCounts = _.find( this.counts, function( type ) { 5277 return type.count === 0; 5278 } ); 5279 5280 if ( typeof checkCounts !== 'undefined' ) { 5281 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 5282 } 5283 5284 this.on( 'menu:create:gallery', this.createMenu, this ); 5285 this.on( 'menu:create:playlist', this.createMenu, this ); 5286 this.on( 'menu:create:video-playlist', this.createMenu, this ); 5287 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 5288 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 5289 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 5290 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 5291 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 5292 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 5293 5294 handlers = { 5295 menu: { 5296 'default': 'mainMenu', 5297 'gallery': 'galleryMenu', 5298 'playlist': 'playlistMenu', 5299 'video-playlist': 'videoPlaylistMenu' 5300 }, 5301 5302 content: { 5303 'embed': 'embedContent', 5304 'edit-image': 'editImageContent', 5305 'edit-selection': 'editSelectionContent' 5306 }, 5307 5308 toolbar: { 5309 'main-insert': 'mainInsertToolbar', 5310 'main-gallery': 'mainGalleryToolbar', 5311 'gallery-edit': 'galleryEditToolbar', 5312 'gallery-add': 'galleryAddToolbar', 5313 'main-playlist': 'mainPlaylistToolbar', 5314 'playlist-edit': 'playlistEditToolbar', 5315 'playlist-add': 'playlistAddToolbar', 5316 'main-video-playlist': 'mainVideoPlaylistToolbar', 5317 'video-playlist-edit': 'videoPlaylistEditToolbar', 5318 'video-playlist-add': 'videoPlaylistAddToolbar' 5319 } 5320 }; 5321 5322 _.each( handlers, function( regionHandlers, region ) { 5323 _.each( regionHandlers, function( callback, handler ) { 5324 this.on( region + ':render:' + handler, this[ callback ], this ); 5325 }, this ); 5326 }, this ); 5327 }, 5328 5329 activate: function() { 5330 // Hide menu items for states tied to particular media types if there are no items 5331 _.each( this.counts, function( type ) { 5332 if ( type.count < 1 ) { 5333 this.menuItemVisibility( type.state, 'hide' ); 5334 } 5335 }, this ); 5336 }, 5337 5338 mediaTypeCounts: function( model, attr ) { 5339 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 5340 this.counts[ attr ].count++; 5341 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 5342 } 5343 }, 5344 5345 // Menus 5346 /** 5347 * @param {wp.Backbone.View} view 5348 */ 5349 mainMenu: function( view ) { 5350 view.set({ 5351 'library-separator': new wp.media.View({ 5352 className: 'separator', 5353 priority: 100 5354 }) 5355 }); 5356 }, 5357 5358 menuItemVisibility: function( state, visibility ) { 5359 var menu = this.menu.get(); 5360 if ( visibility === 'hide' ) { 5361 menu.hide( state ); 5362 } else if ( visibility === 'show' ) { 5363 menu.show( state ); 5364 } 5365 }, 5366 /** 5367 * @param {wp.Backbone.View} view 5368 */ 5369 galleryMenu: function( view ) { 5370 var lastState = this.lastState(), 5371 previous = lastState && lastState.id, 5372 frame = this; 5373 5374 view.set({ 5375 cancel: { 5376 text: l10n.cancelGalleryTitle, 5377 priority: 20, 5378 click: function() { 5379 if ( previous ) { 5380 frame.setState( previous ); 5381 } else { 5382 frame.close(); 5383 } 5384 5385 // Keep focus inside media modal 5386 // after canceling a gallery 5387 this.controller.modal.focusManager.focus(); 5388 } 5389 }, 5390 separateCancel: new wp.media.View({ 5391 className: 'separator', 5392 priority: 40 5393 }) 5394 }); 5395 }, 5396 5397 playlistMenu: function( view ) { 5398 var lastState = this.lastState(), 5399 previous = lastState && lastState.id, 5400 frame = this; 5401 5402 view.set({ 5403 cancel: { 5404 text: l10n.cancelPlaylistTitle, 5405 priority: 20, 5406 click: function() { 5407 if ( previous ) { 5408 frame.setState( previous ); 5409 } else { 5410 frame.close(); 5411 } 5412 } 5413 }, 5414 separateCancel: new wp.media.View({ 5415 className: 'separator', 5416 priority: 40 5417 }) 5418 }); 5419 }, 5420 5421 videoPlaylistMenu: function( view ) { 5422 var lastState = this.lastState(), 5423 previous = lastState && lastState.id, 5424 frame = this; 5425 5426 view.set({ 5427 cancel: { 5428 text: l10n.cancelVideoPlaylistTitle, 5429 priority: 20, 5430 click: function() { 5431 if ( previous ) { 5432 frame.setState( previous ); 5433 } else { 5434 frame.close(); 5435 } 5436 } 5437 }, 5438 separateCancel: new wp.media.View({ 5439 className: 'separator', 5440 priority: 40 5441 }) 5442 }); 5443 }, 5444 5445 // Content 5446 embedContent: function() { 5447 var view = new wp.media.view.Embed({ 5448 controller: this, 5449 model: this.state() 5450 }).render(); 5451 5452 this.content.set( view ); 5453 5454 if ( ! wp.media.isTouchDevice ) { 5455 view.url.focus(); 5456 } 5457 }, 5458 5459 editSelectionContent: function() { 5460 var state = this.state(), 5461 selection = state.get('selection'), 5462 view; 5463 5464 view = new wp.media.view.AttachmentsBrowser({ 5465 controller: this, 5466 collection: selection, 5467 selection: selection, 5468 model: state, 5469 sortable: true, 5470 search: false, 5471 date: false, 5472 dragInfo: true, 5473 5474 AttachmentView: wp.media.view.Attachments.EditSelection 5475 }).render(); 5476 5477 view.toolbar.set( 'backToLibrary', { 5478 text: l10n.returnToLibrary, 5479 priority: -100, 5480 5481 click: function() { 5482 this.controller.content.mode('browse'); 5483 } 5484 }); 5485 5486 // Browse our library of attachments. 5487 this.content.set( view ); 5488 5489 // Trigger the controller to set focus 5490 this.trigger( 'edit:selection', this ); 5491 }, 5492 5493 editImageContent: function() { 5494 var image = this.state().get('image'), 5495 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 5496 5497 this.content.set( view ); 5498 5499 // after creating the wrapper view, load the actual editor via an ajax call 5500 view.loadEditor(); 5501 5502 }, 5503 5504 // Toolbars 5505 5506 /** 5507 * @param {wp.Backbone.View} view 5508 */ 5509 selectionStatusToolbar: function( view ) { 5510 var editable = this.state().get('editable'); 5511 5512 view.set( 'selection', new wp.media.view.Selection({ 5513 controller: this, 5514 collection: this.state().get('selection'), 5515 priority: -40, 5516 5517 // If the selection is editable, pass the callback to 5518 // switch the content mode. 5519 editable: editable && function() { 5520 this.controller.content.mode('edit-selection'); 5521 } 5522 }).render() ); 5523 }, 5524 5525 /** 5526 * @param {wp.Backbone.View} view 5527 */ 5528 mainInsertToolbar: function( view ) { 5529 var controller = this; 5530 5531 this.selectionStatusToolbar( view ); 5532 5533 view.set( 'insert', { 5534 style: 'primary', 5535 priority: 80, 5536 text: l10n.insertIntoPost, 5537 requires: { selection: true }, 5538 5539 /** 5540 * @fires wp.media.controller.State#insert 5541 */ 5542 click: function() { 5543 var state = controller.state(), 5544 selection = state.get('selection'); 5545 5546 controller.close(); 5547 state.trigger( 'insert', selection ).reset(); 5548 } 5549 }); 5550 }, 5551 5552 /** 5553 * @param {wp.Backbone.View} view 5554 */ 5555 mainGalleryToolbar: function( view ) { 5556 var controller = this; 5557 5558 this.selectionStatusToolbar( view ); 5559 5560 view.set( 'gallery', { 5561 style: 'primary', 5562 text: l10n.createNewGallery, 5563 priority: 60, 5564 requires: { selection: true }, 5565 5566 click: function() { 5567 var selection = controller.state().get('selection'), 5568 edit = controller.state('gallery-edit'), 5569 models = selection.where({ type: 'image' }); 5570 5571 edit.set( 'library', new wp.media.model.Selection( models, { 5572 props: selection.props.toJSON(), 5573 multiple: true 5574 }) ); 5575 5576 this.controller.setState('gallery-edit'); 5577 5578 // Keep focus inside media modal 5579 // after jumping to gallery view 5580 this.controller.modal.focusManager.focus(); 5581 } 5582 }); 5583 }, 5584 5585 mainPlaylistToolbar: function( view ) { 5586 var controller = this; 5587 5588 this.selectionStatusToolbar( view ); 5589 5590 view.set( 'playlist', { 5591 style: 'primary', 5592 text: l10n.createNewPlaylist, 5593 priority: 100, 5594 requires: { selection: true }, 5595 5596 click: function() { 5597 var selection = controller.state().get('selection'), 5598 edit = controller.state('playlist-edit'), 5599 models = selection.where({ type: 'audio' }); 5600 5601 edit.set( 'library', new wp.media.model.Selection( models, { 5602 props: selection.props.toJSON(), 5603 multiple: true 5604 }) ); 5605 5606 this.controller.setState('playlist-edit'); 5607 5608 // Keep focus inside media modal 5609 // after jumping to playlist view 5610 this.controller.modal.focusManager.focus(); 5611 } 5612 }); 5613 }, 5614 5615 mainVideoPlaylistToolbar: function( view ) { 5616 var controller = this; 5617 5618 this.selectionStatusToolbar( view ); 5619 5620 view.set( 'video-playlist', { 5621 style: 'primary', 5622 text: l10n.createNewVideoPlaylist, 5623 priority: 100, 5624 requires: { selection: true }, 5625 5626 click: function() { 5627 var selection = controller.state().get('selection'), 5628 edit = controller.state('video-playlist-edit'), 5629 models = selection.where({ type: 'video' }); 5630 5631 edit.set( 'library', new wp.media.model.Selection( models, { 5632 props: selection.props.toJSON(), 5633 multiple: true 5634 }) ); 5635 5636 this.controller.setState('video-playlist-edit'); 5637 5638 // Keep focus inside media modal 5639 // after jumping to video playlist view 5640 this.controller.modal.focusManager.focus(); 5641 } 5642 }); 5643 }, 5644 5645 featuredImageToolbar: function( toolbar ) { 5646 this.createSelectToolbar( toolbar, { 5647 text: l10n.setFeaturedImage, 5648 state: this.options.state 5649 }); 5650 }, 5651 5652 mainEmbedToolbar: function( toolbar ) { 5653 toolbar.view = new wp.media.view.Toolbar.Embed({ 5654 controller: this 5655 }); 5656 }, 5657 5658 galleryEditToolbar: function() { 5659 var editing = this.state().get('editing'); 5660 this.toolbar.set( new wp.media.view.Toolbar({ 5661 controller: this, 5662 items: { 5663 insert: { 5664 style: 'primary', 5665 text: editing ? l10n.updateGallery : l10n.insertGallery, 5666 priority: 80, 5667 requires: { library: true }, 5668 5669 /** 5670 * @fires wp.media.controller.State#update 5671 */ 5672 click: function() { 5673 var controller = this.controller, 5674 state = controller.state(); 5675 5676 controller.close(); 5677 state.trigger( 'update', state.get('library') ); 5678 5679 // Restore and reset the default state. 5680 controller.setState( controller.options.state ); 5681 controller.reset(); 5682 } 5683 } 5684 } 5685 }) ); 5686 }, 5687 5688 galleryAddToolbar: function() { 5689 this.toolbar.set( new wp.media.view.Toolbar({ 5690 controller: this, 5691 items: { 5692 insert: { 5693 style: 'primary', 5694 text: l10n.addToGallery, 5695 priority: 80, 5696 requires: { selection: true }, 5697 5698 /** 5699 * @fires wp.media.controller.State#reset 5700 */ 5701 click: function() { 5702 var controller = this.controller, 5703 state = controller.state(), 5704 edit = controller.state('gallery-edit'); 5705 5706 edit.get('library').add( state.get('selection').models ); 5707 state.trigger('reset'); 5708 controller.setState('gallery-edit'); 5709 } 5710 } 5711 } 5712 }) ); 5713 }, 5714 5715 playlistEditToolbar: function() { 5716 var editing = this.state().get('editing'); 5717 this.toolbar.set( new wp.media.view.Toolbar({ 5718 controller: this, 5719 items: { 5720 insert: { 5721 style: 'primary', 5722 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 5723 priority: 80, 5724 requires: { library: true }, 5725 5726 /** 5727 * @fires wp.media.controller.State#update 5728 */ 5729 click: function() { 5730 var controller = this.controller, 5731 state = controller.state(); 5732 5733 controller.close(); 5734 state.trigger( 'update', state.get('library') ); 5735 5736 // Restore and reset the default state. 5737 controller.setState( controller.options.state ); 5738 controller.reset(); 5739 } 5740 } 5741 } 5742 }) ); 5743 }, 5744 5745 playlistAddToolbar: function() { 5746 this.toolbar.set( new wp.media.view.Toolbar({ 5747 controller: this, 5748 items: { 5749 insert: { 5750 style: 'primary', 5751 text: l10n.addToPlaylist, 5752 priority: 80, 5753 requires: { selection: true }, 5754 5755 /** 5756 * @fires wp.media.controller.State#reset 5757 */ 5758 click: function() { 5759 var controller = this.controller, 5760 state = controller.state(), 5761 edit = controller.state('playlist-edit'); 5762 5763 edit.get('library').add( state.get('selection').models ); 5764 state.trigger('reset'); 5765 controller.setState('playlist-edit'); 5766 } 5767 } 5768 } 5769 }) ); 5770 }, 5771 5772 videoPlaylistEditToolbar: function() { 5773 var editing = this.state().get('editing'); 5774 this.toolbar.set( new wp.media.view.Toolbar({ 5775 controller: this, 5776 items: { 5777 insert: { 5778 style: 'primary', 5779 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 5780 priority: 140, 5781 requires: { library: true }, 5782 5783 click: function() { 5784 var controller = this.controller, 5785 state = controller.state(), 5786 library = state.get('library'); 5787 5788 library.type = 'video'; 5789 5790 controller.close(); 5791 state.trigger( 'update', library ); 5792 5793 // Restore and reset the default state. 5794 controller.setState( controller.options.state ); 5795 controller.reset(); 5796 } 5797 } 5798 } 5799 }) ); 5800 }, 5801 5802 videoPlaylistAddToolbar: function() { 5803 this.toolbar.set( new wp.media.view.Toolbar({ 5804 controller: this, 5805 items: { 5806 insert: { 5807 style: 'primary', 5808 text: l10n.addToVideoPlaylist, 5809 priority: 140, 5810 requires: { selection: true }, 5811 5812 click: function() { 5813 var controller = this.controller, 5814 state = controller.state(), 5815 edit = controller.state('video-playlist-edit'); 5816 5817 edit.get('library').add( state.get('selection').models ); 5818 state.trigger('reset'); 5819 controller.setState('video-playlist-edit'); 5820 } 5821 } 5822 } 5823 }) ); 5824 } 5825 }); 5826 5827 module.exports = Post; 5828 5829 },{}],44:[function(require,module,exports){ 5830 /*globals wp, _ */ 5831 5832 /** 5833 * wp.media.view.MediaFrame.Select 5834 * 5835 * A frame for selecting an item or items from the media library. 5836 * 5837 * @class 5838 * @augments wp.media.view.MediaFrame 5839 * @augments wp.media.view.Frame 5840 * @augments wp.media.View 5841 * @augments wp.Backbone.View 5842 * @augments Backbone.View 5843 * @mixes wp.media.controller.StateMachine 5844 */ 5845 5846 var MediaFrame = wp.media.view.MediaFrame, 5847 l10n = wp.media.view.l10n, 5848 Select; 5849 5850 Select = MediaFrame.extend({ 5851 initialize: function() { 5852 // Call 'initialize' directly on the parent class. 5853 MediaFrame.prototype.initialize.apply( this, arguments ); 5854 5855 _.defaults( this.options, { 5856 selection: [], 5857 library: {}, 5858 multiple: false, 5859 state: 'library' 5860 }); 5861 5862 this.createSelection(); 5863 this.createStates(); 5864 this.bindHandlers(); 5865 }, 5866 5867 /** 5868 * Attach a selection collection to the frame. 5869 * 5870 * A selection is a collection of attachments used for a specific purpose 5871 * by a media frame. e.g. Selecting an attachment (or many) to insert into 5872 * post content. 5873 * 5874 * @see media.model.Selection 5875 */ 5876 createSelection: function() { 5877 var selection = this.options.selection; 5878 5879 if ( ! (selection instanceof wp.media.model.Selection) ) { 5880 this.options.selection = new wp.media.model.Selection( selection, { 5881 multiple: this.options.multiple 5882 }); 5883 } 5884 5885 this._selection = { 5886 attachments: new wp.media.model.Attachments(), 5887 difference: [] 5888 }; 5889 }, 5890 5891 /** 5892 * Create the default states on the frame. 5893 */ 5894 createStates: function() { 5895 var options = this.options; 5896 5897 if ( this.options.states ) { 5898 return; 5899 } 5900 5901 // Add the default states. 5902 this.states.add([ 5903 // Main states. 5904 new wp.media.controller.Library({ 5905 library: wp.media.query( options.library ), 5906 multiple: options.multiple, 5907 title: options.title, 5908 priority: 20 5909 }) 5910 ]); 5911 }, 5912 5913 /** 5914 * Bind region mode event callbacks. 5915 * 5916 * @see media.controller.Region.render 5917 */ 5918 bindHandlers: function() { 5919 this.on( 'router:create:browse', this.createRouter, this ); 5920 this.on( 'router:render:browse', this.browseRouter, this ); 5921 this.on( 'content:create:browse', this.browseContent, this ); 5922 this.on( 'content:render:upload', this.uploadContent, this ); 5923 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 5924 }, 5925 5926 /** 5927 * Render callback for the router region in the `browse` mode. 5928 * 5929 * @param {wp.media.view.Router} routerView 5930 */ 5931 browseRouter: function( routerView ) { 5932 routerView.set({ 5933 upload: { 5934 text: l10n.uploadFilesTitle, 5935 priority: 20 5936 }, 5937 browse: { 5938 text: l10n.mediaLibraryTitle, 5939 priority: 40 5940 } 5941 }); 5942 }, 5943 5944 /** 5945 * Render callback for the content region in the `browse` mode. 5946 * 5947 * @param {wp.media.controller.Region} contentRegion 5948 */ 5949 browseContent: function( contentRegion ) { 5950 var state = this.state(); 5951 5952 this.$el.removeClass('hide-toolbar'); 5953 5954 // Browse our library of attachments. 5955 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 5956 controller: this, 5957 collection: state.get('library'), 5958 selection: state.get('selection'), 5959 model: state, 5960 sortable: state.get('sortable'), 5961 search: state.get('searchable'), 5962 filters: state.get('filterable'), 5963 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 5964 dragInfo: state.get('dragInfo'), 5965 5966 idealColumnWidth: state.get('idealColumnWidth'), 5967 suggestedWidth: state.get('suggestedWidth'), 5968 suggestedHeight: state.get('suggestedHeight'), 5969 5970 AttachmentView: state.get('AttachmentView') 5971 }); 5972 }, 5973 5974 /** 5975 * Render callback for the content region in the `upload` mode. 5976 */ 5977 uploadContent: function() { 5978 this.$el.removeClass( 'hide-toolbar' ); 5979 this.content.set( new wp.media.view.UploaderInline({ 5980 controller: this 5981 }) ); 5982 }, 5983 5984 /** 5985 * Toolbars 5986 * 5987 * @param {Object} toolbar 5988 * @param {Object} [options={}] 5989 * @this wp.media.controller.Region 5990 */ 5991 createSelectToolbar: function( toolbar, options ) { 5992 options = options || this.options.button || {}; 5993 options.controller = this; 5994 5995 toolbar.view = new wp.media.view.Toolbar.Select( options ); 5996 } 5997 }); 5998 5999 module.exports = Select; 6000 6001 },{}],45:[function(require,module,exports){ 6002 /** 6003 * wp.media.view.Iframe 6004 * 6005 * @class 6006 * @augments wp.media.View 6007 * @augments wp.Backbone.View 6008 * @augments Backbone.View 6009 */ 6010 var Iframe = wp.media.View.extend({ 6011 className: 'media-iframe', 6012 /** 6013 * @returns {wp.media.view.Iframe} Returns itself to allow chaining 6014 */ 6015 render: function() { 6016 this.views.detach(); 6017 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 6018 this.views.render(); 6019 return this; 6020 } 6021 }); 6022 6023 module.exports = Iframe; 6024 6025 },{}],46:[function(require,module,exports){ 6026 /*globals wp, _, jQuery */ 6027 6028 /** 6029 * wp.media.view.ImageDetails 6030 * 6031 * @class 6032 * @augments wp.media.view.Settings.AttachmentDisplay 6033 * @augments wp.media.view.Settings 6034 * @augments wp.media.View 6035 * @augments wp.Backbone.View 6036 * @augments Backbone.View 6037 */ 6038 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 6039 $ = jQuery, 6040 ImageDetails; 6041 6042 ImageDetails = AttachmentDisplay.extend({ 6043 className: 'image-details', 6044 template: wp.template('image-details'), 6045 events: _.defaults( AttachmentDisplay.prototype.events, { 6046 'click .edit-attachment': 'editAttachment', 6047 'click .replace-attachment': 'replaceAttachment', 6048 'click .advanced-toggle': 'onToggleAdvanced', 6049 'change [data-setting="customWidth"]': 'onCustomSize', 6050 'change [data-setting="customHeight"]': 'onCustomSize', 6051 'keyup [data-setting="customWidth"]': 'onCustomSize', 6052 'keyup [data-setting="customHeight"]': 'onCustomSize' 6053 } ), 6054 initialize: function() { 6055 // used in AttachmentDisplay.prototype.updateLinkTo 6056 this.options.attachment = this.model.attachment; 6057 this.listenTo( this.model, 'change:url', this.updateUrl ); 6058 this.listenTo( this.model, 'change:link', this.toggleLinkSettings ); 6059 this.listenTo( this.model, 'change:size', this.toggleCustomSize ); 6060 6061 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 6062 }, 6063 6064 prepare: function() { 6065 var attachment = false; 6066 6067 if ( this.model.attachment ) { 6068 attachment = this.model.attachment.toJSON(); 6069 } 6070 return _.defaults({ 6071 model: this.model.toJSON(), 6072 attachment: attachment 6073 }, this.options ); 6074 }, 6075 6076 render: function() { 6077 var args = arguments; 6078 6079 if ( this.model.attachment && 'pending' === this.model.dfd.state() ) { 6080 this.model.dfd 6081 .done( _.bind( function() { 6082 AttachmentDisplay.prototype.render.apply( this, args ); 6083 this.postRender(); 6084 }, this ) ) 6085 .fail( _.bind( function() { 6086 this.model.attachment = false; 6087 AttachmentDisplay.prototype.render.apply( this, args ); 6088 this.postRender(); 6089 }, this ) ); 6090 } else { 6091 AttachmentDisplay.prototype.render.apply( this, arguments ); 6092 this.postRender(); 6093 } 6094 6095 return this; 6096 }, 6097 6098 postRender: function() { 6099 setTimeout( _.bind( this.resetFocus, this ), 10 ); 6100 this.toggleLinkSettings(); 6101 if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) { 6102 this.toggleAdvanced( true ); 6103 } 6104 this.trigger( 'post-render' ); 6105 }, 6106 6107 resetFocus: function() { 6108 this.$( '.link-to-custom' ).blur(); 6109 this.$( '.embed-media-settings' ).scrollTop( 0 ); 6110 }, 6111 6112 updateUrl: function() { 6113 this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) ); 6114 this.$( '.url' ).val( this.model.get( 'url' ) ); 6115 }, 6116 6117 toggleLinkSettings: function() { 6118 if ( this.model.get( 'link' ) === 'none' ) { 6119 this.$( '.link-settings' ).addClass('hidden'); 6120 } else { 6121 this.$( '.link-settings' ).removeClass('hidden'); 6122 } 6123 }, 6124 6125 toggleCustomSize: function() { 6126 if ( this.model.get( 'size' ) !== 'custom' ) { 6127 this.$( '.custom-size' ).addClass('hidden'); 6128 } else { 6129 this.$( '.custom-size' ).removeClass('hidden'); 6130 } 6131 }, 6132 6133 onCustomSize: function( event ) { 6134 var dimension = $( event.target ).data('setting'), 6135 num = $( event.target ).val(), 6136 value; 6137 6138 // Ignore bogus input 6139 if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) { 6140 event.preventDefault(); 6141 return; 6142 } 6143 6144 if ( dimension === 'customWidth' ) { 6145 value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num ); 6146 this.model.set( 'customHeight', value, { silent: true } ); 6147 this.$( '[data-setting="customHeight"]' ).val( value ); 6148 } else { 6149 value = Math.round( this.model.get( 'aspectRatio' ) * num ); 6150 this.model.set( 'customWidth', value, { silent: true } ); 6151 this.$( '[data-setting="customWidth"]' ).val( value ); 6152 } 6153 }, 6154 6155 onToggleAdvanced: function( event ) { 6156 event.preventDefault(); 6157 this.toggleAdvanced(); 6158 }, 6159 6160 toggleAdvanced: function( show ) { 6161 var $advanced = this.$el.find( '.advanced-section' ), 6162 mode; 6163 6164 if ( $advanced.hasClass('advanced-visible') || show === false ) { 6165 $advanced.removeClass('advanced-visible'); 6166 $advanced.find('.advanced-settings').addClass('hidden'); 6167 mode = 'hide'; 6168 } else { 6169 $advanced.addClass('advanced-visible'); 6170 $advanced.find('.advanced-settings').removeClass('hidden'); 6171 mode = 'show'; 6172 } 6173 6174 window.setUserSetting( 'advImgDetails', mode ); 6175 }, 6176 6177 editAttachment: function( event ) { 6178 var editState = this.controller.states.get( 'edit-image' ); 6179 6180 if ( window.imageEdit && editState ) { 6181 event.preventDefault(); 6182 editState.set( 'image', this.model.attachment ); 6183 this.controller.setState( 'edit-image' ); 6184 } 6185 }, 6186 6187 replaceAttachment: function( event ) { 6188 event.preventDefault(); 6189 this.controller.setState( 'replace-image' ); 6190 } 6191 }); 6192 6193 module.exports = ImageDetails; 6194 6195 },{}],47:[function(require,module,exports){ 6196 /** 6197 * wp.media.view.Label 6198 * 6199 * @class 6200 * @augments wp.media.View 6201 * @augments wp.Backbone.View 6202 * @augments Backbone.View 6203 */ 6204 var Label = wp.media.View.extend({ 6205 tagName: 'label', 6206 className: 'screen-reader-text', 6207 6208 initialize: function() { 6209 this.value = this.options.value; 6210 }, 6211 6212 render: function() { 6213 this.$el.html( this.value ); 6214 6215 return this; 6216 } 6217 }); 6218 6219 module.exports = Label; 6220 6221 },{}],48:[function(require,module,exports){ 6222 /*globals wp, _, jQuery */ 6223 6224 /** 6225 * wp.media.view.MediaFrame 6226 * 6227 * The frame used to create the media modal. 6228 * 6229 * @class 6230 * @augments wp.media.view.Frame 6231 * @augments wp.media.View 6232 * @augments wp.Backbone.View 6233 * @augments Backbone.View 6234 * @mixes wp.media.controller.StateMachine 6235 */ 6236 var Frame = wp.media.view.Frame, 6237 $ = jQuery, 6238 MediaFrame; 6239 6240 MediaFrame = Frame.extend({ 6241 className: 'media-frame', 6242 template: wp.template('media-frame'), 6243 regions: ['menu','title','content','toolbar','router'], 6244 6245 events: { 6246 'click div.media-frame-title h1': 'toggleMenu' 6247 }, 6248 6249 /** 6250 * @global wp.Uploader 6251 */ 6252 initialize: function() { 6253 Frame.prototype.initialize.apply( this, arguments ); 6254 6255 _.defaults( this.options, { 6256 title: '', 6257 modal: true, 6258 uploader: true 6259 }); 6260 6261 // Ensure core UI is enabled. 6262 this.$el.addClass('wp-core-ui'); 6263 6264 // Initialize modal container view. 6265 if ( this.options.modal ) { 6266 this.modal = new wp.media.view.Modal({ 6267 controller: this, 6268 title: this.options.title 6269 }); 6270 6271 this.modal.content( this ); 6272 } 6273 6274 // Force the uploader off if the upload limit has been exceeded or 6275 // if the browser isn't supported. 6276 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 6277 this.options.uploader = false; 6278 } 6279 6280 // Initialize window-wide uploader. 6281 if ( this.options.uploader ) { 6282 this.uploader = new wp.media.view.UploaderWindow({ 6283 controller: this, 6284 uploader: { 6285 dropzone: this.modal ? this.modal.$el : this.$el, 6286 container: this.$el 6287 } 6288 }); 6289 this.views.set( '.media-frame-uploader', this.uploader ); 6290 } 6291 6292 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 6293 6294 // Bind default title creation. 6295 this.on( 'title:create:default', this.createTitle, this ); 6296 this.title.mode('default'); 6297 6298 this.on( 'title:render', function( view ) { 6299 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' ); 6300 }); 6301 6302 // Bind default menu. 6303 this.on( 'menu:create:default', this.createMenu, this ); 6304 }, 6305 /** 6306 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6307 */ 6308 render: function() { 6309 // Activate the default state if no active state exists. 6310 if ( ! this.state() && this.options.state ) { 6311 this.setState( this.options.state ); 6312 } 6313 /** 6314 * call 'render' directly on the parent class 6315 */ 6316 return Frame.prototype.render.apply( this, arguments ); 6317 }, 6318 /** 6319 * @param {Object} title 6320 * @this wp.media.controller.Region 6321 */ 6322 createTitle: function( title ) { 6323 title.view = new wp.media.View({ 6324 controller: this, 6325 tagName: 'h1' 6326 }); 6327 }, 6328 /** 6329 * @param {Object} menu 6330 * @this wp.media.controller.Region 6331 */ 6332 createMenu: function( menu ) { 6333 menu.view = new wp.media.view.Menu({ 6334 controller: this 6335 }); 6336 }, 6337 6338 toggleMenu: function() { 6339 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 6340 }, 6341 6342 /** 6343 * @param {Object} toolbar 6344 * @this wp.media.controller.Region 6345 */ 6346 createToolbar: function( toolbar ) { 6347 toolbar.view = new wp.media.view.Toolbar({ 6348 controller: this 6349 }); 6350 }, 6351 /** 6352 * @param {Object} router 6353 * @this wp.media.controller.Region 6354 */ 6355 createRouter: function( router ) { 6356 router.view = new wp.media.view.Router({ 6357 controller: this 6358 }); 6359 }, 6360 /** 6361 * @param {Object} options 6362 */ 6363 createIframeStates: function( options ) { 6364 var settings = wp.media.view.settings, 6365 tabs = settings.tabs, 6366 tabUrl = settings.tabUrl, 6367 $postId; 6368 6369 if ( ! tabs || ! tabUrl ) { 6370 return; 6371 } 6372 6373 // Add the post ID to the tab URL if it exists. 6374 $postId = $('#post_ID'); 6375 if ( $postId.length ) { 6376 tabUrl += '&post_id=' + $postId.val(); 6377 } 6378 6379 // Generate the tab states. 6380 _.each( tabs, function( title, id ) { 6381 this.state( 'iframe:' + id ).set( _.defaults({ 6382 tab: id, 6383 src: tabUrl + '&tab=' + id, 6384 title: title, 6385 content: 'iframe', 6386 menu: 'default' 6387 }, options ) ); 6388 }, this ); 6389 6390 this.on( 'content:create:iframe', this.iframeContent, this ); 6391 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 6392 this.on( 'menu:render:default', this.iframeMenu, this ); 6393 this.on( 'open', this.hijackThickbox, this ); 6394 this.on( 'close', this.restoreThickbox, this ); 6395 }, 6396 6397 /** 6398 * @param {Object} content 6399 * @this wp.media.controller.Region 6400 */ 6401 iframeContent: function( content ) { 6402 this.$el.addClass('hide-toolbar'); 6403 content.view = new wp.media.view.Iframe({ 6404 controller: this 6405 }); 6406 }, 6407 6408 iframeContentCleanup: function() { 6409 this.$el.removeClass('hide-toolbar'); 6410 }, 6411 6412 iframeMenu: function( view ) { 6413 var views = {}; 6414 6415 if ( ! view ) { 6416 return; 6417 } 6418 6419 _.each( wp.media.view.settings.tabs, function( title, id ) { 6420 views[ 'iframe:' + id ] = { 6421 text: this.state( 'iframe:' + id ).get('title'), 6422 priority: 200 6423 }; 6424 }, this ); 6425 6426 view.set( views ); 6427 }, 6428 6429 hijackThickbox: function() { 6430 var frame = this; 6431 6432 if ( ! window.tb_remove || this._tb_remove ) { 6433 return; 6434 } 6435 6436 this._tb_remove = window.tb_remove; 6437 window.tb_remove = function() { 6438 frame.close(); 6439 frame.reset(); 6440 frame.setState( frame.options.state ); 6441 frame._tb_remove.call( window ); 6442 }; 6443 }, 6444 6445 restoreThickbox: function() { 6446 if ( ! this._tb_remove ) { 6447 return; 6448 } 6449 6450 window.tb_remove = this._tb_remove; 6451 delete this._tb_remove; 6452 } 6453 }); 6454 6455 // Map some of the modal's methods to the frame. 6456 _.each(['open','close','attach','detach','escape'], function( method ) { 6457 /** 6458 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6459 */ 6460 MediaFrame.prototype[ method ] = function() { 6461 if ( this.modal ) { 6462 this.modal[ method ].apply( this.modal, arguments ); 6463 } 6464 return this; 6465 }; 6466 }); 6467 6468 module.exports = MediaFrame; 6469 6470 },{}],49:[function(require,module,exports){ 6471 /*globals jQuery */ 6472 6473 /** 6474 * wp.media.view.MenuItem 6475 * 6476 * @class 6477 * @augments wp.media.View 6478 * @augments wp.Backbone.View 6479 * @augments Backbone.View 6480 */ 6481 var $ = jQuery, 6482 MenuItem; 6483 6484 MenuItem = wp.media.View.extend({ 6485 tagName: 'a', 6486 className: 'media-menu-item', 6487 6488 attributes: { 6489 href: '#' 6490 }, 6491 6492 events: { 6493 'click': '_click' 6494 }, 6495 /** 6496 * @param {Object} event 6497 */ 6498 _click: function( event ) { 6499 var clickOverride = this.options.click; 6500 6501 if ( event ) { 6502 event.preventDefault(); 6503 } 6504 6505 if ( clickOverride ) { 6506 clickOverride.call( this ); 6507 } else { 6508 this.click(); 6509 } 6510 6511 // When selecting a tab along the left side, 6512 // focus should be transferred into the main panel 6513 if ( ! wp.media.isTouchDevice ) { 6514 $('.media-frame-content input').first().focus(); 6515 } 6516 }, 6517 6518 click: function() { 6519 var state = this.options.state; 6520 6521 if ( state ) { 6522 this.controller.setState( state ); 6523 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 6524 } 6525 }, 6526 /** 6527 * @returns {wp.media.view.MenuItem} returns itself to allow chaining 6528 */ 6529 render: function() { 6530 var options = this.options; 6531 6532 if ( options.text ) { 6533 this.$el.text( options.text ); 6534 } else if ( options.html ) { 6535 this.$el.html( options.html ); 6536 } 6537 6538 return this; 6539 } 6540 }); 6541 6542 module.exports = MenuItem; 6543 6544 },{}],50:[function(require,module,exports){ 6545 /** 6546 * wp.media.view.Menu 6547 * 6548 * @class 6549 * @augments wp.media.view.PriorityList 6550 * @augments wp.media.View 6551 * @augments wp.Backbone.View 6552 * @augments Backbone.View 6553 */ 6554 var MenuItem = wp.media.view.MenuItem, 6555 PriorityList = wp.media.view.PriorityList, 6556 Menu; 6557 6558 Menu = PriorityList.extend({ 6559 tagName: 'div', 6560 className: 'media-menu', 6561 property: 'state', 6562 ItemView: MenuItem, 6563 region: 'menu', 6564 6565 /* TODO: alternatively hide on any click anywhere 6566 events: { 6567 'click': 'click' 6568 }, 6569 6570 click: function() { 6571 this.$el.removeClass( 'visible' ); 6572 }, 6573 */ 6574 6575 /** 6576 * @param {Object} options 6577 * @param {string} id 6578 * @returns {wp.media.View} 6579 */ 6580 toView: function( options, id ) { 6581 options = options || {}; 6582 options[ this.property ] = options[ this.property ] || id; 6583 return new this.ItemView( options ).render(); 6584 }, 6585 6586 ready: function() { 6587 /** 6588 * call 'ready' directly on the parent class 6589 */ 6590 PriorityList.prototype.ready.apply( this, arguments ); 6591 this.visibility(); 6592 }, 6593 6594 set: function() { 6595 /** 6596 * call 'set' directly on the parent class 6597 */ 6598 PriorityList.prototype.set.apply( this, arguments ); 6599 this.visibility(); 6600 }, 6601 6602 unset: function() { 6603 /** 6604 * call 'unset' directly on the parent class 6605 */ 6606 PriorityList.prototype.unset.apply( this, arguments ); 6607 this.visibility(); 6608 }, 6609 6610 visibility: function() { 6611 var region = this.region, 6612 view = this.controller[ region ].get(), 6613 views = this.views.get(), 6614 hide = ! views || views.length < 2; 6615 6616 if ( this === view ) { 6617 this.controller.$el.toggleClass( 'hide-' + region, hide ); 6618 } 6619 }, 6620 /** 6621 * @param {string} id 6622 */ 6623 select: function( id ) { 6624 var view = this.get( id ); 6625 6626 if ( ! view ) { 6627 return; 6628 } 6629 6630 this.deselect(); 6631 view.$el.addClass('active'); 6632 }, 6633 6634 deselect: function() { 6635 this.$el.children().removeClass('active'); 6636 }, 6637 6638 hide: function( id ) { 6639 var view = this.get( id ); 6640 6641 if ( ! view ) { 6642 return; 6643 } 6644 6645 view.$el.addClass('hidden'); 6646 }, 6647 6648 show: function( id ) { 6649 var view = this.get( id ); 6650 6651 if ( ! view ) { 6652 return; 6653 } 6654 6655 view.$el.removeClass('hidden'); 6656 } 6657 }); 6658 6659 module.exports = Menu; 6660 6661 },{}],51:[function(require,module,exports){ 6662 /*globals wp, _, jQuery */ 6663 6664 /** 6665 * wp.media.view.Modal 6666 * 6667 * A modal view, which the media modal uses as its default container. 6668 * 6669 * @class 6670 * @augments wp.media.View 6671 * @augments wp.Backbone.View 6672 * @augments Backbone.View 6673 */ 6674 var $ = jQuery, 6675 Modal; 6676 6677 Modal = wp.media.View.extend({ 6678 tagName: 'div', 6679 template: wp.template('media-modal'), 6680 6681 attributes: { 6682 tabindex: 0 6683 }, 6684 6685 events: { 6686 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 6687 'keydown': 'keydown' 6688 }, 6689 6690 initialize: function() { 6691 _.defaults( this.options, { 6692 container: document.body, 6693 title: '', 6694 propagate: true, 6695 freeze: true 6696 }); 6697 6698 this.focusManager = new wp.media.view.FocusManager({ 6699 el: this.el 6700 }); 6701 }, 6702 /** 6703 * @returns {Object} 6704 */ 6705 prepare: function() { 6706 return { 6707 title: this.options.title 6708 }; 6709 }, 6710 6711 /** 6712 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6713 */ 6714 attach: function() { 6715 if ( this.views.attached ) { 6716 return this; 6717 } 6718 6719 if ( ! this.views.rendered ) { 6720 this.render(); 6721 } 6722 6723 this.$el.appendTo( this.options.container ); 6724 6725 // Manually mark the view as attached and trigger ready. 6726 this.views.attached = true; 6727 this.views.ready(); 6728 6729 return this.propagate('attach'); 6730 }, 6731 6732 /** 6733 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6734 */ 6735 detach: function() { 6736 if ( this.$el.is(':visible') ) { 6737 this.close(); 6738 } 6739 6740 this.$el.detach(); 6741 this.views.attached = false; 6742 return this.propagate('detach'); 6743 }, 6744 6745 /** 6746 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6747 */ 6748 open: function() { 6749 var $el = this.$el, 6750 options = this.options, 6751 mceEditor; 6752 6753 if ( $el.is(':visible') ) { 6754 return this; 6755 } 6756 6757 if ( ! this.views.attached ) { 6758 this.attach(); 6759 } 6760 6761 // If the `freeze` option is set, record the window's scroll position. 6762 if ( options.freeze ) { 6763 this._freeze = { 6764 scrollTop: $( window ).scrollTop() 6765 }; 6766 } 6767 6768 // Disable page scrolling. 6769 $( 'body' ).addClass( 'modal-open' ); 6770 6771 $el.show(); 6772 6773 // Try to close the onscreen keyboard 6774 if ( 'ontouchend' in document ) { 6775 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 6776 mceEditor.iframeElement.focus(); 6777 mceEditor.iframeElement.blur(); 6778 6779 setTimeout( function() { 6780 mceEditor.iframeElement.blur(); 6781 }, 100 ); 6782 } 6783 } 6784 6785 this.$el.focus(); 6786 6787 return this.propagate('open'); 6788 }, 6789 6790 /** 6791 * @param {Object} options 6792 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6793 */ 6794 close: function( options ) { 6795 var freeze = this._freeze; 6796 6797 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 6798 return this; 6799 } 6800 6801 // Enable page scrolling. 6802 $( 'body' ).removeClass( 'modal-open' ); 6803 6804 // Hide modal and remove restricted media modal tab focus once it's closed 6805 this.$el.hide().undelegate( 'keydown' ); 6806 6807 // Put focus back in useful location once modal is closed 6808 $('#wpbody-content').focus(); 6809 6810 this.propagate('close'); 6811 6812 // If the `freeze` option is set, restore the container's scroll position. 6813 if ( freeze ) { 6814 $( window ).scrollTop( freeze.scrollTop ); 6815 } 6816 6817 if ( options && options.escape ) { 6818 this.propagate('escape'); 6819 } 6820 6821 return this; 6822 }, 6823 /** 6824 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6825 */ 6826 escape: function() { 6827 return this.close({ escape: true }); 6828 }, 6829 /** 6830 * @param {Object} event 6831 */ 6832 escapeHandler: function( event ) { 6833 event.preventDefault(); 6834 this.escape(); 6835 }, 6836 6837 /** 6838 * @param {Array|Object} content Views to register to '.media-modal-content' 6839 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6840 */ 6841 content: function( content ) { 6842 this.views.set( '.media-modal-content', content ); 6843 return this; 6844 }, 6845 6846 /** 6847 * Triggers a modal event and if the `propagate` option is set, 6848 * forwards events to the modal's controller. 6849 * 6850 * @param {string} id 6851 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6852 */ 6853 propagate: function( id ) { 6854 this.trigger( id ); 6855 6856 if ( this.options.propagate ) { 6857 this.controller.trigger( id ); 6858 } 6859 6860 return this; 6861 }, 6862 /** 6863 * @param {Object} event 6864 */ 6865 keydown: function( event ) { 6866 // Close the modal when escape is pressed. 6867 if ( 27 === event.which && this.$el.is(':visible') ) { 6868 this.escape(); 6869 event.stopImmediatePropagation(); 6870 } 6871 } 6872 }); 6873 6874 module.exports = Modal; 6875 6876 },{}],52:[function(require,module,exports){ 6877 /*globals _, Backbone */ 6878 6879 /** 6880 * wp.media.view.PriorityList 6881 * 6882 * @class 6883 * @augments wp.media.View 6884 * @augments wp.Backbone.View 6885 * @augments Backbone.View 6886 */ 6887 var PriorityList = wp.media.View.extend({ 6888 tagName: 'div', 6889 6890 initialize: function() { 6891 this._views = {}; 6892 6893 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 6894 delete this.options.views; 6895 6896 if ( ! this.options.silent ) { 6897 this.render(); 6898 } 6899 }, 6900 /** 6901 * @param {string} id 6902 * @param {wp.media.View|Object} view 6903 * @param {Object} options 6904 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining 6905 */ 6906 set: function( id, view, options ) { 6907 var priority, views, index; 6908 6909 options = options || {}; 6910 6911 // Accept an object with an `id` : `view` mapping. 6912 if ( _.isObject( id ) ) { 6913 _.each( id, function( view, id ) { 6914 this.set( id, view ); 6915 }, this ); 6916 return this; 6917 } 6918 6919 if ( ! (view instanceof Backbone.View) ) { 6920 view = this.toView( view, id, options ); 6921 } 6922 view.controller = view.controller || this.controller; 6923 6924 this.unset( id ); 6925 6926 priority = view.options.priority || 10; 6927 views = this.views.get() || []; 6928 6929 _.find( views, function( existing, i ) { 6930 if ( existing.options.priority > priority ) { 6931 index = i; 6932 return true; 6933 } 6934 }); 6935 6936 this._views[ id ] = view; 6937 this.views.add( view, { 6938 at: _.isNumber( index ) ? index : views.length || 0 6939 }); 6940 6941 return this; 6942 }, 6943 /** 6944 * @param {string} id 6945 * @returns {wp.media.View} 6946 */ 6947 get: function( id ) { 6948 return this._views[ id ]; 6949 }, 6950 /** 6951 * @param {string} id 6952 * @returns {wp.media.view.PriorityList} 6953 */ 6954 unset: function( id ) { 6955 var view = this.get( id ); 6956 6957 if ( view ) { 6958 view.remove(); 6959 } 6960 6961 delete this._views[ id ]; 6962 return this; 6963 }, 6964 /** 6965 * @param {Object} options 6966 * @returns {wp.media.View} 6967 */ 6968 toView: function( options ) { 6969 return new wp.media.View( options ); 6970 } 6971 }); 6972 6973 module.exports = PriorityList; 6974 6975 },{}],53:[function(require,module,exports){ 6976 /** 6977 * wp.media.view.RouterItem 6978 * 6979 * @class 6980 * @augments wp.media.view.MenuItem 6981 * @augments wp.media.View 6982 * @augments wp.Backbone.View 6983 * @augments Backbone.View 6984 */ 6985 var RouterItem = wp.media.view.MenuItem.extend({ 6986 /** 6987 * On click handler to activate the content region's corresponding mode. 6988 */ 6989 click: function() { 6990 var contentMode = this.options.contentMode; 6991 if ( contentMode ) { 6992 this.controller.content.mode( contentMode ); 6993 } 6994 } 6995 }); 6996 6997 module.exports = RouterItem; 6998 6999 },{}],54:[function(require,module,exports){ 7000 /*globals wp */ 7001 7002 /** 7003 * wp.media.view.Router 7004 * 7005 * @class 7006 * @augments wp.media.view.Menu 7007 * @augments wp.media.view.PriorityList 7008 * @augments wp.media.View 7009 * @augments wp.Backbone.View 7010 * @augments Backbone.View 7011 */ 7012 var Menu = wp.media.view.Menu, 7013 Router; 7014 7015 Router = Menu.extend({ 7016 tagName: 'div', 7017 className: 'media-router', 7018 property: 'contentMode', 7019 ItemView: wp.media.view.RouterItem, 7020 region: 'router', 7021 7022 initialize: function() { 7023 this.controller.on( 'content:render', this.update, this ); 7024 // Call 'initialize' directly on the parent class. 7025 Menu.prototype.initialize.apply( this, arguments ); 7026 }, 7027 7028 update: function() { 7029 var mode = this.controller.content.mode(); 7030 if ( mode ) { 7031 this.select( mode ); 7032 } 7033 } 7034 }); 7035 7036 module.exports = Router; 7037 7038 },{}],55:[function(require,module,exports){ 7039 /*globals wp */ 7040 7041 /** 7042 * wp.media.view.Search 7043 * 7044 * @class 7045 * @augments wp.media.View 7046 * @augments wp.Backbone.View 7047 * @augments Backbone.View 7048 */ 7049 var l10n = wp.media.view.l10n, 7050 Search; 7051 7052 Search = wp.media.View.extend({ 7053 tagName: 'input', 7054 className: 'search', 7055 id: 'media-search-input', 7056 7057 attributes: { 7058 type: 'search', 7059 placeholder: l10n.search 7060 }, 7061 7062 events: { 7063 'input': 'search', 7064 'keyup': 'search', 7065 'change': 'search', 7066 'search': 'search' 7067 }, 7068 7069 /** 7070 * @returns {wp.media.view.Search} Returns itself to allow chaining 7071 */ 7072 render: function() { 7073 this.el.value = this.model.escape('search'); 7074 return this; 7075 }, 7076 7077 search: function( event ) { 7078 if ( event.target.value ) { 7079 this.model.set( 'search', event.target.value ); 7080 } else { 7081 this.model.unset('search'); 7082 } 7083 } 7084 }); 7085 7086 module.exports = Search; 7087 7088 },{}],56:[function(require,module,exports){ 7089 /*globals wp, _, Backbone */ 7090 7091 /** 7092 * wp.media.view.Selection 7093 * 7094 * @class 7095 * @augments wp.media.View 7096 * @augments wp.Backbone.View 7097 * @augments Backbone.View 7098 */ 7099 var l10n = wp.media.view.l10n, 7100 Selection; 7101 7102 Selection = wp.media.View.extend({ 7103 tagName: 'div', 7104 className: 'media-selection', 7105 template: wp.template('media-selection'), 7106 7107 events: { 7108 'click .edit-selection': 'edit', 7109 'click .clear-selection': 'clear' 7110 }, 7111 7112 initialize: function() { 7113 _.defaults( this.options, { 7114 editable: false, 7115 clearable: true 7116 }); 7117 7118 /** 7119 * @member {wp.media.view.Attachments.Selection} 7120 */ 7121 this.attachments = new wp.media.view.Attachments.Selection({ 7122 controller: this.controller, 7123 collection: this.collection, 7124 selection: this.collection, 7125 model: new Backbone.Model() 7126 }); 7127 7128 this.views.set( '.selection-view', this.attachments ); 7129 this.collection.on( 'add remove reset', this.refresh, this ); 7130 this.controller.on( 'content:activate', this.refresh, this ); 7131 }, 7132 7133 ready: function() { 7134 this.refresh(); 7135 }, 7136 7137 refresh: function() { 7138 // If the selection hasn't been rendered, bail. 7139 if ( ! this.$el.children().length ) { 7140 return; 7141 } 7142 7143 var collection = this.collection, 7144 editing = 'edit-selection' === this.controller.content.mode(); 7145 7146 // If nothing is selected, display nothing. 7147 this.$el.toggleClass( 'empty', ! collection.length ); 7148 this.$el.toggleClass( 'one', 1 === collection.length ); 7149 this.$el.toggleClass( 'editing', editing ); 7150 7151 this.$('.count').text( l10n.selected.replace('%d', collection.length) ); 7152 }, 7153 7154 edit: function( event ) { 7155 event.preventDefault(); 7156 if ( this.options.editable ) { 7157 this.options.editable.call( this, this.collection ); 7158 } 7159 }, 7160 7161 clear: function( event ) { 7162 event.preventDefault(); 7163 this.collection.reset(); 7164 7165 // Keep focus inside media modal 7166 // after clear link is selected 7167 this.controller.modal.focusManager.focus(); 7168 } 7169 }); 7170 7171 module.exports = Selection; 7172 7173 },{}],57:[function(require,module,exports){ 7174 /*globals _, Backbone */ 7175 7176 /** 7177 * wp.media.view.Settings 7178 * 7179 * @class 7180 * @augments wp.media.View 7181 * @augments wp.Backbone.View 7182 * @augments Backbone.View 7183 */ 7184 var View = wp.media.View, 7185 $ = Backbone.$, 7186 Settings; 7187 7188 Settings = View.extend({ 7189 events: { 7190 'click button': 'updateHandler', 7191 'change input': 'updateHandler', 7192 'change select': 'updateHandler', 7193 'change textarea': 'updateHandler' 7194 }, 7195 7196 initialize: function() { 7197 this.model = this.model || new Backbone.Model(); 7198 this.listenTo( this.model, 'change', this.updateChanges ); 7199 }, 7200 7201 prepare: function() { 7202 return _.defaults({ 7203 model: this.model.toJSON() 7204 }, this.options ); 7205 }, 7206 /** 7207 * @returns {wp.media.view.Settings} Returns itself to allow chaining 7208 */ 7209 render: function() { 7210 View.prototype.render.apply( this, arguments ); 7211 // Select the correct values. 7212 _( this.model.attributes ).chain().keys().each( this.update, this ); 7213 return this; 7214 }, 7215 /** 7216 * @param {string} key 7217 */ 7218 update: function( key ) { 7219 var value = this.model.get( key ), 7220 $setting = this.$('[data-setting="' + key + '"]'), 7221 $buttons, $value; 7222 7223 // Bail if we didn't find a matching setting. 7224 if ( ! $setting.length ) { 7225 return; 7226 } 7227 7228 // Attempt to determine how the setting is rendered and update 7229 // the selected value. 7230 7231 // Handle dropdowns. 7232 if ( $setting.is('select') ) { 7233 $value = $setting.find('[value="' + value + '"]'); 7234 7235 if ( $value.length ) { 7236 $setting.find('option').prop( 'selected', false ); 7237 $value.prop( 'selected', true ); 7238 } else { 7239 // If we can't find the desired value, record what *is* selected. 7240 this.model.set( key, $setting.find(':selected').val() ); 7241 } 7242 7243 // Handle button groups. 7244 } else if ( $setting.hasClass('button-group') ) { 7245 $buttons = $setting.find('button').removeClass('active'); 7246 $buttons.filter( '[value="' + value + '"]' ).addClass('active'); 7247 7248 // Handle text inputs and textareas. 7249 } else if ( $setting.is('input[type="text"], textarea') ) { 7250 if ( ! $setting.is(':focus') ) { 7251 $setting.val( value ); 7252 } 7253 // Handle checkboxes. 7254 } else if ( $setting.is('input[type="checkbox"]') ) { 7255 $setting.prop( 'checked', !! value && 'false' !== value ); 7256 } 7257 }, 7258 /** 7259 * @param {Object} event 7260 */ 7261 updateHandler: function( event ) { 7262 var $setting = $( event.target ).closest('[data-setting]'), 7263 value = event.target.value, 7264 userSetting; 7265 7266 event.preventDefault(); 7267 7268 if ( ! $setting.length ) { 7269 return; 7270 } 7271 7272 // Use the correct value for checkboxes. 7273 if ( $setting.is('input[type="checkbox"]') ) { 7274 value = $setting[0].checked; 7275 } 7276 7277 // Update the corresponding setting. 7278 this.model.set( $setting.data('setting'), value ); 7279 7280 // If the setting has a corresponding user setting, 7281 // update that as well. 7282 if ( userSetting = $setting.data('userSetting') ) { 7283 window.setUserSetting( userSetting, value ); 7284 } 7285 }, 7286 7287 updateChanges: function( model ) { 7288 if ( model.hasChanged() ) { 7289 _( model.changed ).chain().keys().each( this.update, this ); 7290 } 7291 } 7292 }); 7293 7294 module.exports = Settings; 7295 7296 },{}],58:[function(require,module,exports){ 7297 /*globals wp, _ */ 7298 7299 /** 7300 * wp.media.view.Settings.AttachmentDisplay 7301 * 7302 * @class 7303 * @augments wp.media.view.Settings 7304 * @augments wp.media.View 7305 * @augments wp.Backbone.View 7306 * @augments Backbone.View 7307 */ 7308 var Settings = wp.media.view.Settings, 7309 AttachmentDisplay; 7310 7311 AttachmentDisplay = Settings.extend({ 7312 className: 'attachment-display-settings', 7313 template: wp.template('attachment-display-settings'), 7314 7315 initialize: function() { 7316 var attachment = this.options.attachment; 7317 7318 _.defaults( this.options, { 7319 userSettings: false 7320 }); 7321 // Call 'initialize' directly on the parent class. 7322 Settings.prototype.initialize.apply( this, arguments ); 7323 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 7324 7325 if ( attachment ) { 7326 attachment.on( 'change:uploading', this.render, this ); 7327 } 7328 }, 7329 7330 dispose: function() { 7331 var attachment = this.options.attachment; 7332 if ( attachment ) { 7333 attachment.off( null, null, this ); 7334 } 7335 /** 7336 * call 'dispose' directly on the parent class 7337 */ 7338 Settings.prototype.dispose.apply( this, arguments ); 7339 }, 7340 /** 7341 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining 7342 */ 7343 render: function() { 7344 var attachment = this.options.attachment; 7345 if ( attachment ) { 7346 _.extend( this.options, { 7347 sizes: attachment.get('sizes'), 7348 type: attachment.get('type') 7349 }); 7350 } 7351 /** 7352 * call 'render' directly on the parent class 7353 */ 7354 Settings.prototype.render.call( this ); 7355 this.updateLinkTo(); 7356 return this; 7357 }, 7358 7359 updateLinkTo: function() { 7360 var linkTo = this.model.get('link'), 7361 $input = this.$('.link-to-custom'), 7362 attachment = this.options.attachment; 7363 7364 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 7365 $input.addClass( 'hidden' ); 7366 return; 7367 } 7368 7369 if ( attachment ) { 7370 if ( 'post' === linkTo ) { 7371 $input.val( attachment.get('link') ); 7372 } else if ( 'file' === linkTo ) { 7373 $input.val( attachment.get('url') ); 7374 } else if ( ! this.model.get('linkUrl') ) { 7375 $input.val('http://'); 7376 } 7377 7378 $input.prop( 'readonly', 'custom' !== linkTo ); 7379 } 7380 7381 $input.removeClass( 'hidden' ); 7382 7383 // If the input is visible, focus and select its contents. 7384 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) { 7385 $input.focus()[0].select(); 7386 } 7387 } 7388 }); 7389 7390 module.exports = AttachmentDisplay; 7391 7392 },{}],59:[function(require,module,exports){ 7393 /*globals wp */ 7394 7395 /** 7396 * wp.media.view.Settings.Gallery 7397 * 7398 * @class 7399 * @augments wp.media.view.Settings 7400 * @augments wp.media.View 7401 * @augments wp.Backbone.View 7402 * @augments Backbone.View 7403 */ 7404 var Gallery = wp.media.view.Settings.extend({ 7405 className: 'collection-settings gallery-settings', 7406 template: wp.template('gallery-settings') 7407 }); 7408 7409 module.exports = Gallery; 7410 7411 },{}],60:[function(require,module,exports){ 7412 /*globals wp */ 7413 7414 /** 7415 * wp.media.view.Settings.Playlist 7416 * 7417 * @class 7418 * @augments wp.media.view.Settings 7419 * @augments wp.media.View 7420 * @augments wp.Backbone.View 7421 * @augments Backbone.View 7422 */ 7423 var Playlist = wp.media.view.Settings.extend({ 7424 className: 'collection-settings playlist-settings', 7425 template: wp.template('playlist-settings') 7426 }); 7427 7428 module.exports = Playlist; 7429 7430 },{}],61:[function(require,module,exports){ 7431 /** 7432 * wp.media.view.Sidebar 7433 * 7434 * @class 7435 * @augments wp.media.view.PriorityList 7436 * @augments wp.media.View 7437 * @augments wp.Backbone.View 7438 * @augments Backbone.View 7439 */ 7440 var Sidebar = wp.media.view.PriorityList.extend({ 7441 className: 'media-sidebar' 7442 }); 7443 7444 module.exports = Sidebar; 7445 7446 },{}],62:[function(require,module,exports){ 7447 /*globals _ */ 7448 7449 /** 7450 * wp.media.view.Spinner 7451 * 7452 * @class 7453 * @augments wp.media.View 7454 * @augments wp.Backbone.View 7455 * @augments Backbone.View 7456 */ 7457 var Spinner = wp.media.View.extend({ 7458 tagName: 'span', 7459 className: 'spinner', 7460 spinnerTimeout: false, 7461 delay: 400, 7462 7463 show: function() { 7464 if ( ! this.spinnerTimeout ) { 7465 this.spinnerTimeout = _.delay(function( $el ) { 7466 $el.show(); 7467 }, this.delay, this.$el ); 7468 } 7469 7470 return this; 7471 }, 7472 7473 hide: function() { 7474 this.$el.hide(); 7475 this.spinnerTimeout = clearTimeout( this.spinnerTimeout ); 7476 7477 return this; 7478 } 7479 }); 7480 7481 module.exports = Spinner; 7482 7483 },{}],63:[function(require,module,exports){ 7484 /*globals _, Backbone */ 7485 7486 /** 7487 * wp.media.view.Toolbar 7488 * 7489 * A toolbar which consists of a primary and a secondary section. Each sections 7490 * can be filled with views. 7491 * 7492 * @class 7493 * @augments wp.media.View 7494 * @augments wp.Backbone.View 7495 * @augments Backbone.View 7496 */ 7497 var View = wp.media.View, 7498 Toolbar; 7499 7500 Toolbar = View.extend({ 7501 tagName: 'div', 7502 className: 'media-toolbar', 7503 7504 initialize: function() { 7505 var state = this.controller.state(), 7506 selection = this.selection = state.get('selection'), 7507 library = this.library = state.get('library'); 7508 7509 this._views = {}; 7510 7511 // The toolbar is composed of two `PriorityList` views. 7512 this.primary = new wp.media.view.PriorityList(); 7513 this.secondary = new wp.media.view.PriorityList(); 7514 this.primary.$el.addClass('media-toolbar-primary search-form'); 7515 this.secondary.$el.addClass('media-toolbar-secondary'); 7516 7517 this.views.set([ this.secondary, this.primary ]); 7518 7519 if ( this.options.items ) { 7520 this.set( this.options.items, { silent: true }); 7521 } 7522 7523 if ( ! this.options.silent ) { 7524 this.render(); 7525 } 7526 7527 if ( selection ) { 7528 selection.on( 'add remove reset', this.refresh, this ); 7529 } 7530 7531 if ( library ) { 7532 library.on( 'add remove reset', this.refresh, this ); 7533 } 7534 }, 7535 /** 7536 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining 7537 */ 7538 dispose: function() { 7539 if ( this.selection ) { 7540 this.selection.off( null, null, this ); 7541 } 7542 7543 if ( this.library ) { 7544 this.library.off( null, null, this ); 7545 } 7546 /** 7547 * call 'dispose' directly on the parent class 7548 */ 7549 return View.prototype.dispose.apply( this, arguments ); 7550 }, 7551 7552 ready: function() { 7553 this.refresh(); 7554 }, 7555 7556 /** 7557 * @param {string} id 7558 * @param {Backbone.View|Object} view 7559 * @param {Object} [options={}] 7560 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 7561 */ 7562 set: function( id, view, options ) { 7563 var list; 7564 options = options || {}; 7565 7566 // Accept an object with an `id` : `view` mapping. 7567 if ( _.isObject( id ) ) { 7568 _.each( id, function( view, id ) { 7569 this.set( id, view, { silent: true }); 7570 }, this ); 7571 7572 } else { 7573 if ( ! ( view instanceof Backbone.View ) ) { 7574 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 7575 view = new wp.media.view.Button( view ).render(); 7576 } 7577 7578 view.controller = view.controller || this.controller; 7579 7580 this._views[ id ] = view; 7581 7582 list = view.options.priority < 0 ? 'secondary' : 'primary'; 7583 this[ list ].set( id, view, options ); 7584 } 7585 7586 if ( ! options.silent ) { 7587 this.refresh(); 7588 } 7589 7590 return this; 7591 }, 7592 /** 7593 * @param {string} id 7594 * @returns {wp.media.view.Button} 7595 */ 7596 get: function( id ) { 7597 return this._views[ id ]; 7598 }, 7599 /** 7600 * @param {string} id 7601 * @param {Object} options 7602 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 7603 */ 7604 unset: function( id, options ) { 7605 delete this._views[ id ]; 7606 this.primary.unset( id, options ); 7607 this.secondary.unset( id, options ); 7608 7609 if ( ! options || ! options.silent ) { 7610 this.refresh(); 7611 } 7612 return this; 7613 }, 7614 7615 refresh: function() { 7616 var state = this.controller.state(), 7617 library = state.get('library'), 7618 selection = state.get('selection'); 7619 7620 _.each( this._views, function( button ) { 7621 if ( ! button.model || ! button.options || ! button.options.requires ) { 7622 return; 7623 } 7624 7625 var requires = button.options.requires, 7626 disabled = false; 7627 7628 // Prevent insertion of attachments if any of them are still uploading 7629 disabled = _.some( selection.models, function( attachment ) { 7630 return attachment.get('uploading') === true; 7631 }); 7632 7633 if ( requires.selection && selection && ! selection.length ) { 7634 disabled = true; 7635 } else if ( requires.library && library && ! library.length ) { 7636 disabled = true; 7637 } 7638 button.model.set( 'disabled', disabled ); 7639 }); 7640 } 7641 }); 7642 7643 module.exports = Toolbar; 7644 7645 },{}],64:[function(require,module,exports){ 7646 /*globals wp, _ */ 7647 7648 /** 7649 * wp.media.view.Toolbar.Embed 7650 * 7651 * @class 7652 * @augments wp.media.view.Toolbar.Select 7653 * @augments wp.media.view.Toolbar 7654 * @augments wp.media.View 7655 * @augments wp.Backbone.View 7656 * @augments Backbone.View 7657 */ 7658 var Select = wp.media.view.Toolbar.Select, 7659 l10n = wp.media.view.l10n, 7660 Embed; 7661 7662 Embed = Select.extend({ 7663 initialize: function() { 7664 _.defaults( this.options, { 7665 text: l10n.insertIntoPost, 7666 requires: false 7667 }); 7668 // Call 'initialize' directly on the parent class. 7669 Select.prototype.initialize.apply( this, arguments ); 7670 }, 7671 7672 refresh: function() { 7673 var url = this.controller.state().props.get('url'); 7674 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 7675 /** 7676 * call 'refresh' directly on the parent class 7677 */ 7678 Select.prototype.refresh.apply( this, arguments ); 7679 } 7680 }); 7681 7682 module.exports = Embed; 7683 7684 },{}],65:[function(require,module,exports){ 7685 /*globals wp, _ */ 7686 7687 /** 7688 * wp.media.view.Toolbar.Select 7689 * 7690 * @class 7691 * @augments wp.media.view.Toolbar 7692 * @augments wp.media.View 7693 * @augments wp.Backbone.View 7694 * @augments Backbone.View 7695 */ 7696 var Toolbar = wp.media.view.Toolbar, 7697 l10n = wp.media.view.l10n, 7698 Select; 7699 7700 Select = Toolbar.extend({ 7701 initialize: function() { 7702 var options = this.options; 7703 7704 _.bindAll( this, 'clickSelect' ); 7705 7706 _.defaults( options, { 7707 event: 'select', 7708 state: false, 7709 reset: true, 7710 close: true, 7711 text: l10n.select, 7712 7713 // Does the button rely on the selection? 7714 requires: { 7715 selection: true 7716 } 7717 }); 7718 7719 options.items = _.defaults( options.items || {}, { 7720 select: { 7721 style: 'primary', 7722 text: options.text, 7723 priority: 80, 7724 click: this.clickSelect, 7725 requires: options.requires 7726 } 7727 }); 7728 // Call 'initialize' directly on the parent class. 7729 Toolbar.prototype.initialize.apply( this, arguments ); 7730 }, 7731 7732 clickSelect: function() { 7733 var options = this.options, 7734 controller = this.controller; 7735 7736 if ( options.close ) { 7737 controller.close(); 7738 } 7739 7740 if ( options.event ) { 7741 controller.state().trigger( options.event ); 7742 } 7743 7744 if ( options.state ) { 7745 controller.setState( options.state ); 7746 } 7747 7748 if ( options.reset ) { 7749 controller.reset(); 7750 } 7751 } 7752 }); 7753 7754 module.exports = Select; 7755 7756 },{}],66:[function(require,module,exports){ 7757 /*globals wp, _, jQuery */ 7758 7759 /** 7760 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap 7761 * or #wp-fullscreen-body) and relays drag'n'dropped files to a media workflow. 7762 * 7763 * wp.media.view.EditorUploader 7764 * 7765 * @class 7766 * @augments wp.media.View 7767 * @augments wp.Backbone.View 7768 * @augments Backbone.View 7769 */ 7770 var View = wp.media.View, 7771 l10n = wp.media.view.l10n, 7772 $ = jQuery, 7773 EditorUploader; 7774 7775 EditorUploader = View.extend({ 7776 tagName: 'div', 7777 className: 'uploader-editor', 7778 template: wp.template( 'uploader-editor' ), 7779 7780 localDrag: false, 7781 overContainer: false, 7782 overDropzone: false, 7783 draggingFile: null, 7784 7785 /** 7786 * Bind drag'n'drop events to callbacks. 7787 */ 7788 initialize: function() { 7789 this.initialized = false; 7790 7791 // Bail if not enabled or UA does not support drag'n'drop or File API. 7792 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 7793 return this; 7794 } 7795 7796 this.$document = $(document); 7797 this.dropzones = []; 7798 this.files = []; 7799 7800 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 7801 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 7802 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 7803 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 7804 7805 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 7806 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 7807 7808 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 7809 this.localDrag = event.type === 'dragstart'; 7810 }, this ) ); 7811 7812 this.initialized = true; 7813 return this; 7814 }, 7815 7816 /** 7817 * Check browser support for drag'n'drop. 7818 * 7819 * @return Boolean 7820 */ 7821 browserSupport: function() { 7822 var supports = false, div = document.createElement('div'); 7823 7824 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 7825 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 7826 return supports; 7827 }, 7828 7829 isDraggingFile: function( event ) { 7830 if ( this.draggingFile !== null ) { 7831 return this.draggingFile; 7832 } 7833 7834 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 7835 return false; 7836 } 7837 7838 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 7839 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 7840 7841 return this.draggingFile; 7842 }, 7843 7844 refresh: function( e ) { 7845 var dropzone_id; 7846 for ( dropzone_id in this.dropzones ) { 7847 // Hide the dropzones only if dragging has left the screen. 7848 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 7849 } 7850 7851 if ( ! _.isUndefined( e ) ) { 7852 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 7853 } 7854 7855 if ( ! this.overContainer && ! this.overDropzone ) { 7856 this.draggingFile = null; 7857 } 7858 7859 return this; 7860 }, 7861 7862 render: function() { 7863 if ( ! this.initialized ) { 7864 return this; 7865 } 7866 7867 View.prototype.render.apply( this, arguments ); 7868 $( '.wp-editor-wrap, #wp-fullscreen-body' ).each( _.bind( this.attach, this ) ); 7869 return this; 7870 }, 7871 7872 attach: function( index, editor ) { 7873 // Attach a dropzone to an editor. 7874 var dropzone = this.$el.clone(); 7875 this.dropzones.push( dropzone ); 7876 $( editor ).append( dropzone ); 7877 return this; 7878 }, 7879 7880 /** 7881 * When a file is dropped on the editor uploader, open up an editor media workflow 7882 * and upload the file immediately. 7883 * 7884 * @param {jQuery.Event} event The 'drop' event. 7885 */ 7886 drop: function( event ) { 7887 var $wrap = null, uploadView; 7888 7889 this.containerDragleave( event ); 7890 this.dropzoneDragleave( event ); 7891 7892 this.files = event.originalEvent.dataTransfer.files; 7893 if ( this.files.length < 1 ) { 7894 return; 7895 } 7896 7897 // Set the active editor to the drop target. 7898 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 7899 if ( $wrap.length > 0 && $wrap[0].id ) { 7900 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 7901 } 7902 7903 if ( ! this.workflow ) { 7904 this.workflow = wp.media.editor.open( 'content', { 7905 frame: 'post', 7906 state: 'insert', 7907 title: l10n.addMedia, 7908 multiple: true 7909 }); 7910 uploadView = this.workflow.uploader; 7911 if ( uploadView.uploader && uploadView.uploader.ready ) { 7912 this.addFiles.apply( this ); 7913 } else { 7914 this.workflow.on( 'uploader:ready', this.addFiles, this ); 7915 } 7916 } else { 7917 this.workflow.state().reset(); 7918 this.addFiles.apply( this ); 7919 this.workflow.open(); 7920 } 7921 7922 return false; 7923 }, 7924 7925 /** 7926 * Add the files to the uploader. 7927 */ 7928 addFiles: function() { 7929 if ( this.files.length ) { 7930 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 7931 this.files = []; 7932 } 7933 return this; 7934 }, 7935 7936 containerDragover: function( event ) { 7937 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 7938 return; 7939 } 7940 7941 this.overContainer = true; 7942 this.refresh(); 7943 }, 7944 7945 containerDragleave: function() { 7946 this.overContainer = false; 7947 7948 // Throttle dragleave because it's called when bouncing from some elements to others. 7949 _.delay( _.bind( this.refresh, this ), 50 ); 7950 }, 7951 7952 dropzoneDragover: function( event ) { 7953 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 7954 return; 7955 } 7956 7957 this.overDropzone = true; 7958 this.refresh( event ); 7959 return false; 7960 }, 7961 7962 dropzoneDragleave: function( e ) { 7963 this.overDropzone = false; 7964 _.delay( _.bind( this.refresh, this, e ), 50 ); 7965 }, 7966 7967 click: function( e ) { 7968 // In the rare case where the dropzone gets stuck, hide it on click. 7969 this.containerDragleave( e ); 7970 this.dropzoneDragleave( e ); 7971 this.localDrag = false; 7972 } 7973 }); 7974 7975 module.exports = EditorUploader; 7976 7977 },{}],67:[function(require,module,exports){ 7978 /*globals wp, _ */ 7979 7980 /** 7981 * wp.media.view.UploaderInline 7982 * 7983 * The inline uploader that shows up in the 'Upload Files' tab. 7984 * 7985 * @class 7986 * @augments wp.media.View 7987 * @augments wp.Backbone.View 7988 * @augments Backbone.View 7989 */ 7990 var View = wp.media.View, 7991 UploaderInline; 7992 7993 UploaderInline = View.extend({ 7994 tagName: 'div', 7995 className: 'uploader-inline', 7996 template: wp.template('uploader-inline'), 7997 7998 events: { 7999 'click .close': 'hide' 8000 }, 8001 8002 initialize: function() { 8003 _.defaults( this.options, { 8004 message: '', 8005 status: true, 8006 canClose: false 8007 }); 8008 8009 if ( ! this.options.$browser && this.controller.uploader ) { 8010 this.options.$browser = this.controller.uploader.$browser; 8011 } 8012 8013 if ( _.isUndefined( this.options.postId ) ) { 8014 this.options.postId = wp.media.view.settings.post.id; 8015 } 8016 8017 if ( this.options.status ) { 8018 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 8019 controller: this.controller 8020 }) ); 8021 } 8022 }, 8023 8024 prepare: function() { 8025 var suggestedWidth = this.controller.state().get('suggestedWidth'), 8026 suggestedHeight = this.controller.state().get('suggestedHeight'), 8027 data = {}; 8028 8029 data.message = this.options.message; 8030 data.canClose = this.options.canClose; 8031 8032 if ( suggestedWidth && suggestedHeight ) { 8033 data.suggestedWidth = suggestedWidth; 8034 data.suggestedHeight = suggestedHeight; 8035 } 8036 8037 return data; 8038 }, 8039 /** 8040 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 8041 */ 8042 dispose: function() { 8043 if ( this.disposing ) { 8044 /** 8045 * call 'dispose' directly on the parent class 8046 */ 8047 return View.prototype.dispose.apply( this, arguments ); 8048 } 8049 8050 // Run remove on `dispose`, so we can be sure to refresh the 8051 // uploader with a view-less DOM. Track whether we're disposing 8052 // so we don't trigger an infinite loop. 8053 this.disposing = true; 8054 return this.remove(); 8055 }, 8056 /** 8057 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 8058 */ 8059 remove: function() { 8060 /** 8061 * call 'remove' directly on the parent class 8062 */ 8063 var result = View.prototype.remove.apply( this, arguments ); 8064 8065 _.defer( _.bind( this.refresh, this ) ); 8066 return result; 8067 }, 8068 8069 refresh: function() { 8070 var uploader = this.controller.uploader; 8071 8072 if ( uploader ) { 8073 uploader.refresh(); 8074 } 8075 }, 8076 /** 8077 * @returns {wp.media.view.UploaderInline} 8078 */ 8079 ready: function() { 8080 var $browser = this.options.$browser, 8081 $placeholder; 8082 8083 if ( this.controller.uploader ) { 8084 $placeholder = this.$('.browser'); 8085 8086 // Check if we've already replaced the placeholder. 8087 if ( $placeholder[0] === $browser[0] ) { 8088 return; 8089 } 8090 8091 $browser.detach().text( $placeholder.text() ); 8092 $browser[0].className = $placeholder[0].className; 8093 $placeholder.replaceWith( $browser.show() ); 8094 } 8095 8096 this.refresh(); 8097 return this; 8098 }, 8099 show: function() { 8100 this.$el.removeClass( 'hidden' ); 8101 }, 8102 hide: function() { 8103 this.$el.addClass( 'hidden' ); 8104 } 8105 8106 }); 8107 8108 module.exports = UploaderInline; 8109 8110 },{}],68:[function(require,module,exports){ 8111 /*globals wp */ 8112 8113 /** 8114 * wp.media.view.UploaderStatusError 8115 * 8116 * @class 8117 * @augments wp.media.View 8118 * @augments wp.Backbone.View 8119 * @augments Backbone.View 8120 */ 8121 var UploaderStatusError = wp.media.View.extend({ 8122 className: 'upload-error', 8123 template: wp.template('uploader-status-error') 8124 }); 8125 8126 module.exports = UploaderStatusError; 8127 8128 },{}],69:[function(require,module,exports){ 8129 /*globals wp, _ */ 8130 8131 /** 8132 * wp.media.view.UploaderStatus 8133 * 8134 * An uploader status for on-going uploads. 8135 * 8136 * @class 8137 * @augments wp.media.View 8138 * @augments wp.Backbone.View 8139 * @augments Backbone.View 8140 */ 8141 var View = wp.media.View, 8142 UploaderStatus; 8143 8144 UploaderStatus = View.extend({ 8145 className: 'media-uploader-status', 8146 template: wp.template('uploader-status'), 8147 8148 events: { 8149 'click .upload-dismiss-errors': 'dismiss' 8150 }, 8151 8152 initialize: function() { 8153 this.queue = wp.Uploader.queue; 8154 this.queue.on( 'add remove reset', this.visibility, this ); 8155 this.queue.on( 'add remove reset change:percent', this.progress, this ); 8156 this.queue.on( 'add remove reset change:uploading', this.info, this ); 8157 8158 this.errors = wp.Uploader.errors; 8159 this.errors.reset(); 8160 this.errors.on( 'add remove reset', this.visibility, this ); 8161 this.errors.on( 'add', this.error, this ); 8162 }, 8163 /** 8164 * @global wp.Uploader 8165 * @returns {wp.media.view.UploaderStatus} 8166 */ 8167 dispose: function() { 8168 wp.Uploader.queue.off( null, null, this ); 8169 /** 8170 * call 'dispose' directly on the parent class 8171 */ 8172 View.prototype.dispose.apply( this, arguments ); 8173 return this; 8174 }, 8175 8176 visibility: function() { 8177 this.$el.toggleClass( 'uploading', !! this.queue.length ); 8178 this.$el.toggleClass( 'errors', !! this.errors.length ); 8179 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 8180 }, 8181 8182 ready: function() { 8183 _.each({ 8184 '$bar': '.media-progress-bar div', 8185 '$index': '.upload-index', 8186 '$total': '.upload-total', 8187 '$filename': '.upload-filename' 8188 }, function( selector, key ) { 8189 this[ key ] = this.$( selector ); 8190 }, this ); 8191 8192 this.visibility(); 8193 this.progress(); 8194 this.info(); 8195 }, 8196 8197 progress: function() { 8198 var queue = this.queue, 8199 $bar = this.$bar; 8200 8201 if ( ! $bar || ! queue.length ) { 8202 return; 8203 } 8204 8205 $bar.width( ( queue.reduce( function( memo, attachment ) { 8206 if ( ! attachment.get('uploading') ) { 8207 return memo + 100; 8208 } 8209 8210 var percent = attachment.get('percent'); 8211 return memo + ( _.isNumber( percent ) ? percent : 100 ); 8212 }, 0 ) / queue.length ) + '%' ); 8213 }, 8214 8215 info: function() { 8216 var queue = this.queue, 8217 index = 0, active; 8218 8219 if ( ! queue.length ) { 8220 return; 8221 } 8222 8223 active = this.queue.find( function( attachment, i ) { 8224 index = i; 8225 return attachment.get('uploading'); 8226 }); 8227 8228 this.$index.text( index + 1 ); 8229 this.$total.text( queue.length ); 8230 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 8231 }, 8232 /** 8233 * @param {string} filename 8234 * @returns {string} 8235 */ 8236 filename: function( filename ) { 8237 return wp.media.truncate( _.escape( filename ), 24 ); 8238 }, 8239 /** 8240 * @param {Backbone.Model} error 8241 */ 8242 error: function( error ) { 8243 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 8244 filename: this.filename( error.get('file').name ), 8245 message: error.get('message') 8246 }), { at: 0 }); 8247 }, 8248 8249 /** 8250 * @global wp.Uploader 8251 * 8252 * @param {Object} event 8253 */ 8254 dismiss: function( event ) { 8255 var errors = this.views.get('.upload-errors'); 8256 8257 event.preventDefault(); 8258 8259 if ( errors ) { 8260 _.invoke( errors, 'remove' ); 8261 } 8262 wp.Uploader.errors.reset(); 8263 } 8264 }); 8265 8266 module.exports = UploaderStatus; 8267 8268 },{}],70:[function(require,module,exports){ 8269 /*globals wp, _, jQuery */ 8270 8271 /** 8272 * wp.media.view.UploaderWindow 8273 * 8274 * An uploader window that allows for dragging and dropping media. 8275 * 8276 * @class 8277 * @augments wp.media.View 8278 * @augments wp.Backbone.View 8279 * @augments Backbone.View 8280 * 8281 * @param {object} [options] Options hash passed to the view. 8282 * @param {object} [options.uploader] Uploader properties. 8283 * @param {jQuery} [options.uploader.browser] 8284 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 8285 * @param {object} [options.uploader.params] 8286 */ 8287 var $ = jQuery, 8288 UploaderWindow; 8289 8290 UploaderWindow = wp.media.View.extend({ 8291 tagName: 'div', 8292 className: 'uploader-window', 8293 template: wp.template('uploader-window'), 8294 8295 initialize: function() { 8296 var uploader; 8297 8298 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body'); 8299 8300 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 8301 dropzone: this.$el, 8302 browser: this.$browser, 8303 params: {} 8304 }); 8305 8306 // Ensure the dropzone is a jQuery collection. 8307 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 8308 uploader.dropzone = $( uploader.dropzone ); 8309 } 8310 8311 this.controller.on( 'activate', this.refresh, this ); 8312 8313 this.controller.on( 'detach', function() { 8314 this.$browser.remove(); 8315 }, this ); 8316 }, 8317 8318 refresh: function() { 8319 if ( this.uploader ) { 8320 this.uploader.refresh(); 8321 } 8322 }, 8323 8324 ready: function() { 8325 var postId = wp.media.view.settings.post.id, 8326 dropzone; 8327 8328 // If the uploader already exists, bail. 8329 if ( this.uploader ) { 8330 return; 8331 } 8332 8333 if ( postId ) { 8334 this.options.uploader.params.post_id = postId; 8335 } 8336 this.uploader = new wp.Uploader( this.options.uploader ); 8337 8338 dropzone = this.uploader.dropzone; 8339 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 8340 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 8341 8342 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 8343 }, 8344 8345 _ready: function() { 8346 this.controller.trigger( 'uploader:ready' ); 8347 }, 8348 8349 show: function() { 8350 var $el = this.$el.show(); 8351 8352 // Ensure that the animation is triggered by waiting until 8353 // the transparent element is painted into the DOM. 8354 _.defer( function() { 8355 $el.css({ opacity: 1 }); 8356 }); 8357 }, 8358 8359 hide: function() { 8360 var $el = this.$el.css({ opacity: 0 }); 8361 8362 wp.media.transition( $el ).done( function() { 8363 // Transition end events are subject to race conditions. 8364 // Make sure that the value is set as intended. 8365 if ( '0' === $el.css('opacity') ) { 8366 $el.hide(); 8367 } 8368 }); 8369 8370 // https://core.trac.wordpress.org/ticket/27341 8371 _.delay( function() { 8372 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 8373 $el.hide(); 8374 } 8375 }, 500 ); 8376 } 8377 }); 8378 8379 module.exports = UploaderWindow; 8380 8381 },{}],71:[function(require,module,exports){ 8382 /*globals wp */ 8383 8384 /** 8385 * wp.media.View 8386 * 8387 * The base view class for media. 8388 * 8389 * Undelegating events, removing events from the model, and 8390 * removing events from the controller mirror the code for 8391 * `Backbone.View.dispose` in Backbone 0.9.8 development. 8392 * 8393 * This behavior has since been removed, and should not be used 8394 * outside of the media manager. 8395 * 8396 * @class 8397 * @augments wp.Backbone.View 8398 * @augments Backbone.View 8399 */ 8400 var View = wp.Backbone.View.extend({ 8401 constructor: function( options ) { 8402 if ( options && options.controller ) { 8403 this.controller = options.controller; 8404 } 8405 wp.Backbone.View.apply( this, arguments ); 8406 }, 8407 /** 8408 * @todo The internal comment mentions this might have been a stop-gap 8409 * before Backbone 0.9.8 came out. Figure out if Backbone core takes 8410 * care of this in Backbone.View now. 8411 * 8412 * @returns {wp.media.View} Returns itself to allow chaining 8413 */ 8414 dispose: function() { 8415 // Undelegating events, removing events from the model, and 8416 // removing events from the controller mirror the code for 8417 // `Backbone.View.dispose` in Backbone 0.9.8 development. 8418 this.undelegateEvents(); 8419 8420 if ( this.model && this.model.off ) { 8421 this.model.off( null, null, this ); 8422 } 8423 8424 if ( this.collection && this.collection.off ) { 8425 this.collection.off( null, null, this ); 8426 } 8427 8428 // Unbind controller events. 8429 if ( this.controller && this.controller.off ) { 8430 this.controller.off( null, null, this ); 8431 } 8432 8433 return this; 8434 }, 8435 /** 8436 * @returns {wp.media.View} Returns itself to allow chaining 8437 */ 8438 remove: function() { 8439 this.dispose(); 8440 /** 8441 * call 'remove' directly on the parent class 8442 */ 8443 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 8444 } 8445 }); 8446 8447 module.exports = View; 8448 8449 },{}]},{},[17]); -
src/wp-includes/script-loader.php
410 410 $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 ); 411 411 412 412 $scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 ); 413 $scripts->add( 'media-models', "/wp-includes/js/media /models$suffix.js", array( 'wp-backbone' ), false, 1 );413 $scripts->add( 'media-models', "/wp-includes/js/media-models$suffix.js", array( 'wp-backbone' ), false, 1 ); 414 414 did_action( 'init' ) && $scripts->localize( 'media-models', '_wpMediaModelsL10n', array( 415 415 'settings' => array( 416 416 'ajaxurl' => admin_url( 'admin-ajax.php', 'relative' ), … … 420 420 421 421 // To enqueue media-views or media-editor, call wp_enqueue_media(). 422 422 // Both rely on numerous settings, styles, and templates to operate correctly. 423 $scripts->add( 'media-views', "/wp-includes/js/media /views$suffix.js", array( 'utils', 'media-models', 'wp-plupload', 'jquery-ui-sortable', 'wp-mediaelement' ), false, 1 );423 $scripts->add( 'media-views', "/wp-includes/js/media-views$suffix.js", array( 'utils', 'media-models', 'wp-plupload', 'jquery-ui-sortable', 'wp-mediaelement' ), false, 1 ); 424 424 $scripts->add( 'media-editor', "/wp-includes/js/media-editor$suffix.js", array( 'shortcode', 'media-views' ), false, 1 ); 425 $scripts->add( 'media-audiovideo', "/wp-includes/js/media /audio-video$suffix.js", array( 'media-editor' ), false, 1 );425 $scripts->add( 'media-audiovideo', "/wp-includes/js/media-audiovideo$suffix.js", array( 'media-editor' ), false, 1 ); 426 426 $scripts->add( 'mce-view', "/wp-includes/js/mce-view$suffix.js", array( 'shortcode', 'media-models', 'media-audiovideo', 'wp-playlist' ), false, 1 ); 427 427 428 428 if ( is_admin() ) { … … 556 556 557 557 $scripts->add( 'list-revisions', "/wp-includes/js/wp-list-revisions$suffix.js" ); 558 558 559 $scripts->add( 'media-grid', "/wp-includes/js/media /grid$suffix.js", array( 'media-editor' ), false, 1 );559 $scripts->add( 'media-grid', "/wp-includes/js/media-grid$suffix.js", array( 'media-editor' ), false, 1 ); 560 560 $scripts->add( 'media', "/wp-admin/js/media$suffix.js", array( 'jquery' ), false, 1 ); 561 561 did_action( 'init' ) && $scripts->localize( 'media', 'attachMediaBoxL10n', array( 562 562 'error' => __( 'An error has occurred. Please reload the page and try again.' ),