Changeset 50018
- Timestamp:
- 01/25/2021 08:14:35 PM (4 years ago)
- Location:
- branches/4.4/src/wp-includes/js
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/4.4/src/wp-includes/js/media-audiovideo.js
r46498 r50018 1 /******/ (function(modules) { // webpackBootstrap 2 /******/ // The module cache 3 /******/ var installedModules = {}; 4 /******/ 5 /******/ // The require function 6 /******/ function __webpack_require__(moduleId) { 7 /******/ 8 /******/ // Check if module is in cache 9 /******/ if(installedModules[moduleId]) { 10 /******/ return installedModules[moduleId].exports; 11 /******/ } 12 /******/ // Create a new module (and put it into the cache) 13 /******/ var module = installedModules[moduleId] = { 14 /******/ i: moduleId, 15 /******/ l: false, 16 /******/ exports: {} 17 /******/ }; 18 /******/ 19 /******/ // Execute the module function 20 /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 /******/ 22 /******/ // Flag the module as loaded 23 /******/ module.l = true; 24 /******/ 25 /******/ // Return the exports of the module 26 /******/ return module.exports; 27 /******/ } 28 /******/ 29 /******/ 30 /******/ // expose the modules object (__webpack_modules__) 31 /******/ __webpack_require__.m = modules; 32 /******/ 33 /******/ // expose the module cache 34 /******/ __webpack_require__.c = installedModules; 35 /******/ 36 /******/ // define getter function for harmony exports 37 /******/ __webpack_require__.d = function(exports, name, getter) { 38 /******/ if(!__webpack_require__.o(exports, name)) { 39 /******/ Object.defineProperty(exports, name, { 40 /******/ configurable: false, 41 /******/ enumerable: true, 42 /******/ get: getter 43 /******/ }); 44 /******/ } 45 /******/ }; 46 /******/ 47 /******/ // getDefaultExport function for compatibility with non-harmony modules 48 /******/ __webpack_require__.n = function(module) { 49 /******/ var getter = module && module.__esModule ? 50 /******/ function getDefault() { return module['default']; } : 51 /******/ function getModuleExports() { return module; }; 52 /******/ __webpack_require__.d(getter, 'a', getter); 53 /******/ return getter; 54 /******/ }; 55 /******/ 56 /******/ // Object.prototype.hasOwnProperty.call 57 /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 58 /******/ 59 /******/ // __webpack_public_path__ 60 /******/ __webpack_require__.p = ""; 61 /******/ 62 /******/ // Load entry module and return exports 63 /******/ return __webpack_require__(__webpack_require__.s = 0); 64 /******/ }) 65 /************************************************************************/ 66 /******/ ([ 67 /* 0 */ 68 /***/ (function(module, exports, __webpack_require__) { 69 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){ 70 2 var media = wp.media, 71 3 baseSettings = window._wpmejsSettings || {}, … … 274 206 }; 275 207 276 media.model.PostMedia = __webpack_require__( 1 ); 277 media.controller.AudioDetails = __webpack_require__( 2 ); 278 media.controller.VideoDetails = __webpack_require__( 3 ); 279 media.view.MediaFrame.MediaDetails = __webpack_require__( 4 ); 280 media.view.MediaFrame.AudioDetails = __webpack_require__( 5 ); 281 media.view.MediaFrame.VideoDetails = __webpack_require__( 6 ); 282 media.view.MediaDetails = __webpack_require__( 7 ); 283 media.view.AudioDetails = __webpack_require__( 8 ); 284 media.view.VideoDetails = __webpack_require__( 9 ); 285 286 287 /***/ }), 288 /* 1 */ 289 /***/ (function(module, exports) { 290 291 /** 292 * wp.media.model.PostMedia 293 * 294 * Shared model class for audio and video. Updates the model after 295 * "Add Audio|Video Source" and "Replace Audio|Video" states return 296 * 297 * @class 298 * @augments Backbone.Model 299 */ 300 var PostMedia = Backbone.Model.extend({ 301 initialize: function() { 302 this.attachment = false; 303 }, 304 305 setSource: function( attachment ) { 306 this.attachment = attachment; 307 this.extension = attachment.get( 'filename' ).split('.').pop(); 308 309 if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) { 310 this.unset( 'src' ); 311 } 312 313 if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) { 314 this.set( this.extension, this.attachment.get( 'url' ) ); 315 } else { 316 this.unset( this.extension ); 317 } 318 }, 319 320 changeAttachment: function( attachment ) { 321 this.setSource( attachment ); 322 323 this.unset( 'src' ); 324 _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) { 325 this.unset( ext ); 326 }, this ); 327 } 328 }); 329 330 module.exports = PostMedia; 331 332 333 /***/ }), 334 /* 2 */ 335 /***/ (function(module, exports) { 336 208 media.model.PostMedia = require( './models/post-media.js' ); 209 media.controller.AudioDetails = require( './controllers/audio-details.js' ); 210 media.controller.VideoDetails = require( './controllers/video-details.js' ); 211 media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' ); 212 media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' ); 213 media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' ); 214 media.view.MediaDetails = require( './views/media-details.js' ); 215 media.view.AudioDetails = require( './views/audio-details.js' ); 216 media.view.VideoDetails = require( './views/video-details.js' ); 217 218 },{"./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){ 337 219 /** 338 220 * wp.media.controller.AudioDetails … … 367 249 module.exports = AudioDetails; 368 250 369 370 /***/ }), 371 /* 3 */ 372 /***/ (function(module, exports) { 373 251 },{}],3:[function(require,module,exports){ 374 252 /** 375 253 * wp.media.controller.VideoDetails … … 404 282 module.exports = VideoDetails; 405 283 406 407 /***/ }), 408 /* 4 */ 409 /***/ (function(module, exports) { 410 284 },{}],4:[function(require,module,exports){ 285 /** 286 * wp.media.model.PostMedia 287 * 288 * Shared model class for audio and video. Updates the model after 289 * "Add Audio|Video Source" and "Replace Audio|Video" states return 290 * 291 * @class 292 * @augments Backbone.Model 293 */ 294 var PostMedia = Backbone.Model.extend({ 295 initialize: function() { 296 this.attachment = false; 297 }, 298 299 setSource: function( attachment ) { 300 this.attachment = attachment; 301 this.extension = attachment.get( 'filename' ).split('.').pop(); 302 303 if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) { 304 this.unset( 'src' ); 305 } 306 307 if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) { 308 this.set( this.extension, this.attachment.get( 'url' ) ); 309 } else { 310 this.unset( this.extension ); 311 } 312 }, 313 314 changeAttachment: function( attachment ) { 315 this.setSource( attachment ); 316 317 this.unset( 'src' ); 318 _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) { 319 this.unset( ext ); 320 }, this ); 321 } 322 }); 323 324 module.exports = PostMedia; 325 326 },{}],5:[function(require,module,exports){ 327 /** 328 * wp.media.view.AudioDetails 329 * 330 * @class 331 * @augments wp.media.view.MediaDetails 332 * @augments wp.media.view.Settings.AttachmentDisplay 333 * @augments wp.media.view.Settings 334 * @augments wp.media.View 335 * @augments wp.Backbone.View 336 * @augments Backbone.View 337 */ 338 var MediaDetails = wp.media.view.MediaDetails, 339 AudioDetails; 340 341 AudioDetails = MediaDetails.extend({ 342 className: 'audio-details', 343 template: wp.template('audio-details'), 344 345 setMedia: function() { 346 var audio = this.$('.wp-audio-shortcode'); 347 348 if ( audio.find( 'source' ).length ) { 349 if ( audio.is(':hidden') ) { 350 audio.show(); 351 } 352 this.media = MediaDetails.prepareSrc( audio.get(0) ); 353 } else { 354 audio.hide(); 355 this.media = false; 356 } 357 358 return this; 359 } 360 }); 361 362 module.exports = AudioDetails; 363 364 },{}],6:[function(require,module,exports){ 365 /** 366 * wp.media.view.MediaFrame.AudioDetails 367 * 368 * @class 369 * @augments wp.media.view.MediaFrame.MediaDetails 370 * @augments wp.media.view.MediaFrame.Select 371 * @augments wp.media.view.MediaFrame 372 * @augments wp.media.view.Frame 373 * @augments wp.media.View 374 * @augments wp.Backbone.View 375 * @augments Backbone.View 376 * @mixes wp.media.controller.StateMachine 377 */ 378 var MediaDetails = wp.media.view.MediaFrame.MediaDetails, 379 MediaLibrary = wp.media.controller.MediaLibrary, 380 381 l10n = wp.media.view.l10n, 382 AudioDetails; 383 384 AudioDetails = MediaDetails.extend({ 385 defaults: { 386 id: 'audio', 387 url: '', 388 menu: 'audio-details', 389 content: 'audio-details', 390 toolbar: 'audio-details', 391 type: 'link', 392 title: l10n.audioDetailsTitle, 393 priority: 120 394 }, 395 396 initialize: function( options ) { 397 options.DetailsView = wp.media.view.AudioDetails; 398 options.cancelText = l10n.audioDetailsCancel; 399 options.addText = l10n.audioAddSourceTitle; 400 401 MediaDetails.prototype.initialize.call( this, options ); 402 }, 403 404 bindHandlers: function() { 405 MediaDetails.prototype.bindHandlers.apply( this, arguments ); 406 407 this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this ); 408 this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this ); 409 }, 410 411 createStates: function() { 412 this.states.add([ 413 new wp.media.controller.AudioDetails( { 414 media: this.media 415 } ), 416 417 new MediaLibrary( { 418 type: 'audio', 419 id: 'replace-audio', 420 title: l10n.audioReplaceTitle, 421 toolbar: 'replace-audio', 422 media: this.media, 423 menu: 'audio-details' 424 } ), 425 426 new MediaLibrary( { 427 type: 'audio', 428 id: 'add-audio-source', 429 title: l10n.audioAddSourceTitle, 430 toolbar: 'add-audio-source', 431 media: this.media, 432 menu: false 433 } ) 434 ]); 435 } 436 }); 437 438 module.exports = AudioDetails; 439 440 },{}],7:[function(require,module,exports){ 411 441 /** 412 442 * wp.media.view.MediaFrame.MediaDetails … … 538 568 module.exports = MediaDetails; 539 569 540 541 /***/ }), 542 /* 5 */ 543 /***/ (function(module, exports) { 544 545 /** 546 * wp.media.view.MediaFrame.AudioDetails 547 * 548 * @class 549 * @augments wp.media.view.MediaFrame.MediaDetails 550 * @augments wp.media.view.MediaFrame.Select 551 * @augments wp.media.view.MediaFrame 552 * @augments wp.media.view.Frame 553 * @augments wp.media.View 554 * @augments wp.Backbone.View 555 * @augments Backbone.View 556 * @mixes wp.media.controller.StateMachine 557 */ 558 var MediaDetails = wp.media.view.MediaFrame.MediaDetails, 559 MediaLibrary = wp.media.controller.MediaLibrary, 560 561 l10n = wp.media.view.l10n, 562 AudioDetails; 563 564 AudioDetails = MediaDetails.extend({ 565 defaults: { 566 id: 'audio', 567 url: '', 568 menu: 'audio-details', 569 content: 'audio-details', 570 toolbar: 'audio-details', 571 type: 'link', 572 title: l10n.audioDetailsTitle, 573 priority: 120 574 }, 575 576 initialize: function( options ) { 577 options.DetailsView = wp.media.view.AudioDetails; 578 options.cancelText = l10n.audioDetailsCancel; 579 options.addText = l10n.audioAddSourceTitle; 580 581 MediaDetails.prototype.initialize.call( this, options ); 582 }, 583 584 bindHandlers: function() { 585 MediaDetails.prototype.bindHandlers.apply( this, arguments ); 586 587 this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this ); 588 this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this ); 589 }, 590 591 createStates: function() { 592 this.states.add([ 593 new wp.media.controller.AudioDetails( { 594 media: this.media 595 } ), 596 597 new MediaLibrary( { 598 type: 'audio', 599 id: 'replace-audio', 600 title: l10n.audioReplaceTitle, 601 toolbar: 'replace-audio', 602 media: this.media, 603 menu: 'audio-details' 604 } ), 605 606 new MediaLibrary( { 607 type: 'audio', 608 id: 'add-audio-source', 609 title: l10n.audioAddSourceTitle, 610 toolbar: 'add-audio-source', 611 media: this.media, 612 menu: false 613 } ) 614 ]); 615 } 616 }); 617 618 module.exports = AudioDetails; 619 620 621 /***/ }), 622 /* 6 */ 623 /***/ (function(module, exports) { 624 570 },{}],8:[function(require,module,exports){ 625 571 /** 626 572 * wp.media.view.MediaFrame.VideoDetails … … 757 703 module.exports = VideoDetails; 758 704 759 760 /***/ }), 761 /* 7 */ 762 /***/ (function(module, exports) { 763 705 },{}],9:[function(require,module,exports){ 764 706 /* global MediaElementPlayer */ 765 707 … … 929 871 module.exports = MediaDetails; 930 872 931 932 /***/ }), 933 /* 8 */ 934 /***/ (function(module, exports) { 935 936 /** 937 * wp.media.view.AudioDetails 938 * 939 * @class 940 * @augments wp.media.view.MediaDetails 941 * @augments wp.media.view.Settings.AttachmentDisplay 942 * @augments wp.media.view.Settings 943 * @augments wp.media.View 944 * @augments wp.Backbone.View 945 * @augments Backbone.View 946 */ 947 var MediaDetails = wp.media.view.MediaDetails, 948 AudioDetails; 949 950 AudioDetails = MediaDetails.extend({ 951 className: 'audio-details', 952 template: wp.template('audio-details'), 953 954 setMedia: function() { 955 var audio = this.$('.wp-audio-shortcode'); 956 957 if ( audio.find( 'source' ).length ) { 958 if ( audio.is(':hidden') ) { 959 audio.show(); 960 } 961 this.media = MediaDetails.prepareSrc( audio.get(0) ); 962 } else { 963 audio.hide(); 964 this.media = false; 965 } 966 967 return this; 968 } 969 }); 970 971 module.exports = AudioDetails; 972 973 974 /***/ }), 975 /* 9 */ 976 /***/ (function(module, exports) { 977 873 },{}],10:[function(require,module,exports){ 978 874 /** 979 875 * wp.media.view.VideoDetails … … 1018 914 module.exports = VideoDetails; 1019 915 1020 1021 /***/ }) 1022 /******/ ]); 916 },{}]},{},[1]); -
branches/4.4/src/wp-includes/js/media-grid.js
r46498 r50018 1 /******/ (function(modules) { // webpackBootstrap 2 /******/ // The module cache 3 /******/ var installedModules = {}; 4 /******/ 5 /******/ // The require function 6 /******/ function __webpack_require__(moduleId) { 7 /******/ 8 /******/ // Check if module is in cache 9 /******/ if(installedModules[moduleId]) { 10 /******/ return installedModules[moduleId].exports; 11 /******/ } 12 /******/ // Create a new module (and put it into the cache) 13 /******/ var module = installedModules[moduleId] = { 14 /******/ i: moduleId, 15 /******/ l: false, 16 /******/ exports: {} 17 /******/ }; 18 /******/ 19 /******/ // Execute the module function 20 /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 /******/ 22 /******/ // Flag the module as loaded 23 /******/ module.l = true; 24 /******/ 25 /******/ // Return the exports of the module 26 /******/ return module.exports; 27 /******/ } 28 /******/ 29 /******/ 30 /******/ // expose the modules object (__webpack_modules__) 31 /******/ __webpack_require__.m = modules; 32 /******/ 33 /******/ // expose the module cache 34 /******/ __webpack_require__.c = installedModules; 35 /******/ 36 /******/ // define getter function for harmony exports 37 /******/ __webpack_require__.d = function(exports, name, getter) { 38 /******/ if(!__webpack_require__.o(exports, name)) { 39 /******/ Object.defineProperty(exports, name, { 40 /******/ configurable: false, 41 /******/ enumerable: true, 42 /******/ get: getter 43 /******/ }); 44 /******/ } 45 /******/ }; 46 /******/ 47 /******/ // getDefaultExport function for compatibility with non-harmony modules 48 /******/ __webpack_require__.n = function(module) { 49 /******/ var getter = module && module.__esModule ? 50 /******/ function getDefault() { return module['default']; } : 51 /******/ function getModuleExports() { return module; }; 52 /******/ __webpack_require__.d(getter, 'a', getter); 53 /******/ return getter; 54 /******/ }; 55 /******/ 56 /******/ // Object.prototype.hasOwnProperty.call 57 /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 58 /******/ 59 /******/ // __webpack_public_path__ 60 /******/ __webpack_require__.p = ""; 61 /******/ 62 /******/ // Load entry module and return exports 63 /******/ return __webpack_require__(__webpack_require__.s = 10); 64 /******/ }) 65 /************************************************************************/ 66 /******/ ([ 67 /* 0 */, 68 /* 1 */, 69 /* 2 */, 70 /* 3 */, 71 /* 4 */, 72 /* 5 */, 73 /* 6 */, 74 /* 7 */, 75 /* 8 */, 76 /* 9 */, 77 /* 10 */ 78 /***/ (function(module, exports, __webpack_require__) { 79 80 var media = wp.media; 81 82 media.controller.EditAttachmentMetadata = __webpack_require__( 11 ); 83 media.view.MediaFrame.Manage = __webpack_require__( 12 ); 84 media.view.Attachment.Details.TwoColumn = __webpack_require__( 13 ); 85 media.view.MediaFrame.Manage.Router = __webpack_require__( 14 ); 86 media.view.EditImage.Details = __webpack_require__( 15 ); 87 media.view.MediaFrame.EditAttachments = __webpack_require__( 16 ); 88 media.view.SelectModeToggleButton = __webpack_require__( 17 ); 89 media.view.DeleteSelectedButton = __webpack_require__( 18 ); 90 media.view.DeleteSelectedPermanentlyButton = __webpack_require__( 19 ); 91 92 93 /***/ }), 94 /* 11 */ 95 /***/ (function(module, exports) { 96 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){ 97 2 /** 98 3 * wp.media.controller.EditAttachmentMetadata … … 122 27 module.exports = EditAttachmentMetadata; 123 28 124 125 /***/ }), 126 /* 12 */ 127 /***/ (function(module, exports) { 128 129 /** 130 * wp.media.view.MediaFrame.Manage 131 * 132 * A generic management frame workflow. 133 * 134 * Used in the media grid view. 135 * 136 * @class 137 * @augments wp.media.view.MediaFrame 138 * @augments wp.media.view.Frame 139 * @augments wp.media.View 140 * @augments wp.Backbone.View 141 * @augments Backbone.View 142 * @mixes wp.media.controller.StateMachine 143 */ 144 var MediaFrame = wp.media.view.MediaFrame, 145 Library = wp.media.controller.Library, 146 147 $ = Backbone.$, 148 Manage; 149 150 Manage = MediaFrame.extend({ 151 /** 152 * @global wp.Uploader 153 */ 154 initialize: function() { 155 _.defaults( this.options, { 156 title: '', 157 modal: false, 158 selection: [], 159 library: {}, // Options hash for the query to the media library. 160 multiple: 'add', 161 state: 'library', 162 uploader: true, 163 mode: [ 'grid', 'edit' ] 164 }); 165 166 this.$body = $( document.body ); 167 this.$window = $( window ); 168 this.$adminBar = $( '#wpadminbar' ); 169 this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) ); 170 $( document ).on( 'click', '.page-title-action', _.bind( this.addNewClickHandler, this ) ); 171 172 // Ensure core and media grid view UI is enabled. 173 this.$el.addClass('wp-core-ui'); 174 175 // Force the uploader off if the upload limit has been exceeded or 176 // if the browser isn't supported. 177 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 178 this.options.uploader = false; 179 } 180 181 // Initialize a window-wide uploader. 182 if ( this.options.uploader ) { 183 this.uploader = new wp.media.view.UploaderWindow({ 184 controller: this, 185 uploader: { 186 dropzone: document.body, 187 container: document.body 188 } 189 }).render(); 190 this.uploader.ready(); 191 $('body').append( this.uploader.el ); 192 193 this.options.uploader = false; 194 } 195 196 this.gridRouter = new wp.media.view.MediaFrame.Manage.Router(); 197 198 // Call 'initialize' directly on the parent class. 199 MediaFrame.prototype.initialize.apply( this, arguments ); 200 201 // Append the frame view directly the supplied container. 202 this.$el.appendTo( this.options.container ); 203 204 this.createStates(); 205 this.bindRegionModeHandlers(); 206 this.render(); 207 this.bindSearchHandler(); 208 }, 209 210 bindSearchHandler: function() { 211 var search = this.$( '#media-search-input' ), 212 currentSearch = this.options.container.data( 'search' ), 213 searchView = this.browserView.toolbar.get( 'search' ).$el, 214 listMode = this.$( '.view-list' ), 215 216 input = _.debounce( function (e) { 217 var val = $( e.currentTarget ).val(), 218 url = ''; 219 220 if ( val ) { 221 url += '?search=' + val; 222 } 223 this.gridRouter.navigate( this.gridRouter.baseUrl( url ) ); 224 }, 1000 ); 225 226 // Update the URL when entering search string (at most once per second) 227 search.on( 'input', _.bind( input, this ) ); 228 searchView.val( currentSearch ).trigger( 'input' ); 229 230 this.gridRouter.on( 'route:search', function () { 231 var href = window.location.href; 232 if ( href.indexOf( 'mode=' ) > -1 ) { 233 href = href.replace( /mode=[^&]+/g, 'mode=list' ); 234 } else { 235 href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list'; 236 } 237 href = href.replace( 'search=', 's=' ); 238 listMode.prop( 'href', href ); 239 } ); 240 }, 241 242 /** 243 * Create the default states for the frame. 244 */ 245 createStates: function() { 246 var options = this.options; 247 248 if ( this.options.states ) { 249 return; 250 } 251 252 // Add the default states. 253 this.states.add([ 254 new Library({ 255 library: wp.media.query( options.library ), 256 multiple: options.multiple, 257 title: options.title, 258 content: 'browse', 259 toolbar: 'select', 260 contentUserSetting: false, 261 filterable: 'all', 262 autoSelect: false 263 }) 264 ]); 265 }, 266 267 /** 268 * Bind region mode activation events to proper handlers. 269 */ 270 bindRegionModeHandlers: function() { 271 this.on( 'content:create:browse', this.browseContent, this ); 272 273 // Handle a frame-level event for editing an attachment. 274 this.on( 'edit:attachment', this.openEditAttachmentModal, this ); 275 276 this.on( 'select:activate', this.bindKeydown, this ); 277 this.on( 'select:deactivate', this.unbindKeydown, this ); 278 }, 279 280 handleKeydown: function( e ) { 281 if ( 27 === e.which ) { 282 e.preventDefault(); 283 this.deactivateMode( 'select' ).activateMode( 'edit' ); 284 } 285 }, 286 287 bindKeydown: function() { 288 this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) ); 289 }, 290 291 unbindKeydown: function() { 292 this.$body.off( 'keydown.select' ); 293 }, 294 295 fixPosition: function() { 296 var $browser, $toolbar; 297 if ( ! this.isModeActive( 'select' ) ) { 298 return; 299 } 300 301 $browser = this.$('.attachments-browser'); 302 $toolbar = $browser.find('.media-toolbar'); 303 304 // Offset doesn't appear to take top margin into account, hence +16 305 if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) { 306 $browser.addClass( 'fixed' ); 307 $toolbar.css('width', $browser.width() + 'px'); 308 } else { 309 $browser.removeClass( 'fixed' ); 310 $toolbar.css('width', ''); 311 } 312 }, 313 314 /** 315 * Click handler for the `Add New` button. 316 */ 317 addNewClickHandler: function( event ) { 318 event.preventDefault(); 319 this.trigger( 'toggle:upload:attachment' ); 320 }, 321 322 /** 323 * Open the Edit Attachment modal. 324 */ 325 openEditAttachmentModal: function( model ) { 326 // Create a new EditAttachment frame, passing along the library and the attachment model. 327 wp.media( { 328 frame: 'edit-attachments', 329 controller: this, 330 library: this.state().get('library'), 331 model: model 332 } ); 333 }, 334 335 /** 336 * Create an attachments browser view within the content region. 337 * 338 * @param {Object} contentRegion Basic object with a `view` property, which 339 * should be set with the proper region view. 340 * @this wp.media.controller.Region 341 */ 342 browseContent: function( contentRegion ) { 343 var state = this.state(); 344 345 // Browse our library of attachments. 346 this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({ 347 controller: this, 348 collection: state.get('library'), 349 selection: state.get('selection'), 350 model: state, 351 sortable: state.get('sortable'), 352 search: state.get('searchable'), 353 filters: state.get('filterable'), 354 date: state.get('date'), 355 display: state.get('displaySettings'), 356 dragInfo: state.get('dragInfo'), 357 sidebar: 'errors', 358 359 suggestedWidth: state.get('suggestedWidth'), 360 suggestedHeight: state.get('suggestedHeight'), 361 362 AttachmentView: state.get('AttachmentView'), 363 364 scrollElement: document 365 }); 366 this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) ); 367 368 this.errors = wp.Uploader.errors; 369 this.errors.on( 'add remove reset', this.sidebarVisibility, this ); 370 }, 371 372 sidebarVisibility: function() { 373 this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length ); 374 }, 375 376 bindDeferred: function() { 377 if ( ! this.browserView.dfd ) { 378 return; 379 } 380 this.browserView.dfd.done( _.bind( this.startHistory, this ) ); 381 }, 382 383 startHistory: function() { 384 // Verify pushState support and activate 385 if ( window.history && window.history.pushState ) { 386 Backbone.history.start( { 387 root: window._wpMediaGridSettings.adminUrl, 388 pushState: true 389 } ); 390 } 391 } 392 }); 393 394 module.exports = Manage; 395 396 397 /***/ }), 398 /* 13 */ 399 /***/ (function(module, exports) { 400 401 /** 402 * wp.media.view.Attachment.Details.TwoColumn 403 * 404 * A similar view to media.view.Attachment.Details 405 * for use in the Edit Attachment modal. 406 * 407 * @class 408 * @augments wp.media.view.Attachment.Details 409 * @augments wp.media.view.Attachment 410 * @augments wp.media.View 411 * @augments wp.Backbone.View 412 * @augments Backbone.View 413 */ 414 var Details = wp.media.view.Attachment.Details, 415 TwoColumn; 416 417 TwoColumn = Details.extend({ 418 template: wp.template( 'attachment-details-two-column' ), 419 420 editAttachment: function( event ) { 421 event.preventDefault(); 422 this.controller.content.mode( 'edit-image' ); 423 }, 424 425 /** 426 * Noop this from parent class, doesn't apply here. 427 */ 428 toggleSelectionHandler: function() {}, 429 430 render: function() { 431 Details.prototype.render.apply( this, arguments ); 432 433 wp.media.mixin.removeAllPlayers(); 434 this.$( 'audio, video' ).each( function (i, elem) { 435 var el = wp.media.view.MediaDetails.prepareSrc( elem ); 436 new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings ); 437 } ); 438 } 439 }); 440 441 module.exports = TwoColumn; 442 443 444 /***/ }), 445 /* 14 */ 446 /***/ (function(module, exports) { 447 29 },{}],2:[function(require,module,exports){ 30 var media = wp.media; 31 32 media.controller.EditAttachmentMetadata = require( './controllers/edit-attachment-metadata.js' ); 33 media.view.MediaFrame.Manage = require( './views/frame/manage.js' ); 34 media.view.Attachment.Details.TwoColumn = require( './views/attachment/details-two-column.js' ); 35 media.view.MediaFrame.Manage.Router = require( './routers/manage.js' ); 36 media.view.EditImage.Details = require( './views/edit-image-details.js' ); 37 media.view.MediaFrame.EditAttachments = require( './views/frame/edit-attachments.js' ); 38 media.view.SelectModeToggleButton = require( './views/button/select-mode-toggle.js' ); 39 media.view.DeleteSelectedButton = require( './views/button/delete-selected.js' ); 40 media.view.DeleteSelectedPermanentlyButton = require( './views/button/delete-selected-permanently.js' ); 41 42 },{"./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){ 448 43 /** 449 44 * wp.media.view.MediaFrame.Manage.Router … … 493 88 module.exports = Router; 494 89 495 496 /***/ }), 497 /* 15 */ 498 /***/ (function(module, exports) { 499 90 },{}],4:[function(require,module,exports){ 500 91 /** 501 * wp.media.view.EditImage.Details 92 * wp.media.view.Attachment.Details.TwoColumn 93 * 94 * A similar view to media.view.Attachment.Details 95 * for use in the Edit Attachment modal. 502 96 * 503 97 * @class 504 * @augments wp.media.view.EditImage 98 * @augments wp.media.view.Attachment.Details 99 * @augments wp.media.view.Attachment 505 100 * @augments wp.media.View 506 101 * @augments wp.Backbone.View 507 102 * @augments Backbone.View 508 103 */ 509 var View = wp.media.View, 510 EditImage = wp.media.view.EditImage, 511 Details; 512 513 Details = EditImage.extend({ 514 initialize: function( options ) { 515 this.editor = window.imageEdit; 516 this.frame = options.frame; 517 this.controller = options.controller; 518 View.prototype.initialize.apply( this, arguments ); 519 }, 520 521 back: function() { 522 this.frame.content.mode( 'edit-metadata' ); 523 }, 524 525 save: function() { 526 this.model.fetch().done( _.bind( function() { 527 this.frame.content.mode( 'edit-metadata' ); 528 }, this ) ); 104 var Details = wp.media.view.Attachment.Details, 105 TwoColumn; 106 107 TwoColumn = Details.extend({ 108 template: wp.template( 'attachment-details-two-column' ), 109 110 editAttachment: function( event ) { 111 event.preventDefault(); 112 this.controller.content.mode( 'edit-image' ); 113 }, 114 115 /** 116 * Noop this from parent class, doesn't apply here. 117 */ 118 toggleSelectionHandler: function() {}, 119 120 render: function() { 121 Details.prototype.render.apply( this, arguments ); 122 123 wp.media.mixin.removeAllPlayers(); 124 this.$( 'audio, video' ).each( function (i, elem) { 125 var el = wp.media.view.MediaDetails.prepareSrc( elem ); 126 new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings ); 127 } ); 529 128 } 530 129 }); 531 130 532 module.exports = Details; 533 534 535 /***/ }), 536 /* 16 */ 537 /***/ (function(module, exports) { 538 131 module.exports = TwoColumn; 132 133 },{}],5:[function(require,module,exports){ 539 134 /** 540 * wp.media.view.MediaFrame.EditAttachments 541 * 542 * A frame for editing the details of a specific media item. 543 * 544 * Opens in a modal by default. 545 * 546 * Requires an attachment model to be passed in the options hash under `model`. 135 * wp.media.view.DeleteSelectedPermanentlyButton 136 * 137 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic 547 138 * 548 139 * @class 549 * @augments wp.media.view.Frame 140 * @augments wp.media.view.DeleteSelectedButton 141 * @augments wp.media.view.Button 550 142 * @augments wp.media.View 551 143 * @augments wp.Backbone.View 552 144 * @augments Backbone.View 553 * @mixes wp.media.controller.StateMachine554 145 */ 555 var Frame = wp.media.view.Frame, 556 MediaFrame = wp.media.view.MediaFrame, 557 558 $ = jQuery, 559 EditAttachments; 560 561 EditAttachments = MediaFrame.extend({ 562 563 className: 'edit-attachment-frame', 564 template: wp.template( 'edit-attachment-frame' ), 565 regions: [ 'title', 'content' ], 566 567 events: { 568 'click .left': 'previousMediaItem', 569 'click .right': 'nextMediaItem' 570 }, 571 146 var Button = wp.media.view.Button, 147 DeleteSelected = wp.media.view.DeleteSelectedButton, 148 DeleteSelectedPermanently; 149 150 DeleteSelectedPermanently = DeleteSelected.extend({ 572 151 initialize: function() { 573 Frame.prototype.initialize.apply( this, arguments ); 574 575 _.defaults( this.options, { 576 modal: true, 577 state: 'edit-attachment' 578 }); 579 580 this.controller = this.options.controller; 581 this.gridRouter = this.controller.gridRouter; 582 this.library = this.options.library; 583 584 if ( this.options.model ) { 585 this.model = this.options.model; 586 } 587 588 this.bindHandlers(); 589 this.createStates(); 590 this.createModal(); 591 592 this.title.mode( 'default' ); 593 this.toggleNav(); 594 }, 595 596 bindHandlers: function() { 597 // Bind default title creation. 598 this.on( 'title:create:default', this.createTitle, this ); 599 600 // Close the modal if the attachment is deleted. 601 this.listenTo( this.model, 'change:status destroy', this.close, this ); 602 603 this.on( 'content:create:edit-metadata', this.editMetadataMode, this ); 604 this.on( 'content:create:edit-image', this.editImageMode, this ); 605 this.on( 'content:render:edit-image', this.editImageModeRender, this ); 606 this.on( 'close', this.detach ); 607 }, 608 609 createModal: function() { 610 // Initialize modal container view. 611 if ( this.options.modal ) { 612 this.modal = new wp.media.view.Modal({ 613 controller: this, 614 title: this.options.title 615 }); 616 617 this.modal.on( 'open', _.bind( function () { 618 $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) ); 619 }, this ) ); 620 621 // Completely destroy the modal DOM element when closing it. 622 this.modal.on( 'close', _.bind( function() { 623 this.modal.remove(); 624 $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */ 625 // Restore the original focus item if possible 626 $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus(); 627 this.resetRoute(); 628 }, this ) ); 629 630 // Set this frame as the modal's content. 631 this.modal.content( this ); 632 this.modal.open(); 633 } 634 }, 635 636 /** 637 * Add the default states to the frame. 638 */ 639 createStates: function() { 640 this.states.add([ 641 new wp.media.controller.EditAttachmentMetadata( { model: this.model } ) 642 ]); 643 }, 644 645 /** 646 * Content region rendering callback for the `edit-metadata` mode. 647 * 648 * @param {Object} contentRegion Basic object with a `view` property, which 649 * should be set with the proper region view. 650 */ 651 editMetadataMode: function( contentRegion ) { 652 contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({ 653 controller: this, 654 model: this.model 655 }); 656 657 /** 658 * Attach a subview to display fields added via the 659 * `attachment_fields_to_edit` filter. 660 */ 661 contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({ 662 controller: this, 663 model: this.model 664 }) ); 665 666 // Update browser url when navigating media details 667 if ( this.model ) { 668 this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) ); 669 } 670 }, 671 672 /** 673 * Render the EditImage view into the frame's content region. 674 * 675 * @param {Object} contentRegion Basic object with a `view` property, which 676 * should be set with the proper region view. 677 */ 678 editImageMode: function( contentRegion ) { 679 var editImageController = new wp.media.controller.EditImage( { 680 model: this.model, 681 frame: this 682 } ); 683 // Noop some methods. 684 editImageController._toolbar = function() {}; 685 editImageController._router = function() {}; 686 editImageController._menu = function() {}; 687 688 contentRegion.view = new wp.media.view.EditImage.Details( { 689 model: this.model, 690 frame: this, 691 controller: editImageController 692 } ); 693 }, 694 695 editImageModeRender: function( view ) { 696 view.on( 'ready', view.loadEditor ); 697 }, 698 699 toggleNav: function() { 700 this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() ); 701 this.$('.right').toggleClass( 'disabled', ! this.hasNext() ); 702 }, 703 704 /** 705 * Rerender the view. 706 */ 707 rerender: function() { 708 // Only rerender the `content` region. 709 if ( this.content.mode() !== 'edit-metadata' ) { 710 this.content.mode( 'edit-metadata' ); 711 } else { 712 this.content.render(); 713 } 714 715 this.toggleNav(); 716 }, 717 718 /** 719 * Click handler to switch to the previous media item. 720 */ 721 previousMediaItem: function() { 722 if ( ! this.hasPrevious() ) { 723 this.$( '.left' ).blur(); 724 return; 725 } 726 this.model = this.library.at( this.getCurrentIndex() - 1 ); 727 this.rerender(); 728 this.$( '.left' ).focus(); 729 }, 730 731 /** 732 * Click handler to switch to the next media item. 733 */ 734 nextMediaItem: function() { 735 if ( ! this.hasNext() ) { 736 this.$( '.right' ).blur(); 737 return; 738 } 739 this.model = this.library.at( this.getCurrentIndex() + 1 ); 740 this.rerender(); 741 this.$( '.right' ).focus(); 742 }, 743 744 getCurrentIndex: function() { 745 return this.library.indexOf( this.model ); 746 }, 747 748 hasNext: function() { 749 return ( this.getCurrentIndex() + 1 ) < this.library.length; 750 }, 751 752 hasPrevious: function() { 753 return ( this.getCurrentIndex() - 1 ) > -1; 754 }, 755 /** 756 * Respond to the keyboard events: right arrow, left arrow, except when 757 * focus is in a textarea or input field. 758 */ 759 keyEvent: function( event ) { 760 if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) { 761 return; 762 } 763 764 // The right arrow key 765 if ( 39 === event.keyCode ) { 766 this.nextMediaItem(); 767 } 768 // The left arrow key 769 if ( 37 === event.keyCode ) { 770 this.previousMediaItem(); 771 } 772 }, 773 774 resetRoute: function() { 775 this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) ); 152 DeleteSelected.prototype.initialize.apply( this, arguments ); 153 this.listenTo( this.controller, 'select:activate', this.selectActivate ); 154 this.listenTo( this.controller, 'select:deactivate', this.selectDeactivate ); 155 }, 156 157 filterChange: function( model ) { 158 this.canShow = ( 'trash' === model.get( 'status' ) ); 159 }, 160 161 selectActivate: function() { 162 this.toggleDisabled(); 163 this.$el.toggleClass( 'hidden', ! this.canShow ); 164 }, 165 166 selectDeactivate: function() { 167 this.toggleDisabled(); 168 this.$el.addClass( 'hidden' ); 169 }, 170 171 render: function() { 172 Button.prototype.render.apply( this, arguments ); 173 this.selectActivate(); 174 return this; 776 175 } 777 176 }); 778 177 779 module.exports = EditAttachments; 780 781 782 /***/ }), 783 /* 17 */ 784 /***/ (function(module, exports) { 785 178 module.exports = DeleteSelectedPermanently; 179 180 },{}],6:[function(require,module,exports){ 181 /** 182 * wp.media.view.DeleteSelectedButton 183 * 184 * A button that handles bulk Delete/Trash logic 185 * 186 * @class 187 * @augments wp.media.view.Button 188 * @augments wp.media.View 189 * @augments wp.Backbone.View 190 * @augments Backbone.View 191 */ 192 var Button = wp.media.view.Button, 193 l10n = wp.media.view.l10n, 194 DeleteSelected; 195 196 DeleteSelected = Button.extend({ 197 initialize: function() { 198 Button.prototype.initialize.apply( this, arguments ); 199 if ( this.options.filters ) { 200 this.listenTo( this.options.filters.model, 'change', this.filterChange ); 201 } 202 this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled ); 203 }, 204 205 filterChange: function( model ) { 206 if ( 'trash' === model.get( 'status' ) ) { 207 this.model.set( 'text', l10n.untrashSelected ); 208 } else if ( wp.media.view.settings.mediaTrash ) { 209 this.model.set( 'text', l10n.trashSelected ); 210 } else { 211 this.model.set( 'text', l10n.deleteSelected ); 212 } 213 }, 214 215 toggleDisabled: function() { 216 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length ); 217 }, 218 219 render: function() { 220 Button.prototype.render.apply( this, arguments ); 221 if ( this.controller.isModeActive( 'select' ) ) { 222 this.$el.addClass( 'delete-selected-button' ); 223 } else { 224 this.$el.addClass( 'delete-selected-button hidden' ); 225 } 226 this.toggleDisabled(); 227 return this; 228 } 229 }); 230 231 module.exports = DeleteSelected; 232 233 },{}],7:[function(require,module,exports){ 786 234 /** 787 235 * wp.media.view.SelectModeToggleButton … … 857 305 module.exports = SelectModeToggle; 858 306 859 860 /***/ }), 861 /* 18 */ 862 /***/ (function(module, exports) { 863 307 },{}],8:[function(require,module,exports){ 864 308 /** 865 * wp.media.view.DeleteSelectedButton 866 * 867 * A button that handles bulk Delete/Trash logic 309 * wp.media.view.EditImage.Details 868 310 * 869 311 * @class 870 * @augments wp.media.view. Button312 * @augments wp.media.view.EditImage 871 313 * @augments wp.media.View 872 314 * @augments wp.Backbone.View 873 315 * @augments Backbone.View 874 316 */ 875 var Button = wp.media.view.Button, 876 l10n = wp.media.view.l10n, 877 DeleteSelected; 878 879 DeleteSelected = Button.extend({ 880 initialize: function() { 881 Button.prototype.initialize.apply( this, arguments ); 882 if ( this.options.filters ) { 883 this.listenTo( this.options.filters.model, 'change', this.filterChange ); 884 } 885 this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled ); 886 }, 887 888 filterChange: function( model ) { 889 if ( 'trash' === model.get( 'status' ) ) { 890 this.model.set( 'text', l10n.untrashSelected ); 891 } else if ( wp.media.view.settings.mediaTrash ) { 892 this.model.set( 'text', l10n.trashSelected ); 893 } else { 894 this.model.set( 'text', l10n.deleteSelected ); 895 } 896 }, 897 898 toggleDisabled: function() { 899 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length ); 900 }, 901 902 render: function() { 903 Button.prototype.render.apply( this, arguments ); 904 if ( this.controller.isModeActive( 'select' ) ) { 905 this.$el.addClass( 'delete-selected-button' ); 906 } else { 907 this.$el.addClass( 'delete-selected-button hidden' ); 908 } 909 this.toggleDisabled(); 910 return this; 317 var View = wp.media.View, 318 EditImage = wp.media.view.EditImage, 319 Details; 320 321 Details = EditImage.extend({ 322 initialize: function( options ) { 323 this.editor = window.imageEdit; 324 this.frame = options.frame; 325 this.controller = options.controller; 326 View.prototype.initialize.apply( this, arguments ); 327 }, 328 329 back: function() { 330 this.frame.content.mode( 'edit-metadata' ); 331 }, 332 333 save: function() { 334 this.model.fetch().done( _.bind( function() { 335 this.frame.content.mode( 'edit-metadata' ); 336 }, this ) ); 911 337 } 912 338 }); 913 339 914 module.exports = DeleteSelected; 915 916 917 /***/ }), 918 /* 19 */ 919 /***/ (function(module, exports) { 920 340 module.exports = Details; 341 342 },{}],9:[function(require,module,exports){ 921 343 /** 922 * wp.media.view.DeleteSelectedPermanentlyButton 923 * 924 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic 344 * wp.media.view.MediaFrame.EditAttachments 345 * 346 * A frame for editing the details of a specific media item. 347 * 348 * Opens in a modal by default. 349 * 350 * Requires an attachment model to be passed in the options hash under `model`. 925 351 * 926 352 * @class 927 * @augments wp.media.view.DeleteSelectedButton 928 * @augments wp.media.view.Button 353 * @augments wp.media.view.Frame 929 354 * @augments wp.media.View 930 355 * @augments wp.Backbone.View 931 356 * @augments Backbone.View 357 * @mixes wp.media.controller.StateMachine 932 358 */ 933 var Button = wp.media.view.Button, 934 DeleteSelected = wp.media.view.DeleteSelectedButton, 935 DeleteSelectedPermanently; 936 937 DeleteSelectedPermanently = DeleteSelected.extend({ 359 var Frame = wp.media.view.Frame, 360 MediaFrame = wp.media.view.MediaFrame, 361 362 $ = jQuery, 363 EditAttachments; 364 365 EditAttachments = MediaFrame.extend({ 366 367 className: 'edit-attachment-frame', 368 template: wp.template( 'edit-attachment-frame' ), 369 regions: [ 'title', 'content' ], 370 371 events: { 372 'click .left': 'previousMediaItem', 373 'click .right': 'nextMediaItem' 374 }, 375 938 376 initialize: function() { 939 DeleteSelected.prototype.initialize.apply( this, arguments ); 940 this.listenTo( this.controller, 'select:activate', this.selectActivate ); 941 this.listenTo( this.controller, 'select:deactivate', this.selectDeactivate ); 942 }, 943 944 filterChange: function( model ) { 945 this.canShow = ( 'trash' === model.get( 'status' ) ); 946 }, 947 948 selectActivate: function() { 949 this.toggleDisabled(); 950 this.$el.toggleClass( 'hidden', ! this.canShow ); 951 }, 952 953 selectDeactivate: function() { 954 this.toggleDisabled(); 955 this.$el.addClass( 'hidden' ); 956 }, 957 958 render: function() { 959 Button.prototype.render.apply( this, arguments ); 960 this.selectActivate(); 961 return this; 377 Frame.prototype.initialize.apply( this, arguments ); 378 379 _.defaults( this.options, { 380 modal: true, 381 state: 'edit-attachment' 382 }); 383 384 this.controller = this.options.controller; 385 this.gridRouter = this.controller.gridRouter; 386 this.library = this.options.library; 387 388 if ( this.options.model ) { 389 this.model = this.options.model; 390 } 391 392 this.bindHandlers(); 393 this.createStates(); 394 this.createModal(); 395 396 this.title.mode( 'default' ); 397 this.toggleNav(); 398 }, 399 400 bindHandlers: function() { 401 // Bind default title creation. 402 this.on( 'title:create:default', this.createTitle, this ); 403 404 // Close the modal if the attachment is deleted. 405 this.listenTo( this.model, 'change:status destroy', this.close, this ); 406 407 this.on( 'content:create:edit-metadata', this.editMetadataMode, this ); 408 this.on( 'content:create:edit-image', this.editImageMode, this ); 409 this.on( 'content:render:edit-image', this.editImageModeRender, this ); 410 this.on( 'close', this.detach ); 411 }, 412 413 createModal: function() { 414 // Initialize modal container view. 415 if ( this.options.modal ) { 416 this.modal = new wp.media.view.Modal({ 417 controller: this, 418 title: this.options.title 419 }); 420 421 this.modal.on( 'open', _.bind( function () { 422 $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) ); 423 }, this ) ); 424 425 // Completely destroy the modal DOM element when closing it. 426 this.modal.on( 'close', _.bind( function() { 427 this.modal.remove(); 428 $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */ 429 // Restore the original focus item if possible 430 $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus(); 431 this.resetRoute(); 432 }, this ) ); 433 434 // Set this frame as the modal's content. 435 this.modal.content( this ); 436 this.modal.open(); 437 } 438 }, 439 440 /** 441 * Add the default states to the frame. 442 */ 443 createStates: function() { 444 this.states.add([ 445 new wp.media.controller.EditAttachmentMetadata( { model: this.model } ) 446 ]); 447 }, 448 449 /** 450 * Content region rendering callback for the `edit-metadata` mode. 451 * 452 * @param {Object} contentRegion Basic object with a `view` property, which 453 * should be set with the proper region view. 454 */ 455 editMetadataMode: function( contentRegion ) { 456 contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({ 457 controller: this, 458 model: this.model 459 }); 460 461 /** 462 * Attach a subview to display fields added via the 463 * `attachment_fields_to_edit` filter. 464 */ 465 contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({ 466 controller: this, 467 model: this.model 468 }) ); 469 470 // Update browser url when navigating media details 471 if ( this.model ) { 472 this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) ); 473 } 474 }, 475 476 /** 477 * Render the EditImage view into the frame's content region. 478 * 479 * @param {Object} contentRegion Basic object with a `view` property, which 480 * should be set with the proper region view. 481 */ 482 editImageMode: function( contentRegion ) { 483 var editImageController = new wp.media.controller.EditImage( { 484 model: this.model, 485 frame: this 486 } ); 487 // Noop some methods. 488 editImageController._toolbar = function() {}; 489 editImageController._router = function() {}; 490 editImageController._menu = function() {}; 491 492 contentRegion.view = new wp.media.view.EditImage.Details( { 493 model: this.model, 494 frame: this, 495 controller: editImageController 496 } ); 497 }, 498 499 editImageModeRender: function( view ) { 500 view.on( 'ready', view.loadEditor ); 501 }, 502 503 toggleNav: function() { 504 this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() ); 505 this.$('.right').toggleClass( 'disabled', ! this.hasNext() ); 506 }, 507 508 /** 509 * Rerender the view. 510 */ 511 rerender: function() { 512 // Only rerender the `content` region. 513 if ( this.content.mode() !== 'edit-metadata' ) { 514 this.content.mode( 'edit-metadata' ); 515 } else { 516 this.content.render(); 517 } 518 519 this.toggleNav(); 520 }, 521 522 /** 523 * Click handler to switch to the previous media item. 524 */ 525 previousMediaItem: function() { 526 if ( ! this.hasPrevious() ) { 527 this.$( '.left' ).blur(); 528 return; 529 } 530 this.model = this.library.at( this.getCurrentIndex() - 1 ); 531 this.rerender(); 532 this.$( '.left' ).focus(); 533 }, 534 535 /** 536 * Click handler to switch to the next media item. 537 */ 538 nextMediaItem: function() { 539 if ( ! this.hasNext() ) { 540 this.$( '.right' ).blur(); 541 return; 542 } 543 this.model = this.library.at( this.getCurrentIndex() + 1 ); 544 this.rerender(); 545 this.$( '.right' ).focus(); 546 }, 547 548 getCurrentIndex: function() { 549 return this.library.indexOf( this.model ); 550 }, 551 552 hasNext: function() { 553 return ( this.getCurrentIndex() + 1 ) < this.library.length; 554 }, 555 556 hasPrevious: function() { 557 return ( this.getCurrentIndex() - 1 ) > -1; 558 }, 559 /** 560 * Respond to the keyboard events: right arrow, left arrow, except when 561 * focus is in a textarea or input field. 562 */ 563 keyEvent: function( event ) { 564 if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) { 565 return; 566 } 567 568 // The right arrow key 569 if ( 39 === event.keyCode ) { 570 this.nextMediaItem(); 571 } 572 // The left arrow key 573 if ( 37 === event.keyCode ) { 574 this.previousMediaItem(); 575 } 576 }, 577 578 resetRoute: function() { 579 this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) ); 962 580 } 963 581 }); 964 582 965 module.exports = DeleteSelectedPermanently; 966 967 968 /***/ }) 969 /******/ ]); 583 module.exports = EditAttachments; 584 585 },{}],10:[function(require,module,exports){ 586 /** 587 * wp.media.view.MediaFrame.Manage 588 * 589 * A generic management frame workflow. 590 * 591 * Used in the media grid view. 592 * 593 * @class 594 * @augments wp.media.view.MediaFrame 595 * @augments wp.media.view.Frame 596 * @augments wp.media.View 597 * @augments wp.Backbone.View 598 * @augments Backbone.View 599 * @mixes wp.media.controller.StateMachine 600 */ 601 var MediaFrame = wp.media.view.MediaFrame, 602 Library = wp.media.controller.Library, 603 604 $ = Backbone.$, 605 Manage; 606 607 Manage = MediaFrame.extend({ 608 /** 609 * @global wp.Uploader 610 */ 611 initialize: function() { 612 _.defaults( this.options, { 613 title: '', 614 modal: false, 615 selection: [], 616 library: {}, // Options hash for the query to the media library. 617 multiple: 'add', 618 state: 'library', 619 uploader: true, 620 mode: [ 'grid', 'edit' ] 621 }); 622 623 this.$body = $( document.body ); 624 this.$window = $( window ); 625 this.$adminBar = $( '#wpadminbar' ); 626 this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) ); 627 $( document ).on( 'click', '.page-title-action', _.bind( this.addNewClickHandler, this ) ); 628 629 // Ensure core and media grid view UI is enabled. 630 this.$el.addClass('wp-core-ui'); 631 632 // Force the uploader off if the upload limit has been exceeded or 633 // if the browser isn't supported. 634 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 635 this.options.uploader = false; 636 } 637 638 // Initialize a window-wide uploader. 639 if ( this.options.uploader ) { 640 this.uploader = new wp.media.view.UploaderWindow({ 641 controller: this, 642 uploader: { 643 dropzone: document.body, 644 container: document.body 645 } 646 }).render(); 647 this.uploader.ready(); 648 $('body').append( this.uploader.el ); 649 650 this.options.uploader = false; 651 } 652 653 this.gridRouter = new wp.media.view.MediaFrame.Manage.Router(); 654 655 // Call 'initialize' directly on the parent class. 656 MediaFrame.prototype.initialize.apply( this, arguments ); 657 658 // Append the frame view directly the supplied container. 659 this.$el.appendTo( this.options.container ); 660 661 this.createStates(); 662 this.bindRegionModeHandlers(); 663 this.render(); 664 this.bindSearchHandler(); 665 }, 666 667 bindSearchHandler: function() { 668 var search = this.$( '#media-search-input' ), 669 currentSearch = this.options.container.data( 'search' ), 670 searchView = this.browserView.toolbar.get( 'search' ).$el, 671 listMode = this.$( '.view-list' ), 672 673 input = _.debounce( function (e) { 674 var val = $( e.currentTarget ).val(), 675 url = ''; 676 677 if ( val ) { 678 url += '?search=' + val; 679 } 680 this.gridRouter.navigate( this.gridRouter.baseUrl( url ) ); 681 }, 1000 ); 682 683 // Update the URL when entering search string (at most once per second) 684 search.on( 'input', _.bind( input, this ) ); 685 searchView.val( currentSearch ).trigger( 'input' ); 686 687 this.gridRouter.on( 'route:search', function () { 688 var href = window.location.href; 689 if ( href.indexOf( 'mode=' ) > -1 ) { 690 href = href.replace( /mode=[^&]+/g, 'mode=list' ); 691 } else { 692 href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list'; 693 } 694 href = href.replace( 'search=', 's=' ); 695 listMode.prop( 'href', href ); 696 } ); 697 }, 698 699 /** 700 * Create the default states for the frame. 701 */ 702 createStates: function() { 703 var options = this.options; 704 705 if ( this.options.states ) { 706 return; 707 } 708 709 // Add the default states. 710 this.states.add([ 711 new Library({ 712 library: wp.media.query( options.library ), 713 multiple: options.multiple, 714 title: options.title, 715 content: 'browse', 716 toolbar: 'select', 717 contentUserSetting: false, 718 filterable: 'all', 719 autoSelect: false 720 }) 721 ]); 722 }, 723 724 /** 725 * Bind region mode activation events to proper handlers. 726 */ 727 bindRegionModeHandlers: function() { 728 this.on( 'content:create:browse', this.browseContent, this ); 729 730 // Handle a frame-level event for editing an attachment. 731 this.on( 'edit:attachment', this.openEditAttachmentModal, this ); 732 733 this.on( 'select:activate', this.bindKeydown, this ); 734 this.on( 'select:deactivate', this.unbindKeydown, this ); 735 }, 736 737 handleKeydown: function( e ) { 738 if ( 27 === e.which ) { 739 e.preventDefault(); 740 this.deactivateMode( 'select' ).activateMode( 'edit' ); 741 } 742 }, 743 744 bindKeydown: function() { 745 this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) ); 746 }, 747 748 unbindKeydown: function() { 749 this.$body.off( 'keydown.select' ); 750 }, 751 752 fixPosition: function() { 753 var $browser, $toolbar; 754 if ( ! this.isModeActive( 'select' ) ) { 755 return; 756 } 757 758 $browser = this.$('.attachments-browser'); 759 $toolbar = $browser.find('.media-toolbar'); 760 761 // Offset doesn't appear to take top margin into account, hence +16 762 if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) { 763 $browser.addClass( 'fixed' ); 764 $toolbar.css('width', $browser.width() + 'px'); 765 } else { 766 $browser.removeClass( 'fixed' ); 767 $toolbar.css('width', ''); 768 } 769 }, 770 771 /** 772 * Click handler for the `Add New` button. 773 */ 774 addNewClickHandler: function( event ) { 775 event.preventDefault(); 776 this.trigger( 'toggle:upload:attachment' ); 777 }, 778 779 /** 780 * Open the Edit Attachment modal. 781 */ 782 openEditAttachmentModal: function( model ) { 783 // Create a new EditAttachment frame, passing along the library and the attachment model. 784 wp.media( { 785 frame: 'edit-attachments', 786 controller: this, 787 library: this.state().get('library'), 788 model: model 789 } ); 790 }, 791 792 /** 793 * Create an attachments browser view within the content region. 794 * 795 * @param {Object} contentRegion Basic object with a `view` property, which 796 * should be set with the proper region view. 797 * @this wp.media.controller.Region 798 */ 799 browseContent: function( contentRegion ) { 800 var state = this.state(); 801 802 // Browse our library of attachments. 803 this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({ 804 controller: this, 805 collection: state.get('library'), 806 selection: state.get('selection'), 807 model: state, 808 sortable: state.get('sortable'), 809 search: state.get('searchable'), 810 filters: state.get('filterable'), 811 date: state.get('date'), 812 display: state.get('displaySettings'), 813 dragInfo: state.get('dragInfo'), 814 sidebar: 'errors', 815 816 suggestedWidth: state.get('suggestedWidth'), 817 suggestedHeight: state.get('suggestedHeight'), 818 819 AttachmentView: state.get('AttachmentView'), 820 821 scrollElement: document 822 }); 823 this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) ); 824 825 this.errors = wp.Uploader.errors; 826 this.errors.on( 'add remove reset', this.sidebarVisibility, this ); 827 }, 828 829 sidebarVisibility: function() { 830 this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length ); 831 }, 832 833 bindDeferred: function() { 834 if ( ! this.browserView.dfd ) { 835 return; 836 } 837 this.browserView.dfd.done( _.bind( this.startHistory, this ) ); 838 }, 839 840 startHistory: function() { 841 // Verify pushState support and activate 842 if ( window.history && window.history.pushState ) { 843 Backbone.history.start( { 844 root: window._wpMediaGridSettings.adminUrl, 845 pushState: true 846 } ); 847 } 848 } 849 }); 850 851 module.exports = Manage; 852 853 },{}]},{},[2]); -
branches/4.4/src/wp-includes/js/media-views.js
r46498 r50018 1 /******/ (function(modules) { // webpackBootstrap 2 /******/ // The module cache 3 /******/ var installedModules = {}; 4 /******/ 5 /******/ // The require function 6 /******/ function __webpack_require__(moduleId) { 7 /******/ 8 /******/ // Check if module is in cache 9 /******/ if(installedModules[moduleId]) { 10 /******/ return installedModules[moduleId].exports; 11 /******/ } 12 /******/ // Create a new module (and put it into the cache) 13 /******/ var module = installedModules[moduleId] = { 14 /******/ i: moduleId, 15 /******/ l: false, 16 /******/ exports: {} 17 /******/ }; 18 /******/ 19 /******/ // Execute the module function 20 /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 /******/ 22 /******/ // Flag the module as loaded 23 /******/ module.l = true; 24 /******/ 25 /******/ // Return the exports of the module 26 /******/ return module.exports; 27 /******/ } 28 /******/ 29 /******/ 30 /******/ // expose the modules object (__webpack_modules__) 31 /******/ __webpack_require__.m = modules; 32 /******/ 33 /******/ // expose the module cache 34 /******/ __webpack_require__.c = installedModules; 35 /******/ 36 /******/ // define getter function for harmony exports 37 /******/ __webpack_require__.d = function(exports, name, getter) { 38 /******/ if(!__webpack_require__.o(exports, name)) { 39 /******/ Object.defineProperty(exports, name, { 40 /******/ configurable: false, 41 /******/ enumerable: true, 42 /******/ get: getter 43 /******/ }); 44 /******/ } 45 /******/ }; 46 /******/ 47 /******/ // getDefaultExport function for compatibility with non-harmony modules 48 /******/ __webpack_require__.n = function(module) { 49 /******/ var getter = module && module.__esModule ? 50 /******/ function getDefault() { return module['default']; } : 51 /******/ function getModuleExports() { return module; }; 52 /******/ __webpack_require__.d(getter, 'a', getter); 53 /******/ return getter; 54 /******/ }; 55 /******/ 56 /******/ // Object.prototype.hasOwnProperty.call 57 /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 58 /******/ 59 /******/ // __webpack_public_path__ 60 /******/ __webpack_require__.p = ""; 61 /******/ 62 /******/ // Load entry module and return exports 63 /******/ return __webpack_require__(__webpack_require__.s = 26); 64 /******/ }) 65 /************************************************************************/ 66 /******/ (Array(26).concat([ 67 /* 26 */ 68 /***/ (function(module, exports, __webpack_require__) { 69 70 var media = wp.media, 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 /** 3 * wp.media.controller.CollectionAdd 4 * 5 * A state for adding attachments to a collection (e.g. video playlist). 6 * 7 * @class 8 * @augments wp.media.controller.Library 9 * @augments wp.media.controller.State 10 * @augments Backbone.Model 11 * 12 * @param {object} [attributes] The attributes hash passed to the state. 13 * @param {string} [attributes.id=library] Unique identifier. 14 * @param {string} attributes.title Title for the state. Displays in the frame's title region. 15 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 16 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 17 * If one is not supplied, a collection of attachments of the specified type will be created. 18 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 19 * Accepts 'all', 'uploaded', or 'unattached'. 20 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 21 * @param {string} [attributes.content=upload] Initial mode for the content region. 22 * Overridden by persistent user setting if 'contentUserSetting' is true. 23 * @param {string} [attributes.router=browse] Initial mode for the router region. 24 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 25 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 26 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 27 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 28 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 29 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 30 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 31 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 32 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 33 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 34 */ 35 var Selection = wp.media.model.Selection, 36 Library = wp.media.controller.Library, 37 CollectionAdd; 38 39 CollectionAdd = Library.extend({ 40 defaults: _.defaults( { 41 // Selection defaults. @see media.model.Selection 42 multiple: 'add', 43 // Attachments browser defaults. @see media.view.AttachmentsBrowser 44 filterable: 'uploaded', 45 46 priority: 100, 47 syncSelection: false 48 }, Library.prototype.defaults ), 49 50 /** 51 * @since 3.9.0 52 */ 53 initialize: function() { 54 var collectionType = this.get('collectionType'); 55 56 if ( 'video' === this.get( 'type' ) ) { 57 collectionType = 'video-' + collectionType; 58 } 59 60 this.set( 'id', collectionType + '-library' ); 61 this.set( 'toolbar', collectionType + '-add' ); 62 this.set( 'menu', collectionType ); 63 64 // If we haven't been provided a `library`, create a `Selection`. 65 if ( ! this.get('library') ) { 66 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 67 } 68 Library.prototype.initialize.apply( this, arguments ); 69 }, 70 71 /** 72 * @since 3.9.0 73 */ 74 activate: function() { 75 var library = this.get('library'), 76 editLibrary = this.get('editLibrary'), 77 edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library'); 78 79 if ( editLibrary && editLibrary !== edit ) { 80 library.unobserve( editLibrary ); 81 } 82 83 // Accepts attachments that exist in the original library and 84 // that do not exist in gallery's library. 85 library.validator = function( attachment ) { 86 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 87 }; 88 89 // Reset the library to ensure that all attachments are re-added 90 // to the collection. Do so silently, as calling `observe` will 91 // trigger the `reset` event. 92 library.reset( library.mirroring.models, { silent: true }); 93 library.observe( edit ); 94 this.set('editLibrary', edit); 95 96 Library.prototype.activate.apply( this, arguments ); 97 } 98 }); 99 100 module.exports = CollectionAdd; 101 102 },{}],2:[function(require,module,exports){ 103 /** 104 * wp.media.controller.CollectionEdit 105 * 106 * A state for editing a collection, which is used by audio and video playlists, 107 * and can be used for other collections. 108 * 109 * @class 110 * @augments wp.media.controller.Library 111 * @augments wp.media.controller.State 112 * @augments Backbone.Model 113 * 114 * @param {object} [attributes] The attributes hash passed to the state. 115 * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. 116 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. 117 * If one is not supplied, an empty media.model.Selection collection is created. 118 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 119 * @param {string} [attributes.content=browse] Initial mode for the content region. 120 * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. 121 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 122 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 123 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 124 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. 125 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 126 * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. 127 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 128 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 129 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 130 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 131 * Defaults to false for this state, because the library passed in *is* the selection. 132 * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). 133 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 134 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 135 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 136 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 137 */ 138 var Library = wp.media.controller.Library, 139 l10n = wp.media.view.l10n, 71 140 $ = jQuery, 72 l10n; 73 74 media.isTouchDevice = ( 'ontouchend' in document ); 75 76 // Link any localized strings. 77 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 78 79 // Link any settings. 80 media.view.settings = l10n.settings || {}; 81 delete l10n.settings; 82 83 // Copy the `post` setting over to the model settings. 84 media.model.settings.post = media.view.settings.post; 85 86 // Check if the browser supports CSS 3.0 transitions 87 $.support.transition = (function(){ 88 var style = document.documentElement.style, 89 transitions = { 90 WebkitTransition: 'webkitTransitionEnd', 91 MozTransition: 'transitionend', 92 OTransition: 'oTransitionEnd otransitionend', 93 transition: 'transitionend' 94 }, transition; 95 96 transition = _.find( _.keys( transitions ), function( transition ) { 97 return ! _.isUndefined( style[ transition ] ); 98 }); 99 100 return transition && { 101 end: transitions[ transition ] 102 }; 103 }()); 104 141 CollectionEdit; 142 143 CollectionEdit = Library.extend({ 144 defaults: { 145 multiple: false, 146 sortable: true, 147 date: false, 148 searchable: false, 149 content: 'browse', 150 describe: true, 151 dragInfo: true, 152 idealColumnWidth: 170, 153 editing: false, 154 priority: 60, 155 SettingsView: false, 156 syncSelection: false 157 }, 158 159 /** 160 * @since 3.9.0 161 */ 162 initialize: function() { 163 var collectionType = this.get('collectionType'); 164 165 if ( 'video' === this.get( 'type' ) ) { 166 collectionType = 'video-' + collectionType; 167 } 168 169 this.set( 'id', collectionType + '-edit' ); 170 this.set( 'toolbar', collectionType + '-edit' ); 171 172 // If we haven't been provided a `library`, create a `Selection`. 173 if ( ! this.get('library') ) { 174 this.set( 'library', new wp.media.model.Selection() ); 175 } 176 // The single `Attachment` view to be used in the `Attachments` view. 177 if ( ! this.get('AttachmentView') ) { 178 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 179 } 180 Library.prototype.initialize.apply( this, arguments ); 181 }, 182 183 /** 184 * @since 3.9.0 185 */ 186 activate: function() { 187 var library = this.get('library'); 188 189 // Limit the library to images only. 190 library.props.set( 'type', this.get( 'type' ) ); 191 192 // Watch for uploaded attachments. 193 this.get('library').observe( wp.Uploader.queue ); 194 195 this.frame.on( 'content:render:browse', this.renderSettings, this ); 196 197 Library.prototype.activate.apply( this, arguments ); 198 }, 199 200 /** 201 * @since 3.9.0 202 */ 203 deactivate: function() { 204 // Stop watching for uploaded attachments. 205 this.get('library').unobserve( wp.Uploader.queue ); 206 207 this.frame.off( 'content:render:browse', this.renderSettings, this ); 208 209 Library.prototype.deactivate.apply( this, arguments ); 210 }, 211 212 /** 213 * Render the collection embed settings view in the browser sidebar. 214 * 215 * @todo This is against the pattern elsewhere in media. Typically the frame 216 * is responsible for adding region mode callbacks. Explain. 217 * 218 * @since 3.9.0 219 * 220 * @param {wp.media.view.attachmentsBrowser} The attachments browser view. 221 */ 222 renderSettings: function( attachmentsBrowserView ) { 223 var library = this.get('library'), 224 collectionType = this.get('collectionType'), 225 dragInfoText = this.get('dragInfoText'), 226 SettingsView = this.get('SettingsView'), 227 obj = {}; 228 229 if ( ! library || ! attachmentsBrowserView ) { 230 return; 231 } 232 233 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 234 235 obj[ collectionType ] = new SettingsView({ 236 controller: this, 237 model: library[ collectionType ], 238 priority: 40 239 }); 240 241 attachmentsBrowserView.sidebar.set( obj ); 242 243 if ( dragInfoText ) { 244 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 245 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 246 priority: -40 247 }) ); 248 } 249 250 // Add the 'Reverse order' button to the toolbar. 251 attachmentsBrowserView.toolbar.set( 'reverse', { 252 text: l10n.reverseOrder, 253 priority: 80, 254 255 click: function() { 256 library.reset( library.toArray().reverse() ); 257 } 258 }); 259 } 260 }); 261 262 module.exports = CollectionEdit; 263 264 },{}],3:[function(require,module,exports){ 105 265 /** 106 * A shared event bus used to provide events into 107 * the media workflows that 3rd-party devs can use to hook 108 * in. 266 * wp.media.controller.Cropper 267 * 268 * A state for cropping an image. 269 * 270 * @class 271 * @augments wp.media.controller.State 272 * @augments Backbone.Model 109 273 */ 110 media.events = _.extend( {}, Backbone.Events ); 111 274 var l10n = wp.media.view.l10n, 275 Cropper; 276 277 Cropper = wp.media.controller.State.extend({ 278 defaults: { 279 id: 'cropper', 280 title: l10n.cropImage, 281 // Region mode defaults. 282 toolbar: 'crop', 283 content: 'crop', 284 router: false, 285 286 canSkipCrop: false 287 }, 288 289 activate: function() { 290 this.frame.on( 'content:create:crop', this.createCropContent, this ); 291 this.frame.on( 'close', this.removeCropper, this ); 292 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 293 }, 294 295 deactivate: function() { 296 this.frame.toolbar.mode('browse'); 297 }, 298 299 createCropContent: function() { 300 this.cropperView = new wp.media.view.Cropper({ 301 controller: this, 302 attachment: this.get('selection').first() 303 }); 304 this.cropperView.on('image-loaded', this.createCropToolbar, this); 305 this.frame.content.set(this.cropperView); 306 307 }, 308 removeCropper: function() { 309 this.imgSelect.cancelSelection(); 310 this.imgSelect.setOptions({remove: true}); 311 this.imgSelect.update(); 312 this.cropperView.remove(); 313 }, 314 createCropToolbar: function() { 315 var canSkipCrop, toolbarOptions; 316 317 canSkipCrop = this.get('canSkipCrop') || false; 318 319 toolbarOptions = { 320 controller: this.frame, 321 items: { 322 insert: { 323 style: 'primary', 324 text: l10n.cropImage, 325 priority: 80, 326 requires: { library: false, selection: false }, 327 328 click: function() { 329 var controller = this.controller, 330 selection; 331 332 selection = controller.state().get('selection').first(); 333 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 334 335 this.$el.text(l10n.cropping); 336 this.$el.attr('disabled', true); 337 338 controller.state().doCrop( selection ).done( function( croppedImage ) { 339 controller.trigger('cropped', croppedImage ); 340 controller.close(); 341 }).fail( function() { 342 controller.trigger('content:error:crop'); 343 }); 344 } 345 } 346 } 347 }; 348 349 if ( canSkipCrop ) { 350 _.extend( toolbarOptions.items, { 351 skip: { 352 style: 'secondary', 353 text: l10n.skipCropping, 354 priority: 70, 355 requires: { library: false, selection: false }, 356 click: function() { 357 var selection = this.controller.state().get('selection').first(); 358 this.controller.state().cropperView.remove(); 359 this.controller.trigger('skippedcrop', selection); 360 this.controller.close(); 361 } 362 } 363 }); 364 } 365 366 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 367 }, 368 369 doCrop: function( attachment ) { 370 return wp.ajax.post( 'custom-header-crop', { 371 nonce: attachment.get('nonces').edit, 372 id: attachment.get('id'), 373 cropDetails: attachment.get('cropDetails') 374 } ); 375 } 376 }); 377 378 module.exports = Cropper; 379 380 },{}],4:[function(require,module,exports){ 112 381 /** 113 * Makes it easier to bind events using transitions. 114 * 115 * @param {string} selector 116 * @param {Number} sensitivity 117 * @returns {Promise} 382 * wp.media.controller.CustomizeImageCropper 383 * 384 * A state for cropping an image. 385 * 386 * @class 387 * @augments wp.media.controller.Cropper 388 * @augments wp.media.controller.State 389 * @augments Backbone.Model 118 390 */ 119 media.transition = function( selector, sensitivity ) { 120 var deferred = $.Deferred(); 121 122 sensitivity = sensitivity || 2000; 123 124 if ( $.support.transition ) { 125 if ( ! (selector instanceof $) ) { 126 selector = $( selector ); 127 } 128 129 // Resolve the deferred when the first element finishes animating. 130 selector.first().one( $.support.transition.end, deferred.resolve ); 131 132 // Just in case the event doesn't trigger, fire a callback. 133 _.delay( deferred.resolve, sensitivity ); 134 135 // Otherwise, execute on the spot. 136 } else { 137 deferred.resolve(); 391 var Controller = wp.media.controller, 392 CustomizeImageCropper; 393 394 CustomizeImageCropper = Controller.Cropper.extend({ 395 doCrop: function( attachment ) { 396 var cropDetails = attachment.get( 'cropDetails' ), 397 control = this.get( 'control' ); 398 399 cropDetails.dst_width = control.params.width; 400 cropDetails.dst_height = control.params.height; 401 402 return wp.ajax.post( 'crop-image', { 403 wp_customize: 'on', 404 nonce: attachment.get( 'nonces' ).edit, 405 id: attachment.get( 'id' ), 406 context: control.id, 407 cropDetails: cropDetails 408 } ); 138 409 } 139 140 return deferred.promise(); 141 }; 142 143 media.controller.Region = __webpack_require__( 27 ); 144 media.controller.StateMachine = __webpack_require__( 28 ); 145 media.controller.State = __webpack_require__( 29 ); 146 147 media.selectionSync = __webpack_require__( 30 ); 148 media.controller.Library = __webpack_require__( 31 ); 149 media.controller.ImageDetails = __webpack_require__( 32 ); 150 media.controller.GalleryEdit = __webpack_require__( 33 ); 151 media.controller.GalleryAdd = __webpack_require__( 34 ); 152 media.controller.CollectionEdit = __webpack_require__( 35 ); 153 media.controller.CollectionAdd = __webpack_require__( 36 ); 154 media.controller.FeaturedImage = __webpack_require__( 37 ); 155 media.controller.ReplaceImage = __webpack_require__( 38 ); 156 media.controller.EditImage = __webpack_require__( 39 ); 157 media.controller.MediaLibrary = __webpack_require__( 40 ); 158 media.controller.Embed = __webpack_require__( 41 ); 159 media.controller.Cropper = __webpack_require__( 42 ); 160 media.controller.CustomizeImageCropper = __webpack_require__( 43 ); 161 media.controller.SiteIconCropper = __webpack_require__( 44 ); 162 163 media.View = __webpack_require__( 45 ); 164 media.view.Frame = __webpack_require__( 46 ); 165 media.view.MediaFrame = __webpack_require__( 47 ); 166 media.view.MediaFrame.Select = __webpack_require__( 48 ); 167 media.view.MediaFrame.Post = __webpack_require__( 49 ); 168 media.view.MediaFrame.ImageDetails = __webpack_require__( 50 ); 169 media.view.Modal = __webpack_require__( 51 ); 170 media.view.FocusManager = __webpack_require__( 52 ); 171 media.view.UploaderWindow = __webpack_require__( 53 ); 172 media.view.EditorUploader = __webpack_require__( 54 ); 173 media.view.UploaderInline = __webpack_require__( 55 ); 174 media.view.UploaderStatus = __webpack_require__( 56 ); 175 media.view.UploaderStatusError = __webpack_require__( 57 ); 176 media.view.Toolbar = __webpack_require__( 58 ); 177 media.view.Toolbar.Select = __webpack_require__( 59 ); 178 media.view.Toolbar.Embed = __webpack_require__( 60 ); 179 media.view.Button = __webpack_require__( 61 ); 180 media.view.ButtonGroup = __webpack_require__( 62 ); 181 media.view.PriorityList = __webpack_require__( 63 ); 182 media.view.MenuItem = __webpack_require__( 64 ); 183 media.view.Menu = __webpack_require__( 65 ); 184 media.view.RouterItem = __webpack_require__( 66 ); 185 media.view.Router = __webpack_require__( 67 ); 186 media.view.Sidebar = __webpack_require__( 68 ); 187 media.view.Attachment = __webpack_require__( 69 ); 188 media.view.Attachment.Library = __webpack_require__( 70 ); 189 media.view.Attachment.EditLibrary = __webpack_require__( 71 ); 190 media.view.Attachments = __webpack_require__( 72 ); 191 media.view.Search = __webpack_require__( 73 ); 192 media.view.AttachmentFilters = __webpack_require__( 74 ); 193 media.view.DateFilter = __webpack_require__( 75 ); 194 media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 ); 195 media.view.AttachmentFilters.All = __webpack_require__( 77 ); 196 media.view.AttachmentsBrowser = __webpack_require__( 78 ); 197 media.view.Selection = __webpack_require__( 79 ); 198 media.view.Attachment.Selection = __webpack_require__( 80 ); 199 media.view.Attachments.Selection = __webpack_require__( 81 ); 200 media.view.Attachment.EditSelection = __webpack_require__( 82 ); 201 media.view.Settings = __webpack_require__( 83 ); 202 media.view.Settings.AttachmentDisplay = __webpack_require__( 84 ); 203 media.view.Settings.Gallery = __webpack_require__( 85 ); 204 media.view.Settings.Playlist = __webpack_require__( 86 ); 205 media.view.Attachment.Details = __webpack_require__( 87 ); 206 media.view.AttachmentCompat = __webpack_require__( 88 ); 207 media.view.Iframe = __webpack_require__( 89 ); 208 media.view.Embed = __webpack_require__( 90 ); 209 media.view.Label = __webpack_require__( 91 ); 210 media.view.EmbedUrl = __webpack_require__( 92 ); 211 media.view.EmbedLink = __webpack_require__( 93 ); 212 media.view.EmbedImage = __webpack_require__( 94 ); 213 media.view.ImageDetails = __webpack_require__( 95 ); 214 media.view.Cropper = __webpack_require__( 96 ); 215 media.view.SiteIconCropper = __webpack_require__( 97 ); 216 media.view.SiteIconPreview = __webpack_require__( 98 ); 217 media.view.EditImage = __webpack_require__( 99 ); 218 media.view.Spinner = __webpack_require__( 100 ); 219 220 221 /***/ }), 222 /* 27 */ 223 /***/ (function(module, exports) { 224 410 }); 411 412 module.exports = CustomizeImageCropper; 413 414 },{}],5:[function(require,module,exports){ 225 415 /** 226 * wp.media.controller.Region 227 * 228 * A region is a persistent application layout area. 229 * 230 * A region assumes one mode at any time, and can be switched to another. 231 * 232 * When mode changes, events are triggered on the region's parent view. 233 * The parent view will listen to specific events and fill the region with an 234 * appropriate view depending on mode. For example, a frame listens for the 235 * 'browse' mode t be activated on the 'content' view and then fills the region 236 * with an AttachmentsBrowser view. 416 * wp.media.controller.EditImage 417 * 418 * A state for editing (cropping, etc.) an image. 237 419 * 238 420 * @class 239 * 240 * @param {object} options Options hash for the region. 241 * @param {string} options.id Unique identifier for the region. 242 * @param {Backbone.View} options.view A parent view the region exists within. 243 * @param {string} options.selector jQuery selector for the region within the parent view. 421 * @augments wp.media.controller.State 422 * @augments Backbone.Model 423 * 424 * @param {object} attributes The attributes hash passed to the state. 425 * @param {wp.media.model.Attachment} attributes.model The attachment. 426 * @param {string} [attributes.id=edit-image] Unique identifier. 427 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 428 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 429 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 430 * @param {string} [attributes.menu=false] Initial mode for the menu region. 431 * @param {string} [attributes.url] Unused. @todo Consider removal. 244 432 */ 245 var Region = function( options ) { 246 _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); 247 }; 248 249 // Use Backbone's self-propagating `extend` inheritance method. 250 Region.extend = Backbone.Model.extend; 251 252 _.extend( Region.prototype, { 253 /** 254 * Activate a mode. 433 var l10n = wp.media.view.l10n, 434 EditImage; 435 436 EditImage = wp.media.controller.State.extend({ 437 defaults: { 438 id: 'edit-image', 439 title: l10n.editImage, 440 menu: false, 441 toolbar: 'edit-image', 442 content: 'edit-image', 443 url: '' 444 }, 445 446 /** 447 * @since 3.9.0 448 */ 449 activate: function() { 450 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar ); 451 }, 452 453 /** 454 * @since 3.9.0 455 */ 456 deactivate: function() { 457 this.stopListening( this.frame ); 458 }, 459 460 /** 461 * @since 3.9.0 462 */ 463 toolbar: function() { 464 var frame = this.frame, 465 lastState = frame.lastState(), 466 previous = lastState && lastState.id; 467 468 frame.toolbar.set( new wp.media.view.Toolbar({ 469 controller: frame, 470 items: { 471 back: { 472 style: 'primary', 473 text: l10n.back, 474 priority: 20, 475 click: function() { 476 if ( previous ) { 477 frame.setState( previous ); 478 } else { 479 frame.close(); 480 } 481 } 482 } 483 } 484 }) ); 485 } 486 }); 487 488 module.exports = EditImage; 489 490 },{}],6:[function(require,module,exports){ 491 /** 492 * wp.media.controller.Embed 493 * 494 * A state for embedding media from a URL. 495 * 496 * @class 497 * @augments wp.media.controller.State 498 * @augments Backbone.Model 499 * 500 * @param {object} attributes The attributes hash passed to the state. 501 * @param {string} [attributes.id=embed] Unique identifier. 502 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. 503 * @param {string} [attributes.content=embed] Initial mode for the content region. 504 * @param {string} [attributes.menu=default] Initial mode for the menu region. 505 * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. 506 * @param {string} [attributes.menu=false] Initial mode for the menu region. 507 * @param {int} [attributes.priority=120] The priority for the state link in the media menu. 508 * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. 509 * @param {string} [attributes.url] The embed URL. 510 * @param {object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. 511 */ 512 var l10n = wp.media.view.l10n, 513 $ = Backbone.$, 514 Embed; 515 516 Embed = wp.media.controller.State.extend({ 517 defaults: { 518 id: 'embed', 519 title: l10n.insertFromUrlTitle, 520 content: 'embed', 521 menu: 'default', 522 toolbar: 'main-embed', 523 priority: 120, 524 type: 'link', 525 url: '', 526 metadata: {} 527 }, 528 529 // The amount of time used when debouncing the scan. 530 sensitivity: 400, 531 532 initialize: function(options) { 533 this.metadata = options.metadata; 534 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity ); 535 this.props = new Backbone.Model( this.metadata || { url: '' }); 536 this.props.on( 'change:url', this.debouncedScan, this ); 537 this.props.on( 'change:url', this.refresh, this ); 538 this.on( 'scan', this.scanImage, this ); 539 }, 540 541 /** 542 * Trigger a scan of the embedded URL's content for metadata required to embed. 255 543 * 544 * @fires wp.media.controller.Embed#scan 545 */ 546 scan: function() { 547 var scanners, 548 embed = this, 549 attributes = { 550 type: 'link', 551 scanners: [] 552 }; 553 554 // Scan is triggered with the list of `attributes` to set on the 555 // state, useful for the 'type' attribute and 'scanners' attribute, 556 // an array of promise objects for asynchronous scan operations. 557 if ( this.props.get('url') ) { 558 this.trigger( 'scan', attributes ); 559 } 560 561 if ( attributes.scanners.length ) { 562 scanners = attributes.scanners = $.when.apply( $, attributes.scanners ); 563 scanners.always( function() { 564 if ( embed.get('scanners') === scanners ) { 565 embed.set( 'loading', false ); 566 } 567 }); 568 } else { 569 attributes.scanners = null; 570 } 571 572 attributes.loading = !! attributes.scanners; 573 this.set( attributes ); 574 }, 575 /** 576 * Try scanning the embed as an image to discover its dimensions. 577 * 578 * @param {Object} attributes 579 */ 580 scanImage: function( attributes ) { 581 var frame = this.frame, 582 state = this, 583 url = this.props.get('url'), 584 image = new Image(), 585 deferred = $.Deferred(); 586 587 attributes.scanners.push( deferred.promise() ); 588 589 // Try to load the image and find its width/height. 590 image.onload = function() { 591 deferred.resolve(); 592 593 if ( state !== frame.state() || url !== state.props.get('url') ) { 594 return; 595 } 596 597 state.set({ 598 type: 'image' 599 }); 600 601 state.props.set({ 602 width: image.width, 603 height: image.height 604 }); 605 }; 606 607 image.onerror = deferred.reject; 608 image.src = url; 609 }, 610 611 refresh: function() { 612 this.frame.toolbar.get().refresh(); 613 }, 614 615 reset: function() { 616 this.props.clear().set({ url: '' }); 617 618 if ( this.active ) { 619 this.refresh(); 620 } 621 } 622 }); 623 624 module.exports = Embed; 625 626 },{}],7:[function(require,module,exports){ 627 /** 628 * wp.media.controller.FeaturedImage 629 * 630 * A state for selecting a featured image for a post. 631 * 632 * @class 633 * @augments wp.media.controller.Library 634 * @augments wp.media.controller.State 635 * @augments Backbone.Model 636 * 637 * @param {object} [attributes] The attributes hash passed to the state. 638 * @param {string} [attributes.id=featured-image] Unique identifier. 639 * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. 640 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 641 * If one is not supplied, a collection of all images will be created. 642 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 643 * @param {string} [attributes.content=upload] Initial mode for the content region. 644 * Overridden by persistent user setting if 'contentUserSetting' is true. 645 * @param {string} [attributes.menu=default] Initial mode for the menu region. 646 * @param {string} [attributes.router=browse] Initial mode for the router region. 647 * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. 648 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 649 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 650 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 651 * Accepts 'all', 'uploaded', or 'unattached'. 652 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 653 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 654 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 655 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 656 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 657 */ 658 var Attachment = wp.media.model.Attachment, 659 Library = wp.media.controller.Library, 660 l10n = wp.media.view.l10n, 661 FeaturedImage; 662 663 FeaturedImage = Library.extend({ 664 defaults: _.defaults({ 665 id: 'featured-image', 666 title: l10n.setFeaturedImageTitle, 667 multiple: false, 668 filterable: 'uploaded', 669 toolbar: 'featured-image', 670 priority: 60, 671 syncSelection: true 672 }, Library.prototype.defaults ), 673 674 /** 675 * @since 3.5.0 676 */ 677 initialize: function() { 678 var library, comparator; 679 680 // If we haven't been provided a `library`, create a `Selection`. 681 if ( ! this.get('library') ) { 682 this.set( 'library', wp.media.query({ type: 'image' }) ); 683 } 684 685 Library.prototype.initialize.apply( this, arguments ); 686 687 library = this.get('library'); 688 comparator = library.comparator; 689 690 // Overload the library's comparator to push items that are not in 691 // the mirrored query to the front of the aggregate collection. 692 library.comparator = function( a, b ) { 693 var aInQuery = !! this.mirroring.get( a.cid ), 694 bInQuery = !! this.mirroring.get( b.cid ); 695 696 if ( ! aInQuery && bInQuery ) { 697 return -1; 698 } else if ( aInQuery && ! bInQuery ) { 699 return 1; 700 } else { 701 return comparator.apply( this, arguments ); 702 } 703 }; 704 705 // Add all items in the selection to the library, so any featured 706 // images that are not initially loaded still appear. 707 library.observe( this.get('selection') ); 708 }, 709 710 /** 711 * @since 3.5.0 712 */ 713 activate: function() { 714 this.updateSelection(); 715 this.frame.on( 'open', this.updateSelection, this ); 716 717 Library.prototype.activate.apply( this, arguments ); 718 }, 719 720 /** 721 * @since 3.5.0 722 */ 723 deactivate: function() { 724 this.frame.off( 'open', this.updateSelection, this ); 725 726 Library.prototype.deactivate.apply( this, arguments ); 727 }, 728 729 /** 730 * @since 3.5.0 731 */ 732 updateSelection: function() { 733 var selection = this.get('selection'), 734 id = wp.media.view.settings.post.featuredImageId, 735 attachment; 736 737 if ( '' !== id && -1 !== id ) { 738 attachment = Attachment.get( id ); 739 attachment.fetch(); 740 } 741 742 selection.reset( attachment ? [ attachment ] : [] ); 743 } 744 }); 745 746 module.exports = FeaturedImage; 747 748 },{}],8:[function(require,module,exports){ 749 /** 750 * wp.media.controller.GalleryAdd 751 * 752 * A state for selecting more images to add to a gallery. 753 * 754 * @class 755 * @augments wp.media.controller.Library 756 * @augments wp.media.controller.State 757 * @augments Backbone.Model 758 * 759 * @param {object} [attributes] The attributes hash passed to the state. 760 * @param {string} [attributes.id=gallery-library] Unique identifier. 761 * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. 762 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 763 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 764 * If one is not supplied, a collection of all images will be created. 765 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 766 * Accepts 'all', 'uploaded', or 'unattached'. 767 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 768 * @param {string} [attributes.content=upload] Initial mode for the content region. 769 * Overridden by persistent user setting if 'contentUserSetting' is true. 770 * @param {string} [attributes.router=browse] Initial mode for the router region. 771 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 772 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 773 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 774 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 775 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 776 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 777 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 778 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 779 */ 780 var Selection = wp.media.model.Selection, 781 Library = wp.media.controller.Library, 782 l10n = wp.media.view.l10n, 783 GalleryAdd; 784 785 GalleryAdd = Library.extend({ 786 defaults: _.defaults({ 787 id: 'gallery-library', 788 title: l10n.addToGalleryTitle, 789 multiple: 'add', 790 filterable: 'uploaded', 791 menu: 'gallery', 792 toolbar: 'gallery-add', 793 priority: 100, 794 syncSelection: false 795 }, Library.prototype.defaults ), 796 797 /** 798 * @since 3.5.0 799 */ 800 initialize: function() { 801 // If a library wasn't supplied, create a library of images. 802 if ( ! this.get('library') ) { 803 this.set( 'library', wp.media.query({ type: 'image' }) ); 804 } 805 806 Library.prototype.initialize.apply( this, arguments ); 807 }, 808 809 /** 810 * @since 3.5.0 811 */ 812 activate: function() { 813 var library = this.get('library'), 814 edit = this.frame.state('gallery-edit').get('library'); 815 816 if ( this.editLibrary && this.editLibrary !== edit ) { 817 library.unobserve( this.editLibrary ); 818 } 819 820 // Accepts attachments that exist in the original library and 821 // that do not exist in gallery's library. 822 library.validator = function( attachment ) { 823 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 824 }; 825 826 // Reset the library to ensure that all attachments are re-added 827 // to the collection. Do so silently, as calling `observe` will 828 // trigger the `reset` event. 829 library.reset( library.mirroring.models, { silent: true }); 830 library.observe( edit ); 831 this.editLibrary = edit; 832 833 Library.prototype.activate.apply( this, arguments ); 834 } 835 }); 836 837 module.exports = GalleryAdd; 838 839 },{}],9:[function(require,module,exports){ 840 /** 841 * wp.media.controller.GalleryEdit 842 * 843 * A state for editing a gallery's images and settings. 844 * 845 * @class 846 * @augments wp.media.controller.Library 847 * @augments wp.media.controller.State 848 * @augments Backbone.Model 849 * 850 * @param {object} [attributes] The attributes hash passed to the state. 851 * @param {string} [attributes.id=gallery-edit] Unique identifier. 852 * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region. 853 * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery. 854 * If one is not supplied, an empty media.model.Selection collection is created. 855 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 856 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 857 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 858 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 859 * @param {string|false} [attributes.content=browse] Initial mode for the content region. 860 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 861 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 862 * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface. 863 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 864 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 865 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 866 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 867 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 868 * Defaults to false for this state, because the library passed in *is* the selection. 869 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 870 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 871 */ 872 var Library = wp.media.controller.Library, 873 l10n = wp.media.view.l10n, 874 GalleryEdit; 875 876 GalleryEdit = Library.extend({ 877 defaults: { 878 id: 'gallery-edit', 879 title: l10n.editGalleryTitle, 880 multiple: false, 881 searchable: false, 882 sortable: true, 883 date: false, 884 display: false, 885 content: 'browse', 886 toolbar: 'gallery-edit', 887 describe: true, 888 displaySettings: true, 889 dragInfo: true, 890 idealColumnWidth: 170, 891 editing: false, 892 priority: 60, 893 syncSelection: false 894 }, 895 896 /** 897 * @since 3.5.0 898 */ 899 initialize: function() { 900 // If we haven't been provided a `library`, create a `Selection`. 901 if ( ! this.get('library') ) { 902 this.set( 'library', new wp.media.model.Selection() ); 903 } 904 905 // The single `Attachment` view to be used in the `Attachments` view. 906 if ( ! this.get('AttachmentView') ) { 907 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 908 } 909 910 Library.prototype.initialize.apply( this, arguments ); 911 }, 912 913 /** 914 * @since 3.5.0 915 */ 916 activate: function() { 917 var library = this.get('library'); 918 919 // Limit the library to images only. 920 library.props.set( 'type', 'image' ); 921 922 // Watch for uploaded attachments. 923 this.get('library').observe( wp.Uploader.queue ); 924 925 this.frame.on( 'content:render:browse', this.gallerySettings, this ); 926 927 Library.prototype.activate.apply( this, arguments ); 928 }, 929 930 /** 931 * @since 3.5.0 932 */ 933 deactivate: function() { 934 // Stop watching for uploaded attachments. 935 this.get('library').unobserve( wp.Uploader.queue ); 936 937 this.frame.off( 'content:render:browse', this.gallerySettings, this ); 938 939 Library.prototype.deactivate.apply( this, arguments ); 940 }, 941 942 /** 256 943 * @since 3.5.0 257 944 * 258 * @param {string} mode 259 * 260 * @fires this.view#{this.id}:activate:{this._mode} 261 * @fires this.view#{this.id}:activate 262 * @fires this.view#{this.id}:deactivate:{this._mode} 263 * @fires this.view#{this.id}:deactivate 264 * 265 * @returns {wp.media.controller.Region} Returns itself to allow chaining. 266 */ 267 mode: function( mode ) { 268 if ( ! mode ) { 269 return this._mode; 270 } 271 // Bail if we're trying to change to the current mode. 272 if ( mode === this._mode ) { 273 return this; 274 } 275 276 /** 277 * Region mode deactivation event. 278 * 279 * @event this.view#{this.id}:deactivate:{this._mode} 280 * @event this.view#{this.id}:deactivate 281 */ 282 this.trigger('deactivate'); 283 284 this._mode = mode; 285 this.render( mode ); 286 287 /** 288 * Region mode activation event. 289 * 290 * @event this.view#{this.id}:activate:{this._mode} 291 * @event this.view#{this.id}:activate 292 */ 293 this.trigger('activate'); 294 return this; 295 }, 296 /** 297 * Render a mode. 298 * 299 * @since 3.5.0 300 * 301 * @param {string} mode 302 * 303 * @fires this.view#{this.id}:create:{this._mode} 304 * @fires this.view#{this.id}:create 305 * @fires this.view#{this.id}:render:{this._mode} 306 * @fires this.view#{this.id}:render 307 * 308 * @returns {wp.media.controller.Region} Returns itself to allow chaining 309 */ 310 render: function( mode ) { 311 // If the mode isn't active, activate it. 312 if ( mode && mode !== this._mode ) { 313 return this.mode( mode ); 314 } 315 316 var set = { view: null }, 317 view; 318 319 /** 320 * Create region view event. 321 * 322 * Region view creation takes place in an event callback on the frame. 323 * 324 * @event this.view#{this.id}:create:{this._mode} 325 * @event this.view#{this.id}:create 326 */ 327 this.trigger( 'create', set ); 328 view = set.view; 329 330 /** 331 * Render region view event. 332 * 333 * Region view creation takes place in an event callback on the frame. 334 * 335 * @event this.view#{this.id}:create:{this._mode} 336 * @event this.view#{this.id}:create 337 */ 338 this.trigger( 'render', view ); 339 if ( view ) { 340 this.set( view ); 341 } 342 return this; 343 }, 344 345 /** 346 * Get the region's view. 347 * 348 * @since 3.5.0 349 * 350 * @returns {wp.media.View} 351 */ 352 get: function() { 353 return this.view.views.first( this.selector ); 354 }, 355 356 /** 357 * Set the region's view as a subview of the frame. 358 * 359 * @since 3.5.0 360 * 361 * @param {Array|Object} views 362 * @param {Object} [options={}] 363 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining 364 */ 365 set: function( views, options ) { 366 if ( options ) { 367 options.add = false; 368 } 369 return this.view.views.set( this.selector, views, options ); 370 }, 371 372 /** 373 * Trigger regional view events on the frame. 374 * 375 * @since 3.5.0 376 * 377 * @param {string} event 378 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining. 379 */ 380 trigger: function( event ) { 381 var base, args; 382 383 if ( ! this._mode ) { 945 * @param browser 946 */ 947 gallerySettings: function( browser ) { 948 if ( ! this.get('displaySettings') ) { 384 949 return; 385 950 } 386 951 387 args = _.toArray( arguments ); 388 base = this.id + ':' + event; 389 390 // Trigger `{this.id}:{event}:{this._mode}` event on the frame. 391 args[0] = base + ':' + this._mode; 392 this.view.trigger.apply( this.view, args ); 393 394 // Trigger `{this.id}:{event}` event on the frame. 395 args[0] = base; 396 this.view.trigger.apply( this.view, args ); 397 return this; 952 var library = this.get('library'); 953 954 if ( ! library || ! browser ) { 955 return; 956 } 957 958 library.gallery = library.gallery || new Backbone.Model(); 959 960 browser.sidebar.set({ 961 gallery: new wp.media.view.Settings.Gallery({ 962 controller: this, 963 model: library.gallery, 964 priority: 40 965 }) 966 }); 967 968 browser.toolbar.set( 'reverse', { 969 text: l10n.reverseOrder, 970 priority: 80, 971 972 click: function() { 973 library.reset( library.toArray().reverse() ); 974 } 975 }); 398 976 } 399 977 }); 400 978 401 module.exports = Region; 402 403 404 /***/ }), 405 /* 28 */ 406 /***/ (function(module, exports) { 407 979 module.exports = GalleryEdit; 980 981 },{}],10:[function(require,module,exports){ 408 982 /** 409 * wp.media.controller.StateMachine 410 * 411 * A state machine keeps track of state. It is in one state at a time, 412 * and can change from one state to another. 413 * 414 * States are stored as models in a Backbone collection. 415 * 416 * @since 3.5.0 983 * wp.media.controller.ImageDetails 984 * 985 * A state for editing the attachment display settings of an image that's been 986 * inserted into the editor. 417 987 * 418 988 * @class 989 * @augments wp.media.controller.State 419 990 * @augments Backbone.Model 420 * @mixin 421 * @mixes Backbone.Events 422 * 423 * @param {Array} states 991 * 992 * @param {object} [attributes] The attributes hash passed to the state. 993 * @param {string} [attributes.id=image-details] Unique identifier. 994 * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. 995 * @param {wp.media.model.Attachment} attributes.image The image's model. 996 * @param {string|false} [attributes.content=image-details] Initial mode for the content region. 997 * @param {string|false} [attributes.menu=false] Initial mode for the menu region. 998 * @param {string|false} [attributes.router=false] Initial mode for the router region. 999 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1000 * @param {boolean} [attributes.editing=false] Unused. 1001 * @param {int} [attributes.priority=60] Unused. 1002 * 1003 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, 1004 * however this may not do anything. 424 1005 */ 425 var StateMachine = function( states ) { 426 // @todo This is dead code. The states collection gets created in media.view.Frame._createStates. 427 this.states = new Backbone.Collection( states ); 428 }; 429 430 // Use Backbone's self-propagating `extend` inheritance method. 431 StateMachine.extend = Backbone.Model.extend; 432 433 _.extend( StateMachine.prototype, Backbone.Events, { 434 /** 435 * Fetch a state. 1006 var State = wp.media.controller.State, 1007 Library = wp.media.controller.Library, 1008 l10n = wp.media.view.l10n, 1009 ImageDetails; 1010 1011 ImageDetails = State.extend({ 1012 defaults: _.defaults({ 1013 id: 'image-details', 1014 title: l10n.imageDetailsTitle, 1015 content: 'image-details', 1016 menu: false, 1017 router: false, 1018 toolbar: 'image-details', 1019 editing: false, 1020 priority: 60 1021 }, Library.prototype.defaults ), 1022 1023 /** 1024 * @since 3.9.0 436 1025 * 437 * If no `id` is provided, returns the active state. 438 * 439 * Implicitly creates states. 440 * 441 * Ensure that the `states` collection exists so the `StateMachine` 442 * can be used as a mixin. 443 * 444 * @since 3.5.0 445 * 446 * @param {string} id 447 * @returns {wp.media.controller.State} Returns a State model 448 * from the StateMachine collection 449 */ 450 state: function( id ) { 451 this.states = this.states || new Backbone.Collection(); 452 453 // Default to the active state. 454 id = id || this._state; 455 456 if ( id && ! this.states.get( id ) ) { 457 this.states.add({ id: id }); 458 } 459 return this.states.get( id ); 460 }, 461 462 /** 463 * Sets the active state. 464 * 465 * Bail if we're trying to select the current state, if we haven't 466 * created the `states` collection, or are trying to select a state 467 * that does not exist. 468 * 469 * @since 3.5.0 470 * 471 * @param {string} id 472 * 473 * @fires wp.media.controller.State#deactivate 474 * @fires wp.media.controller.State#activate 475 * 476 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining 477 */ 478 setState: function( id ) { 479 var previous = this.state(); 480 481 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 482 return this; 483 } 484 485 if ( previous ) { 486 previous.trigger('deactivate'); 487 this._lastState = previous.id; 488 } 489 490 this._state = id; 491 this.state().trigger('activate'); 492 493 return this; 494 }, 495 496 /** 497 * Returns the previous active state. 498 * 499 * Call the `state()` method with no parameters to retrieve the current 500 * active state. 501 * 502 * @since 3.5.0 503 * 504 * @returns {wp.media.controller.State} Returns a State model 505 * from the StateMachine collection 506 */ 507 lastState: function() { 508 if ( this._lastState ) { 509 return this.state( this._lastState ); 510 } 1026 * @param options Attributes 1027 */ 1028 initialize: function( options ) { 1029 this.image = options.image; 1030 State.prototype.initialize.apply( this, arguments ); 1031 }, 1032 1033 /** 1034 * @since 3.9.0 1035 */ 1036 activate: function() { 1037 this.frame.modal.$el.addClass('image-details'); 511 1038 } 512 1039 }); 513 1040 514 // Map all event binding and triggering on a StateMachine to its `states` collection. 515 _.each([ 'on', 'off', 'trigger' ], function( method ) { 516 /** 517 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 518 */ 519 StateMachine.prototype[ method ] = function() { 520 // Ensure that the `states` collection exists so the `StateMachine` 521 // can be used as a mixin. 522 this.states = this.states || new Backbone.Collection(); 523 // Forward the method to the `states` collection. 524 this.states[ method ].apply( this.states, arguments ); 525 return this; 526 }; 527 }); 528 529 module.exports = StateMachine; 530 531 532 /***/ }), 533 /* 29 */ 534 /***/ (function(module, exports) { 535 536 /** 537 * wp.media.controller.State 538 * 539 * A state is a step in a workflow that when set will trigger the controllers 540 * for the regions to be updated as specified in the frame. 541 * 542 * A state has an event-driven lifecycle: 543 * 544 * 'ready' triggers when a state is added to a state machine's collection. 545 * 'activate' triggers when a state is activated by a state machine. 546 * 'deactivate' triggers when a state is deactivated by a state machine. 547 * 'reset' is not triggered automatically. It should be invoked by the 548 * proper controller to reset the state to its default. 549 * 550 * @class 551 * @augments Backbone.Model 552 */ 553 var State = Backbone.Model.extend({ 554 /** 555 * Constructor. 556 * 557 * @since 3.5.0 558 */ 559 constructor: function() { 560 this.on( 'activate', this._preActivate, this ); 561 this.on( 'activate', this.activate, this ); 562 this.on( 'activate', this._postActivate, this ); 563 this.on( 'deactivate', this._deactivate, this ); 564 this.on( 'deactivate', this.deactivate, this ); 565 this.on( 'reset', this.reset, this ); 566 this.on( 'ready', this._ready, this ); 567 this.on( 'ready', this.ready, this ); 568 /** 569 * Call parent constructor with passed arguments 570 */ 571 Backbone.Model.apply( this, arguments ); 572 this.on( 'change:menu', this._updateMenu, this ); 573 }, 574 /** 575 * Ready event callback. 576 * 577 * @abstract 578 * @since 3.5.0 579 */ 580 ready: function() {}, 581 582 /** 583 * Activate event callback. 584 * 585 * @abstract 586 * @since 3.5.0 587 */ 588 activate: function() {}, 589 590 /** 591 * Deactivate event callback. 592 * 593 * @abstract 594 * @since 3.5.0 595 */ 596 deactivate: function() {}, 597 598 /** 599 * Reset event callback. 600 * 601 * @abstract 602 * @since 3.5.0 603 */ 604 reset: function() {}, 605 606 /** 607 * @access private 608 * @since 3.5.0 609 */ 610 _ready: function() { 611 this._updateMenu(); 612 }, 613 614 /** 615 * @access private 616 * @since 3.5.0 617 */ 618 _preActivate: function() { 619 this.active = true; 620 }, 621 622 /** 623 * @access private 624 * @since 3.5.0 625 */ 626 _postActivate: function() { 627 this.on( 'change:menu', this._menu, this ); 628 this.on( 'change:titleMode', this._title, this ); 629 this.on( 'change:content', this._content, this ); 630 this.on( 'change:toolbar', this._toolbar, this ); 631 632 this.frame.on( 'title:render:default', this._renderTitle, this ); 633 634 this._title(); 635 this._menu(); 636 this._toolbar(); 637 this._content(); 638 this._router(); 639 }, 640 641 /** 642 * @access private 643 * @since 3.5.0 644 */ 645 _deactivate: function() { 646 this.active = false; 647 648 this.frame.off( 'title:render:default', this._renderTitle, this ); 649 650 this.off( 'change:menu', this._menu, this ); 651 this.off( 'change:titleMode', this._title, this ); 652 this.off( 'change:content', this._content, this ); 653 this.off( 'change:toolbar', this._toolbar, this ); 654 }, 655 656 /** 657 * @access private 658 * @since 3.5.0 659 */ 660 _title: function() { 661 this.frame.title.render( this.get('titleMode') || 'default' ); 662 }, 663 664 /** 665 * @access private 666 * @since 3.5.0 667 */ 668 _renderTitle: function( view ) { 669 view.$el.text( this.get('title') || '' ); 670 }, 671 672 /** 673 * @access private 674 * @since 3.5.0 675 */ 676 _router: function() { 677 var router = this.frame.router, 678 mode = this.get('router'), 679 view; 680 681 this.frame.$el.toggleClass( 'hide-router', ! mode ); 682 if ( ! mode ) { 683 return; 684 } 685 686 this.frame.router.render( mode ); 687 688 view = router.get(); 689 if ( view && view.select ) { 690 view.select( this.frame.content.mode() ); 691 } 692 }, 693 694 /** 695 * @access private 696 * @since 3.5.0 697 */ 698 _menu: function() { 699 var menu = this.frame.menu, 700 mode = this.get('menu'), 701 view; 702 703 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 704 if ( ! mode ) { 705 return; 706 } 707 708 menu.mode( mode ); 709 710 view = menu.get(); 711 if ( view && view.select ) { 712 view.select( this.id ); 713 } 714 }, 715 716 /** 717 * @access private 718 * @since 3.5.0 719 */ 720 _updateMenu: function() { 721 var previous = this.previous('menu'), 722 menu = this.get('menu'); 723 724 if ( previous ) { 725 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 726 } 727 728 if ( menu ) { 729 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 730 } 731 }, 732 733 /** 734 * Create a view in the media menu for the state. 735 * 736 * @access private 737 * @since 3.5.0 738 * 739 * @param {media.view.Menu} view The menu view. 740 */ 741 _renderMenu: function( view ) { 742 var menuItem = this.get('menuItem'), 743 title = this.get('title'), 744 priority = this.get('priority'); 745 746 if ( ! menuItem && title ) { 747 menuItem = { text: title }; 748 749 if ( priority ) { 750 menuItem.priority = priority; 751 } 752 } 753 754 if ( ! menuItem ) { 755 return; 756 } 757 758 view.set( this.id, menuItem ); 759 } 760 }); 761 762 _.each(['toolbar','content'], function( region ) { 763 /** 764 * @access private 765 */ 766 State.prototype[ '_' + region ] = function() { 767 var mode = this.get( region ); 768 if ( mode ) { 769 this.frame[ region ].render( mode ); 770 } 771 }; 772 }); 773 774 module.exports = State; 775 776 777 /***/ }), 778 /* 30 */ 779 /***/ (function(module, exports) { 780 781 /** 782 * wp.media.selectionSync 783 * 784 * Sync an attachments selection in a state with another state. 785 * 786 * Allows for selecting multiple images in the Insert Media workflow, and then 787 * switching to the Insert Gallery workflow while preserving the attachments selection. 788 * 789 * @mixin 790 */ 791 var selectionSync = { 792 /** 793 * @since 3.5.0 794 */ 795 syncSelection: function() { 796 var selection = this.get('selection'), 797 manager = this.frame._selection; 798 799 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 800 return; 801 } 802 803 // If the selection supports multiple items, validate the stored 804 // attachments based on the new selection's conditions. Record 805 // the attachments that are not included; we'll maintain a 806 // reference to those. Other attachments are considered in flux. 807 if ( selection.multiple ) { 808 selection.reset( [], { silent: true }); 809 selection.validateAll( manager.attachments ); 810 manager.difference = _.difference( manager.attachments.models, selection.models ); 811 } 812 813 // Sync the selection's single item with the master. 814 selection.single( manager.single ); 815 }, 816 817 /** 818 * Record the currently active attachments, which is a combination 819 * of the selection's attachments and the set of selected 820 * attachments that this specific selection considered invalid. 821 * Reset the difference and record the single attachment. 822 * 823 * @since 3.5.0 824 */ 825 recordSelection: function() { 826 var selection = this.get('selection'), 827 manager = this.frame._selection; 828 829 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 830 return; 831 } 832 833 if ( selection.multiple ) { 834 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 835 manager.difference = []; 836 } else { 837 manager.attachments.add( selection.toArray() ); 838 } 839 840 manager.single = selection._single; 841 } 842 }; 843 844 module.exports = selectionSync; 845 846 847 /***/ }), 848 /* 31 */ 849 /***/ (function(module, exports) { 850 1041 module.exports = ImageDetails; 1042 1043 },{}],11:[function(require,module,exports){ 851 1044 /** 852 1045 * wp.media.controller.Library … … 1141 1334 module.exports = Library; 1142 1335 1143 1144 /***/ }), 1145 /* 32 */ 1146 /***/ (function(module, exports) { 1147 1336 },{}],12:[function(require,module,exports){ 1148 1337 /** 1149 * wp.media.controller.ImageDetails 1150 * 1151 * A state for editing the attachment display settings of an image that's been 1152 * inserted into the editor. 1153 * 1154 * @class 1155 * @augments wp.media.controller.State 1156 * @augments Backbone.Model 1157 * 1158 * @param {object} [attributes] The attributes hash passed to the state. 1159 * @param {string} [attributes.id=image-details] Unique identifier. 1160 * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. 1161 * @param {wp.media.model.Attachment} attributes.image The image's model. 1162 * @param {string|false} [attributes.content=image-details] Initial mode for the content region. 1163 * @param {string|false} [attributes.menu=false] Initial mode for the menu region. 1164 * @param {string|false} [attributes.router=false] Initial mode for the router region. 1165 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1166 * @param {boolean} [attributes.editing=false] Unused. 1167 * @param {int} [attributes.priority=60] Unused. 1168 * 1169 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, 1170 * however this may not do anything. 1171 */ 1172 var State = wp.media.controller.State, 1173 Library = wp.media.controller.Library, 1174 l10n = wp.media.view.l10n, 1175 ImageDetails; 1176 1177 ImageDetails = State.extend({ 1178 defaults: _.defaults({ 1179 id: 'image-details', 1180 title: l10n.imageDetailsTitle, 1181 content: 'image-details', 1182 menu: false, 1183 router: false, 1184 toolbar: 'image-details', 1185 editing: false, 1186 priority: 60 1187 }, Library.prototype.defaults ), 1188 1189 /** 1190 * @since 3.9.0 1191 * 1192 * @param options Attributes 1193 */ 1194 initialize: function( options ) { 1195 this.image = options.image; 1196 State.prototype.initialize.apply( this, arguments ); 1197 }, 1198 1199 /** 1200 * @since 3.9.0 1201 */ 1202 activate: function() { 1203 this.frame.modal.$el.addClass('image-details'); 1204 } 1205 }); 1206 1207 module.exports = ImageDetails; 1208 1209 1210 /***/ }), 1211 /* 33 */ 1212 /***/ (function(module, exports) { 1213 1214 /** 1215 * wp.media.controller.GalleryEdit 1216 * 1217 * A state for editing a gallery's images and settings. 1338 * wp.media.controller.MediaLibrary 1218 1339 * 1219 1340 * @class … … 1221 1342 * @augments wp.media.controller.State 1222 1343 * @augments Backbone.Model 1223 *1224 * @param {object} [attributes] The attributes hash passed to the state.1225 * @param {string} [attributes.id=gallery-edit] Unique identifier.1226 * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region.1227 * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery.1228 * If one is not supplied, an empty media.model.Selection collection is created.1229 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled.1230 * @param {boolean} [attributes.searchable=false] Whether the library is searchable.1231 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.1232 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar.1233 * @param {string|false} [attributes.content=browse] Initial mode for the content region.1234 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region.1235 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery.1236 * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface.1237 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable.1238 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.1239 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance.1240 * @param {int} [attributes.priority=60] The priority for the state link in the media menu.1241 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state.1242 * Defaults to false for this state, because the library passed in *is* the selection.1243 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`.1244 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary.1245 1344 */ 1246 1345 var Library = wp.media.controller.Library, 1247 l10n = wp.media.view.l10n, 1248 GalleryEdit; 1249 1250 GalleryEdit = Library.extend({ 1251 defaults: { 1252 id: 'gallery-edit', 1253 title: l10n.editGalleryTitle, 1254 multiple: false, 1255 searchable: false, 1256 sortable: true, 1257 date: false, 1258 display: false, 1259 content: 'browse', 1260 toolbar: 'gallery-edit', 1261 describe: true, 1262 displaySettings: true, 1263 dragInfo: true, 1264 idealColumnWidth: 170, 1265 editing: false, 1266 priority: 60, 1267 syncSelection: false 1268 }, 1269 1270 /** 1271 * @since 3.5.0 1272 */ 1273 initialize: function() { 1274 // If we haven't been provided a `library`, create a `Selection`. 1275 if ( ! this.get('library') ) { 1276 this.set( 'library', new wp.media.model.Selection() ); 1277 } 1278 1279 // The single `Attachment` view to be used in the `Attachments` view. 1280 if ( ! this.get('AttachmentView') ) { 1281 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 1282 } 1346 MediaLibrary; 1347 1348 MediaLibrary = Library.extend({ 1349 defaults: _.defaults({ 1350 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1351 filterable: 'uploaded', 1352 1353 displaySettings: false, 1354 priority: 80, 1355 syncSelection: false 1356 }, Library.prototype.defaults ), 1357 1358 /** 1359 * @since 3.9.0 1360 * 1361 * @param options 1362 */ 1363 initialize: function( options ) { 1364 this.media = options.media; 1365 this.type = options.type; 1366 this.set( 'library', wp.media.query({ type: this.type }) ); 1283 1367 1284 1368 Library.prototype.initialize.apply( this, arguments ); … … 1286 1370 1287 1371 /** 1288 * @since 3. 5.01372 * @since 3.9.0 1289 1373 */ 1290 1374 activate: function() { 1291 var library = this.get('library'); 1292 1293 // Limit the library to images only. 1294 library.props.set( 'type', 'image' ); 1295 1296 // Watch for uploaded attachments. 1297 this.get('library').observe( wp.Uploader.queue ); 1298 1299 this.frame.on( 'content:render:browse', this.gallerySettings, this ); 1300 1301 Library.prototype.activate.apply( this, arguments ); 1302 }, 1303 1304 /** 1305 * @since 3.5.0 1306 */ 1307 deactivate: function() { 1308 // Stop watching for uploaded attachments. 1309 this.get('library').unobserve( wp.Uploader.queue ); 1310 1311 this.frame.off( 'content:render:browse', this.gallerySettings, this ); 1312 1313 Library.prototype.deactivate.apply( this, arguments ); 1314 }, 1315 1316 /** 1317 * @since 3.5.0 1318 * 1319 * @param browser 1320 */ 1321 gallerySettings: function( browser ) { 1322 if ( ! this.get('displaySettings') ) { 1323 return; 1324 } 1325 1326 var library = this.get('library'); 1327 1328 if ( ! library || ! browser ) { 1329 return; 1330 } 1331 1332 library.gallery = library.gallery || new Backbone.Model(); 1333 1334 browser.sidebar.set({ 1335 gallery: new wp.media.view.Settings.Gallery({ 1336 controller: this, 1337 model: library.gallery, 1338 priority: 40 1339 }) 1340 }); 1341 1342 browser.toolbar.set( 'reverse', { 1343 text: l10n.reverseOrder, 1344 priority: 80, 1345 1346 click: function() { 1347 library.reset( library.toArray().reverse() ); 1348 } 1349 }); 1350 } 1351 }); 1352 1353 module.exports = GalleryEdit; 1354 1355 1356 /***/ }), 1357 /* 34 */ 1358 /***/ (function(module, exports) { 1359 1360 /** 1361 * wp.media.controller.GalleryAdd 1362 * 1363 * A state for selecting more images to add to a gallery. 1364 * 1365 * @class 1366 * @augments wp.media.controller.Library 1367 * @augments wp.media.controller.State 1368 * @augments Backbone.Model 1369 * 1370 * @param {object} [attributes] The attributes hash passed to the state. 1371 * @param {string} [attributes.id=gallery-library] Unique identifier. 1372 * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. 1373 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 1374 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1375 * If one is not supplied, a collection of all images will be created. 1376 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1377 * Accepts 'all', 'uploaded', or 'unattached'. 1378 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 1379 * @param {string} [attributes.content=upload] Initial mode for the content region. 1380 * Overridden by persistent user setting if 'contentUserSetting' is true. 1381 * @param {string} [attributes.router=browse] Initial mode for the router region. 1382 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 1383 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1384 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1385 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1386 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1387 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 1388 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1389 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 1390 */ 1391 var Selection = wp.media.model.Selection, 1392 Library = wp.media.controller.Library, 1393 l10n = wp.media.view.l10n, 1394 GalleryAdd; 1395 1396 GalleryAdd = Library.extend({ 1397 defaults: _.defaults({ 1398 id: 'gallery-library', 1399 title: l10n.addToGalleryTitle, 1400 multiple: 'add', 1401 filterable: 'uploaded', 1402 menu: 'gallery', 1403 toolbar: 'gallery-add', 1404 priority: 100, 1405 syncSelection: false 1406 }, Library.prototype.defaults ), 1407 1408 /** 1409 * @since 3.5.0 1410 */ 1411 initialize: function() { 1412 // If a library wasn't supplied, create a library of images. 1413 if ( ! this.get('library') ) { 1414 this.set( 'library', wp.media.query({ type: 'image' }) ); 1415 } 1416 1417 Library.prototype.initialize.apply( this, arguments ); 1418 }, 1419 1420 /** 1421 * @since 3.5.0 1422 */ 1423 activate: function() { 1424 var library = this.get('library'), 1425 edit = this.frame.state('gallery-edit').get('library'); 1426 1427 if ( this.editLibrary && this.editLibrary !== edit ) { 1428 library.unobserve( this.editLibrary ); 1429 } 1430 1431 // Accepts attachments that exist in the original library and 1432 // that do not exist in gallery's library. 1433 library.validator = function( attachment ) { 1434 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 1435 }; 1436 1437 // Reset the library to ensure that all attachments are re-added 1438 // to the collection. Do so silently, as calling `observe` will 1439 // trigger the `reset` event. 1440 library.reset( library.mirroring.models, { silent: true }); 1441 library.observe( edit ); 1442 this.editLibrary = edit; 1443 1375 // @todo this should use this.frame. 1376 if ( wp.media.frame.lastMime ) { 1377 this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) ); 1378 delete wp.media.frame.lastMime; 1379 } 1444 1380 Library.prototype.activate.apply( this, arguments ); 1445 1381 } 1446 1382 }); 1447 1383 1448 module.exports = GalleryAdd; 1449 1450 1451 /***/ }), 1452 /* 35 */ 1453 /***/ (function(module, exports) { 1454 1384 module.exports = MediaLibrary; 1385 1386 },{}],13:[function(require,module,exports){ 1455 1387 /** 1456 * wp.media.controller.CollectionEdit 1457 * 1458 * A state for editing a collection, which is used by audio and video playlists, 1459 * and can be used for other collections. 1388 * wp.media.controller.Region 1389 * 1390 * A region is a persistent application layout area. 1391 * 1392 * A region assumes one mode at any time, and can be switched to another. 1393 * 1394 * When mode changes, events are triggered on the region's parent view. 1395 * The parent view will listen to specific events and fill the region with an 1396 * appropriate view depending on mode. For example, a frame listens for the 1397 * 'browse' mode t be activated on the 'content' view and then fills the region 1398 * with an AttachmentsBrowser view. 1460 1399 * 1461 1400 * @class 1462 * @augments wp.media.controller.Library 1463 * @augments wp.media.controller.State 1464 * @augments Backbone.Model 1465 * 1466 * @param {object} [attributes] The attributes hash passed to the state. 1467 * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. 1468 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. 1469 * If one is not supplied, an empty media.model.Selection collection is created. 1470 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1471 * @param {string} [attributes.content=browse] Initial mode for the content region. 1472 * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. 1473 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 1474 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1475 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 1476 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. 1477 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 1478 * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. 1479 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 1480 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 1481 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1482 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1483 * Defaults to false for this state, because the library passed in *is* the selection. 1484 * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). 1485 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 1486 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 1487 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 1488 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 1401 * 1402 * @param {object} options Options hash for the region. 1403 * @param {string} options.id Unique identifier for the region. 1404 * @param {Backbone.View} options.view A parent view the region exists within. 1405 * @param {string} options.selector jQuery selector for the region within the parent view. 1489 1406 */ 1490 var Library = wp.media.controller.Library, 1491 l10n = wp.media.view.l10n, 1492 $ = jQuery, 1493 CollectionEdit; 1494 1495 CollectionEdit = Library.extend({ 1496 defaults: { 1497 multiple: false, 1498 sortable: true, 1499 date: false, 1500 searchable: false, 1501 content: 'browse', 1502 describe: true, 1503 dragInfo: true, 1504 idealColumnWidth: 170, 1505 editing: false, 1506 priority: 60, 1507 SettingsView: false, 1508 syncSelection: false 1509 }, 1510 1511 /** 1512 * @since 3.9.0 1513 */ 1514 initialize: function() { 1515 var collectionType = this.get('collectionType'); 1516 1517 if ( 'video' === this.get( 'type' ) ) { 1518 collectionType = 'video-' + collectionType; 1519 } 1520 1521 this.set( 'id', collectionType + '-edit' ); 1522 this.set( 'toolbar', collectionType + '-edit' ); 1523 1524 // If we haven't been provided a `library`, create a `Selection`. 1525 if ( ! this.get('library') ) { 1526 this.set( 'library', new wp.media.model.Selection() ); 1527 } 1528 // The single `Attachment` view to be used in the `Attachments` view. 1529 if ( ! this.get('AttachmentView') ) { 1530 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 1531 } 1532 Library.prototype.initialize.apply( this, arguments ); 1533 }, 1534 1535 /** 1536 * @since 3.9.0 1537 */ 1538 activate: function() { 1539 var library = this.get('library'); 1540 1541 // Limit the library to images only. 1542 library.props.set( 'type', this.get( 'type' ) ); 1543 1544 // Watch for uploaded attachments. 1545 this.get('library').observe( wp.Uploader.queue ); 1546 1547 this.frame.on( 'content:render:browse', this.renderSettings, this ); 1548 1549 Library.prototype.activate.apply( this, arguments ); 1550 }, 1551 1552 /** 1553 * @since 3.9.0 1554 */ 1555 deactivate: function() { 1556 // Stop watching for uploaded attachments. 1557 this.get('library').unobserve( wp.Uploader.queue ); 1558 1559 this.frame.off( 'content:render:browse', this.renderSettings, this ); 1560 1561 Library.prototype.deactivate.apply( this, arguments ); 1562 }, 1563 1564 /** 1565 * Render the collection embed settings view in the browser sidebar. 1407 var Region = function( options ) { 1408 _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); 1409 }; 1410 1411 // Use Backbone's self-propagating `extend` inheritance method. 1412 Region.extend = Backbone.Model.extend; 1413 1414 _.extend( Region.prototype, { 1415 /** 1416 * Activate a mode. 1566 1417 * 1567 * @todo This is against the pattern elsewhere in media. Typically the frame 1568 * is responsible for adding region mode callbacks. Explain. 1418 * @since 3.5.0 1569 1419 * 1570 * @ since 3.9.01420 * @param {string} mode 1571 1421 * 1572 * @param {wp.media.view.attachmentsBrowser} The attachments browser view. 1573 */ 1574 renderSettings: function( attachmentsBrowserView ) { 1575 var library = this.get('library'), 1576 collectionType = this.get('collectionType'), 1577 dragInfoText = this.get('dragInfoText'), 1578 SettingsView = this.get('SettingsView'), 1579 obj = {}; 1580 1581 if ( ! library || ! attachmentsBrowserView ) { 1422 * @fires this.view#{this.id}:activate:{this._mode} 1423 * @fires this.view#{this.id}:activate 1424 * @fires this.view#{this.id}:deactivate:{this._mode} 1425 * @fires this.view#{this.id}:deactivate 1426 * 1427 * @returns {wp.media.controller.Region} Returns itself to allow chaining. 1428 */ 1429 mode: function( mode ) { 1430 if ( ! mode ) { 1431 return this._mode; 1432 } 1433 // Bail if we're trying to change to the current mode. 1434 if ( mode === this._mode ) { 1435 return this; 1436 } 1437 1438 /** 1439 * Region mode deactivation event. 1440 * 1441 * @event this.view#{this.id}:deactivate:{this._mode} 1442 * @event this.view#{this.id}:deactivate 1443 */ 1444 this.trigger('deactivate'); 1445 1446 this._mode = mode; 1447 this.render( mode ); 1448 1449 /** 1450 * Region mode activation event. 1451 * 1452 * @event this.view#{this.id}:activate:{this._mode} 1453 * @event this.view#{this.id}:activate 1454 */ 1455 this.trigger('activate'); 1456 return this; 1457 }, 1458 /** 1459 * Render a mode. 1460 * 1461 * @since 3.5.0 1462 * 1463 * @param {string} mode 1464 * 1465 * @fires this.view#{this.id}:create:{this._mode} 1466 * @fires this.view#{this.id}:create 1467 * @fires this.view#{this.id}:render:{this._mode} 1468 * @fires this.view#{this.id}:render 1469 * 1470 * @returns {wp.media.controller.Region} Returns itself to allow chaining 1471 */ 1472 render: function( mode ) { 1473 // If the mode isn't active, activate it. 1474 if ( mode && mode !== this._mode ) { 1475 return this.mode( mode ); 1476 } 1477 1478 var set = { view: null }, 1479 view; 1480 1481 /** 1482 * Create region view event. 1483 * 1484 * Region view creation takes place in an event callback on the frame. 1485 * 1486 * @event this.view#{this.id}:create:{this._mode} 1487 * @event this.view#{this.id}:create 1488 */ 1489 this.trigger( 'create', set ); 1490 view = set.view; 1491 1492 /** 1493 * Render region view event. 1494 * 1495 * Region view creation takes place in an event callback on the frame. 1496 * 1497 * @event this.view#{this.id}:create:{this._mode} 1498 * @event this.view#{this.id}:create 1499 */ 1500 this.trigger( 'render', view ); 1501 if ( view ) { 1502 this.set( view ); 1503 } 1504 return this; 1505 }, 1506 1507 /** 1508 * Get the region's view. 1509 * 1510 * @since 3.5.0 1511 * 1512 * @returns {wp.media.View} 1513 */ 1514 get: function() { 1515 return this.view.views.first( this.selector ); 1516 }, 1517 1518 /** 1519 * Set the region's view as a subview of the frame. 1520 * 1521 * @since 3.5.0 1522 * 1523 * @param {Array|Object} views 1524 * @param {Object} [options={}] 1525 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining 1526 */ 1527 set: function( views, options ) { 1528 if ( options ) { 1529 options.add = false; 1530 } 1531 return this.view.views.set( this.selector, views, options ); 1532 }, 1533 1534 /** 1535 * Trigger regional view events on the frame. 1536 * 1537 * @since 3.5.0 1538 * 1539 * @param {string} event 1540 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining. 1541 */ 1542 trigger: function( event ) { 1543 var base, args; 1544 1545 if ( ! this._mode ) { 1582 1546 return; 1583 1547 } 1584 1548 1585 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 1586 1587 obj[ collectionType ] = new SettingsView({ 1588 controller: this, 1589 model: library[ collectionType ], 1590 priority: 40 1591 }); 1592 1593 attachmentsBrowserView.sidebar.set( obj ); 1594 1595 if ( dragInfoText ) { 1596 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 1597 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 1598 priority: -40 1599 }) ); 1600 } 1601 1602 // Add the 'Reverse order' button to the toolbar. 1603 attachmentsBrowserView.toolbar.set( 'reverse', { 1604 text: l10n.reverseOrder, 1605 priority: 80, 1606 1607 click: function() { 1608 library.reset( library.toArray().reverse() ); 1609 } 1610 }); 1549 args = _.toArray( arguments ); 1550 base = this.id + ':' + event; 1551 1552 // Trigger `{this.id}:{event}:{this._mode}` event on the frame. 1553 args[0] = base + ':' + this._mode; 1554 this.view.trigger.apply( this.view, args ); 1555 1556 // Trigger `{this.id}:{event}` event on the frame. 1557 args[0] = base; 1558 this.view.trigger.apply( this.view, args ); 1559 return this; 1611 1560 } 1612 1561 }); 1613 1562 1614 module.exports = CollectionEdit; 1615 1616 1617 /***/ }), 1618 /* 36 */ 1619 /***/ (function(module, exports) { 1620 1621 /** 1622 * wp.media.controller.CollectionAdd 1623 * 1624 * A state for adding attachments to a collection (e.g. video playlist). 1625 * 1626 * @class 1627 * @augments wp.media.controller.Library 1628 * @augments wp.media.controller.State 1629 * @augments Backbone.Model 1630 * 1631 * @param {object} [attributes] The attributes hash passed to the state. 1632 * @param {string} [attributes.id=library] Unique identifier. 1633 * @param {string} attributes.title Title for the state. Displays in the frame's title region. 1634 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 1635 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1636 * If one is not supplied, a collection of attachments of the specified type will be created. 1637 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1638 * Accepts 'all', 'uploaded', or 'unattached'. 1639 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 1640 * @param {string} [attributes.content=upload] Initial mode for the content region. 1641 * Overridden by persistent user setting if 'contentUserSetting' is true. 1642 * @param {string} [attributes.router=browse] Initial mode for the router region. 1643 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 1644 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1645 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1646 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1647 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1648 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 1649 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1650 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 1651 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 1652 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 1653 */ 1654 var Selection = wp.media.model.Selection, 1655 Library = wp.media.controller.Library, 1656 CollectionAdd; 1657 1658 CollectionAdd = Library.extend({ 1659 defaults: _.defaults( { 1660 // Selection defaults. @see media.model.Selection 1661 multiple: 'add', 1662 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1663 filterable: 'uploaded', 1664 1665 priority: 100, 1666 syncSelection: false 1667 }, Library.prototype.defaults ), 1668 1669 /** 1670 * @since 3.9.0 1671 */ 1672 initialize: function() { 1673 var collectionType = this.get('collectionType'); 1674 1675 if ( 'video' === this.get( 'type' ) ) { 1676 collectionType = 'video-' + collectionType; 1677 } 1678 1679 this.set( 'id', collectionType + '-library' ); 1680 this.set( 'toolbar', collectionType + '-add' ); 1681 this.set( 'menu', collectionType ); 1682 1683 // If we haven't been provided a `library`, create a `Selection`. 1684 if ( ! this.get('library') ) { 1685 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 1686 } 1687 Library.prototype.initialize.apply( this, arguments ); 1688 }, 1689 1690 /** 1691 * @since 3.9.0 1692 */ 1693 activate: function() { 1694 var library = this.get('library'), 1695 editLibrary = this.get('editLibrary'), 1696 edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library'); 1697 1698 if ( editLibrary && editLibrary !== edit ) { 1699 library.unobserve( editLibrary ); 1700 } 1701 1702 // Accepts attachments that exist in the original library and 1703 // that do not exist in gallery's library. 1704 library.validator = function( attachment ) { 1705 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 1706 }; 1707 1708 // Reset the library to ensure that all attachments are re-added 1709 // to the collection. Do so silently, as calling `observe` will 1710 // trigger the `reset` event. 1711 library.reset( library.mirroring.models, { silent: true }); 1712 library.observe( edit ); 1713 this.set('editLibrary', edit); 1714 1715 Library.prototype.activate.apply( this, arguments ); 1716 } 1717 }); 1718 1719 module.exports = CollectionAdd; 1720 1721 1722 /***/ }), 1723 /* 37 */ 1724 /***/ (function(module, exports) { 1725 1726 /** 1727 * wp.media.controller.FeaturedImage 1728 * 1729 * A state for selecting a featured image for a post. 1730 * 1731 * @class 1732 * @augments wp.media.controller.Library 1733 * @augments wp.media.controller.State 1734 * @augments Backbone.Model 1735 * 1736 * @param {object} [attributes] The attributes hash passed to the state. 1737 * @param {string} [attributes.id=featured-image] Unique identifier. 1738 * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. 1739 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1740 * If one is not supplied, a collection of all images will be created. 1741 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1742 * @param {string} [attributes.content=upload] Initial mode for the content region. 1743 * Overridden by persistent user setting if 'contentUserSetting' is true. 1744 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1745 * @param {string} [attributes.router=browse] Initial mode for the router region. 1746 * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. 1747 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1748 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1749 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 1750 * Accepts 'all', 'uploaded', or 'unattached'. 1751 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1752 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1753 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1754 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1755 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1756 */ 1757 var Attachment = wp.media.model.Attachment, 1758 Library = wp.media.controller.Library, 1759 l10n = wp.media.view.l10n, 1760 FeaturedImage; 1761 1762 FeaturedImage = Library.extend({ 1763 defaults: _.defaults({ 1764 id: 'featured-image', 1765 title: l10n.setFeaturedImageTitle, 1766 multiple: false, 1767 filterable: 'uploaded', 1768 toolbar: 'featured-image', 1769 priority: 60, 1770 syncSelection: true 1771 }, Library.prototype.defaults ), 1772 1773 /** 1774 * @since 3.5.0 1775 */ 1776 initialize: function() { 1777 var library, comparator; 1778 1779 // If we haven't been provided a `library`, create a `Selection`. 1780 if ( ! this.get('library') ) { 1781 this.set( 'library', wp.media.query({ type: 'image' }) ); 1782 } 1783 1784 Library.prototype.initialize.apply( this, arguments ); 1785 1786 library = this.get('library'); 1787 comparator = library.comparator; 1788 1789 // Overload the library's comparator to push items that are not in 1790 // the mirrored query to the front of the aggregate collection. 1791 library.comparator = function( a, b ) { 1792 var aInQuery = !! this.mirroring.get( a.cid ), 1793 bInQuery = !! this.mirroring.get( b.cid ); 1794 1795 if ( ! aInQuery && bInQuery ) { 1796 return -1; 1797 } else if ( aInQuery && ! bInQuery ) { 1798 return 1; 1799 } else { 1800 return comparator.apply( this, arguments ); 1801 } 1802 }; 1803 1804 // Add all items in the selection to the library, so any featured 1805 // images that are not initially loaded still appear. 1806 library.observe( this.get('selection') ); 1807 }, 1808 1809 /** 1810 * @since 3.5.0 1811 */ 1812 activate: function() { 1813 this.updateSelection(); 1814 this.frame.on( 'open', this.updateSelection, this ); 1815 1816 Library.prototype.activate.apply( this, arguments ); 1817 }, 1818 1819 /** 1820 * @since 3.5.0 1821 */ 1822 deactivate: function() { 1823 this.frame.off( 'open', this.updateSelection, this ); 1824 1825 Library.prototype.deactivate.apply( this, arguments ); 1826 }, 1827 1828 /** 1829 * @since 3.5.0 1830 */ 1831 updateSelection: function() { 1832 var selection = this.get('selection'), 1833 id = wp.media.view.settings.post.featuredImageId, 1834 attachment; 1835 1836 if ( '' !== id && -1 !== id ) { 1837 attachment = Attachment.get( id ); 1838 attachment.fetch(); 1839 } 1840 1841 selection.reset( attachment ? [ attachment ] : [] ); 1842 } 1843 }); 1844 1845 module.exports = FeaturedImage; 1846 1847 1848 /***/ }), 1849 /* 38 */ 1850 /***/ (function(module, exports) { 1851 1563 module.exports = Region; 1564 1565 },{}],14:[function(require,module,exports){ 1852 1566 /** 1853 1567 * wp.media.controller.ReplaceImage … … 1957 1671 module.exports = ReplaceImage; 1958 1672 1959 1960 /***/ }), 1961 /* 39 */ 1962 /***/ (function(module, exports) { 1963 1964 /** 1965 * wp.media.controller.EditImage 1966 * 1967 * A state for editing (cropping, etc.) an image. 1968 * 1969 * @class 1970 * @augments wp.media.controller.State 1971 * @augments Backbone.Model 1972 * 1973 * @param {object} attributes The attributes hash passed to the state. 1974 * @param {wp.media.model.Attachment} attributes.model The attachment. 1975 * @param {string} [attributes.id=edit-image] Unique identifier. 1976 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 1977 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 1978 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 1979 * @param {string} [attributes.menu=false] Initial mode for the menu region. 1980 * @param {string} [attributes.url] Unused. @todo Consider removal. 1981 */ 1982 var l10n = wp.media.view.l10n, 1983 EditImage; 1984 1985 EditImage = wp.media.controller.State.extend({ 1986 defaults: { 1987 id: 'edit-image', 1988 title: l10n.editImage, 1989 menu: false, 1990 toolbar: 'edit-image', 1991 content: 'edit-image', 1992 url: '' 1993 }, 1994 1995 /** 1996 * @since 3.9.0 1997 */ 1998 activate: function() { 1999 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar ); 2000 }, 2001 2002 /** 2003 * @since 3.9.0 2004 */ 2005 deactivate: function() { 2006 this.stopListening( this.frame ); 2007 }, 2008 2009 /** 2010 * @since 3.9.0 2011 */ 2012 toolbar: function() { 2013 var frame = this.frame, 2014 lastState = frame.lastState(), 2015 previous = lastState && lastState.id; 2016 2017 frame.toolbar.set( new wp.media.view.Toolbar({ 2018 controller: frame, 2019 items: { 2020 back: { 2021 style: 'primary', 2022 text: l10n.back, 2023 priority: 20, 2024 click: function() { 2025 if ( previous ) { 2026 frame.setState( previous ); 2027 } else { 2028 frame.close(); 2029 } 2030 } 2031 } 2032 } 2033 }) ); 2034 } 2035 }); 2036 2037 module.exports = EditImage; 2038 2039 2040 /***/ }), 2041 /* 40 */ 2042 /***/ (function(module, exports) { 2043 2044 /** 2045 * wp.media.controller.MediaLibrary 2046 * 2047 * @class 2048 * @augments wp.media.controller.Library 2049 * @augments wp.media.controller.State 2050 * @augments Backbone.Model 2051 */ 2052 var Library = wp.media.controller.Library, 2053 MediaLibrary; 2054 2055 MediaLibrary = Library.extend({ 2056 defaults: _.defaults({ 2057 // Attachments browser defaults. @see media.view.AttachmentsBrowser 2058 filterable: 'uploaded', 2059 2060 displaySettings: false, 2061 priority: 80, 2062 syncSelection: false 2063 }, Library.prototype.defaults ), 2064 2065 /** 2066 * @since 3.9.0 2067 * 2068 * @param options 2069 */ 2070 initialize: function( options ) { 2071 this.media = options.media; 2072 this.type = options.type; 2073 this.set( 'library', wp.media.query({ type: this.type }) ); 2074 2075 Library.prototype.initialize.apply( this, arguments ); 2076 }, 2077 2078 /** 2079 * @since 3.9.0 2080 */ 2081 activate: function() { 2082 // @todo this should use this.frame. 2083 if ( wp.media.frame.lastMime ) { 2084 this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) ); 2085 delete wp.media.frame.lastMime; 2086 } 2087 Library.prototype.activate.apply( this, arguments ); 2088 } 2089 }); 2090 2091 module.exports = MediaLibrary; 2092 2093 2094 /***/ }), 2095 /* 41 */ 2096 /***/ (function(module, exports) { 2097 2098 /** 2099 * wp.media.controller.Embed 2100 * 2101 * A state for embedding media from a URL. 2102 * 2103 * @class 2104 * @augments wp.media.controller.State 2105 * @augments Backbone.Model 2106 * 2107 * @param {object} attributes The attributes hash passed to the state. 2108 * @param {string} [attributes.id=embed] Unique identifier. 2109 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. 2110 * @param {string} [attributes.content=embed] Initial mode for the content region. 2111 * @param {string} [attributes.menu=default] Initial mode for the menu region. 2112 * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. 2113 * @param {string} [attributes.menu=false] Initial mode for the menu region. 2114 * @param {int} [attributes.priority=120] The priority for the state link in the media menu. 2115 * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. 2116 * @param {string} [attributes.url] The embed URL. 2117 * @param {object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. 2118 */ 2119 var l10n = wp.media.view.l10n, 2120 $ = Backbone.$, 2121 Embed; 2122 2123 Embed = wp.media.controller.State.extend({ 2124 defaults: { 2125 id: 'embed', 2126 title: l10n.insertFromUrlTitle, 2127 content: 'embed', 2128 menu: 'default', 2129 toolbar: 'main-embed', 2130 priority: 120, 2131 type: 'link', 2132 url: '', 2133 metadata: {} 2134 }, 2135 2136 // The amount of time used when debouncing the scan. 2137 sensitivity: 400, 2138 2139 initialize: function(options) { 2140 this.metadata = options.metadata; 2141 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity ); 2142 this.props = new Backbone.Model( this.metadata || { url: '' }); 2143 this.props.on( 'change:url', this.debouncedScan, this ); 2144 this.props.on( 'change:url', this.refresh, this ); 2145 this.on( 'scan', this.scanImage, this ); 2146 }, 2147 2148 /** 2149 * Trigger a scan of the embedded URL's content for metadata required to embed. 2150 * 2151 * @fires wp.media.controller.Embed#scan 2152 */ 2153 scan: function() { 2154 var scanners, 2155 embed = this, 2156 attributes = { 2157 type: 'link', 2158 scanners: [] 2159 }; 2160 2161 // Scan is triggered with the list of `attributes` to set on the 2162 // state, useful for the 'type' attribute and 'scanners' attribute, 2163 // an array of promise objects for asynchronous scan operations. 2164 if ( this.props.get('url') ) { 2165 this.trigger( 'scan', attributes ); 2166 } 2167 2168 if ( attributes.scanners.length ) { 2169 scanners = attributes.scanners = $.when.apply( $, attributes.scanners ); 2170 scanners.always( function() { 2171 if ( embed.get('scanners') === scanners ) { 2172 embed.set( 'loading', false ); 2173 } 2174 }); 2175 } else { 2176 attributes.scanners = null; 2177 } 2178 2179 attributes.loading = !! attributes.scanners; 2180 this.set( attributes ); 2181 }, 2182 /** 2183 * Try scanning the embed as an image to discover its dimensions. 2184 * 2185 * @param {Object} attributes 2186 */ 2187 scanImage: function( attributes ) { 2188 var frame = this.frame, 2189 state = this, 2190 url = this.props.get('url'), 2191 image = new Image(), 2192 deferred = $.Deferred(); 2193 2194 attributes.scanners.push( deferred.promise() ); 2195 2196 // Try to load the image and find its width/height. 2197 image.onload = function() { 2198 deferred.resolve(); 2199 2200 if ( state !== frame.state() || url !== state.props.get('url') ) { 2201 return; 2202 } 2203 2204 state.set({ 2205 type: 'image' 2206 }); 2207 2208 state.props.set({ 2209 width: image.width, 2210 height: image.height 2211 }); 2212 }; 2213 2214 image.onerror = deferred.reject; 2215 image.src = url; 2216 }, 2217 2218 refresh: function() { 2219 this.frame.toolbar.get().refresh(); 2220 }, 2221 2222 reset: function() { 2223 this.props.clear().set({ url: '' }); 2224 2225 if ( this.active ) { 2226 this.refresh(); 2227 } 2228 } 2229 }); 2230 2231 module.exports = Embed; 2232 2233 2234 /***/ }), 2235 /* 42 */ 2236 /***/ (function(module, exports) { 2237 2238 /** 2239 * wp.media.controller.Cropper 2240 * 2241 * A state for cropping an image. 2242 * 2243 * @class 2244 * @augments wp.media.controller.State 2245 * @augments Backbone.Model 2246 */ 2247 var l10n = wp.media.view.l10n, 2248 Cropper; 2249 2250 Cropper = wp.media.controller.State.extend({ 2251 defaults: { 2252 id: 'cropper', 2253 title: l10n.cropImage, 2254 // Region mode defaults. 2255 toolbar: 'crop', 2256 content: 'crop', 2257 router: false, 2258 2259 canSkipCrop: false 2260 }, 2261 2262 activate: function() { 2263 this.frame.on( 'content:create:crop', this.createCropContent, this ); 2264 this.frame.on( 'close', this.removeCropper, this ); 2265 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 2266 }, 2267 2268 deactivate: function() { 2269 this.frame.toolbar.mode('browse'); 2270 }, 2271 2272 createCropContent: function() { 2273 this.cropperView = new wp.media.view.Cropper({ 2274 controller: this, 2275 attachment: this.get('selection').first() 2276 }); 2277 this.cropperView.on('image-loaded', this.createCropToolbar, this); 2278 this.frame.content.set(this.cropperView); 2279 2280 }, 2281 removeCropper: function() { 2282 this.imgSelect.cancelSelection(); 2283 this.imgSelect.setOptions({remove: true}); 2284 this.imgSelect.update(); 2285 this.cropperView.remove(); 2286 }, 2287 createCropToolbar: function() { 2288 var canSkipCrop, toolbarOptions; 2289 2290 canSkipCrop = this.get('canSkipCrop') || false; 2291 2292 toolbarOptions = { 2293 controller: this.frame, 2294 items: { 2295 insert: { 2296 style: 'primary', 2297 text: l10n.cropImage, 2298 priority: 80, 2299 requires: { library: false, selection: false }, 2300 2301 click: function() { 2302 var controller = this.controller, 2303 selection; 2304 2305 selection = controller.state().get('selection').first(); 2306 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 2307 2308 this.$el.text(l10n.cropping); 2309 this.$el.attr('disabled', true); 2310 2311 controller.state().doCrop( selection ).done( function( croppedImage ) { 2312 controller.trigger('cropped', croppedImage ); 2313 controller.close(); 2314 }).fail( function() { 2315 controller.trigger('content:error:crop'); 2316 }); 2317 } 2318 } 2319 } 2320 }; 2321 2322 if ( canSkipCrop ) { 2323 _.extend( toolbarOptions.items, { 2324 skip: { 2325 style: 'secondary', 2326 text: l10n.skipCropping, 2327 priority: 70, 2328 requires: { library: false, selection: false }, 2329 click: function() { 2330 var selection = this.controller.state().get('selection').first(); 2331 this.controller.state().cropperView.remove(); 2332 this.controller.trigger('skippedcrop', selection); 2333 this.controller.close(); 2334 } 2335 } 2336 }); 2337 } 2338 2339 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 2340 }, 2341 2342 doCrop: function( attachment ) { 2343 return wp.ajax.post( 'custom-header-crop', { 2344 nonce: attachment.get('nonces').edit, 2345 id: attachment.get('id'), 2346 cropDetails: attachment.get('cropDetails') 2347 } ); 2348 } 2349 }); 2350 2351 module.exports = Cropper; 2352 2353 2354 /***/ }), 2355 /* 43 */ 2356 /***/ (function(module, exports) { 2357 2358 /** 2359 * wp.media.controller.CustomizeImageCropper 2360 * 2361 * A state for cropping an image. 2362 * 2363 * @class 2364 * @augments wp.media.controller.Cropper 2365 * @augments wp.media.controller.State 2366 * @augments Backbone.Model 2367 */ 2368 var Controller = wp.media.controller, 2369 CustomizeImageCropper; 2370 2371 CustomizeImageCropper = Controller.Cropper.extend({ 2372 doCrop: function( attachment ) { 2373 var cropDetails = attachment.get( 'cropDetails' ), 2374 control = this.get( 'control' ); 2375 2376 cropDetails.dst_width = control.params.width; 2377 cropDetails.dst_height = control.params.height; 2378 2379 return wp.ajax.post( 'crop-image', { 2380 wp_customize: 'on', 2381 nonce: attachment.get( 'nonces' ).edit, 2382 id: attachment.get( 'id' ), 2383 context: control.id, 2384 cropDetails: cropDetails 2385 } ); 2386 } 2387 }); 2388 2389 module.exports = CustomizeImageCropper; 2390 2391 2392 /***/ }), 2393 /* 44 */ 2394 /***/ (function(module, exports) { 2395 1673 },{}],15:[function(require,module,exports){ 2396 1674 /** 2397 1675 * wp.media.controller.SiteIconCropper … … 2442 1720 module.exports = SiteIconCropper; 2443 1721 2444 2445 /***/ }), 2446 /* 45 */ 2447 /***/ (function(module, exports) { 2448 1722 },{}],16:[function(require,module,exports){ 2449 1723 /** 2450 * wp.media.View 2451 * 2452 * The base view class for media. 2453 * 2454 * Undelegating events, removing events from the model, and 2455 * removing events from the controller mirror the code for 2456 * `Backbone.View.dispose` in Backbone 0.9.8 development. 2457 * 2458 * This behavior has since been removed, and should not be used 2459 * outside of the media manager. 1724 * wp.media.controller.StateMachine 1725 * 1726 * A state machine keeps track of state. It is in one state at a time, 1727 * and can change from one state to another. 1728 * 1729 * States are stored as models in a Backbone collection. 1730 * 1731 * @since 3.5.0 2460 1732 * 2461 1733 * @class 2462 * @augments wp.Backbone.View 2463 * @augments Backbone.View 1734 * @augments Backbone.Model 1735 * @mixin 1736 * @mixes Backbone.Events 1737 * 1738 * @param {Array} states 2464 1739 */ 2465 var View = wp.Backbone.View.extend({2466 constructor: function( options ) {2467 if ( options && options.controller ) {2468 this.controller = options.controller;2469 } 2470 wp.Backbone.View.apply( this, arguments ); 2471 }, 2472 /** 2473 * @todo The internal comment mentions this might have been a stop-gap 2474 * before Backbone 0.9.8 came out. Figure out if Backbone core takes2475 * care of this in Backbone.View now.1740 var StateMachine = function( states ) { 1741 // @todo This is dead code. The states collection gets created in media.view.Frame._createStates. 1742 this.states = new Backbone.Collection( states ); 1743 }; 1744 1745 // Use Backbone's self-propagating `extend` inheritance method. 1746 StateMachine.extend = Backbone.Model.extend; 1747 1748 _.extend( StateMachine.prototype, Backbone.Events, { 1749 /** 1750 * Fetch a state. 2476 1751 * 2477 * @returns {wp.media.View} Returns itself to allow chaining 2478 */ 2479 dispose: function() { 2480 // Undelegating events, removing events from the model, and 2481 // removing events from the controller mirror the code for 2482 // `Backbone.View.dispose` in Backbone 0.9.8 development. 2483 this.undelegateEvents(); 2484 2485 if ( this.model && this.model.off ) { 2486 this.model.off( null, null, this ); 2487 } 2488 2489 if ( this.collection && this.collection.off ) { 2490 this.collection.off( null, null, this ); 2491 } 2492 2493 // Unbind controller events. 2494 if ( this.controller && this.controller.off ) { 2495 this.controller.off( null, null, this ); 2496 } 1752 * If no `id` is provided, returns the active state. 1753 * 1754 * Implicitly creates states. 1755 * 1756 * Ensure that the `states` collection exists so the `StateMachine` 1757 * can be used as a mixin. 1758 * 1759 * @since 3.5.0 1760 * 1761 * @param {string} id 1762 * @returns {wp.media.controller.State} Returns a State model 1763 * from the StateMachine collection 1764 */ 1765 state: function( id ) { 1766 this.states = this.states || new Backbone.Collection(); 1767 1768 // Default to the active state. 1769 id = id || this._state; 1770 1771 if ( id && ! this.states.get( id ) ) { 1772 this.states.add({ id: id }); 1773 } 1774 return this.states.get( id ); 1775 }, 1776 1777 /** 1778 * Sets the active state. 1779 * 1780 * Bail if we're trying to select the current state, if we haven't 1781 * created the `states` collection, or are trying to select a state 1782 * that does not exist. 1783 * 1784 * @since 3.5.0 1785 * 1786 * @param {string} id 1787 * 1788 * @fires wp.media.controller.State#deactivate 1789 * @fires wp.media.controller.State#activate 1790 * 1791 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining 1792 */ 1793 setState: function( id ) { 1794 var previous = this.state(); 1795 1796 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 1797 return this; 1798 } 1799 1800 if ( previous ) { 1801 previous.trigger('deactivate'); 1802 this._lastState = previous.id; 1803 } 1804 1805 this._state = id; 1806 this.state().trigger('activate'); 2497 1807 2498 1808 return this; 2499 1809 }, 2500 /** 2501 * @returns {wp.media.View} Returns itself to allow chaining 2502 */ 2503 remove: function() { 2504 this.dispose(); 2505 /** 2506 * call 'remove' directly on the parent class 2507 */ 2508 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 1810 1811 /** 1812 * Returns the previous active state. 1813 * 1814 * Call the `state()` method with no parameters to retrieve the current 1815 * active state. 1816 * 1817 * @since 3.5.0 1818 * 1819 * @returns {wp.media.controller.State} Returns a State model 1820 * from the StateMachine collection 1821 */ 1822 lastState: function() { 1823 if ( this._lastState ) { 1824 return this.state( this._lastState ); 1825 } 2509 1826 } 2510 1827 }); 2511 1828 2512 module.exports = View; 2513 2514 2515 /***/ }), 2516 /* 46 */ 2517 /***/ (function(module, exports) { 2518 2519 /** 2520 * wp.media.view.Frame 2521 * 2522 * A frame is a composite view consisting of one or more regions and one or more 2523 * states. 2524 * 2525 * @see wp.media.controller.State 2526 * @see wp.media.controller.Region 2527 * 2528 * @class 2529 * @augments wp.media.View 2530 * @augments wp.Backbone.View 2531 * @augments Backbone.View 2532 * @mixes wp.media.controller.StateMachine 2533 */ 2534 var Frame = wp.media.View.extend({ 2535 initialize: function() { 2536 _.defaults( this.options, { 2537 mode: [ 'select' ] 2538 }); 2539 this._createRegions(); 2540 this._createStates(); 2541 this._createModes(); 2542 }, 2543 2544 _createRegions: function() { 2545 // Clone the regions array. 2546 this.regions = this.regions ? this.regions.slice() : []; 2547 2548 // Initialize regions. 2549 _.each( this.regions, function( region ) { 2550 this[ region ] = new wp.media.controller.Region({ 2551 view: this, 2552 id: region, 2553 selector: '.media-frame-' + region 2554 }); 2555 }, this ); 2556 }, 2557 /** 2558 * Create the frame's states. 2559 * 2560 * @see wp.media.controller.State 2561 * @see wp.media.controller.StateMachine 2562 * 2563 * @fires wp.media.controller.State#ready 2564 */ 2565 _createStates: function() { 2566 // Create the default `states` collection. 2567 this.states = new Backbone.Collection( null, { 2568 model: wp.media.controller.State 2569 }); 2570 2571 // Ensure states have a reference to the frame. 2572 this.states.on( 'add', function( model ) { 2573 model.frame = this; 2574 model.trigger('ready'); 2575 }, this ); 2576 2577 if ( this.options.states ) { 2578 this.states.add( this.options.states ); 2579 } 2580 }, 2581 2582 /** 2583 * A frame can be in a mode or multiple modes at one time. 2584 * 2585 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 2586 */ 2587 _createModes: function() { 2588 // Store active "modes" that the frame is in. Unrelated to region modes. 2589 this.activeModes = new Backbone.Collection(); 2590 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 2591 2592 _.each( this.options.mode, function( mode ) { 2593 this.activateMode( mode ); 2594 }, this ); 2595 }, 2596 /** 2597 * Reset all states on the frame to their defaults. 2598 * 2599 * @returns {wp.media.view.Frame} Returns itself to allow chaining 2600 */ 2601 reset: function() { 2602 this.states.invoke( 'trigger', 'reset' ); 2603 return this; 2604 }, 2605 /** 2606 * Map activeMode collection events to the frame. 2607 */ 2608 triggerModeEvents: function( model, collection, options ) { 2609 var collectionEvent, 2610 modeEventMap = { 2611 add: 'activate', 2612 remove: 'deactivate' 2613 }, 2614 eventToTrigger; 2615 // Probably a better way to do this. 2616 _.each( options, function( value, key ) { 2617 if ( value ) { 2618 collectionEvent = key; 2619 } 2620 } ); 2621 2622 if ( ! _.has( modeEventMap, collectionEvent ) ) { 2623 return; 2624 } 2625 2626 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 2627 this.trigger( eventToTrigger ); 2628 }, 2629 /** 2630 * Activate a mode on the frame. 2631 * 2632 * @param string mode Mode ID. 2633 * @returns {this} Returns itself to allow chaining. 2634 */ 2635 activateMode: function( mode ) { 2636 // Bail if the mode is already active. 2637 if ( this.isModeActive( mode ) ) { 2638 return; 2639 } 2640 this.activeModes.add( [ { id: mode } ] ); 2641 // Add a CSS class to the frame so elements can be styled for the mode. 2642 this.$el.addClass( 'mode-' + mode ); 2643 2644 return this; 2645 }, 2646 /** 2647 * Deactivate a mode on the frame. 2648 * 2649 * @param string mode Mode ID. 2650 * @returns {this} Returns itself to allow chaining. 2651 */ 2652 deactivateMode: function( mode ) { 2653 // Bail if the mode isn't active. 2654 if ( ! this.isModeActive( mode ) ) { 2655 return this; 2656 } 2657 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 2658 this.$el.removeClass( 'mode-' + mode ); 2659 /** 2660 * Frame mode deactivation event. 2661 * 2662 * @event this#{mode}:deactivate 2663 */ 2664 this.trigger( mode + ':deactivate' ); 2665 2666 return this; 2667 }, 2668 /** 2669 * Check if a mode is enabled on the frame. 2670 * 2671 * @param string mode Mode ID. 2672 * @return bool 2673 */ 2674 isModeActive: function( mode ) { 2675 return Boolean( this.activeModes.where( { id: mode } ).length ); 2676 } 2677 }); 2678 2679 // Make the `Frame` a `StateMachine`. 2680 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 2681 2682 module.exports = Frame; 2683 2684 2685 /***/ }), 2686 /* 47 */ 2687 /***/ (function(module, exports) { 2688 2689 /** 2690 * wp.media.view.MediaFrame 2691 * 2692 * The frame used to create the media modal. 2693 * 2694 * @class 2695 * @augments wp.media.view.Frame 2696 * @augments wp.media.View 2697 * @augments wp.Backbone.View 2698 * @augments Backbone.View 2699 * @mixes wp.media.controller.StateMachine 2700 */ 2701 var Frame = wp.media.view.Frame, 2702 $ = jQuery, 2703 MediaFrame; 2704 2705 MediaFrame = Frame.extend({ 2706 className: 'media-frame', 2707 template: wp.template('media-frame'), 2708 regions: ['menu','title','content','toolbar','router'], 2709 2710 events: { 2711 'click div.media-frame-title h1': 'toggleMenu' 2712 }, 2713 2714 /** 2715 * @global wp.Uploader 2716 */ 2717 initialize: function() { 2718 Frame.prototype.initialize.apply( this, arguments ); 2719 2720 _.defaults( this.options, { 2721 title: '', 2722 modal: true, 2723 uploader: true 2724 }); 2725 2726 // Ensure core UI is enabled. 2727 this.$el.addClass('wp-core-ui'); 2728 2729 // Initialize modal container view. 2730 if ( this.options.modal ) { 2731 this.modal = new wp.media.view.Modal({ 2732 controller: this, 2733 title: this.options.title 2734 }); 2735 2736 this.modal.content( this ); 2737 } 2738 2739 // Force the uploader off if the upload limit has been exceeded or 2740 // if the browser isn't supported. 2741 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 2742 this.options.uploader = false; 2743 } 2744 2745 // Initialize window-wide uploader. 2746 if ( this.options.uploader ) { 2747 this.uploader = new wp.media.view.UploaderWindow({ 2748 controller: this, 2749 uploader: { 2750 dropzone: this.modal ? this.modal.$el : this.$el, 2751 container: this.$el 2752 } 2753 }); 2754 this.views.set( '.media-frame-uploader', this.uploader ); 2755 } 2756 2757 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 2758 2759 // Bind default title creation. 2760 this.on( 'title:create:default', this.createTitle, this ); 2761 this.title.mode('default'); 2762 2763 this.on( 'title:render', function( view ) { 2764 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' ); 2765 }); 2766 2767 // Bind default menu. 2768 this.on( 'menu:create:default', this.createMenu, this ); 2769 }, 2770 /** 2771 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 2772 */ 2773 render: function() { 2774 // Activate the default state if no active state exists. 2775 if ( ! this.state() && this.options.state ) { 2776 this.setState( this.options.state ); 2777 } 2778 /** 2779 * call 'render' directly on the parent class 2780 */ 2781 return Frame.prototype.render.apply( this, arguments ); 2782 }, 2783 /** 2784 * @param {Object} title 2785 * @this wp.media.controller.Region 2786 */ 2787 createTitle: function( title ) { 2788 title.view = new wp.media.View({ 2789 controller: this, 2790 tagName: 'h1' 2791 }); 2792 }, 2793 /** 2794 * @param {Object} menu 2795 * @this wp.media.controller.Region 2796 */ 2797 createMenu: function( menu ) { 2798 menu.view = new wp.media.view.Menu({ 2799 controller: this 2800 }); 2801 }, 2802 2803 toggleMenu: function() { 2804 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 2805 }, 2806 2807 /** 2808 * @param {Object} toolbar 2809 * @this wp.media.controller.Region 2810 */ 2811 createToolbar: function( toolbar ) { 2812 toolbar.view = new wp.media.view.Toolbar({ 2813 controller: this 2814 }); 2815 }, 2816 /** 2817 * @param {Object} router 2818 * @this wp.media.controller.Region 2819 */ 2820 createRouter: function( router ) { 2821 router.view = new wp.media.view.Router({ 2822 controller: this 2823 }); 2824 }, 2825 /** 2826 * @param {Object} options 2827 */ 2828 createIframeStates: function( options ) { 2829 var settings = wp.media.view.settings, 2830 tabs = settings.tabs, 2831 tabUrl = settings.tabUrl, 2832 $postId; 2833 2834 if ( ! tabs || ! tabUrl ) { 2835 return; 2836 } 2837 2838 // Add the post ID to the tab URL if it exists. 2839 $postId = $('#post_ID'); 2840 if ( $postId.length ) { 2841 tabUrl += '&post_id=' + $postId.val(); 2842 } 2843 2844 // Generate the tab states. 2845 _.each( tabs, function( title, id ) { 2846 this.state( 'iframe:' + id ).set( _.defaults({ 2847 tab: id, 2848 src: tabUrl + '&tab=' + id, 2849 title: title, 2850 content: 'iframe', 2851 menu: 'default' 2852 }, options ) ); 2853 }, this ); 2854 2855 this.on( 'content:create:iframe', this.iframeContent, this ); 2856 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 2857 this.on( 'menu:render:default', this.iframeMenu, this ); 2858 this.on( 'open', this.hijackThickbox, this ); 2859 this.on( 'close', this.restoreThickbox, this ); 2860 }, 2861 2862 /** 2863 * @param {Object} content 2864 * @this wp.media.controller.Region 2865 */ 2866 iframeContent: function( content ) { 2867 this.$el.addClass('hide-toolbar'); 2868 content.view = new wp.media.view.Iframe({ 2869 controller: this 2870 }); 2871 }, 2872 2873 iframeContentCleanup: function() { 2874 this.$el.removeClass('hide-toolbar'); 2875 }, 2876 2877 iframeMenu: function( view ) { 2878 var views = {}; 2879 2880 if ( ! view ) { 2881 return; 2882 } 2883 2884 _.each( wp.media.view.settings.tabs, function( title, id ) { 2885 views[ 'iframe:' + id ] = { 2886 text: this.state( 'iframe:' + id ).get('title'), 2887 priority: 200 2888 }; 2889 }, this ); 2890 2891 view.set( views ); 2892 }, 2893 2894 hijackThickbox: function() { 2895 var frame = this; 2896 2897 if ( ! window.tb_remove || this._tb_remove ) { 2898 return; 2899 } 2900 2901 this._tb_remove = window.tb_remove; 2902 window.tb_remove = function() { 2903 frame.close(); 2904 frame.reset(); 2905 frame.setState( frame.options.state ); 2906 frame._tb_remove.call( window ); 2907 }; 2908 }, 2909 2910 restoreThickbox: function() { 2911 if ( ! this._tb_remove ) { 2912 return; 2913 } 2914 2915 window.tb_remove = this._tb_remove; 2916 delete this._tb_remove; 2917 } 2918 }); 2919 2920 // Map some of the modal's methods to the frame. 2921 _.each(['open','close','attach','detach','escape'], function( method ) { 2922 /** 2923 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 2924 */ 2925 MediaFrame.prototype[ method ] = function() { 2926 if ( this.modal ) { 2927 this.modal[ method ].apply( this.modal, arguments ); 2928 } 1829 // Map all event binding and triggering on a StateMachine to its `states` collection. 1830 _.each([ 'on', 'off', 'trigger' ], function( method ) { 1831 /** 1832 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 1833 */ 1834 StateMachine.prototype[ method ] = function() { 1835 // Ensure that the `states` collection exists so the `StateMachine` 1836 // can be used as a mixin. 1837 this.states = this.states || new Backbone.Collection(); 1838 // Forward the method to the `states` collection. 1839 this.states[ method ].apply( this.states, arguments ); 2929 1840 return this; 2930 1841 }; 2931 1842 }); 2932 1843 2933 module.exports = MediaFrame; 2934 2935 2936 /***/ }), 2937 /* 48 */ 2938 /***/ (function(module, exports) { 2939 1844 module.exports = StateMachine; 1845 1846 },{}],17:[function(require,module,exports){ 2940 1847 /** 2941 * wp.media.view.MediaFrame.Select 2942 * 2943 * A frame for selecting an item or items from the media library. 1848 * wp.media.controller.State 1849 * 1850 * A state is a step in a workflow that when set will trigger the controllers 1851 * for the regions to be updated as specified in the frame. 1852 * 1853 * A state has an event-driven lifecycle: 1854 * 1855 * 'ready' triggers when a state is added to a state machine's collection. 1856 * 'activate' triggers when a state is activated by a state machine. 1857 * 'deactivate' triggers when a state is deactivated by a state machine. 1858 * 'reset' is not triggered automatically. It should be invoked by the 1859 * proper controller to reset the state to its default. 2944 1860 * 2945 1861 * @class 2946 * @augments wp.media.view.MediaFrame 2947 * @augments wp.media.view.Frame 2948 * @augments wp.media.View 2949 * @augments wp.Backbone.View 2950 * @augments Backbone.View 2951 * @mixes wp.media.controller.StateMachine 1862 * @augments Backbone.Model 2952 1863 */ 2953 2954 var MediaFrame = wp.media.view.MediaFrame, 2955 l10n = wp.media.view.l10n, 2956 Select; 2957 2958 Select = MediaFrame.extend({ 2959 initialize: function() { 2960 // Call 'initialize' directly on the parent class. 2961 MediaFrame.prototype.initialize.apply( this, arguments ); 2962 2963 _.defaults( this.options, { 2964 selection: [], 2965 library: {}, 2966 multiple: false, 2967 state: 'library' 2968 }); 2969 2970 this.createSelection(); 2971 this.createStates(); 2972 this.bindHandlers(); 2973 }, 2974 2975 /** 2976 * Attach a selection collection to the frame. 1864 var State = Backbone.Model.extend({ 1865 /** 1866 * Constructor. 2977 1867 * 2978 * A selection is a collection of attachments used for a specific purpose 2979 * by a media frame. e.g. Selecting an attachment (or many) to insert into 2980 * post content. 1868 * @since 3.5.0 1869 */ 1870 constructor: function() { 1871 this.on( 'activate', this._preActivate, this ); 1872 this.on( 'activate', this.activate, this ); 1873 this.on( 'activate', this._postActivate, this ); 1874 this.on( 'deactivate', this._deactivate, this ); 1875 this.on( 'deactivate', this.deactivate, this ); 1876 this.on( 'reset', this.reset, this ); 1877 this.on( 'ready', this._ready, this ); 1878 this.on( 'ready', this.ready, this ); 1879 /** 1880 * Call parent constructor with passed arguments 1881 */ 1882 Backbone.Model.apply( this, arguments ); 1883 this.on( 'change:menu', this._updateMenu, this ); 1884 }, 1885 /** 1886 * Ready event callback. 2981 1887 * 2982 * @see media.model.Selection 2983 */ 2984 createSelection: function() { 2985 var selection = this.options.selection; 2986 2987 if ( ! (selection instanceof wp.media.model.Selection) ) { 2988 this.options.selection = new wp.media.model.Selection( selection, { 2989 multiple: this.options.multiple 2990 }); 2991 } 2992 2993 this._selection = { 2994 attachments: new wp.media.model.Attachments(), 2995 difference: [] 2996 }; 2997 }, 2998 2999 /** 3000 * Create the default states on the frame. 3001 */ 3002 createStates: function() { 3003 var options = this.options; 3004 3005 if ( this.options.states ) { 1888 * @abstract 1889 * @since 3.5.0 1890 */ 1891 ready: function() {}, 1892 1893 /** 1894 * Activate event callback. 1895 * 1896 * @abstract 1897 * @since 3.5.0 1898 */ 1899 activate: function() {}, 1900 1901 /** 1902 * Deactivate event callback. 1903 * 1904 * @abstract 1905 * @since 3.5.0 1906 */ 1907 deactivate: function() {}, 1908 1909 /** 1910 * Reset event callback. 1911 * 1912 * @abstract 1913 * @since 3.5.0 1914 */ 1915 reset: function() {}, 1916 1917 /** 1918 * @access private 1919 * @since 3.5.0 1920 */ 1921 _ready: function() { 1922 this._updateMenu(); 1923 }, 1924 1925 /** 1926 * @access private 1927 * @since 3.5.0 1928 */ 1929 _preActivate: function() { 1930 this.active = true; 1931 }, 1932 1933 /** 1934 * @access private 1935 * @since 3.5.0 1936 */ 1937 _postActivate: function() { 1938 this.on( 'change:menu', this._menu, this ); 1939 this.on( 'change:titleMode', this._title, this ); 1940 this.on( 'change:content', this._content, this ); 1941 this.on( 'change:toolbar', this._toolbar, this ); 1942 1943 this.frame.on( 'title:render:default', this._renderTitle, this ); 1944 1945 this._title(); 1946 this._menu(); 1947 this._toolbar(); 1948 this._content(); 1949 this._router(); 1950 }, 1951 1952 /** 1953 * @access private 1954 * @since 3.5.0 1955 */ 1956 _deactivate: function() { 1957 this.active = false; 1958 1959 this.frame.off( 'title:render:default', this._renderTitle, this ); 1960 1961 this.off( 'change:menu', this._menu, this ); 1962 this.off( 'change:titleMode', this._title, this ); 1963 this.off( 'change:content', this._content, this ); 1964 this.off( 'change:toolbar', this._toolbar, this ); 1965 }, 1966 1967 /** 1968 * @access private 1969 * @since 3.5.0 1970 */ 1971 _title: function() { 1972 this.frame.title.render( this.get('titleMode') || 'default' ); 1973 }, 1974 1975 /** 1976 * @access private 1977 * @since 3.5.0 1978 */ 1979 _renderTitle: function( view ) { 1980 view.$el.text( this.get('title') || '' ); 1981 }, 1982 1983 /** 1984 * @access private 1985 * @since 3.5.0 1986 */ 1987 _router: function() { 1988 var router = this.frame.router, 1989 mode = this.get('router'), 1990 view; 1991 1992 this.frame.$el.toggleClass( 'hide-router', ! mode ); 1993 if ( ! mode ) { 3006 1994 return; 3007 1995 } 3008 1996 3009 // Add the default states. 3010 this.states.add([ 3011 // Main states. 3012 new wp.media.controller.Library({ 3013 library: wp.media.query( options.library ), 3014 multiple: options.multiple, 3015 title: options.title, 3016 priority: 20 3017 }) 3018 ]); 3019 }, 3020 3021 /** 3022 * Bind region mode event callbacks. 1997 this.frame.router.render( mode ); 1998 1999 view = router.get(); 2000 if ( view && view.select ) { 2001 view.select( this.frame.content.mode() ); 2002 } 2003 }, 2004 2005 /** 2006 * @access private 2007 * @since 3.5.0 2008 */ 2009 _menu: function() { 2010 var menu = this.frame.menu, 2011 mode = this.get('menu'), 2012 view; 2013 2014 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 2015 if ( ! mode ) { 2016 return; 2017 } 2018 2019 menu.mode( mode ); 2020 2021 view = menu.get(); 2022 if ( view && view.select ) { 2023 view.select( this.id ); 2024 } 2025 }, 2026 2027 /** 2028 * @access private 2029 * @since 3.5.0 2030 */ 2031 _updateMenu: function() { 2032 var previous = this.previous('menu'), 2033 menu = this.get('menu'); 2034 2035 if ( previous ) { 2036 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 2037 } 2038 2039 if ( menu ) { 2040 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 2041 } 2042 }, 2043 2044 /** 2045 * Create a view in the media menu for the state. 3023 2046 * 3024 * @see media.controller.Region.render 3025 */ 3026 bindHandlers: function() { 3027 this.on( 'router:create:browse', this.createRouter, this ); 3028 this.on( 'router:render:browse', this.browseRouter, this ); 3029 this.on( 'content:create:browse', this.browseContent, this ); 3030 this.on( 'content:render:upload', this.uploadContent, this ); 3031 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 3032 }, 3033 3034 /** 3035 * Render callback for the router region in the `browse` mode. 2047 * @access private 2048 * @since 3.5.0 3036 2049 * 3037 * @param {wp.media.view.Router} routerView 3038 */ 3039 browseRouter: function( routerView ) { 3040 routerView.set({ 3041 upload: { 3042 text: l10n.uploadFilesTitle, 3043 priority: 20 3044 }, 3045 browse: { 3046 text: l10n.mediaLibraryTitle, 3047 priority: 40 2050 * @param {media.view.Menu} view The menu view. 2051 */ 2052 _renderMenu: function( view ) { 2053 var menuItem = this.get('menuItem'), 2054 title = this.get('title'), 2055 priority = this.get('priority'); 2056 2057 if ( ! menuItem && title ) { 2058 menuItem = { text: title }; 2059 2060 if ( priority ) { 2061 menuItem.priority = priority; 3048 2062 } 3049 }); 3050 }, 3051 3052 /** 3053 * Render callback for the content region in the `browse` mode. 3054 * 3055 * @param {wp.media.controller.Region} contentRegion 3056 */ 3057 browseContent: function( contentRegion ) { 3058 var state = this.state(); 3059 3060 this.$el.removeClass('hide-toolbar'); 3061 3062 // Browse our library of attachments. 3063 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 3064 controller: this, 3065 collection: state.get('library'), 3066 selection: state.get('selection'), 3067 model: state, 3068 sortable: state.get('sortable'), 3069 search: state.get('searchable'), 3070 filters: state.get('filterable'), 3071 date: state.get('date'), 3072 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 3073 dragInfo: state.get('dragInfo'), 3074 3075 idealColumnWidth: state.get('idealColumnWidth'), 3076 suggestedWidth: state.get('suggestedWidth'), 3077 suggestedHeight: state.get('suggestedHeight'), 3078 3079 AttachmentView: state.get('AttachmentView') 3080 }); 3081 }, 3082 3083 /** 3084 * Render callback for the content region in the `upload` mode. 3085 */ 3086 uploadContent: function() { 3087 this.$el.removeClass( 'hide-toolbar' ); 3088 this.content.set( new wp.media.view.UploaderInline({ 3089 controller: this 3090 }) ); 3091 }, 3092 3093 /** 3094 * Toolbars 3095 * 3096 * @param {Object} toolbar 3097 * @param {Object} [options={}] 3098 * @this wp.media.controller.Region 3099 */ 3100 createSelectToolbar: function( toolbar, options ) { 3101 options = options || this.options.button || {}; 3102 options.controller = this; 3103 3104 toolbar.view = new wp.media.view.Toolbar.Select( options ); 2063 } 2064 2065 if ( ! menuItem ) { 2066 return; 2067 } 2068 2069 view.set( this.id, menuItem ); 3105 2070 } 3106 2071 }); 3107 2072 3108 module.exports = Select; 3109 3110 3111 /***/ }), 3112 /* 49 */ 3113 /***/ (function(module, exports) { 3114 2073 _.each(['toolbar','content'], function( region ) { 2074 /** 2075 * @access private 2076 */ 2077 State.prototype[ '_' + region ] = function() { 2078 var mode = this.get( region ); 2079 if ( mode ) { 2080 this.frame[ region ].render( mode ); 2081 } 2082 }; 2083 }); 2084 2085 module.exports = State; 2086 2087 },{}],18:[function(require,module,exports){ 3115 2088 /** 3116 * wp.media.view.MediaFrame.Post 3117 * 3118 * The frame for manipulating media on the Edit Post page. 3119 * 3120 * @class 3121 * @augments wp.media.view.MediaFrame.Select 3122 * @augments wp.media.view.MediaFrame 3123 * @augments wp.media.view.Frame 3124 * @augments wp.media.View 3125 * @augments wp.Backbone.View 3126 * @augments Backbone.View 3127 * @mixes wp.media.controller.StateMachine 2089 * wp.media.selectionSync 2090 * 2091 * Sync an attachments selection in a state with another state. 2092 * 2093 * Allows for selecting multiple images in the Insert Media workflow, and then 2094 * switching to the Insert Gallery workflow while preserving the attachments selection. 2095 * 2096 * @mixin 3128 2097 */ 3129 var Select = wp.media.view.MediaFrame.Select, 3130 Library = wp.media.controller.Library, 3131 l10n = wp.media.view.l10n, 3132 Post; 3133 3134 Post = Select.extend({ 3135 initialize: function() { 3136 this.counts = { 3137 audio: { 3138 count: wp.media.view.settings.attachmentCounts.audio, 3139 state: 'playlist' 3140 }, 3141 video: { 3142 count: wp.media.view.settings.attachmentCounts.video, 3143 state: 'video-playlist' 3144 } 3145 }; 3146 3147 _.defaults( this.options, { 3148 multiple: true, 3149 editing: false, 3150 state: 'insert', 3151 metadata: {} 3152 }); 3153 3154 // Call 'initialize' directly on the parent class. 3155 Select.prototype.initialize.apply( this, arguments ); 3156 this.createIframeStates(); 3157 3158 }, 3159 3160 /** 3161 * Create the default states. 3162 */ 3163 createStates: function() { 3164 var options = this.options; 3165 3166 this.states.add([ 3167 // Main states. 3168 new Library({ 3169 id: 'insert', 3170 title: l10n.insertMediaTitle, 3171 priority: 20, 3172 toolbar: 'main-insert', 3173 filterable: 'all', 3174 library: wp.media.query( options.library ), 3175 multiple: options.multiple ? 'reset' : false, 3176 editable: true, 3177 3178 // If the user isn't allowed to edit fields, 3179 // can they still edit it locally? 3180 allowLocalEdits: true, 3181 3182 // Show the attachment display settings. 3183 displaySettings: true, 3184 // Update user settings when users adjust the 3185 // attachment display settings. 3186 displayUserSettings: true 3187 }), 3188 3189 new Library({ 3190 id: 'gallery', 3191 title: l10n.createGalleryTitle, 3192 priority: 40, 3193 toolbar: 'main-gallery', 3194 filterable: 'uploaded', 3195 multiple: 'add', 3196 editable: false, 3197 3198 library: wp.media.query( _.defaults({ 3199 type: 'image' 3200 }, options.library ) ) 3201 }), 3202 3203 // Embed states. 3204 new wp.media.controller.Embed( { metadata: options.metadata } ), 3205 3206 new wp.media.controller.EditImage( { model: options.editImage } ), 3207 3208 // Gallery states. 3209 new wp.media.controller.GalleryEdit({ 3210 library: options.selection, 3211 editing: options.editing, 3212 menu: 'gallery' 3213 }), 3214 3215 new wp.media.controller.GalleryAdd(), 3216 3217 new Library({ 3218 id: 'playlist', 3219 title: l10n.createPlaylistTitle, 3220 priority: 60, 3221 toolbar: 'main-playlist', 3222 filterable: 'uploaded', 3223 multiple: 'add', 3224 editable: false, 3225 3226 library: wp.media.query( _.defaults({ 3227 type: 'audio' 3228 }, options.library ) ) 3229 }), 3230 3231 // Playlist states. 3232 new wp.media.controller.CollectionEdit({ 3233 type: 'audio', 3234 collectionType: 'playlist', 3235 title: l10n.editPlaylistTitle, 3236 SettingsView: wp.media.view.Settings.Playlist, 3237 library: options.selection, 3238 editing: options.editing, 3239 menu: 'playlist', 3240 dragInfoText: l10n.playlistDragInfo, 3241 dragInfo: false 3242 }), 3243 3244 new wp.media.controller.CollectionAdd({ 3245 type: 'audio', 3246 collectionType: 'playlist', 3247 title: l10n.addToPlaylistTitle 3248 }), 3249 3250 new Library({ 3251 id: 'video-playlist', 3252 title: l10n.createVideoPlaylistTitle, 3253 priority: 60, 3254 toolbar: 'main-video-playlist', 3255 filterable: 'uploaded', 3256 multiple: 'add', 3257 editable: false, 3258 3259 library: wp.media.query( _.defaults({ 3260 type: 'video' 3261 }, options.library ) ) 3262 }), 3263 3264 new wp.media.controller.CollectionEdit({ 3265 type: 'video', 3266 collectionType: 'playlist', 3267 title: l10n.editVideoPlaylistTitle, 3268 SettingsView: wp.media.view.Settings.Playlist, 3269 library: options.selection, 3270 editing: options.editing, 3271 menu: 'video-playlist', 3272 dragInfoText: l10n.videoPlaylistDragInfo, 3273 dragInfo: false 3274 }), 3275 3276 new wp.media.controller.CollectionAdd({ 3277 type: 'video', 3278 collectionType: 'playlist', 3279 title: l10n.addToVideoPlaylistTitle 3280 }) 3281 ]); 3282 3283 if ( wp.media.view.settings.post.featuredImageId ) { 3284 this.states.add( new wp.media.controller.FeaturedImage() ); 3285 } 3286 }, 3287 3288 bindHandlers: function() { 3289 var handlers, checkCounts; 3290 3291 Select.prototype.bindHandlers.apply( this, arguments ); 3292 3293 this.on( 'activate', this.activate, this ); 3294 3295 // Only bother checking media type counts if one of the counts is zero 3296 checkCounts = _.find( this.counts, function( type ) { 3297 return type.count === 0; 3298 } ); 3299 3300 if ( typeof checkCounts !== 'undefined' ) { 3301 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 3302 } 3303 3304 this.on( 'menu:create:gallery', this.createMenu, this ); 3305 this.on( 'menu:create:playlist', this.createMenu, this ); 3306 this.on( 'menu:create:video-playlist', this.createMenu, this ); 3307 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 3308 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 3309 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 3310 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 3311 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 3312 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 3313 3314 handlers = { 3315 menu: { 3316 'default': 'mainMenu', 3317 'gallery': 'galleryMenu', 3318 'playlist': 'playlistMenu', 3319 'video-playlist': 'videoPlaylistMenu' 3320 }, 3321 3322 content: { 3323 'embed': 'embedContent', 3324 'edit-image': 'editImageContent', 3325 'edit-selection': 'editSelectionContent' 3326 }, 3327 3328 toolbar: { 3329 'main-insert': 'mainInsertToolbar', 3330 'main-gallery': 'mainGalleryToolbar', 3331 'gallery-edit': 'galleryEditToolbar', 3332 'gallery-add': 'galleryAddToolbar', 3333 'main-playlist': 'mainPlaylistToolbar', 3334 'playlist-edit': 'playlistEditToolbar', 3335 'playlist-add': 'playlistAddToolbar', 3336 'main-video-playlist': 'mainVideoPlaylistToolbar', 3337 'video-playlist-edit': 'videoPlaylistEditToolbar', 3338 'video-playlist-add': 'videoPlaylistAddToolbar' 3339 } 3340 }; 3341 3342 _.each( handlers, function( regionHandlers, region ) { 3343 _.each( regionHandlers, function( callback, handler ) { 3344 this.on( region + ':render:' + handler, this[ callback ], this ); 3345 }, this ); 3346 }, this ); 3347 }, 3348 3349 activate: function() { 3350 // Hide menu items for states tied to particular media types if there are no items 3351 _.each( this.counts, function( type ) { 3352 if ( type.count < 1 ) { 3353 this.menuItemVisibility( type.state, 'hide' ); 3354 } 3355 }, this ); 3356 }, 3357 3358 mediaTypeCounts: function( model, attr ) { 3359 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 3360 this.counts[ attr ].count++; 3361 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 3362 } 3363 }, 3364 3365 // Menus 3366 /** 3367 * @param {wp.Backbone.View} view 3368 */ 3369 mainMenu: function( view ) { 3370 view.set({ 3371 'library-separator': new wp.media.View({ 3372 className: 'separator', 3373 priority: 100 3374 }) 3375 }); 3376 }, 3377 3378 menuItemVisibility: function( state, visibility ) { 3379 var menu = this.menu.get(); 3380 if ( visibility === 'hide' ) { 3381 menu.hide( state ); 3382 } else if ( visibility === 'show' ) { 3383 menu.show( state ); 3384 } 3385 }, 3386 /** 3387 * @param {wp.Backbone.View} view 3388 */ 3389 galleryMenu: function( view ) { 3390 var lastState = this.lastState(), 3391 previous = lastState && lastState.id, 3392 frame = this; 3393 3394 view.set({ 3395 cancel: { 3396 text: l10n.cancelGalleryTitle, 3397 priority: 20, 3398 click: function() { 3399 if ( previous ) { 3400 frame.setState( previous ); 3401 } else { 3402 frame.close(); 3403 } 3404 3405 // Keep focus inside media modal 3406 // after canceling a gallery 3407 this.controller.modal.focusManager.focus(); 3408 } 3409 }, 3410 separateCancel: new wp.media.View({ 3411 className: 'separator', 3412 priority: 40 3413 }) 3414 }); 3415 }, 3416 3417 playlistMenu: function( view ) { 3418 var lastState = this.lastState(), 3419 previous = lastState && lastState.id, 3420 frame = this; 3421 3422 view.set({ 3423 cancel: { 3424 text: l10n.cancelPlaylistTitle, 3425 priority: 20, 3426 click: function() { 3427 if ( previous ) { 3428 frame.setState( previous ); 3429 } else { 3430 frame.close(); 3431 } 3432 } 3433 }, 3434 separateCancel: new wp.media.View({ 3435 className: 'separator', 3436 priority: 40 3437 }) 3438 }); 3439 }, 3440 3441 videoPlaylistMenu: function( view ) { 3442 var lastState = this.lastState(), 3443 previous = lastState && lastState.id, 3444 frame = this; 3445 3446 view.set({ 3447 cancel: { 3448 text: l10n.cancelVideoPlaylistTitle, 3449 priority: 20, 3450 click: function() { 3451 if ( previous ) { 3452 frame.setState( previous ); 3453 } else { 3454 frame.close(); 3455 } 3456 } 3457 }, 3458 separateCancel: new wp.media.View({ 3459 className: 'separator', 3460 priority: 40 3461 }) 3462 }); 3463 }, 3464 3465 // Content 3466 embedContent: function() { 3467 var view = new wp.media.view.Embed({ 3468 controller: this, 3469 model: this.state() 3470 }).render(); 3471 3472 this.content.set( view ); 3473 3474 if ( ! wp.media.isTouchDevice ) { 3475 view.url.focus(); 3476 } 3477 }, 3478 3479 editSelectionContent: function() { 3480 var state = this.state(), 3481 selection = state.get('selection'), 3482 view; 3483 3484 view = new wp.media.view.AttachmentsBrowser({ 3485 controller: this, 3486 collection: selection, 3487 selection: selection, 3488 model: state, 3489 sortable: true, 3490 search: false, 3491 date: false, 3492 dragInfo: true, 3493 3494 AttachmentView: wp.media.view.Attachments.EditSelection 3495 }).render(); 3496 3497 view.toolbar.set( 'backToLibrary', { 3498 text: l10n.returnToLibrary, 3499 priority: -100, 3500 3501 click: function() { 3502 this.controller.content.mode('browse'); 3503 } 3504 }); 3505 3506 // Browse our library of attachments. 3507 this.content.set( view ); 3508 3509 // Trigger the controller to set focus 3510 this.trigger( 'edit:selection', this ); 3511 }, 3512 3513 editImageContent: function() { 3514 var image = this.state().get('image'), 3515 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 3516 3517 this.content.set( view ); 3518 3519 // after creating the wrapper view, load the actual editor via an ajax call 3520 view.loadEditor(); 3521 3522 }, 3523 3524 // Toolbars 3525 3526 /** 3527 * @param {wp.Backbone.View} view 3528 */ 3529 selectionStatusToolbar: function( view ) { 3530 var editable = this.state().get('editable'); 3531 3532 view.set( 'selection', new wp.media.view.Selection({ 3533 controller: this, 3534 collection: this.state().get('selection'), 3535 priority: -40, 3536 3537 // If the selection is editable, pass the callback to 3538 // switch the content mode. 3539 editable: editable && function() { 3540 this.controller.content.mode('edit-selection'); 3541 } 3542 }).render() ); 3543 }, 3544 3545 /** 3546 * @param {wp.Backbone.View} view 3547 */ 3548 mainInsertToolbar: function( view ) { 3549 var controller = this; 3550 3551 this.selectionStatusToolbar( view ); 3552 3553 view.set( 'insert', { 3554 style: 'primary', 3555 priority: 80, 3556 text: l10n.insertIntoPost, 3557 requires: { selection: true }, 3558 3559 /** 3560 * @fires wp.media.controller.State#insert 3561 */ 3562 click: function() { 3563 var state = controller.state(), 3564 selection = state.get('selection'); 3565 3566 controller.close(); 3567 state.trigger( 'insert', selection ).reset(); 3568 } 3569 }); 3570 }, 3571 3572 /** 3573 * @param {wp.Backbone.View} view 3574 */ 3575 mainGalleryToolbar: function( view ) { 3576 var controller = this; 3577 3578 this.selectionStatusToolbar( view ); 3579 3580 view.set( 'gallery', { 3581 style: 'primary', 3582 text: l10n.createNewGallery, 3583 priority: 60, 3584 requires: { selection: true }, 3585 3586 click: function() { 3587 var selection = controller.state().get('selection'), 3588 edit = controller.state('gallery-edit'), 3589 models = selection.where({ type: 'image' }); 3590 3591 edit.set( 'library', new wp.media.model.Selection( models, { 3592 props: selection.props.toJSON(), 3593 multiple: true 3594 }) ); 3595 3596 this.controller.setState('gallery-edit'); 3597 3598 // Keep focus inside media modal 3599 // after jumping to gallery view 3600 this.controller.modal.focusManager.focus(); 3601 } 3602 }); 3603 }, 3604 3605 mainPlaylistToolbar: function( view ) { 3606 var controller = this; 3607 3608 this.selectionStatusToolbar( view ); 3609 3610 view.set( 'playlist', { 3611 style: 'primary', 3612 text: l10n.createNewPlaylist, 3613 priority: 100, 3614 requires: { selection: true }, 3615 3616 click: function() { 3617 var selection = controller.state().get('selection'), 3618 edit = controller.state('playlist-edit'), 3619 models = selection.where({ type: 'audio' }); 3620 3621 edit.set( 'library', new wp.media.model.Selection( models, { 3622 props: selection.props.toJSON(), 3623 multiple: true 3624 }) ); 3625 3626 this.controller.setState('playlist-edit'); 3627 3628 // Keep focus inside media modal 3629 // after jumping to playlist view 3630 this.controller.modal.focusManager.focus(); 3631 } 3632 }); 3633 }, 3634 3635 mainVideoPlaylistToolbar: function( view ) { 3636 var controller = this; 3637 3638 this.selectionStatusToolbar( view ); 3639 3640 view.set( 'video-playlist', { 3641 style: 'primary', 3642 text: l10n.createNewVideoPlaylist, 3643 priority: 100, 3644 requires: { selection: true }, 3645 3646 click: function() { 3647 var selection = controller.state().get('selection'), 3648 edit = controller.state('video-playlist-edit'), 3649 models = selection.where({ type: 'video' }); 3650 3651 edit.set( 'library', new wp.media.model.Selection( models, { 3652 props: selection.props.toJSON(), 3653 multiple: true 3654 }) ); 3655 3656 this.controller.setState('video-playlist-edit'); 3657 3658 // Keep focus inside media modal 3659 // after jumping to video playlist view 3660 this.controller.modal.focusManager.focus(); 3661 } 3662 }); 3663 }, 3664 3665 featuredImageToolbar: function( toolbar ) { 3666 this.createSelectToolbar( toolbar, { 3667 text: l10n.setFeaturedImage, 3668 state: this.options.state 3669 }); 3670 }, 3671 3672 mainEmbedToolbar: function( toolbar ) { 3673 toolbar.view = new wp.media.view.Toolbar.Embed({ 3674 controller: this 3675 }); 3676 }, 3677 3678 galleryEditToolbar: function() { 3679 var editing = this.state().get('editing'); 3680 this.toolbar.set( new wp.media.view.Toolbar({ 3681 controller: this, 3682 items: { 3683 insert: { 3684 style: 'primary', 3685 text: editing ? l10n.updateGallery : l10n.insertGallery, 3686 priority: 80, 3687 requires: { library: true }, 3688 3689 /** 3690 * @fires wp.media.controller.State#update 3691 */ 3692 click: function() { 3693 var controller = this.controller, 3694 state = controller.state(); 3695 3696 controller.close(); 3697 state.trigger( 'update', state.get('library') ); 3698 3699 // Restore and reset the default state. 3700 controller.setState( controller.options.state ); 3701 controller.reset(); 3702 } 3703 } 3704 } 3705 }) ); 3706 }, 3707 3708 galleryAddToolbar: function() { 3709 this.toolbar.set( new wp.media.view.Toolbar({ 3710 controller: this, 3711 items: { 3712 insert: { 3713 style: 'primary', 3714 text: l10n.addToGallery, 3715 priority: 80, 3716 requires: { selection: true }, 3717 3718 /** 3719 * @fires wp.media.controller.State#reset 3720 */ 3721 click: function() { 3722 var controller = this.controller, 3723 state = controller.state(), 3724 edit = controller.state('gallery-edit'); 3725 3726 edit.get('library').add( state.get('selection').models ); 3727 state.trigger('reset'); 3728 controller.setState('gallery-edit'); 3729 } 3730 } 3731 } 3732 }) ); 3733 }, 3734 3735 playlistEditToolbar: function() { 3736 var editing = this.state().get('editing'); 3737 this.toolbar.set( new wp.media.view.Toolbar({ 3738 controller: this, 3739 items: { 3740 insert: { 3741 style: 'primary', 3742 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 3743 priority: 80, 3744 requires: { library: true }, 3745 3746 /** 3747 * @fires wp.media.controller.State#update 3748 */ 3749 click: function() { 3750 var controller = this.controller, 3751 state = controller.state(); 3752 3753 controller.close(); 3754 state.trigger( 'update', state.get('library') ); 3755 3756 // Restore and reset the default state. 3757 controller.setState( controller.options.state ); 3758 controller.reset(); 3759 } 3760 } 3761 } 3762 }) ); 3763 }, 3764 3765 playlistAddToolbar: function() { 3766 this.toolbar.set( new wp.media.view.Toolbar({ 3767 controller: this, 3768 items: { 3769 insert: { 3770 style: 'primary', 3771 text: l10n.addToPlaylist, 3772 priority: 80, 3773 requires: { selection: true }, 3774 3775 /** 3776 * @fires wp.media.controller.State#reset 3777 */ 3778 click: function() { 3779 var controller = this.controller, 3780 state = controller.state(), 3781 edit = controller.state('playlist-edit'); 3782 3783 edit.get('library').add( state.get('selection').models ); 3784 state.trigger('reset'); 3785 controller.setState('playlist-edit'); 3786 } 3787 } 3788 } 3789 }) ); 3790 }, 3791 3792 videoPlaylistEditToolbar: function() { 3793 var editing = this.state().get('editing'); 3794 this.toolbar.set( new wp.media.view.Toolbar({ 3795 controller: this, 3796 items: { 3797 insert: { 3798 style: 'primary', 3799 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 3800 priority: 140, 3801 requires: { library: true }, 3802 3803 click: function() { 3804 var controller = this.controller, 3805 state = controller.state(), 3806 library = state.get('library'); 3807 3808 library.type = 'video'; 3809 3810 controller.close(); 3811 state.trigger( 'update', library ); 3812 3813 // Restore and reset the default state. 3814 controller.setState( controller.options.state ); 3815 controller.reset(); 3816 } 3817 } 3818 } 3819 }) ); 3820 }, 3821 3822 videoPlaylistAddToolbar: function() { 3823 this.toolbar.set( new wp.media.view.Toolbar({ 3824 controller: this, 3825 items: { 3826 insert: { 3827 style: 'primary', 3828 text: l10n.addToVideoPlaylist, 3829 priority: 140, 3830 requires: { selection: true }, 3831 3832 click: function() { 3833 var controller = this.controller, 3834 state = controller.state(), 3835 edit = controller.state('video-playlist-edit'); 3836 3837 edit.get('library').add( state.get('selection').models ); 3838 state.trigger('reset'); 3839 controller.setState('video-playlist-edit'); 3840 } 3841 } 3842 } 3843 }) ); 2098 var selectionSync = { 2099 /** 2100 * @since 3.5.0 2101 */ 2102 syncSelection: function() { 2103 var selection = this.get('selection'), 2104 manager = this.frame._selection; 2105 2106 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2107 return; 2108 } 2109 2110 // If the selection supports multiple items, validate the stored 2111 // attachments based on the new selection's conditions. Record 2112 // the attachments that are not included; we'll maintain a 2113 // reference to those. Other attachments are considered in flux. 2114 if ( selection.multiple ) { 2115 selection.reset( [], { silent: true }); 2116 selection.validateAll( manager.attachments ); 2117 manager.difference = _.difference( manager.attachments.models, selection.models ); 2118 } 2119 2120 // Sync the selection's single item with the master. 2121 selection.single( manager.single ); 2122 }, 2123 2124 /** 2125 * Record the currently active attachments, which is a combination 2126 * of the selection's attachments and the set of selected 2127 * attachments that this specific selection considered invalid. 2128 * Reset the difference and record the single attachment. 2129 * 2130 * @since 3.5.0 2131 */ 2132 recordSelection: function() { 2133 var selection = this.get('selection'), 2134 manager = this.frame._selection; 2135 2136 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2137 return; 2138 } 2139 2140 if ( selection.multiple ) { 2141 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 2142 manager.difference = []; 2143 } else { 2144 manager.attachments.add( selection.toArray() ); 2145 } 2146 2147 manager.single = selection._single; 3844 2148 } 3845 }); 3846 3847 module.exports = Post; 3848 3849 3850 /***/ }), 3851 /* 50 */ 3852 /***/ (function(module, exports) { 2149 }; 2150 2151 module.exports = selectionSync; 2152 2153 },{}],19:[function(require,module,exports){ 2154 var media = wp.media, 2155 $ = jQuery, 2156 l10n; 2157 2158 media.isTouchDevice = ( 'ontouchend' in document ); 2159 2160 // Link any localized strings. 2161 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 2162 2163 // Link any settings. 2164 media.view.settings = l10n.settings || {}; 2165 delete l10n.settings; 2166 2167 // Copy the `post` setting over to the model settings. 2168 media.model.settings.post = media.view.settings.post; 2169 2170 // Check if the browser supports CSS 3.0 transitions 2171 $.support.transition = (function(){ 2172 var style = document.documentElement.style, 2173 transitions = { 2174 WebkitTransition: 'webkitTransitionEnd', 2175 MozTransition: 'transitionend', 2176 OTransition: 'oTransitionEnd otransitionend', 2177 transition: 'transitionend' 2178 }, transition; 2179 2180 transition = _.find( _.keys( transitions ), function( transition ) { 2181 return ! _.isUndefined( style[ transition ] ); 2182 }); 2183 2184 return transition && { 2185 end: transitions[ transition ] 2186 }; 2187 }()); 3853 2188 3854 2189 /** 3855 * wp.media.view.MediaFrame.ImageDetails 3856 * 3857 * A media frame for manipulating an image that's already been inserted 3858 * into a post. 3859 * 3860 * @class 3861 * @augments wp.media.view.MediaFrame.Select 3862 * @augments wp.media.view.MediaFrame 3863 * @augments wp.media.view.Frame 3864 * @augments wp.media.View 3865 * @augments wp.Backbone.View 3866 * @augments Backbone.View 3867 * @mixes wp.media.controller.StateMachine 2190 * A shared event bus used to provide events into 2191 * the media workflows that 3rd-party devs can use to hook 2192 * in. 3868 2193 */ 3869 var Select = wp.media.view.MediaFrame.Select, 3870 l10n = wp.media.view.l10n, 3871 ImageDetails; 3872 3873 ImageDetails = Select.extend({ 3874 defaults: { 3875 id: 'image', 3876 url: '', 3877 menu: 'image-details', 3878 content: 'image-details', 3879 toolbar: 'image-details', 3880 type: 'link', 3881 title: l10n.imageDetailsTitle, 3882 priority: 120 3883 }, 3884 3885 initialize: function( options ) { 3886 this.image = new wp.media.model.PostImage( options.metadata ); 3887 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 3888 Select.prototype.initialize.apply( this, arguments ); 3889 }, 3890 3891 bindHandlers: function() { 3892 Select.prototype.bindHandlers.apply( this, arguments ); 3893 this.on( 'menu:create:image-details', this.createMenu, this ); 3894 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 3895 this.on( 'content:render:edit-image', this.editImageContent, this ); 3896 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 3897 // override the select toolbar 3898 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 3899 }, 3900 3901 createStates: function() { 3902 this.states.add([ 3903 new wp.media.controller.ImageDetails({ 3904 image: this.image, 3905 editable: false 3906 }), 3907 new wp.media.controller.ReplaceImage({ 3908 id: 'replace-image', 3909 library: wp.media.query( { type: 'image' } ), 3910 image: this.image, 3911 multiple: false, 3912 title: l10n.imageReplaceTitle, 3913 toolbar: 'replace', 3914 priority: 80, 3915 displaySettings: true 3916 }), 3917 new wp.media.controller.EditImage( { 3918 image: this.image, 3919 selection: this.options.selection 3920 } ) 3921 ]); 3922 }, 3923 3924 imageDetailsContent: function( options ) { 3925 options.view = new wp.media.view.ImageDetails({ 3926 controller: this, 3927 model: this.state().image, 3928 attachment: this.state().image.attachment 3929 }); 3930 }, 3931 3932 editImageContent: function() { 3933 var state = this.state(), 3934 model = state.get('image'), 3935 view; 3936 3937 if ( ! model ) { 3938 return; 3939 } 3940 3941 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 3942 3943 this.content.set( view ); 3944 3945 // after bringing in the frame, load the actual editor via an ajax call 3946 view.loadEditor(); 3947 3948 }, 3949 3950 renderImageDetailsToolbar: function() { 3951 this.toolbar.set( new wp.media.view.Toolbar({ 3952 controller: this, 3953 items: { 3954 select: { 3955 style: 'primary', 3956 text: l10n.update, 3957 priority: 80, 3958 3959 click: function() { 3960 var controller = this.controller, 3961 state = controller.state(); 3962 3963 controller.close(); 3964 3965 // not sure if we want to use wp.media.string.image which will create a shortcode or 3966 // perhaps wp.html.string to at least to build the <img /> 3967 state.trigger( 'update', controller.image.toJSON() ); 3968 3969 // Restore and reset the default state. 3970 controller.setState( controller.options.state ); 3971 controller.reset(); 3972 } 3973 } 3974 } 3975 }) ); 3976 }, 3977 3978 renderReplaceImageToolbar: function() { 3979 var frame = this, 3980 lastState = frame.lastState(), 3981 previous = lastState && lastState.id; 3982 3983 this.toolbar.set( new wp.media.view.Toolbar({ 3984 controller: this, 3985 items: { 3986 back: { 3987 text: l10n.back, 3988 priority: 20, 3989 click: function() { 3990 if ( previous ) { 3991 frame.setState( previous ); 3992 } else { 3993 frame.close(); 3994 } 3995 } 3996 }, 3997 3998 replace: { 3999 style: 'primary', 4000 text: l10n.replace, 4001 priority: 80, 4002 4003 click: function() { 4004 var controller = this.controller, 4005 state = controller.state(), 4006 selection = state.get( 'selection' ), 4007 attachment = selection.single(); 4008 4009 controller.close(); 4010 4011 controller.image.changeAttachment( attachment, state.display( attachment ) ); 4012 4013 // not sure if we want to use wp.media.string.image which will create a shortcode or 4014 // perhaps wp.html.string to at least to build the <img /> 4015 state.trigger( 'replace', controller.image.toJSON() ); 4016 4017 // Restore and reset the default state. 4018 controller.setState( controller.options.state ); 4019 controller.reset(); 4020 } 4021 } 4022 } 4023 }) ); 2194 media.events = _.extend( {}, Backbone.Events ); 2195 2196 /** 2197 * Makes it easier to bind events using transitions. 2198 * 2199 * @param {string} selector 2200 * @param {Number} sensitivity 2201 * @returns {Promise} 2202 */ 2203 media.transition = function( selector, sensitivity ) { 2204 var deferred = $.Deferred(); 2205 2206 sensitivity = sensitivity || 2000; 2207 2208 if ( $.support.transition ) { 2209 if ( ! (selector instanceof $) ) { 2210 selector = $( selector ); 2211 } 2212 2213 // Resolve the deferred when the first element finishes animating. 2214 selector.first().one( $.support.transition.end, deferred.resolve ); 2215 2216 // Just in case the event doesn't trigger, fire a callback. 2217 _.delay( deferred.resolve, sensitivity ); 2218 2219 // Otherwise, execute on the spot. 2220 } else { 2221 deferred.resolve(); 4024 2222 } 4025 2223 4026 }); 4027 4028 module.exports = ImageDetails; 4029 4030 4031 /***/ }), 4032 /* 51 */ 4033 /***/ (function(module, exports) { 4034 2224 return deferred.promise(); 2225 }; 2226 2227 media.controller.Region = require( './controllers/region.js' ); 2228 media.controller.StateMachine = require( './controllers/state-machine.js' ); 2229 media.controller.State = require( './controllers/state.js' ); 2230 2231 media.selectionSync = require( './utils/selection-sync.js' ); 2232 media.controller.Library = require( './controllers/library.js' ); 2233 media.controller.ImageDetails = require( './controllers/image-details.js' ); 2234 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' ); 2235 media.controller.GalleryAdd = require( './controllers/gallery-add.js' ); 2236 media.controller.CollectionEdit = require( './controllers/collection-edit.js' ); 2237 media.controller.CollectionAdd = require( './controllers/collection-add.js' ); 2238 media.controller.FeaturedImage = require( './controllers/featured-image.js' ); 2239 media.controller.ReplaceImage = require( './controllers/replace-image.js' ); 2240 media.controller.EditImage = require( './controllers/edit-image.js' ); 2241 media.controller.MediaLibrary = require( './controllers/media-library.js' ); 2242 media.controller.Embed = require( './controllers/embed.js' ); 2243 media.controller.Cropper = require( './controllers/cropper.js' ); 2244 media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' ); 2245 media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' ); 2246 2247 media.View = require( './views/view.js' ); 2248 media.view.Frame = require( './views/frame.js' ); 2249 media.view.MediaFrame = require( './views/media-frame.js' ); 2250 media.view.MediaFrame.Select = require( './views/frame/select.js' ); 2251 media.view.MediaFrame.Post = require( './views/frame/post.js' ); 2252 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' ); 2253 media.view.Modal = require( './views/modal.js' ); 2254 media.view.FocusManager = require( './views/focus-manager.js' ); 2255 media.view.UploaderWindow = require( './views/uploader/window.js' ); 2256 media.view.EditorUploader = require( './views/uploader/editor.js' ); 2257 media.view.UploaderInline = require( './views/uploader/inline.js' ); 2258 media.view.UploaderStatus = require( './views/uploader/status.js' ); 2259 media.view.UploaderStatusError = require( './views/uploader/status-error.js' ); 2260 media.view.Toolbar = require( './views/toolbar.js' ); 2261 media.view.Toolbar.Select = require( './views/toolbar/select.js' ); 2262 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' ); 2263 media.view.Button = require( './views/button.js' ); 2264 media.view.ButtonGroup = require( './views/button-group.js' ); 2265 media.view.PriorityList = require( './views/priority-list.js' ); 2266 media.view.MenuItem = require( './views/menu-item.js' ); 2267 media.view.Menu = require( './views/menu.js' ); 2268 media.view.RouterItem = require( './views/router-item.js' ); 2269 media.view.Router = require( './views/router.js' ); 2270 media.view.Sidebar = require( './views/sidebar.js' ); 2271 media.view.Attachment = require( './views/attachment.js' ); 2272 media.view.Attachment.Library = require( './views/attachment/library.js' ); 2273 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' ); 2274 media.view.Attachments = require( './views/attachments.js' ); 2275 media.view.Search = require( './views/search.js' ); 2276 media.view.AttachmentFilters = require( './views/attachment-filters.js' ); 2277 media.view.DateFilter = require( './views/attachment-filters/date.js' ); 2278 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' ); 2279 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' ); 2280 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' ); 2281 media.view.Selection = require( './views/selection.js' ); 2282 media.view.Attachment.Selection = require( './views/attachment/selection.js' ); 2283 media.view.Attachments.Selection = require( './views/attachments/selection.js' ); 2284 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' ); 2285 media.view.Settings = require( './views/settings.js' ); 2286 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' ); 2287 media.view.Settings.Gallery = require( './views/settings/gallery.js' ); 2288 media.view.Settings.Playlist = require( './views/settings/playlist.js' ); 2289 media.view.Attachment.Details = require( './views/attachment/details.js' ); 2290 media.view.AttachmentCompat = require( './views/attachment-compat.js' ); 2291 media.view.Iframe = require( './views/iframe.js' ); 2292 media.view.Embed = require( './views/embed.js' ); 2293 media.view.Label = require( './views/label.js' ); 2294 media.view.EmbedUrl = require( './views/embed/url.js' ); 2295 media.view.EmbedLink = require( './views/embed/link.js' ); 2296 media.view.EmbedImage = require( './views/embed/image.js' ); 2297 media.view.ImageDetails = require( './views/image-details.js' ); 2298 media.view.Cropper = require( './views/cropper.js' ); 2299 media.view.SiteIconCropper = require( './views/site-icon-cropper.js' ); 2300 media.view.SiteIconPreview = require( './views/site-icon-preview.js' ); 2301 media.view.EditImage = require( './views/edit-image.js' ); 2302 media.view.Spinner = require( './views/spinner.js' ); 2303 2304 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/customize-image-cropper.js":4,"./controllers/edit-image.js":5,"./controllers/embed.js":6,"./controllers/featured-image.js":7,"./controllers/gallery-add.js":8,"./controllers/gallery-edit.js":9,"./controllers/image-details.js":10,"./controllers/library.js":11,"./controllers/media-library.js":12,"./controllers/region.js":13,"./controllers/replace-image.js":14,"./controllers/site-icon-cropper.js":15,"./controllers/state-machine.js":16,"./controllers/state.js":17,"./utils/selection-sync.js":18,"./views/attachment-compat.js":20,"./views/attachment-filters.js":21,"./views/attachment-filters/all.js":22,"./views/attachment-filters/date.js":23,"./views/attachment-filters/uploaded.js":24,"./views/attachment.js":25,"./views/attachment/details.js":26,"./views/attachment/edit-library.js":27,"./views/attachment/edit-selection.js":28,"./views/attachment/library.js":29,"./views/attachment/selection.js":30,"./views/attachments.js":31,"./views/attachments/browser.js":32,"./views/attachments/selection.js":33,"./views/button-group.js":34,"./views/button.js":35,"./views/cropper.js":36,"./views/edit-image.js":37,"./views/embed.js":38,"./views/embed/image.js":39,"./views/embed/link.js":40,"./views/embed/url.js":41,"./views/focus-manager.js":42,"./views/frame.js":43,"./views/frame/image-details.js":44,"./views/frame/post.js":45,"./views/frame/select.js":46,"./views/iframe.js":47,"./views/image-details.js":48,"./views/label.js":49,"./views/media-frame.js":50,"./views/menu-item.js":51,"./views/menu.js":52,"./views/modal.js":53,"./views/priority-list.js":54,"./views/router-item.js":55,"./views/router.js":56,"./views/search.js":57,"./views/selection.js":58,"./views/settings.js":59,"./views/settings/attachment-display.js":60,"./views/settings/gallery.js":61,"./views/settings/playlist.js":62,"./views/sidebar.js":63,"./views/site-icon-cropper.js":64,"./views/site-icon-preview.js":65,"./views/spinner.js":66,"./views/toolbar.js":67,"./views/toolbar/embed.js":68,"./views/toolbar/select.js":69,"./views/uploader/editor.js":70,"./views/uploader/inline.js":71,"./views/uploader/status-error.js":72,"./views/uploader/status.js":73,"./views/uploader/window.js":74,"./views/view.js":75}],20:[function(require,module,exports){ 4035 2305 /** 4036 * wp.media.view.Modal 4037 * 4038 * A modal view, which the media modal uses as its default container. 4039 * 4040 * @class 4041 * @augments wp.media.View 4042 * @augments wp.Backbone.View 4043 * @augments Backbone.View 4044 */ 4045 var $ = jQuery, 4046 Modal; 4047 4048 Modal = wp.media.View.extend({ 4049 tagName: 'div', 4050 template: wp.template('media-modal'), 4051 4052 attributes: { 4053 tabindex: 0 4054 }, 4055 4056 events: { 4057 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 4058 'keydown': 'keydown' 4059 }, 4060 4061 initialize: function() { 4062 _.defaults( this.options, { 4063 container: document.body, 4064 title: '', 4065 propagate: true, 4066 freeze: true 4067 }); 4068 4069 this.focusManager = new wp.media.view.FocusManager({ 4070 el: this.el 4071 }); 4072 }, 4073 /** 4074 * @returns {Object} 4075 */ 4076 prepare: function() { 4077 return { 4078 title: this.options.title 4079 }; 4080 }, 4081 4082 /** 4083 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4084 */ 4085 attach: function() { 4086 if ( this.views.attached ) { 4087 return this; 4088 } 4089 4090 if ( ! this.views.rendered ) { 4091 this.render(); 4092 } 4093 4094 this.$el.appendTo( this.options.container ); 4095 4096 // Manually mark the view as attached and trigger ready. 4097 this.views.attached = true; 4098 this.views.ready(); 4099 4100 return this.propagate('attach'); 4101 }, 4102 4103 /** 4104 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4105 */ 4106 detach: function() { 4107 if ( this.$el.is(':visible') ) { 4108 this.close(); 4109 } 4110 4111 this.$el.detach(); 4112 this.views.attached = false; 4113 return this.propagate('detach'); 4114 }, 4115 4116 /** 4117 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4118 */ 4119 open: function() { 4120 var $el = this.$el, 4121 options = this.options, 4122 mceEditor; 4123 4124 if ( $el.is(':visible') ) { 4125 return this; 4126 } 4127 4128 if ( ! this.views.attached ) { 4129 this.attach(); 4130 } 4131 4132 // If the `freeze` option is set, record the window's scroll position. 4133 if ( options.freeze ) { 4134 this._freeze = { 4135 scrollTop: $( window ).scrollTop() 4136 }; 4137 } 4138 4139 // Disable page scrolling. 4140 $( 'body' ).addClass( 'modal-open' ); 4141 4142 $el.show(); 4143 4144 // Try to close the onscreen keyboard 4145 if ( 'ontouchend' in document ) { 4146 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 4147 mceEditor.iframeElement.focus(); 4148 mceEditor.iframeElement.blur(); 4149 4150 setTimeout( function() { 4151 mceEditor.iframeElement.blur(); 4152 }, 100 ); 4153 } 4154 } 4155 4156 this.$el.focus(); 4157 4158 return this.propagate('open'); 4159 }, 4160 4161 /** 4162 * @param {Object} options 4163 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4164 */ 4165 close: function( options ) { 4166 var freeze = this._freeze; 4167 4168 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 4169 return this; 4170 } 4171 4172 // Enable page scrolling. 4173 $( 'body' ).removeClass( 'modal-open' ); 4174 4175 // Hide modal and remove restricted media modal tab focus once it's closed 4176 this.$el.hide().undelegate( 'keydown' ); 4177 4178 // Put focus back in useful location once modal is closed 4179 $('#wpbody-content').focus(); 4180 4181 this.propagate('close'); 4182 4183 // If the `freeze` option is set, restore the container's scroll position. 4184 if ( freeze ) { 4185 $( window ).scrollTop( freeze.scrollTop ); 4186 } 4187 4188 if ( options && options.escape ) { 4189 this.propagate('escape'); 4190 } 4191 4192 return this; 4193 }, 4194 /** 4195 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4196 */ 4197 escape: function() { 4198 return this.close({ escape: true }); 4199 }, 4200 /** 4201 * @param {Object} event 4202 */ 4203 escapeHandler: function( event ) { 4204 event.preventDefault(); 4205 this.escape(); 4206 }, 4207 4208 /** 4209 * @param {Array|Object} content Views to register to '.media-modal-content' 4210 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4211 */ 4212 content: function( content ) { 4213 this.views.set( '.media-modal-content', content ); 4214 return this; 4215 }, 4216 4217 /** 4218 * Triggers a modal event and if the `propagate` option is set, 4219 * forwards events to the modal's controller. 4220 * 4221 * @param {string} id 4222 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4223 */ 4224 propagate: function( id ) { 4225 this.trigger( id ); 4226 4227 if ( this.options.propagate ) { 4228 this.controller.trigger( id ); 4229 } 4230 4231 return this; 4232 }, 4233 /** 4234 * @param {Object} event 4235 */ 4236 keydown: function( event ) { 4237 // Close the modal when escape is pressed. 4238 if ( 27 === event.which && this.$el.is(':visible') ) { 4239 this.escape(); 4240 event.stopImmediatePropagation(); 4241 } 4242 } 4243 }); 4244 4245 module.exports = Modal; 4246 4247 4248 /***/ }), 4249 /* 52 */ 4250 /***/ (function(module, exports) { 4251 4252 /** 4253 * wp.media.view.FocusManager 4254 * 4255 * @class 4256 * @augments wp.media.View 4257 * @augments wp.Backbone.View 4258 * @augments Backbone.View 4259 */ 4260 var FocusManager = wp.media.View.extend({ 4261 4262 events: { 4263 'keydown': 'constrainTabbing' 4264 }, 4265 4266 focus: function() { // Reset focus on first left menu item 4267 this.$('.media-menu-item').first().focus(); 4268 }, 4269 /** 4270 * @param {Object} event 4271 */ 4272 constrainTabbing: function( event ) { 4273 var tabbables; 4274 4275 // Look for the tab key. 4276 if ( 9 !== event.keyCode ) { 4277 return; 4278 } 4279 4280 // Skip the file input added by Plupload. 4281 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 4282 4283 // Keep tab focus within media modal while it's open 4284 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 4285 tabbables.first().focus(); 4286 return false; 4287 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 4288 tabbables.last().focus(); 4289 return false; 4290 } 4291 } 4292 4293 }); 4294 4295 module.exports = FocusManager; 4296 4297 4298 /***/ }), 4299 /* 53 */ 4300 /***/ (function(module, exports) { 4301 4302 /** 4303 * wp.media.view.UploaderWindow 4304 * 4305 * An uploader window that allows for dragging and dropping media. 4306 * 4307 * @class 4308 * @augments wp.media.View 4309 * @augments wp.Backbone.View 4310 * @augments Backbone.View 4311 * 4312 * @param {object} [options] Options hash passed to the view. 4313 * @param {object} [options.uploader] Uploader properties. 4314 * @param {jQuery} [options.uploader.browser] 4315 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 4316 * @param {object} [options.uploader.params] 4317 */ 4318 var $ = jQuery, 4319 UploaderWindow; 4320 4321 UploaderWindow = wp.media.View.extend({ 4322 tagName: 'div', 4323 className: 'uploader-window', 4324 template: wp.template('uploader-window'), 4325 4326 initialize: function() { 4327 var uploader; 4328 4329 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body'); 4330 4331 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 4332 dropzone: this.$el, 4333 browser: this.$browser, 4334 params: {} 4335 }); 4336 4337 // Ensure the dropzone is a jQuery collection. 4338 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 4339 uploader.dropzone = $( uploader.dropzone ); 4340 } 4341 4342 this.controller.on( 'activate', this.refresh, this ); 4343 4344 this.controller.on( 'detach', function() { 4345 this.$browser.remove(); 4346 }, this ); 4347 }, 4348 4349 refresh: function() { 4350 if ( this.uploader ) { 4351 this.uploader.refresh(); 4352 } 4353 }, 4354 4355 ready: function() { 4356 var postId = wp.media.view.settings.post.id, 4357 dropzone; 4358 4359 // If the uploader already exists, bail. 4360 if ( this.uploader ) { 4361 return; 4362 } 4363 4364 if ( postId ) { 4365 this.options.uploader.params.post_id = postId; 4366 } 4367 this.uploader = new wp.Uploader( this.options.uploader ); 4368 4369 dropzone = this.uploader.dropzone; 4370 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 4371 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 4372 4373 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 4374 }, 4375 4376 _ready: function() { 4377 this.controller.trigger( 'uploader:ready' ); 4378 }, 4379 4380 show: function() { 4381 var $el = this.$el.show(); 4382 4383 // Ensure that the animation is triggered by waiting until 4384 // the transparent element is painted into the DOM. 4385 _.defer( function() { 4386 $el.css({ opacity: 1 }); 4387 }); 4388 }, 4389 4390 hide: function() { 4391 var $el = this.$el.css({ opacity: 0 }); 4392 4393 wp.media.transition( $el ).done( function() { 4394 // Transition end events are subject to race conditions. 4395 // Make sure that the value is set as intended. 4396 if ( '0' === $el.css('opacity') ) { 4397 $el.hide(); 4398 } 4399 }); 4400 4401 // https://core.trac.wordpress.org/ticket/27341 4402 _.delay( function() { 4403 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 4404 $el.hide(); 4405 } 4406 }, 500 ); 4407 } 4408 }); 4409 4410 module.exports = UploaderWindow; 4411 4412 4413 /***/ }), 4414 /* 54 */ 4415 /***/ (function(module, exports) { 4416 4417 /** 4418 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap) 4419 * and relays drag'n'dropped files to a media workflow. 4420 * 4421 * wp.media.view.EditorUploader 2306 * wp.media.view.AttachmentCompat 2307 * 2308 * A view to display fields added via the `attachment_fields_to_edit` filter. 4422 2309 * 4423 2310 * @class … … 4427 2314 */ 4428 2315 var View = wp.media.View, 4429 l10n = wp.media.view.l10n, 4430 $ = jQuery, 4431 EditorUploader; 4432 4433 EditorUploader = View.extend({ 4434 tagName: 'div', 4435 className: 'uploader-editor', 4436 template: wp.template( 'uploader-editor' ), 4437 4438 localDrag: false, 4439 overContainer: false, 4440 overDropzone: false, 4441 draggingFile: null, 4442 4443 /** 4444 * Bind drag'n'drop events to callbacks. 4445 */ 2316 AttachmentCompat; 2317 2318 AttachmentCompat = View.extend({ 2319 tagName: 'form', 2320 className: 'compat-item', 2321 2322 events: { 2323 'submit': 'preventDefault', 2324 'change input': 'save', 2325 'change select': 'save', 2326 'change textarea': 'save' 2327 }, 2328 4446 2329 initialize: function() { 4447 this.initialized = false; 4448 4449 // Bail if not enabled or UA does not support drag'n'drop or File API. 4450 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 4451 return this; 4452 } 4453 4454 this.$document = $(document); 4455 this.dropzones = []; 4456 this.files = []; 4457 4458 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 4459 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 4460 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 4461 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 4462 4463 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 4464 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 4465 4466 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 4467 this.localDrag = event.type === 'dragstart'; 4468 4469 if ( event.type === 'drop' ) { 4470 this.containerDragleave(); 4471 } 4472 }, this ) ); 4473 4474 this.initialized = true; 4475 return this; 4476 }, 4477 4478 /** 4479 * Check browser support for drag'n'drop. 4480 * 4481 * @return Boolean 4482 */ 4483 browserSupport: function() { 4484 var supports = false, div = document.createElement('div'); 4485 4486 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 4487 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 4488 return supports; 4489 }, 4490 4491 isDraggingFile: function( event ) { 4492 if ( this.draggingFile !== null ) { 4493 return this.draggingFile; 4494 } 4495 4496 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 4497 return false; 4498 } 4499 4500 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 4501 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 4502 4503 return this.draggingFile; 4504 }, 4505 4506 refresh: function( e ) { 4507 var dropzone_id; 4508 for ( dropzone_id in this.dropzones ) { 4509 // Hide the dropzones only if dragging has left the screen. 4510 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 4511 } 4512 4513 if ( ! _.isUndefined( e ) ) { 4514 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 4515 } 4516 4517 if ( ! this.overContainer && ! this.overDropzone ) { 4518 this.draggingFile = null; 4519 } 4520 4521 return this; 4522 }, 4523 4524 render: function() { 4525 if ( ! this.initialized ) { 4526 return this; 4527 } 4528 4529 View.prototype.render.apply( this, arguments ); 4530 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) ); 4531 return this; 4532 }, 4533 4534 attach: function( index, editor ) { 4535 // Attach a dropzone to an editor. 4536 var dropzone = this.$el.clone(); 4537 this.dropzones.push( dropzone ); 4538 $( editor ).append( dropzone ); 4539 return this; 4540 }, 4541 4542 /** 4543 * When a file is dropped on the editor uploader, open up an editor media workflow 4544 * and upload the file immediately. 4545 * 4546 * @param {jQuery.Event} event The 'drop' event. 4547 */ 4548 drop: function( event ) { 4549 var $wrap, uploadView; 4550 4551 this.containerDragleave( event ); 4552 this.dropzoneDragleave( event ); 4553 4554 this.files = event.originalEvent.dataTransfer.files; 4555 if ( this.files.length < 1 ) { 4556 return; 4557 } 4558 4559 // Set the active editor to the drop target. 4560 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 4561 if ( $wrap.length > 0 && $wrap[0].id ) { 4562 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 4563 } 4564 4565 if ( ! this.workflow ) { 4566 this.workflow = wp.media.editor.open( window.wpActiveEditor, { 4567 frame: 'post', 4568 state: 'insert', 4569 title: l10n.addMedia, 4570 multiple: true 4571 }); 4572 4573 uploadView = this.workflow.uploader; 4574 4575 if ( uploadView.uploader && uploadView.uploader.ready ) { 4576 this.addFiles.apply( this ); 4577 } else { 4578 this.workflow.on( 'uploader:ready', this.addFiles, this ); 4579 } 4580 } else { 4581 this.workflow.state().reset(); 4582 this.addFiles.apply( this ); 4583 this.workflow.open(); 4584 } 4585 4586 return false; 4587 }, 4588 4589 /** 4590 * Add the files to the uploader. 4591 */ 4592 addFiles: function() { 4593 if ( this.files.length ) { 4594 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 4595 this.files = []; 4596 } 4597 return this; 4598 }, 4599 4600 containerDragover: function( event ) { 4601 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 4602 return; 4603 } 4604 4605 this.overContainer = true; 4606 this.refresh(); 4607 }, 4608 4609 containerDragleave: function() { 4610 this.overContainer = false; 4611 4612 // Throttle dragleave because it's called when bouncing from some elements to others. 4613 _.delay( _.bind( this.refresh, this ), 50 ); 4614 }, 4615 4616 dropzoneDragover: function( event ) { 4617 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 4618 return; 4619 } 4620 4621 this.overDropzone = true; 4622 this.refresh( event ); 4623 return false; 4624 }, 4625 4626 dropzoneDragleave: function( e ) { 4627 this.overDropzone = false; 4628 _.delay( _.bind( this.refresh, this, e ), 50 ); 4629 }, 4630 4631 click: function( e ) { 4632 // In the rare case where the dropzone gets stuck, hide it on click. 4633 this.containerDragleave( e ); 4634 this.dropzoneDragleave( e ); 4635 this.localDrag = false; 4636 } 4637 }); 4638 4639 module.exports = EditorUploader; 4640 4641 4642 /***/ }), 4643 /* 55 */ 4644 /***/ (function(module, exports) { 4645 4646 /** 4647 * wp.media.view.UploaderInline 4648 * 4649 * The inline uploader that shows up in the 'Upload Files' tab. 4650 * 4651 * @class 4652 * @augments wp.media.View 4653 * @augments wp.Backbone.View 4654 * @augments Backbone.View 4655 */ 4656 var View = wp.media.View, 4657 UploaderInline; 4658 4659 UploaderInline = View.extend({ 4660 tagName: 'div', 4661 className: 'uploader-inline', 4662 template: wp.template('uploader-inline'), 4663 4664 events: { 4665 'click .close': 'hide' 4666 }, 4667 4668 initialize: function() { 4669 _.defaults( this.options, { 4670 message: '', 4671 status: true, 4672 canClose: false 4673 }); 4674 4675 if ( ! this.options.$browser && this.controller.uploader ) { 4676 this.options.$browser = this.controller.uploader.$browser; 4677 } 4678 4679 if ( _.isUndefined( this.options.postId ) ) { 4680 this.options.postId = wp.media.view.settings.post.id; 4681 } 4682 4683 if ( this.options.status ) { 4684 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 4685 controller: this.controller 4686 }) ); 4687 } 4688 }, 4689 4690 prepare: function() { 4691 var suggestedWidth = this.controller.state().get('suggestedWidth'), 4692 suggestedHeight = this.controller.state().get('suggestedHeight'), 4693 data = {}; 4694 4695 data.message = this.options.message; 4696 data.canClose = this.options.canClose; 4697 4698 if ( suggestedWidth && suggestedHeight ) { 4699 data.suggestedWidth = suggestedWidth; 4700 data.suggestedHeight = suggestedHeight; 4701 } 4702 4703 return data; 4704 }, 4705 /** 4706 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 2330 this.listenTo( this.model, 'change:compat', this.render ); 2331 }, 2332 /** 2333 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 4707 2334 */ 4708 2335 dispose: function() { 4709 if ( this.disposing ) { 4710 /** 4711 * call 'dispose' directly on the parent class 4712 */ 4713 return View.prototype.dispose.apply( this, arguments ); 4714 } 4715 4716 // Run remove on `dispose`, so we can be sure to refresh the 4717 // uploader with a view-less DOM. Track whether we're disposing 4718 // so we don't trigger an infinite loop. 4719 this.disposing = true; 4720 return this.remove(); 4721 }, 4722 /** 4723 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 4724 */ 4725 remove: function() { 4726 /** 4727 * call 'remove' directly on the parent class 4728 */ 4729 var result = View.prototype.remove.apply( this, arguments ); 4730 4731 _.defer( _.bind( this.refresh, this ) ); 4732 return result; 4733 }, 4734 4735 refresh: function() { 4736 var uploader = this.controller.uploader; 4737 4738 if ( uploader ) { 4739 uploader.refresh(); 4740 } 4741 }, 4742 /** 4743 * @returns {wp.media.view.UploaderInline} 4744 */ 4745 ready: function() { 4746 var $browser = this.options.$browser, 4747 $placeholder; 4748 4749 if ( this.controller.uploader ) { 4750 $placeholder = this.$('.browser'); 4751 4752 // Check if we've already replaced the placeholder. 4753 if ( $placeholder[0] === $browser[0] ) { 4754 return; 4755 } 4756 4757 $browser.detach().text( $placeholder.text() ); 4758 $browser[0].className = $placeholder[0].className; 4759 $placeholder.replaceWith( $browser.show() ); 4760 } 4761 4762 this.refresh(); 4763 return this; 4764 }, 4765 show: function() { 4766 this.$el.removeClass( 'hidden' ); 4767 }, 4768 hide: function() { 4769 this.$el.addClass( 'hidden' ); 4770 } 4771 4772 }); 4773 4774 module.exports = UploaderInline; 4775 4776 4777 /***/ }), 4778 /* 56 */ 4779 /***/ (function(module, exports) { 4780 4781 /** 4782 * wp.media.view.UploaderStatus 4783 * 4784 * An uploader status for on-going uploads. 4785 * 4786 * @class 4787 * @augments wp.media.View 4788 * @augments wp.Backbone.View 4789 * @augments Backbone.View 4790 */ 4791 var View = wp.media.View, 4792 UploaderStatus; 4793 4794 UploaderStatus = View.extend({ 4795 className: 'media-uploader-status', 4796 template: wp.template('uploader-status'), 4797 4798 events: { 4799 'click .upload-dismiss-errors': 'dismiss' 4800 }, 4801 4802 initialize: function() { 4803 this.queue = wp.Uploader.queue; 4804 this.queue.on( 'add remove reset', this.visibility, this ); 4805 this.queue.on( 'add remove reset change:percent', this.progress, this ); 4806 this.queue.on( 'add remove reset change:uploading', this.info, this ); 4807 4808 this.errors = wp.Uploader.errors; 4809 this.errors.reset(); 4810 this.errors.on( 'add remove reset', this.visibility, this ); 4811 this.errors.on( 'add', this.error, this ); 4812 }, 4813 /** 4814 * @global wp.Uploader 4815 * @returns {wp.media.view.UploaderStatus} 4816 */ 4817 dispose: function() { 4818 wp.Uploader.queue.off( null, null, this ); 4819 /** 4820 * call 'dispose' directly on the parent class 4821 */ 4822 View.prototype.dispose.apply( this, arguments ); 4823 return this; 4824 }, 4825 4826 visibility: function() { 4827 this.$el.toggleClass( 'uploading', !! this.queue.length ); 4828 this.$el.toggleClass( 'errors', !! this.errors.length ); 4829 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 4830 }, 4831 4832 ready: function() { 4833 _.each({ 4834 '$bar': '.media-progress-bar div', 4835 '$index': '.upload-index', 4836 '$total': '.upload-total', 4837 '$filename': '.upload-filename' 4838 }, function( selector, key ) { 4839 this[ key ] = this.$( selector ); 4840 }, this ); 4841 4842 this.visibility(); 4843 this.progress(); 4844 this.info(); 4845 }, 4846 4847 progress: function() { 4848 var queue = this.queue, 4849 $bar = this.$bar; 4850 4851 if ( ! $bar || ! queue.length ) { 4852 return; 4853 } 4854 4855 $bar.width( ( queue.reduce( function( memo, attachment ) { 4856 if ( ! attachment.get('uploading') ) { 4857 return memo + 100; 4858 } 4859 4860 var percent = attachment.get('percent'); 4861 return memo + ( _.isNumber( percent ) ? percent : 100 ); 4862 }, 0 ) / queue.length ) + '%' ); 4863 }, 4864 4865 info: function() { 4866 var queue = this.queue, 4867 index = 0, active; 4868 4869 if ( ! queue.length ) { 4870 return; 4871 } 4872 4873 active = this.queue.find( function( attachment, i ) { 4874 index = i; 4875 return attachment.get('uploading'); 4876 }); 4877 4878 this.$index.text( index + 1 ); 4879 this.$total.text( queue.length ); 4880 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 4881 }, 4882 /** 4883 * @param {string} filename 4884 * @returns {string} 4885 */ 4886 filename: function( filename ) { 4887 return _.escape( filename ); 4888 }, 4889 /** 4890 * @param {Backbone.Model} error 4891 */ 4892 error: function( error ) { 4893 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 4894 filename: this.filename( error.get('file').name ), 4895 message: error.get('message') 4896 }), { at: 0 }); 4897 }, 4898 4899 /** 4900 * @global wp.Uploader 4901 * 4902 * @param {Object} event 4903 */ 4904 dismiss: function( event ) { 4905 var errors = this.views.get('.upload-errors'); 4906 4907 event.preventDefault(); 4908 4909 if ( errors ) { 4910 _.invoke( errors, 'remove' ); 4911 } 4912 wp.Uploader.errors.reset(); 4913 } 4914 }); 4915 4916 module.exports = UploaderStatus; 4917 4918 4919 /***/ }), 4920 /* 57 */ 4921 /***/ (function(module, exports) { 4922 4923 /** 4924 * wp.media.view.UploaderStatusError 4925 * 4926 * @class 4927 * @augments wp.media.View 4928 * @augments wp.Backbone.View 4929 * @augments Backbone.View 4930 */ 4931 var UploaderStatusError = wp.media.View.extend({ 4932 className: 'upload-error', 4933 template: wp.template('uploader-status-error') 4934 }); 4935 4936 module.exports = UploaderStatusError; 4937 4938 4939 /***/ }), 4940 /* 58 */ 4941 /***/ (function(module, exports) { 4942 4943 /** 4944 * wp.media.view.Toolbar 4945 * 4946 * A toolbar which consists of a primary and a secondary section. Each sections 4947 * can be filled with views. 4948 * 4949 * @class 4950 * @augments wp.media.View 4951 * @augments wp.Backbone.View 4952 * @augments Backbone.View 4953 */ 4954 var View = wp.media.View, 4955 Toolbar; 4956 4957 Toolbar = View.extend({ 4958 tagName: 'div', 4959 className: 'media-toolbar', 4960 4961 initialize: function() { 4962 var state = this.controller.state(), 4963 selection = this.selection = state.get('selection'), 4964 library = this.library = state.get('library'); 4965 4966 this._views = {}; 4967 4968 // The toolbar is composed of two `PriorityList` views. 4969 this.primary = new wp.media.view.PriorityList(); 4970 this.secondary = new wp.media.view.PriorityList(); 4971 this.primary.$el.addClass('media-toolbar-primary search-form'); 4972 this.secondary.$el.addClass('media-toolbar-secondary'); 4973 4974 this.views.set([ this.secondary, this.primary ]); 4975 4976 if ( this.options.items ) { 4977 this.set( this.options.items, { silent: true }); 4978 } 4979 4980 if ( ! this.options.silent ) { 4981 this.render(); 4982 } 4983 4984 if ( selection ) { 4985 selection.on( 'add remove reset', this.refresh, this ); 4986 } 4987 4988 if ( library ) { 4989 library.on( 'add remove reset', this.refresh, this ); 4990 } 4991 }, 4992 /** 4993 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining 4994 */ 4995 dispose: function() { 4996 if ( this.selection ) { 4997 this.selection.off( null, null, this ); 4998 } 4999 5000 if ( this.library ) { 5001 this.library.off( null, null, this ); 2336 if ( this.$(':focus').length ) { 2337 this.save(); 5002 2338 } 5003 2339 /** … … 5006 2342 return View.prototype.dispose.apply( this, arguments ); 5007 2343 }, 5008 5009 ready: function() { 5010 this.refresh(); 5011 }, 5012 5013 /** 5014 * @param {string} id 5015 * @param {Backbone.View|Object} view 5016 * @param {Object} [options={}] 5017 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 5018 */ 5019 set: function( id, view, options ) { 5020 var list; 5021 options = options || {}; 5022 5023 // Accept an object with an `id` : `view` mapping. 5024 if ( _.isObject( id ) ) { 5025 _.each( id, function( view, id ) { 5026 this.set( id, view, { silent: true }); 5027 }, this ); 5028 5029 } else { 5030 if ( ! ( view instanceof Backbone.View ) ) { 5031 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 5032 view = new wp.media.view.Button( view ).render(); 5033 } 5034 5035 view.controller = view.controller || this.controller; 5036 5037 this._views[ id ] = view; 5038 5039 list = view.options.priority < 0 ? 'secondary' : 'primary'; 5040 this[ list ].set( id, view, options ); 5041 } 5042 5043 if ( ! options.silent ) { 5044 this.refresh(); 5045 } 5046 2344 /** 2345 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 2346 */ 2347 render: function() { 2348 var compat = this.model.get('compat'); 2349 if ( ! compat || ! compat.item ) { 2350 return; 2351 } 2352 2353 this.views.detach(); 2354 this.$el.html( compat.item ); 2355 this.views.render(); 5047 2356 return this; 5048 2357 }, 5049 2358 /** 5050 * @param {string} id 5051 * @returns {wp.media.view.Button} 5052 */ 5053 get: function( id ) { 5054 return this._views[ id ]; 5055 }, 5056 /** 5057 * @param {string} id 5058 * @param {Object} options 5059 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 5060 */ 5061 unset: function( id, options ) { 5062 delete this._views[ id ]; 5063 this.primary.unset( id, options ); 5064 this.secondary.unset( id, options ); 5065 5066 if ( ! options || ! options.silent ) { 5067 this.refresh(); 5068 } 5069 return this; 5070 }, 5071 5072 refresh: function() { 5073 var state = this.controller.state(), 5074 library = state.get('library'), 5075 selection = state.get('selection'); 5076 5077 _.each( this._views, function( button ) { 5078 if ( ! button.model || ! button.options || ! button.options.requires ) { 5079 return; 5080 } 5081 5082 var requires = button.options.requires, 5083 disabled = false; 5084 5085 // Prevent insertion of attachments if any of them are still uploading 5086 disabled = _.some( selection.models, function( attachment ) { 5087 return attachment.get('uploading') === true; 5088 }); 5089 5090 if ( requires.selection && selection && ! selection.length ) { 5091 disabled = true; 5092 } else if ( requires.library && library && ! library.length ) { 5093 disabled = true; 5094 } 5095 button.model.set( 'disabled', disabled ); 2359 * @param {Object} event 2360 */ 2361 preventDefault: function( event ) { 2362 event.preventDefault(); 2363 }, 2364 /** 2365 * @param {Object} event 2366 */ 2367 save: function( event ) { 2368 var data = {}; 2369 2370 if ( event ) { 2371 event.preventDefault(); 2372 } 2373 2374 _.each( this.$el.serializeArray(), function( pair ) { 2375 data[ pair.name ] = pair.value; 5096 2376 }); 2377 2378 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 2379 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 2380 }, 2381 2382 postSave: function() { 2383 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 5097 2384 } 5098 2385 }); 5099 2386 5100 module.exports = Toolbar; 5101 5102 5103 /***/ }), 5104 /* 59 */ 5105 /***/ (function(module, exports) { 5106 5107 /** 5108 * wp.media.view.Toolbar.Select 5109 * 5110 * @class 5111 * @augments wp.media.view.Toolbar 5112 * @augments wp.media.View 5113 * @augments wp.Backbone.View 5114 * @augments Backbone.View 5115 */ 5116 var Toolbar = wp.media.view.Toolbar, 5117 l10n = wp.media.view.l10n, 5118 Select; 5119 5120 Select = Toolbar.extend({ 5121 initialize: function() { 5122 var options = this.options; 5123 5124 _.bindAll( this, 'clickSelect' ); 5125 5126 _.defaults( options, { 5127 event: 'select', 5128 state: false, 5129 reset: true, 5130 close: true, 5131 text: l10n.select, 5132 5133 // Does the button rely on the selection? 5134 requires: { 5135 selection: true 5136 } 5137 }); 5138 5139 options.items = _.defaults( options.items || {}, { 5140 select: { 5141 style: 'primary', 5142 text: options.text, 5143 priority: 80, 5144 click: this.clickSelect, 5145 requires: options.requires 5146 } 5147 }); 5148 // Call 'initialize' directly on the parent class. 5149 Toolbar.prototype.initialize.apply( this, arguments ); 5150 }, 5151 5152 clickSelect: function() { 5153 var options = this.options, 5154 controller = this.controller; 5155 5156 if ( options.close ) { 5157 controller.close(); 5158 } 5159 5160 if ( options.event ) { 5161 controller.state().trigger( options.event ); 5162 } 5163 5164 if ( options.state ) { 5165 controller.setState( options.state ); 5166 } 5167 5168 if ( options.reset ) { 5169 controller.reset(); 5170 } 5171 } 5172 }); 5173 5174 module.exports = Select; 5175 5176 5177 /***/ }), 5178 /* 60 */ 5179 /***/ (function(module, exports) { 5180 5181 /** 5182 * wp.media.view.Toolbar.Embed 5183 * 5184 * @class 5185 * @augments wp.media.view.Toolbar.Select 5186 * @augments wp.media.view.Toolbar 5187 * @augments wp.media.View 5188 * @augments wp.Backbone.View 5189 * @augments Backbone.View 5190 */ 5191 var Select = wp.media.view.Toolbar.Select, 5192 l10n = wp.media.view.l10n, 5193 Embed; 5194 5195 Embed = Select.extend({ 5196 initialize: function() { 5197 _.defaults( this.options, { 5198 text: l10n.insertIntoPost, 5199 requires: false 5200 }); 5201 // Call 'initialize' directly on the parent class. 5202 Select.prototype.initialize.apply( this, arguments ); 5203 }, 5204 5205 refresh: function() { 5206 var url = this.controller.state().props.get('url'); 5207 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 5208 /** 5209 * call 'refresh' directly on the parent class 5210 */ 5211 Select.prototype.refresh.apply( this, arguments ); 5212 } 5213 }); 5214 5215 module.exports = Embed; 5216 5217 5218 /***/ }), 5219 /* 61 */ 5220 /***/ (function(module, exports) { 5221 5222 /** 5223 * wp.media.view.Button 5224 * 5225 * @class 5226 * @augments wp.media.View 5227 * @augments wp.Backbone.View 5228 * @augments Backbone.View 5229 */ 5230 var Button = wp.media.View.extend({ 5231 tagName: 'button', 5232 className: 'media-button', 5233 attributes: { type: 'button' }, 5234 5235 events: { 5236 'click': 'click' 5237 }, 5238 5239 defaults: { 5240 text: '', 5241 style: '', 5242 size: 'large', 5243 disabled: false 5244 }, 5245 5246 initialize: function() { 5247 /** 5248 * Create a model with the provided `defaults`. 5249 * 5250 * @member {Backbone.Model} 5251 */ 5252 this.model = new Backbone.Model( this.defaults ); 5253 5254 // If any of the `options` have a key from `defaults`, apply its 5255 // value to the `model` and remove it from the `options object. 5256 _.each( this.defaults, function( def, key ) { 5257 var value = this.options[ key ]; 5258 if ( _.isUndefined( value ) ) { 5259 return; 5260 } 5261 5262 this.model.set( key, value ); 5263 delete this.options[ key ]; 5264 }, this ); 5265 5266 this.listenTo( this.model, 'change', this.render ); 5267 }, 5268 /** 5269 * @returns {wp.media.view.Button} Returns itself to allow chaining 5270 */ 5271 render: function() { 5272 var classes = [ 'button', this.className ], 5273 model = this.model.toJSON(); 5274 5275 if ( model.style ) { 5276 classes.push( 'button-' + model.style ); 5277 } 5278 5279 if ( model.size ) { 5280 classes.push( 'button-' + model.size ); 5281 } 5282 5283 classes = _.uniq( classes.concat( this.options.classes ) ); 5284 this.el.className = classes.join(' '); 5285 5286 this.$el.attr( 'disabled', model.disabled ); 5287 this.$el.text( this.model.get('text') ); 5288 5289 return this; 5290 }, 5291 /** 5292 * @param {Object} event 5293 */ 5294 click: function( event ) { 5295 if ( '#' === this.attributes.href ) { 5296 event.preventDefault(); 5297 } 5298 5299 if ( this.options.click && ! this.model.get('disabled') ) { 5300 this.options.click.apply( this, arguments ); 5301 } 5302 } 5303 }); 5304 5305 module.exports = Button; 5306 5307 5308 /***/ }), 5309 /* 62 */ 5310 /***/ (function(module, exports) { 5311 5312 /** 5313 * wp.media.view.ButtonGroup 5314 * 5315 * @class 5316 * @augments wp.media.View 5317 * @augments wp.Backbone.View 5318 * @augments Backbone.View 5319 */ 5320 var $ = Backbone.$, 5321 ButtonGroup; 5322 5323 ButtonGroup = wp.media.View.extend({ 5324 tagName: 'div', 5325 className: 'button-group button-large media-button-group', 5326 5327 initialize: function() { 5328 /** 5329 * @member {wp.media.view.Button[]} 5330 */ 5331 this.buttons = _.map( this.options.buttons || [], function( button ) { 5332 if ( button instanceof Backbone.View ) { 5333 return button; 5334 } else { 5335 return new wp.media.view.Button( button ).render(); 5336 } 5337 }); 5338 5339 delete this.options.buttons; 5340 5341 if ( this.options.classes ) { 5342 this.$el.addClass( this.options.classes ); 5343 } 5344 }, 5345 5346 /** 5347 * @returns {wp.media.view.ButtonGroup} 5348 */ 5349 render: function() { 5350 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 5351 return this; 5352 } 5353 }); 5354 5355 module.exports = ButtonGroup; 5356 5357 5358 /***/ }), 5359 /* 63 */ 5360 /***/ (function(module, exports) { 5361 5362 /** 5363 * wp.media.view.PriorityList 5364 * 5365 * @class 5366 * @augments wp.media.View 5367 * @augments wp.Backbone.View 5368 * @augments Backbone.View 5369 */ 5370 var PriorityList = wp.media.View.extend({ 5371 tagName: 'div', 5372 5373 initialize: function() { 5374 this._views = {}; 5375 5376 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 5377 delete this.options.views; 5378 5379 if ( ! this.options.silent ) { 5380 this.render(); 5381 } 5382 }, 5383 /** 5384 * @param {string} id 5385 * @param {wp.media.View|Object} view 5386 * @param {Object} options 5387 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining 5388 */ 5389 set: function( id, view, options ) { 5390 var priority, views, index; 5391 5392 options = options || {}; 5393 5394 // Accept an object with an `id` : `view` mapping. 5395 if ( _.isObject( id ) ) { 5396 _.each( id, function( view, id ) { 5397 this.set( id, view ); 5398 }, this ); 5399 return this; 5400 } 5401 5402 if ( ! (view instanceof Backbone.View) ) { 5403 view = this.toView( view, id, options ); 5404 } 5405 view.controller = view.controller || this.controller; 5406 5407 this.unset( id ); 5408 5409 priority = view.options.priority || 10; 5410 views = this.views.get() || []; 5411 5412 _.find( views, function( existing, i ) { 5413 if ( existing.options.priority > priority ) { 5414 index = i; 5415 return true; 5416 } 5417 }); 5418 5419 this._views[ id ] = view; 5420 this.views.add( view, { 5421 at: _.isNumber( index ) ? index : views.length || 0 5422 }); 5423 5424 return this; 5425 }, 5426 /** 5427 * @param {string} id 5428 * @returns {wp.media.View} 5429 */ 5430 get: function( id ) { 5431 return this._views[ id ]; 5432 }, 5433 /** 5434 * @param {string} id 5435 * @returns {wp.media.view.PriorityList} 5436 */ 5437 unset: function( id ) { 5438 var view = this.get( id ); 5439 5440 if ( view ) { 5441 view.remove(); 5442 } 5443 5444 delete this._views[ id ]; 5445 return this; 5446 }, 5447 /** 5448 * @param {Object} options 5449 * @returns {wp.media.View} 5450 */ 5451 toView: function( options ) { 5452 return new wp.media.View( options ); 5453 } 5454 }); 5455 5456 module.exports = PriorityList; 5457 5458 5459 /***/ }), 5460 /* 64 */ 5461 /***/ (function(module, exports) { 5462 5463 /** 5464 * wp.media.view.MenuItem 5465 * 5466 * @class 5467 * @augments wp.media.View 5468 * @augments wp.Backbone.View 5469 * @augments Backbone.View 5470 */ 5471 var $ = jQuery, 5472 MenuItem; 5473 5474 MenuItem = wp.media.View.extend({ 5475 tagName: 'a', 5476 className: 'media-menu-item', 5477 5478 attributes: { 5479 href: '#' 5480 }, 5481 5482 events: { 5483 'click': '_click' 5484 }, 5485 /** 5486 * @param {Object} event 5487 */ 5488 _click: function( event ) { 5489 var clickOverride = this.options.click; 5490 5491 if ( event ) { 5492 event.preventDefault(); 5493 } 5494 5495 if ( clickOverride ) { 5496 clickOverride.call( this ); 5497 } else { 5498 this.click(); 5499 } 5500 5501 // When selecting a tab along the left side, 5502 // focus should be transferred into the main panel 5503 if ( ! wp.media.isTouchDevice ) { 5504 $('.media-frame-content input').first().focus(); 5505 } 5506 }, 5507 5508 click: function() { 5509 var state = this.options.state; 5510 5511 if ( state ) { 5512 this.controller.setState( state ); 5513 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 5514 } 5515 }, 5516 /** 5517 * @returns {wp.media.view.MenuItem} returns itself to allow chaining 5518 */ 5519 render: function() { 5520 var options = this.options; 5521 5522 if ( options.text ) { 5523 this.$el.text( options.text ); 5524 } else if ( options.html ) { 5525 this.$el.html( options.html ); 5526 } 5527 5528 return this; 5529 } 5530 }); 5531 5532 module.exports = MenuItem; 5533 5534 5535 /***/ }), 5536 /* 65 */ 5537 /***/ (function(module, exports) { 5538 5539 /** 5540 * wp.media.view.Menu 5541 * 5542 * @class 5543 * @augments wp.media.view.PriorityList 5544 * @augments wp.media.View 5545 * @augments wp.Backbone.View 5546 * @augments Backbone.View 5547 */ 5548 var MenuItem = wp.media.view.MenuItem, 5549 PriorityList = wp.media.view.PriorityList, 5550 Menu; 5551 5552 Menu = PriorityList.extend({ 5553 tagName: 'div', 5554 className: 'media-menu', 5555 property: 'state', 5556 ItemView: MenuItem, 5557 region: 'menu', 5558 5559 /* TODO: alternatively hide on any click anywhere 5560 events: { 5561 'click': 'click' 5562 }, 5563 5564 click: function() { 5565 this.$el.removeClass( 'visible' ); 5566 }, 5567 */ 5568 5569 /** 5570 * @param {Object} options 5571 * @param {string} id 5572 * @returns {wp.media.View} 5573 */ 5574 toView: function( options, id ) { 5575 options = options || {}; 5576 options[ this.property ] = options[ this.property ] || id; 5577 return new this.ItemView( options ).render(); 5578 }, 5579 5580 ready: function() { 5581 /** 5582 * call 'ready' directly on the parent class 5583 */ 5584 PriorityList.prototype.ready.apply( this, arguments ); 5585 this.visibility(); 5586 }, 5587 5588 set: function() { 5589 /** 5590 * call 'set' directly on the parent class 5591 */ 5592 PriorityList.prototype.set.apply( this, arguments ); 5593 this.visibility(); 5594 }, 5595 5596 unset: function() { 5597 /** 5598 * call 'unset' directly on the parent class 5599 */ 5600 PriorityList.prototype.unset.apply( this, arguments ); 5601 this.visibility(); 5602 }, 5603 5604 visibility: function() { 5605 var region = this.region, 5606 view = this.controller[ region ].get(), 5607 views = this.views.get(), 5608 hide = ! views || views.length < 2; 5609 5610 if ( this === view ) { 5611 this.controller.$el.toggleClass( 'hide-' + region, hide ); 5612 } 5613 }, 5614 /** 5615 * @param {string} id 5616 */ 5617 select: function( id ) { 5618 var view = this.get( id ); 5619 5620 if ( ! view ) { 5621 return; 5622 } 5623 5624 this.deselect(); 5625 view.$el.addClass('active'); 5626 }, 5627 5628 deselect: function() { 5629 this.$el.children().removeClass('active'); 5630 }, 5631 5632 hide: function( id ) { 5633 var view = this.get( id ); 5634 5635 if ( ! view ) { 5636 return; 5637 } 5638 5639 view.$el.addClass('hidden'); 5640 }, 5641 5642 show: function( id ) { 5643 var view = this.get( id ); 5644 5645 if ( ! view ) { 5646 return; 5647 } 5648 5649 view.$el.removeClass('hidden'); 5650 } 5651 }); 5652 5653 module.exports = Menu; 5654 5655 5656 /***/ }), 5657 /* 66 */ 5658 /***/ (function(module, exports) { 5659 5660 /** 5661 * wp.media.view.RouterItem 5662 * 5663 * @class 5664 * @augments wp.media.view.MenuItem 5665 * @augments wp.media.View 5666 * @augments wp.Backbone.View 5667 * @augments Backbone.View 5668 */ 5669 var RouterItem = wp.media.view.MenuItem.extend({ 5670 /** 5671 * On click handler to activate the content region's corresponding mode. 5672 */ 5673 click: function() { 5674 var contentMode = this.options.contentMode; 5675 if ( contentMode ) { 5676 this.controller.content.mode( contentMode ); 5677 } 5678 } 5679 }); 5680 5681 module.exports = RouterItem; 5682 5683 5684 /***/ }), 5685 /* 67 */ 5686 /***/ (function(module, exports) { 5687 5688 /** 5689 * wp.media.view.Router 5690 * 5691 * @class 5692 * @augments wp.media.view.Menu 5693 * @augments wp.media.view.PriorityList 5694 * @augments wp.media.View 5695 * @augments wp.Backbone.View 5696 * @augments Backbone.View 5697 */ 5698 var Menu = wp.media.view.Menu, 5699 Router; 5700 5701 Router = Menu.extend({ 5702 tagName: 'div', 5703 className: 'media-router', 5704 property: 'contentMode', 5705 ItemView: wp.media.view.RouterItem, 5706 region: 'router', 5707 5708 initialize: function() { 5709 this.controller.on( 'content:render', this.update, this ); 5710 // Call 'initialize' directly on the parent class. 5711 Menu.prototype.initialize.apply( this, arguments ); 5712 }, 5713 5714 update: function() { 5715 var mode = this.controller.content.mode(); 5716 if ( mode ) { 5717 this.select( mode ); 5718 } 5719 } 5720 }); 5721 5722 module.exports = Router; 5723 5724 5725 /***/ }), 5726 /* 68 */ 5727 /***/ (function(module, exports) { 5728 5729 /** 5730 * wp.media.view.Sidebar 5731 * 5732 * @class 5733 * @augments wp.media.view.PriorityList 5734 * @augments wp.media.View 5735 * @augments wp.Backbone.View 5736 * @augments Backbone.View 5737 */ 5738 var Sidebar = wp.media.view.PriorityList.extend({ 5739 className: 'media-sidebar' 5740 }); 5741 5742 module.exports = Sidebar; 5743 5744 5745 /***/ }), 5746 /* 69 */ 5747 /***/ (function(module, exports) { 5748 5749 /** 5750 * wp.media.view.Attachment 5751 * 5752 * @class 5753 * @augments wp.media.View 5754 * @augments wp.Backbone.View 5755 * @augments Backbone.View 5756 */ 5757 var View = wp.media.View, 5758 $ = jQuery, 5759 Attachment; 5760 5761 Attachment = View.extend({ 5762 tagName: 'li', 5763 className: 'attachment', 5764 template: wp.template('attachment'), 5765 5766 attributes: function() { 5767 return { 5768 'tabIndex': 0, 5769 'role': 'checkbox', 5770 'aria-label': this.model.get( 'title' ), 5771 'aria-checked': false, 5772 'data-id': this.model.get( 'id' ) 5773 }; 5774 }, 5775 5776 events: { 5777 'click .js--select-attachment': 'toggleSelectionHandler', 5778 'change [data-setting]': 'updateSetting', 5779 'change [data-setting] input': 'updateSetting', 5780 'change [data-setting] select': 'updateSetting', 5781 'change [data-setting] textarea': 'updateSetting', 5782 'click .attachment-close': 'removeFromLibrary', 5783 'click .check': 'checkClickHandler', 5784 'keydown': 'toggleSelectionHandler' 5785 }, 5786 5787 buttons: {}, 5788 5789 initialize: function() { 5790 var selection = this.options.selection, 5791 options = _.defaults( this.options, { 5792 rerenderOnModelChange: true 5793 } ); 5794 5795 if ( options.rerenderOnModelChange ) { 5796 this.listenTo( this.model, 'change', this.render ); 5797 } else { 5798 this.listenTo( this.model, 'change:percent', this.progress ); 5799 } 5800 this.listenTo( this.model, 'change:title', this._syncTitle ); 5801 this.listenTo( this.model, 'change:caption', this._syncCaption ); 5802 this.listenTo( this.model, 'change:artist', this._syncArtist ); 5803 this.listenTo( this.model, 'change:album', this._syncAlbum ); 5804 5805 // Update the selection. 5806 this.listenTo( this.model, 'add', this.select ); 5807 this.listenTo( this.model, 'remove', this.deselect ); 5808 if ( selection ) { 5809 selection.on( 'reset', this.updateSelect, this ); 5810 // Update the model's details view. 5811 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 5812 this.details( this.model, this.controller.state().get('selection') ); 5813 } 5814 5815 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 5816 }, 5817 /** 5818 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 5819 */ 5820 dispose: function() { 5821 var selection = this.options.selection; 5822 5823 // Make sure all settings are saved before removing the view. 5824 this.updateAll(); 5825 5826 if ( selection ) { 5827 selection.off( null, null, this ); 5828 } 5829 /** 5830 * call 'dispose' directly on the parent class 5831 */ 5832 View.prototype.dispose.apply( this, arguments ); 5833 return this; 5834 }, 5835 /** 5836 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 5837 */ 5838 render: function() { 5839 var options = _.defaults( this.model.toJSON(), { 5840 orientation: 'landscape', 5841 uploading: false, 5842 type: '', 5843 subtype: '', 5844 icon: '', 5845 filename: '', 5846 caption: '', 5847 title: '', 5848 dateFormatted: '', 5849 width: '', 5850 height: '', 5851 compat: false, 5852 alt: '', 5853 description: '' 5854 }, this.options ); 5855 5856 options.buttons = this.buttons; 5857 options.describe = this.controller.state().get('describe'); 5858 5859 if ( 'image' === options.type ) { 5860 options.size = this.imageSize(); 5861 } 5862 5863 options.can = {}; 5864 if ( options.nonces ) { 5865 options.can.remove = !! options.nonces['delete']; 5866 options.can.save = !! options.nonces.update; 5867 } 5868 5869 if ( this.controller.state().get('allowLocalEdits') ) { 5870 options.allowLocalEdits = true; 5871 } 5872 5873 if ( options.uploading && ! options.percent ) { 5874 options.percent = 0; 5875 } 5876 5877 this.views.detach(); 5878 this.$el.html( this.template( options ) ); 5879 5880 this.$el.toggleClass( 'uploading', options.uploading ); 5881 5882 if ( options.uploading ) { 5883 this.$bar = this.$('.media-progress-bar div'); 5884 } else { 5885 delete this.$bar; 5886 } 5887 5888 // Check if the model is selected. 5889 this.updateSelect(); 5890 5891 // Update the save status. 5892 this.updateSave(); 5893 5894 this.views.render(); 5895 5896 return this; 5897 }, 5898 5899 progress: function() { 5900 if ( this.$bar && this.$bar.length ) { 5901 this.$bar.width( this.model.get('percent') + '%' ); 5902 } 5903 }, 5904 5905 /** 5906 * @param {Object} event 5907 */ 5908 toggleSelectionHandler: function( event ) { 5909 var method; 5910 5911 // Don't do anything inside inputs and on the attachment check and remove buttons. 5912 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) { 5913 return; 5914 } 5915 5916 // Catch arrow events 5917 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 5918 this.controller.trigger( 'attachment:keydown:arrow', event ); 5919 return; 5920 } 5921 5922 // Catch enter and space events 5923 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 5924 return; 5925 } 5926 5927 event.preventDefault(); 5928 5929 // In the grid view, bubble up an edit:attachment event to the controller. 5930 if ( this.controller.isModeActive( 'grid' ) ) { 5931 if ( this.controller.isModeActive( 'edit' ) ) { 5932 // Pass the current target to restore focus when closing 5933 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 5934 return; 5935 } 5936 5937 if ( this.controller.isModeActive( 'select' ) ) { 5938 method = 'toggle'; 5939 } 5940 } 5941 5942 if ( event.shiftKey ) { 5943 method = 'between'; 5944 } else if ( event.ctrlKey || event.metaKey ) { 5945 method = 'toggle'; 5946 } 5947 5948 this.toggleSelection({ 5949 method: method 5950 }); 5951 5952 this.controller.trigger( 'selection:toggle' ); 5953 }, 5954 /** 5955 * @param {Object} options 5956 */ 5957 toggleSelection: function( options ) { 5958 var collection = this.collection, 5959 selection = this.options.selection, 5960 model = this.model, 5961 method = options && options.method, 5962 single, models, singleIndex, modelIndex; 5963 5964 if ( ! selection ) { 5965 return; 5966 } 5967 5968 single = selection.single(); 5969 method = _.isUndefined( method ) ? selection.multiple : method; 5970 5971 // If the `method` is set to `between`, select all models that 5972 // exist between the current and the selected model. 5973 if ( 'between' === method && single && selection.multiple ) { 5974 // If the models are the same, short-circuit. 5975 if ( single === model ) { 5976 return; 5977 } 5978 5979 singleIndex = collection.indexOf( single ); 5980 modelIndex = collection.indexOf( this.model ); 5981 5982 if ( singleIndex < modelIndex ) { 5983 models = collection.models.slice( singleIndex, modelIndex + 1 ); 5984 } else { 5985 models = collection.models.slice( modelIndex, singleIndex + 1 ); 5986 } 5987 5988 selection.add( models ); 5989 selection.single( model ); 5990 return; 5991 5992 // If the `method` is set to `toggle`, just flip the selection 5993 // status, regardless of whether the model is the single model. 5994 } else if ( 'toggle' === method ) { 5995 selection[ this.selected() ? 'remove' : 'add' ]( model ); 5996 selection.single( model ); 5997 return; 5998 } else if ( 'add' === method ) { 5999 selection.add( model ); 6000 selection.single( model ); 6001 return; 6002 } 6003 6004 // Fixes bug that loses focus when selecting a featured image 6005 if ( ! method ) { 6006 method = 'add'; 6007 } 6008 6009 if ( method !== 'add' ) { 6010 method = 'reset'; 6011 } 6012 6013 if ( this.selected() ) { 6014 // If the model is the single model, remove it. 6015 // If it is not the same as the single model, 6016 // it now becomes the single model. 6017 selection[ single === model ? 'remove' : 'single' ]( model ); 6018 } else { 6019 // If the model is not selected, run the `method` on the 6020 // selection. By default, we `reset` the selection, but the 6021 // `method` can be set to `add` the model to the selection. 6022 selection[ method ]( model ); 6023 selection.single( model ); 6024 } 6025 }, 6026 6027 updateSelect: function() { 6028 this[ this.selected() ? 'select' : 'deselect' ](); 6029 }, 6030 /** 6031 * @returns {unresolved|Boolean} 6032 */ 6033 selected: function() { 6034 var selection = this.options.selection; 6035 if ( selection ) { 6036 return !! selection.get( this.model.cid ); 6037 } 6038 }, 6039 /** 6040 * @param {Backbone.Model} model 6041 * @param {Backbone.Collection} collection 6042 */ 6043 select: function( model, collection ) { 6044 var selection = this.options.selection, 6045 controller = this.controller; 6046 6047 // Check if a selection exists and if it's the collection provided. 6048 // If they're not the same collection, bail; we're in another 6049 // selection's event loop. 6050 if ( ! selection || ( collection && collection !== selection ) ) { 6051 return; 6052 } 6053 6054 // Bail if the model is already selected. 6055 if ( this.$el.hasClass( 'selected' ) ) { 6056 return; 6057 } 6058 6059 // Add 'selected' class to model, set aria-checked to true. 6060 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 6061 // Make the checkbox tabable, except in media grid (bulk select mode). 6062 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 6063 this.$( '.check' ).attr( 'tabindex', '0' ); 6064 } 6065 }, 6066 /** 6067 * @param {Backbone.Model} model 6068 * @param {Backbone.Collection} collection 6069 */ 6070 deselect: function( model, collection ) { 6071 var selection = this.options.selection; 6072 6073 // Check if a selection exists and if it's the collection provided. 6074 // If they're not the same collection, bail; we're in another 6075 // selection's event loop. 6076 if ( ! selection || ( collection && collection !== selection ) ) { 6077 return; 6078 } 6079 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 6080 .find( '.check' ).attr( 'tabindex', '-1' ); 6081 }, 6082 /** 6083 * @param {Backbone.Model} model 6084 * @param {Backbone.Collection} collection 6085 */ 6086 details: function( model, collection ) { 6087 var selection = this.options.selection, 6088 details; 6089 6090 if ( selection !== collection ) { 6091 return; 6092 } 6093 6094 details = selection.single(); 6095 this.$el.toggleClass( 'details', details === this.model ); 6096 }, 6097 /** 6098 * @param {string} size 6099 * @returns {Object} 6100 */ 6101 imageSize: function( size ) { 6102 var sizes = this.model.get('sizes'), matched = false; 6103 6104 size = size || 'medium'; 6105 6106 // Use the provided image size if possible. 6107 if ( sizes ) { 6108 if ( sizes[ size ] ) { 6109 matched = sizes[ size ]; 6110 } else if ( sizes.large ) { 6111 matched = sizes.large; 6112 } else if ( sizes.thumbnail ) { 6113 matched = sizes.thumbnail; 6114 } else if ( sizes.full ) { 6115 matched = sizes.full; 6116 } 6117 6118 if ( matched ) { 6119 return _.clone( matched ); 6120 } 6121 } 6122 6123 return { 6124 url: this.model.get('url'), 6125 width: this.model.get('width'), 6126 height: this.model.get('height'), 6127 orientation: this.model.get('orientation') 6128 }; 6129 }, 6130 /** 6131 * @param {Object} event 6132 */ 6133 updateSetting: function( event ) { 6134 var $setting = $( event.target ).closest('[data-setting]'), 6135 setting, value; 6136 6137 if ( ! $setting.length ) { 6138 return; 6139 } 6140 6141 setting = $setting.data('setting'); 6142 value = event.target.value; 6143 6144 if ( this.model.get( setting ) !== value ) { 6145 this.save( setting, value ); 6146 } 6147 }, 6148 6149 /** 6150 * Pass all the arguments to the model's save method. 6151 * 6152 * Records the aggregate status of all save requests and updates the 6153 * view's classes accordingly. 6154 */ 6155 save: function() { 6156 var view = this, 6157 save = this._save = this._save || { status: 'ready' }, 6158 request = this.model.save.apply( this.model, arguments ), 6159 requests = save.requests ? $.when( request, save.requests ) : request; 6160 6161 // If we're waiting to remove 'Saved.', stop. 6162 if ( save.savedTimer ) { 6163 clearTimeout( save.savedTimer ); 6164 } 6165 6166 this.updateSave('waiting'); 6167 save.requests = requests; 6168 requests.always( function() { 6169 // If we've performed another request since this one, bail. 6170 if ( save.requests !== requests ) { 6171 return; 6172 } 6173 6174 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 6175 save.savedTimer = setTimeout( function() { 6176 view.updateSave('ready'); 6177 delete save.savedTimer; 6178 }, 2000 ); 6179 }); 6180 }, 6181 /** 6182 * @param {string} status 6183 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 6184 */ 6185 updateSave: function( status ) { 6186 var save = this._save = this._save || { status: 'ready' }; 6187 6188 if ( status && status !== save.status ) { 6189 this.$el.removeClass( 'save-' + save.status ); 6190 save.status = status; 6191 } 6192 6193 this.$el.addClass( 'save-' + save.status ); 6194 return this; 6195 }, 6196 6197 updateAll: function() { 6198 var $settings = this.$('[data-setting]'), 6199 model = this.model, 6200 changed; 6201 6202 changed = _.chain( $settings ).map( function( el ) { 6203 var $input = $('input, textarea, select, [value]', el ), 6204 setting, value; 6205 6206 if ( ! $input.length ) { 6207 return; 6208 } 6209 6210 setting = $(el).data('setting'); 6211 value = $input.val(); 6212 6213 // Record the value if it changed. 6214 if ( model.get( setting ) !== value ) { 6215 return [ setting, value ]; 6216 } 6217 }).compact().object().value(); 6218 6219 if ( ! _.isEmpty( changed ) ) { 6220 model.save( changed ); 6221 } 6222 }, 6223 /** 6224 * @param {Object} event 6225 */ 6226 removeFromLibrary: function( event ) { 6227 // Catch enter and space events 6228 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 6229 return; 6230 } 6231 6232 // Stop propagation so the model isn't selected. 6233 event.stopPropagation(); 6234 6235 this.collection.remove( this.model ); 6236 }, 6237 6238 /** 6239 * Add the model if it isn't in the selection, if it is in the selection, 6240 * remove it. 6241 * 6242 * @param {[type]} event [description] 6243 * @return {[type]} [description] 6244 */ 6245 checkClickHandler: function ( event ) { 6246 var selection = this.options.selection; 6247 if ( ! selection ) { 6248 return; 6249 } 6250 event.stopPropagation(); 6251 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 6252 selection.remove( this.model ); 6253 // Move focus back to the attachment tile (from the check). 6254 this.$el.focus(); 6255 } else { 6256 selection.add( this.model ); 6257 } 6258 } 6259 }); 6260 6261 // Ensure settings remain in sync between attachment views. 6262 _.each({ 6263 caption: '_syncCaption', 6264 title: '_syncTitle', 6265 artist: '_syncArtist', 6266 album: '_syncAlbum' 6267 }, function( method, setting ) { 6268 /** 6269 * @param {Backbone.Model} model 6270 * @param {string} value 6271 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 6272 */ 6273 Attachment.prototype[ method ] = function( model, value ) { 6274 var $setting = this.$('[data-setting="' + setting + '"]'); 6275 6276 if ( ! $setting.length ) { 6277 return this; 6278 } 6279 6280 // If the updated value is in sync with the value in the DOM, there 6281 // is no need to re-render. If we're currently editing the value, 6282 // it will automatically be in sync, suppressing the re-render for 6283 // the view we're editing, while updating any others. 6284 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 6285 return this; 6286 } 6287 6288 return this.render(); 6289 }; 6290 }); 6291 6292 module.exports = Attachment; 6293 6294 6295 /***/ }), 6296 /* 70 */ 6297 /***/ (function(module, exports) { 6298 6299 /** 6300 * wp.media.view.Attachment.Library 6301 * 6302 * @class 6303 * @augments wp.media.view.Attachment 6304 * @augments wp.media.View 6305 * @augments wp.Backbone.View 6306 * @augments Backbone.View 6307 */ 6308 var Library = wp.media.view.Attachment.extend({ 6309 buttons: { 6310 check: true 6311 } 6312 }); 6313 6314 module.exports = Library; 6315 6316 6317 /***/ }), 6318 /* 71 */ 6319 /***/ (function(module, exports) { 6320 6321 /** 6322 * wp.media.view.Attachment.EditLibrary 6323 * 6324 * @class 6325 * @augments wp.media.view.Attachment 6326 * @augments wp.media.View 6327 * @augments wp.Backbone.View 6328 * @augments Backbone.View 6329 */ 6330 var EditLibrary = wp.media.view.Attachment.extend({ 6331 buttons: { 6332 close: true 6333 } 6334 }); 6335 6336 module.exports = EditLibrary; 6337 6338 6339 /***/ }), 6340 /* 72 */ 6341 /***/ (function(module, exports) { 6342 6343 /** 6344 * wp.media.view.Attachments 6345 * 6346 * @class 6347 * @augments wp.media.View 6348 * @augments wp.Backbone.View 6349 * @augments Backbone.View 6350 */ 6351 var View = wp.media.View, 6352 $ = jQuery, 6353 Attachments; 6354 6355 Attachments = View.extend({ 6356 tagName: 'ul', 6357 className: 'attachments', 6358 6359 attributes: { 6360 tabIndex: -1 6361 }, 6362 6363 initialize: function() { 6364 this.el.id = _.uniqueId('__attachments-view-'); 6365 6366 _.defaults( this.options, { 6367 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 6368 refreshThreshold: 3, 6369 AttachmentView: wp.media.view.Attachment, 6370 sortable: false, 6371 resize: true, 6372 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 6373 }); 6374 6375 this._viewsByCid = {}; 6376 this.$window = $( window ); 6377 this.resizeEvent = 'resize.media-modal-columns'; 6378 6379 this.collection.on( 'add', function( attachment ) { 6380 this.views.add( this.createAttachmentView( attachment ), { 6381 at: this.collection.indexOf( attachment ) 6382 }); 6383 }, this ); 6384 6385 this.collection.on( 'remove', function( attachment ) { 6386 var view = this._viewsByCid[ attachment.cid ]; 6387 delete this._viewsByCid[ attachment.cid ]; 6388 6389 if ( view ) { 6390 view.remove(); 6391 } 6392 }, this ); 6393 6394 this.collection.on( 'reset', this.render, this ); 6395 6396 this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus ); 6397 6398 // Throttle the scroll handler and bind this. 6399 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 6400 6401 this.options.scrollElement = this.options.scrollElement || this.el; 6402 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 6403 6404 this.initSortable(); 6405 6406 _.bindAll( this, 'setColumns' ); 6407 6408 if ( this.options.resize ) { 6409 this.on( 'ready', this.bindEvents ); 6410 this.controller.on( 'open', this.setColumns ); 6411 6412 // Call this.setColumns() after this view has been rendered in the DOM so 6413 // attachments get proper width applied. 6414 _.defer( this.setColumns, this ); 6415 } 6416 }, 6417 6418 bindEvents: function() { 6419 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 6420 }, 6421 6422 attachmentFocus: function() { 6423 this.$( 'li:first' ).focus(); 6424 }, 6425 6426 restoreFocus: function() { 6427 this.$( 'li.selected:first' ).focus(); 6428 }, 6429 6430 arrowEvent: function( event ) { 6431 var attachments = this.$el.children( 'li' ), 6432 perRow = this.columns, 6433 index = attachments.filter( ':focus' ).index(), 6434 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 6435 6436 if ( index === -1 ) { 6437 return; 6438 } 6439 6440 // Left arrow 6441 if ( 37 === event.keyCode ) { 6442 if ( 0 === index ) { 6443 return; 6444 } 6445 attachments.eq( index - 1 ).focus(); 6446 } 6447 6448 // Up arrow 6449 if ( 38 === event.keyCode ) { 6450 if ( 1 === row ) { 6451 return; 6452 } 6453 attachments.eq( index - perRow ).focus(); 6454 } 6455 6456 // Right arrow 6457 if ( 39 === event.keyCode ) { 6458 if ( attachments.length === index ) { 6459 return; 6460 } 6461 attachments.eq( index + 1 ).focus(); 6462 } 6463 6464 // Down arrow 6465 if ( 40 === event.keyCode ) { 6466 if ( Math.ceil( attachments.length / perRow ) === row ) { 6467 return; 6468 } 6469 attachments.eq( index + perRow ).focus(); 6470 } 6471 }, 6472 6473 dispose: function() { 6474 this.collection.props.off( null, null, this ); 6475 if ( this.options.resize ) { 6476 this.$window.off( this.resizeEvent ); 6477 } 6478 6479 /** 6480 * call 'dispose' directly on the parent class 6481 */ 6482 View.prototype.dispose.apply( this, arguments ); 6483 }, 6484 6485 setColumns: function() { 6486 var prev = this.columns, 6487 width = this.$el.width(); 6488 6489 if ( width ) { 6490 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 6491 6492 if ( ! prev || prev !== this.columns ) { 6493 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 6494 } 6495 } 6496 }, 6497 6498 initSortable: function() { 6499 var collection = this.collection; 6500 6501 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 6502 return; 6503 } 6504 6505 this.$el.sortable( _.extend({ 6506 // If the `collection` has a `comparator`, disable sorting. 6507 disabled: !! collection.comparator, 6508 6509 // Change the position of the attachment as soon as the 6510 // mouse pointer overlaps a thumbnail. 6511 tolerance: 'pointer', 6512 6513 // Record the initial `index` of the dragged model. 6514 start: function( event, ui ) { 6515 ui.item.data('sortableIndexStart', ui.item.index()); 6516 }, 6517 6518 // Update the model's index in the collection. 6519 // Do so silently, as the view is already accurate. 6520 update: function( event, ui ) { 6521 var model = collection.at( ui.item.data('sortableIndexStart') ), 6522 comparator = collection.comparator; 6523 6524 // Temporarily disable the comparator to prevent `add` 6525 // from re-sorting. 6526 delete collection.comparator; 6527 6528 // Silently shift the model to its new index. 6529 collection.remove( model, { 6530 silent: true 6531 }); 6532 collection.add( model, { 6533 silent: true, 6534 at: ui.item.index() 6535 }); 6536 6537 // Restore the comparator. 6538 collection.comparator = comparator; 6539 6540 // Fire the `reset` event to ensure other collections sync. 6541 collection.trigger( 'reset', collection ); 6542 6543 // If the collection is sorted by menu order, 6544 // update the menu order. 6545 collection.saveMenuOrder(); 6546 } 6547 }, this.options.sortable ) ); 6548 6549 // If the `orderby` property is changed on the `collection`, 6550 // check to see if we have a `comparator`. If so, disable sorting. 6551 collection.props.on( 'change:orderby', function() { 6552 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 6553 }, this ); 6554 6555 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 6556 this.refreshSortable(); 6557 }, 6558 6559 refreshSortable: function() { 6560 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 6561 return; 6562 } 6563 6564 // If the `collection` has a `comparator`, disable sorting. 6565 var collection = this.collection, 6566 orderby = collection.props.get('orderby'), 6567 enabled = 'menuOrder' === orderby || ! collection.comparator; 6568 6569 this.$el.sortable( 'option', 'disabled', ! enabled ); 6570 }, 6571 6572 /** 6573 * @param {wp.media.model.Attachment} attachment 6574 * @returns {wp.media.View} 6575 */ 6576 createAttachmentView: function( attachment ) { 6577 var view = new this.options.AttachmentView({ 6578 controller: this.controller, 6579 model: attachment, 6580 collection: this.collection, 6581 selection: this.options.selection 6582 }); 6583 6584 return this._viewsByCid[ attachment.cid ] = view; 6585 }, 6586 6587 prepare: function() { 6588 // Create all of the Attachment views, and replace 6589 // the list in a single DOM operation. 6590 if ( this.collection.length ) { 6591 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 6592 6593 // If there are no elements, clear the views and load some. 6594 } else { 6595 this.views.unset(); 6596 this.collection.more().done( this.scroll ); 6597 } 6598 }, 6599 6600 ready: function() { 6601 // Trigger the scroll event to check if we're within the 6602 // threshold to query for additional attachments. 6603 this.scroll(); 6604 }, 6605 6606 scroll: function() { 6607 var view = this, 6608 el = this.options.scrollElement, 6609 scrollTop = el.scrollTop, 6610 toolbar; 6611 6612 // The scroll event occurs on the document, but the element 6613 // that should be checked is the document body. 6614 if ( el === document ) { 6615 el = document.body; 6616 scrollTop = $(document).scrollTop(); 6617 } 6618 6619 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 6620 return; 6621 } 6622 6623 toolbar = this.views.parent.toolbar; 6624 6625 // Show the spinner only if we are close to the bottom. 6626 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 6627 toolbar.get('spinner').show(); 6628 } 6629 6630 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 6631 this.collection.more().done(function() { 6632 view.scroll(); 6633 toolbar.get('spinner').hide(); 6634 }); 6635 } 6636 } 6637 }); 6638 6639 module.exports = Attachments; 6640 6641 6642 /***/ }), 6643 /* 73 */ 6644 /***/ (function(module, exports) { 6645 6646 /** 6647 * wp.media.view.Search 6648 * 6649 * @class 6650 * @augments wp.media.View 6651 * @augments wp.Backbone.View 6652 * @augments Backbone.View 6653 */ 6654 var l10n = wp.media.view.l10n, 6655 Search; 6656 6657 Search = wp.media.View.extend({ 6658 tagName: 'input', 6659 className: 'search', 6660 id: 'media-search-input', 6661 6662 attributes: { 6663 type: 'search', 6664 placeholder: l10n.search 6665 }, 6666 6667 events: { 6668 'input': 'search', 6669 'keyup': 'search', 6670 'change': 'search', 6671 'search': 'search' 6672 }, 6673 6674 /** 6675 * @returns {wp.media.view.Search} Returns itself to allow chaining 6676 */ 6677 render: function() { 6678 this.el.value = this.model.escape('search'); 6679 return this; 6680 }, 6681 6682 search: function( event ) { 6683 if ( event.target.value ) { 6684 this.model.set( 'search', event.target.value ); 6685 } else { 6686 this.model.unset('search'); 6687 } 6688 } 6689 }); 6690 6691 module.exports = Search; 6692 6693 6694 /***/ }), 6695 /* 74 */ 6696 /***/ (function(module, exports) { 6697 2387 module.exports = AttachmentCompat; 2388 2389 },{}],21:[function(require,module,exports){ 6698 2390 /** 6699 2391 * wp.media.view.AttachmentFilters … … 6772 2464 module.exports = AttachmentFilters; 6773 2465 6774 6775 /***/ }), 6776 /* 75 */ 6777 /***/ (function(module, exports) { 6778 2466 },{}],22:[function(require,module,exports){ 2467 /** 2468 * wp.media.view.AttachmentFilters.All 2469 * 2470 * @class 2471 * @augments wp.media.view.AttachmentFilters 2472 * @augments wp.media.View 2473 * @augments wp.Backbone.View 2474 * @augments Backbone.View 2475 */ 2476 var l10n = wp.media.view.l10n, 2477 All; 2478 2479 All = wp.media.view.AttachmentFilters.extend({ 2480 createFilters: function() { 2481 var filters = {}; 2482 2483 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 2484 filters[ key ] = { 2485 text: text, 2486 props: { 2487 status: null, 2488 type: key, 2489 uploadedTo: null, 2490 orderby: 'date', 2491 order: 'DESC' 2492 } 2493 }; 2494 }); 2495 2496 filters.all = { 2497 text: l10n.allMediaItems, 2498 props: { 2499 status: null, 2500 type: null, 2501 uploadedTo: null, 2502 orderby: 'date', 2503 order: 'DESC' 2504 }, 2505 priority: 10 2506 }; 2507 2508 if ( wp.media.view.settings.post.id ) { 2509 filters.uploaded = { 2510 text: l10n.uploadedToThisPost, 2511 props: { 2512 status: null, 2513 type: null, 2514 uploadedTo: wp.media.view.settings.post.id, 2515 orderby: 'menuOrder', 2516 order: 'ASC' 2517 }, 2518 priority: 20 2519 }; 2520 } 2521 2522 filters.unattached = { 2523 text: l10n.unattached, 2524 props: { 2525 status: null, 2526 uploadedTo: 0, 2527 type: null, 2528 orderby: 'menuOrder', 2529 order: 'ASC' 2530 }, 2531 priority: 50 2532 }; 2533 2534 if ( wp.media.view.settings.mediaTrash && 2535 this.controller.isModeActive( 'grid' ) ) { 2536 2537 filters.trash = { 2538 text: l10n.trash, 2539 props: { 2540 uploadedTo: null, 2541 status: 'trash', 2542 type: null, 2543 orderby: 'date', 2544 order: 'DESC' 2545 }, 2546 priority: 50 2547 }; 2548 } 2549 2550 this.filters = filters; 2551 } 2552 }); 2553 2554 module.exports = All; 2555 2556 },{}],23:[function(require,module,exports){ 6779 2557 /** 6780 2558 * A filter dropdown for month/dates. … … 6817 2595 module.exports = DateFilter; 6818 2596 6819 6820 /***/ }), 6821 /* 76 */ 6822 /***/ (function(module, exports) { 6823 2597 },{}],24:[function(require,module,exports){ 6824 2598 /** 6825 2599 * wp.media.view.AttachmentFilters.Uploaded … … 6880 2654 module.exports = Uploaded; 6881 2655 6882 6883 /***/ }), 6884 /* 77 */ 6885 /***/ (function(module, exports) { 6886 2656 },{}],25:[function(require,module,exports){ 6887 2657 /** 6888 * wp.media.view.Attachment Filters.All2658 * wp.media.view.Attachment 6889 2659 * 6890 2660 * @class 6891 * @augments wp.media.view.AttachmentFilters6892 2661 * @augments wp.media.View 6893 2662 * @augments wp.Backbone.View 6894 2663 * @augments Backbone.View 6895 2664 */ 6896 var l10n = wp.media.view.l10n, 6897 All; 6898 6899 All = wp.media.view.AttachmentFilters.extend({ 6900 createFilters: function() { 6901 var filters = {}; 6902 6903 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 6904 filters[ key ] = { 6905 text: text, 6906 props: { 6907 status: null, 6908 type: key, 6909 uploadedTo: null, 6910 orderby: 'date', 6911 order: 'DESC' 6912 } 6913 }; 2665 var View = wp.media.View, 2666 $ = jQuery, 2667 Attachment; 2668 2669 Attachment = View.extend({ 2670 tagName: 'li', 2671 className: 'attachment', 2672 template: wp.template('attachment'), 2673 2674 attributes: function() { 2675 return { 2676 'tabIndex': 0, 2677 'role': 'checkbox', 2678 'aria-label': this.model.get( 'title' ), 2679 'aria-checked': false, 2680 'data-id': this.model.get( 'id' ) 2681 }; 2682 }, 2683 2684 events: { 2685 'click .js--select-attachment': 'toggleSelectionHandler', 2686 'change [data-setting]': 'updateSetting', 2687 'change [data-setting] input': 'updateSetting', 2688 'change [data-setting] select': 'updateSetting', 2689 'change [data-setting] textarea': 'updateSetting', 2690 'click .attachment-close': 'removeFromLibrary', 2691 'click .check': 'checkClickHandler', 2692 'keydown': 'toggleSelectionHandler' 2693 }, 2694 2695 buttons: {}, 2696 2697 initialize: function() { 2698 var selection = this.options.selection, 2699 options = _.defaults( this.options, { 2700 rerenderOnModelChange: true 2701 } ); 2702 2703 if ( options.rerenderOnModelChange ) { 2704 this.listenTo( this.model, 'change', this.render ); 2705 } else { 2706 this.listenTo( this.model, 'change:percent', this.progress ); 2707 } 2708 this.listenTo( this.model, 'change:title', this._syncTitle ); 2709 this.listenTo( this.model, 'change:caption', this._syncCaption ); 2710 this.listenTo( this.model, 'change:artist', this._syncArtist ); 2711 this.listenTo( this.model, 'change:album', this._syncAlbum ); 2712 2713 // Update the selection. 2714 this.listenTo( this.model, 'add', this.select ); 2715 this.listenTo( this.model, 'remove', this.deselect ); 2716 if ( selection ) { 2717 selection.on( 'reset', this.updateSelect, this ); 2718 // Update the model's details view. 2719 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 2720 this.details( this.model, this.controller.state().get('selection') ); 2721 } 2722 2723 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 2724 }, 2725 /** 2726 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 2727 */ 2728 dispose: function() { 2729 var selection = this.options.selection; 2730 2731 // Make sure all settings are saved before removing the view. 2732 this.updateAll(); 2733 2734 if ( selection ) { 2735 selection.off( null, null, this ); 2736 } 2737 /** 2738 * call 'dispose' directly on the parent class 2739 */ 2740 View.prototype.dispose.apply( this, arguments ); 2741 return this; 2742 }, 2743 /** 2744 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 2745 */ 2746 render: function() { 2747 var options = _.defaults( this.model.toJSON(), { 2748 orientation: 'landscape', 2749 uploading: false, 2750 type: '', 2751 subtype: '', 2752 icon: '', 2753 filename: '', 2754 caption: '', 2755 title: '', 2756 dateFormatted: '', 2757 width: '', 2758 height: '', 2759 compat: false, 2760 alt: '', 2761 description: '' 2762 }, this.options ); 2763 2764 options.buttons = this.buttons; 2765 options.describe = this.controller.state().get('describe'); 2766 2767 if ( 'image' === options.type ) { 2768 options.size = this.imageSize(); 2769 } 2770 2771 options.can = {}; 2772 if ( options.nonces ) { 2773 options.can.remove = !! options.nonces['delete']; 2774 options.can.save = !! options.nonces.update; 2775 } 2776 2777 if ( this.controller.state().get('allowLocalEdits') ) { 2778 options.allowLocalEdits = true; 2779 } 2780 2781 if ( options.uploading && ! options.percent ) { 2782 options.percent = 0; 2783 } 2784 2785 this.views.detach(); 2786 this.$el.html( this.template( options ) ); 2787 2788 this.$el.toggleClass( 'uploading', options.uploading ); 2789 2790 if ( options.uploading ) { 2791 this.$bar = this.$('.media-progress-bar div'); 2792 } else { 2793 delete this.$bar; 2794 } 2795 2796 // Check if the model is selected. 2797 this.updateSelect(); 2798 2799 // Update the save status. 2800 this.updateSave(); 2801 2802 this.views.render(); 2803 2804 return this; 2805 }, 2806 2807 progress: function() { 2808 if ( this.$bar && this.$bar.length ) { 2809 this.$bar.width( this.model.get('percent') + '%' ); 2810 } 2811 }, 2812 2813 /** 2814 * @param {Object} event 2815 */ 2816 toggleSelectionHandler: function( event ) { 2817 var method; 2818 2819 // Don't do anything inside inputs and on the attachment check and remove buttons. 2820 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) { 2821 return; 2822 } 2823 2824 // Catch arrow events 2825 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 2826 this.controller.trigger( 'attachment:keydown:arrow', event ); 2827 return; 2828 } 2829 2830 // Catch enter and space events 2831 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 2832 return; 2833 } 2834 2835 event.preventDefault(); 2836 2837 // In the grid view, bubble up an edit:attachment event to the controller. 2838 if ( this.controller.isModeActive( 'grid' ) ) { 2839 if ( this.controller.isModeActive( 'edit' ) ) { 2840 // Pass the current target to restore focus when closing 2841 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 2842 return; 2843 } 2844 2845 if ( this.controller.isModeActive( 'select' ) ) { 2846 method = 'toggle'; 2847 } 2848 } 2849 2850 if ( event.shiftKey ) { 2851 method = 'between'; 2852 } else if ( event.ctrlKey || event.metaKey ) { 2853 method = 'toggle'; 2854 } 2855 2856 this.toggleSelection({ 2857 method: method 6914 2858 }); 6915 2859 6916 filters.all = { 6917 text: l10n.allMediaItems, 6918 props: { 6919 status: null, 6920 type: null, 6921 uploadedTo: null, 6922 orderby: 'date', 6923 order: 'DESC' 6924 }, 6925 priority: 10 2860 this.controller.trigger( 'selection:toggle' ); 2861 }, 2862 /** 2863 * @param {Object} options 2864 */ 2865 toggleSelection: function( options ) { 2866 var collection = this.collection, 2867 selection = this.options.selection, 2868 model = this.model, 2869 method = options && options.method, 2870 single, models, singleIndex, modelIndex; 2871 2872 if ( ! selection ) { 2873 return; 2874 } 2875 2876 single = selection.single(); 2877 method = _.isUndefined( method ) ? selection.multiple : method; 2878 2879 // If the `method` is set to `between`, select all models that 2880 // exist between the current and the selected model. 2881 if ( 'between' === method && single && selection.multiple ) { 2882 // If the models are the same, short-circuit. 2883 if ( single === model ) { 2884 return; 2885 } 2886 2887 singleIndex = collection.indexOf( single ); 2888 modelIndex = collection.indexOf( this.model ); 2889 2890 if ( singleIndex < modelIndex ) { 2891 models = collection.models.slice( singleIndex, modelIndex + 1 ); 2892 } else { 2893 models = collection.models.slice( modelIndex, singleIndex + 1 ); 2894 } 2895 2896 selection.add( models ); 2897 selection.single( model ); 2898 return; 2899 2900 // If the `method` is set to `toggle`, just flip the selection 2901 // status, regardless of whether the model is the single model. 2902 } else if ( 'toggle' === method ) { 2903 selection[ this.selected() ? 'remove' : 'add' ]( model ); 2904 selection.single( model ); 2905 return; 2906 } else if ( 'add' === method ) { 2907 selection.add( model ); 2908 selection.single( model ); 2909 return; 2910 } 2911 2912 // Fixes bug that loses focus when selecting a featured image 2913 if ( ! method ) { 2914 method = 'add'; 2915 } 2916 2917 if ( method !== 'add' ) { 2918 method = 'reset'; 2919 } 2920 2921 if ( this.selected() ) { 2922 // If the model is the single model, remove it. 2923 // If it is not the same as the single model, 2924 // it now becomes the single model. 2925 selection[ single === model ? 'remove' : 'single' ]( model ); 2926 } else { 2927 // If the model is not selected, run the `method` on the 2928 // selection. By default, we `reset` the selection, but the 2929 // `method` can be set to `add` the model to the selection. 2930 selection[ method ]( model ); 2931 selection.single( model ); 2932 } 2933 }, 2934 2935 updateSelect: function() { 2936 this[ this.selected() ? 'select' : 'deselect' ](); 2937 }, 2938 /** 2939 * @returns {unresolved|Boolean} 2940 */ 2941 selected: function() { 2942 var selection = this.options.selection; 2943 if ( selection ) { 2944 return !! selection.get( this.model.cid ); 2945 } 2946 }, 2947 /** 2948 * @param {Backbone.Model} model 2949 * @param {Backbone.Collection} collection 2950 */ 2951 select: function( model, collection ) { 2952 var selection = this.options.selection, 2953 controller = this.controller; 2954 2955 // Check if a selection exists and if it's the collection provided. 2956 // If they're not the same collection, bail; we're in another 2957 // selection's event loop. 2958 if ( ! selection || ( collection && collection !== selection ) ) { 2959 return; 2960 } 2961 2962 // Bail if the model is already selected. 2963 if ( this.$el.hasClass( 'selected' ) ) { 2964 return; 2965 } 2966 2967 // Add 'selected' class to model, set aria-checked to true. 2968 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 2969 // Make the checkbox tabable, except in media grid (bulk select mode). 2970 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 2971 this.$( '.check' ).attr( 'tabindex', '0' ); 2972 } 2973 }, 2974 /** 2975 * @param {Backbone.Model} model 2976 * @param {Backbone.Collection} collection 2977 */ 2978 deselect: function( model, collection ) { 2979 var selection = this.options.selection; 2980 2981 // Check if a selection exists and if it's the collection provided. 2982 // If they're not the same collection, bail; we're in another 2983 // selection's event loop. 2984 if ( ! selection || ( collection && collection !== selection ) ) { 2985 return; 2986 } 2987 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 2988 .find( '.check' ).attr( 'tabindex', '-1' ); 2989 }, 2990 /** 2991 * @param {Backbone.Model} model 2992 * @param {Backbone.Collection} collection 2993 */ 2994 details: function( model, collection ) { 2995 var selection = this.options.selection, 2996 details; 2997 2998 if ( selection !== collection ) { 2999 return; 3000 } 3001 3002 details = selection.single(); 3003 this.$el.toggleClass( 'details', details === this.model ); 3004 }, 3005 /** 3006 * @param {string} size 3007 * @returns {Object} 3008 */ 3009 imageSize: function( size ) { 3010 var sizes = this.model.get('sizes'), matched = false; 3011 3012 size = size || 'medium'; 3013 3014 // Use the provided image size if possible. 3015 if ( sizes ) { 3016 if ( sizes[ size ] ) { 3017 matched = sizes[ size ]; 3018 } else if ( sizes.large ) { 3019 matched = sizes.large; 3020 } else if ( sizes.thumbnail ) { 3021 matched = sizes.thumbnail; 3022 } else if ( sizes.full ) { 3023 matched = sizes.full; 3024 } 3025 3026 if ( matched ) { 3027 return _.clone( matched ); 3028 } 3029 } 3030 3031 return { 3032 url: this.model.get('url'), 3033 width: this.model.get('width'), 3034 height: this.model.get('height'), 3035 orientation: this.model.get('orientation') 6926 3036 }; 6927 6928 if ( wp.media.view.settings.post.id ) { 6929 filters.uploaded = { 6930 text: l10n.uploadedToThisPost, 6931 props: { 6932 status: null, 6933 type: null, 6934 uploadedTo: wp.media.view.settings.post.id, 6935 orderby: 'menuOrder', 6936 order: 'ASC' 6937 }, 6938 priority: 20 6939 }; 6940 } 6941 6942 filters.unattached = { 6943 text: l10n.unattached, 6944 props: { 6945 status: null, 6946 uploadedTo: 0, 6947 type: null, 6948 orderby: 'menuOrder', 6949 order: 'ASC' 6950 }, 6951 priority: 50 6952 }; 6953 6954 if ( wp.media.view.settings.mediaTrash && 6955 this.controller.isModeActive( 'grid' ) ) { 6956 6957 filters.trash = { 6958 text: l10n.trash, 6959 props: { 6960 uploadedTo: null, 6961 status: 'trash', 6962 type: null, 6963 orderby: 'date', 6964 order: 'DESC' 6965 }, 6966 priority: 50 6967 }; 6968 } 6969 6970 this.filters = filters; 3037 }, 3038 /** 3039 * @param {Object} event 3040 */ 3041 updateSetting: function( event ) { 3042 var $setting = $( event.target ).closest('[data-setting]'), 3043 setting, value; 3044 3045 if ( ! $setting.length ) { 3046 return; 3047 } 3048 3049 setting = $setting.data('setting'); 3050 value = event.target.value; 3051 3052 if ( this.model.get( setting ) !== value ) { 3053 this.save( setting, value ); 3054 } 3055 }, 3056 3057 /** 3058 * Pass all the arguments to the model's save method. 3059 * 3060 * Records the aggregate status of all save requests and updates the 3061 * view's classes accordingly. 3062 */ 3063 save: function() { 3064 var view = this, 3065 save = this._save = this._save || { status: 'ready' }, 3066 request = this.model.save.apply( this.model, arguments ), 3067 requests = save.requests ? $.when( request, save.requests ) : request; 3068 3069 // If we're waiting to remove 'Saved.', stop. 3070 if ( save.savedTimer ) { 3071 clearTimeout( save.savedTimer ); 3072 } 3073 3074 this.updateSave('waiting'); 3075 save.requests = requests; 3076 requests.always( function() { 3077 // If we've performed another request since this one, bail. 3078 if ( save.requests !== requests ) { 3079 return; 3080 } 3081 3082 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 3083 save.savedTimer = setTimeout( function() { 3084 view.updateSave('ready'); 3085 delete save.savedTimer; 3086 }, 2000 ); 3087 }); 3088 }, 3089 /** 3090 * @param {string} status 3091 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3092 */ 3093 updateSave: function( status ) { 3094 var save = this._save = this._save || { status: 'ready' }; 3095 3096 if ( status && status !== save.status ) { 3097 this.$el.removeClass( 'save-' + save.status ); 3098 save.status = status; 3099 } 3100 3101 this.$el.addClass( 'save-' + save.status ); 3102 return this; 3103 }, 3104 3105 updateAll: function() { 3106 var $settings = this.$('[data-setting]'), 3107 model = this.model, 3108 changed; 3109 3110 changed = _.chain( $settings ).map( function( el ) { 3111 var $input = $('input, textarea, select, [value]', el ), 3112 setting, value; 3113 3114 if ( ! $input.length ) { 3115 return; 3116 } 3117 3118 setting = $(el).data('setting'); 3119 value = $input.val(); 3120 3121 // Record the value if it changed. 3122 if ( model.get( setting ) !== value ) { 3123 return [ setting, value ]; 3124 } 3125 }).compact().object().value(); 3126 3127 if ( ! _.isEmpty( changed ) ) { 3128 model.save( changed ); 3129 } 3130 }, 3131 /** 3132 * @param {Object} event 3133 */ 3134 removeFromLibrary: function( event ) { 3135 // Catch enter and space events 3136 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 3137 return; 3138 } 3139 3140 // Stop propagation so the model isn't selected. 3141 event.stopPropagation(); 3142 3143 this.collection.remove( this.model ); 3144 }, 3145 3146 /** 3147 * Add the model if it isn't in the selection, if it is in the selection, 3148 * remove it. 3149 * 3150 * @param {[type]} event [description] 3151 * @return {[type]} [description] 3152 */ 3153 checkClickHandler: function ( event ) { 3154 var selection = this.options.selection; 3155 if ( ! selection ) { 3156 return; 3157 } 3158 event.stopPropagation(); 3159 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 3160 selection.remove( this.model ); 3161 // Move focus back to the attachment tile (from the check). 3162 this.$el.focus(); 3163 } else { 3164 selection.add( this.model ); 3165 } 6971 3166 } 6972 3167 }); 6973 3168 6974 module.exports = All; 6975 6976 6977 /***/ }), 6978 /* 78 */ 6979 /***/ (function(module, exports) { 6980 3169 // Ensure settings remain in sync between attachment views. 3170 _.each({ 3171 caption: '_syncCaption', 3172 title: '_syncTitle', 3173 artist: '_syncArtist', 3174 album: '_syncAlbum' 3175 }, function( method, setting ) { 3176 /** 3177 * @param {Backbone.Model} model 3178 * @param {string} value 3179 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3180 */ 3181 Attachment.prototype[ method ] = function( model, value ) { 3182 var $setting = this.$('[data-setting="' + setting + '"]'); 3183 3184 if ( ! $setting.length ) { 3185 return this; 3186 } 3187 3188 // If the updated value is in sync with the value in the DOM, there 3189 // is no need to re-render. If we're currently editing the value, 3190 // it will automatically be in sync, suppressing the re-render for 3191 // the view we're editing, while updating any others. 3192 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 3193 return this; 3194 } 3195 3196 return this.render(); 3197 }; 3198 }); 3199 3200 module.exports = Attachment; 3201 3202 },{}],26:[function(require,module,exports){ 3203 /** 3204 * wp.media.view.Attachment.Details 3205 * 3206 * @class 3207 * @augments wp.media.view.Attachment 3208 * @augments wp.media.View 3209 * @augments wp.Backbone.View 3210 * @augments Backbone.View 3211 */ 3212 var Attachment = wp.media.view.Attachment, 3213 l10n = wp.media.view.l10n, 3214 Details; 3215 3216 Details = Attachment.extend({ 3217 tagName: 'div', 3218 className: 'attachment-details', 3219 template: wp.template('attachment-details'), 3220 3221 attributes: function() { 3222 return { 3223 'tabIndex': 0, 3224 'data-id': this.model.get( 'id' ) 3225 }; 3226 }, 3227 3228 events: { 3229 'change [data-setting]': 'updateSetting', 3230 'change [data-setting] input': 'updateSetting', 3231 'change [data-setting] select': 'updateSetting', 3232 'change [data-setting] textarea': 'updateSetting', 3233 'click .delete-attachment': 'deleteAttachment', 3234 'click .trash-attachment': 'trashAttachment', 3235 'click .untrash-attachment': 'untrashAttachment', 3236 'click .edit-attachment': 'editAttachment', 3237 'keydown': 'toggleSelectionHandler' 3238 }, 3239 3240 initialize: function() { 3241 this.options = _.defaults( this.options, { 3242 rerenderOnModelChange: false 3243 }); 3244 3245 this.on( 'ready', this.initialFocus ); 3246 // Call 'initialize' directly on the parent class. 3247 Attachment.prototype.initialize.apply( this, arguments ); 3248 }, 3249 3250 initialFocus: function() { 3251 if ( ! wp.media.isTouchDevice ) { 3252 /* 3253 Previously focused the first ':input' (the readonly URL text field). 3254 Since the first ':input' is now a button (delete/trash): when pressing 3255 spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment 3256 as soon as focus is moved. Explicitly target the first text field for now. 3257 @todo change initial focus logic, also for accessibility. 3258 */ 3259 this.$( 'input[type="text"]' ).eq( 0 ).focus(); 3260 } 3261 }, 3262 /** 3263 * @param {Object} event 3264 */ 3265 deleteAttachment: function( event ) { 3266 event.preventDefault(); 3267 3268 if ( window.confirm( l10n.warnDelete ) ) { 3269 this.model.destroy(); 3270 // Keep focus inside media modal 3271 // after image is deleted 3272 this.controller.modal.focusManager.focus(); 3273 } 3274 }, 3275 /** 3276 * @param {Object} event 3277 */ 3278 trashAttachment: function( event ) { 3279 var library = this.controller.library; 3280 event.preventDefault(); 3281 3282 if ( wp.media.view.settings.mediaTrash && 3283 'edit-metadata' === this.controller.content.mode() ) { 3284 3285 this.model.set( 'status', 'trash' ); 3286 this.model.save().done( function() { 3287 library._requery( true ); 3288 } ); 3289 } else { 3290 this.model.destroy(); 3291 } 3292 }, 3293 /** 3294 * @param {Object} event 3295 */ 3296 untrashAttachment: function( event ) { 3297 var library = this.controller.library; 3298 event.preventDefault(); 3299 3300 this.model.set( 'status', 'inherit' ); 3301 this.model.save().done( function() { 3302 library._requery( true ); 3303 } ); 3304 }, 3305 /** 3306 * @param {Object} event 3307 */ 3308 editAttachment: function( event ) { 3309 var editState = this.controller.states.get( 'edit-image' ); 3310 if ( window.imageEdit && editState ) { 3311 event.preventDefault(); 3312 3313 editState.set( 'image', this.model ); 3314 this.controller.setState( 'edit-image' ); 3315 } else { 3316 this.$el.addClass('needs-refresh'); 3317 } 3318 }, 3319 /** 3320 * When reverse tabbing(shift+tab) out of the right details panel, deliver 3321 * the focus to the item in the list that was being edited. 3322 * 3323 * @param {Object} event 3324 */ 3325 toggleSelectionHandler: function( event ) { 3326 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 3327 this.controller.trigger( 'attachment:details:shift-tab', event ); 3328 return false; 3329 } 3330 3331 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 3332 this.controller.trigger( 'attachment:keydown:arrow', event ); 3333 return; 3334 } 3335 } 3336 }); 3337 3338 module.exports = Details; 3339 3340 },{}],27:[function(require,module,exports){ 3341 /** 3342 * wp.media.view.Attachment.EditLibrary 3343 * 3344 * @class 3345 * @augments wp.media.view.Attachment 3346 * @augments wp.media.View 3347 * @augments wp.Backbone.View 3348 * @augments Backbone.View 3349 */ 3350 var EditLibrary = wp.media.view.Attachment.extend({ 3351 buttons: { 3352 close: true 3353 } 3354 }); 3355 3356 module.exports = EditLibrary; 3357 3358 },{}],28:[function(require,module,exports){ 3359 /** 3360 * wp.media.view.Attachments.EditSelection 3361 * 3362 * @class 3363 * @augments wp.media.view.Attachment.Selection 3364 * @augments wp.media.view.Attachment 3365 * @augments wp.media.View 3366 * @augments wp.Backbone.View 3367 * @augments Backbone.View 3368 */ 3369 var EditSelection = wp.media.view.Attachment.Selection.extend({ 3370 buttons: { 3371 close: true 3372 } 3373 }); 3374 3375 module.exports = EditSelection; 3376 3377 },{}],29:[function(require,module,exports){ 3378 /** 3379 * wp.media.view.Attachment.Library 3380 * 3381 * @class 3382 * @augments wp.media.view.Attachment 3383 * @augments wp.media.View 3384 * @augments wp.Backbone.View 3385 * @augments Backbone.View 3386 */ 3387 var Library = wp.media.view.Attachment.extend({ 3388 buttons: { 3389 check: true 3390 } 3391 }); 3392 3393 module.exports = Library; 3394 3395 },{}],30:[function(require,module,exports){ 3396 /** 3397 * wp.media.view.Attachment.Selection 3398 * 3399 * @class 3400 * @augments wp.media.view.Attachment 3401 * @augments wp.media.View 3402 * @augments wp.Backbone.View 3403 * @augments Backbone.View 3404 */ 3405 var Selection = wp.media.view.Attachment.extend({ 3406 className: 'attachment selection', 3407 3408 // On click, just select the model, instead of removing the model from 3409 // the selection. 3410 toggleSelection: function() { 3411 this.options.selection.single( this.model ); 3412 } 3413 }); 3414 3415 module.exports = Selection; 3416 3417 },{}],31:[function(require,module,exports){ 3418 /** 3419 * wp.media.view.Attachments 3420 * 3421 * @class 3422 * @augments wp.media.View 3423 * @augments wp.Backbone.View 3424 * @augments Backbone.View 3425 */ 3426 var View = wp.media.View, 3427 $ = jQuery, 3428 Attachments; 3429 3430 Attachments = View.extend({ 3431 tagName: 'ul', 3432 className: 'attachments', 3433 3434 attributes: { 3435 tabIndex: -1 3436 }, 3437 3438 initialize: function() { 3439 this.el.id = _.uniqueId('__attachments-view-'); 3440 3441 _.defaults( this.options, { 3442 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 3443 refreshThreshold: 3, 3444 AttachmentView: wp.media.view.Attachment, 3445 sortable: false, 3446 resize: true, 3447 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 3448 }); 3449 3450 this._viewsByCid = {}; 3451 this.$window = $( window ); 3452 this.resizeEvent = 'resize.media-modal-columns'; 3453 3454 this.collection.on( 'add', function( attachment ) { 3455 this.views.add( this.createAttachmentView( attachment ), { 3456 at: this.collection.indexOf( attachment ) 3457 }); 3458 }, this ); 3459 3460 this.collection.on( 'remove', function( attachment ) { 3461 var view = this._viewsByCid[ attachment.cid ]; 3462 delete this._viewsByCid[ attachment.cid ]; 3463 3464 if ( view ) { 3465 view.remove(); 3466 } 3467 }, this ); 3468 3469 this.collection.on( 'reset', this.render, this ); 3470 3471 this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus ); 3472 3473 // Throttle the scroll handler and bind this. 3474 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 3475 3476 this.options.scrollElement = this.options.scrollElement || this.el; 3477 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 3478 3479 this.initSortable(); 3480 3481 _.bindAll( this, 'setColumns' ); 3482 3483 if ( this.options.resize ) { 3484 this.on( 'ready', this.bindEvents ); 3485 this.controller.on( 'open', this.setColumns ); 3486 3487 // Call this.setColumns() after this view has been rendered in the DOM so 3488 // attachments get proper width applied. 3489 _.defer( this.setColumns, this ); 3490 } 3491 }, 3492 3493 bindEvents: function() { 3494 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 3495 }, 3496 3497 attachmentFocus: function() { 3498 this.$( 'li:first' ).focus(); 3499 }, 3500 3501 restoreFocus: function() { 3502 this.$( 'li.selected:first' ).focus(); 3503 }, 3504 3505 arrowEvent: function( event ) { 3506 var attachments = this.$el.children( 'li' ), 3507 perRow = this.columns, 3508 index = attachments.filter( ':focus' ).index(), 3509 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 3510 3511 if ( index === -1 ) { 3512 return; 3513 } 3514 3515 // Left arrow 3516 if ( 37 === event.keyCode ) { 3517 if ( 0 === index ) { 3518 return; 3519 } 3520 attachments.eq( index - 1 ).focus(); 3521 } 3522 3523 // Up arrow 3524 if ( 38 === event.keyCode ) { 3525 if ( 1 === row ) { 3526 return; 3527 } 3528 attachments.eq( index - perRow ).focus(); 3529 } 3530 3531 // Right arrow 3532 if ( 39 === event.keyCode ) { 3533 if ( attachments.length === index ) { 3534 return; 3535 } 3536 attachments.eq( index + 1 ).focus(); 3537 } 3538 3539 // Down arrow 3540 if ( 40 === event.keyCode ) { 3541 if ( Math.ceil( attachments.length / perRow ) === row ) { 3542 return; 3543 } 3544 attachments.eq( index + perRow ).focus(); 3545 } 3546 }, 3547 3548 dispose: function() { 3549 this.collection.props.off( null, null, this ); 3550 if ( this.options.resize ) { 3551 this.$window.off( this.resizeEvent ); 3552 } 3553 3554 /** 3555 * call 'dispose' directly on the parent class 3556 */ 3557 View.prototype.dispose.apply( this, arguments ); 3558 }, 3559 3560 setColumns: function() { 3561 var prev = this.columns, 3562 width = this.$el.width(); 3563 3564 if ( width ) { 3565 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 3566 3567 if ( ! prev || prev !== this.columns ) { 3568 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 3569 } 3570 } 3571 }, 3572 3573 initSortable: function() { 3574 var collection = this.collection; 3575 3576 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 3577 return; 3578 } 3579 3580 this.$el.sortable( _.extend({ 3581 // If the `collection` has a `comparator`, disable sorting. 3582 disabled: !! collection.comparator, 3583 3584 // Change the position of the attachment as soon as the 3585 // mouse pointer overlaps a thumbnail. 3586 tolerance: 'pointer', 3587 3588 // Record the initial `index` of the dragged model. 3589 start: function( event, ui ) { 3590 ui.item.data('sortableIndexStart', ui.item.index()); 3591 }, 3592 3593 // Update the model's index in the collection. 3594 // Do so silently, as the view is already accurate. 3595 update: function( event, ui ) { 3596 var model = collection.at( ui.item.data('sortableIndexStart') ), 3597 comparator = collection.comparator; 3598 3599 // Temporarily disable the comparator to prevent `add` 3600 // from re-sorting. 3601 delete collection.comparator; 3602 3603 // Silently shift the model to its new index. 3604 collection.remove( model, { 3605 silent: true 3606 }); 3607 collection.add( model, { 3608 silent: true, 3609 at: ui.item.index() 3610 }); 3611 3612 // Restore the comparator. 3613 collection.comparator = comparator; 3614 3615 // Fire the `reset` event to ensure other collections sync. 3616 collection.trigger( 'reset', collection ); 3617 3618 // If the collection is sorted by menu order, 3619 // update the menu order. 3620 collection.saveMenuOrder(); 3621 } 3622 }, this.options.sortable ) ); 3623 3624 // If the `orderby` property is changed on the `collection`, 3625 // check to see if we have a `comparator`. If so, disable sorting. 3626 collection.props.on( 'change:orderby', function() { 3627 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 3628 }, this ); 3629 3630 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 3631 this.refreshSortable(); 3632 }, 3633 3634 refreshSortable: function() { 3635 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 3636 return; 3637 } 3638 3639 // If the `collection` has a `comparator`, disable sorting. 3640 var collection = this.collection, 3641 orderby = collection.props.get('orderby'), 3642 enabled = 'menuOrder' === orderby || ! collection.comparator; 3643 3644 this.$el.sortable( 'option', 'disabled', ! enabled ); 3645 }, 3646 3647 /** 3648 * @param {wp.media.model.Attachment} attachment 3649 * @returns {wp.media.View} 3650 */ 3651 createAttachmentView: function( attachment ) { 3652 var view = new this.options.AttachmentView({ 3653 controller: this.controller, 3654 model: attachment, 3655 collection: this.collection, 3656 selection: this.options.selection 3657 }); 3658 3659 return this._viewsByCid[ attachment.cid ] = view; 3660 }, 3661 3662 prepare: function() { 3663 // Create all of the Attachment views, and replace 3664 // the list in a single DOM operation. 3665 if ( this.collection.length ) { 3666 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 3667 3668 // If there are no elements, clear the views and load some. 3669 } else { 3670 this.views.unset(); 3671 this.collection.more().done( this.scroll ); 3672 } 3673 }, 3674 3675 ready: function() { 3676 // Trigger the scroll event to check if we're within the 3677 // threshold to query for additional attachments. 3678 this.scroll(); 3679 }, 3680 3681 scroll: function() { 3682 var view = this, 3683 el = this.options.scrollElement, 3684 scrollTop = el.scrollTop, 3685 toolbar; 3686 3687 // The scroll event occurs on the document, but the element 3688 // that should be checked is the document body. 3689 if ( el === document ) { 3690 el = document.body; 3691 scrollTop = $(document).scrollTop(); 3692 } 3693 3694 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 3695 return; 3696 } 3697 3698 toolbar = this.views.parent.toolbar; 3699 3700 // Show the spinner only if we are close to the bottom. 3701 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 3702 toolbar.get('spinner').show(); 3703 } 3704 3705 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 3706 this.collection.more().done(function() { 3707 view.scroll(); 3708 toolbar.get('spinner').hide(); 3709 }); 3710 } 3711 } 3712 }); 3713 3714 module.exports = Attachments; 3715 3716 },{}],32:[function(require,module,exports){ 6981 3717 /** 6982 3718 * wp.media.view.AttachmentsBrowser … … 7422 4158 module.exports = AttachmentsBrowser; 7423 4159 7424 7425 /***/ }), 7426 /* 79 */ 7427 /***/ (function(module, exports) { 7428 7429 /** 7430 * wp.media.view.Selection 7431 * 7432 * @class 7433 * @augments wp.media.View 7434 * @augments wp.Backbone.View 7435 * @augments Backbone.View 7436 */ 7437 var l10n = wp.media.view.l10n, 7438 Selection; 7439 7440 Selection = wp.media.View.extend({ 7441 tagName: 'div', 7442 className: 'media-selection', 7443 template: wp.template('media-selection'), 7444 7445 events: { 7446 'click .edit-selection': 'edit', 7447 'click .clear-selection': 'clear' 7448 }, 7449 7450 initialize: function() { 7451 _.defaults( this.options, { 7452 editable: false, 7453 clearable: true 7454 }); 7455 7456 /** 7457 * @member {wp.media.view.Attachments.Selection} 7458 */ 7459 this.attachments = new wp.media.view.Attachments.Selection({ 7460 controller: this.controller, 7461 collection: this.collection, 7462 selection: this.collection, 7463 model: new Backbone.Model() 7464 }); 7465 7466 this.views.set( '.selection-view', this.attachments ); 7467 this.collection.on( 'add remove reset', this.refresh, this ); 7468 this.controller.on( 'content:activate', this.refresh, this ); 7469 }, 7470 7471 ready: function() { 7472 this.refresh(); 7473 }, 7474 7475 refresh: function() { 7476 // If the selection hasn't been rendered, bail. 7477 if ( ! this.$el.children().length ) { 7478 return; 7479 } 7480 7481 var collection = this.collection, 7482 editing = 'edit-selection' === this.controller.content.mode(); 7483 7484 // If nothing is selected, display nothing. 7485 this.$el.toggleClass( 'empty', ! collection.length ); 7486 this.$el.toggleClass( 'one', 1 === collection.length ); 7487 this.$el.toggleClass( 'editing', editing ); 7488 7489 this.$('.count').text( l10n.selected.replace('%d', collection.length) ); 7490 }, 7491 7492 edit: function( event ) { 7493 event.preventDefault(); 7494 if ( this.options.editable ) { 7495 this.options.editable.call( this, this.collection ); 7496 } 7497 }, 7498 7499 clear: function( event ) { 7500 event.preventDefault(); 7501 this.collection.reset(); 7502 7503 // Keep focus inside media modal 7504 // after clear link is selected 7505 this.controller.modal.focusManager.focus(); 7506 } 7507 }); 7508 7509 module.exports = Selection; 7510 7511 7512 /***/ }), 7513 /* 80 */ 7514 /***/ (function(module, exports) { 7515 7516 /** 7517 * wp.media.view.Attachment.Selection 7518 * 7519 * @class 7520 * @augments wp.media.view.Attachment 7521 * @augments wp.media.View 7522 * @augments wp.Backbone.View 7523 * @augments Backbone.View 7524 */ 7525 var Selection = wp.media.view.Attachment.extend({ 7526 className: 'attachment selection', 7527 7528 // On click, just select the model, instead of removing the model from 7529 // the selection. 7530 toggleSelection: function() { 7531 this.options.selection.single( this.model ); 7532 } 7533 }); 7534 7535 module.exports = Selection; 7536 7537 7538 /***/ }), 7539 /* 81 */ 7540 /***/ (function(module, exports) { 7541 4160 },{}],33:[function(require,module,exports){ 7542 4161 /** 7543 4162 * wp.media.view.Attachments.Selection … … 7569 4188 module.exports = Selection; 7570 4189 7571 7572 /***/ }), 7573 /* 82 */ 7574 /***/ (function(module, exports) { 7575 4190 },{}],34:[function(require,module,exports){ 7576 4191 /** 7577 * wp.media.view. Attachments.EditSelection4192 * wp.media.view.ButtonGroup 7578 4193 * 7579 4194 * @class 7580 * @augments wp.media.view.Attachment.Selection7581 * @augments wp.media.view.Attachment7582 4195 * @augments wp.media.View 7583 4196 * @augments wp.Backbone.View 7584 4197 * @augments Backbone.View 7585 4198 */ 7586 var EditSelection = wp.media.view.Attachment.Selection.extend({ 7587 buttons: { 7588 close: true 4199 var $ = Backbone.$, 4200 ButtonGroup; 4201 4202 ButtonGroup = wp.media.View.extend({ 4203 tagName: 'div', 4204 className: 'button-group button-large media-button-group', 4205 4206 initialize: function() { 4207 /** 4208 * @member {wp.media.view.Button[]} 4209 */ 4210 this.buttons = _.map( this.options.buttons || [], function( button ) { 4211 if ( button instanceof Backbone.View ) { 4212 return button; 4213 } else { 4214 return new wp.media.view.Button( button ).render(); 4215 } 4216 }); 4217 4218 delete this.options.buttons; 4219 4220 if ( this.options.classes ) { 4221 this.$el.addClass( this.options.classes ); 4222 } 4223 }, 4224 4225 /** 4226 * @returns {wp.media.view.ButtonGroup} 4227 */ 4228 render: function() { 4229 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 4230 return this; 7589 4231 } 7590 4232 }); 7591 4233 7592 module.exports = EditSelection; 7593 7594 7595 /***/ }), 7596 /* 83 */ 7597 /***/ (function(module, exports) { 7598 4234 module.exports = ButtonGroup; 4235 4236 },{}],35:[function(require,module,exports){ 7599 4237 /** 7600 * wp.media.view.Settings 4238 * wp.media.view.Button 4239 * 4240 * @class 4241 * @augments wp.media.View 4242 * @augments wp.Backbone.View 4243 * @augments Backbone.View 4244 */ 4245 var Button = wp.media.View.extend({ 4246 tagName: 'button', 4247 className: 'media-button', 4248 attributes: { type: 'button' }, 4249 4250 events: { 4251 'click': 'click' 4252 }, 4253 4254 defaults: { 4255 text: '', 4256 style: '', 4257 size: 'large', 4258 disabled: false 4259 }, 4260 4261 initialize: function() { 4262 /** 4263 * Create a model with the provided `defaults`. 4264 * 4265 * @member {Backbone.Model} 4266 */ 4267 this.model = new Backbone.Model( this.defaults ); 4268 4269 // If any of the `options` have a key from `defaults`, apply its 4270 // value to the `model` and remove it from the `options object. 4271 _.each( this.defaults, function( def, key ) { 4272 var value = this.options[ key ]; 4273 if ( _.isUndefined( value ) ) { 4274 return; 4275 } 4276 4277 this.model.set( key, value ); 4278 delete this.options[ key ]; 4279 }, this ); 4280 4281 this.listenTo( this.model, 'change', this.render ); 4282 }, 4283 /** 4284 * @returns {wp.media.view.Button} Returns itself to allow chaining 4285 */ 4286 render: function() { 4287 var classes = [ 'button', this.className ], 4288 model = this.model.toJSON(); 4289 4290 if ( model.style ) { 4291 classes.push( 'button-' + model.style ); 4292 } 4293 4294 if ( model.size ) { 4295 classes.push( 'button-' + model.size ); 4296 } 4297 4298 classes = _.uniq( classes.concat( this.options.classes ) ); 4299 this.el.className = classes.join(' '); 4300 4301 this.$el.attr( 'disabled', model.disabled ); 4302 this.$el.text( this.model.get('text') ); 4303 4304 return this; 4305 }, 4306 /** 4307 * @param {Object} event 4308 */ 4309 click: function( event ) { 4310 if ( '#' === this.attributes.href ) { 4311 event.preventDefault(); 4312 } 4313 4314 if ( this.options.click && ! this.model.get('disabled') ) { 4315 this.options.click.apply( this, arguments ); 4316 } 4317 } 4318 }); 4319 4320 module.exports = Button; 4321 4322 },{}],36:[function(require,module,exports){ 4323 /** 4324 * wp.media.view.Cropper 4325 * 4326 * Uses the imgAreaSelect plugin to allow a user to crop an image. 4327 * 4328 * Takes imgAreaSelect options from 4329 * wp.customize.HeaderControl.calculateImageSelectOptions via 4330 * wp.customize.HeaderControl.openMM. 7601 4331 * 7602 4332 * @class … … 7606 4336 */ 7607 4337 var View = wp.media.View, 7608 $ = Backbone.$, 7609 Settings; 7610 7611 Settings = View.extend({ 7612 events: { 7613 'click button': 'updateHandler', 7614 'change input': 'updateHandler', 7615 'change select': 'updateHandler', 7616 'change textarea': 'updateHandler' 7617 }, 7618 4338 UploaderStatus = wp.media.view.UploaderStatus, 4339 l10n = wp.media.view.l10n, 4340 $ = jQuery, 4341 Cropper; 4342 4343 Cropper = View.extend({ 4344 className: 'crop-content', 4345 template: wp.template('crop-content'), 7619 4346 initialize: function() { 7620 this.model = this.model || new Backbone.Model(); 7621 this.listenTo( this.model, 'change', this.updateChanges ); 7622 }, 7623 4347 _.bindAll(this, 'onImageLoad'); 4348 }, 4349 ready: function() { 4350 this.controller.frame.on('content:error:crop', this.onError, this); 4351 this.$image = this.$el.find('.crop-image'); 4352 this.$image.on('load', this.onImageLoad); 4353 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250)); 4354 }, 4355 remove: function() { 4356 $(window).off('resize.cropper'); 4357 this.$el.remove(); 4358 this.$el.off(); 4359 View.prototype.remove.apply(this, arguments); 4360 }, 7624 4361 prepare: function() { 7625 return _.defaults({ 7626 model: this.model.toJSON() 7627 }, this.options ); 7628 }, 7629 /** 7630 * @returns {wp.media.view.Settings} Returns itself to allow chaining 7631 */ 7632 render: function() { 7633 View.prototype.render.apply( this, arguments ); 7634 // Select the correct values. 7635 _( this.model.attributes ).chain().keys().each( this.update, this ); 7636 return this; 7637 }, 7638 /** 7639 * @param {string} key 7640 */ 7641 update: function( key ) { 7642 var value = this.model.get( key ), 7643 $setting = this.$('[data-setting="' + key + '"]'), 7644 $buttons, $value; 7645 7646 // Bail if we didn't find a matching setting. 7647 if ( ! $setting.length ) { 7648 return; 7649 } 7650 7651 // Attempt to determine how the setting is rendered and update 7652 // the selected value. 7653 7654 // Handle dropdowns. 7655 if ( $setting.is('select') ) { 7656 $value = $setting.find('[value="' + value + '"]'); 7657 7658 if ( $value.length ) { 7659 $setting.find('option').prop( 'selected', false ); 7660 $value.prop( 'selected', true ); 7661 } else { 7662 // If we can't find the desired value, record what *is* selected. 7663 this.model.set( key, $setting.find(':selected').val() ); 7664 } 7665 7666 // Handle button groups. 7667 } else if ( $setting.hasClass('button-group') ) { 7668 $buttons = $setting.find('button').removeClass('active'); 7669 $buttons.filter( '[value="' + value + '"]' ).addClass('active'); 7670 7671 // Handle text inputs and textareas. 7672 } else if ( $setting.is('input[type="text"], textarea') ) { 7673 if ( ! $setting.is(':focus') ) { 7674 $setting.val( value ); 7675 } 7676 // Handle checkboxes. 7677 } else if ( $setting.is('input[type="checkbox"]') ) { 7678 $setting.prop( 'checked', !! value && 'false' !== value ); 7679 } 7680 }, 7681 /** 7682 * @param {Object} event 7683 */ 7684 updateHandler: function( event ) { 7685 var $setting = $( event.target ).closest('[data-setting]'), 7686 value = event.target.value, 7687 userSetting; 7688 7689 event.preventDefault(); 7690 7691 if ( ! $setting.length ) { 7692 return; 7693 } 7694 7695 // Use the correct value for checkboxes. 7696 if ( $setting.is('input[type="checkbox"]') ) { 7697 value = $setting[0].checked; 7698 } 7699 7700 // Update the corresponding setting. 7701 this.model.set( $setting.data('setting'), value ); 7702 7703 // If the setting has a corresponding user setting, 7704 // update that as well. 7705 if ( userSetting = $setting.data('userSetting') ) { 7706 window.setUserSetting( userSetting, value ); 7707 } 7708 }, 7709 7710 updateChanges: function( model ) { 7711 if ( model.hasChanged() ) { 7712 _( model.changed ).chain().keys().each( this.update, this ); 7713 } 4362 return { 4363 title: l10n.cropYourImage, 4364 url: this.options.attachment.get('url') 4365 }; 4366 }, 4367 onImageLoad: function() { 4368 var imgOptions = this.controller.get('imgSelectOptions'); 4369 if (typeof imgOptions === 'function') { 4370 imgOptions = imgOptions(this.options.attachment, this.controller); 4371 } 4372 4373 imgOptions = _.extend(imgOptions, {parent: this.$el}); 4374 this.trigger('image-loaded'); 4375 this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 4376 }, 4377 onError: function() { 4378 var filename = this.options.attachment.get('filename'); 4379 4380 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 4381 filename: UploaderStatus.prototype.filename(filename), 4382 message: window._wpMediaViewsL10n.cropError 4383 }), { at: 0 }); 7714 4384 } 7715 4385 }); 7716 4386 7717 module.exports = Settings; 7718 7719 7720 /***/ }), 7721 /* 84 */ 7722 /***/ (function(module, exports) { 7723 4387 module.exports = Cropper; 4388 4389 },{}],37:[function(require,module,exports){ 7724 4390 /** 7725 * wp.media.view.Settings.AttachmentDisplay 7726 * 7727 * @class 7728 * @augments wp.media.view.Settings 7729 * @augments wp.media.View 7730 * @augments wp.Backbone.View 7731 * @augments Backbone.View 7732 */ 7733 var Settings = wp.media.view.Settings, 7734 AttachmentDisplay; 7735 7736 AttachmentDisplay = Settings.extend({ 7737 className: 'attachment-display-settings', 7738 template: wp.template('attachment-display-settings'), 7739 7740 initialize: function() { 7741 var attachment = this.options.attachment; 7742 7743 _.defaults( this.options, { 7744 userSettings: false 7745 }); 7746 // Call 'initialize' directly on the parent class. 7747 Settings.prototype.initialize.apply( this, arguments ); 7748 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 7749 7750 if ( attachment ) { 7751 attachment.on( 'change:uploading', this.render, this ); 7752 } 7753 }, 7754 7755 dispose: function() { 7756 var attachment = this.options.attachment; 7757 if ( attachment ) { 7758 attachment.off( null, null, this ); 7759 } 7760 /** 7761 * call 'dispose' directly on the parent class 7762 */ 7763 Settings.prototype.dispose.apply( this, arguments ); 7764 }, 7765 /** 7766 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining 7767 */ 7768 render: function() { 7769 var attachment = this.options.attachment; 7770 if ( attachment ) { 7771 _.extend( this.options, { 7772 sizes: attachment.get('sizes'), 7773 type: attachment.get('type') 7774 }); 7775 } 7776 /** 7777 * call 'render' directly on the parent class 7778 */ 7779 Settings.prototype.render.call( this ); 7780 this.updateLinkTo(); 7781 return this; 7782 }, 7783 7784 updateLinkTo: function() { 7785 var linkTo = this.model.get('link'), 7786 $input = this.$('.link-to-custom'), 7787 attachment = this.options.attachment; 7788 7789 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 7790 $input.addClass( 'hidden' ); 7791 return; 7792 } 7793 7794 if ( attachment ) { 7795 if ( 'post' === linkTo ) { 7796 $input.val( attachment.get('link') ); 7797 } else if ( 'file' === linkTo ) { 7798 $input.val( attachment.get('url') ); 7799 } else if ( ! this.model.get('linkUrl') ) { 7800 $input.val('http://'); 7801 } 7802 7803 $input.prop( 'readonly', 'custom' !== linkTo ); 7804 } 7805 7806 $input.removeClass( 'hidden' ); 7807 7808 // If the input is visible, focus and select its contents. 7809 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) { 7810 $input.focus()[0].select(); 7811 } 7812 } 7813 }); 7814 7815 module.exports = AttachmentDisplay; 7816 7817 7818 /***/ }), 7819 /* 85 */ 7820 /***/ (function(module, exports) { 7821 7822 /** 7823 * wp.media.view.Settings.Gallery 7824 * 7825 * @class 7826 * @augments wp.media.view.Settings 7827 * @augments wp.media.View 7828 * @augments wp.Backbone.View 7829 * @augments Backbone.View 7830 */ 7831 var Gallery = wp.media.view.Settings.extend({ 7832 className: 'collection-settings gallery-settings', 7833 template: wp.template('gallery-settings') 7834 }); 7835 7836 module.exports = Gallery; 7837 7838 7839 /***/ }), 7840 /* 86 */ 7841 /***/ (function(module, exports) { 7842 7843 /** 7844 * wp.media.view.Settings.Playlist 7845 * 7846 * @class 7847 * @augments wp.media.view.Settings 7848 * @augments wp.media.View 7849 * @augments wp.Backbone.View 7850 * @augments Backbone.View 7851 */ 7852 var Playlist = wp.media.view.Settings.extend({ 7853 className: 'collection-settings playlist-settings', 7854 template: wp.template('playlist-settings') 7855 }); 7856 7857 module.exports = Playlist; 7858 7859 7860 /***/ }), 7861 /* 87 */ 7862 /***/ (function(module, exports) { 7863 7864 /** 7865 * wp.media.view.Attachment.Details 7866 * 7867 * @class 7868 * @augments wp.media.view.Attachment 7869 * @augments wp.media.View 7870 * @augments wp.Backbone.View 7871 * @augments Backbone.View 7872 */ 7873 var Attachment = wp.media.view.Attachment, 7874 l10n = wp.media.view.l10n, 7875 Details; 7876 7877 Details = Attachment.extend({ 7878 tagName: 'div', 7879 className: 'attachment-details', 7880 template: wp.template('attachment-details'), 7881 7882 attributes: function() { 7883 return { 7884 'tabIndex': 0, 7885 'data-id': this.model.get( 'id' ) 7886 }; 7887 }, 7888 7889 events: { 7890 'change [data-setting]': 'updateSetting', 7891 'change [data-setting] input': 'updateSetting', 7892 'change [data-setting] select': 'updateSetting', 7893 'change [data-setting] textarea': 'updateSetting', 7894 'click .delete-attachment': 'deleteAttachment', 7895 'click .trash-attachment': 'trashAttachment', 7896 'click .untrash-attachment': 'untrashAttachment', 7897 'click .edit-attachment': 'editAttachment', 7898 'keydown': 'toggleSelectionHandler' 7899 }, 7900 7901 initialize: function() { 7902 this.options = _.defaults( this.options, { 7903 rerenderOnModelChange: false 7904 }); 7905 7906 this.on( 'ready', this.initialFocus ); 7907 // Call 'initialize' directly on the parent class. 7908 Attachment.prototype.initialize.apply( this, arguments ); 7909 }, 7910 7911 initialFocus: function() { 7912 if ( ! wp.media.isTouchDevice ) { 7913 /* 7914 Previously focused the first ':input' (the readonly URL text field). 7915 Since the first ':input' is now a button (delete/trash): when pressing 7916 spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment 7917 as soon as focus is moved. Explicitly target the first text field for now. 7918 @todo change initial focus logic, also for accessibility. 7919 */ 7920 this.$( 'input[type="text"]' ).eq( 0 ).focus(); 7921 } 7922 }, 7923 /** 7924 * @param {Object} event 7925 */ 7926 deleteAttachment: function( event ) { 7927 event.preventDefault(); 7928 7929 if ( window.confirm( l10n.warnDelete ) ) { 7930 this.model.destroy(); 7931 // Keep focus inside media modal 7932 // after image is deleted 7933 this.controller.modal.focusManager.focus(); 7934 } 7935 }, 7936 /** 7937 * @param {Object} event 7938 */ 7939 trashAttachment: function( event ) { 7940 var library = this.controller.library; 7941 event.preventDefault(); 7942 7943 if ( wp.media.view.settings.mediaTrash && 7944 'edit-metadata' === this.controller.content.mode() ) { 7945 7946 this.model.set( 'status', 'trash' ); 7947 this.model.save().done( function() { 7948 library._requery( true ); 7949 } ); 7950 } else { 7951 this.model.destroy(); 7952 } 7953 }, 7954 /** 7955 * @param {Object} event 7956 */ 7957 untrashAttachment: function( event ) { 7958 var library = this.controller.library; 7959 event.preventDefault(); 7960 7961 this.model.set( 'status', 'inherit' ); 7962 this.model.save().done( function() { 7963 library._requery( true ); 7964 } ); 7965 }, 7966 /** 7967 * @param {Object} event 7968 */ 7969 editAttachment: function( event ) { 7970 var editState = this.controller.states.get( 'edit-image' ); 7971 if ( window.imageEdit && editState ) { 7972 event.preventDefault(); 7973 7974 editState.set( 'image', this.model ); 7975 this.controller.setState( 'edit-image' ); 7976 } else { 7977 this.$el.addClass('needs-refresh'); 7978 } 7979 }, 7980 /** 7981 * When reverse tabbing(shift+tab) out of the right details panel, deliver 7982 * the focus to the item in the list that was being edited. 7983 * 7984 * @param {Object} event 7985 */ 7986 toggleSelectionHandler: function( event ) { 7987 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 7988 this.controller.trigger( 'attachment:details:shift-tab', event ); 7989 return false; 7990 } 7991 7992 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 7993 this.controller.trigger( 'attachment:keydown:arrow', event ); 7994 return; 7995 } 7996 } 7997 }); 7998 7999 module.exports = Details; 8000 8001 8002 /***/ }), 8003 /* 88 */ 8004 /***/ (function(module, exports) { 8005 8006 /** 8007 * wp.media.view.AttachmentCompat 8008 * 8009 * A view to display fields added via the `attachment_fields_to_edit` filter. 4391 * wp.media.view.EditImage 8010 4392 * 8011 4393 * @class … … 8015 4397 */ 8016 4398 var View = wp.media.View, 8017 AttachmentCompat; 8018 8019 AttachmentCompat = View.extend({ 8020 tagName: 'form', 8021 className: 'compat-item', 8022 8023 events: { 8024 'submit': 'preventDefault', 8025 'change input': 'save', 8026 'change select': 'save', 8027 'change textarea': 'save' 8028 }, 8029 8030 initialize: function() { 8031 this.listenTo( this.model, 'change:compat', this.render ); 8032 }, 8033 /** 8034 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 8035 */ 8036 dispose: function() { 8037 if ( this.$(':focus').length ) { 8038 this.save(); 8039 } 8040 /** 8041 * call 'dispose' directly on the parent class 8042 */ 8043 return View.prototype.dispose.apply( this, arguments ); 8044 }, 8045 /** 8046 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 8047 */ 8048 render: function() { 8049 var compat = this.model.get('compat'); 8050 if ( ! compat || ! compat.item ) { 8051 return; 8052 } 8053 8054 this.views.detach(); 8055 this.$el.html( compat.item ); 8056 this.views.render(); 8057 return this; 8058 }, 8059 /** 8060 * @param {Object} event 8061 */ 8062 preventDefault: function( event ) { 8063 event.preventDefault(); 8064 }, 8065 /** 8066 * @param {Object} event 8067 */ 8068 save: function( event ) { 8069 var data = {}; 8070 8071 if ( event ) { 8072 event.preventDefault(); 8073 } 8074 8075 _.each( this.$el.serializeArray(), function( pair ) { 8076 data[ pair.name ] = pair.value; 8077 }); 8078 8079 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 8080 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 8081 }, 8082 8083 postSave: function() { 8084 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 4399 EditImage; 4400 4401 EditImage = View.extend({ 4402 className: 'image-editor', 4403 template: wp.template('image-editor'), 4404 4405 initialize: function( options ) { 4406 this.editor = window.imageEdit; 4407 this.controller = options.controller; 4408 View.prototype.initialize.apply( this, arguments ); 4409 }, 4410 4411 prepare: function() { 4412 return this.model.toJSON(); 4413 }, 4414 4415 loadEditor: function() { 4416 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this ); 4417 dfd.done( _.bind( this.focus, this ) ); 4418 }, 4419 4420 focus: function() { 4421 this.$( '.imgedit-submit .button' ).eq( 0 ).focus(); 4422 }, 4423 4424 back: function() { 4425 var lastState = this.controller.lastState(); 4426 this.controller.setState( lastState ); 4427 }, 4428 4429 refresh: function() { 4430 this.model.fetch(); 4431 }, 4432 4433 save: function() { 4434 var lastState = this.controller.lastState(); 4435 4436 this.model.fetch().done( _.bind( function() { 4437 this.controller.setState( lastState ); 4438 }, this ) ); 8085 4439 } 4440 8086 4441 }); 8087 4442 8088 module.exports = AttachmentCompat; 8089 8090 8091 /***/ }), 8092 /* 89 */ 8093 /***/ (function(module, exports) { 8094 8095 /** 8096 * wp.media.view.Iframe 8097 * 8098 * @class 8099 * @augments wp.media.View 8100 * @augments wp.Backbone.View 8101 * @augments Backbone.View 8102 */ 8103 var Iframe = wp.media.View.extend({ 8104 className: 'media-iframe', 8105 /** 8106 * @returns {wp.media.view.Iframe} Returns itself to allow chaining 8107 */ 8108 render: function() { 8109 this.views.detach(); 8110 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 8111 this.views.render(); 8112 return this; 8113 } 8114 }); 8115 8116 module.exports = Iframe; 8117 8118 8119 /***/ }), 8120 /* 90 */ 8121 /***/ (function(module, exports) { 8122 4443 module.exports = EditImage; 4444 4445 },{}],38:[function(require,module,exports){ 8123 4446 /** 8124 4447 * wp.media.view.Embed … … 8184 4507 module.exports = Embed; 8185 4508 8186 8187 /***/ }), 8188 /* 91 */ 8189 /***/ (function(module, exports) { 8190 4509 },{}],39:[function(require,module,exports){ 8191 4510 /** 8192 * wp.media.view. Label4511 * wp.media.view.EmbedImage 8193 4512 * 8194 4513 * @class 4514 * @augments wp.media.view.Settings.AttachmentDisplay 4515 * @augments wp.media.view.Settings 8195 4516 * @augments wp.media.View 8196 4517 * @augments wp.Backbone.View 8197 4518 * @augments Backbone.View 8198 4519 */ 8199 var Label = wp.media.View.extend({ 8200 tagName: 'label', 8201 className: 'screen-reader-text', 4520 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 4521 EmbedImage; 4522 4523 EmbedImage = AttachmentDisplay.extend({ 4524 className: 'embed-media-settings', 4525 template: wp.template('embed-image-settings'), 8202 4526 8203 4527 initialize: function() { 8204 this.value = this.options.value; 8205 }, 8206 8207 render: function() { 8208 this.$el.html( this.value ); 8209 8210 return this; 4528 /** 4529 * Call `initialize` directly on parent class with passed arguments 4530 */ 4531 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 4532 this.listenTo( this.model, 'change:url', this.updateImage ); 4533 }, 4534 4535 updateImage: function() { 4536 this.$('img').attr( 'src', this.model.get('url') ); 8211 4537 } 8212 4538 }); 8213 4539 8214 module.exports = Label; 8215 8216 8217 /***/ }), 8218 /* 92 */ 8219 /***/ (function(module, exports) { 8220 8221 /** 8222 * wp.media.view.EmbedUrl 8223 * 8224 * @class 8225 * @augments wp.media.View 8226 * @augments wp.Backbone.View 8227 * @augments Backbone.View 8228 */ 8229 var View = wp.media.View, 8230 $ = jQuery, 8231 EmbedUrl; 8232 8233 EmbedUrl = View.extend({ 8234 tagName: 'label', 8235 className: 'embed-url', 8236 8237 events: { 8238 'input': 'url', 8239 'keyup': 'url', 8240 'change': 'url' 8241 }, 8242 8243 initialize: function() { 8244 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') ); 8245 this.input = this.$input[0]; 8246 8247 this.spinner = $('<span class="spinner" />')[0]; 8248 this.$el.append([ this.input, this.spinner ]); 8249 8250 this.listenTo( this.model, 'change:url', this.render ); 8251 8252 if ( this.model.get( 'url' ) ) { 8253 _.delay( _.bind( function () { 8254 this.model.trigger( 'change:url' ); 8255 }, this ), 500 ); 8256 } 8257 }, 8258 /** 8259 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining 8260 */ 8261 render: function() { 8262 var $input = this.$input; 8263 8264 if ( $input.is(':focus') ) { 8265 return; 8266 } 8267 8268 this.input.value = this.model.get('url') || 'http://'; 8269 /** 8270 * Call `render` directly on parent class with passed arguments 8271 */ 8272 View.prototype.render.apply( this, arguments ); 8273 return this; 8274 }, 8275 8276 ready: function() { 8277 if ( ! wp.media.isTouchDevice ) { 8278 this.focus(); 8279 } 8280 }, 8281 8282 url: function( event ) { 8283 this.model.set( 'url', event.target.value ); 8284 }, 8285 8286 /** 8287 * If the input is visible, focus and select its contents. 8288 */ 8289 focus: function() { 8290 var $input = this.$input; 8291 if ( $input.is(':visible') ) { 8292 $input.focus()[0].select(); 8293 } 8294 } 8295 }); 8296 8297 module.exports = EmbedUrl; 8298 8299 8300 /***/ }), 8301 /* 93 */ 8302 /***/ (function(module, exports) { 8303 4540 module.exports = EmbedImage; 4541 4542 },{}],40:[function(require,module,exports){ 8304 4543 /** 8305 4544 * wp.media.view.EmbedLink … … 8390 4629 module.exports = EmbedLink; 8391 4630 8392 8393 /***/ }), 8394 /* 94 */ 8395 /***/ (function(module, exports) { 8396 4631 },{}],41:[function(require,module,exports){ 8397 4632 /** 8398 * wp.media.view.Embed Image4633 * wp.media.view.EmbedUrl 8399 4634 * 8400 4635 * @class 8401 * @augments wp.media.view.Settings.AttachmentDisplay8402 * @augments wp.media.view.Settings8403 4636 * @augments wp.media.View 8404 4637 * @augments wp.Backbone.View 8405 4638 * @augments Backbone.View 8406 4639 */ 8407 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 8408 EmbedImage; 8409 8410 EmbedImage = AttachmentDisplay.extend({ 8411 className: 'embed-media-settings', 8412 template: wp.template('embed-image-settings'), 4640 var View = wp.media.View, 4641 $ = jQuery, 4642 EmbedUrl; 4643 4644 EmbedUrl = View.extend({ 4645 tagName: 'label', 4646 className: 'embed-url', 4647 4648 events: { 4649 'input': 'url', 4650 'keyup': 'url', 4651 'change': 'url' 4652 }, 8413 4653 8414 4654 initialize: function() { 4655 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') ); 4656 this.input = this.$input[0]; 4657 4658 this.spinner = $('<span class="spinner" />')[0]; 4659 this.$el.append([ this.input, this.spinner ]); 4660 4661 this.listenTo( this.model, 'change:url', this.render ); 4662 4663 if ( this.model.get( 'url' ) ) { 4664 _.delay( _.bind( function () { 4665 this.model.trigger( 'change:url' ); 4666 }, this ), 500 ); 4667 } 4668 }, 4669 /** 4670 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining 4671 */ 4672 render: function() { 4673 var $input = this.$input; 4674 4675 if ( $input.is(':focus') ) { 4676 return; 4677 } 4678 4679 this.input.value = this.model.get('url') || 'http://'; 8415 4680 /** 8416 * Call ` initialize` directly on parent class with passed arguments4681 * Call `render` directly on parent class with passed arguments 8417 4682 */ 8418 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 8419 this.listenTo( this.model, 'change:url', this.updateImage ); 8420 }, 8421 8422 updateImage: function() { 8423 this.$('img').attr( 'src', this.model.get('url') ); 4683 View.prototype.render.apply( this, arguments ); 4684 return this; 4685 }, 4686 4687 ready: function() { 4688 if ( ! wp.media.isTouchDevice ) { 4689 this.focus(); 4690 } 4691 }, 4692 4693 url: function( event ) { 4694 this.model.set( 'url', event.target.value ); 4695 }, 4696 4697 /** 4698 * If the input is visible, focus and select its contents. 4699 */ 4700 focus: function() { 4701 var $input = this.$input; 4702 if ( $input.is(':visible') ) { 4703 $input.focus()[0].select(); 4704 } 8424 4705 } 8425 4706 }); 8426 4707 8427 module.exports = EmbedImage; 8428 8429 8430 /***/ }), 8431 /* 95 */ 8432 /***/ (function(module, exports) { 8433 4708 module.exports = EmbedUrl; 4709 4710 },{}],42:[function(require,module,exports){ 4711 /** 4712 * wp.media.view.FocusManager 4713 * 4714 * @class 4715 * @augments wp.media.View 4716 * @augments wp.Backbone.View 4717 * @augments Backbone.View 4718 */ 4719 var FocusManager = wp.media.View.extend({ 4720 4721 events: { 4722 'keydown': 'constrainTabbing' 4723 }, 4724 4725 focus: function() { // Reset focus on first left menu item 4726 this.$('.media-menu-item').first().focus(); 4727 }, 4728 /** 4729 * @param {Object} event 4730 */ 4731 constrainTabbing: function( event ) { 4732 var tabbables; 4733 4734 // Look for the tab key. 4735 if ( 9 !== event.keyCode ) { 4736 return; 4737 } 4738 4739 // Skip the file input added by Plupload. 4740 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 4741 4742 // Keep tab focus within media modal while it's open 4743 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 4744 tabbables.first().focus(); 4745 return false; 4746 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 4747 tabbables.last().focus(); 4748 return false; 4749 } 4750 } 4751 4752 }); 4753 4754 module.exports = FocusManager; 4755 4756 },{}],43:[function(require,module,exports){ 4757 /** 4758 * wp.media.view.Frame 4759 * 4760 * A frame is a composite view consisting of one or more regions and one or more 4761 * states. 4762 * 4763 * @see wp.media.controller.State 4764 * @see wp.media.controller.Region 4765 * 4766 * @class 4767 * @augments wp.media.View 4768 * @augments wp.Backbone.View 4769 * @augments Backbone.View 4770 * @mixes wp.media.controller.StateMachine 4771 */ 4772 var Frame = wp.media.View.extend({ 4773 initialize: function() { 4774 _.defaults( this.options, { 4775 mode: [ 'select' ] 4776 }); 4777 this._createRegions(); 4778 this._createStates(); 4779 this._createModes(); 4780 }, 4781 4782 _createRegions: function() { 4783 // Clone the regions array. 4784 this.regions = this.regions ? this.regions.slice() : []; 4785 4786 // Initialize regions. 4787 _.each( this.regions, function( region ) { 4788 this[ region ] = new wp.media.controller.Region({ 4789 view: this, 4790 id: region, 4791 selector: '.media-frame-' + region 4792 }); 4793 }, this ); 4794 }, 4795 /** 4796 * Create the frame's states. 4797 * 4798 * @see wp.media.controller.State 4799 * @see wp.media.controller.StateMachine 4800 * 4801 * @fires wp.media.controller.State#ready 4802 */ 4803 _createStates: function() { 4804 // Create the default `states` collection. 4805 this.states = new Backbone.Collection( null, { 4806 model: wp.media.controller.State 4807 }); 4808 4809 // Ensure states have a reference to the frame. 4810 this.states.on( 'add', function( model ) { 4811 model.frame = this; 4812 model.trigger('ready'); 4813 }, this ); 4814 4815 if ( this.options.states ) { 4816 this.states.add( this.options.states ); 4817 } 4818 }, 4819 4820 /** 4821 * A frame can be in a mode or multiple modes at one time. 4822 * 4823 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 4824 */ 4825 _createModes: function() { 4826 // Store active "modes" that the frame is in. Unrelated to region modes. 4827 this.activeModes = new Backbone.Collection(); 4828 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 4829 4830 _.each( this.options.mode, function( mode ) { 4831 this.activateMode( mode ); 4832 }, this ); 4833 }, 4834 /** 4835 * Reset all states on the frame to their defaults. 4836 * 4837 * @returns {wp.media.view.Frame} Returns itself to allow chaining 4838 */ 4839 reset: function() { 4840 this.states.invoke( 'trigger', 'reset' ); 4841 return this; 4842 }, 4843 /** 4844 * Map activeMode collection events to the frame. 4845 */ 4846 triggerModeEvents: function( model, collection, options ) { 4847 var collectionEvent, 4848 modeEventMap = { 4849 add: 'activate', 4850 remove: 'deactivate' 4851 }, 4852 eventToTrigger; 4853 // Probably a better way to do this. 4854 _.each( options, function( value, key ) { 4855 if ( value ) { 4856 collectionEvent = key; 4857 } 4858 } ); 4859 4860 if ( ! _.has( modeEventMap, collectionEvent ) ) { 4861 return; 4862 } 4863 4864 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 4865 this.trigger( eventToTrigger ); 4866 }, 4867 /** 4868 * Activate a mode on the frame. 4869 * 4870 * @param string mode Mode ID. 4871 * @returns {this} Returns itself to allow chaining. 4872 */ 4873 activateMode: function( mode ) { 4874 // Bail if the mode is already active. 4875 if ( this.isModeActive( mode ) ) { 4876 return; 4877 } 4878 this.activeModes.add( [ { id: mode } ] ); 4879 // Add a CSS class to the frame so elements can be styled for the mode. 4880 this.$el.addClass( 'mode-' + mode ); 4881 4882 return this; 4883 }, 4884 /** 4885 * Deactivate a mode on the frame. 4886 * 4887 * @param string mode Mode ID. 4888 * @returns {this} Returns itself to allow chaining. 4889 */ 4890 deactivateMode: function( mode ) { 4891 // Bail if the mode isn't active. 4892 if ( ! this.isModeActive( mode ) ) { 4893 return this; 4894 } 4895 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 4896 this.$el.removeClass( 'mode-' + mode ); 4897 /** 4898 * Frame mode deactivation event. 4899 * 4900 * @event this#{mode}:deactivate 4901 */ 4902 this.trigger( mode + ':deactivate' ); 4903 4904 return this; 4905 }, 4906 /** 4907 * Check if a mode is enabled on the frame. 4908 * 4909 * @param string mode Mode ID. 4910 * @return bool 4911 */ 4912 isModeActive: function( mode ) { 4913 return Boolean( this.activeModes.where( { id: mode } ).length ); 4914 } 4915 }); 4916 4917 // Make the `Frame` a `StateMachine`. 4918 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 4919 4920 module.exports = Frame; 4921 4922 },{}],44:[function(require,module,exports){ 4923 /** 4924 * wp.media.view.MediaFrame.ImageDetails 4925 * 4926 * A media frame for manipulating an image that's already been inserted 4927 * into a post. 4928 * 4929 * @class 4930 * @augments wp.media.view.MediaFrame.Select 4931 * @augments wp.media.view.MediaFrame 4932 * @augments wp.media.view.Frame 4933 * @augments wp.media.View 4934 * @augments wp.Backbone.View 4935 * @augments Backbone.View 4936 * @mixes wp.media.controller.StateMachine 4937 */ 4938 var Select = wp.media.view.MediaFrame.Select, 4939 l10n = wp.media.view.l10n, 4940 ImageDetails; 4941 4942 ImageDetails = Select.extend({ 4943 defaults: { 4944 id: 'image', 4945 url: '', 4946 menu: 'image-details', 4947 content: 'image-details', 4948 toolbar: 'image-details', 4949 type: 'link', 4950 title: l10n.imageDetailsTitle, 4951 priority: 120 4952 }, 4953 4954 initialize: function( options ) { 4955 this.image = new wp.media.model.PostImage( options.metadata ); 4956 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 4957 Select.prototype.initialize.apply( this, arguments ); 4958 }, 4959 4960 bindHandlers: function() { 4961 Select.prototype.bindHandlers.apply( this, arguments ); 4962 this.on( 'menu:create:image-details', this.createMenu, this ); 4963 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 4964 this.on( 'content:render:edit-image', this.editImageContent, this ); 4965 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 4966 // override the select toolbar 4967 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 4968 }, 4969 4970 createStates: function() { 4971 this.states.add([ 4972 new wp.media.controller.ImageDetails({ 4973 image: this.image, 4974 editable: false 4975 }), 4976 new wp.media.controller.ReplaceImage({ 4977 id: 'replace-image', 4978 library: wp.media.query( { type: 'image' } ), 4979 image: this.image, 4980 multiple: false, 4981 title: l10n.imageReplaceTitle, 4982 toolbar: 'replace', 4983 priority: 80, 4984 displaySettings: true 4985 }), 4986 new wp.media.controller.EditImage( { 4987 image: this.image, 4988 selection: this.options.selection 4989 } ) 4990 ]); 4991 }, 4992 4993 imageDetailsContent: function( options ) { 4994 options.view = new wp.media.view.ImageDetails({ 4995 controller: this, 4996 model: this.state().image, 4997 attachment: this.state().image.attachment 4998 }); 4999 }, 5000 5001 editImageContent: function() { 5002 var state = this.state(), 5003 model = state.get('image'), 5004 view; 5005 5006 if ( ! model ) { 5007 return; 5008 } 5009 5010 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 5011 5012 this.content.set( view ); 5013 5014 // after bringing in the frame, load the actual editor via an ajax call 5015 view.loadEditor(); 5016 5017 }, 5018 5019 renderImageDetailsToolbar: function() { 5020 this.toolbar.set( new wp.media.view.Toolbar({ 5021 controller: this, 5022 items: { 5023 select: { 5024 style: 'primary', 5025 text: l10n.update, 5026 priority: 80, 5027 5028 click: function() { 5029 var controller = this.controller, 5030 state = controller.state(); 5031 5032 controller.close(); 5033 5034 // not sure if we want to use wp.media.string.image which will create a shortcode or 5035 // perhaps wp.html.string to at least to build the <img /> 5036 state.trigger( 'update', controller.image.toJSON() ); 5037 5038 // Restore and reset the default state. 5039 controller.setState( controller.options.state ); 5040 controller.reset(); 5041 } 5042 } 5043 } 5044 }) ); 5045 }, 5046 5047 renderReplaceImageToolbar: function() { 5048 var frame = this, 5049 lastState = frame.lastState(), 5050 previous = lastState && lastState.id; 5051 5052 this.toolbar.set( new wp.media.view.Toolbar({ 5053 controller: this, 5054 items: { 5055 back: { 5056 text: l10n.back, 5057 priority: 20, 5058 click: function() { 5059 if ( previous ) { 5060 frame.setState( previous ); 5061 } else { 5062 frame.close(); 5063 } 5064 } 5065 }, 5066 5067 replace: { 5068 style: 'primary', 5069 text: l10n.replace, 5070 priority: 80, 5071 5072 click: function() { 5073 var controller = this.controller, 5074 state = controller.state(), 5075 selection = state.get( 'selection' ), 5076 attachment = selection.single(); 5077 5078 controller.close(); 5079 5080 controller.image.changeAttachment( attachment, state.display( attachment ) ); 5081 5082 // not sure if we want to use wp.media.string.image which will create a shortcode or 5083 // perhaps wp.html.string to at least to build the <img /> 5084 state.trigger( 'replace', controller.image.toJSON() ); 5085 5086 // Restore and reset the default state. 5087 controller.setState( controller.options.state ); 5088 controller.reset(); 5089 } 5090 } 5091 } 5092 }) ); 5093 } 5094 5095 }); 5096 5097 module.exports = ImageDetails; 5098 5099 },{}],45:[function(require,module,exports){ 5100 /** 5101 * wp.media.view.MediaFrame.Post 5102 * 5103 * The frame for manipulating media on the Edit Post page. 5104 * 5105 * @class 5106 * @augments wp.media.view.MediaFrame.Select 5107 * @augments wp.media.view.MediaFrame 5108 * @augments wp.media.view.Frame 5109 * @augments wp.media.View 5110 * @augments wp.Backbone.View 5111 * @augments Backbone.View 5112 * @mixes wp.media.controller.StateMachine 5113 */ 5114 var Select = wp.media.view.MediaFrame.Select, 5115 Library = wp.media.controller.Library, 5116 l10n = wp.media.view.l10n, 5117 Post; 5118 5119 Post = Select.extend({ 5120 initialize: function() { 5121 this.counts = { 5122 audio: { 5123 count: wp.media.view.settings.attachmentCounts.audio, 5124 state: 'playlist' 5125 }, 5126 video: { 5127 count: wp.media.view.settings.attachmentCounts.video, 5128 state: 'video-playlist' 5129 } 5130 }; 5131 5132 _.defaults( this.options, { 5133 multiple: true, 5134 editing: false, 5135 state: 'insert', 5136 metadata: {} 5137 }); 5138 5139 // Call 'initialize' directly on the parent class. 5140 Select.prototype.initialize.apply( this, arguments ); 5141 this.createIframeStates(); 5142 5143 }, 5144 5145 /** 5146 * Create the default states. 5147 */ 5148 createStates: function() { 5149 var options = this.options; 5150 5151 this.states.add([ 5152 // Main states. 5153 new Library({ 5154 id: 'insert', 5155 title: l10n.insertMediaTitle, 5156 priority: 20, 5157 toolbar: 'main-insert', 5158 filterable: 'all', 5159 library: wp.media.query( options.library ), 5160 multiple: options.multiple ? 'reset' : false, 5161 editable: true, 5162 5163 // If the user isn't allowed to edit fields, 5164 // can they still edit it locally? 5165 allowLocalEdits: true, 5166 5167 // Show the attachment display settings. 5168 displaySettings: true, 5169 // Update user settings when users adjust the 5170 // attachment display settings. 5171 displayUserSettings: true 5172 }), 5173 5174 new Library({ 5175 id: 'gallery', 5176 title: l10n.createGalleryTitle, 5177 priority: 40, 5178 toolbar: 'main-gallery', 5179 filterable: 'uploaded', 5180 multiple: 'add', 5181 editable: false, 5182 5183 library: wp.media.query( _.defaults({ 5184 type: 'image' 5185 }, options.library ) ) 5186 }), 5187 5188 // Embed states. 5189 new wp.media.controller.Embed( { metadata: options.metadata } ), 5190 5191 new wp.media.controller.EditImage( { model: options.editImage } ), 5192 5193 // Gallery states. 5194 new wp.media.controller.GalleryEdit({ 5195 library: options.selection, 5196 editing: options.editing, 5197 menu: 'gallery' 5198 }), 5199 5200 new wp.media.controller.GalleryAdd(), 5201 5202 new Library({ 5203 id: 'playlist', 5204 title: l10n.createPlaylistTitle, 5205 priority: 60, 5206 toolbar: 'main-playlist', 5207 filterable: 'uploaded', 5208 multiple: 'add', 5209 editable: false, 5210 5211 library: wp.media.query( _.defaults({ 5212 type: 'audio' 5213 }, options.library ) ) 5214 }), 5215 5216 // Playlist states. 5217 new wp.media.controller.CollectionEdit({ 5218 type: 'audio', 5219 collectionType: 'playlist', 5220 title: l10n.editPlaylistTitle, 5221 SettingsView: wp.media.view.Settings.Playlist, 5222 library: options.selection, 5223 editing: options.editing, 5224 menu: 'playlist', 5225 dragInfoText: l10n.playlistDragInfo, 5226 dragInfo: false 5227 }), 5228 5229 new wp.media.controller.CollectionAdd({ 5230 type: 'audio', 5231 collectionType: 'playlist', 5232 title: l10n.addToPlaylistTitle 5233 }), 5234 5235 new Library({ 5236 id: 'video-playlist', 5237 title: l10n.createVideoPlaylistTitle, 5238 priority: 60, 5239 toolbar: 'main-video-playlist', 5240 filterable: 'uploaded', 5241 multiple: 'add', 5242 editable: false, 5243 5244 library: wp.media.query( _.defaults({ 5245 type: 'video' 5246 }, options.library ) ) 5247 }), 5248 5249 new wp.media.controller.CollectionEdit({ 5250 type: 'video', 5251 collectionType: 'playlist', 5252 title: l10n.editVideoPlaylistTitle, 5253 SettingsView: wp.media.view.Settings.Playlist, 5254 library: options.selection, 5255 editing: options.editing, 5256 menu: 'video-playlist', 5257 dragInfoText: l10n.videoPlaylistDragInfo, 5258 dragInfo: false 5259 }), 5260 5261 new wp.media.controller.CollectionAdd({ 5262 type: 'video', 5263 collectionType: 'playlist', 5264 title: l10n.addToVideoPlaylistTitle 5265 }) 5266 ]); 5267 5268 if ( wp.media.view.settings.post.featuredImageId ) { 5269 this.states.add( new wp.media.controller.FeaturedImage() ); 5270 } 5271 }, 5272 5273 bindHandlers: function() { 5274 var handlers, checkCounts; 5275 5276 Select.prototype.bindHandlers.apply( this, arguments ); 5277 5278 this.on( 'activate', this.activate, this ); 5279 5280 // Only bother checking media type counts if one of the counts is zero 5281 checkCounts = _.find( this.counts, function( type ) { 5282 return type.count === 0; 5283 } ); 5284 5285 if ( typeof checkCounts !== 'undefined' ) { 5286 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 5287 } 5288 5289 this.on( 'menu:create:gallery', this.createMenu, this ); 5290 this.on( 'menu:create:playlist', this.createMenu, this ); 5291 this.on( 'menu:create:video-playlist', this.createMenu, this ); 5292 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 5293 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 5294 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 5295 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 5296 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 5297 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 5298 5299 handlers = { 5300 menu: { 5301 'default': 'mainMenu', 5302 'gallery': 'galleryMenu', 5303 'playlist': 'playlistMenu', 5304 'video-playlist': 'videoPlaylistMenu' 5305 }, 5306 5307 content: { 5308 'embed': 'embedContent', 5309 'edit-image': 'editImageContent', 5310 'edit-selection': 'editSelectionContent' 5311 }, 5312 5313 toolbar: { 5314 'main-insert': 'mainInsertToolbar', 5315 'main-gallery': 'mainGalleryToolbar', 5316 'gallery-edit': 'galleryEditToolbar', 5317 'gallery-add': 'galleryAddToolbar', 5318 'main-playlist': 'mainPlaylistToolbar', 5319 'playlist-edit': 'playlistEditToolbar', 5320 'playlist-add': 'playlistAddToolbar', 5321 'main-video-playlist': 'mainVideoPlaylistToolbar', 5322 'video-playlist-edit': 'videoPlaylistEditToolbar', 5323 'video-playlist-add': 'videoPlaylistAddToolbar' 5324 } 5325 }; 5326 5327 _.each( handlers, function( regionHandlers, region ) { 5328 _.each( regionHandlers, function( callback, handler ) { 5329 this.on( region + ':render:' + handler, this[ callback ], this ); 5330 }, this ); 5331 }, this ); 5332 }, 5333 5334 activate: function() { 5335 // Hide menu items for states tied to particular media types if there are no items 5336 _.each( this.counts, function( type ) { 5337 if ( type.count < 1 ) { 5338 this.menuItemVisibility( type.state, 'hide' ); 5339 } 5340 }, this ); 5341 }, 5342 5343 mediaTypeCounts: function( model, attr ) { 5344 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 5345 this.counts[ attr ].count++; 5346 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 5347 } 5348 }, 5349 5350 // Menus 5351 /** 5352 * @param {wp.Backbone.View} view 5353 */ 5354 mainMenu: function( view ) { 5355 view.set({ 5356 'library-separator': new wp.media.View({ 5357 className: 'separator', 5358 priority: 100 5359 }) 5360 }); 5361 }, 5362 5363 menuItemVisibility: function( state, visibility ) { 5364 var menu = this.menu.get(); 5365 if ( visibility === 'hide' ) { 5366 menu.hide( state ); 5367 } else if ( visibility === 'show' ) { 5368 menu.show( state ); 5369 } 5370 }, 5371 /** 5372 * @param {wp.Backbone.View} view 5373 */ 5374 galleryMenu: function( view ) { 5375 var lastState = this.lastState(), 5376 previous = lastState && lastState.id, 5377 frame = this; 5378 5379 view.set({ 5380 cancel: { 5381 text: l10n.cancelGalleryTitle, 5382 priority: 20, 5383 click: function() { 5384 if ( previous ) { 5385 frame.setState( previous ); 5386 } else { 5387 frame.close(); 5388 } 5389 5390 // Keep focus inside media modal 5391 // after canceling a gallery 5392 this.controller.modal.focusManager.focus(); 5393 } 5394 }, 5395 separateCancel: new wp.media.View({ 5396 className: 'separator', 5397 priority: 40 5398 }) 5399 }); 5400 }, 5401 5402 playlistMenu: function( view ) { 5403 var lastState = this.lastState(), 5404 previous = lastState && lastState.id, 5405 frame = this; 5406 5407 view.set({ 5408 cancel: { 5409 text: l10n.cancelPlaylistTitle, 5410 priority: 20, 5411 click: function() { 5412 if ( previous ) { 5413 frame.setState( previous ); 5414 } else { 5415 frame.close(); 5416 } 5417 } 5418 }, 5419 separateCancel: new wp.media.View({ 5420 className: 'separator', 5421 priority: 40 5422 }) 5423 }); 5424 }, 5425 5426 videoPlaylistMenu: function( view ) { 5427 var lastState = this.lastState(), 5428 previous = lastState && lastState.id, 5429 frame = this; 5430 5431 view.set({ 5432 cancel: { 5433 text: l10n.cancelVideoPlaylistTitle, 5434 priority: 20, 5435 click: function() { 5436 if ( previous ) { 5437 frame.setState( previous ); 5438 } else { 5439 frame.close(); 5440 } 5441 } 5442 }, 5443 separateCancel: new wp.media.View({ 5444 className: 'separator', 5445 priority: 40 5446 }) 5447 }); 5448 }, 5449 5450 // Content 5451 embedContent: function() { 5452 var view = new wp.media.view.Embed({ 5453 controller: this, 5454 model: this.state() 5455 }).render(); 5456 5457 this.content.set( view ); 5458 5459 if ( ! wp.media.isTouchDevice ) { 5460 view.url.focus(); 5461 } 5462 }, 5463 5464 editSelectionContent: function() { 5465 var state = this.state(), 5466 selection = state.get('selection'), 5467 view; 5468 5469 view = new wp.media.view.AttachmentsBrowser({ 5470 controller: this, 5471 collection: selection, 5472 selection: selection, 5473 model: state, 5474 sortable: true, 5475 search: false, 5476 date: false, 5477 dragInfo: true, 5478 5479 AttachmentView: wp.media.view.Attachments.EditSelection 5480 }).render(); 5481 5482 view.toolbar.set( 'backToLibrary', { 5483 text: l10n.returnToLibrary, 5484 priority: -100, 5485 5486 click: function() { 5487 this.controller.content.mode('browse'); 5488 } 5489 }); 5490 5491 // Browse our library of attachments. 5492 this.content.set( view ); 5493 5494 // Trigger the controller to set focus 5495 this.trigger( 'edit:selection', this ); 5496 }, 5497 5498 editImageContent: function() { 5499 var image = this.state().get('image'), 5500 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 5501 5502 this.content.set( view ); 5503 5504 // after creating the wrapper view, load the actual editor via an ajax call 5505 view.loadEditor(); 5506 5507 }, 5508 5509 // Toolbars 5510 5511 /** 5512 * @param {wp.Backbone.View} view 5513 */ 5514 selectionStatusToolbar: function( view ) { 5515 var editable = this.state().get('editable'); 5516 5517 view.set( 'selection', new wp.media.view.Selection({ 5518 controller: this, 5519 collection: this.state().get('selection'), 5520 priority: -40, 5521 5522 // If the selection is editable, pass the callback to 5523 // switch the content mode. 5524 editable: editable && function() { 5525 this.controller.content.mode('edit-selection'); 5526 } 5527 }).render() ); 5528 }, 5529 5530 /** 5531 * @param {wp.Backbone.View} view 5532 */ 5533 mainInsertToolbar: function( view ) { 5534 var controller = this; 5535 5536 this.selectionStatusToolbar( view ); 5537 5538 view.set( 'insert', { 5539 style: 'primary', 5540 priority: 80, 5541 text: l10n.insertIntoPost, 5542 requires: { selection: true }, 5543 5544 /** 5545 * @fires wp.media.controller.State#insert 5546 */ 5547 click: function() { 5548 var state = controller.state(), 5549 selection = state.get('selection'); 5550 5551 controller.close(); 5552 state.trigger( 'insert', selection ).reset(); 5553 } 5554 }); 5555 }, 5556 5557 /** 5558 * @param {wp.Backbone.View} view 5559 */ 5560 mainGalleryToolbar: function( view ) { 5561 var controller = this; 5562 5563 this.selectionStatusToolbar( view ); 5564 5565 view.set( 'gallery', { 5566 style: 'primary', 5567 text: l10n.createNewGallery, 5568 priority: 60, 5569 requires: { selection: true }, 5570 5571 click: function() { 5572 var selection = controller.state().get('selection'), 5573 edit = controller.state('gallery-edit'), 5574 models = selection.where({ type: 'image' }); 5575 5576 edit.set( 'library', new wp.media.model.Selection( models, { 5577 props: selection.props.toJSON(), 5578 multiple: true 5579 }) ); 5580 5581 this.controller.setState('gallery-edit'); 5582 5583 // Keep focus inside media modal 5584 // after jumping to gallery view 5585 this.controller.modal.focusManager.focus(); 5586 } 5587 }); 5588 }, 5589 5590 mainPlaylistToolbar: function( view ) { 5591 var controller = this; 5592 5593 this.selectionStatusToolbar( view ); 5594 5595 view.set( 'playlist', { 5596 style: 'primary', 5597 text: l10n.createNewPlaylist, 5598 priority: 100, 5599 requires: { selection: true }, 5600 5601 click: function() { 5602 var selection = controller.state().get('selection'), 5603 edit = controller.state('playlist-edit'), 5604 models = selection.where({ type: 'audio' }); 5605 5606 edit.set( 'library', new wp.media.model.Selection( models, { 5607 props: selection.props.toJSON(), 5608 multiple: true 5609 }) ); 5610 5611 this.controller.setState('playlist-edit'); 5612 5613 // Keep focus inside media modal 5614 // after jumping to playlist view 5615 this.controller.modal.focusManager.focus(); 5616 } 5617 }); 5618 }, 5619 5620 mainVideoPlaylistToolbar: function( view ) { 5621 var controller = this; 5622 5623 this.selectionStatusToolbar( view ); 5624 5625 view.set( 'video-playlist', { 5626 style: 'primary', 5627 text: l10n.createNewVideoPlaylist, 5628 priority: 100, 5629 requires: { selection: true }, 5630 5631 click: function() { 5632 var selection = controller.state().get('selection'), 5633 edit = controller.state('video-playlist-edit'), 5634 models = selection.where({ type: 'video' }); 5635 5636 edit.set( 'library', new wp.media.model.Selection( models, { 5637 props: selection.props.toJSON(), 5638 multiple: true 5639 }) ); 5640 5641 this.controller.setState('video-playlist-edit'); 5642 5643 // Keep focus inside media modal 5644 // after jumping to video playlist view 5645 this.controller.modal.focusManager.focus(); 5646 } 5647 }); 5648 }, 5649 5650 featuredImageToolbar: function( toolbar ) { 5651 this.createSelectToolbar( toolbar, { 5652 text: l10n.setFeaturedImage, 5653 state: this.options.state 5654 }); 5655 }, 5656 5657 mainEmbedToolbar: function( toolbar ) { 5658 toolbar.view = new wp.media.view.Toolbar.Embed({ 5659 controller: this 5660 }); 5661 }, 5662 5663 galleryEditToolbar: function() { 5664 var editing = this.state().get('editing'); 5665 this.toolbar.set( new wp.media.view.Toolbar({ 5666 controller: this, 5667 items: { 5668 insert: { 5669 style: 'primary', 5670 text: editing ? l10n.updateGallery : l10n.insertGallery, 5671 priority: 80, 5672 requires: { library: true }, 5673 5674 /** 5675 * @fires wp.media.controller.State#update 5676 */ 5677 click: function() { 5678 var controller = this.controller, 5679 state = controller.state(); 5680 5681 controller.close(); 5682 state.trigger( 'update', state.get('library') ); 5683 5684 // Restore and reset the default state. 5685 controller.setState( controller.options.state ); 5686 controller.reset(); 5687 } 5688 } 5689 } 5690 }) ); 5691 }, 5692 5693 galleryAddToolbar: function() { 5694 this.toolbar.set( new wp.media.view.Toolbar({ 5695 controller: this, 5696 items: { 5697 insert: { 5698 style: 'primary', 5699 text: l10n.addToGallery, 5700 priority: 80, 5701 requires: { selection: true }, 5702 5703 /** 5704 * @fires wp.media.controller.State#reset 5705 */ 5706 click: function() { 5707 var controller = this.controller, 5708 state = controller.state(), 5709 edit = controller.state('gallery-edit'); 5710 5711 edit.get('library').add( state.get('selection').models ); 5712 state.trigger('reset'); 5713 controller.setState('gallery-edit'); 5714 } 5715 } 5716 } 5717 }) ); 5718 }, 5719 5720 playlistEditToolbar: function() { 5721 var editing = this.state().get('editing'); 5722 this.toolbar.set( new wp.media.view.Toolbar({ 5723 controller: this, 5724 items: { 5725 insert: { 5726 style: 'primary', 5727 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 5728 priority: 80, 5729 requires: { library: true }, 5730 5731 /** 5732 * @fires wp.media.controller.State#update 5733 */ 5734 click: function() { 5735 var controller = this.controller, 5736 state = controller.state(); 5737 5738 controller.close(); 5739 state.trigger( 'update', state.get('library') ); 5740 5741 // Restore and reset the default state. 5742 controller.setState( controller.options.state ); 5743 controller.reset(); 5744 } 5745 } 5746 } 5747 }) ); 5748 }, 5749 5750 playlistAddToolbar: function() { 5751 this.toolbar.set( new wp.media.view.Toolbar({ 5752 controller: this, 5753 items: { 5754 insert: { 5755 style: 'primary', 5756 text: l10n.addToPlaylist, 5757 priority: 80, 5758 requires: { selection: true }, 5759 5760 /** 5761 * @fires wp.media.controller.State#reset 5762 */ 5763 click: function() { 5764 var controller = this.controller, 5765 state = controller.state(), 5766 edit = controller.state('playlist-edit'); 5767 5768 edit.get('library').add( state.get('selection').models ); 5769 state.trigger('reset'); 5770 controller.setState('playlist-edit'); 5771 } 5772 } 5773 } 5774 }) ); 5775 }, 5776 5777 videoPlaylistEditToolbar: function() { 5778 var editing = this.state().get('editing'); 5779 this.toolbar.set( new wp.media.view.Toolbar({ 5780 controller: this, 5781 items: { 5782 insert: { 5783 style: 'primary', 5784 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 5785 priority: 140, 5786 requires: { library: true }, 5787 5788 click: function() { 5789 var controller = this.controller, 5790 state = controller.state(), 5791 library = state.get('library'); 5792 5793 library.type = 'video'; 5794 5795 controller.close(); 5796 state.trigger( 'update', library ); 5797 5798 // Restore and reset the default state. 5799 controller.setState( controller.options.state ); 5800 controller.reset(); 5801 } 5802 } 5803 } 5804 }) ); 5805 }, 5806 5807 videoPlaylistAddToolbar: function() { 5808 this.toolbar.set( new wp.media.view.Toolbar({ 5809 controller: this, 5810 items: { 5811 insert: { 5812 style: 'primary', 5813 text: l10n.addToVideoPlaylist, 5814 priority: 140, 5815 requires: { selection: true }, 5816 5817 click: function() { 5818 var controller = this.controller, 5819 state = controller.state(), 5820 edit = controller.state('video-playlist-edit'); 5821 5822 edit.get('library').add( state.get('selection').models ); 5823 state.trigger('reset'); 5824 controller.setState('video-playlist-edit'); 5825 } 5826 } 5827 } 5828 }) ); 5829 } 5830 }); 5831 5832 module.exports = Post; 5833 5834 },{}],46:[function(require,module,exports){ 5835 /** 5836 * wp.media.view.MediaFrame.Select 5837 * 5838 * A frame for selecting an item or items from the media library. 5839 * 5840 * @class 5841 * @augments wp.media.view.MediaFrame 5842 * @augments wp.media.view.Frame 5843 * @augments wp.media.View 5844 * @augments wp.Backbone.View 5845 * @augments Backbone.View 5846 * @mixes wp.media.controller.StateMachine 5847 */ 5848 5849 var MediaFrame = wp.media.view.MediaFrame, 5850 l10n = wp.media.view.l10n, 5851 Select; 5852 5853 Select = MediaFrame.extend({ 5854 initialize: function() { 5855 // Call 'initialize' directly on the parent class. 5856 MediaFrame.prototype.initialize.apply( this, arguments ); 5857 5858 _.defaults( this.options, { 5859 selection: [], 5860 library: {}, 5861 multiple: false, 5862 state: 'library' 5863 }); 5864 5865 this.createSelection(); 5866 this.createStates(); 5867 this.bindHandlers(); 5868 }, 5869 5870 /** 5871 * Attach a selection collection to the frame. 5872 * 5873 * A selection is a collection of attachments used for a specific purpose 5874 * by a media frame. e.g. Selecting an attachment (or many) to insert into 5875 * post content. 5876 * 5877 * @see media.model.Selection 5878 */ 5879 createSelection: function() { 5880 var selection = this.options.selection; 5881 5882 if ( ! (selection instanceof wp.media.model.Selection) ) { 5883 this.options.selection = new wp.media.model.Selection( selection, { 5884 multiple: this.options.multiple 5885 }); 5886 } 5887 5888 this._selection = { 5889 attachments: new wp.media.model.Attachments(), 5890 difference: [] 5891 }; 5892 }, 5893 5894 /** 5895 * Create the default states on the frame. 5896 */ 5897 createStates: function() { 5898 var options = this.options; 5899 5900 if ( this.options.states ) { 5901 return; 5902 } 5903 5904 // Add the default states. 5905 this.states.add([ 5906 // Main states. 5907 new wp.media.controller.Library({ 5908 library: wp.media.query( options.library ), 5909 multiple: options.multiple, 5910 title: options.title, 5911 priority: 20 5912 }) 5913 ]); 5914 }, 5915 5916 /** 5917 * Bind region mode event callbacks. 5918 * 5919 * @see media.controller.Region.render 5920 */ 5921 bindHandlers: function() { 5922 this.on( 'router:create:browse', this.createRouter, this ); 5923 this.on( 'router:render:browse', this.browseRouter, this ); 5924 this.on( 'content:create:browse', this.browseContent, this ); 5925 this.on( 'content:render:upload', this.uploadContent, this ); 5926 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 5927 }, 5928 5929 /** 5930 * Render callback for the router region in the `browse` mode. 5931 * 5932 * @param {wp.media.view.Router} routerView 5933 */ 5934 browseRouter: function( routerView ) { 5935 routerView.set({ 5936 upload: { 5937 text: l10n.uploadFilesTitle, 5938 priority: 20 5939 }, 5940 browse: { 5941 text: l10n.mediaLibraryTitle, 5942 priority: 40 5943 } 5944 }); 5945 }, 5946 5947 /** 5948 * Render callback for the content region in the `browse` mode. 5949 * 5950 * @param {wp.media.controller.Region} contentRegion 5951 */ 5952 browseContent: function( contentRegion ) { 5953 var state = this.state(); 5954 5955 this.$el.removeClass('hide-toolbar'); 5956 5957 // Browse our library of attachments. 5958 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 5959 controller: this, 5960 collection: state.get('library'), 5961 selection: state.get('selection'), 5962 model: state, 5963 sortable: state.get('sortable'), 5964 search: state.get('searchable'), 5965 filters: state.get('filterable'), 5966 date: state.get('date'), 5967 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 5968 dragInfo: state.get('dragInfo'), 5969 5970 idealColumnWidth: state.get('idealColumnWidth'), 5971 suggestedWidth: state.get('suggestedWidth'), 5972 suggestedHeight: state.get('suggestedHeight'), 5973 5974 AttachmentView: state.get('AttachmentView') 5975 }); 5976 }, 5977 5978 /** 5979 * Render callback for the content region in the `upload` mode. 5980 */ 5981 uploadContent: function() { 5982 this.$el.removeClass( 'hide-toolbar' ); 5983 this.content.set( new wp.media.view.UploaderInline({ 5984 controller: this 5985 }) ); 5986 }, 5987 5988 /** 5989 * Toolbars 5990 * 5991 * @param {Object} toolbar 5992 * @param {Object} [options={}] 5993 * @this wp.media.controller.Region 5994 */ 5995 createSelectToolbar: function( toolbar, options ) { 5996 options = options || this.options.button || {}; 5997 options.controller = this; 5998 5999 toolbar.view = new wp.media.view.Toolbar.Select( options ); 6000 } 6001 }); 6002 6003 module.exports = Select; 6004 6005 },{}],47:[function(require,module,exports){ 6006 /** 6007 * wp.media.view.Iframe 6008 * 6009 * @class 6010 * @augments wp.media.View 6011 * @augments wp.Backbone.View 6012 * @augments Backbone.View 6013 */ 6014 var Iframe = wp.media.View.extend({ 6015 className: 'media-iframe', 6016 /** 6017 * @returns {wp.media.view.Iframe} Returns itself to allow chaining 6018 */ 6019 render: function() { 6020 this.views.detach(); 6021 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 6022 this.views.render(); 6023 return this; 6024 } 6025 }); 6026 6027 module.exports = Iframe; 6028 6029 },{}],48:[function(require,module,exports){ 8434 6030 /** 8435 6031 * wp.media.view.ImageDetails … … 8599 6195 module.exports = ImageDetails; 8600 6196 8601 8602 /***/ }), 8603 /* 96 */ 8604 /***/ (function(module, exports) { 8605 6197 },{}],49:[function(require,module,exports){ 8606 6198 /** 8607 * wp.media.view.Cropper 8608 * 8609 * Uses the imgAreaSelect plugin to allow a user to crop an image. 8610 * 8611 * Takes imgAreaSelect options from 8612 * wp.customize.HeaderControl.calculateImageSelectOptions via 8613 * wp.customize.HeaderControl.openMM. 6199 * wp.media.view.Label 6200 * 6201 * @class 6202 * @augments wp.media.View 6203 * @augments wp.Backbone.View 6204 * @augments Backbone.View 6205 */ 6206 var Label = wp.media.View.extend({ 6207 tagName: 'label', 6208 className: 'screen-reader-text', 6209 6210 initialize: function() { 6211 this.value = this.options.value; 6212 }, 6213 6214 render: function() { 6215 this.$el.html( this.value ); 6216 6217 return this; 6218 } 6219 }); 6220 6221 module.exports = Label; 6222 6223 },{}],50:[function(require,module,exports){ 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 },{}],51:[function(require,module,exports){ 6471 /** 6472 * wp.media.view.MenuItem 6473 * 6474 * @class 6475 * @augments wp.media.View 6476 * @augments wp.Backbone.View 6477 * @augments Backbone.View 6478 */ 6479 var $ = jQuery, 6480 MenuItem; 6481 6482 MenuItem = wp.media.View.extend({ 6483 tagName: 'a', 6484 className: 'media-menu-item', 6485 6486 attributes: { 6487 href: '#' 6488 }, 6489 6490 events: { 6491 'click': '_click' 6492 }, 6493 /** 6494 * @param {Object} event 6495 */ 6496 _click: function( event ) { 6497 var clickOverride = this.options.click; 6498 6499 if ( event ) { 6500 event.preventDefault(); 6501 } 6502 6503 if ( clickOverride ) { 6504 clickOverride.call( this ); 6505 } else { 6506 this.click(); 6507 } 6508 6509 // When selecting a tab along the left side, 6510 // focus should be transferred into the main panel 6511 if ( ! wp.media.isTouchDevice ) { 6512 $('.media-frame-content input').first().focus(); 6513 } 6514 }, 6515 6516 click: function() { 6517 var state = this.options.state; 6518 6519 if ( state ) { 6520 this.controller.setState( state ); 6521 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 6522 } 6523 }, 6524 /** 6525 * @returns {wp.media.view.MenuItem} returns itself to allow chaining 6526 */ 6527 render: function() { 6528 var options = this.options; 6529 6530 if ( options.text ) { 6531 this.$el.text( options.text ); 6532 } else if ( options.html ) { 6533 this.$el.html( options.html ); 6534 } 6535 6536 return this; 6537 } 6538 }); 6539 6540 module.exports = MenuItem; 6541 6542 },{}],52:[function(require,module,exports){ 6543 /** 6544 * wp.media.view.Menu 6545 * 6546 * @class 6547 * @augments wp.media.view.PriorityList 6548 * @augments wp.media.View 6549 * @augments wp.Backbone.View 6550 * @augments Backbone.View 6551 */ 6552 var MenuItem = wp.media.view.MenuItem, 6553 PriorityList = wp.media.view.PriorityList, 6554 Menu; 6555 6556 Menu = PriorityList.extend({ 6557 tagName: 'div', 6558 className: 'media-menu', 6559 property: 'state', 6560 ItemView: MenuItem, 6561 region: 'menu', 6562 6563 /* TODO: alternatively hide on any click anywhere 6564 events: { 6565 'click': 'click' 6566 }, 6567 6568 click: function() { 6569 this.$el.removeClass( 'visible' ); 6570 }, 6571 */ 6572 6573 /** 6574 * @param {Object} options 6575 * @param {string} id 6576 * @returns {wp.media.View} 6577 */ 6578 toView: function( options, id ) { 6579 options = options || {}; 6580 options[ this.property ] = options[ this.property ] || id; 6581 return new this.ItemView( options ).render(); 6582 }, 6583 6584 ready: function() { 6585 /** 6586 * call 'ready' directly on the parent class 6587 */ 6588 PriorityList.prototype.ready.apply( this, arguments ); 6589 this.visibility(); 6590 }, 6591 6592 set: function() { 6593 /** 6594 * call 'set' directly on the parent class 6595 */ 6596 PriorityList.prototype.set.apply( this, arguments ); 6597 this.visibility(); 6598 }, 6599 6600 unset: function() { 6601 /** 6602 * call 'unset' directly on the parent class 6603 */ 6604 PriorityList.prototype.unset.apply( this, arguments ); 6605 this.visibility(); 6606 }, 6607 6608 visibility: function() { 6609 var region = this.region, 6610 view = this.controller[ region ].get(), 6611 views = this.views.get(), 6612 hide = ! views || views.length < 2; 6613 6614 if ( this === view ) { 6615 this.controller.$el.toggleClass( 'hide-' + region, hide ); 6616 } 6617 }, 6618 /** 6619 * @param {string} id 6620 */ 6621 select: function( id ) { 6622 var view = this.get( id ); 6623 6624 if ( ! view ) { 6625 return; 6626 } 6627 6628 this.deselect(); 6629 view.$el.addClass('active'); 6630 }, 6631 6632 deselect: function() { 6633 this.$el.children().removeClass('active'); 6634 }, 6635 6636 hide: function( id ) { 6637 var view = this.get( id ); 6638 6639 if ( ! view ) { 6640 return; 6641 } 6642 6643 view.$el.addClass('hidden'); 6644 }, 6645 6646 show: function( id ) { 6647 var view = this.get( id ); 6648 6649 if ( ! view ) { 6650 return; 6651 } 6652 6653 view.$el.removeClass('hidden'); 6654 } 6655 }); 6656 6657 module.exports = Menu; 6658 6659 },{}],53:[function(require,module,exports){ 6660 /** 6661 * wp.media.view.Modal 6662 * 6663 * A modal view, which the media modal uses as its default container. 6664 * 6665 * @class 6666 * @augments wp.media.View 6667 * @augments wp.Backbone.View 6668 * @augments Backbone.View 6669 */ 6670 var $ = jQuery, 6671 Modal; 6672 6673 Modal = wp.media.View.extend({ 6674 tagName: 'div', 6675 template: wp.template('media-modal'), 6676 6677 attributes: { 6678 tabindex: 0 6679 }, 6680 6681 events: { 6682 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 6683 'keydown': 'keydown' 6684 }, 6685 6686 initialize: function() { 6687 _.defaults( this.options, { 6688 container: document.body, 6689 title: '', 6690 propagate: true, 6691 freeze: true 6692 }); 6693 6694 this.focusManager = new wp.media.view.FocusManager({ 6695 el: this.el 6696 }); 6697 }, 6698 /** 6699 * @returns {Object} 6700 */ 6701 prepare: function() { 6702 return { 6703 title: this.options.title 6704 }; 6705 }, 6706 6707 /** 6708 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6709 */ 6710 attach: function() { 6711 if ( this.views.attached ) { 6712 return this; 6713 } 6714 6715 if ( ! this.views.rendered ) { 6716 this.render(); 6717 } 6718 6719 this.$el.appendTo( this.options.container ); 6720 6721 // Manually mark the view as attached and trigger ready. 6722 this.views.attached = true; 6723 this.views.ready(); 6724 6725 return this.propagate('attach'); 6726 }, 6727 6728 /** 6729 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6730 */ 6731 detach: function() { 6732 if ( this.$el.is(':visible') ) { 6733 this.close(); 6734 } 6735 6736 this.$el.detach(); 6737 this.views.attached = false; 6738 return this.propagate('detach'); 6739 }, 6740 6741 /** 6742 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6743 */ 6744 open: function() { 6745 var $el = this.$el, 6746 options = this.options, 6747 mceEditor; 6748 6749 if ( $el.is(':visible') ) { 6750 return this; 6751 } 6752 6753 if ( ! this.views.attached ) { 6754 this.attach(); 6755 } 6756 6757 // If the `freeze` option is set, record the window's scroll position. 6758 if ( options.freeze ) { 6759 this._freeze = { 6760 scrollTop: $( window ).scrollTop() 6761 }; 6762 } 6763 6764 // Disable page scrolling. 6765 $( 'body' ).addClass( 'modal-open' ); 6766 6767 $el.show(); 6768 6769 // Try to close the onscreen keyboard 6770 if ( 'ontouchend' in document ) { 6771 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 6772 mceEditor.iframeElement.focus(); 6773 mceEditor.iframeElement.blur(); 6774 6775 setTimeout( function() { 6776 mceEditor.iframeElement.blur(); 6777 }, 100 ); 6778 } 6779 } 6780 6781 this.$el.focus(); 6782 6783 return this.propagate('open'); 6784 }, 6785 6786 /** 6787 * @param {Object} options 6788 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6789 */ 6790 close: function( options ) { 6791 var freeze = this._freeze; 6792 6793 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 6794 return this; 6795 } 6796 6797 // Enable page scrolling. 6798 $( 'body' ).removeClass( 'modal-open' ); 6799 6800 // Hide modal and remove restricted media modal tab focus once it's closed 6801 this.$el.hide().undelegate( 'keydown' ); 6802 6803 // Put focus back in useful location once modal is closed 6804 $('#wpbody-content').focus(); 6805 6806 this.propagate('close'); 6807 6808 // If the `freeze` option is set, restore the container's scroll position. 6809 if ( freeze ) { 6810 $( window ).scrollTop( freeze.scrollTop ); 6811 } 6812 6813 if ( options && options.escape ) { 6814 this.propagate('escape'); 6815 } 6816 6817 return this; 6818 }, 6819 /** 6820 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6821 */ 6822 escape: function() { 6823 return this.close({ escape: true }); 6824 }, 6825 /** 6826 * @param {Object} event 6827 */ 6828 escapeHandler: function( event ) { 6829 event.preventDefault(); 6830 this.escape(); 6831 }, 6832 6833 /** 6834 * @param {Array|Object} content Views to register to '.media-modal-content' 6835 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6836 */ 6837 content: function( content ) { 6838 this.views.set( '.media-modal-content', content ); 6839 return this; 6840 }, 6841 6842 /** 6843 * Triggers a modal event and if the `propagate` option is set, 6844 * forwards events to the modal's controller. 6845 * 6846 * @param {string} id 6847 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6848 */ 6849 propagate: function( id ) { 6850 this.trigger( id ); 6851 6852 if ( this.options.propagate ) { 6853 this.controller.trigger( id ); 6854 } 6855 6856 return this; 6857 }, 6858 /** 6859 * @param {Object} event 6860 */ 6861 keydown: function( event ) { 6862 // Close the modal when escape is pressed. 6863 if ( 27 === event.which && this.$el.is(':visible') ) { 6864 this.escape(); 6865 event.stopImmediatePropagation(); 6866 } 6867 } 6868 }); 6869 6870 module.exports = Modal; 6871 6872 },{}],54:[function(require,module,exports){ 6873 /** 6874 * wp.media.view.PriorityList 6875 * 6876 * @class 6877 * @augments wp.media.View 6878 * @augments wp.Backbone.View 6879 * @augments Backbone.View 6880 */ 6881 var PriorityList = wp.media.View.extend({ 6882 tagName: 'div', 6883 6884 initialize: function() { 6885 this._views = {}; 6886 6887 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 6888 delete this.options.views; 6889 6890 if ( ! this.options.silent ) { 6891 this.render(); 6892 } 6893 }, 6894 /** 6895 * @param {string} id 6896 * @param {wp.media.View|Object} view 6897 * @param {Object} options 6898 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining 6899 */ 6900 set: function( id, view, options ) { 6901 var priority, views, index; 6902 6903 options = options || {}; 6904 6905 // Accept an object with an `id` : `view` mapping. 6906 if ( _.isObject( id ) ) { 6907 _.each( id, function( view, id ) { 6908 this.set( id, view ); 6909 }, this ); 6910 return this; 6911 } 6912 6913 if ( ! (view instanceof Backbone.View) ) { 6914 view = this.toView( view, id, options ); 6915 } 6916 view.controller = view.controller || this.controller; 6917 6918 this.unset( id ); 6919 6920 priority = view.options.priority || 10; 6921 views = this.views.get() || []; 6922 6923 _.find( views, function( existing, i ) { 6924 if ( existing.options.priority > priority ) { 6925 index = i; 6926 return true; 6927 } 6928 }); 6929 6930 this._views[ id ] = view; 6931 this.views.add( view, { 6932 at: _.isNumber( index ) ? index : views.length || 0 6933 }); 6934 6935 return this; 6936 }, 6937 /** 6938 * @param {string} id 6939 * @returns {wp.media.View} 6940 */ 6941 get: function( id ) { 6942 return this._views[ id ]; 6943 }, 6944 /** 6945 * @param {string} id 6946 * @returns {wp.media.view.PriorityList} 6947 */ 6948 unset: function( id ) { 6949 var view = this.get( id ); 6950 6951 if ( view ) { 6952 view.remove(); 6953 } 6954 6955 delete this._views[ id ]; 6956 return this; 6957 }, 6958 /** 6959 * @param {Object} options 6960 * @returns {wp.media.View} 6961 */ 6962 toView: function( options ) { 6963 return new wp.media.View( options ); 6964 } 6965 }); 6966 6967 module.exports = PriorityList; 6968 6969 },{}],55:[function(require,module,exports){ 6970 /** 6971 * wp.media.view.RouterItem 6972 * 6973 * @class 6974 * @augments wp.media.view.MenuItem 6975 * @augments wp.media.View 6976 * @augments wp.Backbone.View 6977 * @augments Backbone.View 6978 */ 6979 var RouterItem = wp.media.view.MenuItem.extend({ 6980 /** 6981 * On click handler to activate the content region's corresponding mode. 6982 */ 6983 click: function() { 6984 var contentMode = this.options.contentMode; 6985 if ( contentMode ) { 6986 this.controller.content.mode( contentMode ); 6987 } 6988 } 6989 }); 6990 6991 module.exports = RouterItem; 6992 6993 },{}],56:[function(require,module,exports){ 6994 /** 6995 * wp.media.view.Router 6996 * 6997 * @class 6998 * @augments wp.media.view.Menu 6999 * @augments wp.media.view.PriorityList 7000 * @augments wp.media.View 7001 * @augments wp.Backbone.View 7002 * @augments Backbone.View 7003 */ 7004 var Menu = wp.media.view.Menu, 7005 Router; 7006 7007 Router = Menu.extend({ 7008 tagName: 'div', 7009 className: 'media-router', 7010 property: 'contentMode', 7011 ItemView: wp.media.view.RouterItem, 7012 region: 'router', 7013 7014 initialize: function() { 7015 this.controller.on( 'content:render', this.update, this ); 7016 // Call 'initialize' directly on the parent class. 7017 Menu.prototype.initialize.apply( this, arguments ); 7018 }, 7019 7020 update: function() { 7021 var mode = this.controller.content.mode(); 7022 if ( mode ) { 7023 this.select( mode ); 7024 } 7025 } 7026 }); 7027 7028 module.exports = Router; 7029 7030 },{}],57:[function(require,module,exports){ 7031 /** 7032 * wp.media.view.Search 7033 * 7034 * @class 7035 * @augments wp.media.View 7036 * @augments wp.Backbone.View 7037 * @augments Backbone.View 7038 */ 7039 var l10n = wp.media.view.l10n, 7040 Search; 7041 7042 Search = wp.media.View.extend({ 7043 tagName: 'input', 7044 className: 'search', 7045 id: 'media-search-input', 7046 7047 attributes: { 7048 type: 'search', 7049 placeholder: l10n.search 7050 }, 7051 7052 events: { 7053 'input': 'search', 7054 'keyup': 'search', 7055 'change': 'search', 7056 'search': 'search' 7057 }, 7058 7059 /** 7060 * @returns {wp.media.view.Search} Returns itself to allow chaining 7061 */ 7062 render: function() { 7063 this.el.value = this.model.escape('search'); 7064 return this; 7065 }, 7066 7067 search: function( event ) { 7068 if ( event.target.value ) { 7069 this.model.set( 'search', event.target.value ); 7070 } else { 7071 this.model.unset('search'); 7072 } 7073 } 7074 }); 7075 7076 module.exports = Search; 7077 7078 },{}],58:[function(require,module,exports){ 7079 /** 7080 * wp.media.view.Selection 7081 * 7082 * @class 7083 * @augments wp.media.View 7084 * @augments wp.Backbone.View 7085 * @augments Backbone.View 7086 */ 7087 var l10n = wp.media.view.l10n, 7088 Selection; 7089 7090 Selection = wp.media.View.extend({ 7091 tagName: 'div', 7092 className: 'media-selection', 7093 template: wp.template('media-selection'), 7094 7095 events: { 7096 'click .edit-selection': 'edit', 7097 'click .clear-selection': 'clear' 7098 }, 7099 7100 initialize: function() { 7101 _.defaults( this.options, { 7102 editable: false, 7103 clearable: true 7104 }); 7105 7106 /** 7107 * @member {wp.media.view.Attachments.Selection} 7108 */ 7109 this.attachments = new wp.media.view.Attachments.Selection({ 7110 controller: this.controller, 7111 collection: this.collection, 7112 selection: this.collection, 7113 model: new Backbone.Model() 7114 }); 7115 7116 this.views.set( '.selection-view', this.attachments ); 7117 this.collection.on( 'add remove reset', this.refresh, this ); 7118 this.controller.on( 'content:activate', this.refresh, this ); 7119 }, 7120 7121 ready: function() { 7122 this.refresh(); 7123 }, 7124 7125 refresh: function() { 7126 // If the selection hasn't been rendered, bail. 7127 if ( ! this.$el.children().length ) { 7128 return; 7129 } 7130 7131 var collection = this.collection, 7132 editing = 'edit-selection' === this.controller.content.mode(); 7133 7134 // If nothing is selected, display nothing. 7135 this.$el.toggleClass( 'empty', ! collection.length ); 7136 this.$el.toggleClass( 'one', 1 === collection.length ); 7137 this.$el.toggleClass( 'editing', editing ); 7138 7139 this.$('.count').text( l10n.selected.replace('%d', collection.length) ); 7140 }, 7141 7142 edit: function( event ) { 7143 event.preventDefault(); 7144 if ( this.options.editable ) { 7145 this.options.editable.call( this, this.collection ); 7146 } 7147 }, 7148 7149 clear: function( event ) { 7150 event.preventDefault(); 7151 this.collection.reset(); 7152 7153 // Keep focus inside media modal 7154 // after clear link is selected 7155 this.controller.modal.focusManager.focus(); 7156 } 7157 }); 7158 7159 module.exports = Selection; 7160 7161 },{}],59:[function(require,module,exports){ 7162 /** 7163 * wp.media.view.Settings 8614 7164 * 8615 7165 * @class … … 8619 7169 */ 8620 7170 var View = wp.media.View, 8621 UploaderStatus = wp.media.view.UploaderStatus, 8622 l10n = wp.media.view.l10n, 8623 $ = jQuery, 8624 Cropper; 8625 8626 Cropper = View.extend({ 8627 className: 'crop-content', 8628 template: wp.template('crop-content'), 7171 $ = Backbone.$, 7172 Settings; 7173 7174 Settings = View.extend({ 7175 events: { 7176 'click button': 'updateHandler', 7177 'change input': 'updateHandler', 7178 'change select': 'updateHandler', 7179 'change textarea': 'updateHandler' 7180 }, 7181 8629 7182 initialize: function() { 8630 _.bindAll(this, 'onImageLoad'); 8631 }, 8632 ready: function() { 8633 this.controller.frame.on('content:error:crop', this.onError, this); 8634 this.$image = this.$el.find('.crop-image'); 8635 this.$image.on('load', this.onImageLoad); 8636 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250)); 8637 }, 8638 remove: function() { 8639 $(window).off('resize.cropper'); 8640 this.$el.remove(); 8641 this.$el.off(); 8642 View.prototype.remove.apply(this, arguments); 8643 }, 7183 this.model = this.model || new Backbone.Model(); 7184 this.listenTo( this.model, 'change', this.updateChanges ); 7185 }, 7186 8644 7187 prepare: function() { 8645 return { 8646 title: l10n.cropYourImage, 8647 url: this.options.attachment.get('url') 8648 }; 8649 }, 8650 onImageLoad: function() { 8651 var imgOptions = this.controller.get('imgSelectOptions'); 8652 if (typeof imgOptions === 'function') { 8653 imgOptions = imgOptions(this.options.attachment, this.controller); 8654 } 8655 8656 imgOptions = _.extend(imgOptions, {parent: this.$el}); 8657 this.trigger('image-loaded'); 8658 this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 8659 }, 8660 onError: function() { 8661 var filename = this.options.attachment.get('filename'); 8662 8663 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 8664 filename: UploaderStatus.prototype.filename(filename), 8665 message: window._wpMediaViewsL10n.cropError 8666 }), { at: 0 }); 7188 return _.defaults({ 7189 model: this.model.toJSON() 7190 }, this.options ); 7191 }, 7192 /** 7193 * @returns {wp.media.view.Settings} Returns itself to allow chaining 7194 */ 7195 render: function() { 7196 View.prototype.render.apply( this, arguments ); 7197 // Select the correct values. 7198 _( this.model.attributes ).chain().keys().each( this.update, this ); 7199 return this; 7200 }, 7201 /** 7202 * @param {string} key 7203 */ 7204 update: function( key ) { 7205 var value = this.model.get( key ), 7206 $setting = this.$('[data-setting="' + key + '"]'), 7207 $buttons, $value; 7208 7209 // Bail if we didn't find a matching setting. 7210 if ( ! $setting.length ) { 7211 return; 7212 } 7213 7214 // Attempt to determine how the setting is rendered and update 7215 // the selected value. 7216 7217 // Handle dropdowns. 7218 if ( $setting.is('select') ) { 7219 $value = $setting.find('[value="' + value + '"]'); 7220 7221 if ( $value.length ) { 7222 $setting.find('option').prop( 'selected', false ); 7223 $value.prop( 'selected', true ); 7224 } else { 7225 // If we can't find the desired value, record what *is* selected. 7226 this.model.set( key, $setting.find(':selected').val() ); 7227 } 7228 7229 // Handle button groups. 7230 } else if ( $setting.hasClass('button-group') ) { 7231 $buttons = $setting.find('button').removeClass('active'); 7232 $buttons.filter( '[value="' + value + '"]' ).addClass('active'); 7233 7234 // Handle text inputs and textareas. 7235 } else if ( $setting.is('input[type="text"], textarea') ) { 7236 if ( ! $setting.is(':focus') ) { 7237 $setting.val( value ); 7238 } 7239 // Handle checkboxes. 7240 } else if ( $setting.is('input[type="checkbox"]') ) { 7241 $setting.prop( 'checked', !! value && 'false' !== value ); 7242 } 7243 }, 7244 /** 7245 * @param {Object} event 7246 */ 7247 updateHandler: function( event ) { 7248 var $setting = $( event.target ).closest('[data-setting]'), 7249 value = event.target.value, 7250 userSetting; 7251 7252 event.preventDefault(); 7253 7254 if ( ! $setting.length ) { 7255 return; 7256 } 7257 7258 // Use the correct value for checkboxes. 7259 if ( $setting.is('input[type="checkbox"]') ) { 7260 value = $setting[0].checked; 7261 } 7262 7263 // Update the corresponding setting. 7264 this.model.set( $setting.data('setting'), value ); 7265 7266 // If the setting has a corresponding user setting, 7267 // update that as well. 7268 if ( userSetting = $setting.data('userSetting') ) { 7269 window.setUserSetting( userSetting, value ); 7270 } 7271 }, 7272 7273 updateChanges: function( model ) { 7274 if ( model.hasChanged() ) { 7275 _( model.changed ).chain().keys().each( this.update, this ); 7276 } 8667 7277 } 8668 7278 }); 8669 7279 8670 module.exports = Cropper; 8671 8672 8673 /***/ }), 8674 /* 97 */ 8675 /***/ (function(module, exports) { 8676 7280 module.exports = Settings; 7281 7282 },{}],60:[function(require,module,exports){ 7283 /** 7284 * wp.media.view.Settings.AttachmentDisplay 7285 * 7286 * @class 7287 * @augments wp.media.view.Settings 7288 * @augments wp.media.View 7289 * @augments wp.Backbone.View 7290 * @augments Backbone.View 7291 */ 7292 var Settings = wp.media.view.Settings, 7293 AttachmentDisplay; 7294 7295 AttachmentDisplay = Settings.extend({ 7296 className: 'attachment-display-settings', 7297 template: wp.template('attachment-display-settings'), 7298 7299 initialize: function() { 7300 var attachment = this.options.attachment; 7301 7302 _.defaults( this.options, { 7303 userSettings: false 7304 }); 7305 // Call 'initialize' directly on the parent class. 7306 Settings.prototype.initialize.apply( this, arguments ); 7307 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 7308 7309 if ( attachment ) { 7310 attachment.on( 'change:uploading', this.render, this ); 7311 } 7312 }, 7313 7314 dispose: function() { 7315 var attachment = this.options.attachment; 7316 if ( attachment ) { 7317 attachment.off( null, null, this ); 7318 } 7319 /** 7320 * call 'dispose' directly on the parent class 7321 */ 7322 Settings.prototype.dispose.apply( this, arguments ); 7323 }, 7324 /** 7325 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining 7326 */ 7327 render: function() { 7328 var attachment = this.options.attachment; 7329 if ( attachment ) { 7330 _.extend( this.options, { 7331 sizes: attachment.get('sizes'), 7332 type: attachment.get('type') 7333 }); 7334 } 7335 /** 7336 * call 'render' directly on the parent class 7337 */ 7338 Settings.prototype.render.call( this ); 7339 this.updateLinkTo(); 7340 return this; 7341 }, 7342 7343 updateLinkTo: function() { 7344 var linkTo = this.model.get('link'), 7345 $input = this.$('.link-to-custom'), 7346 attachment = this.options.attachment; 7347 7348 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 7349 $input.addClass( 'hidden' ); 7350 return; 7351 } 7352 7353 if ( attachment ) { 7354 if ( 'post' === linkTo ) { 7355 $input.val( attachment.get('link') ); 7356 } else if ( 'file' === linkTo ) { 7357 $input.val( attachment.get('url') ); 7358 } else if ( ! this.model.get('linkUrl') ) { 7359 $input.val('http://'); 7360 } 7361 7362 $input.prop( 'readonly', 'custom' !== linkTo ); 7363 } 7364 7365 $input.removeClass( 'hidden' ); 7366 7367 // If the input is visible, focus and select its contents. 7368 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) { 7369 $input.focus()[0].select(); 7370 } 7371 } 7372 }); 7373 7374 module.exports = AttachmentDisplay; 7375 7376 },{}],61:[function(require,module,exports){ 7377 /** 7378 * wp.media.view.Settings.Gallery 7379 * 7380 * @class 7381 * @augments wp.media.view.Settings 7382 * @augments wp.media.View 7383 * @augments wp.Backbone.View 7384 * @augments Backbone.View 7385 */ 7386 var Gallery = wp.media.view.Settings.extend({ 7387 className: 'collection-settings gallery-settings', 7388 template: wp.template('gallery-settings') 7389 }); 7390 7391 module.exports = Gallery; 7392 7393 },{}],62:[function(require,module,exports){ 7394 /** 7395 * wp.media.view.Settings.Playlist 7396 * 7397 * @class 7398 * @augments wp.media.view.Settings 7399 * @augments wp.media.View 7400 * @augments wp.Backbone.View 7401 * @augments Backbone.View 7402 */ 7403 var Playlist = wp.media.view.Settings.extend({ 7404 className: 'collection-settings playlist-settings', 7405 template: wp.template('playlist-settings') 7406 }); 7407 7408 module.exports = Playlist; 7409 7410 },{}],63:[function(require,module,exports){ 7411 /** 7412 * wp.media.view.Sidebar 7413 * 7414 * @class 7415 * @augments wp.media.view.PriorityList 7416 * @augments wp.media.View 7417 * @augments wp.Backbone.View 7418 * @augments Backbone.View 7419 */ 7420 var Sidebar = wp.media.view.PriorityList.extend({ 7421 className: 'media-sidebar' 7422 }); 7423 7424 module.exports = Sidebar; 7425 7426 },{}],64:[function(require,module,exports){ 8677 7427 /** 8678 7428 * wp.media.view.SiteIconCropper … … 8717 7467 module.exports = SiteIconCropper; 8718 7468 8719 8720 /***/ }), 8721 /* 98 */ 8722 /***/ (function(module, exports) { 8723 7469 },{}],65:[function(require,module,exports){ 8724 7470 /** 8725 7471 * wp.media.view.SiteIconPreview … … 8777 7523 module.exports = SiteIconPreview; 8778 7524 8779 8780 /***/ }), 8781 /* 99 */ 8782 /***/ (function(module, exports) { 8783 8784 /** 8785 * wp.media.view.EditImage 8786 * 8787 * @class 8788 * @augments wp.media.View 8789 * @augments wp.Backbone.View 8790 * @augments Backbone.View 8791 */ 8792 var View = wp.media.View, 8793 EditImage; 8794 8795 EditImage = View.extend({ 8796 className: 'image-editor', 8797 template: wp.template('image-editor'), 8798 8799 initialize: function( options ) { 8800 this.editor = window.imageEdit; 8801 this.controller = options.controller; 8802 View.prototype.initialize.apply( this, arguments ); 8803 }, 8804 8805 prepare: function() { 8806 return this.model.toJSON(); 8807 }, 8808 8809 loadEditor: function() { 8810 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this ); 8811 dfd.done( _.bind( this.focus, this ) ); 8812 }, 8813 8814 focus: function() { 8815 this.$( '.imgedit-submit .button' ).eq( 0 ).focus(); 8816 }, 8817 8818 back: function() { 8819 var lastState = this.controller.lastState(); 8820 this.controller.setState( lastState ); 8821 }, 8822 8823 refresh: function() { 8824 this.model.fetch(); 8825 }, 8826 8827 save: function() { 8828 var lastState = this.controller.lastState(); 8829 8830 this.model.fetch().done( _.bind( function() { 8831 this.controller.setState( lastState ); 8832 }, this ) ); 8833 } 8834 8835 }); 8836 8837 module.exports = EditImage; 8838 8839 8840 /***/ }), 8841 /* 100 */ 8842 /***/ (function(module, exports) { 8843 7525 },{}],66:[function(require,module,exports){ 8844 7526 /** 8845 7527 * wp.media.view.Spinner … … 8876 7558 module.exports = Spinner; 8877 7559 8878 8879 /***/ }) 8880 /******/ ])); 7560 },{}],67:[function(require,module,exports){ 7561 /** 7562 * wp.media.view.Toolbar 7563 * 7564 * A toolbar which consists of a primary and a secondary section. Each sections 7565 * can be filled with views. 7566 * 7567 * @class 7568 * @augments wp.media.View 7569 * @augments wp.Backbone.View 7570 * @augments Backbone.View 7571 */ 7572 var View = wp.media.View, 7573 Toolbar; 7574 7575 Toolbar = View.extend({ 7576 tagName: 'div', 7577 className: 'media-toolbar', 7578 7579 initialize: function() { 7580 var state = this.controller.state(), 7581 selection = this.selection = state.get('selection'), 7582 library = this.library = state.get('library'); 7583 7584 this._views = {}; 7585 7586 // The toolbar is composed of two `PriorityList` views. 7587 this.primary = new wp.media.view.PriorityList(); 7588 this.secondary = new wp.media.view.PriorityList(); 7589 this.primary.$el.addClass('media-toolbar-primary search-form'); 7590 this.secondary.$el.addClass('media-toolbar-secondary'); 7591 7592 this.views.set([ this.secondary, this.primary ]); 7593 7594 if ( this.options.items ) { 7595 this.set( this.options.items, { silent: true }); 7596 } 7597 7598 if ( ! this.options.silent ) { 7599 this.render(); 7600 } 7601 7602 if ( selection ) { 7603 selection.on( 'add remove reset', this.refresh, this ); 7604 } 7605 7606 if ( library ) { 7607 library.on( 'add remove reset', this.refresh, this ); 7608 } 7609 }, 7610 /** 7611 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining 7612 */ 7613 dispose: function() { 7614 if ( this.selection ) { 7615 this.selection.off( null, null, this ); 7616 } 7617 7618 if ( this.library ) { 7619 this.library.off( null, null, this ); 7620 } 7621 /** 7622 * call 'dispose' directly on the parent class 7623 */ 7624 return View.prototype.dispose.apply( this, arguments ); 7625 }, 7626 7627 ready: function() { 7628 this.refresh(); 7629 }, 7630 7631 /** 7632 * @param {string} id 7633 * @param {Backbone.View|Object} view 7634 * @param {Object} [options={}] 7635 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 7636 */ 7637 set: function( id, view, options ) { 7638 var list; 7639 options = options || {}; 7640 7641 // Accept an object with an `id` : `view` mapping. 7642 if ( _.isObject( id ) ) { 7643 _.each( id, function( view, id ) { 7644 this.set( id, view, { silent: true }); 7645 }, this ); 7646 7647 } else { 7648 if ( ! ( view instanceof Backbone.View ) ) { 7649 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 7650 view = new wp.media.view.Button( view ).render(); 7651 } 7652 7653 view.controller = view.controller || this.controller; 7654 7655 this._views[ id ] = view; 7656 7657 list = view.options.priority < 0 ? 'secondary' : 'primary'; 7658 this[ list ].set( id, view, options ); 7659 } 7660 7661 if ( ! options.silent ) { 7662 this.refresh(); 7663 } 7664 7665 return this; 7666 }, 7667 /** 7668 * @param {string} id 7669 * @returns {wp.media.view.Button} 7670 */ 7671 get: function( id ) { 7672 return this._views[ id ]; 7673 }, 7674 /** 7675 * @param {string} id 7676 * @param {Object} options 7677 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 7678 */ 7679 unset: function( id, options ) { 7680 delete this._views[ id ]; 7681 this.primary.unset( id, options ); 7682 this.secondary.unset( id, options ); 7683 7684 if ( ! options || ! options.silent ) { 7685 this.refresh(); 7686 } 7687 return this; 7688 }, 7689 7690 refresh: function() { 7691 var state = this.controller.state(), 7692 library = state.get('library'), 7693 selection = state.get('selection'); 7694 7695 _.each( this._views, function( button ) { 7696 if ( ! button.model || ! button.options || ! button.options.requires ) { 7697 return; 7698 } 7699 7700 var requires = button.options.requires, 7701 disabled = false; 7702 7703 // Prevent insertion of attachments if any of them are still uploading 7704 disabled = _.some( selection.models, function( attachment ) { 7705 return attachment.get('uploading') === true; 7706 }); 7707 7708 if ( requires.selection && selection && ! selection.length ) { 7709 disabled = true; 7710 } else if ( requires.library && library && ! library.length ) { 7711 disabled = true; 7712 } 7713 button.model.set( 'disabled', disabled ); 7714 }); 7715 } 7716 }); 7717 7718 module.exports = Toolbar; 7719 7720 },{}],68:[function(require,module,exports){ 7721 /** 7722 * wp.media.view.Toolbar.Embed 7723 * 7724 * @class 7725 * @augments wp.media.view.Toolbar.Select 7726 * @augments wp.media.view.Toolbar 7727 * @augments wp.media.View 7728 * @augments wp.Backbone.View 7729 * @augments Backbone.View 7730 */ 7731 var Select = wp.media.view.Toolbar.Select, 7732 l10n = wp.media.view.l10n, 7733 Embed; 7734 7735 Embed = Select.extend({ 7736 initialize: function() { 7737 _.defaults( this.options, { 7738 text: l10n.insertIntoPost, 7739 requires: false 7740 }); 7741 // Call 'initialize' directly on the parent class. 7742 Select.prototype.initialize.apply( this, arguments ); 7743 }, 7744 7745 refresh: function() { 7746 var url = this.controller.state().props.get('url'); 7747 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 7748 /** 7749 * call 'refresh' directly on the parent class 7750 */ 7751 Select.prototype.refresh.apply( this, arguments ); 7752 } 7753 }); 7754 7755 module.exports = Embed; 7756 7757 },{}],69:[function(require,module,exports){ 7758 /** 7759 * wp.media.view.Toolbar.Select 7760 * 7761 * @class 7762 * @augments wp.media.view.Toolbar 7763 * @augments wp.media.View 7764 * @augments wp.Backbone.View 7765 * @augments Backbone.View 7766 */ 7767 var Toolbar = wp.media.view.Toolbar, 7768 l10n = wp.media.view.l10n, 7769 Select; 7770 7771 Select = Toolbar.extend({ 7772 initialize: function() { 7773 var options = this.options; 7774 7775 _.bindAll( this, 'clickSelect' ); 7776 7777 _.defaults( options, { 7778 event: 'select', 7779 state: false, 7780 reset: true, 7781 close: true, 7782 text: l10n.select, 7783 7784 // Does the button rely on the selection? 7785 requires: { 7786 selection: true 7787 } 7788 }); 7789 7790 options.items = _.defaults( options.items || {}, { 7791 select: { 7792 style: 'primary', 7793 text: options.text, 7794 priority: 80, 7795 click: this.clickSelect, 7796 requires: options.requires 7797 } 7798 }); 7799 // Call 'initialize' directly on the parent class. 7800 Toolbar.prototype.initialize.apply( this, arguments ); 7801 }, 7802 7803 clickSelect: function() { 7804 var options = this.options, 7805 controller = this.controller; 7806 7807 if ( options.close ) { 7808 controller.close(); 7809 } 7810 7811 if ( options.event ) { 7812 controller.state().trigger( options.event ); 7813 } 7814 7815 if ( options.state ) { 7816 controller.setState( options.state ); 7817 } 7818 7819 if ( options.reset ) { 7820 controller.reset(); 7821 } 7822 } 7823 }); 7824 7825 module.exports = Select; 7826 7827 },{}],70:[function(require,module,exports){ 7828 /** 7829 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap) 7830 * and relays drag'n'dropped files to a media workflow. 7831 * 7832 * wp.media.view.EditorUploader 7833 * 7834 * @class 7835 * @augments wp.media.View 7836 * @augments wp.Backbone.View 7837 * @augments Backbone.View 7838 */ 7839 var View = wp.media.View, 7840 l10n = wp.media.view.l10n, 7841 $ = jQuery, 7842 EditorUploader; 7843 7844 EditorUploader = View.extend({ 7845 tagName: 'div', 7846 className: 'uploader-editor', 7847 template: wp.template( 'uploader-editor' ), 7848 7849 localDrag: false, 7850 overContainer: false, 7851 overDropzone: false, 7852 draggingFile: null, 7853 7854 /** 7855 * Bind drag'n'drop events to callbacks. 7856 */ 7857 initialize: function() { 7858 this.initialized = false; 7859 7860 // Bail if not enabled or UA does not support drag'n'drop or File API. 7861 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 7862 return this; 7863 } 7864 7865 this.$document = $(document); 7866 this.dropzones = []; 7867 this.files = []; 7868 7869 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 7870 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 7871 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 7872 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 7873 7874 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 7875 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 7876 7877 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 7878 this.localDrag = event.type === 'dragstart'; 7879 7880 if ( event.type === 'drop' ) { 7881 this.containerDragleave(); 7882 } 7883 }, this ) ); 7884 7885 this.initialized = true; 7886 return this; 7887 }, 7888 7889 /** 7890 * Check browser support for drag'n'drop. 7891 * 7892 * @return Boolean 7893 */ 7894 browserSupport: function() { 7895 var supports = false, div = document.createElement('div'); 7896 7897 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 7898 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 7899 return supports; 7900 }, 7901 7902 isDraggingFile: function( event ) { 7903 if ( this.draggingFile !== null ) { 7904 return this.draggingFile; 7905 } 7906 7907 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 7908 return false; 7909 } 7910 7911 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 7912 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 7913 7914 return this.draggingFile; 7915 }, 7916 7917 refresh: function( e ) { 7918 var dropzone_id; 7919 for ( dropzone_id in this.dropzones ) { 7920 // Hide the dropzones only if dragging has left the screen. 7921 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 7922 } 7923 7924 if ( ! _.isUndefined( e ) ) { 7925 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 7926 } 7927 7928 if ( ! this.overContainer && ! this.overDropzone ) { 7929 this.draggingFile = null; 7930 } 7931 7932 return this; 7933 }, 7934 7935 render: function() { 7936 if ( ! this.initialized ) { 7937 return this; 7938 } 7939 7940 View.prototype.render.apply( this, arguments ); 7941 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) ); 7942 return this; 7943 }, 7944 7945 attach: function( index, editor ) { 7946 // Attach a dropzone to an editor. 7947 var dropzone = this.$el.clone(); 7948 this.dropzones.push( dropzone ); 7949 $( editor ).append( dropzone ); 7950 return this; 7951 }, 7952 7953 /** 7954 * When a file is dropped on the editor uploader, open up an editor media workflow 7955 * and upload the file immediately. 7956 * 7957 * @param {jQuery.Event} event The 'drop' event. 7958 */ 7959 drop: function( event ) { 7960 var $wrap, uploadView; 7961 7962 this.containerDragleave( event ); 7963 this.dropzoneDragleave( event ); 7964 7965 this.files = event.originalEvent.dataTransfer.files; 7966 if ( this.files.length < 1 ) { 7967 return; 7968 } 7969 7970 // Set the active editor to the drop target. 7971 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 7972 if ( $wrap.length > 0 && $wrap[0].id ) { 7973 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 7974 } 7975 7976 if ( ! this.workflow ) { 7977 this.workflow = wp.media.editor.open( window.wpActiveEditor, { 7978 frame: 'post', 7979 state: 'insert', 7980 title: l10n.addMedia, 7981 multiple: true 7982 }); 7983 7984 uploadView = this.workflow.uploader; 7985 7986 if ( uploadView.uploader && uploadView.uploader.ready ) { 7987 this.addFiles.apply( this ); 7988 } else { 7989 this.workflow.on( 'uploader:ready', this.addFiles, this ); 7990 } 7991 } else { 7992 this.workflow.state().reset(); 7993 this.addFiles.apply( this ); 7994 this.workflow.open(); 7995 } 7996 7997 return false; 7998 }, 7999 8000 /** 8001 * Add the files to the uploader. 8002 */ 8003 addFiles: function() { 8004 if ( this.files.length ) { 8005 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 8006 this.files = []; 8007 } 8008 return this; 8009 }, 8010 8011 containerDragover: function( event ) { 8012 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 8013 return; 8014 } 8015 8016 this.overContainer = true; 8017 this.refresh(); 8018 }, 8019 8020 containerDragleave: function() { 8021 this.overContainer = false; 8022 8023 // Throttle dragleave because it's called when bouncing from some elements to others. 8024 _.delay( _.bind( this.refresh, this ), 50 ); 8025 }, 8026 8027 dropzoneDragover: function( event ) { 8028 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 8029 return; 8030 } 8031 8032 this.overDropzone = true; 8033 this.refresh( event ); 8034 return false; 8035 }, 8036 8037 dropzoneDragleave: function( e ) { 8038 this.overDropzone = false; 8039 _.delay( _.bind( this.refresh, this, e ), 50 ); 8040 }, 8041 8042 click: function( e ) { 8043 // In the rare case where the dropzone gets stuck, hide it on click. 8044 this.containerDragleave( e ); 8045 this.dropzoneDragleave( e ); 8046 this.localDrag = false; 8047 } 8048 }); 8049 8050 module.exports = EditorUploader; 8051 8052 },{}],71:[function(require,module,exports){ 8053 /** 8054 * wp.media.view.UploaderInline 8055 * 8056 * The inline uploader that shows up in the 'Upload Files' tab. 8057 * 8058 * @class 8059 * @augments wp.media.View 8060 * @augments wp.Backbone.View 8061 * @augments Backbone.View 8062 */ 8063 var View = wp.media.View, 8064 UploaderInline; 8065 8066 UploaderInline = View.extend({ 8067 tagName: 'div', 8068 className: 'uploader-inline', 8069 template: wp.template('uploader-inline'), 8070 8071 events: { 8072 'click .close': 'hide' 8073 }, 8074 8075 initialize: function() { 8076 _.defaults( this.options, { 8077 message: '', 8078 status: true, 8079 canClose: false 8080 }); 8081 8082 if ( ! this.options.$browser && this.controller.uploader ) { 8083 this.options.$browser = this.controller.uploader.$browser; 8084 } 8085 8086 if ( _.isUndefined( this.options.postId ) ) { 8087 this.options.postId = wp.media.view.settings.post.id; 8088 } 8089 8090 if ( this.options.status ) { 8091 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 8092 controller: this.controller 8093 }) ); 8094 } 8095 }, 8096 8097 prepare: function() { 8098 var suggestedWidth = this.controller.state().get('suggestedWidth'), 8099 suggestedHeight = this.controller.state().get('suggestedHeight'), 8100 data = {}; 8101 8102 data.message = this.options.message; 8103 data.canClose = this.options.canClose; 8104 8105 if ( suggestedWidth && suggestedHeight ) { 8106 data.suggestedWidth = suggestedWidth; 8107 data.suggestedHeight = suggestedHeight; 8108 } 8109 8110 return data; 8111 }, 8112 /** 8113 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 8114 */ 8115 dispose: function() { 8116 if ( this.disposing ) { 8117 /** 8118 * call 'dispose' directly on the parent class 8119 */ 8120 return View.prototype.dispose.apply( this, arguments ); 8121 } 8122 8123 // Run remove on `dispose`, so we can be sure to refresh the 8124 // uploader with a view-less DOM. Track whether we're disposing 8125 // so we don't trigger an infinite loop. 8126 this.disposing = true; 8127 return this.remove(); 8128 }, 8129 /** 8130 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 8131 */ 8132 remove: function() { 8133 /** 8134 * call 'remove' directly on the parent class 8135 */ 8136 var result = View.prototype.remove.apply( this, arguments ); 8137 8138 _.defer( _.bind( this.refresh, this ) ); 8139 return result; 8140 }, 8141 8142 refresh: function() { 8143 var uploader = this.controller.uploader; 8144 8145 if ( uploader ) { 8146 uploader.refresh(); 8147 } 8148 }, 8149 /** 8150 * @returns {wp.media.view.UploaderInline} 8151 */ 8152 ready: function() { 8153 var $browser = this.options.$browser, 8154 $placeholder; 8155 8156 if ( this.controller.uploader ) { 8157 $placeholder = this.$('.browser'); 8158 8159 // Check if we've already replaced the placeholder. 8160 if ( $placeholder[0] === $browser[0] ) { 8161 return; 8162 } 8163 8164 $browser.detach().text( $placeholder.text() ); 8165 $browser[0].className = $placeholder[0].className; 8166 $placeholder.replaceWith( $browser.show() ); 8167 } 8168 8169 this.refresh(); 8170 return this; 8171 }, 8172 show: function() { 8173 this.$el.removeClass( 'hidden' ); 8174 }, 8175 hide: function() { 8176 this.$el.addClass( 'hidden' ); 8177 } 8178 8179 }); 8180 8181 module.exports = UploaderInline; 8182 8183 },{}],72:[function(require,module,exports){ 8184 /** 8185 * wp.media.view.UploaderStatusError 8186 * 8187 * @class 8188 * @augments wp.media.View 8189 * @augments wp.Backbone.View 8190 * @augments Backbone.View 8191 */ 8192 var UploaderStatusError = wp.media.View.extend({ 8193 className: 'upload-error', 8194 template: wp.template('uploader-status-error') 8195 }); 8196 8197 module.exports = UploaderStatusError; 8198 8199 },{}],73:[function(require,module,exports){ 8200 /** 8201 * wp.media.view.UploaderStatus 8202 * 8203 * An uploader status for on-going uploads. 8204 * 8205 * @class 8206 * @augments wp.media.View 8207 * @augments wp.Backbone.View 8208 * @augments Backbone.View 8209 */ 8210 var View = wp.media.View, 8211 UploaderStatus; 8212 8213 UploaderStatus = View.extend({ 8214 className: 'media-uploader-status', 8215 template: wp.template('uploader-status'), 8216 8217 events: { 8218 'click .upload-dismiss-errors': 'dismiss' 8219 }, 8220 8221 initialize: function() { 8222 this.queue = wp.Uploader.queue; 8223 this.queue.on( 'add remove reset', this.visibility, this ); 8224 this.queue.on( 'add remove reset change:percent', this.progress, this ); 8225 this.queue.on( 'add remove reset change:uploading', this.info, this ); 8226 8227 this.errors = wp.Uploader.errors; 8228 this.errors.reset(); 8229 this.errors.on( 'add remove reset', this.visibility, this ); 8230 this.errors.on( 'add', this.error, this ); 8231 }, 8232 /** 8233 * @global wp.Uploader 8234 * @returns {wp.media.view.UploaderStatus} 8235 */ 8236 dispose: function() { 8237 wp.Uploader.queue.off( null, null, this ); 8238 /** 8239 * call 'dispose' directly on the parent class 8240 */ 8241 View.prototype.dispose.apply( this, arguments ); 8242 return this; 8243 }, 8244 8245 visibility: function() { 8246 this.$el.toggleClass( 'uploading', !! this.queue.length ); 8247 this.$el.toggleClass( 'errors', !! this.errors.length ); 8248 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 8249 }, 8250 8251 ready: function() { 8252 _.each({ 8253 '$bar': '.media-progress-bar div', 8254 '$index': '.upload-index', 8255 '$total': '.upload-total', 8256 '$filename': '.upload-filename' 8257 }, function( selector, key ) { 8258 this[ key ] = this.$( selector ); 8259 }, this ); 8260 8261 this.visibility(); 8262 this.progress(); 8263 this.info(); 8264 }, 8265 8266 progress: function() { 8267 var queue = this.queue, 8268 $bar = this.$bar; 8269 8270 if ( ! $bar || ! queue.length ) { 8271 return; 8272 } 8273 8274 $bar.width( ( queue.reduce( function( memo, attachment ) { 8275 if ( ! attachment.get('uploading') ) { 8276 return memo + 100; 8277 } 8278 8279 var percent = attachment.get('percent'); 8280 return memo + ( _.isNumber( percent ) ? percent : 100 ); 8281 }, 0 ) / queue.length ) + '%' ); 8282 }, 8283 8284 info: function() { 8285 var queue = this.queue, 8286 index = 0, active; 8287 8288 if ( ! queue.length ) { 8289 return; 8290 } 8291 8292 active = this.queue.find( function( attachment, i ) { 8293 index = i; 8294 return attachment.get('uploading'); 8295 }); 8296 8297 this.$index.text( index + 1 ); 8298 this.$total.text( queue.length ); 8299 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 8300 }, 8301 /** 8302 * @param {string} filename 8303 * @returns {string} 8304 */ 8305 filename: function( filename ) { 8306 return _.escape( filename ); 8307 }, 8308 /** 8309 * @param {Backbone.Model} error 8310 */ 8311 error: function( error ) { 8312 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 8313 filename: this.filename( error.get('file').name ), 8314 message: error.get('message') 8315 }), { at: 0 }); 8316 }, 8317 8318 /** 8319 * @global wp.Uploader 8320 * 8321 * @param {Object} event 8322 */ 8323 dismiss: function( event ) { 8324 var errors = this.views.get('.upload-errors'); 8325 8326 event.preventDefault(); 8327 8328 if ( errors ) { 8329 _.invoke( errors, 'remove' ); 8330 } 8331 wp.Uploader.errors.reset(); 8332 } 8333 }); 8334 8335 module.exports = UploaderStatus; 8336 8337 },{}],74:[function(require,module,exports){ 8338 /** 8339 * wp.media.view.UploaderWindow 8340 * 8341 * An uploader window that allows for dragging and dropping media. 8342 * 8343 * @class 8344 * @augments wp.media.View 8345 * @augments wp.Backbone.View 8346 * @augments Backbone.View 8347 * 8348 * @param {object} [options] Options hash passed to the view. 8349 * @param {object} [options.uploader] Uploader properties. 8350 * @param {jQuery} [options.uploader.browser] 8351 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 8352 * @param {object} [options.uploader.params] 8353 */ 8354 var $ = jQuery, 8355 UploaderWindow; 8356 8357 UploaderWindow = wp.media.View.extend({ 8358 tagName: 'div', 8359 className: 'uploader-window', 8360 template: wp.template('uploader-window'), 8361 8362 initialize: function() { 8363 var uploader; 8364 8365 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body'); 8366 8367 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 8368 dropzone: this.$el, 8369 browser: this.$browser, 8370 params: {} 8371 }); 8372 8373 // Ensure the dropzone is a jQuery collection. 8374 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 8375 uploader.dropzone = $( uploader.dropzone ); 8376 } 8377 8378 this.controller.on( 'activate', this.refresh, this ); 8379 8380 this.controller.on( 'detach', function() { 8381 this.$browser.remove(); 8382 }, this ); 8383 }, 8384 8385 refresh: function() { 8386 if ( this.uploader ) { 8387 this.uploader.refresh(); 8388 } 8389 }, 8390 8391 ready: function() { 8392 var postId = wp.media.view.settings.post.id, 8393 dropzone; 8394 8395 // If the uploader already exists, bail. 8396 if ( this.uploader ) { 8397 return; 8398 } 8399 8400 if ( postId ) { 8401 this.options.uploader.params.post_id = postId; 8402 } 8403 this.uploader = new wp.Uploader( this.options.uploader ); 8404 8405 dropzone = this.uploader.dropzone; 8406 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 8407 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 8408 8409 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 8410 }, 8411 8412 _ready: function() { 8413 this.controller.trigger( 'uploader:ready' ); 8414 }, 8415 8416 show: function() { 8417 var $el = this.$el.show(); 8418 8419 // Ensure that the animation is triggered by waiting until 8420 // the transparent element is painted into the DOM. 8421 _.defer( function() { 8422 $el.css({ opacity: 1 }); 8423 }); 8424 }, 8425 8426 hide: function() { 8427 var $el = this.$el.css({ opacity: 0 }); 8428 8429 wp.media.transition( $el ).done( function() { 8430 // Transition end events are subject to race conditions. 8431 // Make sure that the value is set as intended. 8432 if ( '0' === $el.css('opacity') ) { 8433 $el.hide(); 8434 } 8435 }); 8436 8437 // https://core.trac.wordpress.org/ticket/27341 8438 _.delay( function() { 8439 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 8440 $el.hide(); 8441 } 8442 }, 500 ); 8443 } 8444 }); 8445 8446 module.exports = UploaderWindow; 8447 8448 },{}],75:[function(require,module,exports){ 8449 /** 8450 * wp.media.View 8451 * 8452 * The base view class for media. 8453 * 8454 * Undelegating events, removing events from the model, and 8455 * removing events from the controller mirror the code for 8456 * `Backbone.View.dispose` in Backbone 0.9.8 development. 8457 * 8458 * This behavior has since been removed, and should not be used 8459 * outside of the media manager. 8460 * 8461 * @class 8462 * @augments wp.Backbone.View 8463 * @augments Backbone.View 8464 */ 8465 var View = wp.Backbone.View.extend({ 8466 constructor: function( options ) { 8467 if ( options && options.controller ) { 8468 this.controller = options.controller; 8469 } 8470 wp.Backbone.View.apply( this, arguments ); 8471 }, 8472 /** 8473 * @todo The internal comment mentions this might have been a stop-gap 8474 * before Backbone 0.9.8 came out. Figure out if Backbone core takes 8475 * care of this in Backbone.View now. 8476 * 8477 * @returns {wp.media.View} Returns itself to allow chaining 8478 */ 8479 dispose: function() { 8480 // Undelegating events, removing events from the model, and 8481 // removing events from the controller mirror the code for 8482 // `Backbone.View.dispose` in Backbone 0.9.8 development. 8483 this.undelegateEvents(); 8484 8485 if ( this.model && this.model.off ) { 8486 this.model.off( null, null, this ); 8487 } 8488 8489 if ( this.collection && this.collection.off ) { 8490 this.collection.off( null, null, this ); 8491 } 8492 8493 // Unbind controller events. 8494 if ( this.controller && this.controller.off ) { 8495 this.controller.off( null, null, this ); 8496 } 8497 8498 return this; 8499 }, 8500 /** 8501 * @returns {wp.media.View} Returns itself to allow chaining 8502 */ 8503 remove: function() { 8504 this.dispose(); 8505 /** 8506 * call 'remove' directly on the parent class 8507 */ 8508 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 8509 } 8510 }); 8511 8512 module.exports = View; 8513 8514 },{}]},{},[19]);
Note: See TracChangeset
for help on using the changeset viewer.