Changeset 50020 for branches/4.2
- Timestamp:
- 01/25/2021 08:16:08 PM (4 years ago)
- Location:
- branches/4.2/src/wp-includes/js
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/4.2/src/wp-includes/js/media-audiovideo.js
r46500 r50020 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 /*globals wp, _ */ 71 3 … … 276 208 }; 277 209 278 media.model.PostMedia = __webpack_require__( 1 ); 279 media.controller.AudioDetails = __webpack_require__( 2 ); 280 media.controller.VideoDetails = __webpack_require__( 3 ); 281 media.view.MediaFrame.MediaDetails = __webpack_require__( 4 ); 282 media.view.MediaFrame.AudioDetails = __webpack_require__( 5 ); 283 media.view.MediaFrame.VideoDetails = __webpack_require__( 6 ); 284 media.view.MediaDetails = __webpack_require__( 7 ); 285 media.view.AudioDetails = __webpack_require__( 8 ); 286 media.view.VideoDetails = __webpack_require__( 9 ); 287 288 289 /***/ }), 290 /* 1 */ 291 /***/ (function(module, exports) { 292 293 /*globals wp, Backbone, _ */ 294 295 /** 296 * wp.media.model.PostMedia 297 * 298 * Shared model class for audio and video. Updates the model after 299 * "Add Audio|Video Source" and "Replace Audio|Video" states return 300 * 301 * @class 302 * @augments Backbone.Model 303 */ 304 var PostMedia = Backbone.Model.extend({ 305 initialize: function() { 306 this.attachment = false; 307 }, 308 309 setSource: function( attachment ) { 310 this.attachment = attachment; 311 this.extension = attachment.get( 'filename' ).split('.').pop(); 312 313 if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) { 314 this.unset( 'src' ); 315 } 316 317 if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) { 318 this.set( this.extension, this.attachment.get( 'url' ) ); 319 } else { 320 this.unset( this.extension ); 321 } 322 }, 323 324 changeAttachment: function( attachment ) { 325 this.setSource( attachment ); 326 327 this.unset( 'src' ); 328 _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) { 329 this.unset( ext ); 330 }, this ); 331 } 332 }); 333 334 module.exports = PostMedia; 335 336 337 /***/ }), 338 /* 2 */ 339 /***/ (function(module, exports) { 340 210 media.model.PostMedia = require( './models/post-media.js' ); 211 media.controller.AudioDetails = require( './controllers/audio-details.js' ); 212 media.controller.VideoDetails = require( './controllers/video-details.js' ); 213 media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' ); 214 media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' ); 215 media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' ); 216 media.view.MediaDetails = require( './views/media-details.js' ); 217 media.view.AudioDetails = require( './views/audio-details.js' ); 218 media.view.VideoDetails = require( './views/video-details.js' ); 219 220 },{"./controllers/audio-details.js":2,"./controllers/video-details.js":3,"./models/post-media.js":4,"./views/audio-details.js":5,"./views/frame/audio-details.js":6,"./views/frame/media-details.js":7,"./views/frame/video-details.js":8,"./views/media-details.js":9,"./views/video-details.js":10}],2:[function(require,module,exports){ 341 221 /*globals wp */ 342 222 … … 373 253 module.exports = AudioDetails; 374 254 375 376 /***/ }), 377 /* 3 */ 378 /***/ (function(module, exports) { 379 255 },{}],3:[function(require,module,exports){ 380 256 /*globals wp */ 381 257 … … 412 288 module.exports = VideoDetails; 413 289 414 415 /***/ }), 416 /* 4 */ 417 /***/ (function(module, exports) { 418 290 },{}],4:[function(require,module,exports){ 291 /*globals wp, Backbone, _ */ 292 293 /** 294 * wp.media.model.PostMedia 295 * 296 * Shared model class for audio and video. Updates the model after 297 * "Add Audio|Video Source" and "Replace Audio|Video" states return 298 * 299 * @class 300 * @augments Backbone.Model 301 */ 302 var PostMedia = Backbone.Model.extend({ 303 initialize: function() { 304 this.attachment = false; 305 }, 306 307 setSource: function( attachment ) { 308 this.attachment = attachment; 309 this.extension = attachment.get( 'filename' ).split('.').pop(); 310 311 if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) { 312 this.unset( 'src' ); 313 } 314 315 if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) { 316 this.set( this.extension, this.attachment.get( 'url' ) ); 317 } else { 318 this.unset( this.extension ); 319 } 320 }, 321 322 changeAttachment: function( attachment ) { 323 this.setSource( attachment ); 324 325 this.unset( 'src' ); 326 _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) { 327 this.unset( ext ); 328 }, this ); 329 } 330 }); 331 332 module.exports = PostMedia; 333 334 },{}],5:[function(require,module,exports){ 335 /*globals wp */ 336 337 /** 338 * wp.media.view.AudioDetails 339 * 340 * @class 341 * @augments wp.media.view.MediaDetails 342 * @augments wp.media.view.Settings.AttachmentDisplay 343 * @augments wp.media.view.Settings 344 * @augments wp.media.View 345 * @augments wp.Backbone.View 346 * @augments Backbone.View 347 */ 348 var MediaDetails = wp.media.view.MediaDetails, 349 AudioDetails; 350 351 AudioDetails = MediaDetails.extend({ 352 className: 'audio-details', 353 template: wp.template('audio-details'), 354 355 setMedia: function() { 356 var audio = this.$('.wp-audio-shortcode'); 357 358 if ( audio.find( 'source' ).length ) { 359 if ( audio.is(':hidden') ) { 360 audio.show(); 361 } 362 this.media = MediaDetails.prepareSrc( audio.get(0) ); 363 } else { 364 audio.hide(); 365 this.media = false; 366 } 367 368 return this; 369 } 370 }); 371 372 module.exports = AudioDetails; 373 374 },{}],6:[function(require,module,exports){ 375 /*globals wp */ 376 377 /** 378 * wp.media.view.MediaFrame.AudioDetails 379 * 380 * @class 381 * @augments wp.media.view.MediaFrame.MediaDetails 382 * @augments wp.media.view.MediaFrame.Select 383 * @augments wp.media.view.MediaFrame 384 * @augments wp.media.view.Frame 385 * @augments wp.media.View 386 * @augments wp.Backbone.View 387 * @augments Backbone.View 388 * @mixes wp.media.controller.StateMachine 389 */ 390 var MediaDetails = wp.media.view.MediaFrame.MediaDetails, 391 MediaLibrary = wp.media.controller.MediaLibrary, 392 393 l10n = wp.media.view.l10n, 394 AudioDetails; 395 396 AudioDetails = MediaDetails.extend({ 397 defaults: { 398 id: 'audio', 399 url: '', 400 menu: 'audio-details', 401 content: 'audio-details', 402 toolbar: 'audio-details', 403 type: 'link', 404 title: l10n.audioDetailsTitle, 405 priority: 120 406 }, 407 408 initialize: function( options ) { 409 options.DetailsView = wp.media.view.AudioDetails; 410 options.cancelText = l10n.audioDetailsCancel; 411 options.addText = l10n.audioAddSourceTitle; 412 413 MediaDetails.prototype.initialize.call( this, options ); 414 }, 415 416 bindHandlers: function() { 417 MediaDetails.prototype.bindHandlers.apply( this, arguments ); 418 419 this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this ); 420 this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this ); 421 }, 422 423 createStates: function() { 424 this.states.add([ 425 new wp.media.controller.AudioDetails( { 426 media: this.media 427 } ), 428 429 new MediaLibrary( { 430 type: 'audio', 431 id: 'replace-audio', 432 title: l10n.audioReplaceTitle, 433 toolbar: 'replace-audio', 434 media: this.media, 435 menu: 'audio-details' 436 } ), 437 438 new MediaLibrary( { 439 type: 'audio', 440 id: 'add-audio-source', 441 title: l10n.audioAddSourceTitle, 442 toolbar: 'add-audio-source', 443 media: this.media, 444 menu: false 445 } ) 446 ]); 447 } 448 }); 449 450 module.exports = AudioDetails; 451 452 },{}],7:[function(require,module,exports){ 419 453 /*globals wp */ 420 454 … … 548 582 module.exports = MediaDetails; 549 583 550 551 /***/ }), 552 /* 5 */ 553 /***/ (function(module, exports) { 554 555 /*globals wp */ 556 557 /** 558 * wp.media.view.MediaFrame.AudioDetails 559 * 560 * @class 561 * @augments wp.media.view.MediaFrame.MediaDetails 562 * @augments wp.media.view.MediaFrame.Select 563 * @augments wp.media.view.MediaFrame 564 * @augments wp.media.view.Frame 565 * @augments wp.media.View 566 * @augments wp.Backbone.View 567 * @augments Backbone.View 568 * @mixes wp.media.controller.StateMachine 569 */ 570 var MediaDetails = wp.media.view.MediaFrame.MediaDetails, 571 MediaLibrary = wp.media.controller.MediaLibrary, 572 573 l10n = wp.media.view.l10n, 574 AudioDetails; 575 576 AudioDetails = MediaDetails.extend({ 577 defaults: { 578 id: 'audio', 579 url: '', 580 menu: 'audio-details', 581 content: 'audio-details', 582 toolbar: 'audio-details', 583 type: 'link', 584 title: l10n.audioDetailsTitle, 585 priority: 120 586 }, 587 588 initialize: function( options ) { 589 options.DetailsView = wp.media.view.AudioDetails; 590 options.cancelText = l10n.audioDetailsCancel; 591 options.addText = l10n.audioAddSourceTitle; 592 593 MediaDetails.prototype.initialize.call( this, options ); 594 }, 595 596 bindHandlers: function() { 597 MediaDetails.prototype.bindHandlers.apply( this, arguments ); 598 599 this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this ); 600 this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this ); 601 }, 602 603 createStates: function() { 604 this.states.add([ 605 new wp.media.controller.AudioDetails( { 606 media: this.media 607 } ), 608 609 new MediaLibrary( { 610 type: 'audio', 611 id: 'replace-audio', 612 title: l10n.audioReplaceTitle, 613 toolbar: 'replace-audio', 614 media: this.media, 615 menu: 'audio-details' 616 } ), 617 618 new MediaLibrary( { 619 type: 'audio', 620 id: 'add-audio-source', 621 title: l10n.audioAddSourceTitle, 622 toolbar: 'add-audio-source', 623 media: this.media, 624 menu: false 625 } ) 626 ]); 627 } 628 }); 629 630 module.exports = AudioDetails; 631 632 633 /***/ }), 634 /* 6 */ 635 /***/ (function(module, exports) { 636 584 },{}],8:[function(require,module,exports){ 637 585 /*globals wp, _ */ 638 586 … … 771 719 module.exports = VideoDetails; 772 720 773 774 /***/ }), 775 /* 7 */ 776 /***/ (function(module, exports) { 777 721 },{}],9:[function(require,module,exports){ 778 722 /*global wp, jQuery, _, MediaElementPlayer */ 779 723 … … 943 887 module.exports = MediaDetails; 944 888 945 946 /***/ }), 947 /* 8 */ 948 /***/ (function(module, exports) { 949 950 /*globals wp */ 951 952 /** 953 * wp.media.view.AudioDetails 954 * 955 * @class 956 * @augments wp.media.view.MediaDetails 957 * @augments wp.media.view.Settings.AttachmentDisplay 958 * @augments wp.media.view.Settings 959 * @augments wp.media.View 960 * @augments wp.Backbone.View 961 * @augments Backbone.View 962 */ 963 var MediaDetails = wp.media.view.MediaDetails, 964 AudioDetails; 965 966 AudioDetails = MediaDetails.extend({ 967 className: 'audio-details', 968 template: wp.template('audio-details'), 969 970 setMedia: function() { 971 var audio = this.$('.wp-audio-shortcode'); 972 973 if ( audio.find( 'source' ).length ) { 974 if ( audio.is(':hidden') ) { 975 audio.show(); 976 } 977 this.media = MediaDetails.prepareSrc( audio.get(0) ); 978 } else { 979 audio.hide(); 980 this.media = false; 981 } 982 983 return this; 984 } 985 }); 986 987 module.exports = AudioDetails; 988 989 990 /***/ }), 991 /* 9 */ 992 /***/ (function(module, exports) { 993 889 },{}],10:[function(require,module,exports){ 994 890 /*globals wp */ 995 891 … … 1036 932 module.exports = VideoDetails; 1037 933 1038 1039 /***/ }) 1040 /******/ ]); 934 },{}]},{},[1]); -
branches/4.2/src/wp-includes/js/media-grid.js
r46500 r50020 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 /*globals wp */ 81 82 var media = wp.media; 83 84 media.controller.EditAttachmentMetadata = __webpack_require__( 11 ); 85 media.view.MediaFrame.Manage = __webpack_require__( 12 ); 86 media.view.Attachment.Details.TwoColumn = __webpack_require__( 13 ); 87 media.view.MediaFrame.Manage.Router = __webpack_require__( 14 ); 88 media.view.EditImage.Details = __webpack_require__( 15 ); 89 media.view.MediaFrame.EditAttachments = __webpack_require__( 16 ); 90 media.view.SelectModeToggleButton = __webpack_require__( 17 ); 91 media.view.DeleteSelectedButton = __webpack_require__( 18 ); 92 media.view.DeleteSelectedPermanentlyButton = __webpack_require__( 19 ); 93 94 95 /***/ }), 96 /* 11 */ 97 /***/ (function(module, exports) { 98 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){ 99 2 /*globals wp */ 100 3 … … 126 29 module.exports = EditAttachmentMetadata; 127 30 128 129 /***/ }), 130 /* 12 */ 131 /***/ (function(module, exports) { 132 31 },{}],2:[function(require,module,exports){ 32 /*globals wp */ 33 34 var media = wp.media; 35 36 media.controller.EditAttachmentMetadata = require( './controllers/edit-attachment-metadata.js' ); 37 media.view.MediaFrame.Manage = require( './views/frame/manage.js' ); 38 media.view.Attachment.Details.TwoColumn = require( './views/attachment/details-two-column.js' ); 39 media.view.MediaFrame.Manage.Router = require( './routers/manage.js' ); 40 media.view.EditImage.Details = require( './views/edit-image-details.js' ); 41 media.view.MediaFrame.EditAttachments = require( './views/frame/edit-attachments.js' ); 42 media.view.SelectModeToggleButton = require( './views/button/select-mode-toggle.js' ); 43 media.view.DeleteSelectedButton = require( './views/button/delete-selected.js' ); 44 media.view.DeleteSelectedPermanentlyButton = require( './views/button/delete-selected-permanently.js' ); 45 46 },{"./controllers/edit-attachment-metadata.js":1,"./routers/manage.js":3,"./views/attachment/details-two-column.js":4,"./views/button/delete-selected-permanently.js":5,"./views/button/delete-selected.js":6,"./views/button/select-mode-toggle.js":7,"./views/edit-image-details.js":8,"./views/frame/edit-attachments.js":9,"./views/frame/manage.js":10}],3:[function(require,module,exports){ 47 /*globals wp, Backbone */ 48 49 /** 50 * wp.media.view.MediaFrame.Manage.Router 51 * 52 * A router for handling the browser history and application state. 53 * 54 * @class 55 * @augments Backbone.Router 56 */ 57 var Router = Backbone.Router.extend({ 58 routes: { 59 'upload.php?item=:slug': 'showItem', 60 'upload.php?search=:query': 'search' 61 }, 62 63 // Map routes against the page URL 64 baseUrl: function( url ) { 65 return 'upload.php' + url; 66 }, 67 68 // Respond to the search route by filling the search field and trigggering the input event 69 search: function( query ) { 70 jQuery( '#media-search-input' ).val( query ).trigger( 'input' ); 71 }, 72 73 // Show the modal with a specific item 74 showItem: function( query ) { 75 var media = wp.media, 76 library = media.frame.state().get('library'), 77 item; 78 79 // Trigger the media frame to open the correct item 80 item = library.findWhere( { id: parseInt( query, 10 ) } ); 81 if ( item ) { 82 media.frame.trigger( 'edit:attachment', item ); 83 } else { 84 item = media.attachment( query ); 85 media.frame.listenTo( item, 'change', function( model ) { 86 media.frame.stopListening( item ); 87 media.frame.trigger( 'edit:attachment', model ); 88 } ); 89 item.fetch(); 90 } 91 } 92 }); 93 94 module.exports = Router; 95 96 },{}],4:[function(require,module,exports){ 97 /*globals wp */ 98 99 /** 100 * wp.media.view.Attachment.Details.TwoColumn 101 * 102 * A similar view to media.view.Attachment.Details 103 * for use in the Edit Attachment modal. 104 * 105 * @class 106 * @augments wp.media.view.Attachment.Details 107 * @augments wp.media.view.Attachment 108 * @augments wp.media.View 109 * @augments wp.Backbone.View 110 * @augments Backbone.View 111 */ 112 var Details = wp.media.view.Attachment.Details, 113 TwoColumn; 114 115 TwoColumn = Details.extend({ 116 template: wp.template( 'attachment-details-two-column' ), 117 118 editAttachment: function( event ) { 119 event.preventDefault(); 120 this.controller.content.mode( 'edit-image' ); 121 }, 122 123 /** 124 * Noop this from parent class, doesn't apply here. 125 */ 126 toggleSelectionHandler: function() {}, 127 128 render: function() { 129 Details.prototype.render.apply( this, arguments ); 130 131 wp.media.mixin.removeAllPlayers(); 132 this.$( 'audio, video' ).each( function (i, elem) { 133 var el = wp.media.view.MediaDetails.prepareSrc( elem ); 134 new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings ); 135 } ); 136 } 137 }); 138 139 module.exports = TwoColumn; 140 141 },{}],5:[function(require,module,exports){ 142 /*globals wp */ 143 144 /** 145 * wp.media.view.DeleteSelectedPermanentlyButton 146 * 147 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic 148 * 149 * @class 150 * @augments wp.media.view.DeleteSelectedButton 151 * @augments wp.media.view.Button 152 * @augments wp.media.View 153 * @augments wp.Backbone.View 154 * @augments Backbone.View 155 */ 156 var Button = wp.media.view.Button, 157 DeleteSelected = wp.media.view.DeleteSelectedButton, 158 DeleteSelectedPermanently; 159 160 DeleteSelectedPermanently = DeleteSelected.extend({ 161 initialize: function() { 162 DeleteSelected.prototype.initialize.apply( this, arguments ); 163 this.listenTo( this.controller, 'select:activate', this.selectActivate ); 164 this.listenTo( this.controller, 'select:deactivate', this.selectDeactivate ); 165 }, 166 167 filterChange: function( model ) { 168 this.canShow = ( 'trash' === model.get( 'status' ) ); 169 }, 170 171 selectActivate: function() { 172 this.toggleDisabled(); 173 this.$el.toggleClass( 'hidden', ! this.canShow ); 174 }, 175 176 selectDeactivate: function() { 177 this.toggleDisabled(); 178 this.$el.addClass( 'hidden' ); 179 }, 180 181 render: function() { 182 Button.prototype.render.apply( this, arguments ); 183 this.selectActivate(); 184 return this; 185 } 186 }); 187 188 module.exports = DeleteSelectedPermanently; 189 190 },{}],6:[function(require,module,exports){ 191 /*globals wp */ 192 193 /** 194 * wp.media.view.DeleteSelectedButton 195 * 196 * A button that handles bulk Delete/Trash logic 197 * 198 * @class 199 * @augments wp.media.view.Button 200 * @augments wp.media.View 201 * @augments wp.Backbone.View 202 * @augments Backbone.View 203 */ 204 var Button = wp.media.view.Button, 205 l10n = wp.media.view.l10n, 206 DeleteSelected; 207 208 DeleteSelected = Button.extend({ 209 initialize: function() { 210 Button.prototype.initialize.apply( this, arguments ); 211 if ( this.options.filters ) { 212 this.listenTo( this.options.filters.model, 'change', this.filterChange ); 213 } 214 this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled ); 215 }, 216 217 filterChange: function( model ) { 218 if ( 'trash' === model.get( 'status' ) ) { 219 this.model.set( 'text', l10n.untrashSelected ); 220 } else if ( wp.media.view.settings.mediaTrash ) { 221 this.model.set( 'text', l10n.trashSelected ); 222 } else { 223 this.model.set( 'text', l10n.deleteSelected ); 224 } 225 }, 226 227 toggleDisabled: function() { 228 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length ); 229 }, 230 231 render: function() { 232 Button.prototype.render.apply( this, arguments ); 233 if ( this.controller.isModeActive( 'select' ) ) { 234 this.$el.addClass( 'delete-selected-button' ); 235 } else { 236 this.$el.addClass( 'delete-selected-button hidden' ); 237 } 238 this.toggleDisabled(); 239 return this; 240 } 241 }); 242 243 module.exports = DeleteSelected; 244 245 },{}],7:[function(require,module,exports){ 246 /*globals wp */ 247 248 /** 249 * wp.media.view.SelectModeToggleButton 250 * 251 * @class 252 * @augments wp.media.view.Button 253 * @augments wp.media.View 254 * @augments wp.Backbone.View 255 * @augments Backbone.View 256 */ 257 var Button = wp.media.view.Button, 258 l10n = wp.media.view.l10n, 259 SelectModeToggle; 260 261 SelectModeToggle = Button.extend({ 262 initialize: function() { 263 Button.prototype.initialize.apply( this, arguments ); 264 this.listenTo( this.controller, 'select:activate select:deactivate', this.toggleBulkEditHandler ); 265 this.listenTo( this.controller, 'selection:action:done', this.back ); 266 }, 267 268 back: function () { 269 this.controller.deactivateMode( 'select' ).activateMode( 'edit' ); 270 }, 271 272 click: function() { 273 Button.prototype.click.apply( this, arguments ); 274 if ( this.controller.isModeActive( 'select' ) ) { 275 this.back(); 276 } else { 277 this.controller.deactivateMode( 'edit' ).activateMode( 'select' ); 278 } 279 }, 280 281 render: function() { 282 Button.prototype.render.apply( this, arguments ); 283 this.$el.addClass( 'select-mode-toggle-button' ); 284 return this; 285 }, 286 287 toggleBulkEditHandler: function() { 288 var toolbar = this.controller.content.get().toolbar, children; 289 290 children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' ); 291 292 // TODO: the Frame should be doing all of this. 293 if ( this.controller.isModeActive( 'select' ) ) { 294 this.model.set( 'text', l10n.cancelSelection ); 295 children.not( '.media-button' ).hide(); 296 this.$el.show(); 297 toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' ); 298 } else { 299 this.model.set( 'text', l10n.bulkSelect ); 300 this.controller.content.get().$el.removeClass( 'fixed' ); 301 toolbar.$el.css( 'width', '' ); 302 toolbar.$( '.delete-selected-button' ).addClass( 'hidden' ); 303 children.not( '.spinner, .media-button' ).show(); 304 this.controller.state().get( 'selection' ).reset(); 305 } 306 } 307 }); 308 309 module.exports = SelectModeToggle; 310 311 },{}],8:[function(require,module,exports){ 312 /*globals wp, _ */ 313 314 /** 315 * wp.media.view.EditImage.Details 316 * 317 * @class 318 * @augments wp.media.view.EditImage 319 * @augments wp.media.View 320 * @augments wp.Backbone.View 321 * @augments Backbone.View 322 */ 323 var View = wp.media.View, 324 EditImage = wp.media.view.EditImage, 325 Details; 326 327 Details = EditImage.extend({ 328 initialize: function( options ) { 329 this.editor = window.imageEdit; 330 this.frame = options.frame; 331 this.controller = options.controller; 332 View.prototype.initialize.apply( this, arguments ); 333 }, 334 335 back: function() { 336 this.frame.content.mode( 'edit-metadata' ); 337 }, 338 339 save: function() { 340 this.model.fetch().done( _.bind( function() { 341 this.frame.content.mode( 'edit-metadata' ); 342 }, this ) ); 343 } 344 }); 345 346 module.exports = Details; 347 348 },{}],9:[function(require,module,exports){ 349 /*globals wp, _, jQuery */ 350 351 /** 352 * wp.media.view.MediaFrame.EditAttachments 353 * 354 * A frame for editing the details of a specific media item. 355 * 356 * Opens in a modal by default. 357 * 358 * Requires an attachment model to be passed in the options hash under `model`. 359 * 360 * @class 361 * @augments wp.media.view.Frame 362 * @augments wp.media.View 363 * @augments wp.Backbone.View 364 * @augments Backbone.View 365 * @mixes wp.media.controller.StateMachine 366 */ 367 var Frame = wp.media.view.Frame, 368 MediaFrame = wp.media.view.MediaFrame, 369 370 $ = jQuery, 371 EditAttachments; 372 373 EditAttachments = MediaFrame.extend({ 374 375 className: 'edit-attachment-frame', 376 template: wp.template( 'edit-attachment-frame' ), 377 regions: [ 'title', 'content' ], 378 379 events: { 380 'click .left': 'previousMediaItem', 381 'click .right': 'nextMediaItem' 382 }, 383 384 initialize: function() { 385 Frame.prototype.initialize.apply( this, arguments ); 386 387 _.defaults( this.options, { 388 modal: true, 389 state: 'edit-attachment' 390 }); 391 392 this.controller = this.options.controller; 393 this.gridRouter = this.controller.gridRouter; 394 this.library = this.options.library; 395 396 if ( this.options.model ) { 397 this.model = this.options.model; 398 } 399 400 this.bindHandlers(); 401 this.createStates(); 402 this.createModal(); 403 404 this.title.mode( 'default' ); 405 this.toggleNav(); 406 }, 407 408 bindHandlers: function() { 409 // Bind default title creation. 410 this.on( 'title:create:default', this.createTitle, this ); 411 412 // Close the modal if the attachment is deleted. 413 this.listenTo( this.model, 'change:status destroy', this.close, this ); 414 415 this.on( 'content:create:edit-metadata', this.editMetadataMode, this ); 416 this.on( 'content:create:edit-image', this.editImageMode, this ); 417 this.on( 'content:render:edit-image', this.editImageModeRender, this ); 418 this.on( 'close', this.detach ); 419 }, 420 421 createModal: function() { 422 // Initialize modal container view. 423 if ( this.options.modal ) { 424 this.modal = new wp.media.view.Modal({ 425 controller: this, 426 title: this.options.title 427 }); 428 429 this.modal.on( 'open', _.bind( function () { 430 $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) ); 431 }, this ) ); 432 433 // Completely destroy the modal DOM element when closing it. 434 this.modal.on( 'close', _.bind( function() { 435 this.modal.remove(); 436 $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */ 437 // Restore the original focus item if possible 438 $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus(); 439 this.resetRoute(); 440 }, this ) ); 441 442 // Set this frame as the modal's content. 443 this.modal.content( this ); 444 this.modal.open(); 445 } 446 }, 447 448 /** 449 * Add the default states to the frame. 450 */ 451 createStates: function() { 452 this.states.add([ 453 new wp.media.controller.EditAttachmentMetadata( { model: this.model } ) 454 ]); 455 }, 456 457 /** 458 * Content region rendering callback for the `edit-metadata` mode. 459 * 460 * @param {Object} contentRegion Basic object with a `view` property, which 461 * should be set with the proper region view. 462 */ 463 editMetadataMode: function( contentRegion ) { 464 contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({ 465 controller: this, 466 model: this.model 467 }); 468 469 /** 470 * Attach a subview to display fields added via the 471 * `attachment_fields_to_edit` filter. 472 */ 473 contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({ 474 controller: this, 475 model: this.model 476 }) ); 477 478 // Update browser url when navigating media details 479 if ( this.model ) { 480 this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) ); 481 } 482 }, 483 484 /** 485 * Render the EditImage view into the frame's content region. 486 * 487 * @param {Object} contentRegion Basic object with a `view` property, which 488 * should be set with the proper region view. 489 */ 490 editImageMode: function( contentRegion ) { 491 var editImageController = new wp.media.controller.EditImage( { 492 model: this.model, 493 frame: this 494 } ); 495 // Noop some methods. 496 editImageController._toolbar = function() {}; 497 editImageController._router = function() {}; 498 editImageController._menu = function() {}; 499 500 contentRegion.view = new wp.media.view.EditImage.Details( { 501 model: this.model, 502 frame: this, 503 controller: editImageController 504 } ); 505 }, 506 507 editImageModeRender: function( view ) { 508 view.on( 'ready', view.loadEditor ); 509 }, 510 511 toggleNav: function() { 512 this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() ); 513 this.$('.right').toggleClass( 'disabled', ! this.hasNext() ); 514 }, 515 516 /** 517 * Rerender the view. 518 */ 519 rerender: function() { 520 // Only rerender the `content` region. 521 if ( this.content.mode() !== 'edit-metadata' ) { 522 this.content.mode( 'edit-metadata' ); 523 } else { 524 this.content.render(); 525 } 526 527 this.toggleNav(); 528 }, 529 530 /** 531 * Click handler to switch to the previous media item. 532 */ 533 previousMediaItem: function() { 534 if ( ! this.hasPrevious() ) { 535 this.$( '.left' ).blur(); 536 return; 537 } 538 this.model = this.library.at( this.getCurrentIndex() - 1 ); 539 this.rerender(); 540 this.$( '.left' ).focus(); 541 }, 542 543 /** 544 * Click handler to switch to the next media item. 545 */ 546 nextMediaItem: function() { 547 if ( ! this.hasNext() ) { 548 this.$( '.right' ).blur(); 549 return; 550 } 551 this.model = this.library.at( this.getCurrentIndex() + 1 ); 552 this.rerender(); 553 this.$( '.right' ).focus(); 554 }, 555 556 getCurrentIndex: function() { 557 return this.library.indexOf( this.model ); 558 }, 559 560 hasNext: function() { 561 return ( this.getCurrentIndex() + 1 ) < this.library.length; 562 }, 563 564 hasPrevious: function() { 565 return ( this.getCurrentIndex() - 1 ) > -1; 566 }, 567 /** 568 * Respond to the keyboard events: right arrow, left arrow, except when 569 * focus is in a textarea or input field. 570 */ 571 keyEvent: function( event ) { 572 if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) { 573 return; 574 } 575 576 // The right arrow key 577 if ( 39 === event.keyCode ) { 578 this.nextMediaItem(); 579 } 580 // The left arrow key 581 if ( 37 === event.keyCode ) { 582 this.previousMediaItem(); 583 } 584 }, 585 586 resetRoute: function() { 587 this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) ); 588 } 589 }); 590 591 module.exports = EditAttachments; 592 593 },{}],10:[function(require,module,exports){ 133 594 /*globals wp, _, Backbone */ 134 595 … … 400 861 module.exports = Manage; 401 862 402 403 /***/ }), 404 /* 13 */ 405 /***/ (function(module, exports) { 406 407 /*globals wp */ 408 409 /** 410 * wp.media.view.Attachment.Details.TwoColumn 411 * 412 * A similar view to media.view.Attachment.Details 413 * for use in the Edit Attachment modal. 414 * 415 * @class 416 * @augments wp.media.view.Attachment.Details 417 * @augments wp.media.view.Attachment 418 * @augments wp.media.View 419 * @augments wp.Backbone.View 420 * @augments Backbone.View 421 */ 422 var Details = wp.media.view.Attachment.Details, 423 TwoColumn; 424 425 TwoColumn = Details.extend({ 426 template: wp.template( 'attachment-details-two-column' ), 427 428 editAttachment: function( event ) { 429 event.preventDefault(); 430 this.controller.content.mode( 'edit-image' ); 431 }, 432 433 /** 434 * Noop this from parent class, doesn't apply here. 435 */ 436 toggleSelectionHandler: function() {}, 437 438 render: function() { 439 Details.prototype.render.apply( this, arguments ); 440 441 wp.media.mixin.removeAllPlayers(); 442 this.$( 'audio, video' ).each( function (i, elem) { 443 var el = wp.media.view.MediaDetails.prepareSrc( elem ); 444 new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings ); 445 } ); 446 } 447 }); 448 449 module.exports = TwoColumn; 450 451 452 /***/ }), 453 /* 14 */ 454 /***/ (function(module, exports) { 455 456 /*globals wp, Backbone */ 457 458 /** 459 * wp.media.view.MediaFrame.Manage.Router 460 * 461 * A router for handling the browser history and application state. 462 * 463 * @class 464 * @augments Backbone.Router 465 */ 466 var Router = Backbone.Router.extend({ 467 routes: { 468 'upload.php?item=:slug': 'showItem', 469 'upload.php?search=:query': 'search' 470 }, 471 472 // Map routes against the page URL 473 baseUrl: function( url ) { 474 return 'upload.php' + url; 475 }, 476 477 // Respond to the search route by filling the search field and trigggering the input event 478 search: function( query ) { 479 jQuery( '#media-search-input' ).val( query ).trigger( 'input' ); 480 }, 481 482 // Show the modal with a specific item 483 showItem: function( query ) { 484 var media = wp.media, 485 library = media.frame.state().get('library'), 486 item; 487 488 // Trigger the media frame to open the correct item 489 item = library.findWhere( { id: parseInt( query, 10 ) } ); 490 if ( item ) { 491 media.frame.trigger( 'edit:attachment', item ); 492 } else { 493 item = media.attachment( query ); 494 media.frame.listenTo( item, 'change', function( model ) { 495 media.frame.stopListening( item ); 496 media.frame.trigger( 'edit:attachment', model ); 497 } ); 498 item.fetch(); 499 } 500 } 501 }); 502 503 module.exports = Router; 504 505 506 /***/ }), 507 /* 15 */ 508 /***/ (function(module, exports) { 509 510 /*globals wp, _ */ 511 512 /** 513 * wp.media.view.EditImage.Details 514 * 515 * @class 516 * @augments wp.media.view.EditImage 517 * @augments wp.media.View 518 * @augments wp.Backbone.View 519 * @augments Backbone.View 520 */ 521 var View = wp.media.View, 522 EditImage = wp.media.view.EditImage, 523 Details; 524 525 Details = EditImage.extend({ 526 initialize: function( options ) { 527 this.editor = window.imageEdit; 528 this.frame = options.frame; 529 this.controller = options.controller; 530 View.prototype.initialize.apply( this, arguments ); 531 }, 532 533 back: function() { 534 this.frame.content.mode( 'edit-metadata' ); 535 }, 536 537 save: function() { 538 this.model.fetch().done( _.bind( function() { 539 this.frame.content.mode( 'edit-metadata' ); 540 }, this ) ); 541 } 542 }); 543 544 module.exports = Details; 545 546 547 /***/ }), 548 /* 16 */ 549 /***/ (function(module, exports) { 550 551 /*globals wp, _, jQuery */ 552 553 /** 554 * wp.media.view.MediaFrame.EditAttachments 555 * 556 * A frame for editing the details of a specific media item. 557 * 558 * Opens in a modal by default. 559 * 560 * Requires an attachment model to be passed in the options hash under `model`. 561 * 562 * @class 563 * @augments wp.media.view.Frame 564 * @augments wp.media.View 565 * @augments wp.Backbone.View 566 * @augments Backbone.View 567 * @mixes wp.media.controller.StateMachine 568 */ 569 var Frame = wp.media.view.Frame, 570 MediaFrame = wp.media.view.MediaFrame, 571 572 $ = jQuery, 573 EditAttachments; 574 575 EditAttachments = MediaFrame.extend({ 576 577 className: 'edit-attachment-frame', 578 template: wp.template( 'edit-attachment-frame' ), 579 regions: [ 'title', 'content' ], 580 581 events: { 582 'click .left': 'previousMediaItem', 583 'click .right': 'nextMediaItem' 584 }, 585 586 initialize: function() { 587 Frame.prototype.initialize.apply( this, arguments ); 588 589 _.defaults( this.options, { 590 modal: true, 591 state: 'edit-attachment' 592 }); 593 594 this.controller = this.options.controller; 595 this.gridRouter = this.controller.gridRouter; 596 this.library = this.options.library; 597 598 if ( this.options.model ) { 599 this.model = this.options.model; 600 } 601 602 this.bindHandlers(); 603 this.createStates(); 604 this.createModal(); 605 606 this.title.mode( 'default' ); 607 this.toggleNav(); 608 }, 609 610 bindHandlers: function() { 611 // Bind default title creation. 612 this.on( 'title:create:default', this.createTitle, this ); 613 614 // Close the modal if the attachment is deleted. 615 this.listenTo( this.model, 'change:status destroy', this.close, this ); 616 617 this.on( 'content:create:edit-metadata', this.editMetadataMode, this ); 618 this.on( 'content:create:edit-image', this.editImageMode, this ); 619 this.on( 'content:render:edit-image', this.editImageModeRender, this ); 620 this.on( 'close', this.detach ); 621 }, 622 623 createModal: function() { 624 // Initialize modal container view. 625 if ( this.options.modal ) { 626 this.modal = new wp.media.view.Modal({ 627 controller: this, 628 title: this.options.title 629 }); 630 631 this.modal.on( 'open', _.bind( function () { 632 $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) ); 633 }, this ) ); 634 635 // Completely destroy the modal DOM element when closing it. 636 this.modal.on( 'close', _.bind( function() { 637 this.modal.remove(); 638 $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */ 639 // Restore the original focus item if possible 640 $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus(); 641 this.resetRoute(); 642 }, this ) ); 643 644 // Set this frame as the modal's content. 645 this.modal.content( this ); 646 this.modal.open(); 647 } 648 }, 649 650 /** 651 * Add the default states to the frame. 652 */ 653 createStates: function() { 654 this.states.add([ 655 new wp.media.controller.EditAttachmentMetadata( { model: this.model } ) 656 ]); 657 }, 658 659 /** 660 * Content region rendering callback for the `edit-metadata` mode. 661 * 662 * @param {Object} contentRegion Basic object with a `view` property, which 663 * should be set with the proper region view. 664 */ 665 editMetadataMode: function( contentRegion ) { 666 contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({ 667 controller: this, 668 model: this.model 669 }); 670 671 /** 672 * Attach a subview to display fields added via the 673 * `attachment_fields_to_edit` filter. 674 */ 675 contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({ 676 controller: this, 677 model: this.model 678 }) ); 679 680 // Update browser url when navigating media details 681 if ( this.model ) { 682 this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) ); 683 } 684 }, 685 686 /** 687 * Render the EditImage view into the frame's content region. 688 * 689 * @param {Object} contentRegion Basic object with a `view` property, which 690 * should be set with the proper region view. 691 */ 692 editImageMode: function( contentRegion ) { 693 var editImageController = new wp.media.controller.EditImage( { 694 model: this.model, 695 frame: this 696 } ); 697 // Noop some methods. 698 editImageController._toolbar = function() {}; 699 editImageController._router = function() {}; 700 editImageController._menu = function() {}; 701 702 contentRegion.view = new wp.media.view.EditImage.Details( { 703 model: this.model, 704 frame: this, 705 controller: editImageController 706 } ); 707 }, 708 709 editImageModeRender: function( view ) { 710 view.on( 'ready', view.loadEditor ); 711 }, 712 713 toggleNav: function() { 714 this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() ); 715 this.$('.right').toggleClass( 'disabled', ! this.hasNext() ); 716 }, 717 718 /** 719 * Rerender the view. 720 */ 721 rerender: function() { 722 // Only rerender the `content` region. 723 if ( this.content.mode() !== 'edit-metadata' ) { 724 this.content.mode( 'edit-metadata' ); 725 } else { 726 this.content.render(); 727 } 728 729 this.toggleNav(); 730 }, 731 732 /** 733 * Click handler to switch to the previous media item. 734 */ 735 previousMediaItem: function() { 736 if ( ! this.hasPrevious() ) { 737 this.$( '.left' ).blur(); 738 return; 739 } 740 this.model = this.library.at( this.getCurrentIndex() - 1 ); 741 this.rerender(); 742 this.$( '.left' ).focus(); 743 }, 744 745 /** 746 * Click handler to switch to the next media item. 747 */ 748 nextMediaItem: function() { 749 if ( ! this.hasNext() ) { 750 this.$( '.right' ).blur(); 751 return; 752 } 753 this.model = this.library.at( this.getCurrentIndex() + 1 ); 754 this.rerender(); 755 this.$( '.right' ).focus(); 756 }, 757 758 getCurrentIndex: function() { 759 return this.library.indexOf( this.model ); 760 }, 761 762 hasNext: function() { 763 return ( this.getCurrentIndex() + 1 ) < this.library.length; 764 }, 765 766 hasPrevious: function() { 767 return ( this.getCurrentIndex() - 1 ) > -1; 768 }, 769 /** 770 * Respond to the keyboard events: right arrow, left arrow, except when 771 * focus is in a textarea or input field. 772 */ 773 keyEvent: function( event ) { 774 if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) { 775 return; 776 } 777 778 // The right arrow key 779 if ( 39 === event.keyCode ) { 780 this.nextMediaItem(); 781 } 782 // The left arrow key 783 if ( 37 === event.keyCode ) { 784 this.previousMediaItem(); 785 } 786 }, 787 788 resetRoute: function() { 789 this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) ); 790 } 791 }); 792 793 module.exports = EditAttachments; 794 795 796 /***/ }), 797 /* 17 */ 798 /***/ (function(module, exports) { 799 800 /*globals wp */ 801 802 /** 803 * wp.media.view.SelectModeToggleButton 804 * 805 * @class 806 * @augments wp.media.view.Button 807 * @augments wp.media.View 808 * @augments wp.Backbone.View 809 * @augments Backbone.View 810 */ 811 var Button = wp.media.view.Button, 812 l10n = wp.media.view.l10n, 813 SelectModeToggle; 814 815 SelectModeToggle = Button.extend({ 816 initialize: function() { 817 Button.prototype.initialize.apply( this, arguments ); 818 this.listenTo( this.controller, 'select:activate select:deactivate', this.toggleBulkEditHandler ); 819 this.listenTo( this.controller, 'selection:action:done', this.back ); 820 }, 821 822 back: function () { 823 this.controller.deactivateMode( 'select' ).activateMode( 'edit' ); 824 }, 825 826 click: function() { 827 Button.prototype.click.apply( this, arguments ); 828 if ( this.controller.isModeActive( 'select' ) ) { 829 this.back(); 830 } else { 831 this.controller.deactivateMode( 'edit' ).activateMode( 'select' ); 832 } 833 }, 834 835 render: function() { 836 Button.prototype.render.apply( this, arguments ); 837 this.$el.addClass( 'select-mode-toggle-button' ); 838 return this; 839 }, 840 841 toggleBulkEditHandler: function() { 842 var toolbar = this.controller.content.get().toolbar, children; 843 844 children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' ); 845 846 // TODO: the Frame should be doing all of this. 847 if ( this.controller.isModeActive( 'select' ) ) { 848 this.model.set( 'text', l10n.cancelSelection ); 849 children.not( '.media-button' ).hide(); 850 this.$el.show(); 851 toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' ); 852 } else { 853 this.model.set( 'text', l10n.bulkSelect ); 854 this.controller.content.get().$el.removeClass( 'fixed' ); 855 toolbar.$el.css( 'width', '' ); 856 toolbar.$( '.delete-selected-button' ).addClass( 'hidden' ); 857 children.not( '.spinner, .media-button' ).show(); 858 this.controller.state().get( 'selection' ).reset(); 859 } 860 } 861 }); 862 863 module.exports = SelectModeToggle; 864 865 866 /***/ }), 867 /* 18 */ 868 /***/ (function(module, exports) { 869 870 /*globals wp */ 871 872 /** 873 * wp.media.view.DeleteSelectedButton 874 * 875 * A button that handles bulk Delete/Trash logic 876 * 877 * @class 878 * @augments wp.media.view.Button 879 * @augments wp.media.View 880 * @augments wp.Backbone.View 881 * @augments Backbone.View 882 */ 883 var Button = wp.media.view.Button, 884 l10n = wp.media.view.l10n, 885 DeleteSelected; 886 887 DeleteSelected = Button.extend({ 888 initialize: function() { 889 Button.prototype.initialize.apply( this, arguments ); 890 if ( this.options.filters ) { 891 this.listenTo( this.options.filters.model, 'change', this.filterChange ); 892 } 893 this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled ); 894 }, 895 896 filterChange: function( model ) { 897 if ( 'trash' === model.get( 'status' ) ) { 898 this.model.set( 'text', l10n.untrashSelected ); 899 } else if ( wp.media.view.settings.mediaTrash ) { 900 this.model.set( 'text', l10n.trashSelected ); 901 } else { 902 this.model.set( 'text', l10n.deleteSelected ); 903 } 904 }, 905 906 toggleDisabled: function() { 907 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length ); 908 }, 909 910 render: function() { 911 Button.prototype.render.apply( this, arguments ); 912 if ( this.controller.isModeActive( 'select' ) ) { 913 this.$el.addClass( 'delete-selected-button' ); 914 } else { 915 this.$el.addClass( 'delete-selected-button hidden' ); 916 } 917 this.toggleDisabled(); 918 return this; 919 } 920 }); 921 922 module.exports = DeleteSelected; 923 924 925 /***/ }), 926 /* 19 */ 927 /***/ (function(module, exports) { 928 929 /*globals wp */ 930 931 /** 932 * wp.media.view.DeleteSelectedPermanentlyButton 933 * 934 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic 935 * 936 * @class 937 * @augments wp.media.view.DeleteSelectedButton 938 * @augments wp.media.view.Button 939 * @augments wp.media.View 940 * @augments wp.Backbone.View 941 * @augments Backbone.View 942 */ 943 var Button = wp.media.view.Button, 944 DeleteSelected = wp.media.view.DeleteSelectedButton, 945 DeleteSelectedPermanently; 946 947 DeleteSelectedPermanently = DeleteSelected.extend({ 948 initialize: function() { 949 DeleteSelected.prototype.initialize.apply( this, arguments ); 950 this.listenTo( this.controller, 'select:activate', this.selectActivate ); 951 this.listenTo( this.controller, 'select:deactivate', this.selectDeactivate ); 952 }, 953 954 filterChange: function( model ) { 955 this.canShow = ( 'trash' === model.get( 'status' ) ); 956 }, 957 958 selectActivate: function() { 959 this.toggleDisabled(); 960 this.$el.toggleClass( 'hidden', ! this.canShow ); 961 }, 962 963 selectDeactivate: function() { 964 this.toggleDisabled(); 965 this.$el.addClass( 'hidden' ); 966 }, 967 968 render: function() { 969 Button.prototype.render.apply( this, arguments ); 970 this.selectActivate(); 971 return this; 972 } 973 }); 974 975 module.exports = DeleteSelectedPermanently; 976 977 978 /***/ }) 979 /******/ ]); 863 },{}]},{},[2]); -
branches/4.2/src/wp-includes/js/media-models.js
r46500 r50020 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 = 20); 64 /******/ }) 65 /************************************************************************/ 66 /******/ ({ 67 68 /***/ 20: 69 /***/ (function(module, exports, __webpack_require__) { 70 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){ 71 2 /*globals wp, _, jQuery */ 72 3 … … 128 59 delete l10n.settings; 129 60 130 Attachment = media.model.Attachment = __webpack_require__( 21);131 Attachments = media.model.Attachments = __webpack_require__( 22);132 133 media.model.Query = __webpack_require__( 23);134 media.model.PostImage = __webpack_require__( 24);135 media.model.Selection = __webpack_require__( 25);61 Attachment = media.model.Attachment = require( './models/attachment.js' ); 62 Attachments = media.model.Attachments = require( './models/attachments.js' ); 63 64 media.model.Query = require( './models/query.js' ); 65 media.model.PostImage = require( './models/post-image.js' ); 66 media.model.Selection = require( './models/selection.js' ); 136 67 137 68 /** … … 301 232 }); 302 233 303 304 /***/ }), 305 306 /***/ 21: 307 /***/ (function(module, exports) { 308 234 },{"./models/attachment.js":2,"./models/attachments.js":3,"./models/post-image.js":4,"./models/query.js":5,"./models/selection.js":6}],2:[function(require,module,exports){ 309 235 /*globals wp, _, Backbone */ 310 236 … … 476 402 module.exports = Attachment; 477 403 478 479 /***/ }), 480 481 /***/ 22: 482 /***/ (function(module, exports) { 483 404 },{}],3:[function(require,module,exports){ 484 405 /*globals wp, _, Backbone */ 485 406 … … 1015 936 module.exports = Attachments; 1016 937 1017 1018 /***/ }), 1019 1020 /***/ 23: 1021 /***/ (function(module, exports) { 1022 1023 /*globals wp, _ */ 1024 1025 /** 1026 * wp.media.model.Query 1027 * 1028 * A collection of attachments that match the supplied query arguments. 1029 * 1030 * Note: Do NOT change this.args after the query has been initialized. 1031 * Things will break. 1032 * 1033 * @class 1034 * @augments wp.media.model.Attachments 1035 * @augments Backbone.Collection 1036 * 1037 * @param {array} [models] Models to initialize with the collection. 1038 * @param {object} [options] Options hash. 1039 * @param {object} [options.args] Attachments query arguments. 1040 * @param {object} [options.args.posts_per_page] 1041 */ 1042 var Attachments = wp.media.model.Attachments, 1043 Query; 1044 1045 Query = Attachments.extend({ 1046 /** 1047 * @global wp.Uploader 1048 * 1049 * @param {array} [models=[]] Array of initial models to populate the collection. 1050 * @param {object} [options={}] 1051 */ 1052 initialize: function( models, options ) { 1053 var allowed; 1054 1055 options = options || {}; 1056 Attachments.prototype.initialize.apply( this, arguments ); 1057 1058 this.args = options.args; 1059 this._hasMore = true; 1060 this.created = new Date(); 1061 1062 this.filters.order = function( attachment ) { 1063 var orderby = this.props.get('orderby'), 1064 order = this.props.get('order'); 1065 1066 if ( ! this.comparator ) { 1067 return true; 1068 } 1069 1070 // We want any items that can be placed before the last 1071 // item in the set. If we add any items after the last 1072 // item, then we can't guarantee the set is complete. 1073 if ( this.length ) { 1074 return 1 !== this.comparator( attachment, this.last(), { ties: true }); 1075 1076 // Handle the case where there are no items yet and 1077 // we're sorting for recent items. In that case, we want 1078 // changes that occurred after we created the query. 1079 } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) { 1080 return attachment.get( orderby ) >= this.created; 1081 1082 // If we're sorting by menu order and we have no items, 1083 // accept any items that have the default menu order (0). 1084 } else if ( 'ASC' === order && 'menuOrder' === orderby ) { 1085 return attachment.get( orderby ) === 0; 1086 } 1087 1088 // Otherwise, we don't want any items yet. 1089 return false; 1090 }; 1091 1092 // Observe the central `wp.Uploader.queue` collection to watch for 1093 // new matches for the query. 1094 // 1095 // Only observe when a limited number of query args are set. There 1096 // are no filters for other properties, so observing will result in 1097 // false positives in those queries. 1098 allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ]; 1099 if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) { 1100 this.observe( wp.Uploader.queue ); 1101 } 1102 }, 1103 /** 1104 * Whether there are more attachments that haven't been sync'd from the server 1105 * that match the collection's query. 1106 * 1107 * @returns {boolean} 1108 */ 1109 hasMore: function() { 1110 return this._hasMore; 1111 }, 1112 /** 1113 * Fetch more attachments from the server for the collection. 1114 * 1115 * @param {object} [options={}] 1116 * @returns {Promise} 1117 */ 1118 more: function( options ) { 1119 var query = this; 1120 1121 // If there is already a request pending, return early with the Deferred object. 1122 if ( this._more && 'pending' === this._more.state() ) { 1123 return this._more; 1124 } 1125 1126 if ( ! this.hasMore() ) { 1127 return jQuery.Deferred().resolveWith( this ).promise(); 1128 } 1129 1130 options = options || {}; 1131 options.remove = false; 1132 1133 return this._more = this.fetch( options ).done( function( resp ) { 1134 if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) { 1135 query._hasMore = false; 1136 } 1137 }); 1138 }, 1139 /** 1140 * Overrides Backbone.Collection.sync 1141 * Overrides wp.media.model.Attachments.sync 1142 * 1143 * @param {String} method 1144 * @param {Backbone.Model} model 1145 * @param {Object} [options={}] 1146 * @returns {Promise} 1147 */ 1148 sync: function( method, model, options ) { 1149 var args, fallback; 1150 1151 // Overload the read method so Attachment.fetch() functions correctly. 1152 if ( 'read' === method ) { 1153 options = options || {}; 1154 options.context = this; 1155 options.data = _.extend( options.data || {}, { 1156 action: 'query-attachments', 1157 post_id: wp.media.model.settings.post.id 1158 }); 1159 1160 // Clone the args so manipulation is non-destructive. 1161 args = _.clone( this.args ); 1162 1163 // Determine which page to query. 1164 if ( -1 !== args.posts_per_page ) { 1165 args.paged = Math.round( this.length / args.posts_per_page ) + 1; 1166 } 1167 1168 options.data.query = args; 1169 return wp.media.ajax( options ); 1170 1171 // Otherwise, fall back to Backbone.sync() 1172 } else { 1173 /** 1174 * Call wp.media.model.Attachments.sync or Backbone.sync 1175 */ 1176 fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone; 1177 return fallback.sync.apply( this, arguments ); 1178 } 1179 } 1180 }, { 1181 /** 1182 * @readonly 1183 */ 1184 defaultProps: { 1185 orderby: 'date', 1186 order: 'DESC' 1187 }, 1188 /** 1189 * @readonly 1190 */ 1191 defaultArgs: { 1192 posts_per_page: 40 1193 }, 1194 /** 1195 * @readonly 1196 */ 1197 orderby: { 1198 allowed: [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ], 1199 /** 1200 * A map of JavaScript orderby values to their WP_Query equivalents. 1201 * @type {Object} 1202 */ 1203 valuemap: { 1204 'id': 'ID', 1205 'uploadedTo': 'parent', 1206 'menuOrder': 'menu_order ID' 1207 } 1208 }, 1209 /** 1210 * A map of JavaScript query properties to their WP_Query equivalents. 1211 * 1212 * @readonly 1213 */ 1214 propmap: { 1215 'search': 's', 1216 'type': 'post_mime_type', 1217 'perPage': 'posts_per_page', 1218 'menuOrder': 'menu_order', 1219 'uploadedTo': 'post_parent', 1220 'status': 'post_status', 1221 'include': 'post__in', 1222 'exclude': 'post__not_in' 1223 }, 1224 /** 1225 * Creates and returns an Attachments Query collection given the properties. 1226 * 1227 * Caches query objects and reuses where possible. 1228 * 1229 * @static 1230 * @method 1231 * 1232 * @param {object} [props] 1233 * @param {Object} [props.cache=true] Whether to use the query cache or not. 1234 * @param {Object} [props.order] 1235 * @param {Object} [props.orderby] 1236 * @param {Object} [props.include] 1237 * @param {Object} [props.exclude] 1238 * @param {Object} [props.s] 1239 * @param {Object} [props.post_mime_type] 1240 * @param {Object} [props.posts_per_page] 1241 * @param {Object} [props.menu_order] 1242 * @param {Object} [props.post_parent] 1243 * @param {Object} [props.post_status] 1244 * @param {Object} [options] 1245 * 1246 * @returns {wp.media.model.Query} A new Attachments Query collection. 1247 */ 1248 get: (function(){ 1249 /** 1250 * @static 1251 * @type Array 1252 */ 1253 var queries = []; 1254 1255 /** 1256 * @returns {Query} 1257 */ 1258 return function( props, options ) { 1259 var args = {}, 1260 orderby = Query.orderby, 1261 defaults = Query.defaultProps, 1262 query, 1263 cache = !! props.cache || _.isUndefined( props.cache ); 1264 1265 // Remove the `query` property. This isn't linked to a query, 1266 // this *is* the query. 1267 delete props.query; 1268 delete props.cache; 1269 1270 // Fill default args. 1271 _.defaults( props, defaults ); 1272 1273 // Normalize the order. 1274 props.order = props.order.toUpperCase(); 1275 if ( 'DESC' !== props.order && 'ASC' !== props.order ) { 1276 props.order = defaults.order.toUpperCase(); 1277 } 1278 1279 // Ensure we have a valid orderby value. 1280 if ( ! _.contains( orderby.allowed, props.orderby ) ) { 1281 props.orderby = defaults.orderby; 1282 } 1283 1284 _.each( [ 'include', 'exclude' ], function( prop ) { 1285 if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) { 1286 props[ prop ] = [ props[ prop ] ]; 1287 } 1288 } ); 1289 1290 // Generate the query `args` object. 1291 // Correct any differing property names. 1292 _.each( props, function( value, prop ) { 1293 if ( _.isNull( value ) ) { 1294 return; 1295 } 1296 1297 args[ Query.propmap[ prop ] || prop ] = value; 1298 }); 1299 1300 // Fill any other default query args. 1301 _.defaults( args, Query.defaultArgs ); 1302 1303 // `props.orderby` does not always map directly to `args.orderby`. 1304 // Substitute exceptions specified in orderby.keymap. 1305 args.orderby = orderby.valuemap[ props.orderby ] || props.orderby; 1306 1307 // Search the query cache for a matching query. 1308 if ( cache ) { 1309 query = _.find( queries, function( query ) { 1310 return _.isEqual( query.args, args ); 1311 }); 1312 } else { 1313 queries = []; 1314 } 1315 1316 // Otherwise, create a new query and add it to the cache. 1317 if ( ! query ) { 1318 query = new Query( [], _.extend( options || {}, { 1319 props: props, 1320 args: args 1321 } ) ); 1322 queries.push( query ); 1323 } 1324 1325 return query; 1326 }; 1327 }()) 1328 }); 1329 1330 module.exports = Query; 1331 1332 1333 /***/ }), 1334 1335 /***/ 24: 1336 /***/ (function(module, exports) { 1337 938 },{}],4:[function(require,module,exports){ 1338 939 /*globals Backbone */ 1339 940 … … 1491 1092 module.exports = PostImage; 1492 1093 1493 1494 /***/ }), 1495 1496 /***/ 25: 1497 /***/ (function(module, exports) { 1498 1094 },{}],5:[function(require,module,exports){ 1095 /*globals wp, _ */ 1096 1097 /** 1098 * wp.media.model.Query 1099 * 1100 * A collection of attachments that match the supplied query arguments. 1101 * 1102 * Note: Do NOT change this.args after the query has been initialized. 1103 * Things will break. 1104 * 1105 * @class 1106 * @augments wp.media.model.Attachments 1107 * @augments Backbone.Collection 1108 * 1109 * @param {array} [models] Models to initialize with the collection. 1110 * @param {object} [options] Options hash. 1111 * @param {object} [options.args] Attachments query arguments. 1112 * @param {object} [options.args.posts_per_page] 1113 */ 1114 var Attachments = wp.media.model.Attachments, 1115 Query; 1116 1117 Query = Attachments.extend({ 1118 /** 1119 * @global wp.Uploader 1120 * 1121 * @param {array} [models=[]] Array of initial models to populate the collection. 1122 * @param {object} [options={}] 1123 */ 1124 initialize: function( models, options ) { 1125 var allowed; 1126 1127 options = options || {}; 1128 Attachments.prototype.initialize.apply( this, arguments ); 1129 1130 this.args = options.args; 1131 this._hasMore = true; 1132 this.created = new Date(); 1133 1134 this.filters.order = function( attachment ) { 1135 var orderby = this.props.get('orderby'), 1136 order = this.props.get('order'); 1137 1138 if ( ! this.comparator ) { 1139 return true; 1140 } 1141 1142 // We want any items that can be placed before the last 1143 // item in the set. If we add any items after the last 1144 // item, then we can't guarantee the set is complete. 1145 if ( this.length ) { 1146 return 1 !== this.comparator( attachment, this.last(), { ties: true }); 1147 1148 // Handle the case where there are no items yet and 1149 // we're sorting for recent items. In that case, we want 1150 // changes that occurred after we created the query. 1151 } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) { 1152 return attachment.get( orderby ) >= this.created; 1153 1154 // If we're sorting by menu order and we have no items, 1155 // accept any items that have the default menu order (0). 1156 } else if ( 'ASC' === order && 'menuOrder' === orderby ) { 1157 return attachment.get( orderby ) === 0; 1158 } 1159 1160 // Otherwise, we don't want any items yet. 1161 return false; 1162 }; 1163 1164 // Observe the central `wp.Uploader.queue` collection to watch for 1165 // new matches for the query. 1166 // 1167 // Only observe when a limited number of query args are set. There 1168 // are no filters for other properties, so observing will result in 1169 // false positives in those queries. 1170 allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ]; 1171 if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) { 1172 this.observe( wp.Uploader.queue ); 1173 } 1174 }, 1175 /** 1176 * Whether there are more attachments that haven't been sync'd from the server 1177 * that match the collection's query. 1178 * 1179 * @returns {boolean} 1180 */ 1181 hasMore: function() { 1182 return this._hasMore; 1183 }, 1184 /** 1185 * Fetch more attachments from the server for the collection. 1186 * 1187 * @param {object} [options={}] 1188 * @returns {Promise} 1189 */ 1190 more: function( options ) { 1191 var query = this; 1192 1193 // If there is already a request pending, return early with the Deferred object. 1194 if ( this._more && 'pending' === this._more.state() ) { 1195 return this._more; 1196 } 1197 1198 if ( ! this.hasMore() ) { 1199 return jQuery.Deferred().resolveWith( this ).promise(); 1200 } 1201 1202 options = options || {}; 1203 options.remove = false; 1204 1205 return this._more = this.fetch( options ).done( function( resp ) { 1206 if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) { 1207 query._hasMore = false; 1208 } 1209 }); 1210 }, 1211 /** 1212 * Overrides Backbone.Collection.sync 1213 * Overrides wp.media.model.Attachments.sync 1214 * 1215 * @param {String} method 1216 * @param {Backbone.Model} model 1217 * @param {Object} [options={}] 1218 * @returns {Promise} 1219 */ 1220 sync: function( method, model, options ) { 1221 var args, fallback; 1222 1223 // Overload the read method so Attachment.fetch() functions correctly. 1224 if ( 'read' === method ) { 1225 options = options || {}; 1226 options.context = this; 1227 options.data = _.extend( options.data || {}, { 1228 action: 'query-attachments', 1229 post_id: wp.media.model.settings.post.id 1230 }); 1231 1232 // Clone the args so manipulation is non-destructive. 1233 args = _.clone( this.args ); 1234 1235 // Determine which page to query. 1236 if ( -1 !== args.posts_per_page ) { 1237 args.paged = Math.round( this.length / args.posts_per_page ) + 1; 1238 } 1239 1240 options.data.query = args; 1241 return wp.media.ajax( options ); 1242 1243 // Otherwise, fall back to Backbone.sync() 1244 } else { 1245 /** 1246 * Call wp.media.model.Attachments.sync or Backbone.sync 1247 */ 1248 fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone; 1249 return fallback.sync.apply( this, arguments ); 1250 } 1251 } 1252 }, { 1253 /** 1254 * @readonly 1255 */ 1256 defaultProps: { 1257 orderby: 'date', 1258 order: 'DESC' 1259 }, 1260 /** 1261 * @readonly 1262 */ 1263 defaultArgs: { 1264 posts_per_page: 40 1265 }, 1266 /** 1267 * @readonly 1268 */ 1269 orderby: { 1270 allowed: [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ], 1271 /** 1272 * A map of JavaScript orderby values to their WP_Query equivalents. 1273 * @type {Object} 1274 */ 1275 valuemap: { 1276 'id': 'ID', 1277 'uploadedTo': 'parent', 1278 'menuOrder': 'menu_order ID' 1279 } 1280 }, 1281 /** 1282 * A map of JavaScript query properties to their WP_Query equivalents. 1283 * 1284 * @readonly 1285 */ 1286 propmap: { 1287 'search': 's', 1288 'type': 'post_mime_type', 1289 'perPage': 'posts_per_page', 1290 'menuOrder': 'menu_order', 1291 'uploadedTo': 'post_parent', 1292 'status': 'post_status', 1293 'include': 'post__in', 1294 'exclude': 'post__not_in' 1295 }, 1296 /** 1297 * Creates and returns an Attachments Query collection given the properties. 1298 * 1299 * Caches query objects and reuses where possible. 1300 * 1301 * @static 1302 * @method 1303 * 1304 * @param {object} [props] 1305 * @param {Object} [props.cache=true] Whether to use the query cache or not. 1306 * @param {Object} [props.order] 1307 * @param {Object} [props.orderby] 1308 * @param {Object} [props.include] 1309 * @param {Object} [props.exclude] 1310 * @param {Object} [props.s] 1311 * @param {Object} [props.post_mime_type] 1312 * @param {Object} [props.posts_per_page] 1313 * @param {Object} [props.menu_order] 1314 * @param {Object} [props.post_parent] 1315 * @param {Object} [props.post_status] 1316 * @param {Object} [options] 1317 * 1318 * @returns {wp.media.model.Query} A new Attachments Query collection. 1319 */ 1320 get: (function(){ 1321 /** 1322 * @static 1323 * @type Array 1324 */ 1325 var queries = []; 1326 1327 /** 1328 * @returns {Query} 1329 */ 1330 return function( props, options ) { 1331 var args = {}, 1332 orderby = Query.orderby, 1333 defaults = Query.defaultProps, 1334 query, 1335 cache = !! props.cache || _.isUndefined( props.cache ); 1336 1337 // Remove the `query` property. This isn't linked to a query, 1338 // this *is* the query. 1339 delete props.query; 1340 delete props.cache; 1341 1342 // Fill default args. 1343 _.defaults( props, defaults ); 1344 1345 // Normalize the order. 1346 props.order = props.order.toUpperCase(); 1347 if ( 'DESC' !== props.order && 'ASC' !== props.order ) { 1348 props.order = defaults.order.toUpperCase(); 1349 } 1350 1351 // Ensure we have a valid orderby value. 1352 if ( ! _.contains( orderby.allowed, props.orderby ) ) { 1353 props.orderby = defaults.orderby; 1354 } 1355 1356 _.each( [ 'include', 'exclude' ], function( prop ) { 1357 if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) { 1358 props[ prop ] = [ props[ prop ] ]; 1359 } 1360 } ); 1361 1362 // Generate the query `args` object. 1363 // Correct any differing property names. 1364 _.each( props, function( value, prop ) { 1365 if ( _.isNull( value ) ) { 1366 return; 1367 } 1368 1369 args[ Query.propmap[ prop ] || prop ] = value; 1370 }); 1371 1372 // Fill any other default query args. 1373 _.defaults( args, Query.defaultArgs ); 1374 1375 // `props.orderby` does not always map directly to `args.orderby`. 1376 // Substitute exceptions specified in orderby.keymap. 1377 args.orderby = orderby.valuemap[ props.orderby ] || props.orderby; 1378 1379 // Search the query cache for a matching query. 1380 if ( cache ) { 1381 query = _.find( queries, function( query ) { 1382 return _.isEqual( query.args, args ); 1383 }); 1384 } else { 1385 queries = []; 1386 } 1387 1388 // Otherwise, create a new query and add it to the cache. 1389 if ( ! query ) { 1390 query = new Query( [], _.extend( options || {}, { 1391 props: props, 1392 args: args 1393 } ) ); 1394 queries.push( query ); 1395 } 1396 1397 return query; 1398 }; 1399 }()) 1400 }); 1401 1402 module.exports = Query; 1403 1404 },{}],6:[function(require,module,exports){ 1499 1405 /*globals wp, _ */ 1500 1406 … … 1595 1501 module.exports = Selection; 1596 1502 1597 1598 /***/ }) 1599 1600 /******/ }); 1503 },{}]},{},[1]); -
branches/4.2/src/wp-includes/js/media-views.js
r46500 r50020 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 /*globals wp, jQuery, _, Backbone */ 71 72 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 /*globals wp, _ */ 3 4 /** 5 * wp.media.controller.CollectionAdd 6 * 7 * A state for adding attachments to a collection (e.g. video playlist). 8 * 9 * @class 10 * @augments wp.media.controller.Library 11 * @augments wp.media.controller.State 12 * @augments Backbone.Model 13 * 14 * @param {object} [attributes] The attributes hash passed to the state. 15 * @param {string} [attributes.id=library] Unique identifier. 16 * @param {string} attributes.title Title for the state. Displays in the frame's title region. 17 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 18 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 19 * If one is not supplied, a collection of attachments of the specified type will be created. 20 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 21 * Accepts 'all', 'uploaded', or 'unattached'. 22 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 23 * @param {string} [attributes.content=upload] Initial mode for the content region. 24 * Overridden by persistent user setting if 'contentUserSetting' is true. 25 * @param {string} [attributes.router=browse] Initial mode for the router region. 26 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 27 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 28 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 29 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 30 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 31 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 32 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 33 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 34 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 35 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 36 */ 37 var Selection = wp.media.model.Selection, 38 Library = wp.media.controller.Library, 39 CollectionAdd; 40 41 CollectionAdd = Library.extend({ 42 defaults: _.defaults( { 43 // Selection defaults. @see media.model.Selection 44 multiple: 'add', 45 // Attachments browser defaults. @see media.view.AttachmentsBrowser 46 filterable: 'uploaded', 47 48 priority: 100, 49 syncSelection: false 50 }, Library.prototype.defaults ), 51 52 /** 53 * @since 3.9.0 54 */ 55 initialize: function() { 56 var collectionType = this.get('collectionType'); 57 58 if ( 'video' === this.get( 'type' ) ) { 59 collectionType = 'video-' + collectionType; 60 } 61 62 this.set( 'id', collectionType + '-library' ); 63 this.set( 'toolbar', collectionType + '-add' ); 64 this.set( 'menu', collectionType ); 65 66 // If we haven't been provided a `library`, create a `Selection`. 67 if ( ! this.get('library') ) { 68 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 69 } 70 Library.prototype.initialize.apply( this, arguments ); 71 }, 72 73 /** 74 * @since 3.9.0 75 */ 76 activate: function() { 77 var library = this.get('library'), 78 editLibrary = this.get('editLibrary'), 79 edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library'); 80 81 if ( editLibrary && editLibrary !== edit ) { 82 library.unobserve( editLibrary ); 83 } 84 85 // Accepts attachments that exist in the original library and 86 // that do not exist in gallery's library. 87 library.validator = function( attachment ) { 88 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 89 }; 90 91 // Reset the library to ensure that all attachments are re-added 92 // to the collection. Do so silently, as calling `observe` will 93 // trigger the `reset` event. 94 library.reset( library.mirroring.models, { silent: true }); 95 library.observe( edit ); 96 this.set('editLibrary', edit); 97 98 Library.prototype.activate.apply( this, arguments ); 99 } 100 }); 101 102 module.exports = CollectionAdd; 103 104 },{}],2:[function(require,module,exports){ 105 /*globals wp, Backbone */ 106 107 /** 108 * wp.media.controller.CollectionEdit 109 * 110 * A state for editing a collection, which is used by audio and video playlists, 111 * and can be used for other collections. 112 * 113 * @class 114 * @augments wp.media.controller.Library 115 * @augments wp.media.controller.State 116 * @augments Backbone.Model 117 * 118 * @param {object} [attributes] The attributes hash passed to the state. 119 * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. 120 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. 121 * If one is not supplied, an empty media.model.Selection collection is created. 122 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 123 * @param {string} [attributes.content=browse] Initial mode for the content region. 124 * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. 125 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 126 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 127 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 128 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. 129 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 130 * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. 131 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 132 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 133 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 134 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 135 * Defaults to false for this state, because the library passed in *is* the selection. 136 * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). 137 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 138 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 139 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 140 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 141 */ 142 var Library = wp.media.controller.Library, 143 l10n = wp.media.view.l10n, 73 144 $ = jQuery, 74 l10n; 75 76 media.isTouchDevice = ( 'ontouchend' in document ); 77 78 // Link any localized strings. 79 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 80 81 // Link any settings. 82 media.view.settings = l10n.settings || {}; 83 delete l10n.settings; 84 85 // Copy the `post` setting over to the model settings. 86 media.model.settings.post = media.view.settings.post; 87 88 // Check if the browser supports CSS 3.0 transitions 89 $.support.transition = (function(){ 90 var style = document.documentElement.style, 91 transitions = { 92 WebkitTransition: 'webkitTransitionEnd', 93 MozTransition: 'transitionend', 94 OTransition: 'oTransitionEnd otransitionend', 95 transition: 'transitionend' 96 }, transition; 97 98 transition = _.find( _.keys( transitions ), function( transition ) { 99 return ! _.isUndefined( style[ transition ] ); 100 }); 101 102 return transition && { 103 end: transitions[ transition ] 104 }; 105 }()); 145 CollectionEdit; 146 147 CollectionEdit = Library.extend({ 148 defaults: { 149 multiple: false, 150 sortable: true, 151 date: false, 152 searchable: false, 153 content: 'browse', 154 describe: true, 155 dragInfo: true, 156 idealColumnWidth: 170, 157 editing: false, 158 priority: 60, 159 SettingsView: false, 160 syncSelection: false 161 }, 162 163 /** 164 * @since 3.9.0 165 */ 166 initialize: function() { 167 var collectionType = this.get('collectionType'); 168 169 if ( 'video' === this.get( 'type' ) ) { 170 collectionType = 'video-' + collectionType; 171 } 172 173 this.set( 'id', collectionType + '-edit' ); 174 this.set( 'toolbar', collectionType + '-edit' ); 175 176 // If we haven't been provided a `library`, create a `Selection`. 177 if ( ! this.get('library') ) { 178 this.set( 'library', new wp.media.model.Selection() ); 179 } 180 // The single `Attachment` view to be used in the `Attachments` view. 181 if ( ! this.get('AttachmentView') ) { 182 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 183 } 184 Library.prototype.initialize.apply( this, arguments ); 185 }, 186 187 /** 188 * @since 3.9.0 189 */ 190 activate: function() { 191 var library = this.get('library'); 192 193 // Limit the library to images only. 194 library.props.set( 'type', this.get( 'type' ) ); 195 196 // Watch for uploaded attachments. 197 this.get('library').observe( wp.Uploader.queue ); 198 199 this.frame.on( 'content:render:browse', this.renderSettings, this ); 200 201 Library.prototype.activate.apply( this, arguments ); 202 }, 203 204 /** 205 * @since 3.9.0 206 */ 207 deactivate: function() { 208 // Stop watching for uploaded attachments. 209 this.get('library').unobserve( wp.Uploader.queue ); 210 211 this.frame.off( 'content:render:browse', this.renderSettings, this ); 212 213 Library.prototype.deactivate.apply( this, arguments ); 214 }, 215 216 /** 217 * Render the collection embed settings view in the browser sidebar. 218 * 219 * @todo This is against the pattern elsewhere in media. Typically the frame 220 * is responsible for adding region mode callbacks. Explain. 221 * 222 * @since 3.9.0 223 * 224 * @param {wp.media.view.attachmentsBrowser} The attachments browser view. 225 */ 226 renderSettings: function( attachmentsBrowserView ) { 227 var library = this.get('library'), 228 collectionType = this.get('collectionType'), 229 dragInfoText = this.get('dragInfoText'), 230 SettingsView = this.get('SettingsView'), 231 obj = {}; 232 233 if ( ! library || ! attachmentsBrowserView ) { 234 return; 235 } 236 237 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 238 239 obj[ collectionType ] = new SettingsView({ 240 controller: this, 241 model: library[ collectionType ], 242 priority: 40 243 }); 244 245 attachmentsBrowserView.sidebar.set( obj ); 246 247 if ( dragInfoText ) { 248 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 249 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 250 priority: -40 251 }) ); 252 } 253 254 // Add the 'Reverse order' button to the toolbar. 255 attachmentsBrowserView.toolbar.set( 'reverse', { 256 text: l10n.reverseOrder, 257 priority: 80, 258 259 click: function() { 260 library.reset( library.toArray().reverse() ); 261 } 262 }); 263 } 264 }); 265 266 module.exports = CollectionEdit; 267 268 },{}],3:[function(require,module,exports){ 269 /*globals wp, _, Backbone */ 106 270 107 271 /** 108 * A shared event bus used to provide events into 109 * the media workflows that 3rd-party devs can use to hook 110 * in. 272 * wp.media.controller.Cropper 273 * 274 * A state for cropping an image. 275 * 276 * @class 277 * @augments wp.media.controller.State 278 * @augments Backbone.Model 111 279 */ 112 media.events = _.extend( {}, Backbone.Events ); 280 var l10n = wp.media.view.l10n, 281 Cropper; 282 283 Cropper = wp.media.controller.State.extend({ 284 defaults: { 285 id: 'cropper', 286 title: l10n.cropImage, 287 // Region mode defaults. 288 toolbar: 'crop', 289 content: 'crop', 290 router: false, 291 292 canSkipCrop: false 293 }, 294 295 activate: function() { 296 this.frame.on( 'content:create:crop', this.createCropContent, this ); 297 this.frame.on( 'close', this.removeCropper, this ); 298 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 299 }, 300 301 deactivate: function() { 302 this.frame.toolbar.mode('browse'); 303 }, 304 305 createCropContent: function() { 306 this.cropperView = new wp.media.view.Cropper({ 307 controller: this, 308 attachment: this.get('selection').first() 309 }); 310 this.cropperView.on('image-loaded', this.createCropToolbar, this); 311 this.frame.content.set(this.cropperView); 312 313 }, 314 removeCropper: function() { 315 this.imgSelect.cancelSelection(); 316 this.imgSelect.setOptions({remove: true}); 317 this.imgSelect.update(); 318 this.cropperView.remove(); 319 }, 320 createCropToolbar: function() { 321 var canSkipCrop, toolbarOptions; 322 323 canSkipCrop = this.get('canSkipCrop') || false; 324 325 toolbarOptions = { 326 controller: this.frame, 327 items: { 328 insert: { 329 style: 'primary', 330 text: l10n.cropImage, 331 priority: 80, 332 requires: { library: false, selection: false }, 333 334 click: function() { 335 var controller = this.controller, 336 selection; 337 338 selection = controller.state().get('selection').first(); 339 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 340 341 this.$el.text(l10n.cropping); 342 this.$el.attr('disabled', true); 343 344 controller.state().doCrop( selection ).done( function( croppedImage ) { 345 controller.trigger('cropped', croppedImage ); 346 controller.close(); 347 }).fail( function() { 348 controller.trigger('content:error:crop'); 349 }); 350 } 351 } 352 } 353 }; 354 355 if ( canSkipCrop ) { 356 _.extend( toolbarOptions.items, { 357 skip: { 358 style: 'secondary', 359 text: l10n.skipCropping, 360 priority: 70, 361 requires: { library: false, selection: false }, 362 click: function() { 363 var selection = this.controller.state().get('selection').first(); 364 this.controller.state().cropperView.remove(); 365 this.controller.trigger('skippedcrop', selection); 366 this.controller.close(); 367 } 368 } 369 }); 370 } 371 372 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 373 }, 374 375 doCrop: function( attachment ) { 376 return wp.ajax.post( 'custom-header-crop', { 377 nonce: attachment.get('nonces').edit, 378 id: attachment.get('id'), 379 cropDetails: attachment.get('cropDetails') 380 } ); 381 } 382 }); 383 384 module.exports = Cropper; 385 386 },{}],4:[function(require,module,exports){ 387 /*globals wp */ 113 388 114 389 /** 115 * Makes it easier to bind events using transitions. 116 * 117 * @param {string} selector 118 * @param {Number} sensitivity 119 * @returns {Promise} 390 * wp.media.controller.EditImage 391 * 392 * A state for editing (cropping, etc.) an image. 393 * 394 * @class 395 * @augments wp.media.controller.State 396 * @augments Backbone.Model 397 * 398 * @param {object} attributes The attributes hash passed to the state. 399 * @param {wp.media.model.Attachment} attributes.model The attachment. 400 * @param {string} [attributes.id=edit-image] Unique identifier. 401 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 402 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 403 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 404 * @param {string} [attributes.menu=false] Initial mode for the menu region. 405 * @param {string} [attributes.url] Unused. @todo Consider removal. 120 406 */ 121 media.transition = function( selector, sensitivity ) { 122 var deferred = $.Deferred(); 123 124 sensitivity = sensitivity || 2000; 125 126 if ( $.support.transition ) { 127 if ( ! (selector instanceof $) ) { 128 selector = $( selector ); 129 } 130 131 // Resolve the deferred when the first element finishes animating. 132 selector.first().one( $.support.transition.end, deferred.resolve ); 133 134 // Just in case the event doesn't trigger, fire a callback. 135 _.delay( deferred.resolve, sensitivity ); 136 137 // Otherwise, execute on the spot. 138 } else { 139 deferred.resolve(); 407 var l10n = wp.media.view.l10n, 408 EditImage; 409 410 EditImage = wp.media.controller.State.extend({ 411 defaults: { 412 id: 'edit-image', 413 title: l10n.editImage, 414 menu: false, 415 toolbar: 'edit-image', 416 content: 'edit-image', 417 url: '' 418 }, 419 420 /** 421 * @since 3.9.0 422 */ 423 activate: function() { 424 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar ); 425 }, 426 427 /** 428 * @since 3.9.0 429 */ 430 deactivate: function() { 431 this.stopListening( this.frame ); 432 }, 433 434 /** 435 * @since 3.9.0 436 */ 437 toolbar: function() { 438 var frame = this.frame, 439 lastState = frame.lastState(), 440 previous = lastState && lastState.id; 441 442 frame.toolbar.set( new wp.media.view.Toolbar({ 443 controller: frame, 444 items: { 445 back: { 446 style: 'primary', 447 text: l10n.back, 448 priority: 20, 449 click: function() { 450 if ( previous ) { 451 frame.setState( previous ); 452 } else { 453 frame.close(); 454 } 455 } 456 } 457 } 458 }) ); 140 459 } 141 142 return deferred.promise(); 143 }; 144 145 media.controller.Region = __webpack_require__( 27 ); 146 media.controller.StateMachine = __webpack_require__( 28 ); 147 media.controller.State = __webpack_require__( 29 ); 148 149 media.selectionSync = __webpack_require__( 30 ); 150 media.controller.Library = __webpack_require__( 31 ); 151 media.controller.ImageDetails = __webpack_require__( 32 ); 152 media.controller.GalleryEdit = __webpack_require__( 33 ); 153 media.controller.GalleryAdd = __webpack_require__( 34 ); 154 media.controller.CollectionEdit = __webpack_require__( 35 ); 155 media.controller.CollectionAdd = __webpack_require__( 36 ); 156 media.controller.FeaturedImage = __webpack_require__( 37 ); 157 media.controller.ReplaceImage = __webpack_require__( 38 ); 158 media.controller.EditImage = __webpack_require__( 39 ); 159 media.controller.MediaLibrary = __webpack_require__( 40 ); 160 media.controller.Embed = __webpack_require__( 41 ); 161 media.controller.Cropper = __webpack_require__( 42 ); 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.EditImage = __webpack_require__( 99 ); 216 media.view.Spinner = __webpack_require__( 100 ); 217 218 219 /***/ }), 220 /* 27 */ 221 /***/ (function(module, exports) { 222 223 /*globals Backbone, _ */ 460 }); 461 462 module.exports = EditImage; 463 464 },{}],5:[function(require,module,exports){ 465 /*globals wp, _, Backbone */ 224 466 225 467 /** 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. 468 * wp.media.controller.Embed 469 * 470 * A state for embedding media from a URL. 237 471 * 238 472 * @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. 473 * @augments wp.media.controller.State 474 * @augments Backbone.Model 475 * 476 * @param {object} attributes The attributes hash passed to the state. 477 * @param {string} [attributes.id=embed] Unique identifier. 478 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. 479 * @param {string} [attributes.content=embed] Initial mode for the content region. 480 * @param {string} [attributes.menu=default] Initial mode for the menu region. 481 * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. 482 * @param {string} [attributes.menu=false] Initial mode for the menu region. 483 * @param {int} [attributes.priority=120] The priority for the state link in the media menu. 484 * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. 485 * @param {string} [attributes.url] The embed URL. 486 * @param {object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. 244 487 */ 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. 488 var l10n = wp.media.view.l10n, 489 $ = Backbone.$, 490 Embed; 491 492 Embed = wp.media.controller.State.extend({ 493 defaults: { 494 id: 'embed', 495 title: l10n.insertFromUrlTitle, 496 content: 'embed', 497 menu: 'default', 498 toolbar: 'main-embed', 499 priority: 120, 500 type: 'link', 501 url: '', 502 metadata: {} 503 }, 504 505 // The amount of time used when debouncing the scan. 506 sensitivity: 200, 507 508 initialize: function(options) { 509 this.metadata = options.metadata; 510 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity ); 511 this.props = new Backbone.Model( this.metadata || { url: '' }); 512 this.props.on( 'change:url', this.debouncedScan, this ); 513 this.props.on( 'change:url', this.refresh, this ); 514 this.on( 'scan', this.scanImage, this ); 515 }, 516 517 /** 518 * Trigger a scan of the embedded URL's content for metadata required to embed. 255 519 * 520 * @fires wp.media.controller.Embed#scan 521 */ 522 scan: function() { 523 var scanners, 524 embed = this, 525 attributes = { 526 type: 'link', 527 scanners: [] 528 }; 529 530 // Scan is triggered with the list of `attributes` to set on the 531 // state, useful for the 'type' attribute and 'scanners' attribute, 532 // an array of promise objects for asynchronous scan operations. 533 if ( this.props.get('url') ) { 534 this.trigger( 'scan', attributes ); 535 } 536 537 if ( attributes.scanners.length ) { 538 scanners = attributes.scanners = $.when.apply( $, attributes.scanners ); 539 scanners.always( function() { 540 if ( embed.get('scanners') === scanners ) { 541 embed.set( 'loading', false ); 542 } 543 }); 544 } else { 545 attributes.scanners = null; 546 } 547 548 attributes.loading = !! attributes.scanners; 549 this.set( attributes ); 550 }, 551 /** 552 * Try scanning the embed as an image to discover its dimensions. 553 * 554 * @param {Object} attributes 555 */ 556 scanImage: function( attributes ) { 557 var frame = this.frame, 558 state = this, 559 url = this.props.get('url'), 560 image = new Image(), 561 deferred = $.Deferred(); 562 563 attributes.scanners.push( deferred.promise() ); 564 565 // Try to load the image and find its width/height. 566 image.onload = function() { 567 deferred.resolve(); 568 569 if ( state !== frame.state() || url !== state.props.get('url') ) { 570 return; 571 } 572 573 state.set({ 574 type: 'image' 575 }); 576 577 state.props.set({ 578 width: image.width, 579 height: image.height 580 }); 581 }; 582 583 image.onerror = deferred.reject; 584 image.src = url; 585 }, 586 587 refresh: function() { 588 this.frame.toolbar.get().refresh(); 589 }, 590 591 reset: function() { 592 this.props.clear().set({ url: '' }); 593 594 if ( this.active ) { 595 this.refresh(); 596 } 597 } 598 }); 599 600 module.exports = Embed; 601 602 },{}],6:[function(require,module,exports){ 603 /*globals wp, _ */ 604 605 /** 606 * wp.media.controller.FeaturedImage 607 * 608 * A state for selecting a featured image for a post. 609 * 610 * @class 611 * @augments wp.media.controller.Library 612 * @augments wp.media.controller.State 613 * @augments Backbone.Model 614 * 615 * @param {object} [attributes] The attributes hash passed to the state. 616 * @param {string} [attributes.id=featured-image] Unique identifier. 617 * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. 618 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 619 * If one is not supplied, a collection of all images will be created. 620 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 621 * @param {string} [attributes.content=upload] Initial mode for the content region. 622 * Overridden by persistent user setting if 'contentUserSetting' is true. 623 * @param {string} [attributes.menu=default] Initial mode for the menu region. 624 * @param {string} [attributes.router=browse] Initial mode for the router region. 625 * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. 626 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 627 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 628 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 629 * Accepts 'all', 'uploaded', or 'unattached'. 630 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 631 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 632 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 633 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 634 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 635 */ 636 var Attachment = wp.media.model.Attachment, 637 Library = wp.media.controller.Library, 638 l10n = wp.media.view.l10n, 639 FeaturedImage; 640 641 FeaturedImage = Library.extend({ 642 defaults: _.defaults({ 643 id: 'featured-image', 644 title: l10n.setFeaturedImageTitle, 645 multiple: false, 646 filterable: 'uploaded', 647 toolbar: 'featured-image', 648 priority: 60, 649 syncSelection: true 650 }, Library.prototype.defaults ), 651 652 /** 653 * @since 3.5.0 654 */ 655 initialize: function() { 656 var library, comparator; 657 658 // If we haven't been provided a `library`, create a `Selection`. 659 if ( ! this.get('library') ) { 660 this.set( 'library', wp.media.query({ type: 'image' }) ); 661 } 662 663 Library.prototype.initialize.apply( this, arguments ); 664 665 library = this.get('library'); 666 comparator = library.comparator; 667 668 // Overload the library's comparator to push items that are not in 669 // the mirrored query to the front of the aggregate collection. 670 library.comparator = function( a, b ) { 671 var aInQuery = !! this.mirroring.get( a.cid ), 672 bInQuery = !! this.mirroring.get( b.cid ); 673 674 if ( ! aInQuery && bInQuery ) { 675 return -1; 676 } else if ( aInQuery && ! bInQuery ) { 677 return 1; 678 } else { 679 return comparator.apply( this, arguments ); 680 } 681 }; 682 683 // Add all items in the selection to the library, so any featured 684 // images that are not initially loaded still appear. 685 library.observe( this.get('selection') ); 686 }, 687 688 /** 689 * @since 3.5.0 690 */ 691 activate: function() { 692 this.updateSelection(); 693 this.frame.on( 'open', this.updateSelection, this ); 694 695 Library.prototype.activate.apply( this, arguments ); 696 }, 697 698 /** 699 * @since 3.5.0 700 */ 701 deactivate: function() { 702 this.frame.off( 'open', this.updateSelection, this ); 703 704 Library.prototype.deactivate.apply( this, arguments ); 705 }, 706 707 /** 708 * @since 3.5.0 709 */ 710 updateSelection: function() { 711 var selection = this.get('selection'), 712 id = wp.media.view.settings.post.featuredImageId, 713 attachment; 714 715 if ( '' !== id && -1 !== id ) { 716 attachment = Attachment.get( id ); 717 attachment.fetch(); 718 } 719 720 selection.reset( attachment ? [ attachment ] : [] ); 721 } 722 }); 723 724 module.exports = FeaturedImage; 725 726 },{}],7:[function(require,module,exports){ 727 /*globals wp, _ */ 728 729 /** 730 * wp.media.controller.GalleryAdd 731 * 732 * A state for selecting more images to add to a gallery. 733 * 734 * @class 735 * @augments wp.media.controller.Library 736 * @augments wp.media.controller.State 737 * @augments Backbone.Model 738 * 739 * @param {object} [attributes] The attributes hash passed to the state. 740 * @param {string} [attributes.id=gallery-library] Unique identifier. 741 * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. 742 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 743 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 744 * If one is not supplied, a collection of all images will be created. 745 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 746 * Accepts 'all', 'uploaded', or 'unattached'. 747 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 748 * @param {string} [attributes.content=upload] Initial mode for the content region. 749 * Overridden by persistent user setting if 'contentUserSetting' is true. 750 * @param {string} [attributes.router=browse] Initial mode for the router region. 751 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 752 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 753 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 754 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 755 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 756 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 757 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 758 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 759 */ 760 var Selection = wp.media.model.Selection, 761 Library = wp.media.controller.Library, 762 l10n = wp.media.view.l10n, 763 GalleryAdd; 764 765 GalleryAdd = Library.extend({ 766 defaults: _.defaults({ 767 id: 'gallery-library', 768 title: l10n.addToGalleryTitle, 769 multiple: 'add', 770 filterable: 'uploaded', 771 menu: 'gallery', 772 toolbar: 'gallery-add', 773 priority: 100, 774 syncSelection: false 775 }, Library.prototype.defaults ), 776 777 /** 778 * @since 3.5.0 779 */ 780 initialize: function() { 781 // If a library wasn't supplied, create a library of images. 782 if ( ! this.get('library') ) { 783 this.set( 'library', wp.media.query({ type: 'image' }) ); 784 } 785 786 Library.prototype.initialize.apply( this, arguments ); 787 }, 788 789 /** 790 * @since 3.5.0 791 */ 792 activate: function() { 793 var library = this.get('library'), 794 edit = this.frame.state('gallery-edit').get('library'); 795 796 if ( this.editLibrary && this.editLibrary !== edit ) { 797 library.unobserve( this.editLibrary ); 798 } 799 800 // Accepts attachments that exist in the original library and 801 // that do not exist in gallery's library. 802 library.validator = function( attachment ) { 803 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 804 }; 805 806 // Reset the library to ensure that all attachments are re-added 807 // to the collection. Do so silently, as calling `observe` will 808 // trigger the `reset` event. 809 library.reset( library.mirroring.models, { silent: true }); 810 library.observe( edit ); 811 this.editLibrary = edit; 812 813 Library.prototype.activate.apply( this, arguments ); 814 } 815 }); 816 817 module.exports = GalleryAdd; 818 819 },{}],8:[function(require,module,exports){ 820 /*globals wp */ 821 822 /** 823 * wp.media.controller.GalleryEdit 824 * 825 * A state for editing a gallery's images and settings. 826 * 827 * @class 828 * @augments wp.media.controller.Library 829 * @augments wp.media.controller.State 830 * @augments Backbone.Model 831 * 832 * @param {object} [attributes] The attributes hash passed to the state. 833 * @param {string} [attributes.id=gallery-edit] Unique identifier. 834 * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region. 835 * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery. 836 * If one is not supplied, an empty media.model.Selection collection is created. 837 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 838 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 839 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 840 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 841 * @param {string|false} [attributes.content=browse] Initial mode for the content region. 842 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 843 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 844 * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface. 845 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 846 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 847 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 848 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 849 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 850 * Defaults to false for this state, because the library passed in *is* the selection. 851 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 852 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 853 */ 854 var Library = wp.media.controller.Library, 855 l10n = wp.media.view.l10n, 856 GalleryEdit; 857 858 GalleryEdit = Library.extend({ 859 defaults: { 860 id: 'gallery-edit', 861 title: l10n.editGalleryTitle, 862 multiple: false, 863 searchable: false, 864 sortable: true, 865 date: false, 866 display: false, 867 content: 'browse', 868 toolbar: 'gallery-edit', 869 describe: true, 870 displaySettings: true, 871 dragInfo: true, 872 idealColumnWidth: 170, 873 editing: false, 874 priority: 60, 875 syncSelection: false 876 }, 877 878 /** 879 * @since 3.5.0 880 */ 881 initialize: function() { 882 // If we haven't been provided a `library`, create a `Selection`. 883 if ( ! this.get('library') ) { 884 this.set( 'library', new wp.media.model.Selection() ); 885 } 886 887 // The single `Attachment` view to be used in the `Attachments` view. 888 if ( ! this.get('AttachmentView') ) { 889 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 890 } 891 892 Library.prototype.initialize.apply( this, arguments ); 893 }, 894 895 /** 896 * @since 3.5.0 897 */ 898 activate: function() { 899 var library = this.get('library'); 900 901 // Limit the library to images only. 902 library.props.set( 'type', 'image' ); 903 904 // Watch for uploaded attachments. 905 this.get('library').observe( wp.Uploader.queue ); 906 907 this.frame.on( 'content:render:browse', this.gallerySettings, this ); 908 909 Library.prototype.activate.apply( this, arguments ); 910 }, 911 912 /** 913 * @since 3.5.0 914 */ 915 deactivate: function() { 916 // Stop watching for uploaded attachments. 917 this.get('library').unobserve( wp.Uploader.queue ); 918 919 this.frame.off( 'content:render:browse', this.gallerySettings, this ); 920 921 Library.prototype.deactivate.apply( this, arguments ); 922 }, 923 924 /** 256 925 * @since 3.5.0 257 926 * 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 ) { 927 * @param browser 928 */ 929 gallerySettings: function( browser ) { 930 if ( ! this.get('displaySettings') ) { 384 931 return; 385 932 } 386 933 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; 934 var library = this.get('library'); 935 936 if ( ! library || ! browser ) { 937 return; 938 } 939 940 library.gallery = library.gallery || new Backbone.Model(); 941 942 browser.sidebar.set({ 943 gallery: new wp.media.view.Settings.Gallery({ 944 controller: this, 945 model: library.gallery, 946 priority: 40 947 }) 948 }); 949 950 browser.toolbar.set( 'reverse', { 951 text: l10n.reverseOrder, 952 priority: 80, 953 954 click: function() { 955 library.reset( library.toArray().reverse() ); 956 } 957 }); 398 958 } 399 959 }); 400 960 401 module.exports = Region; 402 403 404 /***/ }), 405 /* 28 */ 406 /***/ (function(module, exports) { 407 408 /*globals _, Backbone */ 961 module.exports = GalleryEdit; 962 963 },{}],9:[function(require,module,exports){ 964 /*globals wp, _ */ 409 965 410 966 /** 411 * wp.media.controller.StateMachine 412 * 413 * A state machine keeps track of state. It is in one state at a time, 414 * and can change from one state to another. 415 * 416 * States are stored as models in a Backbone collection. 417 * 418 * @since 3.5.0 967 * wp.media.controller.ImageDetails 968 * 969 * A state for editing the attachment display settings of an image that's been 970 * inserted into the editor. 419 971 * 420 972 * @class 973 * @augments wp.media.controller.State 421 974 * @augments Backbone.Model 422 * @mixin 423 * @mixes Backbone.Events 424 * 425 * @param {Array} states 975 * 976 * @param {object} [attributes] The attributes hash passed to the state. 977 * @param {string} [attributes.id=image-details] Unique identifier. 978 * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. 979 * @param {wp.media.model.Attachment} attributes.image The image's model. 980 * @param {string|false} [attributes.content=image-details] Initial mode for the content region. 981 * @param {string|false} [attributes.menu=false] Initial mode for the menu region. 982 * @param {string|false} [attributes.router=false] Initial mode for the router region. 983 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 984 * @param {boolean} [attributes.editing=false] Unused. 985 * @param {int} [attributes.priority=60] Unused. 986 * 987 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, 988 * however this may not do anything. 426 989 */ 427 var StateMachine = function( states ) { 428 // @todo This is dead code. The states collection gets created in media.view.Frame._createStates. 429 this.states = new Backbone.Collection( states ); 430 }; 431 432 // Use Backbone's self-propagating `extend` inheritance method. 433 StateMachine.extend = Backbone.Model.extend; 434 435 _.extend( StateMachine.prototype, Backbone.Events, { 436 /** 437 * Fetch a state. 990 var State = wp.media.controller.State, 991 Library = wp.media.controller.Library, 992 l10n = wp.media.view.l10n, 993 ImageDetails; 994 995 ImageDetails = State.extend({ 996 defaults: _.defaults({ 997 id: 'image-details', 998 title: l10n.imageDetailsTitle, 999 content: 'image-details', 1000 menu: false, 1001 router: false, 1002 toolbar: 'image-details', 1003 editing: false, 1004 priority: 60 1005 }, Library.prototype.defaults ), 1006 1007 /** 1008 * @since 3.9.0 438 1009 * 439 * If no `id` is provided, returns the active state. 440 * 441 * Implicitly creates states. 442 * 443 * Ensure that the `states` collection exists so the `StateMachine` 444 * can be used as a mixin. 445 * 446 * @since 3.5.0 447 * 448 * @param {string} id 449 * @returns {wp.media.controller.State} Returns a State model 450 * from the StateMachine collection 451 */ 452 state: function( id ) { 453 this.states = this.states || new Backbone.Collection(); 454 455 // Default to the active state. 456 id = id || this._state; 457 458 if ( id && ! this.states.get( id ) ) { 459 this.states.add({ id: id }); 460 } 461 return this.states.get( id ); 462 }, 463 464 /** 465 * Sets the active state. 466 * 467 * Bail if we're trying to select the current state, if we haven't 468 * created the `states` collection, or are trying to select a state 469 * that does not exist. 470 * 471 * @since 3.5.0 472 * 473 * @param {string} id 474 * 475 * @fires wp.media.controller.State#deactivate 476 * @fires wp.media.controller.State#activate 477 * 478 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining 479 */ 480 setState: function( id ) { 481 var previous = this.state(); 482 483 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 484 return this; 485 } 486 487 if ( previous ) { 488 previous.trigger('deactivate'); 489 this._lastState = previous.id; 490 } 491 492 this._state = id; 493 this.state().trigger('activate'); 494 495 return this; 496 }, 497 498 /** 499 * Returns the previous active state. 500 * 501 * Call the `state()` method with no parameters to retrieve the current 502 * active state. 503 * 504 * @since 3.5.0 505 * 506 * @returns {wp.media.controller.State} Returns a State model 507 * from the StateMachine collection 508 */ 509 lastState: function() { 510 if ( this._lastState ) { 511 return this.state( this._lastState ); 512 } 1010 * @param options Attributes 1011 */ 1012 initialize: function( options ) { 1013 this.image = options.image; 1014 State.prototype.initialize.apply( this, arguments ); 1015 }, 1016 1017 /** 1018 * @since 3.9.0 1019 */ 1020 activate: function() { 1021 this.frame.modal.$el.addClass('image-details'); 513 1022 } 514 1023 }); 515 1024 516 // Map all event binding and triggering on a StateMachine to its `states` collection. 517 _.each([ 'on', 'off', 'trigger' ], function( method ) { 518 /** 519 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 520 */ 521 StateMachine.prototype[ method ] = function() { 522 // Ensure that the `states` collection exists so the `StateMachine` 523 // can be used as a mixin. 524 this.states = this.states || new Backbone.Collection(); 525 // Forward the method to the `states` collection. 526 this.states[ method ].apply( this.states, arguments ); 527 return this; 528 }; 529 }); 530 531 module.exports = StateMachine; 532 533 534 /***/ }), 535 /* 29 */ 536 /***/ (function(module, exports) { 537 538 /*globals _, Backbone */ 539 540 /** 541 * wp.media.controller.State 542 * 543 * A state is a step in a workflow that when set will trigger the controllers 544 * for the regions to be updated as specified in the frame. 545 * 546 * A state has an event-driven lifecycle: 547 * 548 * 'ready' triggers when a state is added to a state machine's collection. 549 * 'activate' triggers when a state is activated by a state machine. 550 * 'deactivate' triggers when a state is deactivated by a state machine. 551 * 'reset' is not triggered automatically. It should be invoked by the 552 * proper controller to reset the state to its default. 553 * 554 * @class 555 * @augments Backbone.Model 556 */ 557 var State = Backbone.Model.extend({ 558 /** 559 * Constructor. 560 * 561 * @since 3.5.0 562 */ 563 constructor: function() { 564 this.on( 'activate', this._preActivate, this ); 565 this.on( 'activate', this.activate, this ); 566 this.on( 'activate', this._postActivate, this ); 567 this.on( 'deactivate', this._deactivate, this ); 568 this.on( 'deactivate', this.deactivate, this ); 569 this.on( 'reset', this.reset, this ); 570 this.on( 'ready', this._ready, this ); 571 this.on( 'ready', this.ready, this ); 572 /** 573 * Call parent constructor with passed arguments 574 */ 575 Backbone.Model.apply( this, arguments ); 576 this.on( 'change:menu', this._updateMenu, this ); 577 }, 578 /** 579 * Ready event callback. 580 * 581 * @abstract 582 * @since 3.5.0 583 */ 584 ready: function() {}, 585 586 /** 587 * Activate event callback. 588 * 589 * @abstract 590 * @since 3.5.0 591 */ 592 activate: function() {}, 593 594 /** 595 * Deactivate event callback. 596 * 597 * @abstract 598 * @since 3.5.0 599 */ 600 deactivate: function() {}, 601 602 /** 603 * Reset event callback. 604 * 605 * @abstract 606 * @since 3.5.0 607 */ 608 reset: function() {}, 609 610 /** 611 * @access private 612 * @since 3.5.0 613 */ 614 _ready: function() { 615 this._updateMenu(); 616 }, 617 618 /** 619 * @access private 620 * @since 3.5.0 621 */ 622 _preActivate: function() { 623 this.active = true; 624 }, 625 626 /** 627 * @access private 628 * @since 3.5.0 629 */ 630 _postActivate: function() { 631 this.on( 'change:menu', this._menu, this ); 632 this.on( 'change:titleMode', this._title, this ); 633 this.on( 'change:content', this._content, this ); 634 this.on( 'change:toolbar', this._toolbar, this ); 635 636 this.frame.on( 'title:render:default', this._renderTitle, this ); 637 638 this._title(); 639 this._menu(); 640 this._toolbar(); 641 this._content(); 642 this._router(); 643 }, 644 645 /** 646 * @access private 647 * @since 3.5.0 648 */ 649 _deactivate: function() { 650 this.active = false; 651 652 this.frame.off( 'title:render:default', this._renderTitle, this ); 653 654 this.off( 'change:menu', this._menu, this ); 655 this.off( 'change:titleMode', this._title, this ); 656 this.off( 'change:content', this._content, this ); 657 this.off( 'change:toolbar', this._toolbar, this ); 658 }, 659 660 /** 661 * @access private 662 * @since 3.5.0 663 */ 664 _title: function() { 665 this.frame.title.render( this.get('titleMode') || 'default' ); 666 }, 667 668 /** 669 * @access private 670 * @since 3.5.0 671 */ 672 _renderTitle: function( view ) { 673 view.$el.text( this.get('title') || '' ); 674 }, 675 676 /** 677 * @access private 678 * @since 3.5.0 679 */ 680 _router: function() { 681 var router = this.frame.router, 682 mode = this.get('router'), 683 view; 684 685 this.frame.$el.toggleClass( 'hide-router', ! mode ); 686 if ( ! mode ) { 687 return; 688 } 689 690 this.frame.router.render( mode ); 691 692 view = router.get(); 693 if ( view && view.select ) { 694 view.select( this.frame.content.mode() ); 695 } 696 }, 697 698 /** 699 * @access private 700 * @since 3.5.0 701 */ 702 _menu: function() { 703 var menu = this.frame.menu, 704 mode = this.get('menu'), 705 view; 706 707 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 708 if ( ! mode ) { 709 return; 710 } 711 712 menu.mode( mode ); 713 714 view = menu.get(); 715 if ( view && view.select ) { 716 view.select( this.id ); 717 } 718 }, 719 720 /** 721 * @access private 722 * @since 3.5.0 723 */ 724 _updateMenu: function() { 725 var previous = this.previous('menu'), 726 menu = this.get('menu'); 727 728 if ( previous ) { 729 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 730 } 731 732 if ( menu ) { 733 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 734 } 735 }, 736 737 /** 738 * Create a view in the media menu for the state. 739 * 740 * @access private 741 * @since 3.5.0 742 * 743 * @param {media.view.Menu} view The menu view. 744 */ 745 _renderMenu: function( view ) { 746 var menuItem = this.get('menuItem'), 747 title = this.get('title'), 748 priority = this.get('priority'); 749 750 if ( ! menuItem && title ) { 751 menuItem = { text: title }; 752 753 if ( priority ) { 754 menuItem.priority = priority; 755 } 756 } 757 758 if ( ! menuItem ) { 759 return; 760 } 761 762 view.set( this.id, menuItem ); 763 } 764 }); 765 766 _.each(['toolbar','content'], function( region ) { 767 /** 768 * @access private 769 */ 770 State.prototype[ '_' + region ] = function() { 771 var mode = this.get( region ); 772 if ( mode ) { 773 this.frame[ region ].render( mode ); 774 } 775 }; 776 }); 777 778 module.exports = State; 779 780 781 /***/ }), 782 /* 30 */ 783 /***/ (function(module, exports) { 784 785 /*globals _ */ 786 787 /** 788 * wp.media.selectionSync 789 * 790 * Sync an attachments selection in a state with another state. 791 * 792 * Allows for selecting multiple images in the Insert Media workflow, and then 793 * switching to the Insert Gallery workflow while preserving the attachments selection. 794 * 795 * @mixin 796 */ 797 var selectionSync = { 798 /** 799 * @since 3.5.0 800 */ 801 syncSelection: function() { 802 var selection = this.get('selection'), 803 manager = this.frame._selection; 804 805 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 806 return; 807 } 808 809 // If the selection supports multiple items, validate the stored 810 // attachments based on the new selection's conditions. Record 811 // the attachments that are not included; we'll maintain a 812 // reference to those. Other attachments are considered in flux. 813 if ( selection.multiple ) { 814 selection.reset( [], { silent: true }); 815 selection.validateAll( manager.attachments ); 816 manager.difference = _.difference( manager.attachments.models, selection.models ); 817 } 818 819 // Sync the selection's single item with the master. 820 selection.single( manager.single ); 821 }, 822 823 /** 824 * Record the currently active attachments, which is a combination 825 * of the selection's attachments and the set of selected 826 * attachments that this specific selection considered invalid. 827 * Reset the difference and record the single attachment. 828 * 829 * @since 3.5.0 830 */ 831 recordSelection: function() { 832 var selection = this.get('selection'), 833 manager = this.frame._selection; 834 835 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 836 return; 837 } 838 839 if ( selection.multiple ) { 840 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 841 manager.difference = []; 842 } else { 843 manager.attachments.add( selection.toArray() ); 844 } 845 846 manager.single = selection._single; 847 } 848 }; 849 850 module.exports = selectionSync; 851 852 853 /***/ }), 854 /* 31 */ 855 /***/ (function(module, exports) { 856 1025 module.exports = ImageDetails; 1026 1027 },{}],10:[function(require,module,exports){ 857 1028 /*globals wp, _, Backbone */ 858 1029 … … 1128 1299 module.exports = Library; 1129 1300 1130 1131 /***/ }), 1132 /* 32 */ 1133 /***/ (function(module, exports) { 1134 1301 },{}],11:[function(require,module,exports){ 1135 1302 /*globals wp, _ */ 1136 1303 1137 1304 /** 1138 * wp.media.controller.ImageDetails 1139 * 1140 * A state for editing the attachment display settings of an image that's been 1141 * inserted into the editor. 1142 * 1143 * @class 1144 * @augments wp.media.controller.State 1145 * @augments Backbone.Model 1146 * 1147 * @param {object} [attributes] The attributes hash passed to the state. 1148 * @param {string} [attributes.id=image-details] Unique identifier. 1149 * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. 1150 * @param {wp.media.model.Attachment} attributes.image The image's model. 1151 * @param {string|false} [attributes.content=image-details] Initial mode for the content region. 1152 * @param {string|false} [attributes.menu=false] Initial mode for the menu region. 1153 * @param {string|false} [attributes.router=false] Initial mode for the router region. 1154 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1155 * @param {boolean} [attributes.editing=false] Unused. 1156 * @param {int} [attributes.priority=60] Unused. 1157 * 1158 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, 1159 * however this may not do anything. 1160 */ 1161 var State = wp.media.controller.State, 1162 Library = wp.media.controller.Library, 1163 l10n = wp.media.view.l10n, 1164 ImageDetails; 1165 1166 ImageDetails = State.extend({ 1167 defaults: _.defaults({ 1168 id: 'image-details', 1169 title: l10n.imageDetailsTitle, 1170 content: 'image-details', 1171 menu: false, 1172 router: false, 1173 toolbar: 'image-details', 1174 editing: false, 1175 priority: 60 1176 }, Library.prototype.defaults ), 1177 1178 /** 1179 * @since 3.9.0 1180 * 1181 * @param options Attributes 1182 */ 1183 initialize: function( options ) { 1184 this.image = options.image; 1185 State.prototype.initialize.apply( this, arguments ); 1186 }, 1187 1188 /** 1189 * @since 3.9.0 1190 */ 1191 activate: function() { 1192 this.frame.modal.$el.addClass('image-details'); 1193 } 1194 }); 1195 1196 module.exports = ImageDetails; 1197 1198 1199 /***/ }), 1200 /* 33 */ 1201 /***/ (function(module, exports) { 1202 1203 /*globals wp */ 1204 1205 /** 1206 * wp.media.controller.GalleryEdit 1207 * 1208 * A state for editing a gallery's images and settings. 1305 * wp.media.controller.MediaLibrary 1209 1306 * 1210 1307 * @class … … 1212 1309 * @augments wp.media.controller.State 1213 1310 * @augments Backbone.Model 1214 *1215 * @param {object} [attributes] The attributes hash passed to the state.1216 * @param {string} [attributes.id=gallery-edit] Unique identifier.1217 * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region.1218 * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery.1219 * If one is not supplied, an empty media.model.Selection collection is created.1220 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled.1221 * @param {boolean} [attributes.searchable=false] Whether the library is searchable.1222 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.1223 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar.1224 * @param {string|false} [attributes.content=browse] Initial mode for the content region.1225 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region.1226 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery.1227 * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface.1228 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable.1229 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.1230 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance.1231 * @param {int} [attributes.priority=60] The priority for the state link in the media menu.1232 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state.1233 * Defaults to false for this state, because the library passed in *is* the selection.1234 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`.1235 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary.1236 1311 */ 1237 1312 var Library = wp.media.controller.Library, 1238 l10n = wp.media.view.l10n, 1239 GalleryEdit; 1240 1241 GalleryEdit = Library.extend({ 1242 defaults: { 1243 id: 'gallery-edit', 1244 title: l10n.editGalleryTitle, 1245 multiple: false, 1246 searchable: false, 1247 sortable: true, 1248 date: false, 1249 display: false, 1250 content: 'browse', 1251 toolbar: 'gallery-edit', 1252 describe: true, 1253 displaySettings: true, 1254 dragInfo: true, 1255 idealColumnWidth: 170, 1256 editing: false, 1257 priority: 60, 1258 syncSelection: false 1259 }, 1260 1261 /** 1262 * @since 3.5.0 1263 */ 1264 initialize: function() { 1265 // If we haven't been provided a `library`, create a `Selection`. 1266 if ( ! this.get('library') ) { 1267 this.set( 'library', new wp.media.model.Selection() ); 1268 } 1269 1270 // The single `Attachment` view to be used in the `Attachments` view. 1271 if ( ! this.get('AttachmentView') ) { 1272 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 1273 } 1313 MediaLibrary; 1314 1315 MediaLibrary = Library.extend({ 1316 defaults: _.defaults({ 1317 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1318 filterable: 'uploaded', 1319 1320 displaySettings: false, 1321 priority: 80, 1322 syncSelection: false 1323 }, Library.prototype.defaults ), 1324 1325 /** 1326 * @since 3.9.0 1327 * 1328 * @param options 1329 */ 1330 initialize: function( options ) { 1331 this.media = options.media; 1332 this.type = options.type; 1333 this.set( 'library', wp.media.query({ type: this.type }) ); 1274 1334 1275 1335 Library.prototype.initialize.apply( this, arguments ); … … 1277 1337 1278 1338 /** 1279 * @since 3. 5.01339 * @since 3.9.0 1280 1340 */ 1281 1341 activate: function() { 1282 var library = this.get('library'); 1283 1284 // Limit the library to images only. 1285 library.props.set( 'type', 'image' ); 1286 1287 // Watch for uploaded attachments. 1288 this.get('library').observe( wp.Uploader.queue ); 1289 1290 this.frame.on( 'content:render:browse', this.gallerySettings, this ); 1291 1292 Library.prototype.activate.apply( this, arguments ); 1293 }, 1294 1295 /** 1296 * @since 3.5.0 1297 */ 1298 deactivate: function() { 1299 // Stop watching for uploaded attachments. 1300 this.get('library').unobserve( wp.Uploader.queue ); 1301 1302 this.frame.off( 'content:render:browse', this.gallerySettings, this ); 1303 1304 Library.prototype.deactivate.apply( this, arguments ); 1305 }, 1306 1307 /** 1308 * @since 3.5.0 1309 * 1310 * @param browser 1311 */ 1312 gallerySettings: function( browser ) { 1313 if ( ! this.get('displaySettings') ) { 1314 return; 1315 } 1316 1317 var library = this.get('library'); 1318 1319 if ( ! library || ! browser ) { 1320 return; 1321 } 1322 1323 library.gallery = library.gallery || new Backbone.Model(); 1324 1325 browser.sidebar.set({ 1326 gallery: new wp.media.view.Settings.Gallery({ 1327 controller: this, 1328 model: library.gallery, 1329 priority: 40 1330 }) 1331 }); 1332 1333 browser.toolbar.set( 'reverse', { 1334 text: l10n.reverseOrder, 1335 priority: 80, 1336 1337 click: function() { 1338 library.reset( library.toArray().reverse() ); 1339 } 1340 }); 1341 } 1342 }); 1343 1344 module.exports = GalleryEdit; 1345 1346 1347 /***/ }), 1348 /* 34 */ 1349 /***/ (function(module, exports) { 1350 1351 /*globals wp, _ */ 1352 1353 /** 1354 * wp.media.controller.GalleryAdd 1355 * 1356 * A state for selecting more images to add to a gallery. 1357 * 1358 * @class 1359 * @augments wp.media.controller.Library 1360 * @augments wp.media.controller.State 1361 * @augments Backbone.Model 1362 * 1363 * @param {object} [attributes] The attributes hash passed to the state. 1364 * @param {string} [attributes.id=gallery-library] Unique identifier. 1365 * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. 1366 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 1367 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1368 * If one is not supplied, a collection of all images will be created. 1369 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1370 * Accepts 'all', 'uploaded', or 'unattached'. 1371 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 1372 * @param {string} [attributes.content=upload] Initial mode for the content region. 1373 * Overridden by persistent user setting if 'contentUserSetting' is true. 1374 * @param {string} [attributes.router=browse] Initial mode for the router region. 1375 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 1376 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1377 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1378 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1379 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1380 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 1381 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1382 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 1383 */ 1384 var Selection = wp.media.model.Selection, 1385 Library = wp.media.controller.Library, 1386 l10n = wp.media.view.l10n, 1387 GalleryAdd; 1388 1389 GalleryAdd = Library.extend({ 1390 defaults: _.defaults({ 1391 id: 'gallery-library', 1392 title: l10n.addToGalleryTitle, 1393 multiple: 'add', 1394 filterable: 'uploaded', 1395 menu: 'gallery', 1396 toolbar: 'gallery-add', 1397 priority: 100, 1398 syncSelection: false 1399 }, Library.prototype.defaults ), 1400 1401 /** 1402 * @since 3.5.0 1403 */ 1404 initialize: function() { 1405 // If a library wasn't supplied, create a library of images. 1406 if ( ! this.get('library') ) { 1407 this.set( 'library', wp.media.query({ type: 'image' }) ); 1408 } 1409 1410 Library.prototype.initialize.apply( this, arguments ); 1411 }, 1412 1413 /** 1414 * @since 3.5.0 1415 */ 1416 activate: function() { 1417 var library = this.get('library'), 1418 edit = this.frame.state('gallery-edit').get('library'); 1419 1420 if ( this.editLibrary && this.editLibrary !== edit ) { 1421 library.unobserve( this.editLibrary ); 1422 } 1423 1424 // Accepts attachments that exist in the original library and 1425 // that do not exist in gallery's library. 1426 library.validator = function( attachment ) { 1427 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 1428 }; 1429 1430 // Reset the library to ensure that all attachments are re-added 1431 // to the collection. Do so silently, as calling `observe` will 1432 // trigger the `reset` event. 1433 library.reset( library.mirroring.models, { silent: true }); 1434 library.observe( edit ); 1435 this.editLibrary = edit; 1436 1342 // @todo this should use this.frame. 1343 if ( wp.media.frame.lastMime ) { 1344 this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) ); 1345 delete wp.media.frame.lastMime; 1346 } 1437 1347 Library.prototype.activate.apply( this, arguments ); 1438 1348 } 1439 1349 }); 1440 1350 1441 module.exports = GalleryAdd; 1442 1443 1444 /***/ }), 1445 /* 35 */ 1446 /***/ (function(module, exports) { 1447 1448 /*globals wp, Backbone */ 1351 module.exports = MediaLibrary; 1352 1353 },{}],12:[function(require,module,exports){ 1354 /*globals Backbone, _ */ 1449 1355 1450 1356 /** 1451 * wp.media.controller.CollectionEdit 1452 * 1453 * A state for editing a collection, which is used by audio and video playlists, 1454 * and can be used for other collections. 1357 * wp.media.controller.Region 1358 * 1359 * A region is a persistent application layout area. 1360 * 1361 * A region assumes one mode at any time, and can be switched to another. 1362 * 1363 * When mode changes, events are triggered on the region's parent view. 1364 * The parent view will listen to specific events and fill the region with an 1365 * appropriate view depending on mode. For example, a frame listens for the 1366 * 'browse' mode t be activated on the 'content' view and then fills the region 1367 * with an AttachmentsBrowser view. 1455 1368 * 1456 1369 * @class 1457 * @augments wp.media.controller.Library 1458 * @augments wp.media.controller.State 1459 * @augments Backbone.Model 1460 * 1461 * @param {object} [attributes] The attributes hash passed to the state. 1462 * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. 1463 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. 1464 * If one is not supplied, an empty media.model.Selection collection is created. 1465 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1466 * @param {string} [attributes.content=browse] Initial mode for the content region. 1467 * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. 1468 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 1469 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1470 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 1471 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. 1472 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 1473 * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. 1474 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 1475 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 1476 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1477 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1478 * Defaults to false for this state, because the library passed in *is* the selection. 1479 * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). 1480 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 1481 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 1482 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 1483 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 1370 * 1371 * @param {object} options Options hash for the region. 1372 * @param {string} options.id Unique identifier for the region. 1373 * @param {Backbone.View} options.view A parent view the region exists within. 1374 * @param {string} options.selector jQuery selector for the region within the parent view. 1484 1375 */ 1485 var Library = wp.media.controller.Library, 1486 l10n = wp.media.view.l10n, 1487 $ = jQuery, 1488 CollectionEdit; 1489 1490 CollectionEdit = Library.extend({ 1491 defaults: { 1492 multiple: false, 1493 sortable: true, 1494 date: false, 1495 searchable: false, 1496 content: 'browse', 1497 describe: true, 1498 dragInfo: true, 1499 idealColumnWidth: 170, 1500 editing: false, 1501 priority: 60, 1502 SettingsView: false, 1503 syncSelection: false 1504 }, 1505 1506 /** 1507 * @since 3.9.0 1508 */ 1509 initialize: function() { 1510 var collectionType = this.get('collectionType'); 1511 1512 if ( 'video' === this.get( 'type' ) ) { 1513 collectionType = 'video-' + collectionType; 1514 } 1515 1516 this.set( 'id', collectionType + '-edit' ); 1517 this.set( 'toolbar', collectionType + '-edit' ); 1518 1519 // If we haven't been provided a `library`, create a `Selection`. 1520 if ( ! this.get('library') ) { 1521 this.set( 'library', new wp.media.model.Selection() ); 1522 } 1523 // The single `Attachment` view to be used in the `Attachments` view. 1524 if ( ! this.get('AttachmentView') ) { 1525 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 1526 } 1527 Library.prototype.initialize.apply( this, arguments ); 1528 }, 1529 1530 /** 1531 * @since 3.9.0 1532 */ 1533 activate: function() { 1534 var library = this.get('library'); 1535 1536 // Limit the library to images only. 1537 library.props.set( 'type', this.get( 'type' ) ); 1538 1539 // Watch for uploaded attachments. 1540 this.get('library').observe( wp.Uploader.queue ); 1541 1542 this.frame.on( 'content:render:browse', this.renderSettings, this ); 1543 1544 Library.prototype.activate.apply( this, arguments ); 1545 }, 1546 1547 /** 1548 * @since 3.9.0 1549 */ 1550 deactivate: function() { 1551 // Stop watching for uploaded attachments. 1552 this.get('library').unobserve( wp.Uploader.queue ); 1553 1554 this.frame.off( 'content:render:browse', this.renderSettings, this ); 1555 1556 Library.prototype.deactivate.apply( this, arguments ); 1557 }, 1558 1559 /** 1560 * Render the collection embed settings view in the browser sidebar. 1376 var Region = function( options ) { 1377 _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); 1378 }; 1379 1380 // Use Backbone's self-propagating `extend` inheritance method. 1381 Region.extend = Backbone.Model.extend; 1382 1383 _.extend( Region.prototype, { 1384 /** 1385 * Activate a mode. 1561 1386 * 1562 * @todo This is against the pattern elsewhere in media. Typically the frame 1563 * is responsible for adding region mode callbacks. Explain. 1387 * @since 3.5.0 1564 1388 * 1565 * @ since 3.9.01389 * @param {string} mode 1566 1390 * 1567 * @param {wp.media.view.attachmentsBrowser} The attachments browser view. 1568 */ 1569 renderSettings: function( attachmentsBrowserView ) { 1570 var library = this.get('library'), 1571 collectionType = this.get('collectionType'), 1572 dragInfoText = this.get('dragInfoText'), 1573 SettingsView = this.get('SettingsView'), 1574 obj = {}; 1575 1576 if ( ! library || ! attachmentsBrowserView ) { 1391 * @fires this.view#{this.id}:activate:{this._mode} 1392 * @fires this.view#{this.id}:activate 1393 * @fires this.view#{this.id}:deactivate:{this._mode} 1394 * @fires this.view#{this.id}:deactivate 1395 * 1396 * @returns {wp.media.controller.Region} Returns itself to allow chaining. 1397 */ 1398 mode: function( mode ) { 1399 if ( ! mode ) { 1400 return this._mode; 1401 } 1402 // Bail if we're trying to change to the current mode. 1403 if ( mode === this._mode ) { 1404 return this; 1405 } 1406 1407 /** 1408 * Region mode deactivation event. 1409 * 1410 * @event this.view#{this.id}:deactivate:{this._mode} 1411 * @event this.view#{this.id}:deactivate 1412 */ 1413 this.trigger('deactivate'); 1414 1415 this._mode = mode; 1416 this.render( mode ); 1417 1418 /** 1419 * Region mode activation event. 1420 * 1421 * @event this.view#{this.id}:activate:{this._mode} 1422 * @event this.view#{this.id}:activate 1423 */ 1424 this.trigger('activate'); 1425 return this; 1426 }, 1427 /** 1428 * Render a mode. 1429 * 1430 * @since 3.5.0 1431 * 1432 * @param {string} mode 1433 * 1434 * @fires this.view#{this.id}:create:{this._mode} 1435 * @fires this.view#{this.id}:create 1436 * @fires this.view#{this.id}:render:{this._mode} 1437 * @fires this.view#{this.id}:render 1438 * 1439 * @returns {wp.media.controller.Region} Returns itself to allow chaining 1440 */ 1441 render: function( mode ) { 1442 // If the mode isn't active, activate it. 1443 if ( mode && mode !== this._mode ) { 1444 return this.mode( mode ); 1445 } 1446 1447 var set = { view: null }, 1448 view; 1449 1450 /** 1451 * Create region view event. 1452 * 1453 * Region view creation takes place in an event callback on the frame. 1454 * 1455 * @event this.view#{this.id}:create:{this._mode} 1456 * @event this.view#{this.id}:create 1457 */ 1458 this.trigger( 'create', set ); 1459 view = set.view; 1460 1461 /** 1462 * Render region view event. 1463 * 1464 * Region view creation takes place in an event callback on the frame. 1465 * 1466 * @event this.view#{this.id}:create:{this._mode} 1467 * @event this.view#{this.id}:create 1468 */ 1469 this.trigger( 'render', view ); 1470 if ( view ) { 1471 this.set( view ); 1472 } 1473 return this; 1474 }, 1475 1476 /** 1477 * Get the region's view. 1478 * 1479 * @since 3.5.0 1480 * 1481 * @returns {wp.media.View} 1482 */ 1483 get: function() { 1484 return this.view.views.first( this.selector ); 1485 }, 1486 1487 /** 1488 * Set the region's view as a subview of the frame. 1489 * 1490 * @since 3.5.0 1491 * 1492 * @param {Array|Object} views 1493 * @param {Object} [options={}] 1494 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining 1495 */ 1496 set: function( views, options ) { 1497 if ( options ) { 1498 options.add = false; 1499 } 1500 return this.view.views.set( this.selector, views, options ); 1501 }, 1502 1503 /** 1504 * Trigger regional view events on the frame. 1505 * 1506 * @since 3.5.0 1507 * 1508 * @param {string} event 1509 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining. 1510 */ 1511 trigger: function( event ) { 1512 var base, args; 1513 1514 if ( ! this._mode ) { 1577 1515 return; 1578 1516 } 1579 1517 1580 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 1581 1582 obj[ collectionType ] = new SettingsView({ 1583 controller: this, 1584 model: library[ collectionType ], 1585 priority: 40 1586 }); 1587 1588 attachmentsBrowserView.sidebar.set( obj ); 1589 1590 if ( dragInfoText ) { 1591 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 1592 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 1593 priority: -40 1594 }) ); 1595 } 1596 1597 // Add the 'Reverse order' button to the toolbar. 1598 attachmentsBrowserView.toolbar.set( 'reverse', { 1599 text: l10n.reverseOrder, 1600 priority: 80, 1601 1602 click: function() { 1603 library.reset( library.toArray().reverse() ); 1604 } 1605 }); 1518 args = _.toArray( arguments ); 1519 base = this.id + ':' + event; 1520 1521 // Trigger `{this.id}:{event}:{this._mode}` event on the frame. 1522 args[0] = base + ':' + this._mode; 1523 this.view.trigger.apply( this.view, args ); 1524 1525 // Trigger `{this.id}:{event}` event on the frame. 1526 args[0] = base; 1527 this.view.trigger.apply( this.view, args ); 1528 return this; 1606 1529 } 1607 1530 }); 1608 1531 1609 module.exports = CollectionEdit; 1610 1611 1612 /***/ }), 1613 /* 36 */ 1614 /***/ (function(module, exports) { 1615 1616 /*globals wp, _ */ 1617 1618 /** 1619 * wp.media.controller.CollectionAdd 1620 * 1621 * A state for adding attachments to a collection (e.g. video playlist). 1622 * 1623 * @class 1624 * @augments wp.media.controller.Library 1625 * @augments wp.media.controller.State 1626 * @augments Backbone.Model 1627 * 1628 * @param {object} [attributes] The attributes hash passed to the state. 1629 * @param {string} [attributes.id=library] Unique identifier. 1630 * @param {string} attributes.title Title for the state. Displays in the frame's title region. 1631 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 1632 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1633 * If one is not supplied, a collection of attachments of the specified type will be created. 1634 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1635 * Accepts 'all', 'uploaded', or 'unattached'. 1636 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 1637 * @param {string} [attributes.content=upload] Initial mode for the content region. 1638 * Overridden by persistent user setting if 'contentUserSetting' is true. 1639 * @param {string} [attributes.router=browse] Initial mode for the router region. 1640 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 1641 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1642 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1643 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1644 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1645 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 1646 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1647 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 1648 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 1649 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 1650 */ 1651 var Selection = wp.media.model.Selection, 1652 Library = wp.media.controller.Library, 1653 CollectionAdd; 1654 1655 CollectionAdd = Library.extend({ 1656 defaults: _.defaults( { 1657 // Selection defaults. @see media.model.Selection 1658 multiple: 'add', 1659 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1660 filterable: 'uploaded', 1661 1662 priority: 100, 1663 syncSelection: false 1664 }, Library.prototype.defaults ), 1665 1666 /** 1667 * @since 3.9.0 1668 */ 1669 initialize: function() { 1670 var collectionType = this.get('collectionType'); 1671 1672 if ( 'video' === this.get( 'type' ) ) { 1673 collectionType = 'video-' + collectionType; 1674 } 1675 1676 this.set( 'id', collectionType + '-library' ); 1677 this.set( 'toolbar', collectionType + '-add' ); 1678 this.set( 'menu', collectionType ); 1679 1680 // If we haven't been provided a `library`, create a `Selection`. 1681 if ( ! this.get('library') ) { 1682 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 1683 } 1684 Library.prototype.initialize.apply( this, arguments ); 1685 }, 1686 1687 /** 1688 * @since 3.9.0 1689 */ 1690 activate: function() { 1691 var library = this.get('library'), 1692 editLibrary = this.get('editLibrary'), 1693 edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library'); 1694 1695 if ( editLibrary && editLibrary !== edit ) { 1696 library.unobserve( editLibrary ); 1697 } 1698 1699 // Accepts attachments that exist in the original library and 1700 // that do not exist in gallery's library. 1701 library.validator = function( attachment ) { 1702 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 1703 }; 1704 1705 // Reset the library to ensure that all attachments are re-added 1706 // to the collection. Do so silently, as calling `observe` will 1707 // trigger the `reset` event. 1708 library.reset( library.mirroring.models, { silent: true }); 1709 library.observe( edit ); 1710 this.set('editLibrary', edit); 1711 1712 Library.prototype.activate.apply( this, arguments ); 1713 } 1714 }); 1715 1716 module.exports = CollectionAdd; 1717 1718 1719 /***/ }), 1720 /* 37 */ 1721 /***/ (function(module, exports) { 1722 1723 /*globals wp, _ */ 1724 1725 /** 1726 * wp.media.controller.FeaturedImage 1727 * 1728 * A state for selecting a featured image for a post. 1729 * 1730 * @class 1731 * @augments wp.media.controller.Library 1732 * @augments wp.media.controller.State 1733 * @augments Backbone.Model 1734 * 1735 * @param {object} [attributes] The attributes hash passed to the state. 1736 * @param {string} [attributes.id=featured-image] Unique identifier. 1737 * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. 1738 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1739 * If one is not supplied, a collection of all images will be created. 1740 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1741 * @param {string} [attributes.content=upload] Initial mode for the content region. 1742 * Overridden by persistent user setting if 'contentUserSetting' is true. 1743 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1744 * @param {string} [attributes.router=browse] Initial mode for the router region. 1745 * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. 1746 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1747 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1748 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 1749 * Accepts 'all', 'uploaded', or 'unattached'. 1750 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1751 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1752 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1753 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1754 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1755 */ 1756 var Attachment = wp.media.model.Attachment, 1757 Library = wp.media.controller.Library, 1758 l10n = wp.media.view.l10n, 1759 FeaturedImage; 1760 1761 FeaturedImage = Library.extend({ 1762 defaults: _.defaults({ 1763 id: 'featured-image', 1764 title: l10n.setFeaturedImageTitle, 1765 multiple: false, 1766 filterable: 'uploaded', 1767 toolbar: 'featured-image', 1768 priority: 60, 1769 syncSelection: true 1770 }, Library.prototype.defaults ), 1771 1772 /** 1773 * @since 3.5.0 1774 */ 1775 initialize: function() { 1776 var library, comparator; 1777 1778 // If we haven't been provided a `library`, create a `Selection`. 1779 if ( ! this.get('library') ) { 1780 this.set( 'library', wp.media.query({ type: 'image' }) ); 1781 } 1782 1783 Library.prototype.initialize.apply( this, arguments ); 1784 1785 library = this.get('library'); 1786 comparator = library.comparator; 1787 1788 // Overload the library's comparator to push items that are not in 1789 // the mirrored query to the front of the aggregate collection. 1790 library.comparator = function( a, b ) { 1791 var aInQuery = !! this.mirroring.get( a.cid ), 1792 bInQuery = !! this.mirroring.get( b.cid ); 1793 1794 if ( ! aInQuery && bInQuery ) { 1795 return -1; 1796 } else if ( aInQuery && ! bInQuery ) { 1797 return 1; 1798 } else { 1799 return comparator.apply( this, arguments ); 1800 } 1801 }; 1802 1803 // Add all items in the selection to the library, so any featured 1804 // images that are not initially loaded still appear. 1805 library.observe( this.get('selection') ); 1806 }, 1807 1808 /** 1809 * @since 3.5.0 1810 */ 1811 activate: function() { 1812 this.updateSelection(); 1813 this.frame.on( 'open', this.updateSelection, this ); 1814 1815 Library.prototype.activate.apply( this, arguments ); 1816 }, 1817 1818 /** 1819 * @since 3.5.0 1820 */ 1821 deactivate: function() { 1822 this.frame.off( 'open', this.updateSelection, this ); 1823 1824 Library.prototype.deactivate.apply( this, arguments ); 1825 }, 1826 1827 /** 1828 * @since 3.5.0 1829 */ 1830 updateSelection: function() { 1831 var selection = this.get('selection'), 1832 id = wp.media.view.settings.post.featuredImageId, 1833 attachment; 1834 1835 if ( '' !== id && -1 !== id ) { 1836 attachment = Attachment.get( id ); 1837 attachment.fetch(); 1838 } 1839 1840 selection.reset( attachment ? [ attachment ] : [] ); 1841 } 1842 }); 1843 1844 module.exports = FeaturedImage; 1845 1846 1847 /***/ }), 1848 /* 38 */ 1849 /***/ (function(module, exports) { 1850 1532 module.exports = Region; 1533 1534 },{}],13:[function(require,module,exports){ 1851 1535 /*globals wp, _ */ 1852 1536 … … 1958 1642 module.exports = ReplaceImage; 1959 1643 1960 1961 /***/ }), 1962 /* 39 */ 1963 /***/ (function(module, exports) { 1964 1965 /*globals wp */ 1644 },{}],14:[function(require,module,exports){ 1645 /*globals _, Backbone */ 1966 1646 1967 1647 /** 1968 * wp.media.controller.EditImage 1969 * 1970 * A state for editing (cropping, etc.) an image. 1648 * wp.media.controller.StateMachine 1649 * 1650 * A state machine keeps track of state. It is in one state at a time, 1651 * and can change from one state to another. 1652 * 1653 * States are stored as models in a Backbone collection. 1654 * 1655 * @since 3.5.0 1971 1656 * 1972 1657 * @class 1973 * @augments wp.media.controller.State1974 1658 * @augments Backbone.Model 1975 * 1976 * @param {object} attributes The attributes hash passed to the state. 1977 * @param {wp.media.model.Attachment} attributes.model The attachment. 1978 * @param {string} [attributes.id=edit-image] Unique identifier. 1979 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 1980 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 1981 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 1982 * @param {string} [attributes.menu=false] Initial mode for the menu region. 1983 * @param {string} [attributes.url] Unused. @todo Consider removal. 1659 * @mixin 1660 * @mixes Backbone.Events 1661 * 1662 * @param {Array} states 1984 1663 */ 1985 var l10n = wp.media.view.l10n, 1986 EditImage; 1987 1988 EditImage = wp.media.controller.State.extend({ 1989 defaults: { 1990 id: 'edit-image', 1991 title: l10n.editImage, 1992 menu: false, 1993 toolbar: 'edit-image', 1994 content: 'edit-image', 1995 url: '' 1996 }, 1997 1998 /** 1999 * @since 3.9.0 2000 */ 2001 activate: function() { 2002 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar ); 2003 }, 2004 2005 /** 2006 * @since 3.9.0 2007 */ 2008 deactivate: function() { 2009 this.stopListening( this.frame ); 2010 }, 2011 2012 /** 2013 * @since 3.9.0 2014 */ 2015 toolbar: function() { 2016 var frame = this.frame, 2017 lastState = frame.lastState(), 2018 previous = lastState && lastState.id; 2019 2020 frame.toolbar.set( new wp.media.view.Toolbar({ 2021 controller: frame, 2022 items: { 2023 back: { 2024 style: 'primary', 2025 text: l10n.back, 2026 priority: 20, 2027 click: function() { 2028 if ( previous ) { 2029 frame.setState( previous ); 2030 } else { 2031 frame.close(); 2032 } 2033 } 2034 } 2035 } 2036 }) ); 1664 var StateMachine = function( states ) { 1665 // @todo This is dead code. The states collection gets created in media.view.Frame._createStates. 1666 this.states = new Backbone.Collection( states ); 1667 }; 1668 1669 // Use Backbone's self-propagating `extend` inheritance method. 1670 StateMachine.extend = Backbone.Model.extend; 1671 1672 _.extend( StateMachine.prototype, Backbone.Events, { 1673 /** 1674 * Fetch a state. 1675 * 1676 * If no `id` is provided, returns the active state. 1677 * 1678 * Implicitly creates states. 1679 * 1680 * Ensure that the `states` collection exists so the `StateMachine` 1681 * can be used as a mixin. 1682 * 1683 * @since 3.5.0 1684 * 1685 * @param {string} id 1686 * @returns {wp.media.controller.State} Returns a State model 1687 * from the StateMachine collection 1688 */ 1689 state: function( id ) { 1690 this.states = this.states || new Backbone.Collection(); 1691 1692 // Default to the active state. 1693 id = id || this._state; 1694 1695 if ( id && ! this.states.get( id ) ) { 1696 this.states.add({ id: id }); 1697 } 1698 return this.states.get( id ); 1699 }, 1700 1701 /** 1702 * Sets the active state. 1703 * 1704 * Bail if we're trying to select the current state, if we haven't 1705 * created the `states` collection, or are trying to select a state 1706 * that does not exist. 1707 * 1708 * @since 3.5.0 1709 * 1710 * @param {string} id 1711 * 1712 * @fires wp.media.controller.State#deactivate 1713 * @fires wp.media.controller.State#activate 1714 * 1715 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining 1716 */ 1717 setState: function( id ) { 1718 var previous = this.state(); 1719 1720 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 1721 return this; 1722 } 1723 1724 if ( previous ) { 1725 previous.trigger('deactivate'); 1726 this._lastState = previous.id; 1727 } 1728 1729 this._state = id; 1730 this.state().trigger('activate'); 1731 1732 return this; 1733 }, 1734 1735 /** 1736 * Returns the previous active state. 1737 * 1738 * Call the `state()` method with no parameters to retrieve the current 1739 * active state. 1740 * 1741 * @since 3.5.0 1742 * 1743 * @returns {wp.media.controller.State} Returns a State model 1744 * from the StateMachine collection 1745 */ 1746 lastState: function() { 1747 if ( this._lastState ) { 1748 return this.state( this._lastState ); 1749 } 2037 1750 } 2038 1751 }); 2039 1752 2040 module.exports = EditImage; 2041 2042 2043 /***/ }), 2044 /* 40 */ 2045 /***/ (function(module, exports) { 2046 2047 /*globals wp, _ */ 2048 2049 /** 2050 * wp.media.controller.MediaLibrary 2051 * 2052 * @class 2053 * @augments wp.media.controller.Library 2054 * @augments wp.media.controller.State 2055 * @augments Backbone.Model 2056 */ 2057 var Library = wp.media.controller.Library, 2058 MediaLibrary; 2059 2060 MediaLibrary = Library.extend({ 2061 defaults: _.defaults({ 2062 // Attachments browser defaults. @see media.view.AttachmentsBrowser 2063 filterable: 'uploaded', 2064 2065 displaySettings: false, 2066 priority: 80, 2067 syncSelection: false 2068 }, Library.prototype.defaults ), 2069 2070 /** 2071 * @since 3.9.0 2072 * 2073 * @param options 2074 */ 2075 initialize: function( options ) { 2076 this.media = options.media; 2077 this.type = options.type; 2078 this.set( 'library', wp.media.query({ type: this.type }) ); 2079 2080 Library.prototype.initialize.apply( this, arguments ); 2081 }, 2082 2083 /** 2084 * @since 3.9.0 2085 */ 2086 activate: function() { 2087 // @todo this should use this.frame. 2088 if ( wp.media.frame.lastMime ) { 2089 this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) ); 2090 delete wp.media.frame.lastMime; 2091 } 2092 Library.prototype.activate.apply( this, arguments ); 2093 } 2094 }); 2095 2096 module.exports = MediaLibrary; 2097 2098 2099 /***/ }), 2100 /* 41 */ 2101 /***/ (function(module, exports) { 2102 2103 /*globals wp, _, Backbone */ 2104 2105 /** 2106 * wp.media.controller.Embed 2107 * 2108 * A state for embedding media from a URL. 2109 * 2110 * @class 2111 * @augments wp.media.controller.State 2112 * @augments Backbone.Model 2113 * 2114 * @param {object} attributes The attributes hash passed to the state. 2115 * @param {string} [attributes.id=embed] Unique identifier. 2116 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. 2117 * @param {string} [attributes.content=embed] Initial mode for the content region. 2118 * @param {string} [attributes.menu=default] Initial mode for the menu region. 2119 * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. 2120 * @param {string} [attributes.menu=false] Initial mode for the menu region. 2121 * @param {int} [attributes.priority=120] The priority for the state link in the media menu. 2122 * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. 2123 * @param {string} [attributes.url] The embed URL. 2124 * @param {object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. 2125 */ 2126 var l10n = wp.media.view.l10n, 2127 $ = Backbone.$, 2128 Embed; 2129 2130 Embed = wp.media.controller.State.extend({ 2131 defaults: { 2132 id: 'embed', 2133 title: l10n.insertFromUrlTitle, 2134 content: 'embed', 2135 menu: 'default', 2136 toolbar: 'main-embed', 2137 priority: 120, 2138 type: 'link', 2139 url: '', 2140 metadata: {} 2141 }, 2142 2143 // The amount of time used when debouncing the scan. 2144 sensitivity: 200, 2145 2146 initialize: function(options) { 2147 this.metadata = options.metadata; 2148 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity ); 2149 this.props = new Backbone.Model( this.metadata || { url: '' }); 2150 this.props.on( 'change:url', this.debouncedScan, this ); 2151 this.props.on( 'change:url', this.refresh, this ); 2152 this.on( 'scan', this.scanImage, this ); 2153 }, 2154 2155 /** 2156 * Trigger a scan of the embedded URL's content for metadata required to embed. 2157 * 2158 * @fires wp.media.controller.Embed#scan 2159 */ 2160 scan: function() { 2161 var scanners, 2162 embed = this, 2163 attributes = { 2164 type: 'link', 2165 scanners: [] 2166 }; 2167 2168 // Scan is triggered with the list of `attributes` to set on the 2169 // state, useful for the 'type' attribute and 'scanners' attribute, 2170 // an array of promise objects for asynchronous scan operations. 2171 if ( this.props.get('url') ) { 2172 this.trigger( 'scan', attributes ); 2173 } 2174 2175 if ( attributes.scanners.length ) { 2176 scanners = attributes.scanners = $.when.apply( $, attributes.scanners ); 2177 scanners.always( function() { 2178 if ( embed.get('scanners') === scanners ) { 2179 embed.set( 'loading', false ); 2180 } 2181 }); 2182 } else { 2183 attributes.scanners = null; 2184 } 2185 2186 attributes.loading = !! attributes.scanners; 2187 this.set( attributes ); 2188 }, 2189 /** 2190 * Try scanning the embed as an image to discover its dimensions. 2191 * 2192 * @param {Object} attributes 2193 */ 2194 scanImage: function( attributes ) { 2195 var frame = this.frame, 2196 state = this, 2197 url = this.props.get('url'), 2198 image = new Image(), 2199 deferred = $.Deferred(); 2200 2201 attributes.scanners.push( deferred.promise() ); 2202 2203 // Try to load the image and find its width/height. 2204 image.onload = function() { 2205 deferred.resolve(); 2206 2207 if ( state !== frame.state() || url !== state.props.get('url') ) { 2208 return; 2209 } 2210 2211 state.set({ 2212 type: 'image' 2213 }); 2214 2215 state.props.set({ 2216 width: image.width, 2217 height: image.height 2218 }); 2219 }; 2220 2221 image.onerror = deferred.reject; 2222 image.src = url; 2223 }, 2224 2225 refresh: function() { 2226 this.frame.toolbar.get().refresh(); 2227 }, 2228 2229 reset: function() { 2230 this.props.clear().set({ url: '' }); 2231 2232 if ( this.active ) { 2233 this.refresh(); 2234 } 2235 } 2236 }); 2237 2238 module.exports = Embed; 2239 2240 2241 /***/ }), 2242 /* 42 */ 2243 /***/ (function(module, exports) { 2244 2245 /*globals wp, _, Backbone */ 2246 2247 /** 2248 * wp.media.controller.Cropper 2249 * 2250 * A state for cropping an image. 2251 * 2252 * @class 2253 * @augments wp.media.controller.State 2254 * @augments Backbone.Model 2255 */ 2256 var l10n = wp.media.view.l10n, 2257 Cropper; 2258 2259 Cropper = wp.media.controller.State.extend({ 2260 defaults: { 2261 id: 'cropper', 2262 title: l10n.cropImage, 2263 // Region mode defaults. 2264 toolbar: 'crop', 2265 content: 'crop', 2266 router: false, 2267 2268 canSkipCrop: false 2269 }, 2270 2271 activate: function() { 2272 this.frame.on( 'content:create:crop', this.createCropContent, this ); 2273 this.frame.on( 'close', this.removeCropper, this ); 2274 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 2275 }, 2276 2277 deactivate: function() { 2278 this.frame.toolbar.mode('browse'); 2279 }, 2280 2281 createCropContent: function() { 2282 this.cropperView = new wp.media.view.Cropper({ 2283 controller: this, 2284 attachment: this.get('selection').first() 2285 }); 2286 this.cropperView.on('image-loaded', this.createCropToolbar, this); 2287 this.frame.content.set(this.cropperView); 2288 2289 }, 2290 removeCropper: function() { 2291 this.imgSelect.cancelSelection(); 2292 this.imgSelect.setOptions({remove: true}); 2293 this.imgSelect.update(); 2294 this.cropperView.remove(); 2295 }, 2296 createCropToolbar: function() { 2297 var canSkipCrop, toolbarOptions; 2298 2299 canSkipCrop = this.get('canSkipCrop') || false; 2300 2301 toolbarOptions = { 2302 controller: this.frame, 2303 items: { 2304 insert: { 2305 style: 'primary', 2306 text: l10n.cropImage, 2307 priority: 80, 2308 requires: { library: false, selection: false }, 2309 2310 click: function() { 2311 var controller = this.controller, 2312 selection; 2313 2314 selection = controller.state().get('selection').first(); 2315 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 2316 2317 this.$el.text(l10n.cropping); 2318 this.$el.attr('disabled', true); 2319 2320 controller.state().doCrop( selection ).done( function( croppedImage ) { 2321 controller.trigger('cropped', croppedImage ); 2322 controller.close(); 2323 }).fail( function() { 2324 controller.trigger('content:error:crop'); 2325 }); 2326 } 2327 } 2328 } 2329 }; 2330 2331 if ( canSkipCrop ) { 2332 _.extend( toolbarOptions.items, { 2333 skip: { 2334 style: 'secondary', 2335 text: l10n.skipCropping, 2336 priority: 70, 2337 requires: { library: false, selection: false }, 2338 click: function() { 2339 var selection = this.controller.state().get('selection').first(); 2340 this.controller.state().cropperView.remove(); 2341 this.controller.trigger('skippedcrop', selection); 2342 this.controller.close(); 2343 } 2344 } 2345 }); 2346 } 2347 2348 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 2349 }, 2350 2351 doCrop: function( attachment ) { 2352 return wp.ajax.post( 'custom-header-crop', { 2353 nonce: attachment.get('nonces').edit, 2354 id: attachment.get('id'), 2355 cropDetails: attachment.get('cropDetails') 2356 } ); 2357 } 2358 }); 2359 2360 module.exports = Cropper; 2361 2362 2363 /***/ }), 2364 /* 43 */, 2365 /* 44 */, 2366 /* 45 */ 2367 /***/ (function(module, exports) { 2368 2369 /*globals wp */ 2370 2371 /** 2372 * wp.media.View 2373 * 2374 * The base view class for media. 2375 * 2376 * Undelegating events, removing events from the model, and 2377 * removing events from the controller mirror the code for 2378 * `Backbone.View.dispose` in Backbone 0.9.8 development. 2379 * 2380 * This behavior has since been removed, and should not be used 2381 * outside of the media manager. 2382 * 2383 * @class 2384 * @augments wp.Backbone.View 2385 * @augments Backbone.View 2386 */ 2387 var View = wp.Backbone.View.extend({ 2388 constructor: function( options ) { 2389 if ( options && options.controller ) { 2390 this.controller = options.controller; 2391 } 2392 wp.Backbone.View.apply( this, arguments ); 2393 }, 2394 /** 2395 * @todo The internal comment mentions this might have been a stop-gap 2396 * before Backbone 0.9.8 came out. Figure out if Backbone core takes 2397 * care of this in Backbone.View now. 2398 * 2399 * @returns {wp.media.View} Returns itself to allow chaining 2400 */ 2401 dispose: function() { 2402 // Undelegating events, removing events from the model, and 2403 // removing events from the controller mirror the code for 2404 // `Backbone.View.dispose` in Backbone 0.9.8 development. 2405 this.undelegateEvents(); 2406 2407 if ( this.model && this.model.off ) { 2408 this.model.off( null, null, this ); 2409 } 2410 2411 if ( this.collection && this.collection.off ) { 2412 this.collection.off( null, null, this ); 2413 } 2414 2415 // Unbind controller events. 2416 if ( this.controller && this.controller.off ) { 2417 this.controller.off( null, null, this ); 2418 } 2419 2420 return this; 2421 }, 2422 /** 2423 * @returns {wp.media.View} Returns itself to allow chaining 2424 */ 2425 remove: function() { 2426 this.dispose(); 2427 /** 2428 * call 'remove' directly on the parent class 2429 */ 2430 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 2431 } 2432 }); 2433 2434 module.exports = View; 2435 2436 2437 /***/ }), 2438 /* 46 */ 2439 /***/ (function(module, exports) { 2440 2441 /*globals _, Backbone */ 2442 2443 /** 2444 * wp.media.view.Frame 2445 * 2446 * A frame is a composite view consisting of one or more regions and one or more 2447 * states. 2448 * 2449 * @see wp.media.controller.State 2450 * @see wp.media.controller.Region 2451 * 2452 * @class 2453 * @augments wp.media.View 2454 * @augments wp.Backbone.View 2455 * @augments Backbone.View 2456 * @mixes wp.media.controller.StateMachine 2457 */ 2458 var Frame = wp.media.View.extend({ 2459 initialize: function() { 2460 _.defaults( this.options, { 2461 mode: [ 'select' ] 2462 }); 2463 this._createRegions(); 2464 this._createStates(); 2465 this._createModes(); 2466 }, 2467 2468 _createRegions: function() { 2469 // Clone the regions array. 2470 this.regions = this.regions ? this.regions.slice() : []; 2471 2472 // Initialize regions. 2473 _.each( this.regions, function( region ) { 2474 this[ region ] = new wp.media.controller.Region({ 2475 view: this, 2476 id: region, 2477 selector: '.media-frame-' + region 2478 }); 2479 }, this ); 2480 }, 2481 /** 2482 * Create the frame's states. 2483 * 2484 * @see wp.media.controller.State 2485 * @see wp.media.controller.StateMachine 2486 * 2487 * @fires wp.media.controller.State#ready 2488 */ 2489 _createStates: function() { 2490 // Create the default `states` collection. 2491 this.states = new Backbone.Collection( null, { 2492 model: wp.media.controller.State 2493 }); 2494 2495 // Ensure states have a reference to the frame. 2496 this.states.on( 'add', function( model ) { 2497 model.frame = this; 2498 model.trigger('ready'); 2499 }, this ); 2500 2501 if ( this.options.states ) { 2502 this.states.add( this.options.states ); 2503 } 2504 }, 2505 2506 /** 2507 * A frame can be in a mode or multiple modes at one time. 2508 * 2509 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 2510 */ 2511 _createModes: function() { 2512 // Store active "modes" that the frame is in. Unrelated to region modes. 2513 this.activeModes = new Backbone.Collection(); 2514 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 2515 2516 _.each( this.options.mode, function( mode ) { 2517 this.activateMode( mode ); 2518 }, this ); 2519 }, 2520 /** 2521 * Reset all states on the frame to their defaults. 2522 * 2523 * @returns {wp.media.view.Frame} Returns itself to allow chaining 2524 */ 2525 reset: function() { 2526 this.states.invoke( 'trigger', 'reset' ); 2527 return this; 2528 }, 2529 /** 2530 * Map activeMode collection events to the frame. 2531 */ 2532 triggerModeEvents: function( model, collection, options ) { 2533 var collectionEvent, 2534 modeEventMap = { 2535 add: 'activate', 2536 remove: 'deactivate' 2537 }, 2538 eventToTrigger; 2539 // Probably a better way to do this. 2540 _.each( options, function( value, key ) { 2541 if ( value ) { 2542 collectionEvent = key; 2543 } 2544 } ); 2545 2546 if ( ! _.has( modeEventMap, collectionEvent ) ) { 2547 return; 2548 } 2549 2550 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 2551 this.trigger( eventToTrigger ); 2552 }, 2553 /** 2554 * Activate a mode on the frame. 2555 * 2556 * @param string mode Mode ID. 2557 * @returns {this} Returns itself to allow chaining. 2558 */ 2559 activateMode: function( mode ) { 2560 // Bail if the mode is already active. 2561 if ( this.isModeActive( mode ) ) { 2562 return; 2563 } 2564 this.activeModes.add( [ { id: mode } ] ); 2565 // Add a CSS class to the frame so elements can be styled for the mode. 2566 this.$el.addClass( 'mode-' + mode ); 2567 2568 return this; 2569 }, 2570 /** 2571 * Deactivate a mode on the frame. 2572 * 2573 * @param string mode Mode ID. 2574 * @returns {this} Returns itself to allow chaining. 2575 */ 2576 deactivateMode: function( mode ) { 2577 // Bail if the mode isn't active. 2578 if ( ! this.isModeActive( mode ) ) { 2579 return this; 2580 } 2581 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 2582 this.$el.removeClass( 'mode-' + mode ); 2583 /** 2584 * Frame mode deactivation event. 2585 * 2586 * @event this#{mode}:deactivate 2587 */ 2588 this.trigger( mode + ':deactivate' ); 2589 2590 return this; 2591 }, 2592 /** 2593 * Check if a mode is enabled on the frame. 2594 * 2595 * @param string mode Mode ID. 2596 * @return bool 2597 */ 2598 isModeActive: function( mode ) { 2599 return Boolean( this.activeModes.where( { id: mode } ).length ); 2600 } 2601 }); 2602 2603 // Make the `Frame` a `StateMachine`. 2604 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 2605 2606 module.exports = Frame; 2607 2608 2609 /***/ }), 2610 /* 47 */ 2611 /***/ (function(module, exports) { 2612 2613 /*globals wp, _, jQuery */ 2614 2615 /** 2616 * wp.media.view.MediaFrame 2617 * 2618 * The frame used to create the media modal. 2619 * 2620 * @class 2621 * @augments wp.media.view.Frame 2622 * @augments wp.media.View 2623 * @augments wp.Backbone.View 2624 * @augments Backbone.View 2625 * @mixes wp.media.controller.StateMachine 2626 */ 2627 var Frame = wp.media.view.Frame, 2628 $ = jQuery, 2629 MediaFrame; 2630 2631 MediaFrame = Frame.extend({ 2632 className: 'media-frame', 2633 template: wp.template('media-frame'), 2634 regions: ['menu','title','content','toolbar','router'], 2635 2636 events: { 2637 'click div.media-frame-title h1': 'toggleMenu' 2638 }, 2639 2640 /** 2641 * @global wp.Uploader 2642 */ 2643 initialize: function() { 2644 Frame.prototype.initialize.apply( this, arguments ); 2645 2646 _.defaults( this.options, { 2647 title: '', 2648 modal: true, 2649 uploader: true 2650 }); 2651 2652 // Ensure core UI is enabled. 2653 this.$el.addClass('wp-core-ui'); 2654 2655 // Initialize modal container view. 2656 if ( this.options.modal ) { 2657 this.modal = new wp.media.view.Modal({ 2658 controller: this, 2659 title: this.options.title 2660 }); 2661 2662 this.modal.content( this ); 2663 } 2664 2665 // Force the uploader off if the upload limit has been exceeded or 2666 // if the browser isn't supported. 2667 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 2668 this.options.uploader = false; 2669 } 2670 2671 // Initialize window-wide uploader. 2672 if ( this.options.uploader ) { 2673 this.uploader = new wp.media.view.UploaderWindow({ 2674 controller: this, 2675 uploader: { 2676 dropzone: this.modal ? this.modal.$el : this.$el, 2677 container: this.$el 2678 } 2679 }); 2680 this.views.set( '.media-frame-uploader', this.uploader ); 2681 } 2682 2683 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 2684 2685 // Bind default title creation. 2686 this.on( 'title:create:default', this.createTitle, this ); 2687 this.title.mode('default'); 2688 2689 this.on( 'title:render', function( view ) { 2690 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' ); 2691 }); 2692 2693 // Bind default menu. 2694 this.on( 'menu:create:default', this.createMenu, this ); 2695 }, 2696 /** 2697 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 2698 */ 2699 render: function() { 2700 // Activate the default state if no active state exists. 2701 if ( ! this.state() && this.options.state ) { 2702 this.setState( this.options.state ); 2703 } 2704 /** 2705 * call 'render' directly on the parent class 2706 */ 2707 return Frame.prototype.render.apply( this, arguments ); 2708 }, 2709 /** 2710 * @param {Object} title 2711 * @this wp.media.controller.Region 2712 */ 2713 createTitle: function( title ) { 2714 title.view = new wp.media.View({ 2715 controller: this, 2716 tagName: 'h1' 2717 }); 2718 }, 2719 /** 2720 * @param {Object} menu 2721 * @this wp.media.controller.Region 2722 */ 2723 createMenu: function( menu ) { 2724 menu.view = new wp.media.view.Menu({ 2725 controller: this 2726 }); 2727 }, 2728 2729 toggleMenu: function() { 2730 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 2731 }, 2732 2733 /** 2734 * @param {Object} toolbar 2735 * @this wp.media.controller.Region 2736 */ 2737 createToolbar: function( toolbar ) { 2738 toolbar.view = new wp.media.view.Toolbar({ 2739 controller: this 2740 }); 2741 }, 2742 /** 2743 * @param {Object} router 2744 * @this wp.media.controller.Region 2745 */ 2746 createRouter: function( router ) { 2747 router.view = new wp.media.view.Router({ 2748 controller: this 2749 }); 2750 }, 2751 /** 2752 * @param {Object} options 2753 */ 2754 createIframeStates: function( options ) { 2755 var settings = wp.media.view.settings, 2756 tabs = settings.tabs, 2757 tabUrl = settings.tabUrl, 2758 $postId; 2759 2760 if ( ! tabs || ! tabUrl ) { 2761 return; 2762 } 2763 2764 // Add the post ID to the tab URL if it exists. 2765 $postId = $('#post_ID'); 2766 if ( $postId.length ) { 2767 tabUrl += '&post_id=' + $postId.val(); 2768 } 2769 2770 // Generate the tab states. 2771 _.each( tabs, function( title, id ) { 2772 this.state( 'iframe:' + id ).set( _.defaults({ 2773 tab: id, 2774 src: tabUrl + '&tab=' + id, 2775 title: title, 2776 content: 'iframe', 2777 menu: 'default' 2778 }, options ) ); 2779 }, this ); 2780 2781 this.on( 'content:create:iframe', this.iframeContent, this ); 2782 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 2783 this.on( 'menu:render:default', this.iframeMenu, this ); 2784 this.on( 'open', this.hijackThickbox, this ); 2785 this.on( 'close', this.restoreThickbox, this ); 2786 }, 2787 2788 /** 2789 * @param {Object} content 2790 * @this wp.media.controller.Region 2791 */ 2792 iframeContent: function( content ) { 2793 this.$el.addClass('hide-toolbar'); 2794 content.view = new wp.media.view.Iframe({ 2795 controller: this 2796 }); 2797 }, 2798 2799 iframeContentCleanup: function() { 2800 this.$el.removeClass('hide-toolbar'); 2801 }, 2802 2803 iframeMenu: function( view ) { 2804 var views = {}; 2805 2806 if ( ! view ) { 2807 return; 2808 } 2809 2810 _.each( wp.media.view.settings.tabs, function( title, id ) { 2811 views[ 'iframe:' + id ] = { 2812 text: this.state( 'iframe:' + id ).get('title'), 2813 priority: 200 2814 }; 2815 }, this ); 2816 2817 view.set( views ); 2818 }, 2819 2820 hijackThickbox: function() { 2821 var frame = this; 2822 2823 if ( ! window.tb_remove || this._tb_remove ) { 2824 return; 2825 } 2826 2827 this._tb_remove = window.tb_remove; 2828 window.tb_remove = function() { 2829 frame.close(); 2830 frame.reset(); 2831 frame.setState( frame.options.state ); 2832 frame._tb_remove.call( window ); 2833 }; 2834 }, 2835 2836 restoreThickbox: function() { 2837 if ( ! this._tb_remove ) { 2838 return; 2839 } 2840 2841 window.tb_remove = this._tb_remove; 2842 delete this._tb_remove; 2843 } 2844 }); 2845 2846 // Map some of the modal's methods to the frame. 2847 _.each(['open','close','attach','detach','escape'], function( method ) { 2848 /** 2849 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 2850 */ 2851 MediaFrame.prototype[ method ] = function() { 2852 if ( this.modal ) { 2853 this.modal[ method ].apply( this.modal, arguments ); 2854 } 1753 // Map all event binding and triggering on a StateMachine to its `states` collection. 1754 _.each([ 'on', 'off', 'trigger' ], function( method ) { 1755 /** 1756 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 1757 */ 1758 StateMachine.prototype[ method ] = function() { 1759 // Ensure that the `states` collection exists so the `StateMachine` 1760 // can be used as a mixin. 1761 this.states = this.states || new Backbone.Collection(); 1762 // Forward the method to the `states` collection. 1763 this.states[ method ].apply( this.states, arguments ); 2855 1764 return this; 2856 1765 }; 2857 1766 }); 2858 1767 2859 module.exports = MediaFrame; 2860 2861 2862 /***/ }), 2863 /* 48 */ 2864 /***/ (function(module, exports) { 2865 2866 /*globals wp, _ */ 1768 module.exports = StateMachine; 1769 1770 },{}],15:[function(require,module,exports){ 1771 /*globals _, Backbone */ 2867 1772 2868 1773 /** 2869 * wp.media.view.MediaFrame.Select 2870 * 2871 * A frame for selecting an item or items from the media library. 1774 * wp.media.controller.State 1775 * 1776 * A state is a step in a workflow that when set will trigger the controllers 1777 * for the regions to be updated as specified in the frame. 1778 * 1779 * A state has an event-driven lifecycle: 1780 * 1781 * 'ready' triggers when a state is added to a state machine's collection. 1782 * 'activate' triggers when a state is activated by a state machine. 1783 * 'deactivate' triggers when a state is deactivated by a state machine. 1784 * 'reset' is not triggered automatically. It should be invoked by the 1785 * proper controller to reset the state to its default. 2872 1786 * 2873 1787 * @class 2874 * @augments wp.media.view.MediaFrame 2875 * @augments wp.media.view.Frame 2876 * @augments wp.media.View 2877 * @augments wp.Backbone.View 2878 * @augments Backbone.View 2879 * @mixes wp.media.controller.StateMachine 1788 * @augments Backbone.Model 2880 1789 */ 2881 2882 var MediaFrame = wp.media.view.MediaFrame, 2883 l10n = wp.media.view.l10n, 2884 Select; 2885 2886 Select = MediaFrame.extend({ 2887 initialize: function() { 2888 // Call 'initialize' directly on the parent class. 2889 MediaFrame.prototype.initialize.apply( this, arguments ); 2890 2891 _.defaults( this.options, { 2892 selection: [], 2893 library: {}, 2894 multiple: false, 2895 state: 'library' 2896 }); 2897 2898 this.createSelection(); 2899 this.createStates(); 2900 this.bindHandlers(); 2901 }, 2902 2903 /** 2904 * Attach a selection collection to the frame. 1790 var State = Backbone.Model.extend({ 1791 /** 1792 * Constructor. 2905 1793 * 2906 * A selection is a collection of attachments used for a specific purpose 2907 * by a media frame. e.g. Selecting an attachment (or many) to insert into 2908 * post content. 1794 * @since 3.5.0 1795 */ 1796 constructor: function() { 1797 this.on( 'activate', this._preActivate, this ); 1798 this.on( 'activate', this.activate, this ); 1799 this.on( 'activate', this._postActivate, this ); 1800 this.on( 'deactivate', this._deactivate, this ); 1801 this.on( 'deactivate', this.deactivate, this ); 1802 this.on( 'reset', this.reset, this ); 1803 this.on( 'ready', this._ready, this ); 1804 this.on( 'ready', this.ready, this ); 1805 /** 1806 * Call parent constructor with passed arguments 1807 */ 1808 Backbone.Model.apply( this, arguments ); 1809 this.on( 'change:menu', this._updateMenu, this ); 1810 }, 1811 /** 1812 * Ready event callback. 2909 1813 * 2910 * @see media.model.Selection 2911 */ 2912 createSelection: function() { 2913 var selection = this.options.selection; 2914 2915 if ( ! (selection instanceof wp.media.model.Selection) ) { 2916 this.options.selection = new wp.media.model.Selection( selection, { 2917 multiple: this.options.multiple 2918 }); 2919 } 2920 2921 this._selection = { 2922 attachments: new wp.media.model.Attachments(), 2923 difference: [] 2924 }; 2925 }, 2926 2927 /** 2928 * Create the default states on the frame. 2929 */ 2930 createStates: function() { 2931 var options = this.options; 2932 2933 if ( this.options.states ) { 1814 * @abstract 1815 * @since 3.5.0 1816 */ 1817 ready: function() {}, 1818 1819 /** 1820 * Activate event callback. 1821 * 1822 * @abstract 1823 * @since 3.5.0 1824 */ 1825 activate: function() {}, 1826 1827 /** 1828 * Deactivate event callback. 1829 * 1830 * @abstract 1831 * @since 3.5.0 1832 */ 1833 deactivate: function() {}, 1834 1835 /** 1836 * Reset event callback. 1837 * 1838 * @abstract 1839 * @since 3.5.0 1840 */ 1841 reset: function() {}, 1842 1843 /** 1844 * @access private 1845 * @since 3.5.0 1846 */ 1847 _ready: function() { 1848 this._updateMenu(); 1849 }, 1850 1851 /** 1852 * @access private 1853 * @since 3.5.0 1854 */ 1855 _preActivate: function() { 1856 this.active = true; 1857 }, 1858 1859 /** 1860 * @access private 1861 * @since 3.5.0 1862 */ 1863 _postActivate: function() { 1864 this.on( 'change:menu', this._menu, this ); 1865 this.on( 'change:titleMode', this._title, this ); 1866 this.on( 'change:content', this._content, this ); 1867 this.on( 'change:toolbar', this._toolbar, this ); 1868 1869 this.frame.on( 'title:render:default', this._renderTitle, this ); 1870 1871 this._title(); 1872 this._menu(); 1873 this._toolbar(); 1874 this._content(); 1875 this._router(); 1876 }, 1877 1878 /** 1879 * @access private 1880 * @since 3.5.0 1881 */ 1882 _deactivate: function() { 1883 this.active = false; 1884 1885 this.frame.off( 'title:render:default', this._renderTitle, this ); 1886 1887 this.off( 'change:menu', this._menu, this ); 1888 this.off( 'change:titleMode', this._title, this ); 1889 this.off( 'change:content', this._content, this ); 1890 this.off( 'change:toolbar', this._toolbar, this ); 1891 }, 1892 1893 /** 1894 * @access private 1895 * @since 3.5.0 1896 */ 1897 _title: function() { 1898 this.frame.title.render( this.get('titleMode') || 'default' ); 1899 }, 1900 1901 /** 1902 * @access private 1903 * @since 3.5.0 1904 */ 1905 _renderTitle: function( view ) { 1906 view.$el.text( this.get('title') || '' ); 1907 }, 1908 1909 /** 1910 * @access private 1911 * @since 3.5.0 1912 */ 1913 _router: function() { 1914 var router = this.frame.router, 1915 mode = this.get('router'), 1916 view; 1917 1918 this.frame.$el.toggleClass( 'hide-router', ! mode ); 1919 if ( ! mode ) { 2934 1920 return; 2935 1921 } 2936 1922 2937 // Add the default states. 2938 this.states.add([ 2939 // Main states. 2940 new wp.media.controller.Library({ 2941 library: wp.media.query( options.library ), 2942 multiple: options.multiple, 2943 title: options.title, 2944 priority: 20 2945 }) 2946 ]); 2947 }, 2948 2949 /** 2950 * Bind region mode event callbacks. 1923 this.frame.router.render( mode ); 1924 1925 view = router.get(); 1926 if ( view && view.select ) { 1927 view.select( this.frame.content.mode() ); 1928 } 1929 }, 1930 1931 /** 1932 * @access private 1933 * @since 3.5.0 1934 */ 1935 _menu: function() { 1936 var menu = this.frame.menu, 1937 mode = this.get('menu'), 1938 view; 1939 1940 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 1941 if ( ! mode ) { 1942 return; 1943 } 1944 1945 menu.mode( mode ); 1946 1947 view = menu.get(); 1948 if ( view && view.select ) { 1949 view.select( this.id ); 1950 } 1951 }, 1952 1953 /** 1954 * @access private 1955 * @since 3.5.0 1956 */ 1957 _updateMenu: function() { 1958 var previous = this.previous('menu'), 1959 menu = this.get('menu'); 1960 1961 if ( previous ) { 1962 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 1963 } 1964 1965 if ( menu ) { 1966 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 1967 } 1968 }, 1969 1970 /** 1971 * Create a view in the media menu for the state. 2951 1972 * 2952 * @see media.controller.Region.render 2953 */ 2954 bindHandlers: function() { 2955 this.on( 'router:create:browse', this.createRouter, this ); 2956 this.on( 'router:render:browse', this.browseRouter, this ); 2957 this.on( 'content:create:browse', this.browseContent, this ); 2958 this.on( 'content:render:upload', this.uploadContent, this ); 2959 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 2960 }, 2961 2962 /** 2963 * Render callback for the router region in the `browse` mode. 1973 * @access private 1974 * @since 3.5.0 2964 1975 * 2965 * @param {wp.media.view.Router} routerView 2966 */ 2967 browseRouter: function( routerView ) { 2968 routerView.set({ 2969 upload: { 2970 text: l10n.uploadFilesTitle, 2971 priority: 20 2972 }, 2973 browse: { 2974 text: l10n.mediaLibraryTitle, 2975 priority: 40 1976 * @param {media.view.Menu} view The menu view. 1977 */ 1978 _renderMenu: function( view ) { 1979 var menuItem = this.get('menuItem'), 1980 title = this.get('title'), 1981 priority = this.get('priority'); 1982 1983 if ( ! menuItem && title ) { 1984 menuItem = { text: title }; 1985 1986 if ( priority ) { 1987 menuItem.priority = priority; 2976 1988 } 2977 }); 2978 }, 2979 2980 /** 2981 * Render callback for the content region in the `browse` mode. 2982 * 2983 * @param {wp.media.controller.Region} contentRegion 2984 */ 2985 browseContent: function( contentRegion ) { 2986 var state = this.state(); 2987 2988 this.$el.removeClass('hide-toolbar'); 2989 2990 // Browse our library of attachments. 2991 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 2992 controller: this, 2993 collection: state.get('library'), 2994 selection: state.get('selection'), 2995 model: state, 2996 sortable: state.get('sortable'), 2997 search: state.get('searchable'), 2998 filters: state.get('filterable'), 2999 date: state.get('date'), 3000 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 3001 dragInfo: state.get('dragInfo'), 3002 3003 idealColumnWidth: state.get('idealColumnWidth'), 3004 suggestedWidth: state.get('suggestedWidth'), 3005 suggestedHeight: state.get('suggestedHeight'), 3006 3007 AttachmentView: state.get('AttachmentView') 3008 }); 3009 }, 3010 3011 /** 3012 * Render callback for the content region in the `upload` mode. 3013 */ 3014 uploadContent: function() { 3015 this.$el.removeClass( 'hide-toolbar' ); 3016 this.content.set( new wp.media.view.UploaderInline({ 3017 controller: this 3018 }) ); 3019 }, 3020 3021 /** 3022 * Toolbars 3023 * 3024 * @param {Object} toolbar 3025 * @param {Object} [options={}] 3026 * @this wp.media.controller.Region 3027 */ 3028 createSelectToolbar: function( toolbar, options ) { 3029 options = options || this.options.button || {}; 3030 options.controller = this; 3031 3032 toolbar.view = new wp.media.view.Toolbar.Select( options ); 1989 } 1990 1991 if ( ! menuItem ) { 1992 return; 1993 } 1994 1995 view.set( this.id, menuItem ); 3033 1996 } 3034 1997 }); 3035 1998 3036 module.exports = Select; 3037 3038 3039 /***/ }), 3040 /* 49 */ 3041 /***/ (function(module, exports) { 3042 3043 /*globals wp, _ */ 1999 _.each(['toolbar','content'], function( region ) { 2000 /** 2001 * @access private 2002 */ 2003 State.prototype[ '_' + region ] = function() { 2004 var mode = this.get( region ); 2005 if ( mode ) { 2006 this.frame[ region ].render( mode ); 2007 } 2008 }; 2009 }); 2010 2011 module.exports = State; 2012 2013 },{}],16:[function(require,module,exports){ 2014 /*globals _ */ 3044 2015 3045 2016 /** 3046 * wp.media.view.MediaFrame.Post 3047 * 3048 * The frame for manipulating media on the Edit Post page. 3049 * 3050 * @class 3051 * @augments wp.media.view.MediaFrame.Select 3052 * @augments wp.media.view.MediaFrame 3053 * @augments wp.media.view.Frame 3054 * @augments wp.media.View 3055 * @augments wp.Backbone.View 3056 * @augments Backbone.View 3057 * @mixes wp.media.controller.StateMachine 2017 * wp.media.selectionSync 2018 * 2019 * Sync an attachments selection in a state with another state. 2020 * 2021 * Allows for selecting multiple images in the Insert Media workflow, and then 2022 * switching to the Insert Gallery workflow while preserving the attachments selection. 2023 * 2024 * @mixin 3058 2025 */ 3059 var Select = wp.media.view.MediaFrame.Select, 3060 Library = wp.media.controller.Library, 3061 l10n = wp.media.view.l10n, 3062 Post; 3063 3064 Post = Select.extend({ 3065 initialize: function() { 3066 this.counts = { 3067 audio: { 3068 count: wp.media.view.settings.attachmentCounts.audio, 3069 state: 'playlist' 3070 }, 3071 video: { 3072 count: wp.media.view.settings.attachmentCounts.video, 3073 state: 'video-playlist' 3074 } 3075 }; 3076 3077 _.defaults( this.options, { 3078 multiple: true, 3079 editing: false, 3080 state: 'insert', 3081 metadata: {} 3082 }); 3083 3084 // Call 'initialize' directly on the parent class. 3085 Select.prototype.initialize.apply( this, arguments ); 3086 this.createIframeStates(); 3087 3088 }, 3089 3090 /** 3091 * Create the default states. 3092 */ 3093 createStates: function() { 3094 var options = this.options; 3095 3096 this.states.add([ 3097 // Main states. 3098 new Library({ 3099 id: 'insert', 3100 title: l10n.insertMediaTitle, 3101 priority: 20, 3102 toolbar: 'main-insert', 3103 filterable: 'all', 3104 library: wp.media.query( options.library ), 3105 multiple: options.multiple ? 'reset' : false, 3106 editable: true, 3107 3108 // If the user isn't allowed to edit fields, 3109 // can they still edit it locally? 3110 allowLocalEdits: true, 3111 3112 // Show the attachment display settings. 3113 displaySettings: true, 3114 // Update user settings when users adjust the 3115 // attachment display settings. 3116 displayUserSettings: true 3117 }), 3118 3119 new Library({ 3120 id: 'gallery', 3121 title: l10n.createGalleryTitle, 3122 priority: 40, 3123 toolbar: 'main-gallery', 3124 filterable: 'uploaded', 3125 multiple: 'add', 3126 editable: false, 3127 3128 library: wp.media.query( _.defaults({ 3129 type: 'image' 3130 }, options.library ) ) 3131 }), 3132 3133 // Embed states. 3134 new wp.media.controller.Embed( { metadata: options.metadata } ), 3135 3136 new wp.media.controller.EditImage( { model: options.editImage } ), 3137 3138 // Gallery states. 3139 new wp.media.controller.GalleryEdit({ 3140 library: options.selection, 3141 editing: options.editing, 3142 menu: 'gallery' 3143 }), 3144 3145 new wp.media.controller.GalleryAdd(), 3146 3147 new Library({ 3148 id: 'playlist', 3149 title: l10n.createPlaylistTitle, 3150 priority: 60, 3151 toolbar: 'main-playlist', 3152 filterable: 'uploaded', 3153 multiple: 'add', 3154 editable: false, 3155 3156 library: wp.media.query( _.defaults({ 3157 type: 'audio' 3158 }, options.library ) ) 3159 }), 3160 3161 // Playlist states. 3162 new wp.media.controller.CollectionEdit({ 3163 type: 'audio', 3164 collectionType: 'playlist', 3165 title: l10n.editPlaylistTitle, 3166 SettingsView: wp.media.view.Settings.Playlist, 3167 library: options.selection, 3168 editing: options.editing, 3169 menu: 'playlist', 3170 dragInfoText: l10n.playlistDragInfo, 3171 dragInfo: false 3172 }), 3173 3174 new wp.media.controller.CollectionAdd({ 3175 type: 'audio', 3176 collectionType: 'playlist', 3177 title: l10n.addToPlaylistTitle 3178 }), 3179 3180 new Library({ 3181 id: 'video-playlist', 3182 title: l10n.createVideoPlaylistTitle, 3183 priority: 60, 3184 toolbar: 'main-video-playlist', 3185 filterable: 'uploaded', 3186 multiple: 'add', 3187 editable: false, 3188 3189 library: wp.media.query( _.defaults({ 3190 type: 'video' 3191 }, options.library ) ) 3192 }), 3193 3194 new wp.media.controller.CollectionEdit({ 3195 type: 'video', 3196 collectionType: 'playlist', 3197 title: l10n.editVideoPlaylistTitle, 3198 SettingsView: wp.media.view.Settings.Playlist, 3199 library: options.selection, 3200 editing: options.editing, 3201 menu: 'video-playlist', 3202 dragInfoText: l10n.videoPlaylistDragInfo, 3203 dragInfo: false 3204 }), 3205 3206 new wp.media.controller.CollectionAdd({ 3207 type: 'video', 3208 collectionType: 'playlist', 3209 title: l10n.addToVideoPlaylistTitle 3210 }) 3211 ]); 3212 3213 if ( wp.media.view.settings.post.featuredImageId ) { 3214 this.states.add( new wp.media.controller.FeaturedImage() ); 3215 } 3216 }, 3217 3218 bindHandlers: function() { 3219 var handlers, checkCounts; 3220 3221 Select.prototype.bindHandlers.apply( this, arguments ); 3222 3223 this.on( 'activate', this.activate, this ); 3224 3225 // Only bother checking media type counts if one of the counts is zero 3226 checkCounts = _.find( this.counts, function( type ) { 3227 return type.count === 0; 3228 } ); 3229 3230 if ( typeof checkCounts !== 'undefined' ) { 3231 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 3232 } 3233 3234 this.on( 'menu:create:gallery', this.createMenu, this ); 3235 this.on( 'menu:create:playlist', this.createMenu, this ); 3236 this.on( 'menu:create:video-playlist', this.createMenu, this ); 3237 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 3238 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 3239 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 3240 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 3241 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 3242 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 3243 3244 handlers = { 3245 menu: { 3246 'default': 'mainMenu', 3247 'gallery': 'galleryMenu', 3248 'playlist': 'playlistMenu', 3249 'video-playlist': 'videoPlaylistMenu' 3250 }, 3251 3252 content: { 3253 'embed': 'embedContent', 3254 'edit-image': 'editImageContent', 3255 'edit-selection': 'editSelectionContent' 3256 }, 3257 3258 toolbar: { 3259 'main-insert': 'mainInsertToolbar', 3260 'main-gallery': 'mainGalleryToolbar', 3261 'gallery-edit': 'galleryEditToolbar', 3262 'gallery-add': 'galleryAddToolbar', 3263 'main-playlist': 'mainPlaylistToolbar', 3264 'playlist-edit': 'playlistEditToolbar', 3265 'playlist-add': 'playlistAddToolbar', 3266 'main-video-playlist': 'mainVideoPlaylistToolbar', 3267 'video-playlist-edit': 'videoPlaylistEditToolbar', 3268 'video-playlist-add': 'videoPlaylistAddToolbar' 3269 } 3270 }; 3271 3272 _.each( handlers, function( regionHandlers, region ) { 3273 _.each( regionHandlers, function( callback, handler ) { 3274 this.on( region + ':render:' + handler, this[ callback ], this ); 3275 }, this ); 3276 }, this ); 3277 }, 3278 3279 activate: function() { 3280 // Hide menu items for states tied to particular media types if there are no items 3281 _.each( this.counts, function( type ) { 3282 if ( type.count < 1 ) { 3283 this.menuItemVisibility( type.state, 'hide' ); 3284 } 3285 }, this ); 3286 }, 3287 3288 mediaTypeCounts: function( model, attr ) { 3289 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 3290 this.counts[ attr ].count++; 3291 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 3292 } 3293 }, 3294 3295 // Menus 3296 /** 3297 * @param {wp.Backbone.View} view 3298 */ 3299 mainMenu: function( view ) { 3300 view.set({ 3301 'library-separator': new wp.media.View({ 3302 className: 'separator', 3303 priority: 100 3304 }) 3305 }); 3306 }, 3307 3308 menuItemVisibility: function( state, visibility ) { 3309 var menu = this.menu.get(); 3310 if ( visibility === 'hide' ) { 3311 menu.hide( state ); 3312 } else if ( visibility === 'show' ) { 3313 menu.show( state ); 3314 } 3315 }, 3316 /** 3317 * @param {wp.Backbone.View} view 3318 */ 3319 galleryMenu: function( view ) { 3320 var lastState = this.lastState(), 3321 previous = lastState && lastState.id, 3322 frame = this; 3323 3324 view.set({ 3325 cancel: { 3326 text: l10n.cancelGalleryTitle, 3327 priority: 20, 3328 click: function() { 3329 if ( previous ) { 3330 frame.setState( previous ); 3331 } else { 3332 frame.close(); 3333 } 3334 3335 // Keep focus inside media modal 3336 // after canceling a gallery 3337 this.controller.modal.focusManager.focus(); 3338 } 3339 }, 3340 separateCancel: new wp.media.View({ 3341 className: 'separator', 3342 priority: 40 3343 }) 3344 }); 3345 }, 3346 3347 playlistMenu: function( view ) { 3348 var lastState = this.lastState(), 3349 previous = lastState && lastState.id, 3350 frame = this; 3351 3352 view.set({ 3353 cancel: { 3354 text: l10n.cancelPlaylistTitle, 3355 priority: 20, 3356 click: function() { 3357 if ( previous ) { 3358 frame.setState( previous ); 3359 } else { 3360 frame.close(); 3361 } 3362 } 3363 }, 3364 separateCancel: new wp.media.View({ 3365 className: 'separator', 3366 priority: 40 3367 }) 3368 }); 3369 }, 3370 3371 videoPlaylistMenu: function( view ) { 3372 var lastState = this.lastState(), 3373 previous = lastState && lastState.id, 3374 frame = this; 3375 3376 view.set({ 3377 cancel: { 3378 text: l10n.cancelVideoPlaylistTitle, 3379 priority: 20, 3380 click: function() { 3381 if ( previous ) { 3382 frame.setState( previous ); 3383 } else { 3384 frame.close(); 3385 } 3386 } 3387 }, 3388 separateCancel: new wp.media.View({ 3389 className: 'separator', 3390 priority: 40 3391 }) 3392 }); 3393 }, 3394 3395 // Content 3396 embedContent: function() { 3397 var view = new wp.media.view.Embed({ 3398 controller: this, 3399 model: this.state() 3400 }).render(); 3401 3402 this.content.set( view ); 3403 3404 if ( ! wp.media.isTouchDevice ) { 3405 view.url.focus(); 3406 } 3407 }, 3408 3409 editSelectionContent: function() { 3410 var state = this.state(), 3411 selection = state.get('selection'), 3412 view; 3413 3414 view = new wp.media.view.AttachmentsBrowser({ 3415 controller: this, 3416 collection: selection, 3417 selection: selection, 3418 model: state, 3419 sortable: true, 3420 search: false, 3421 date: false, 3422 dragInfo: true, 3423 3424 AttachmentView: wp.media.view.Attachments.EditSelection 3425 }).render(); 3426 3427 view.toolbar.set( 'backToLibrary', { 3428 text: l10n.returnToLibrary, 3429 priority: -100, 3430 3431 click: function() { 3432 this.controller.content.mode('browse'); 3433 } 3434 }); 3435 3436 // Browse our library of attachments. 3437 this.content.set( view ); 3438 3439 // Trigger the controller to set focus 3440 this.trigger( 'edit:selection', this ); 3441 }, 3442 3443 editImageContent: function() { 3444 var image = this.state().get('image'), 3445 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 3446 3447 this.content.set( view ); 3448 3449 // after creating the wrapper view, load the actual editor via an ajax call 3450 view.loadEditor(); 3451 3452 }, 3453 3454 // Toolbars 3455 3456 /** 3457 * @param {wp.Backbone.View} view 3458 */ 3459 selectionStatusToolbar: function( view ) { 3460 var editable = this.state().get('editable'); 3461 3462 view.set( 'selection', new wp.media.view.Selection({ 3463 controller: this, 3464 collection: this.state().get('selection'), 3465 priority: -40, 3466 3467 // If the selection is editable, pass the callback to 3468 // switch the content mode. 3469 editable: editable && function() { 3470 this.controller.content.mode('edit-selection'); 3471 } 3472 }).render() ); 3473 }, 3474 3475 /** 3476 * @param {wp.Backbone.View} view 3477 */ 3478 mainInsertToolbar: function( view ) { 3479 var controller = this; 3480 3481 this.selectionStatusToolbar( view ); 3482 3483 view.set( 'insert', { 3484 style: 'primary', 3485 priority: 80, 3486 text: l10n.insertIntoPost, 3487 requires: { selection: true }, 3488 3489 /** 3490 * @fires wp.media.controller.State#insert 3491 */ 3492 click: function() { 3493 var state = controller.state(), 3494 selection = state.get('selection'); 3495 3496 controller.close(); 3497 state.trigger( 'insert', selection ).reset(); 3498 } 3499 }); 3500 }, 3501 3502 /** 3503 * @param {wp.Backbone.View} view 3504 */ 3505 mainGalleryToolbar: function( view ) { 3506 var controller = this; 3507 3508 this.selectionStatusToolbar( view ); 3509 3510 view.set( 'gallery', { 3511 style: 'primary', 3512 text: l10n.createNewGallery, 3513 priority: 60, 3514 requires: { selection: true }, 3515 3516 click: function() { 3517 var selection = controller.state().get('selection'), 3518 edit = controller.state('gallery-edit'), 3519 models = selection.where({ type: 'image' }); 3520 3521 edit.set( 'library', new wp.media.model.Selection( models, { 3522 props: selection.props.toJSON(), 3523 multiple: true 3524 }) ); 3525 3526 this.controller.setState('gallery-edit'); 3527 3528 // Keep focus inside media modal 3529 // after jumping to gallery view 3530 this.controller.modal.focusManager.focus(); 3531 } 3532 }); 3533 }, 3534 3535 mainPlaylistToolbar: function( view ) { 3536 var controller = this; 3537 3538 this.selectionStatusToolbar( view ); 3539 3540 view.set( 'playlist', { 3541 style: 'primary', 3542 text: l10n.createNewPlaylist, 3543 priority: 100, 3544 requires: { selection: true }, 3545 3546 click: function() { 3547 var selection = controller.state().get('selection'), 3548 edit = controller.state('playlist-edit'), 3549 models = selection.where({ type: 'audio' }); 3550 3551 edit.set( 'library', new wp.media.model.Selection( models, { 3552 props: selection.props.toJSON(), 3553 multiple: true 3554 }) ); 3555 3556 this.controller.setState('playlist-edit'); 3557 3558 // Keep focus inside media modal 3559 // after jumping to playlist view 3560 this.controller.modal.focusManager.focus(); 3561 } 3562 }); 3563 }, 3564 3565 mainVideoPlaylistToolbar: function( view ) { 3566 var controller = this; 3567 3568 this.selectionStatusToolbar( view ); 3569 3570 view.set( 'video-playlist', { 3571 style: 'primary', 3572 text: l10n.createNewVideoPlaylist, 3573 priority: 100, 3574 requires: { selection: true }, 3575 3576 click: function() { 3577 var selection = controller.state().get('selection'), 3578 edit = controller.state('video-playlist-edit'), 3579 models = selection.where({ type: 'video' }); 3580 3581 edit.set( 'library', new wp.media.model.Selection( models, { 3582 props: selection.props.toJSON(), 3583 multiple: true 3584 }) ); 3585 3586 this.controller.setState('video-playlist-edit'); 3587 3588 // Keep focus inside media modal 3589 // after jumping to video playlist view 3590 this.controller.modal.focusManager.focus(); 3591 } 3592 }); 3593 }, 3594 3595 featuredImageToolbar: function( toolbar ) { 3596 this.createSelectToolbar( toolbar, { 3597 text: l10n.setFeaturedImage, 3598 state: this.options.state 3599 }); 3600 }, 3601 3602 mainEmbedToolbar: function( toolbar ) { 3603 toolbar.view = new wp.media.view.Toolbar.Embed({ 3604 controller: this 3605 }); 3606 }, 3607 3608 galleryEditToolbar: function() { 3609 var editing = this.state().get('editing'); 3610 this.toolbar.set( new wp.media.view.Toolbar({ 3611 controller: this, 3612 items: { 3613 insert: { 3614 style: 'primary', 3615 text: editing ? l10n.updateGallery : l10n.insertGallery, 3616 priority: 80, 3617 requires: { library: true }, 3618 3619 /** 3620 * @fires wp.media.controller.State#update 3621 */ 3622 click: function() { 3623 var controller = this.controller, 3624 state = controller.state(); 3625 3626 controller.close(); 3627 state.trigger( 'update', state.get('library') ); 3628 3629 // Restore and reset the default state. 3630 controller.setState( controller.options.state ); 3631 controller.reset(); 3632 } 3633 } 3634 } 3635 }) ); 3636 }, 3637 3638 galleryAddToolbar: function() { 3639 this.toolbar.set( new wp.media.view.Toolbar({ 3640 controller: this, 3641 items: { 3642 insert: { 3643 style: 'primary', 3644 text: l10n.addToGallery, 3645 priority: 80, 3646 requires: { selection: true }, 3647 3648 /** 3649 * @fires wp.media.controller.State#reset 3650 */ 3651 click: function() { 3652 var controller = this.controller, 3653 state = controller.state(), 3654 edit = controller.state('gallery-edit'); 3655 3656 edit.get('library').add( state.get('selection').models ); 3657 state.trigger('reset'); 3658 controller.setState('gallery-edit'); 3659 } 3660 } 3661 } 3662 }) ); 3663 }, 3664 3665 playlistEditToolbar: function() { 3666 var editing = this.state().get('editing'); 3667 this.toolbar.set( new wp.media.view.Toolbar({ 3668 controller: this, 3669 items: { 3670 insert: { 3671 style: 'primary', 3672 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 3673 priority: 80, 3674 requires: { library: true }, 3675 3676 /** 3677 * @fires wp.media.controller.State#update 3678 */ 3679 click: function() { 3680 var controller = this.controller, 3681 state = controller.state(); 3682 3683 controller.close(); 3684 state.trigger( 'update', state.get('library') ); 3685 3686 // Restore and reset the default state. 3687 controller.setState( controller.options.state ); 3688 controller.reset(); 3689 } 3690 } 3691 } 3692 }) ); 3693 }, 3694 3695 playlistAddToolbar: function() { 3696 this.toolbar.set( new wp.media.view.Toolbar({ 3697 controller: this, 3698 items: { 3699 insert: { 3700 style: 'primary', 3701 text: l10n.addToPlaylist, 3702 priority: 80, 3703 requires: { selection: true }, 3704 3705 /** 3706 * @fires wp.media.controller.State#reset 3707 */ 3708 click: function() { 3709 var controller = this.controller, 3710 state = controller.state(), 3711 edit = controller.state('playlist-edit'); 3712 3713 edit.get('library').add( state.get('selection').models ); 3714 state.trigger('reset'); 3715 controller.setState('playlist-edit'); 3716 } 3717 } 3718 } 3719 }) ); 3720 }, 3721 3722 videoPlaylistEditToolbar: function() { 3723 var editing = this.state().get('editing'); 3724 this.toolbar.set( new wp.media.view.Toolbar({ 3725 controller: this, 3726 items: { 3727 insert: { 3728 style: 'primary', 3729 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 3730 priority: 140, 3731 requires: { library: true }, 3732 3733 click: function() { 3734 var controller = this.controller, 3735 state = controller.state(), 3736 library = state.get('library'); 3737 3738 library.type = 'video'; 3739 3740 controller.close(); 3741 state.trigger( 'update', library ); 3742 3743 // Restore and reset the default state. 3744 controller.setState( controller.options.state ); 3745 controller.reset(); 3746 } 3747 } 3748 } 3749 }) ); 3750 }, 3751 3752 videoPlaylistAddToolbar: function() { 3753 this.toolbar.set( new wp.media.view.Toolbar({ 3754 controller: this, 3755 items: { 3756 insert: { 3757 style: 'primary', 3758 text: l10n.addToVideoPlaylist, 3759 priority: 140, 3760 requires: { selection: true }, 3761 3762 click: function() { 3763 var controller = this.controller, 3764 state = controller.state(), 3765 edit = controller.state('video-playlist-edit'); 3766 3767 edit.get('library').add( state.get('selection').models ); 3768 state.trigger('reset'); 3769 controller.setState('video-playlist-edit'); 3770 } 3771 } 3772 } 3773 }) ); 2026 var selectionSync = { 2027 /** 2028 * @since 3.5.0 2029 */ 2030 syncSelection: function() { 2031 var selection = this.get('selection'), 2032 manager = this.frame._selection; 2033 2034 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2035 return; 2036 } 2037 2038 // If the selection supports multiple items, validate the stored 2039 // attachments based on the new selection's conditions. Record 2040 // the attachments that are not included; we'll maintain a 2041 // reference to those. Other attachments are considered in flux. 2042 if ( selection.multiple ) { 2043 selection.reset( [], { silent: true }); 2044 selection.validateAll( manager.attachments ); 2045 manager.difference = _.difference( manager.attachments.models, selection.models ); 2046 } 2047 2048 // Sync the selection's single item with the master. 2049 selection.single( manager.single ); 2050 }, 2051 2052 /** 2053 * Record the currently active attachments, which is a combination 2054 * of the selection's attachments and the set of selected 2055 * attachments that this specific selection considered invalid. 2056 * Reset the difference and record the single attachment. 2057 * 2058 * @since 3.5.0 2059 */ 2060 recordSelection: function() { 2061 var selection = this.get('selection'), 2062 manager = this.frame._selection; 2063 2064 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2065 return; 2066 } 2067 2068 if ( selection.multiple ) { 2069 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 2070 manager.difference = []; 2071 } else { 2072 manager.attachments.add( selection.toArray() ); 2073 } 2074 2075 manager.single = selection._single; 3774 2076 } 3775 }); 3776 3777 module.exports = Post; 3778 3779 3780 /***/ }), 3781 /* 50 */ 3782 /***/ (function(module, exports) { 3783 3784 /*globals wp */ 2077 }; 2078 2079 module.exports = selectionSync; 2080 2081 },{}],17:[function(require,module,exports){ 2082 /*globals wp, jQuery, _, Backbone */ 2083 2084 var media = wp.media, 2085 $ = jQuery, 2086 l10n; 2087 2088 media.isTouchDevice = ( 'ontouchend' in document ); 2089 2090 // Link any localized strings. 2091 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 2092 2093 // Link any settings. 2094 media.view.settings = l10n.settings || {}; 2095 delete l10n.settings; 2096 2097 // Copy the `post` setting over to the model settings. 2098 media.model.settings.post = media.view.settings.post; 2099 2100 // Check if the browser supports CSS 3.0 transitions 2101 $.support.transition = (function(){ 2102 var style = document.documentElement.style, 2103 transitions = { 2104 WebkitTransition: 'webkitTransitionEnd', 2105 MozTransition: 'transitionend', 2106 OTransition: 'oTransitionEnd otransitionend', 2107 transition: 'transitionend' 2108 }, transition; 2109 2110 transition = _.find( _.keys( transitions ), function( transition ) { 2111 return ! _.isUndefined( style[ transition ] ); 2112 }); 2113 2114 return transition && { 2115 end: transitions[ transition ] 2116 }; 2117 }()); 3785 2118 3786 2119 /** 3787 * wp.media.view.MediaFrame.ImageDetails 3788 * 3789 * A media frame for manipulating an image that's already been inserted 3790 * into a post. 3791 * 3792 * @class 3793 * @augments wp.media.view.MediaFrame.Select 3794 * @augments wp.media.view.MediaFrame 3795 * @augments wp.media.view.Frame 3796 * @augments wp.media.View 3797 * @augments wp.Backbone.View 3798 * @augments Backbone.View 3799 * @mixes wp.media.controller.StateMachine 2120 * A shared event bus used to provide events into 2121 * the media workflows that 3rd-party devs can use to hook 2122 * in. 3800 2123 */ 3801 var Select = wp.media.view.MediaFrame.Select, 3802 l10n = wp.media.view.l10n, 3803 ImageDetails; 3804 3805 ImageDetails = Select.extend({ 3806 defaults: { 3807 id: 'image', 3808 url: '', 3809 menu: 'image-details', 3810 content: 'image-details', 3811 toolbar: 'image-details', 3812 type: 'link', 3813 title: l10n.imageDetailsTitle, 3814 priority: 120 3815 }, 3816 3817 initialize: function( options ) { 3818 this.image = new wp.media.model.PostImage( options.metadata ); 3819 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 3820 Select.prototype.initialize.apply( this, arguments ); 3821 }, 3822 3823 bindHandlers: function() { 3824 Select.prototype.bindHandlers.apply( this, arguments ); 3825 this.on( 'menu:create:image-details', this.createMenu, this ); 3826 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 3827 this.on( 'content:render:edit-image', this.editImageContent, this ); 3828 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 3829 // override the select toolbar 3830 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 3831 }, 3832 3833 createStates: function() { 3834 this.states.add([ 3835 new wp.media.controller.ImageDetails({ 3836 image: this.image, 3837 editable: false 3838 }), 3839 new wp.media.controller.ReplaceImage({ 3840 id: 'replace-image', 3841 library: wp.media.query( { type: 'image' } ), 3842 image: this.image, 3843 multiple: false, 3844 title: l10n.imageReplaceTitle, 3845 toolbar: 'replace', 3846 priority: 80, 3847 displaySettings: true 3848 }), 3849 new wp.media.controller.EditImage( { 3850 image: this.image, 3851 selection: this.options.selection 3852 } ) 3853 ]); 3854 }, 3855 3856 imageDetailsContent: function( options ) { 3857 options.view = new wp.media.view.ImageDetails({ 3858 controller: this, 3859 model: this.state().image, 3860 attachment: this.state().image.attachment 3861 }); 3862 }, 3863 3864 editImageContent: function() { 3865 var state = this.state(), 3866 model = state.get('image'), 3867 view; 3868 3869 if ( ! model ) { 3870 return; 3871 } 3872 3873 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 3874 3875 this.content.set( view ); 3876 3877 // after bringing in the frame, load the actual editor via an ajax call 3878 view.loadEditor(); 3879 3880 }, 3881 3882 renderImageDetailsToolbar: function() { 3883 this.toolbar.set( new wp.media.view.Toolbar({ 3884 controller: this, 3885 items: { 3886 select: { 3887 style: 'primary', 3888 text: l10n.update, 3889 priority: 80, 3890 3891 click: function() { 3892 var controller = this.controller, 3893 state = controller.state(); 3894 3895 controller.close(); 3896 3897 // not sure if we want to use wp.media.string.image which will create a shortcode or 3898 // perhaps wp.html.string to at least to build the <img /> 3899 state.trigger( 'update', controller.image.toJSON() ); 3900 3901 // Restore and reset the default state. 3902 controller.setState( controller.options.state ); 3903 controller.reset(); 3904 } 3905 } 3906 } 3907 }) ); 3908 }, 3909 3910 renderReplaceImageToolbar: function() { 3911 var frame = this, 3912 lastState = frame.lastState(), 3913 previous = lastState && lastState.id; 3914 3915 this.toolbar.set( new wp.media.view.Toolbar({ 3916 controller: this, 3917 items: { 3918 back: { 3919 text: l10n.back, 3920 priority: 20, 3921 click: function() { 3922 if ( previous ) { 3923 frame.setState( previous ); 3924 } else { 3925 frame.close(); 3926 } 3927 } 3928 }, 3929 3930 replace: { 3931 style: 'primary', 3932 text: l10n.replace, 3933 priority: 80, 3934 3935 click: function() { 3936 var controller = this.controller, 3937 state = controller.state(), 3938 selection = state.get( 'selection' ), 3939 attachment = selection.single(); 3940 3941 controller.close(); 3942 3943 controller.image.changeAttachment( attachment, state.display( attachment ) ); 3944 3945 // not sure if we want to use wp.media.string.image which will create a shortcode or 3946 // perhaps wp.html.string to at least to build the <img /> 3947 state.trigger( 'replace', controller.image.toJSON() ); 3948 3949 // Restore and reset the default state. 3950 controller.setState( controller.options.state ); 3951 controller.reset(); 3952 } 3953 } 3954 } 3955 }) ); 2124 media.events = _.extend( {}, Backbone.Events ); 2125 2126 /** 2127 * Makes it easier to bind events using transitions. 2128 * 2129 * @param {string} selector 2130 * @param {Number} sensitivity 2131 * @returns {Promise} 2132 */ 2133 media.transition = function( selector, sensitivity ) { 2134 var deferred = $.Deferred(); 2135 2136 sensitivity = sensitivity || 2000; 2137 2138 if ( $.support.transition ) { 2139 if ( ! (selector instanceof $) ) { 2140 selector = $( selector ); 2141 } 2142 2143 // Resolve the deferred when the first element finishes animating. 2144 selector.first().one( $.support.transition.end, deferred.resolve ); 2145 2146 // Just in case the event doesn't trigger, fire a callback. 2147 _.delay( deferred.resolve, sensitivity ); 2148 2149 // Otherwise, execute on the spot. 2150 } else { 2151 deferred.resolve(); 3956 2152 } 3957 2153 3958 }); 3959 3960 module.exports = ImageDetails; 3961 3962 3963 /***/ }), 3964 /* 51 */ 3965 /***/ (function(module, exports) { 3966 3967 /*globals wp, _, jQuery */ 2154 return deferred.promise(); 2155 }; 2156 2157 media.controller.Region = require( './controllers/region.js' ); 2158 media.controller.StateMachine = require( './controllers/state-machine.js' ); 2159 media.controller.State = require( './controllers/state.js' ); 2160 2161 media.selectionSync = require( './utils/selection-sync.js' ); 2162 media.controller.Library = require( './controllers/library.js' ); 2163 media.controller.ImageDetails = require( './controllers/image-details.js' ); 2164 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' ); 2165 media.controller.GalleryAdd = require( './controllers/gallery-add.js' ); 2166 media.controller.CollectionEdit = require( './controllers/collection-edit.js' ); 2167 media.controller.CollectionAdd = require( './controllers/collection-add.js' ); 2168 media.controller.FeaturedImage = require( './controllers/featured-image.js' ); 2169 media.controller.ReplaceImage = require( './controllers/replace-image.js' ); 2170 media.controller.EditImage = require( './controllers/edit-image.js' ); 2171 media.controller.MediaLibrary = require( './controllers/media-library.js' ); 2172 media.controller.Embed = require( './controllers/embed.js' ); 2173 media.controller.Cropper = require( './controllers/cropper.js' ); 2174 2175 media.View = require( './views/view.js' ); 2176 media.view.Frame = require( './views/frame.js' ); 2177 media.view.MediaFrame = require( './views/media-frame.js' ); 2178 media.view.MediaFrame.Select = require( './views/frame/select.js' ); 2179 media.view.MediaFrame.Post = require( './views/frame/post.js' ); 2180 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' ); 2181 media.view.Modal = require( './views/modal.js' ); 2182 media.view.FocusManager = require( './views/focus-manager.js' ); 2183 media.view.UploaderWindow = require( './views/uploader/window.js' ); 2184 media.view.EditorUploader = require( './views/uploader/editor.js' ); 2185 media.view.UploaderInline = require( './views/uploader/inline.js' ); 2186 media.view.UploaderStatus = require( './views/uploader/status.js' ); 2187 media.view.UploaderStatusError = require( './views/uploader/status-error.js' ); 2188 media.view.Toolbar = require( './views/toolbar.js' ); 2189 media.view.Toolbar.Select = require( './views/toolbar/select.js' ); 2190 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' ); 2191 media.view.Button = require( './views/button.js' ); 2192 media.view.ButtonGroup = require( './views/button-group.js' ); 2193 media.view.PriorityList = require( './views/priority-list.js' ); 2194 media.view.MenuItem = require( './views/menu-item.js' ); 2195 media.view.Menu = require( './views/menu.js' ); 2196 media.view.RouterItem = require( './views/router-item.js' ); 2197 media.view.Router = require( './views/router.js' ); 2198 media.view.Sidebar = require( './views/sidebar.js' ); 2199 media.view.Attachment = require( './views/attachment.js' ); 2200 media.view.Attachment.Library = require( './views/attachment/library.js' ); 2201 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' ); 2202 media.view.Attachments = require( './views/attachments.js' ); 2203 media.view.Search = require( './views/search.js' ); 2204 media.view.AttachmentFilters = require( './views/attachment-filters.js' ); 2205 media.view.DateFilter = require( './views/attachment-filters/date.js' ); 2206 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' ); 2207 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' ); 2208 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' ); 2209 media.view.Selection = require( './views/selection.js' ); 2210 media.view.Attachment.Selection = require( './views/attachment/selection.js' ); 2211 media.view.Attachments.Selection = require( './views/attachments/selection.js' ); 2212 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' ); 2213 media.view.Settings = require( './views/settings.js' ); 2214 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' ); 2215 media.view.Settings.Gallery = require( './views/settings/gallery.js' ); 2216 media.view.Settings.Playlist = require( './views/settings/playlist.js' ); 2217 media.view.Attachment.Details = require( './views/attachment/details.js' ); 2218 media.view.AttachmentCompat = require( './views/attachment-compat.js' ); 2219 media.view.Iframe = require( './views/iframe.js' ); 2220 media.view.Embed = require( './views/embed.js' ); 2221 media.view.Label = require( './views/label.js' ); 2222 media.view.EmbedUrl = require( './views/embed/url.js' ); 2223 media.view.EmbedLink = require( './views/embed/link.js' ); 2224 media.view.EmbedImage = require( './views/embed/image.js' ); 2225 media.view.ImageDetails = require( './views/image-details.js' ); 2226 media.view.Cropper = require( './views/cropper.js' ); 2227 media.view.EditImage = require( './views/edit-image.js' ); 2228 media.view.Spinner = require( './views/spinner.js' ); 2229 2230 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/edit-image.js":4,"./controllers/embed.js":5,"./controllers/featured-image.js":6,"./controllers/gallery-add.js":7,"./controllers/gallery-edit.js":8,"./controllers/image-details.js":9,"./controllers/library.js":10,"./controllers/media-library.js":11,"./controllers/region.js":12,"./controllers/replace-image.js":13,"./controllers/state-machine.js":14,"./controllers/state.js":15,"./utils/selection-sync.js":16,"./views/attachment-compat.js":18,"./views/attachment-filters.js":19,"./views/attachment-filters/all.js":20,"./views/attachment-filters/date.js":21,"./views/attachment-filters/uploaded.js":22,"./views/attachment.js":23,"./views/attachment/details.js":24,"./views/attachment/edit-library.js":25,"./views/attachment/edit-selection.js":26,"./views/attachment/library.js":27,"./views/attachment/selection.js":28,"./views/attachments.js":29,"./views/attachments/browser.js":30,"./views/attachments/selection.js":31,"./views/button-group.js":32,"./views/button.js":33,"./views/cropper.js":34,"./views/edit-image.js":35,"./views/embed.js":36,"./views/embed/image.js":37,"./views/embed/link.js":38,"./views/embed/url.js":39,"./views/focus-manager.js":40,"./views/frame.js":41,"./views/frame/image-details.js":42,"./views/frame/post.js":43,"./views/frame/select.js":44,"./views/iframe.js":45,"./views/image-details.js":46,"./views/label.js":47,"./views/media-frame.js":48,"./views/menu-item.js":49,"./views/menu.js":50,"./views/modal.js":51,"./views/priority-list.js":52,"./views/router-item.js":53,"./views/router.js":54,"./views/search.js":55,"./views/selection.js":56,"./views/settings.js":57,"./views/settings/attachment-display.js":58,"./views/settings/gallery.js":59,"./views/settings/playlist.js":60,"./views/sidebar.js":61,"./views/spinner.js":62,"./views/toolbar.js":63,"./views/toolbar/embed.js":64,"./views/toolbar/select.js":65,"./views/uploader/editor.js":66,"./views/uploader/inline.js":67,"./views/uploader/status-error.js":68,"./views/uploader/status.js":69,"./views/uploader/window.js":70,"./views/view.js":71}],18:[function(require,module,exports){ 2231 /*globals _ */ 3968 2232 3969 2233 /** 3970 * wp.media.view.Modal 3971 * 3972 * A modal view, which the media modal uses as its default container. 3973 * 3974 * @class 3975 * @augments wp.media.View 3976 * @augments wp.Backbone.View 3977 * @augments Backbone.View 3978 */ 3979 var $ = jQuery, 3980 Modal; 3981 3982 Modal = wp.media.View.extend({ 3983 tagName: 'div', 3984 template: wp.template('media-modal'), 3985 3986 attributes: { 3987 tabindex: 0 3988 }, 3989 3990 events: { 3991 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 3992 'keydown': 'keydown' 3993 }, 3994 3995 initialize: function() { 3996 _.defaults( this.options, { 3997 container: document.body, 3998 title: '', 3999 propagate: true, 4000 freeze: true 4001 }); 4002 4003 this.focusManager = new wp.media.view.FocusManager({ 4004 el: this.el 4005 }); 4006 }, 4007 /** 4008 * @returns {Object} 4009 */ 4010 prepare: function() { 4011 return { 4012 title: this.options.title 4013 }; 4014 }, 4015 4016 /** 4017 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4018 */ 4019 attach: function() { 4020 if ( this.views.attached ) { 4021 return this; 4022 } 4023 4024 if ( ! this.views.rendered ) { 4025 this.render(); 4026 } 4027 4028 this.$el.appendTo( this.options.container ); 4029 4030 // Manually mark the view as attached and trigger ready. 4031 this.views.attached = true; 4032 this.views.ready(); 4033 4034 return this.propagate('attach'); 4035 }, 4036 4037 /** 4038 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4039 */ 4040 detach: function() { 4041 if ( this.$el.is(':visible') ) { 4042 this.close(); 4043 } 4044 4045 this.$el.detach(); 4046 this.views.attached = false; 4047 return this.propagate('detach'); 4048 }, 4049 4050 /** 4051 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4052 */ 4053 open: function() { 4054 var $el = this.$el, 4055 options = this.options, 4056 mceEditor; 4057 4058 if ( $el.is(':visible') ) { 4059 return this; 4060 } 4061 4062 if ( ! this.views.attached ) { 4063 this.attach(); 4064 } 4065 4066 // If the `freeze` option is set, record the window's scroll position. 4067 if ( options.freeze ) { 4068 this._freeze = { 4069 scrollTop: $( window ).scrollTop() 4070 }; 4071 } 4072 4073 // Disable page scrolling. 4074 $( 'body' ).addClass( 'modal-open' ); 4075 4076 $el.show(); 4077 4078 // Try to close the onscreen keyboard 4079 if ( 'ontouchend' in document ) { 4080 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 4081 mceEditor.iframeElement.focus(); 4082 mceEditor.iframeElement.blur(); 4083 4084 setTimeout( function() { 4085 mceEditor.iframeElement.blur(); 4086 }, 100 ); 4087 } 4088 } 4089 4090 this.$el.focus(); 4091 4092 return this.propagate('open'); 4093 }, 4094 4095 /** 4096 * @param {Object} options 4097 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4098 */ 4099 close: function( options ) { 4100 var freeze = this._freeze; 4101 4102 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 4103 return this; 4104 } 4105 4106 // Enable page scrolling. 4107 $( 'body' ).removeClass( 'modal-open' ); 4108 4109 // Hide modal and remove restricted media modal tab focus once it's closed 4110 this.$el.hide().undelegate( 'keydown' ); 4111 4112 // Put focus back in useful location once modal is closed 4113 $('#wpbody-content').focus(); 4114 4115 this.propagate('close'); 4116 4117 // If the `freeze` option is set, restore the container's scroll position. 4118 if ( freeze ) { 4119 $( window ).scrollTop( freeze.scrollTop ); 4120 } 4121 4122 if ( options && options.escape ) { 4123 this.propagate('escape'); 4124 } 4125 4126 return this; 4127 }, 4128 /** 4129 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4130 */ 4131 escape: function() { 4132 return this.close({ escape: true }); 4133 }, 4134 /** 4135 * @param {Object} event 4136 */ 4137 escapeHandler: function( event ) { 4138 event.preventDefault(); 4139 this.escape(); 4140 }, 4141 4142 /** 4143 * @param {Array|Object} content Views to register to '.media-modal-content' 4144 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4145 */ 4146 content: function( content ) { 4147 this.views.set( '.media-modal-content', content ); 4148 return this; 4149 }, 4150 4151 /** 4152 * Triggers a modal event and if the `propagate` option is set, 4153 * forwards events to the modal's controller. 4154 * 4155 * @param {string} id 4156 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4157 */ 4158 propagate: function( id ) { 4159 this.trigger( id ); 4160 4161 if ( this.options.propagate ) { 4162 this.controller.trigger( id ); 4163 } 4164 4165 return this; 4166 }, 4167 /** 4168 * @param {Object} event 4169 */ 4170 keydown: function( event ) { 4171 // Close the modal when escape is pressed. 4172 if ( 27 === event.which && this.$el.is(':visible') ) { 4173 this.escape(); 4174 event.stopImmediatePropagation(); 4175 } 4176 } 4177 }); 4178 4179 module.exports = Modal; 4180 4181 4182 /***/ }), 4183 /* 52 */ 4184 /***/ (function(module, exports) { 4185 4186 /** 4187 * wp.media.view.FocusManager 4188 * 4189 * @class 4190 * @augments wp.media.View 4191 * @augments wp.Backbone.View 4192 * @augments Backbone.View 4193 */ 4194 var FocusManager = wp.media.View.extend({ 4195 4196 events: { 4197 'keydown': 'constrainTabbing' 4198 }, 4199 4200 focus: function() { // Reset focus on first left menu item 4201 this.$('.media-menu-item').first().focus(); 4202 }, 4203 /** 4204 * @param {Object} event 4205 */ 4206 constrainTabbing: function( event ) { 4207 var tabbables; 4208 4209 // Look for the tab key. 4210 if ( 9 !== event.keyCode ) { 4211 return; 4212 } 4213 4214 // Skip the file input added by Plupload. 4215 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 4216 4217 // Keep tab focus within media modal while it's open 4218 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 4219 tabbables.first().focus(); 4220 return false; 4221 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 4222 tabbables.last().focus(); 4223 return false; 4224 } 4225 } 4226 4227 }); 4228 4229 module.exports = FocusManager; 4230 4231 4232 /***/ }), 4233 /* 53 */ 4234 /***/ (function(module, exports) { 4235 4236 /*globals wp, _, jQuery */ 4237 4238 /** 4239 * wp.media.view.UploaderWindow 4240 * 4241 * An uploader window that allows for dragging and dropping media. 4242 * 4243 * @class 4244 * @augments wp.media.View 4245 * @augments wp.Backbone.View 4246 * @augments Backbone.View 4247 * 4248 * @param {object} [options] Options hash passed to the view. 4249 * @param {object} [options.uploader] Uploader properties. 4250 * @param {jQuery} [options.uploader.browser] 4251 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 4252 * @param {object} [options.uploader.params] 4253 */ 4254 var $ = jQuery, 4255 UploaderWindow; 4256 4257 UploaderWindow = wp.media.View.extend({ 4258 tagName: 'div', 4259 className: 'uploader-window', 4260 template: wp.template('uploader-window'), 4261 4262 initialize: function() { 4263 var uploader; 4264 4265 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body'); 4266 4267 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 4268 dropzone: this.$el, 4269 browser: this.$browser, 4270 params: {} 4271 }); 4272 4273 // Ensure the dropzone is a jQuery collection. 4274 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 4275 uploader.dropzone = $( uploader.dropzone ); 4276 } 4277 4278 this.controller.on( 'activate', this.refresh, this ); 4279 4280 this.controller.on( 'detach', function() { 4281 this.$browser.remove(); 4282 }, this ); 4283 }, 4284 4285 refresh: function() { 4286 if ( this.uploader ) { 4287 this.uploader.refresh(); 4288 } 4289 }, 4290 4291 ready: function() { 4292 var postId = wp.media.view.settings.post.id, 4293 dropzone; 4294 4295 // If the uploader already exists, bail. 4296 if ( this.uploader ) { 4297 return; 4298 } 4299 4300 if ( postId ) { 4301 this.options.uploader.params.post_id = postId; 4302 } 4303 this.uploader = new wp.Uploader( this.options.uploader ); 4304 4305 dropzone = this.uploader.dropzone; 4306 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 4307 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 4308 4309 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 4310 }, 4311 4312 _ready: function() { 4313 this.controller.trigger( 'uploader:ready' ); 4314 }, 4315 4316 show: function() { 4317 var $el = this.$el.show(); 4318 4319 // Ensure that the animation is triggered by waiting until 4320 // the transparent element is painted into the DOM. 4321 _.defer( function() { 4322 $el.css({ opacity: 1 }); 4323 }); 4324 }, 4325 4326 hide: function() { 4327 var $el = this.$el.css({ opacity: 0 }); 4328 4329 wp.media.transition( $el ).done( function() { 4330 // Transition end events are subject to race conditions. 4331 // Make sure that the value is set as intended. 4332 if ( '0' === $el.css('opacity') ) { 4333 $el.hide(); 4334 } 4335 }); 4336 4337 // https://core.trac.wordpress.org/ticket/27341 4338 _.delay( function() { 4339 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 4340 $el.hide(); 4341 } 4342 }, 500 ); 4343 } 4344 }); 4345 4346 module.exports = UploaderWindow; 4347 4348 4349 /***/ }), 4350 /* 54 */ 4351 /***/ (function(module, exports) { 4352 4353 /*globals wp, _, jQuery */ 4354 4355 /** 4356 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap 4357 * or #wp-fullscreen-body) and relays drag'n'dropped files to a media workflow. 4358 * 4359 * wp.media.view.EditorUploader 2234 * wp.media.view.AttachmentCompat 2235 * 2236 * A view to display fields added via the `attachment_fields_to_edit` filter. 4360 2237 * 4361 2238 * @class … … 4365 2242 */ 4366 2243 var View = wp.media.View, 4367 l10n = wp.media.view.l10n, 4368 $ = jQuery, 4369 EditorUploader; 4370 4371 EditorUploader = View.extend({ 4372 tagName: 'div', 4373 className: 'uploader-editor', 4374 template: wp.template( 'uploader-editor' ), 4375 4376 localDrag: false, 4377 overContainer: false, 4378 overDropzone: false, 4379 draggingFile: null, 4380 4381 /** 4382 * Bind drag'n'drop events to callbacks. 4383 */ 2244 AttachmentCompat; 2245 2246 AttachmentCompat = View.extend({ 2247 tagName: 'form', 2248 className: 'compat-item', 2249 2250 events: { 2251 'submit': 'preventDefault', 2252 'change input': 'save', 2253 'change select': 'save', 2254 'change textarea': 'save' 2255 }, 2256 4384 2257 initialize: function() { 4385 this.initialized = false; 4386 4387 // Bail if not enabled or UA does not support drag'n'drop or File API. 4388 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 4389 return this; 4390 } 4391 4392 this.$document = $(document); 4393 this.dropzones = []; 4394 this.files = []; 4395 4396 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 4397 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 4398 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 4399 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 4400 4401 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 4402 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 4403 4404 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 4405 this.localDrag = event.type === 'dragstart'; 4406 }, this ) ); 4407 4408 this.initialized = true; 4409 return this; 4410 }, 4411 4412 /** 4413 * Check browser support for drag'n'drop. 4414 * 4415 * @return Boolean 4416 */ 4417 browserSupport: function() { 4418 var supports = false, div = document.createElement('div'); 4419 4420 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 4421 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 4422 return supports; 4423 }, 4424 4425 isDraggingFile: function( event ) { 4426 if ( this.draggingFile !== null ) { 4427 return this.draggingFile; 4428 } 4429 4430 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 4431 return false; 4432 } 4433 4434 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 4435 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 4436 4437 return this.draggingFile; 4438 }, 4439 4440 refresh: function( e ) { 4441 var dropzone_id; 4442 for ( dropzone_id in this.dropzones ) { 4443 // Hide the dropzones only if dragging has left the screen. 4444 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 4445 } 4446 4447 if ( ! _.isUndefined( e ) ) { 4448 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 4449 } 4450 4451 if ( ! this.overContainer && ! this.overDropzone ) { 4452 this.draggingFile = null; 4453 } 4454 4455 return this; 4456 }, 4457 4458 render: function() { 4459 if ( ! this.initialized ) { 4460 return this; 4461 } 4462 4463 View.prototype.render.apply( this, arguments ); 4464 $( '.wp-editor-wrap, #wp-fullscreen-body' ).each( _.bind( this.attach, this ) ); 4465 return this; 4466 }, 4467 4468 attach: function( index, editor ) { 4469 // Attach a dropzone to an editor. 4470 var dropzone = this.$el.clone(); 4471 this.dropzones.push( dropzone ); 4472 $( editor ).append( dropzone ); 4473 return this; 4474 }, 4475 4476 /** 4477 * When a file is dropped on the editor uploader, open up an editor media workflow 4478 * and upload the file immediately. 4479 * 4480 * @param {jQuery.Event} event The 'drop' event. 4481 */ 4482 drop: function( event ) { 4483 var $wrap = null, uploadView; 4484 4485 this.containerDragleave( event ); 4486 this.dropzoneDragleave( event ); 4487 4488 this.files = event.originalEvent.dataTransfer.files; 4489 if ( this.files.length < 1 ) { 4490 return; 4491 } 4492 4493 // Set the active editor to the drop target. 4494 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 4495 if ( $wrap.length > 0 && $wrap[0].id ) { 4496 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 4497 } 4498 4499 if ( ! this.workflow ) { 4500 this.workflow = wp.media.editor.open( 'content', { 4501 frame: 'post', 4502 state: 'insert', 4503 title: l10n.addMedia, 4504 multiple: true 4505 }); 4506 uploadView = this.workflow.uploader; 4507 if ( uploadView.uploader && uploadView.uploader.ready ) { 4508 this.addFiles.apply( this ); 4509 } else { 4510 this.workflow.on( 'uploader:ready', this.addFiles, this ); 4511 } 4512 } else { 4513 this.workflow.state().reset(); 4514 this.addFiles.apply( this ); 4515 this.workflow.open(); 4516 } 4517 4518 return false; 4519 }, 4520 4521 /** 4522 * Add the files to the uploader. 4523 */ 4524 addFiles: function() { 4525 if ( this.files.length ) { 4526 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 4527 this.files = []; 4528 } 4529 return this; 4530 }, 4531 4532 containerDragover: function( event ) { 4533 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 4534 return; 4535 } 4536 4537 this.overContainer = true; 4538 this.refresh(); 4539 }, 4540 4541 containerDragleave: function() { 4542 this.overContainer = false; 4543 4544 // Throttle dragleave because it's called when bouncing from some elements to others. 4545 _.delay( _.bind( this.refresh, this ), 50 ); 4546 }, 4547 4548 dropzoneDragover: function( event ) { 4549 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 4550 return; 4551 } 4552 4553 this.overDropzone = true; 4554 this.refresh( event ); 4555 return false; 4556 }, 4557 4558 dropzoneDragleave: function( e ) { 4559 this.overDropzone = false; 4560 _.delay( _.bind( this.refresh, this, e ), 50 ); 4561 }, 4562 4563 click: function( e ) { 4564 // In the rare case where the dropzone gets stuck, hide it on click. 4565 this.containerDragleave( e ); 4566 this.dropzoneDragleave( e ); 4567 this.localDrag = false; 4568 } 4569 }); 4570 4571 module.exports = EditorUploader; 4572 4573 4574 /***/ }), 4575 /* 55 */ 4576 /***/ (function(module, exports) { 4577 4578 /*globals wp, _ */ 4579 4580 /** 4581 * wp.media.view.UploaderInline 4582 * 4583 * The inline uploader that shows up in the 'Upload Files' tab. 4584 * 4585 * @class 4586 * @augments wp.media.View 4587 * @augments wp.Backbone.View 4588 * @augments Backbone.View 4589 */ 4590 var View = wp.media.View, 4591 UploaderInline; 4592 4593 UploaderInline = View.extend({ 4594 tagName: 'div', 4595 className: 'uploader-inline', 4596 template: wp.template('uploader-inline'), 4597 4598 events: { 4599 'click .close': 'hide' 4600 }, 4601 4602 initialize: function() { 4603 _.defaults( this.options, { 4604 message: '', 4605 status: true, 4606 canClose: false 4607 }); 4608 4609 if ( ! this.options.$browser && this.controller.uploader ) { 4610 this.options.$browser = this.controller.uploader.$browser; 4611 } 4612 4613 if ( _.isUndefined( this.options.postId ) ) { 4614 this.options.postId = wp.media.view.settings.post.id; 4615 } 4616 4617 if ( this.options.status ) { 4618 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 4619 controller: this.controller 4620 }) ); 4621 } 4622 }, 4623 4624 prepare: function() { 4625 var suggestedWidth = this.controller.state().get('suggestedWidth'), 4626 suggestedHeight = this.controller.state().get('suggestedHeight'), 4627 data = {}; 4628 4629 data.message = this.options.message; 4630 data.canClose = this.options.canClose; 4631 4632 if ( suggestedWidth && suggestedHeight ) { 4633 data.suggestedWidth = suggestedWidth; 4634 data.suggestedHeight = suggestedHeight; 4635 } 4636 4637 return data; 4638 }, 4639 /** 4640 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 2258 this.listenTo( this.model, 'change:compat', this.render ); 2259 }, 2260 /** 2261 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 4641 2262 */ 4642 2263 dispose: function() { 4643 if ( this.disposing ) { 4644 /** 4645 * call 'dispose' directly on the parent class 4646 */ 4647 return View.prototype.dispose.apply( this, arguments ); 4648 } 4649 4650 // Run remove on `dispose`, so we can be sure to refresh the 4651 // uploader with a view-less DOM. Track whether we're disposing 4652 // so we don't trigger an infinite loop. 4653 this.disposing = true; 4654 return this.remove(); 4655 }, 4656 /** 4657 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 4658 */ 4659 remove: function() { 4660 /** 4661 * call 'remove' directly on the parent class 4662 */ 4663 var result = View.prototype.remove.apply( this, arguments ); 4664 4665 _.defer( _.bind( this.refresh, this ) ); 4666 return result; 4667 }, 4668 4669 refresh: function() { 4670 var uploader = this.controller.uploader; 4671 4672 if ( uploader ) { 4673 uploader.refresh(); 4674 } 4675 }, 4676 /** 4677 * @returns {wp.media.view.UploaderInline} 4678 */ 4679 ready: function() { 4680 var $browser = this.options.$browser, 4681 $placeholder; 4682 4683 if ( this.controller.uploader ) { 4684 $placeholder = this.$('.browser'); 4685 4686 // Check if we've already replaced the placeholder. 4687 if ( $placeholder[0] === $browser[0] ) { 4688 return; 4689 } 4690 4691 $browser.detach().text( $placeholder.text() ); 4692 $browser[0].className = $placeholder[0].className; 4693 $placeholder.replaceWith( $browser.show() ); 4694 } 4695 4696 this.refresh(); 4697 return this; 4698 }, 4699 show: function() { 4700 this.$el.removeClass( 'hidden' ); 4701 }, 4702 hide: function() { 4703 this.$el.addClass( 'hidden' ); 4704 } 4705 4706 }); 4707 4708 module.exports = UploaderInline; 4709 4710 4711 /***/ }), 4712 /* 56 */ 4713 /***/ (function(module, exports) { 4714 4715 /*globals wp, _ */ 4716 4717 /** 4718 * wp.media.view.UploaderStatus 4719 * 4720 * An uploader status for on-going uploads. 4721 * 4722 * @class 4723 * @augments wp.media.View 4724 * @augments wp.Backbone.View 4725 * @augments Backbone.View 4726 */ 4727 var View = wp.media.View, 4728 UploaderStatus; 4729 4730 UploaderStatus = View.extend({ 4731 className: 'media-uploader-status', 4732 template: wp.template('uploader-status'), 4733 4734 events: { 4735 'click .upload-dismiss-errors': 'dismiss' 4736 }, 4737 4738 initialize: function() { 4739 this.queue = wp.Uploader.queue; 4740 this.queue.on( 'add remove reset', this.visibility, this ); 4741 this.queue.on( 'add remove reset change:percent', this.progress, this ); 4742 this.queue.on( 'add remove reset change:uploading', this.info, this ); 4743 4744 this.errors = wp.Uploader.errors; 4745 this.errors.reset(); 4746 this.errors.on( 'add remove reset', this.visibility, this ); 4747 this.errors.on( 'add', this.error, this ); 4748 }, 4749 /** 4750 * @global wp.Uploader 4751 * @returns {wp.media.view.UploaderStatus} 4752 */ 4753 dispose: function() { 4754 wp.Uploader.queue.off( null, null, this ); 4755 /** 4756 * call 'dispose' directly on the parent class 4757 */ 4758 View.prototype.dispose.apply( this, arguments ); 4759 return this; 4760 }, 4761 4762 visibility: function() { 4763 this.$el.toggleClass( 'uploading', !! this.queue.length ); 4764 this.$el.toggleClass( 'errors', !! this.errors.length ); 4765 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 4766 }, 4767 4768 ready: function() { 4769 _.each({ 4770 '$bar': '.media-progress-bar div', 4771 '$index': '.upload-index', 4772 '$total': '.upload-total', 4773 '$filename': '.upload-filename' 4774 }, function( selector, key ) { 4775 this[ key ] = this.$( selector ); 4776 }, this ); 4777 4778 this.visibility(); 4779 this.progress(); 4780 this.info(); 4781 }, 4782 4783 progress: function() { 4784 var queue = this.queue, 4785 $bar = this.$bar; 4786 4787 if ( ! $bar || ! queue.length ) { 4788 return; 4789 } 4790 4791 $bar.width( ( queue.reduce( function( memo, attachment ) { 4792 if ( ! attachment.get('uploading') ) { 4793 return memo + 100; 4794 } 4795 4796 var percent = attachment.get('percent'); 4797 return memo + ( _.isNumber( percent ) ? percent : 100 ); 4798 }, 0 ) / queue.length ) + '%' ); 4799 }, 4800 4801 info: function() { 4802 var queue = this.queue, 4803 index = 0, active; 4804 4805 if ( ! queue.length ) { 4806 return; 4807 } 4808 4809 active = this.queue.find( function( attachment, i ) { 4810 index = i; 4811 return attachment.get('uploading'); 4812 }); 4813 4814 this.$index.text( index + 1 ); 4815 this.$total.text( queue.length ); 4816 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 4817 }, 4818 /** 4819 * @param {string} filename 4820 * @returns {string} 4821 */ 4822 filename: function( filename ) { 4823 return wp.media.truncate( _.escape( filename ), 24 ); 4824 }, 4825 /** 4826 * @param {Backbone.Model} error 4827 */ 4828 error: function( error ) { 4829 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 4830 filename: this.filename( error.get('file').name ), 4831 message: error.get('message') 4832 }), { at: 0 }); 4833 }, 4834 4835 /** 4836 * @global wp.Uploader 4837 * 4838 * @param {Object} event 4839 */ 4840 dismiss: function( event ) { 4841 var errors = this.views.get('.upload-errors'); 4842 4843 event.preventDefault(); 4844 4845 if ( errors ) { 4846 _.invoke( errors, 'remove' ); 4847 } 4848 wp.Uploader.errors.reset(); 4849 } 4850 }); 4851 4852 module.exports = UploaderStatus; 4853 4854 4855 /***/ }), 4856 /* 57 */ 4857 /***/ (function(module, exports) { 4858 4859 /*globals wp */ 4860 4861 /** 4862 * wp.media.view.UploaderStatusError 4863 * 4864 * @class 4865 * @augments wp.media.View 4866 * @augments wp.Backbone.View 4867 * @augments Backbone.View 4868 */ 4869 var UploaderStatusError = wp.media.View.extend({ 4870 className: 'upload-error', 4871 template: wp.template('uploader-status-error') 4872 }); 4873 4874 module.exports = UploaderStatusError; 4875 4876 4877 /***/ }), 4878 /* 58 */ 4879 /***/ (function(module, exports) { 4880 4881 /*globals _, Backbone */ 4882 4883 /** 4884 * wp.media.view.Toolbar 4885 * 4886 * A toolbar which consists of a primary and a secondary section. Each sections 4887 * can be filled with views. 4888 * 4889 * @class 4890 * @augments wp.media.View 4891 * @augments wp.Backbone.View 4892 * @augments Backbone.View 4893 */ 4894 var View = wp.media.View, 4895 Toolbar; 4896 4897 Toolbar = View.extend({ 4898 tagName: 'div', 4899 className: 'media-toolbar', 4900 4901 initialize: function() { 4902 var state = this.controller.state(), 4903 selection = this.selection = state.get('selection'), 4904 library = this.library = state.get('library'); 4905 4906 this._views = {}; 4907 4908 // The toolbar is composed of two `PriorityList` views. 4909 this.primary = new wp.media.view.PriorityList(); 4910 this.secondary = new wp.media.view.PriorityList(); 4911 this.primary.$el.addClass('media-toolbar-primary search-form'); 4912 this.secondary.$el.addClass('media-toolbar-secondary'); 4913 4914 this.views.set([ this.secondary, this.primary ]); 4915 4916 if ( this.options.items ) { 4917 this.set( this.options.items, { silent: true }); 4918 } 4919 4920 if ( ! this.options.silent ) { 4921 this.render(); 4922 } 4923 4924 if ( selection ) { 4925 selection.on( 'add remove reset', this.refresh, this ); 4926 } 4927 4928 if ( library ) { 4929 library.on( 'add remove reset', this.refresh, this ); 4930 } 4931 }, 4932 /** 4933 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining 4934 */ 4935 dispose: function() { 4936 if ( this.selection ) { 4937 this.selection.off( null, null, this ); 4938 } 4939 4940 if ( this.library ) { 4941 this.library.off( null, null, this ); 2264 if ( this.$(':focus').length ) { 2265 this.save(); 4942 2266 } 4943 2267 /** … … 4946 2270 return View.prototype.dispose.apply( this, arguments ); 4947 2271 }, 4948 4949 ready: function() { 4950 this.refresh(); 4951 }, 4952 4953 /** 4954 * @param {string} id 4955 * @param {Backbone.View|Object} view 4956 * @param {Object} [options={}] 4957 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 4958 */ 4959 set: function( id, view, options ) { 4960 var list; 4961 options = options || {}; 4962 4963 // Accept an object with an `id` : `view` mapping. 4964 if ( _.isObject( id ) ) { 4965 _.each( id, function( view, id ) { 4966 this.set( id, view, { silent: true }); 4967 }, this ); 4968 4969 } else { 4970 if ( ! ( view instanceof Backbone.View ) ) { 4971 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 4972 view = new wp.media.view.Button( view ).render(); 4973 } 4974 4975 view.controller = view.controller || this.controller; 4976 4977 this._views[ id ] = view; 4978 4979 list = view.options.priority < 0 ? 'secondary' : 'primary'; 4980 this[ list ].set( id, view, options ); 4981 } 4982 4983 if ( ! options.silent ) { 4984 this.refresh(); 4985 } 4986 2272 /** 2273 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 2274 */ 2275 render: function() { 2276 var compat = this.model.get('compat'); 2277 if ( ! compat || ! compat.item ) { 2278 return; 2279 } 2280 2281 this.views.detach(); 2282 this.$el.html( compat.item ); 2283 this.views.render(); 4987 2284 return this; 4988 2285 }, 4989 2286 /** 4990 * @param {string} id 4991 * @returns {wp.media.view.Button} 4992 */ 4993 get: function( id ) { 4994 return this._views[ id ]; 4995 }, 4996 /** 4997 * @param {string} id 4998 * @param {Object} options 4999 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 5000 */ 5001 unset: function( id, options ) { 5002 delete this._views[ id ]; 5003 this.primary.unset( id, options ); 5004 this.secondary.unset( id, options ); 5005 5006 if ( ! options || ! options.silent ) { 5007 this.refresh(); 5008 } 5009 return this; 5010 }, 5011 5012 refresh: function() { 5013 var state = this.controller.state(), 5014 library = state.get('library'), 5015 selection = state.get('selection'); 5016 5017 _.each( this._views, function( button ) { 5018 if ( ! button.model || ! button.options || ! button.options.requires ) { 5019 return; 5020 } 5021 5022 var requires = button.options.requires, 5023 disabled = false; 5024 5025 // Prevent insertion of attachments if any of them are still uploading 5026 disabled = _.some( selection.models, function( attachment ) { 5027 return attachment.get('uploading') === true; 5028 }); 5029 5030 if ( requires.selection && selection && ! selection.length ) { 5031 disabled = true; 5032 } else if ( requires.library && library && ! library.length ) { 5033 disabled = true; 5034 } 5035 button.model.set( 'disabled', disabled ); 2287 * @param {Object} event 2288 */ 2289 preventDefault: function( event ) { 2290 event.preventDefault(); 2291 }, 2292 /** 2293 * @param {Object} event 2294 */ 2295 save: function( event ) { 2296 var data = {}; 2297 2298 if ( event ) { 2299 event.preventDefault(); 2300 } 2301 2302 _.each( this.$el.serializeArray(), function( pair ) { 2303 data[ pair.name ] = pair.value; 5036 2304 }); 2305 2306 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 2307 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 2308 }, 2309 2310 postSave: function() { 2311 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 5037 2312 } 5038 2313 }); 5039 2314 5040 module.exports = Toolbar; 5041 5042 5043 /***/ }), 5044 /* 59 */ 5045 /***/ (function(module, exports) { 5046 5047 /*globals wp, _ */ 5048 5049 /** 5050 * wp.media.view.Toolbar.Select 5051 * 5052 * @class 5053 * @augments wp.media.view.Toolbar 5054 * @augments wp.media.View 5055 * @augments wp.Backbone.View 5056 * @augments Backbone.View 5057 */ 5058 var Toolbar = wp.media.view.Toolbar, 5059 l10n = wp.media.view.l10n, 5060 Select; 5061 5062 Select = Toolbar.extend({ 5063 initialize: function() { 5064 var options = this.options; 5065 5066 _.bindAll( this, 'clickSelect' ); 5067 5068 _.defaults( options, { 5069 event: 'select', 5070 state: false, 5071 reset: true, 5072 close: true, 5073 text: l10n.select, 5074 5075 // Does the button rely on the selection? 5076 requires: { 5077 selection: true 5078 } 5079 }); 5080 5081 options.items = _.defaults( options.items || {}, { 5082 select: { 5083 style: 'primary', 5084 text: options.text, 5085 priority: 80, 5086 click: this.clickSelect, 5087 requires: options.requires 5088 } 5089 }); 5090 // Call 'initialize' directly on the parent class. 5091 Toolbar.prototype.initialize.apply( this, arguments ); 5092 }, 5093 5094 clickSelect: function() { 5095 var options = this.options, 5096 controller = this.controller; 5097 5098 if ( options.close ) { 5099 controller.close(); 5100 } 5101 5102 if ( options.event ) { 5103 controller.state().trigger( options.event ); 5104 } 5105 5106 if ( options.state ) { 5107 controller.setState( options.state ); 5108 } 5109 5110 if ( options.reset ) { 5111 controller.reset(); 5112 } 5113 } 5114 }); 5115 5116 module.exports = Select; 5117 5118 5119 /***/ }), 5120 /* 60 */ 5121 /***/ (function(module, exports) { 5122 5123 /*globals wp, _ */ 5124 5125 /** 5126 * wp.media.view.Toolbar.Embed 5127 * 5128 * @class 5129 * @augments wp.media.view.Toolbar.Select 5130 * @augments wp.media.view.Toolbar 5131 * @augments wp.media.View 5132 * @augments wp.Backbone.View 5133 * @augments Backbone.View 5134 */ 5135 var Select = wp.media.view.Toolbar.Select, 5136 l10n = wp.media.view.l10n, 5137 Embed; 5138 5139 Embed = Select.extend({ 5140 initialize: function() { 5141 _.defaults( this.options, { 5142 text: l10n.insertIntoPost, 5143 requires: false 5144 }); 5145 // Call 'initialize' directly on the parent class. 5146 Select.prototype.initialize.apply( this, arguments ); 5147 }, 5148 5149 refresh: function() { 5150 var url = this.controller.state().props.get('url'); 5151 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 5152 /** 5153 * call 'refresh' directly on the parent class 5154 */ 5155 Select.prototype.refresh.apply( this, arguments ); 5156 } 5157 }); 5158 5159 module.exports = Embed; 5160 5161 5162 /***/ }), 5163 /* 61 */ 5164 /***/ (function(module, exports) { 5165 5166 /*globals _, Backbone */ 5167 5168 /** 5169 * wp.media.view.Button 5170 * 5171 * @class 5172 * @augments wp.media.View 5173 * @augments wp.Backbone.View 5174 * @augments Backbone.View 5175 */ 5176 var Button = wp.media.View.extend({ 5177 tagName: 'a', 5178 className: 'media-button', 5179 attributes: { href: '#' }, 5180 5181 events: { 5182 'click': 'click' 5183 }, 5184 5185 defaults: { 5186 text: '', 5187 style: '', 5188 size: 'large', 5189 disabled: false 5190 }, 5191 5192 initialize: function() { 5193 /** 5194 * Create a model with the provided `defaults`. 5195 * 5196 * @member {Backbone.Model} 5197 */ 5198 this.model = new Backbone.Model( this.defaults ); 5199 5200 // If any of the `options` have a key from `defaults`, apply its 5201 // value to the `model` and remove it from the `options object. 5202 _.each( this.defaults, function( def, key ) { 5203 var value = this.options[ key ]; 5204 if ( _.isUndefined( value ) ) { 5205 return; 5206 } 5207 5208 this.model.set( key, value ); 5209 delete this.options[ key ]; 5210 }, this ); 5211 5212 this.listenTo( this.model, 'change', this.render ); 5213 }, 5214 /** 5215 * @returns {wp.media.view.Button} Returns itself to allow chaining 5216 */ 5217 render: function() { 5218 var classes = [ 'button', this.className ], 5219 model = this.model.toJSON(); 5220 5221 if ( model.style ) { 5222 classes.push( 'button-' + model.style ); 5223 } 5224 5225 if ( model.size ) { 5226 classes.push( 'button-' + model.size ); 5227 } 5228 5229 classes = _.uniq( classes.concat( this.options.classes ) ); 5230 this.el.className = classes.join(' '); 5231 5232 this.$el.attr( 'disabled', model.disabled ); 5233 this.$el.text( this.model.get('text') ); 5234 5235 return this; 5236 }, 5237 /** 5238 * @param {Object} event 5239 */ 5240 click: function( event ) { 5241 if ( '#' === this.attributes.href ) { 5242 event.preventDefault(); 5243 } 5244 5245 if ( this.options.click && ! this.model.get('disabled') ) { 5246 this.options.click.apply( this, arguments ); 5247 } 5248 } 5249 }); 5250 5251 module.exports = Button; 5252 5253 5254 /***/ }), 5255 /* 62 */ 5256 /***/ (function(module, exports) { 5257 5258 /*globals _, Backbone */ 5259 5260 /** 5261 * wp.media.view.ButtonGroup 5262 * 5263 * @class 5264 * @augments wp.media.View 5265 * @augments wp.Backbone.View 5266 * @augments Backbone.View 5267 */ 5268 var $ = Backbone.$, 5269 ButtonGroup; 5270 5271 ButtonGroup = wp.media.View.extend({ 5272 tagName: 'div', 5273 className: 'button-group button-large media-button-group', 5274 5275 initialize: function() { 5276 /** 5277 * @member {wp.media.view.Button[]} 5278 */ 5279 this.buttons = _.map( this.options.buttons || [], function( button ) { 5280 if ( button instanceof Backbone.View ) { 5281 return button; 5282 } else { 5283 return new wp.media.view.Button( button ).render(); 5284 } 5285 }); 5286 5287 delete this.options.buttons; 5288 5289 if ( this.options.classes ) { 5290 this.$el.addClass( this.options.classes ); 5291 } 5292 }, 5293 5294 /** 5295 * @returns {wp.media.view.ButtonGroup} 5296 */ 5297 render: function() { 5298 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 5299 return this; 5300 } 5301 }); 5302 5303 module.exports = ButtonGroup; 5304 5305 5306 /***/ }), 5307 /* 63 */ 5308 /***/ (function(module, exports) { 5309 5310 /*globals _, Backbone */ 5311 5312 /** 5313 * wp.media.view.PriorityList 5314 * 5315 * @class 5316 * @augments wp.media.View 5317 * @augments wp.Backbone.View 5318 * @augments Backbone.View 5319 */ 5320 var PriorityList = wp.media.View.extend({ 5321 tagName: 'div', 5322 5323 initialize: function() { 5324 this._views = {}; 5325 5326 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 5327 delete this.options.views; 5328 5329 if ( ! this.options.silent ) { 5330 this.render(); 5331 } 5332 }, 5333 /** 5334 * @param {string} id 5335 * @param {wp.media.View|Object} view 5336 * @param {Object} options 5337 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining 5338 */ 5339 set: function( id, view, options ) { 5340 var priority, views, index; 5341 5342 options = options || {}; 5343 5344 // Accept an object with an `id` : `view` mapping. 5345 if ( _.isObject( id ) ) { 5346 _.each( id, function( view, id ) { 5347 this.set( id, view ); 5348 }, this ); 5349 return this; 5350 } 5351 5352 if ( ! (view instanceof Backbone.View) ) { 5353 view = this.toView( view, id, options ); 5354 } 5355 view.controller = view.controller || this.controller; 5356 5357 this.unset( id ); 5358 5359 priority = view.options.priority || 10; 5360 views = this.views.get() || []; 5361 5362 _.find( views, function( existing, i ) { 5363 if ( existing.options.priority > priority ) { 5364 index = i; 5365 return true; 5366 } 5367 }); 5368 5369 this._views[ id ] = view; 5370 this.views.add( view, { 5371 at: _.isNumber( index ) ? index : views.length || 0 5372 }); 5373 5374 return this; 5375 }, 5376 /** 5377 * @param {string} id 5378 * @returns {wp.media.View} 5379 */ 5380 get: function( id ) { 5381 return this._views[ id ]; 5382 }, 5383 /** 5384 * @param {string} id 5385 * @returns {wp.media.view.PriorityList} 5386 */ 5387 unset: function( id ) { 5388 var view = this.get( id ); 5389 5390 if ( view ) { 5391 view.remove(); 5392 } 5393 5394 delete this._views[ id ]; 5395 return this; 5396 }, 5397 /** 5398 * @param {Object} options 5399 * @returns {wp.media.View} 5400 */ 5401 toView: function( options ) { 5402 return new wp.media.View( options ); 5403 } 5404 }); 5405 5406 module.exports = PriorityList; 5407 5408 5409 /***/ }), 5410 /* 64 */ 5411 /***/ (function(module, exports) { 5412 5413 /*globals jQuery */ 5414 5415 /** 5416 * wp.media.view.MenuItem 5417 * 5418 * @class 5419 * @augments wp.media.View 5420 * @augments wp.Backbone.View 5421 * @augments Backbone.View 5422 */ 5423 var $ = jQuery, 5424 MenuItem; 5425 5426 MenuItem = wp.media.View.extend({ 5427 tagName: 'a', 5428 className: 'media-menu-item', 5429 5430 attributes: { 5431 href: '#' 5432 }, 5433 5434 events: { 5435 'click': '_click' 5436 }, 5437 /** 5438 * @param {Object} event 5439 */ 5440 _click: function( event ) { 5441 var clickOverride = this.options.click; 5442 5443 if ( event ) { 5444 event.preventDefault(); 5445 } 5446 5447 if ( clickOverride ) { 5448 clickOverride.call( this ); 5449 } else { 5450 this.click(); 5451 } 5452 5453 // When selecting a tab along the left side, 5454 // focus should be transferred into the main panel 5455 if ( ! wp.media.isTouchDevice ) { 5456 $('.media-frame-content input').first().focus(); 5457 } 5458 }, 5459 5460 click: function() { 5461 var state = this.options.state; 5462 5463 if ( state ) { 5464 this.controller.setState( state ); 5465 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 5466 } 5467 }, 5468 /** 5469 * @returns {wp.media.view.MenuItem} returns itself to allow chaining 5470 */ 5471 render: function() { 5472 var options = this.options; 5473 5474 if ( options.text ) { 5475 this.$el.text( options.text ); 5476 } else if ( options.html ) { 5477 this.$el.html( options.html ); 5478 } 5479 5480 return this; 5481 } 5482 }); 5483 5484 module.exports = MenuItem; 5485 5486 5487 /***/ }), 5488 /* 65 */ 5489 /***/ (function(module, exports) { 5490 5491 /** 5492 * wp.media.view.Menu 5493 * 5494 * @class 5495 * @augments wp.media.view.PriorityList 5496 * @augments wp.media.View 5497 * @augments wp.Backbone.View 5498 * @augments Backbone.View 5499 */ 5500 var MenuItem = wp.media.view.MenuItem, 5501 PriorityList = wp.media.view.PriorityList, 5502 Menu; 5503 5504 Menu = PriorityList.extend({ 5505 tagName: 'div', 5506 className: 'media-menu', 5507 property: 'state', 5508 ItemView: MenuItem, 5509 region: 'menu', 5510 5511 /* TODO: alternatively hide on any click anywhere 5512 events: { 5513 'click': 'click' 5514 }, 5515 5516 click: function() { 5517 this.$el.removeClass( 'visible' ); 5518 }, 5519 */ 5520 5521 /** 5522 * @param {Object} options 5523 * @param {string} id 5524 * @returns {wp.media.View} 5525 */ 5526 toView: function( options, id ) { 5527 options = options || {}; 5528 options[ this.property ] = options[ this.property ] || id; 5529 return new this.ItemView( options ).render(); 5530 }, 5531 5532 ready: function() { 5533 /** 5534 * call 'ready' directly on the parent class 5535 */ 5536 PriorityList.prototype.ready.apply( this, arguments ); 5537 this.visibility(); 5538 }, 5539 5540 set: function() { 5541 /** 5542 * call 'set' directly on the parent class 5543 */ 5544 PriorityList.prototype.set.apply( this, arguments ); 5545 this.visibility(); 5546 }, 5547 5548 unset: function() { 5549 /** 5550 * call 'unset' directly on the parent class 5551 */ 5552 PriorityList.prototype.unset.apply( this, arguments ); 5553 this.visibility(); 5554 }, 5555 5556 visibility: function() { 5557 var region = this.region, 5558 view = this.controller[ region ].get(), 5559 views = this.views.get(), 5560 hide = ! views || views.length < 2; 5561 5562 if ( this === view ) { 5563 this.controller.$el.toggleClass( 'hide-' + region, hide ); 5564 } 5565 }, 5566 /** 5567 * @param {string} id 5568 */ 5569 select: function( id ) { 5570 var view = this.get( id ); 5571 5572 if ( ! view ) { 5573 return; 5574 } 5575 5576 this.deselect(); 5577 view.$el.addClass('active'); 5578 }, 5579 5580 deselect: function() { 5581 this.$el.children().removeClass('active'); 5582 }, 5583 5584 hide: function( id ) { 5585 var view = this.get( id ); 5586 5587 if ( ! view ) { 5588 return; 5589 } 5590 5591 view.$el.addClass('hidden'); 5592 }, 5593 5594 show: function( id ) { 5595 var view = this.get( id ); 5596 5597 if ( ! view ) { 5598 return; 5599 } 5600 5601 view.$el.removeClass('hidden'); 5602 } 5603 }); 5604 5605 module.exports = Menu; 5606 5607 5608 /***/ }), 5609 /* 66 */ 5610 /***/ (function(module, exports) { 5611 5612 /** 5613 * wp.media.view.RouterItem 5614 * 5615 * @class 5616 * @augments wp.media.view.MenuItem 5617 * @augments wp.media.View 5618 * @augments wp.Backbone.View 5619 * @augments Backbone.View 5620 */ 5621 var RouterItem = wp.media.view.MenuItem.extend({ 5622 /** 5623 * On click handler to activate the content region's corresponding mode. 5624 */ 5625 click: function() { 5626 var contentMode = this.options.contentMode; 5627 if ( contentMode ) { 5628 this.controller.content.mode( contentMode ); 5629 } 5630 } 5631 }); 5632 5633 module.exports = RouterItem; 5634 5635 5636 /***/ }), 5637 /* 67 */ 5638 /***/ (function(module, exports) { 5639 5640 /*globals wp */ 5641 5642 /** 5643 * wp.media.view.Router 5644 * 5645 * @class 5646 * @augments wp.media.view.Menu 5647 * @augments wp.media.view.PriorityList 5648 * @augments wp.media.View 5649 * @augments wp.Backbone.View 5650 * @augments Backbone.View 5651 */ 5652 var Menu = wp.media.view.Menu, 5653 Router; 5654 5655 Router = Menu.extend({ 5656 tagName: 'div', 5657 className: 'media-router', 5658 property: 'contentMode', 5659 ItemView: wp.media.view.RouterItem, 5660 region: 'router', 5661 5662 initialize: function() { 5663 this.controller.on( 'content:render', this.update, this ); 5664 // Call 'initialize' directly on the parent class. 5665 Menu.prototype.initialize.apply( this, arguments ); 5666 }, 5667 5668 update: function() { 5669 var mode = this.controller.content.mode(); 5670 if ( mode ) { 5671 this.select( mode ); 5672 } 5673 } 5674 }); 5675 5676 module.exports = Router; 5677 5678 5679 /***/ }), 5680 /* 68 */ 5681 /***/ (function(module, exports) { 5682 5683 /** 5684 * wp.media.view.Sidebar 5685 * 5686 * @class 5687 * @augments wp.media.view.PriorityList 5688 * @augments wp.media.View 5689 * @augments wp.Backbone.View 5690 * @augments Backbone.View 5691 */ 5692 var Sidebar = wp.media.view.PriorityList.extend({ 5693 className: 'media-sidebar' 5694 }); 5695 5696 module.exports = Sidebar; 5697 5698 5699 /***/ }), 5700 /* 69 */ 5701 /***/ (function(module, exports) { 5702 5703 /*globals wp, _, jQuery */ 5704 5705 /** 5706 * wp.media.view.Attachment 5707 * 5708 * @class 5709 * @augments wp.media.View 5710 * @augments wp.Backbone.View 5711 * @augments Backbone.View 5712 */ 5713 var View = wp.media.View, 5714 $ = jQuery, 5715 Attachment; 5716 5717 Attachment = View.extend({ 5718 tagName: 'li', 5719 className: 'attachment', 5720 template: wp.template('attachment'), 5721 5722 attributes: function() { 5723 return { 5724 'tabIndex': 0, 5725 'role': 'checkbox', 5726 'aria-label': this.model.get( 'title' ), 5727 'aria-checked': false, 5728 'data-id': this.model.get( 'id' ) 5729 }; 5730 }, 5731 5732 events: { 5733 'click .js--select-attachment': 'toggleSelectionHandler', 5734 'change [data-setting]': 'updateSetting', 5735 'change [data-setting] input': 'updateSetting', 5736 'change [data-setting] select': 'updateSetting', 5737 'change [data-setting] textarea': 'updateSetting', 5738 'click .close': 'removeFromLibrary', 5739 'click .check': 'checkClickHandler', 5740 'click a': 'preventDefault', 5741 'keydown .close': 'removeFromLibrary', 5742 'keydown': 'toggleSelectionHandler' 5743 }, 5744 5745 buttons: {}, 5746 5747 initialize: function() { 5748 var selection = this.options.selection, 5749 options = _.defaults( this.options, { 5750 rerenderOnModelChange: true 5751 } ); 5752 5753 if ( options.rerenderOnModelChange ) { 5754 this.listenTo( this.model, 'change', this.render ); 5755 } else { 5756 this.listenTo( this.model, 'change:percent', this.progress ); 5757 } 5758 this.listenTo( this.model, 'change:title', this._syncTitle ); 5759 this.listenTo( this.model, 'change:caption', this._syncCaption ); 5760 this.listenTo( this.model, 'change:artist', this._syncArtist ); 5761 this.listenTo( this.model, 'change:album', this._syncAlbum ); 5762 5763 // Update the selection. 5764 this.listenTo( this.model, 'add', this.select ); 5765 this.listenTo( this.model, 'remove', this.deselect ); 5766 if ( selection ) { 5767 selection.on( 'reset', this.updateSelect, this ); 5768 // Update the model's details view. 5769 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 5770 this.details( this.model, this.controller.state().get('selection') ); 5771 } 5772 5773 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 5774 }, 5775 /** 5776 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 5777 */ 5778 dispose: function() { 5779 var selection = this.options.selection; 5780 5781 // Make sure all settings are saved before removing the view. 5782 this.updateAll(); 5783 5784 if ( selection ) { 5785 selection.off( null, null, this ); 5786 } 5787 /** 5788 * call 'dispose' directly on the parent class 5789 */ 5790 View.prototype.dispose.apply( this, arguments ); 5791 return this; 5792 }, 5793 /** 5794 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 5795 */ 5796 render: function() { 5797 var options = _.defaults( this.model.toJSON(), { 5798 orientation: 'landscape', 5799 uploading: false, 5800 type: '', 5801 subtype: '', 5802 icon: '', 5803 filename: '', 5804 caption: '', 5805 title: '', 5806 dateFormatted: '', 5807 width: '', 5808 height: '', 5809 compat: false, 5810 alt: '', 5811 description: '' 5812 }, this.options ); 5813 5814 options.buttons = this.buttons; 5815 options.describe = this.controller.state().get('describe'); 5816 5817 if ( 'image' === options.type ) { 5818 options.size = this.imageSize(); 5819 } 5820 5821 options.can = {}; 5822 if ( options.nonces ) { 5823 options.can.remove = !! options.nonces['delete']; 5824 options.can.save = !! options.nonces.update; 5825 } 5826 5827 if ( this.controller.state().get('allowLocalEdits') ) { 5828 options.allowLocalEdits = true; 5829 } 5830 5831 if ( options.uploading && ! options.percent ) { 5832 options.percent = 0; 5833 } 5834 5835 this.views.detach(); 5836 this.$el.html( this.template( options ) ); 5837 5838 this.$el.toggleClass( 'uploading', options.uploading ); 5839 5840 if ( options.uploading ) { 5841 this.$bar = this.$('.media-progress-bar div'); 5842 } else { 5843 delete this.$bar; 5844 } 5845 5846 // Check if the model is selected. 5847 this.updateSelect(); 5848 5849 // Update the save status. 5850 this.updateSave(); 5851 5852 this.views.render(); 5853 5854 return this; 5855 }, 5856 5857 progress: function() { 5858 if ( this.$bar && this.$bar.length ) { 5859 this.$bar.width( this.model.get('percent') + '%' ); 5860 } 5861 }, 5862 5863 /** 5864 * @param {Object} event 5865 */ 5866 toggleSelectionHandler: function( event ) { 5867 var method; 5868 5869 // Don't do anything inside inputs. 5870 if ( 'INPUT' === event.target.nodeName ) { 5871 return; 5872 } 5873 5874 // Catch arrow events 5875 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 5876 this.controller.trigger( 'attachment:keydown:arrow', event ); 5877 return; 5878 } 5879 5880 // Catch enter and space events 5881 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 5882 return; 5883 } 5884 5885 event.preventDefault(); 5886 5887 // In the grid view, bubble up an edit:attachment event to the controller. 5888 if ( this.controller.isModeActive( 'grid' ) ) { 5889 if ( this.controller.isModeActive( 'edit' ) ) { 5890 // Pass the current target to restore focus when closing 5891 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 5892 return; 5893 } 5894 5895 if ( this.controller.isModeActive( 'select' ) ) { 5896 method = 'toggle'; 5897 } 5898 } 5899 5900 if ( event.shiftKey ) { 5901 method = 'between'; 5902 } else if ( event.ctrlKey || event.metaKey ) { 5903 method = 'toggle'; 5904 } 5905 5906 this.toggleSelection({ 5907 method: method 5908 }); 5909 5910 this.controller.trigger( 'selection:toggle' ); 5911 }, 5912 /** 5913 * @param {Object} options 5914 */ 5915 toggleSelection: function( options ) { 5916 var collection = this.collection, 5917 selection = this.options.selection, 5918 model = this.model, 5919 method = options && options.method, 5920 single, models, singleIndex, modelIndex; 5921 5922 if ( ! selection ) { 5923 return; 5924 } 5925 5926 single = selection.single(); 5927 method = _.isUndefined( method ) ? selection.multiple : method; 5928 5929 // If the `method` is set to `between`, select all models that 5930 // exist between the current and the selected model. 5931 if ( 'between' === method && single && selection.multiple ) { 5932 // If the models are the same, short-circuit. 5933 if ( single === model ) { 5934 return; 5935 } 5936 5937 singleIndex = collection.indexOf( single ); 5938 modelIndex = collection.indexOf( this.model ); 5939 5940 if ( singleIndex < modelIndex ) { 5941 models = collection.models.slice( singleIndex, modelIndex + 1 ); 5942 } else { 5943 models = collection.models.slice( modelIndex, singleIndex + 1 ); 5944 } 5945 5946 selection.add( models ); 5947 selection.single( model ); 5948 return; 5949 5950 // If the `method` is set to `toggle`, just flip the selection 5951 // status, regardless of whether the model is the single model. 5952 } else if ( 'toggle' === method ) { 5953 selection[ this.selected() ? 'remove' : 'add' ]( model ); 5954 selection.single( model ); 5955 return; 5956 } else if ( 'add' === method ) { 5957 selection.add( model ); 5958 selection.single( model ); 5959 return; 5960 } 5961 5962 // Fixes bug that loses focus when selecting a featured image 5963 if ( ! method ) { 5964 method = 'add'; 5965 } 5966 5967 if ( method !== 'add' ) { 5968 method = 'reset'; 5969 } 5970 5971 if ( this.selected() ) { 5972 // If the model is the single model, remove it. 5973 // If it is not the same as the single model, 5974 // it now becomes the single model. 5975 selection[ single === model ? 'remove' : 'single' ]( model ); 5976 } else { 5977 // If the model is not selected, run the `method` on the 5978 // selection. By default, we `reset` the selection, but the 5979 // `method` can be set to `add` the model to the selection. 5980 selection[ method ]( model ); 5981 selection.single( model ); 5982 } 5983 }, 5984 5985 updateSelect: function() { 5986 this[ this.selected() ? 'select' : 'deselect' ](); 5987 }, 5988 /** 5989 * @returns {unresolved|Boolean} 5990 */ 5991 selected: function() { 5992 var selection = this.options.selection; 5993 if ( selection ) { 5994 return !! selection.get( this.model.cid ); 5995 } 5996 }, 5997 /** 5998 * @param {Backbone.Model} model 5999 * @param {Backbone.Collection} collection 6000 */ 6001 select: function( model, collection ) { 6002 var selection = this.options.selection, 6003 controller = this.controller; 6004 6005 // Check if a selection exists and if it's the collection provided. 6006 // If they're not the same collection, bail; we're in another 6007 // selection's event loop. 6008 if ( ! selection || ( collection && collection !== selection ) ) { 6009 return; 6010 } 6011 6012 // Bail if the model is already selected. 6013 if ( this.$el.hasClass( 'selected' ) ) { 6014 return; 6015 } 6016 6017 // Add 'selected' class to model, set aria-checked to true. 6018 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 6019 // Make the checkbox tabable, except in media grid (bulk select mode). 6020 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 6021 this.$( '.check' ).attr( 'tabindex', '0' ); 6022 } 6023 }, 6024 /** 6025 * @param {Backbone.Model} model 6026 * @param {Backbone.Collection} collection 6027 */ 6028 deselect: function( model, collection ) { 6029 var selection = this.options.selection; 6030 6031 // Check if a selection exists and if it's the collection provided. 6032 // If they're not the same collection, bail; we're in another 6033 // selection's event loop. 6034 if ( ! selection || ( collection && collection !== selection ) ) { 6035 return; 6036 } 6037 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 6038 .find( '.check' ).attr( 'tabindex', '-1' ); 6039 }, 6040 /** 6041 * @param {Backbone.Model} model 6042 * @param {Backbone.Collection} collection 6043 */ 6044 details: function( model, collection ) { 6045 var selection = this.options.selection, 6046 details; 6047 6048 if ( selection !== collection ) { 6049 return; 6050 } 6051 6052 details = selection.single(); 6053 this.$el.toggleClass( 'details', details === this.model ); 6054 }, 6055 /** 6056 * @param {Object} event 6057 */ 6058 preventDefault: function( event ) { 6059 event.preventDefault(); 6060 }, 6061 /** 6062 * @param {string} size 6063 * @returns {Object} 6064 */ 6065 imageSize: function( size ) { 6066 var sizes = this.model.get('sizes'), matched = false; 6067 6068 size = size || 'medium'; 6069 6070 // Use the provided image size if possible. 6071 if ( sizes ) { 6072 if ( sizes[ size ] ) { 6073 matched = sizes[ size ]; 6074 } else if ( sizes.large ) { 6075 matched = sizes.large; 6076 } else if ( sizes.thumbnail ) { 6077 matched = sizes.thumbnail; 6078 } else if ( sizes.full ) { 6079 matched = sizes.full; 6080 } 6081 6082 if ( matched ) { 6083 return _.clone( matched ); 6084 } 6085 } 6086 6087 return { 6088 url: this.model.get('url'), 6089 width: this.model.get('width'), 6090 height: this.model.get('height'), 6091 orientation: this.model.get('orientation') 6092 }; 6093 }, 6094 /** 6095 * @param {Object} event 6096 */ 6097 updateSetting: function( event ) { 6098 var $setting = $( event.target ).closest('[data-setting]'), 6099 setting, value; 6100 6101 if ( ! $setting.length ) { 6102 return; 6103 } 6104 6105 setting = $setting.data('setting'); 6106 value = event.target.value; 6107 6108 if ( this.model.get( setting ) !== value ) { 6109 this.save( setting, value ); 6110 } 6111 }, 6112 6113 /** 6114 * Pass all the arguments to the model's save method. 6115 * 6116 * Records the aggregate status of all save requests and updates the 6117 * view's classes accordingly. 6118 */ 6119 save: function() { 6120 var view = this, 6121 save = this._save = this._save || { status: 'ready' }, 6122 request = this.model.save.apply( this.model, arguments ), 6123 requests = save.requests ? $.when( request, save.requests ) : request; 6124 6125 // If we're waiting to remove 'Saved.', stop. 6126 if ( save.savedTimer ) { 6127 clearTimeout( save.savedTimer ); 6128 } 6129 6130 this.updateSave('waiting'); 6131 save.requests = requests; 6132 requests.always( function() { 6133 // If we've performed another request since this one, bail. 6134 if ( save.requests !== requests ) { 6135 return; 6136 } 6137 6138 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 6139 save.savedTimer = setTimeout( function() { 6140 view.updateSave('ready'); 6141 delete save.savedTimer; 6142 }, 2000 ); 6143 }); 6144 }, 6145 /** 6146 * @param {string} status 6147 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 6148 */ 6149 updateSave: function( status ) { 6150 var save = this._save = this._save || { status: 'ready' }; 6151 6152 if ( status && status !== save.status ) { 6153 this.$el.removeClass( 'save-' + save.status ); 6154 save.status = status; 6155 } 6156 6157 this.$el.addClass( 'save-' + save.status ); 6158 return this; 6159 }, 6160 6161 updateAll: function() { 6162 var $settings = this.$('[data-setting]'), 6163 model = this.model, 6164 changed; 6165 6166 changed = _.chain( $settings ).map( function( el ) { 6167 var $input = $('input, textarea, select, [value]', el ), 6168 setting, value; 6169 6170 if ( ! $input.length ) { 6171 return; 6172 } 6173 6174 setting = $(el).data('setting'); 6175 value = $input.val(); 6176 6177 // Record the value if it changed. 6178 if ( model.get( setting ) !== value ) { 6179 return [ setting, value ]; 6180 } 6181 }).compact().object().value(); 6182 6183 if ( ! _.isEmpty( changed ) ) { 6184 model.save( changed ); 6185 } 6186 }, 6187 /** 6188 * @param {Object} event 6189 */ 6190 removeFromLibrary: function( event ) { 6191 // Catch enter and space events 6192 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 6193 return; 6194 } 6195 6196 // Stop propagation so the model isn't selected. 6197 event.stopPropagation(); 6198 6199 this.collection.remove( this.model ); 6200 }, 6201 6202 /** 6203 * Add the model if it isn't in the selection, if it is in the selection, 6204 * remove it. 6205 * 6206 * @param {[type]} event [description] 6207 * @return {[type]} [description] 6208 */ 6209 checkClickHandler: function ( event ) { 6210 var selection = this.options.selection; 6211 if ( ! selection ) { 6212 return; 6213 } 6214 event.stopPropagation(); 6215 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 6216 selection.remove( this.model ); 6217 // Move focus back to the attachment tile (from the check). 6218 this.$el.focus(); 6219 } else { 6220 selection.add( this.model ); 6221 } 6222 } 6223 }); 6224 6225 // Ensure settings remain in sync between attachment views. 6226 _.each({ 6227 caption: '_syncCaption', 6228 title: '_syncTitle', 6229 artist: '_syncArtist', 6230 album: '_syncAlbum' 6231 }, function( method, setting ) { 6232 /** 6233 * @param {Backbone.Model} model 6234 * @param {string} value 6235 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 6236 */ 6237 Attachment.prototype[ method ] = function( model, value ) { 6238 var $setting = this.$('[data-setting="' + setting + '"]'); 6239 6240 if ( ! $setting.length ) { 6241 return this; 6242 } 6243 6244 // If the updated value is in sync with the value in the DOM, there 6245 // is no need to re-render. If we're currently editing the value, 6246 // it will automatically be in sync, suppressing the re-render for 6247 // the view we're editing, while updating any others. 6248 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 6249 return this; 6250 } 6251 6252 return this.render(); 6253 }; 6254 }); 6255 6256 module.exports = Attachment; 6257 6258 6259 /***/ }), 6260 /* 70 */ 6261 /***/ (function(module, exports) { 6262 6263 /*globals wp */ 6264 6265 /** 6266 * wp.media.view.Attachment.Library 6267 * 6268 * @class 6269 * @augments wp.media.view.Attachment 6270 * @augments wp.media.View 6271 * @augments wp.Backbone.View 6272 * @augments Backbone.View 6273 */ 6274 var Library = wp.media.view.Attachment.extend({ 6275 buttons: { 6276 check: true 6277 } 6278 }); 6279 6280 module.exports = Library; 6281 6282 6283 /***/ }), 6284 /* 71 */ 6285 /***/ (function(module, exports) { 6286 6287 /*globals wp */ 6288 6289 /** 6290 * wp.media.view.Attachment.EditLibrary 6291 * 6292 * @class 6293 * @augments wp.media.view.Attachment 6294 * @augments wp.media.View 6295 * @augments wp.Backbone.View 6296 * @augments Backbone.View 6297 */ 6298 var EditLibrary = wp.media.view.Attachment.extend({ 6299 buttons: { 6300 close: true 6301 } 6302 }); 6303 6304 module.exports = EditLibrary; 6305 6306 6307 /***/ }), 6308 /* 72 */ 6309 /***/ (function(module, exports) { 6310 6311 /*globals wp, _, jQuery */ 6312 6313 /** 6314 * wp.media.view.Attachments 6315 * 6316 * @class 6317 * @augments wp.media.View 6318 * @augments wp.Backbone.View 6319 * @augments Backbone.View 6320 */ 6321 var View = wp.media.View, 6322 $ = jQuery, 6323 Attachments; 6324 6325 Attachments = View.extend({ 6326 tagName: 'ul', 6327 className: 'attachments', 6328 6329 attributes: { 6330 tabIndex: -1 6331 }, 6332 6333 initialize: function() { 6334 this.el.id = _.uniqueId('__attachments-view-'); 6335 6336 _.defaults( this.options, { 6337 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 6338 refreshThreshold: 3, 6339 AttachmentView: wp.media.view.Attachment, 6340 sortable: false, 6341 resize: true, 6342 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 6343 }); 6344 6345 this._viewsByCid = {}; 6346 this.$window = $( window ); 6347 this.resizeEvent = 'resize.media-modal-columns'; 6348 6349 this.collection.on( 'add', function( attachment ) { 6350 this.views.add( this.createAttachmentView( attachment ), { 6351 at: this.collection.indexOf( attachment ) 6352 }); 6353 }, this ); 6354 6355 this.collection.on( 'remove', function( attachment ) { 6356 var view = this._viewsByCid[ attachment.cid ]; 6357 delete this._viewsByCid[ attachment.cid ]; 6358 6359 if ( view ) { 6360 view.remove(); 6361 } 6362 }, this ); 6363 6364 this.collection.on( 'reset', this.render, this ); 6365 6366 this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus ); 6367 6368 // Throttle the scroll handler and bind this. 6369 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 6370 6371 this.options.scrollElement = this.options.scrollElement || this.el; 6372 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 6373 6374 this.initSortable(); 6375 6376 _.bindAll( this, 'setColumns' ); 6377 6378 if ( this.options.resize ) { 6379 this.on( 'ready', this.bindEvents ); 6380 this.controller.on( 'open', this.setColumns ); 6381 6382 // Call this.setColumns() after this view has been rendered in the DOM so 6383 // attachments get proper width applied. 6384 _.defer( this.setColumns, this ); 6385 } 6386 }, 6387 6388 bindEvents: function() { 6389 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 6390 }, 6391 6392 attachmentFocus: function() { 6393 this.$( 'li:first' ).focus(); 6394 }, 6395 6396 restoreFocus: function() { 6397 this.$( 'li.selected:first' ).focus(); 6398 }, 6399 6400 arrowEvent: function( event ) { 6401 var attachments = this.$el.children( 'li' ), 6402 perRow = this.columns, 6403 index = attachments.filter( ':focus' ).index(), 6404 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 6405 6406 if ( index === -1 ) { 6407 return; 6408 } 6409 6410 // Left arrow 6411 if ( 37 === event.keyCode ) { 6412 if ( 0 === index ) { 6413 return; 6414 } 6415 attachments.eq( index - 1 ).focus(); 6416 } 6417 6418 // Up arrow 6419 if ( 38 === event.keyCode ) { 6420 if ( 1 === row ) { 6421 return; 6422 } 6423 attachments.eq( index - perRow ).focus(); 6424 } 6425 6426 // Right arrow 6427 if ( 39 === event.keyCode ) { 6428 if ( attachments.length === index ) { 6429 return; 6430 } 6431 attachments.eq( index + 1 ).focus(); 6432 } 6433 6434 // Down arrow 6435 if ( 40 === event.keyCode ) { 6436 if ( Math.ceil( attachments.length / perRow ) === row ) { 6437 return; 6438 } 6439 attachments.eq( index + perRow ).focus(); 6440 } 6441 }, 6442 6443 dispose: function() { 6444 this.collection.props.off( null, null, this ); 6445 if ( this.options.resize ) { 6446 this.$window.off( this.resizeEvent ); 6447 } 6448 6449 /** 6450 * call 'dispose' directly on the parent class 6451 */ 6452 View.prototype.dispose.apply( this, arguments ); 6453 }, 6454 6455 setColumns: function() { 6456 var prev = this.columns, 6457 width = this.$el.width(); 6458 6459 if ( width ) { 6460 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 6461 6462 if ( ! prev || prev !== this.columns ) { 6463 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 6464 } 6465 } 6466 }, 6467 6468 initSortable: function() { 6469 var collection = this.collection; 6470 6471 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 6472 return; 6473 } 6474 6475 this.$el.sortable( _.extend({ 6476 // If the `collection` has a `comparator`, disable sorting. 6477 disabled: !! collection.comparator, 6478 6479 // Change the position of the attachment as soon as the 6480 // mouse pointer overlaps a thumbnail. 6481 tolerance: 'pointer', 6482 6483 // Record the initial `index` of the dragged model. 6484 start: function( event, ui ) { 6485 ui.item.data('sortableIndexStart', ui.item.index()); 6486 }, 6487 6488 // Update the model's index in the collection. 6489 // Do so silently, as the view is already accurate. 6490 update: function( event, ui ) { 6491 var model = collection.at( ui.item.data('sortableIndexStart') ), 6492 comparator = collection.comparator; 6493 6494 // Temporarily disable the comparator to prevent `add` 6495 // from re-sorting. 6496 delete collection.comparator; 6497 6498 // Silently shift the model to its new index. 6499 collection.remove( model, { 6500 silent: true 6501 }); 6502 collection.add( model, { 6503 silent: true, 6504 at: ui.item.index() 6505 }); 6506 6507 // Restore the comparator. 6508 collection.comparator = comparator; 6509 6510 // Fire the `reset` event to ensure other collections sync. 6511 collection.trigger( 'reset', collection ); 6512 6513 // If the collection is sorted by menu order, 6514 // update the menu order. 6515 collection.saveMenuOrder(); 6516 } 6517 }, this.options.sortable ) ); 6518 6519 // If the `orderby` property is changed on the `collection`, 6520 // check to see if we have a `comparator`. If so, disable sorting. 6521 collection.props.on( 'change:orderby', function() { 6522 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 6523 }, this ); 6524 6525 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 6526 this.refreshSortable(); 6527 }, 6528 6529 refreshSortable: function() { 6530 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 6531 return; 6532 } 6533 6534 // If the `collection` has a `comparator`, disable sorting. 6535 var collection = this.collection, 6536 orderby = collection.props.get('orderby'), 6537 enabled = 'menuOrder' === orderby || ! collection.comparator; 6538 6539 this.$el.sortable( 'option', 'disabled', ! enabled ); 6540 }, 6541 6542 /** 6543 * @param {wp.media.model.Attachment} attachment 6544 * @returns {wp.media.View} 6545 */ 6546 createAttachmentView: function( attachment ) { 6547 var view = new this.options.AttachmentView({ 6548 controller: this.controller, 6549 model: attachment, 6550 collection: this.collection, 6551 selection: this.options.selection 6552 }); 6553 6554 return this._viewsByCid[ attachment.cid ] = view; 6555 }, 6556 6557 prepare: function() { 6558 // Create all of the Attachment views, and replace 6559 // the list in a single DOM operation. 6560 if ( this.collection.length ) { 6561 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 6562 6563 // If there are no elements, clear the views and load some. 6564 } else { 6565 this.views.unset(); 6566 this.collection.more().done( this.scroll ); 6567 } 6568 }, 6569 6570 ready: function() { 6571 // Trigger the scroll event to check if we're within the 6572 // threshold to query for additional attachments. 6573 this.scroll(); 6574 }, 6575 6576 scroll: function() { 6577 var view = this, 6578 el = this.options.scrollElement, 6579 scrollTop = el.scrollTop, 6580 toolbar; 6581 6582 // The scroll event occurs on the document, but the element 6583 // that should be checked is the document body. 6584 if ( el === document ) { 6585 el = document.body; 6586 scrollTop = $(document).scrollTop(); 6587 } 6588 6589 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 6590 return; 6591 } 6592 6593 toolbar = this.views.parent.toolbar; 6594 6595 // Show the spinner only if we are close to the bottom. 6596 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 6597 toolbar.get('spinner').show(); 6598 } 6599 6600 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 6601 this.collection.more().done(function() { 6602 view.scroll(); 6603 toolbar.get('spinner').hide(); 6604 }); 6605 } 6606 } 6607 }); 6608 6609 module.exports = Attachments; 6610 6611 6612 /***/ }), 6613 /* 73 */ 6614 /***/ (function(module, exports) { 6615 6616 /*globals wp */ 6617 6618 /** 6619 * wp.media.view.Search 6620 * 6621 * @class 6622 * @augments wp.media.View 6623 * @augments wp.Backbone.View 6624 * @augments Backbone.View 6625 */ 6626 var l10n = wp.media.view.l10n, 6627 Search; 6628 6629 Search = wp.media.View.extend({ 6630 tagName: 'input', 6631 className: 'search', 6632 id: 'media-search-input', 6633 6634 attributes: { 6635 type: 'search', 6636 placeholder: l10n.search 6637 }, 6638 6639 events: { 6640 'input': 'search', 6641 'keyup': 'search', 6642 'change': 'search', 6643 'search': 'search' 6644 }, 6645 6646 /** 6647 * @returns {wp.media.view.Search} Returns itself to allow chaining 6648 */ 6649 render: function() { 6650 this.el.value = this.model.escape('search'); 6651 return this; 6652 }, 6653 6654 search: function( event ) { 6655 if ( event.target.value ) { 6656 this.model.set( 'search', event.target.value ); 6657 } else { 6658 this.model.unset('search'); 6659 } 6660 } 6661 }); 6662 6663 module.exports = Search; 6664 6665 6666 /***/ }), 6667 /* 74 */ 6668 /***/ (function(module, exports) { 6669 2315 module.exports = AttachmentCompat; 2316 2317 },{}],19:[function(require,module,exports){ 6670 2318 /*globals _, jQuery */ 6671 2319 … … 6746 2394 module.exports = AttachmentFilters; 6747 2395 6748 6749 /***/ }), 6750 /* 75 */ 6751 /***/ (function(module, exports) { 6752 2396 },{}],20:[function(require,module,exports){ 2397 /*globals wp */ 2398 2399 /** 2400 * wp.media.view.AttachmentFilters.All 2401 * 2402 * @class 2403 * @augments wp.media.view.AttachmentFilters 2404 * @augments wp.media.View 2405 * @augments wp.Backbone.View 2406 * @augments Backbone.View 2407 */ 2408 var l10n = wp.media.view.l10n, 2409 All; 2410 2411 All = wp.media.view.AttachmentFilters.extend({ 2412 createFilters: function() { 2413 var filters = {}; 2414 2415 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 2416 filters[ key ] = { 2417 text: text, 2418 props: { 2419 status: null, 2420 type: key, 2421 uploadedTo: null, 2422 orderby: 'date', 2423 order: 'DESC' 2424 } 2425 }; 2426 }); 2427 2428 filters.all = { 2429 text: l10n.allMediaItems, 2430 props: { 2431 status: null, 2432 type: null, 2433 uploadedTo: null, 2434 orderby: 'date', 2435 order: 'DESC' 2436 }, 2437 priority: 10 2438 }; 2439 2440 if ( wp.media.view.settings.post.id ) { 2441 filters.uploaded = { 2442 text: l10n.uploadedToThisPost, 2443 props: { 2444 status: null, 2445 type: null, 2446 uploadedTo: wp.media.view.settings.post.id, 2447 orderby: 'menuOrder', 2448 order: 'ASC' 2449 }, 2450 priority: 20 2451 }; 2452 } 2453 2454 filters.unattached = { 2455 text: l10n.unattached, 2456 props: { 2457 status: null, 2458 uploadedTo: 0, 2459 type: null, 2460 orderby: 'menuOrder', 2461 order: 'ASC' 2462 }, 2463 priority: 50 2464 }; 2465 2466 if ( wp.media.view.settings.mediaTrash && 2467 this.controller.isModeActive( 'grid' ) ) { 2468 2469 filters.trash = { 2470 text: l10n.trash, 2471 props: { 2472 uploadedTo: null, 2473 status: 'trash', 2474 type: null, 2475 orderby: 'date', 2476 order: 'DESC' 2477 }, 2478 priority: 50 2479 }; 2480 } 2481 2482 this.filters = filters; 2483 } 2484 }); 2485 2486 module.exports = All; 2487 2488 },{}],21:[function(require,module,exports){ 6753 2489 /*globals wp, _ */ 6754 2490 … … 6793 2529 module.exports = DateFilter; 6794 2530 6795 6796 /***/ }), 6797 /* 76 */ 6798 /***/ (function(module, exports) { 6799 2531 },{}],22:[function(require,module,exports){ 6800 2532 /*globals wp */ 6801 2533 … … 6858 2590 module.exports = Uploaded; 6859 2591 6860 6861 /***/ }), 6862 /* 77 */ 6863 /***/ (function(module, exports) { 6864 6865 /*globals wp */ 2592 },{}],23:[function(require,module,exports){ 2593 /*globals wp, _, jQuery */ 6866 2594 6867 2595 /** 6868 * wp.media.view.Attachment Filters.All2596 * wp.media.view.Attachment 6869 2597 * 6870 2598 * @class 6871 * @augments wp.media.view.AttachmentFilters6872 2599 * @augments wp.media.View 6873 2600 * @augments wp.Backbone.View 6874 2601 * @augments Backbone.View 6875 2602 */ 6876 var l10n = wp.media.view.l10n, 6877 All; 6878 6879 All = wp.media.view.AttachmentFilters.extend({ 6880 createFilters: function() { 6881 var filters = {}; 6882 6883 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 6884 filters[ key ] = { 6885 text: text, 6886 props: { 6887 status: null, 6888 type: key, 6889 uploadedTo: null, 6890 orderby: 'date', 6891 order: 'DESC' 6892 } 6893 }; 2603 var View = wp.media.View, 2604 $ = jQuery, 2605 Attachment; 2606 2607 Attachment = View.extend({ 2608 tagName: 'li', 2609 className: 'attachment', 2610 template: wp.template('attachment'), 2611 2612 attributes: function() { 2613 return { 2614 'tabIndex': 0, 2615 'role': 'checkbox', 2616 'aria-label': this.model.get( 'title' ), 2617 'aria-checked': false, 2618 'data-id': this.model.get( 'id' ) 2619 }; 2620 }, 2621 2622 events: { 2623 'click .js--select-attachment': 'toggleSelectionHandler', 2624 'change [data-setting]': 'updateSetting', 2625 'change [data-setting] input': 'updateSetting', 2626 'change [data-setting] select': 'updateSetting', 2627 'change [data-setting] textarea': 'updateSetting', 2628 'click .close': 'removeFromLibrary', 2629 'click .check': 'checkClickHandler', 2630 'click a': 'preventDefault', 2631 'keydown .close': 'removeFromLibrary', 2632 'keydown': 'toggleSelectionHandler' 2633 }, 2634 2635 buttons: {}, 2636 2637 initialize: function() { 2638 var selection = this.options.selection, 2639 options = _.defaults( this.options, { 2640 rerenderOnModelChange: true 2641 } ); 2642 2643 if ( options.rerenderOnModelChange ) { 2644 this.listenTo( this.model, 'change', this.render ); 2645 } else { 2646 this.listenTo( this.model, 'change:percent', this.progress ); 2647 } 2648 this.listenTo( this.model, 'change:title', this._syncTitle ); 2649 this.listenTo( this.model, 'change:caption', this._syncCaption ); 2650 this.listenTo( this.model, 'change:artist', this._syncArtist ); 2651 this.listenTo( this.model, 'change:album', this._syncAlbum ); 2652 2653 // Update the selection. 2654 this.listenTo( this.model, 'add', this.select ); 2655 this.listenTo( this.model, 'remove', this.deselect ); 2656 if ( selection ) { 2657 selection.on( 'reset', this.updateSelect, this ); 2658 // Update the model's details view. 2659 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 2660 this.details( this.model, this.controller.state().get('selection') ); 2661 } 2662 2663 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 2664 }, 2665 /** 2666 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 2667 */ 2668 dispose: function() { 2669 var selection = this.options.selection; 2670 2671 // Make sure all settings are saved before removing the view. 2672 this.updateAll(); 2673 2674 if ( selection ) { 2675 selection.off( null, null, this ); 2676 } 2677 /** 2678 * call 'dispose' directly on the parent class 2679 */ 2680 View.prototype.dispose.apply( this, arguments ); 2681 return this; 2682 }, 2683 /** 2684 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 2685 */ 2686 render: function() { 2687 var options = _.defaults( this.model.toJSON(), { 2688 orientation: 'landscape', 2689 uploading: false, 2690 type: '', 2691 subtype: '', 2692 icon: '', 2693 filename: '', 2694 caption: '', 2695 title: '', 2696 dateFormatted: '', 2697 width: '', 2698 height: '', 2699 compat: false, 2700 alt: '', 2701 description: '' 2702 }, this.options ); 2703 2704 options.buttons = this.buttons; 2705 options.describe = this.controller.state().get('describe'); 2706 2707 if ( 'image' === options.type ) { 2708 options.size = this.imageSize(); 2709 } 2710 2711 options.can = {}; 2712 if ( options.nonces ) { 2713 options.can.remove = !! options.nonces['delete']; 2714 options.can.save = !! options.nonces.update; 2715 } 2716 2717 if ( this.controller.state().get('allowLocalEdits') ) { 2718 options.allowLocalEdits = true; 2719 } 2720 2721 if ( options.uploading && ! options.percent ) { 2722 options.percent = 0; 2723 } 2724 2725 this.views.detach(); 2726 this.$el.html( this.template( options ) ); 2727 2728 this.$el.toggleClass( 'uploading', options.uploading ); 2729 2730 if ( options.uploading ) { 2731 this.$bar = this.$('.media-progress-bar div'); 2732 } else { 2733 delete this.$bar; 2734 } 2735 2736 // Check if the model is selected. 2737 this.updateSelect(); 2738 2739 // Update the save status. 2740 this.updateSave(); 2741 2742 this.views.render(); 2743 2744 return this; 2745 }, 2746 2747 progress: function() { 2748 if ( this.$bar && this.$bar.length ) { 2749 this.$bar.width( this.model.get('percent') + '%' ); 2750 } 2751 }, 2752 2753 /** 2754 * @param {Object} event 2755 */ 2756 toggleSelectionHandler: function( event ) { 2757 var method; 2758 2759 // Don't do anything inside inputs. 2760 if ( 'INPUT' === event.target.nodeName ) { 2761 return; 2762 } 2763 2764 // Catch arrow events 2765 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 2766 this.controller.trigger( 'attachment:keydown:arrow', event ); 2767 return; 2768 } 2769 2770 // Catch enter and space events 2771 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 2772 return; 2773 } 2774 2775 event.preventDefault(); 2776 2777 // In the grid view, bubble up an edit:attachment event to the controller. 2778 if ( this.controller.isModeActive( 'grid' ) ) { 2779 if ( this.controller.isModeActive( 'edit' ) ) { 2780 // Pass the current target to restore focus when closing 2781 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 2782 return; 2783 } 2784 2785 if ( this.controller.isModeActive( 'select' ) ) { 2786 method = 'toggle'; 2787 } 2788 } 2789 2790 if ( event.shiftKey ) { 2791 method = 'between'; 2792 } else if ( event.ctrlKey || event.metaKey ) { 2793 method = 'toggle'; 2794 } 2795 2796 this.toggleSelection({ 2797 method: method 6894 2798 }); 6895 2799 6896 filters.all = { 6897 text: l10n.allMediaItems, 6898 props: { 6899 status: null, 6900 type: null, 6901 uploadedTo: null, 6902 orderby: 'date', 6903 order: 'DESC' 6904 }, 6905 priority: 10 2800 this.controller.trigger( 'selection:toggle' ); 2801 }, 2802 /** 2803 * @param {Object} options 2804 */ 2805 toggleSelection: function( options ) { 2806 var collection = this.collection, 2807 selection = this.options.selection, 2808 model = this.model, 2809 method = options && options.method, 2810 single, models, singleIndex, modelIndex; 2811 2812 if ( ! selection ) { 2813 return; 2814 } 2815 2816 single = selection.single(); 2817 method = _.isUndefined( method ) ? selection.multiple : method; 2818 2819 // If the `method` is set to `between`, select all models that 2820 // exist between the current and the selected model. 2821 if ( 'between' === method && single && selection.multiple ) { 2822 // If the models are the same, short-circuit. 2823 if ( single === model ) { 2824 return; 2825 } 2826 2827 singleIndex = collection.indexOf( single ); 2828 modelIndex = collection.indexOf( this.model ); 2829 2830 if ( singleIndex < modelIndex ) { 2831 models = collection.models.slice( singleIndex, modelIndex + 1 ); 2832 } else { 2833 models = collection.models.slice( modelIndex, singleIndex + 1 ); 2834 } 2835 2836 selection.add( models ); 2837 selection.single( model ); 2838 return; 2839 2840 // If the `method` is set to `toggle`, just flip the selection 2841 // status, regardless of whether the model is the single model. 2842 } else if ( 'toggle' === method ) { 2843 selection[ this.selected() ? 'remove' : 'add' ]( model ); 2844 selection.single( model ); 2845 return; 2846 } else if ( 'add' === method ) { 2847 selection.add( model ); 2848 selection.single( model ); 2849 return; 2850 } 2851 2852 // Fixes bug that loses focus when selecting a featured image 2853 if ( ! method ) { 2854 method = 'add'; 2855 } 2856 2857 if ( method !== 'add' ) { 2858 method = 'reset'; 2859 } 2860 2861 if ( this.selected() ) { 2862 // If the model is the single model, remove it. 2863 // If it is not the same as the single model, 2864 // it now becomes the single model. 2865 selection[ single === model ? 'remove' : 'single' ]( model ); 2866 } else { 2867 // If the model is not selected, run the `method` on the 2868 // selection. By default, we `reset` the selection, but the 2869 // `method` can be set to `add` the model to the selection. 2870 selection[ method ]( model ); 2871 selection.single( model ); 2872 } 2873 }, 2874 2875 updateSelect: function() { 2876 this[ this.selected() ? 'select' : 'deselect' ](); 2877 }, 2878 /** 2879 * @returns {unresolved|Boolean} 2880 */ 2881 selected: function() { 2882 var selection = this.options.selection; 2883 if ( selection ) { 2884 return !! selection.get( this.model.cid ); 2885 } 2886 }, 2887 /** 2888 * @param {Backbone.Model} model 2889 * @param {Backbone.Collection} collection 2890 */ 2891 select: function( model, collection ) { 2892 var selection = this.options.selection, 2893 controller = this.controller; 2894 2895 // Check if a selection exists and if it's the collection provided. 2896 // If they're not the same collection, bail; we're in another 2897 // selection's event loop. 2898 if ( ! selection || ( collection && collection !== selection ) ) { 2899 return; 2900 } 2901 2902 // Bail if the model is already selected. 2903 if ( this.$el.hasClass( 'selected' ) ) { 2904 return; 2905 } 2906 2907 // Add 'selected' class to model, set aria-checked to true. 2908 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 2909 // Make the checkbox tabable, except in media grid (bulk select mode). 2910 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 2911 this.$( '.check' ).attr( 'tabindex', '0' ); 2912 } 2913 }, 2914 /** 2915 * @param {Backbone.Model} model 2916 * @param {Backbone.Collection} collection 2917 */ 2918 deselect: function( model, collection ) { 2919 var selection = this.options.selection; 2920 2921 // Check if a selection exists and if it's the collection provided. 2922 // If they're not the same collection, bail; we're in another 2923 // selection's event loop. 2924 if ( ! selection || ( collection && collection !== selection ) ) { 2925 return; 2926 } 2927 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 2928 .find( '.check' ).attr( 'tabindex', '-1' ); 2929 }, 2930 /** 2931 * @param {Backbone.Model} model 2932 * @param {Backbone.Collection} collection 2933 */ 2934 details: function( model, collection ) { 2935 var selection = this.options.selection, 2936 details; 2937 2938 if ( selection !== collection ) { 2939 return; 2940 } 2941 2942 details = selection.single(); 2943 this.$el.toggleClass( 'details', details === this.model ); 2944 }, 2945 /** 2946 * @param {Object} event 2947 */ 2948 preventDefault: function( event ) { 2949 event.preventDefault(); 2950 }, 2951 /** 2952 * @param {string} size 2953 * @returns {Object} 2954 */ 2955 imageSize: function( size ) { 2956 var sizes = this.model.get('sizes'), matched = false; 2957 2958 size = size || 'medium'; 2959 2960 // Use the provided image size if possible. 2961 if ( sizes ) { 2962 if ( sizes[ size ] ) { 2963 matched = sizes[ size ]; 2964 } else if ( sizes.large ) { 2965 matched = sizes.large; 2966 } else if ( sizes.thumbnail ) { 2967 matched = sizes.thumbnail; 2968 } else if ( sizes.full ) { 2969 matched = sizes.full; 2970 } 2971 2972 if ( matched ) { 2973 return _.clone( matched ); 2974 } 2975 } 2976 2977 return { 2978 url: this.model.get('url'), 2979 width: this.model.get('width'), 2980 height: this.model.get('height'), 2981 orientation: this.model.get('orientation') 6906 2982 }; 6907 6908 if ( wp.media.view.settings.post.id ) { 6909 filters.uploaded = { 6910 text: l10n.uploadedToThisPost, 6911 props: { 6912 status: null, 6913 type: null, 6914 uploadedTo: wp.media.view.settings.post.id, 6915 orderby: 'menuOrder', 6916 order: 'ASC' 6917 }, 6918 priority: 20 6919 }; 6920 } 6921 6922 filters.unattached = { 6923 text: l10n.unattached, 6924 props: { 6925 status: null, 6926 uploadedTo: 0, 6927 type: null, 6928 orderby: 'menuOrder', 6929 order: 'ASC' 6930 }, 6931 priority: 50 6932 }; 6933 6934 if ( wp.media.view.settings.mediaTrash && 6935 this.controller.isModeActive( 'grid' ) ) { 6936 6937 filters.trash = { 6938 text: l10n.trash, 6939 props: { 6940 uploadedTo: null, 6941 status: 'trash', 6942 type: null, 6943 orderby: 'date', 6944 order: 'DESC' 6945 }, 6946 priority: 50 6947 }; 6948 } 6949 6950 this.filters = filters; 2983 }, 2984 /** 2985 * @param {Object} event 2986 */ 2987 updateSetting: function( event ) { 2988 var $setting = $( event.target ).closest('[data-setting]'), 2989 setting, value; 2990 2991 if ( ! $setting.length ) { 2992 return; 2993 } 2994 2995 setting = $setting.data('setting'); 2996 value = event.target.value; 2997 2998 if ( this.model.get( setting ) !== value ) { 2999 this.save( setting, value ); 3000 } 3001 }, 3002 3003 /** 3004 * Pass all the arguments to the model's save method. 3005 * 3006 * Records the aggregate status of all save requests and updates the 3007 * view's classes accordingly. 3008 */ 3009 save: function() { 3010 var view = this, 3011 save = this._save = this._save || { status: 'ready' }, 3012 request = this.model.save.apply( this.model, arguments ), 3013 requests = save.requests ? $.when( request, save.requests ) : request; 3014 3015 // If we're waiting to remove 'Saved.', stop. 3016 if ( save.savedTimer ) { 3017 clearTimeout( save.savedTimer ); 3018 } 3019 3020 this.updateSave('waiting'); 3021 save.requests = requests; 3022 requests.always( function() { 3023 // If we've performed another request since this one, bail. 3024 if ( save.requests !== requests ) { 3025 return; 3026 } 3027 3028 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 3029 save.savedTimer = setTimeout( function() { 3030 view.updateSave('ready'); 3031 delete save.savedTimer; 3032 }, 2000 ); 3033 }); 3034 }, 3035 /** 3036 * @param {string} status 3037 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3038 */ 3039 updateSave: function( status ) { 3040 var save = this._save = this._save || { status: 'ready' }; 3041 3042 if ( status && status !== save.status ) { 3043 this.$el.removeClass( 'save-' + save.status ); 3044 save.status = status; 3045 } 3046 3047 this.$el.addClass( 'save-' + save.status ); 3048 return this; 3049 }, 3050 3051 updateAll: function() { 3052 var $settings = this.$('[data-setting]'), 3053 model = this.model, 3054 changed; 3055 3056 changed = _.chain( $settings ).map( function( el ) { 3057 var $input = $('input, textarea, select, [value]', el ), 3058 setting, value; 3059 3060 if ( ! $input.length ) { 3061 return; 3062 } 3063 3064 setting = $(el).data('setting'); 3065 value = $input.val(); 3066 3067 // Record the value if it changed. 3068 if ( model.get( setting ) !== value ) { 3069 return [ setting, value ]; 3070 } 3071 }).compact().object().value(); 3072 3073 if ( ! _.isEmpty( changed ) ) { 3074 model.save( changed ); 3075 } 3076 }, 3077 /** 3078 * @param {Object} event 3079 */ 3080 removeFromLibrary: function( event ) { 3081 // Catch enter and space events 3082 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 3083 return; 3084 } 3085 3086 // Stop propagation so the model isn't selected. 3087 event.stopPropagation(); 3088 3089 this.collection.remove( this.model ); 3090 }, 3091 3092 /** 3093 * Add the model if it isn't in the selection, if it is in the selection, 3094 * remove it. 3095 * 3096 * @param {[type]} event [description] 3097 * @return {[type]} [description] 3098 */ 3099 checkClickHandler: function ( event ) { 3100 var selection = this.options.selection; 3101 if ( ! selection ) { 3102 return; 3103 } 3104 event.stopPropagation(); 3105 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 3106 selection.remove( this.model ); 3107 // Move focus back to the attachment tile (from the check). 3108 this.$el.focus(); 3109 } else { 3110 selection.add( this.model ); 3111 } 6951 3112 } 6952 3113 }); 6953 3114 6954 module.exports = All; 6955 6956 6957 /***/ }), 6958 /* 78 */ 6959 /***/ (function(module, exports) { 6960 3115 // Ensure settings remain in sync between attachment views. 3116 _.each({ 3117 caption: '_syncCaption', 3118 title: '_syncTitle', 3119 artist: '_syncArtist', 3120 album: '_syncAlbum' 3121 }, function( method, setting ) { 3122 /** 3123 * @param {Backbone.Model} model 3124 * @param {string} value 3125 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3126 */ 3127 Attachment.prototype[ method ] = function( model, value ) { 3128 var $setting = this.$('[data-setting="' + setting + '"]'); 3129 3130 if ( ! $setting.length ) { 3131 return this; 3132 } 3133 3134 // If the updated value is in sync with the value in the DOM, there 3135 // is no need to re-render. If we're currently editing the value, 3136 // it will automatically be in sync, suppressing the re-render for 3137 // the view we're editing, while updating any others. 3138 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 3139 return this; 3140 } 3141 3142 return this.render(); 3143 }; 3144 }); 3145 3146 module.exports = Attachment; 3147 3148 },{}],24:[function(require,module,exports){ 3149 /*globals wp, _ */ 3150 3151 /** 3152 * wp.media.view.Attachment.Details 3153 * 3154 * @class 3155 * @augments wp.media.view.Attachment 3156 * @augments wp.media.View 3157 * @augments wp.Backbone.View 3158 * @augments Backbone.View 3159 */ 3160 var Attachment = wp.media.view.Attachment, 3161 l10n = wp.media.view.l10n, 3162 Details; 3163 3164 Details = Attachment.extend({ 3165 tagName: 'div', 3166 className: 'attachment-details', 3167 template: wp.template('attachment-details'), 3168 3169 attributes: function() { 3170 return { 3171 'tabIndex': 0, 3172 'data-id': this.model.get( 'id' ) 3173 }; 3174 }, 3175 3176 events: { 3177 'change [data-setting]': 'updateSetting', 3178 'change [data-setting] input': 'updateSetting', 3179 'change [data-setting] select': 'updateSetting', 3180 'change [data-setting] textarea': 'updateSetting', 3181 'click .delete-attachment': 'deleteAttachment', 3182 'click .trash-attachment': 'trashAttachment', 3183 'click .untrash-attachment': 'untrashAttachment', 3184 'click .edit-attachment': 'editAttachment', 3185 'click .refresh-attachment': 'refreshAttachment', 3186 'keydown': 'toggleSelectionHandler' 3187 }, 3188 3189 initialize: function() { 3190 this.options = _.defaults( this.options, { 3191 rerenderOnModelChange: false 3192 }); 3193 3194 this.on( 'ready', this.initialFocus ); 3195 // Call 'initialize' directly on the parent class. 3196 Attachment.prototype.initialize.apply( this, arguments ); 3197 }, 3198 3199 initialFocus: function() { 3200 if ( ! wp.media.isTouchDevice ) { 3201 this.$( ':input' ).eq( 0 ).focus(); 3202 } 3203 }, 3204 /** 3205 * @param {Object} event 3206 */ 3207 deleteAttachment: function( event ) { 3208 event.preventDefault(); 3209 3210 if ( window.confirm( l10n.warnDelete ) ) { 3211 this.model.destroy(); 3212 // Keep focus inside media modal 3213 // after image is deleted 3214 this.controller.modal.focusManager.focus(); 3215 } 3216 }, 3217 /** 3218 * @param {Object} event 3219 */ 3220 trashAttachment: function( event ) { 3221 var library = this.controller.library; 3222 event.preventDefault(); 3223 3224 if ( wp.media.view.settings.mediaTrash && 3225 'edit-metadata' === this.controller.content.mode() ) { 3226 3227 this.model.set( 'status', 'trash' ); 3228 this.model.save().done( function() { 3229 library._requery( true ); 3230 } ); 3231 } else { 3232 this.model.destroy(); 3233 } 3234 }, 3235 /** 3236 * @param {Object} event 3237 */ 3238 untrashAttachment: function( event ) { 3239 var library = this.controller.library; 3240 event.preventDefault(); 3241 3242 this.model.set( 'status', 'inherit' ); 3243 this.model.save().done( function() { 3244 library._requery( true ); 3245 } ); 3246 }, 3247 /** 3248 * @param {Object} event 3249 */ 3250 editAttachment: function( event ) { 3251 var editState = this.controller.states.get( 'edit-image' ); 3252 if ( window.imageEdit && editState ) { 3253 event.preventDefault(); 3254 3255 editState.set( 'image', this.model ); 3256 this.controller.setState( 'edit-image' ); 3257 } else { 3258 this.$el.addClass('needs-refresh'); 3259 } 3260 }, 3261 /** 3262 * @param {Object} event 3263 */ 3264 refreshAttachment: function( event ) { 3265 this.$el.removeClass('needs-refresh'); 3266 event.preventDefault(); 3267 this.model.fetch(); 3268 }, 3269 /** 3270 * When reverse tabbing(shift+tab) out of the right details panel, deliver 3271 * the focus to the item in the list that was being edited. 3272 * 3273 * @param {Object} event 3274 */ 3275 toggleSelectionHandler: function( event ) { 3276 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 3277 this.controller.trigger( 'attachment:details:shift-tab', event ); 3278 return false; 3279 } 3280 3281 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 3282 this.controller.trigger( 'attachment:keydown:arrow', event ); 3283 return; 3284 } 3285 } 3286 }); 3287 3288 module.exports = Details; 3289 3290 },{}],25:[function(require,module,exports){ 3291 /*globals wp */ 3292 3293 /** 3294 * wp.media.view.Attachment.EditLibrary 3295 * 3296 * @class 3297 * @augments wp.media.view.Attachment 3298 * @augments wp.media.View 3299 * @augments wp.Backbone.View 3300 * @augments Backbone.View 3301 */ 3302 var EditLibrary = wp.media.view.Attachment.extend({ 3303 buttons: { 3304 close: true 3305 } 3306 }); 3307 3308 module.exports = EditLibrary; 3309 3310 },{}],26:[function(require,module,exports){ 3311 /*globals wp */ 3312 3313 /** 3314 * wp.media.view.Attachments.EditSelection 3315 * 3316 * @class 3317 * @augments wp.media.view.Attachment.Selection 3318 * @augments wp.media.view.Attachment 3319 * @augments wp.media.View 3320 * @augments wp.Backbone.View 3321 * @augments Backbone.View 3322 */ 3323 var EditSelection = wp.media.view.Attachment.Selection.extend({ 3324 buttons: { 3325 close: true 3326 } 3327 }); 3328 3329 module.exports = EditSelection; 3330 3331 },{}],27:[function(require,module,exports){ 3332 /*globals wp */ 3333 3334 /** 3335 * wp.media.view.Attachment.Library 3336 * 3337 * @class 3338 * @augments wp.media.view.Attachment 3339 * @augments wp.media.View 3340 * @augments wp.Backbone.View 3341 * @augments Backbone.View 3342 */ 3343 var Library = wp.media.view.Attachment.extend({ 3344 buttons: { 3345 check: true 3346 } 3347 }); 3348 3349 module.exports = Library; 3350 3351 },{}],28:[function(require,module,exports){ 3352 /*globals wp */ 3353 3354 /** 3355 * wp.media.view.Attachment.Selection 3356 * 3357 * @class 3358 * @augments wp.media.view.Attachment 3359 * @augments wp.media.View 3360 * @augments wp.Backbone.View 3361 * @augments Backbone.View 3362 */ 3363 var Selection = wp.media.view.Attachment.extend({ 3364 className: 'attachment selection', 3365 3366 // On click, just select the model, instead of removing the model from 3367 // the selection. 3368 toggleSelection: function() { 3369 this.options.selection.single( this.model ); 3370 } 3371 }); 3372 3373 module.exports = Selection; 3374 3375 },{}],29:[function(require,module,exports){ 3376 /*globals wp, _, jQuery */ 3377 3378 /** 3379 * wp.media.view.Attachments 3380 * 3381 * @class 3382 * @augments wp.media.View 3383 * @augments wp.Backbone.View 3384 * @augments Backbone.View 3385 */ 3386 var View = wp.media.View, 3387 $ = jQuery, 3388 Attachments; 3389 3390 Attachments = View.extend({ 3391 tagName: 'ul', 3392 className: 'attachments', 3393 3394 attributes: { 3395 tabIndex: -1 3396 }, 3397 3398 initialize: function() { 3399 this.el.id = _.uniqueId('__attachments-view-'); 3400 3401 _.defaults( this.options, { 3402 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 3403 refreshThreshold: 3, 3404 AttachmentView: wp.media.view.Attachment, 3405 sortable: false, 3406 resize: true, 3407 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 3408 }); 3409 3410 this._viewsByCid = {}; 3411 this.$window = $( window ); 3412 this.resizeEvent = 'resize.media-modal-columns'; 3413 3414 this.collection.on( 'add', function( attachment ) { 3415 this.views.add( this.createAttachmentView( attachment ), { 3416 at: this.collection.indexOf( attachment ) 3417 }); 3418 }, this ); 3419 3420 this.collection.on( 'remove', function( attachment ) { 3421 var view = this._viewsByCid[ attachment.cid ]; 3422 delete this._viewsByCid[ attachment.cid ]; 3423 3424 if ( view ) { 3425 view.remove(); 3426 } 3427 }, this ); 3428 3429 this.collection.on( 'reset', this.render, this ); 3430 3431 this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus ); 3432 3433 // Throttle the scroll handler and bind this. 3434 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 3435 3436 this.options.scrollElement = this.options.scrollElement || this.el; 3437 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 3438 3439 this.initSortable(); 3440 3441 _.bindAll( this, 'setColumns' ); 3442 3443 if ( this.options.resize ) { 3444 this.on( 'ready', this.bindEvents ); 3445 this.controller.on( 'open', this.setColumns ); 3446 3447 // Call this.setColumns() after this view has been rendered in the DOM so 3448 // attachments get proper width applied. 3449 _.defer( this.setColumns, this ); 3450 } 3451 }, 3452 3453 bindEvents: function() { 3454 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 3455 }, 3456 3457 attachmentFocus: function() { 3458 this.$( 'li:first' ).focus(); 3459 }, 3460 3461 restoreFocus: function() { 3462 this.$( 'li.selected:first' ).focus(); 3463 }, 3464 3465 arrowEvent: function( event ) { 3466 var attachments = this.$el.children( 'li' ), 3467 perRow = this.columns, 3468 index = attachments.filter( ':focus' ).index(), 3469 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 3470 3471 if ( index === -1 ) { 3472 return; 3473 } 3474 3475 // Left arrow 3476 if ( 37 === event.keyCode ) { 3477 if ( 0 === index ) { 3478 return; 3479 } 3480 attachments.eq( index - 1 ).focus(); 3481 } 3482 3483 // Up arrow 3484 if ( 38 === event.keyCode ) { 3485 if ( 1 === row ) { 3486 return; 3487 } 3488 attachments.eq( index - perRow ).focus(); 3489 } 3490 3491 // Right arrow 3492 if ( 39 === event.keyCode ) { 3493 if ( attachments.length === index ) { 3494 return; 3495 } 3496 attachments.eq( index + 1 ).focus(); 3497 } 3498 3499 // Down arrow 3500 if ( 40 === event.keyCode ) { 3501 if ( Math.ceil( attachments.length / perRow ) === row ) { 3502 return; 3503 } 3504 attachments.eq( index + perRow ).focus(); 3505 } 3506 }, 3507 3508 dispose: function() { 3509 this.collection.props.off( null, null, this ); 3510 if ( this.options.resize ) { 3511 this.$window.off( this.resizeEvent ); 3512 } 3513 3514 /** 3515 * call 'dispose' directly on the parent class 3516 */ 3517 View.prototype.dispose.apply( this, arguments ); 3518 }, 3519 3520 setColumns: function() { 3521 var prev = this.columns, 3522 width = this.$el.width(); 3523 3524 if ( width ) { 3525 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 3526 3527 if ( ! prev || prev !== this.columns ) { 3528 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 3529 } 3530 } 3531 }, 3532 3533 initSortable: function() { 3534 var collection = this.collection; 3535 3536 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 3537 return; 3538 } 3539 3540 this.$el.sortable( _.extend({ 3541 // If the `collection` has a `comparator`, disable sorting. 3542 disabled: !! collection.comparator, 3543 3544 // Change the position of the attachment as soon as the 3545 // mouse pointer overlaps a thumbnail. 3546 tolerance: 'pointer', 3547 3548 // Record the initial `index` of the dragged model. 3549 start: function( event, ui ) { 3550 ui.item.data('sortableIndexStart', ui.item.index()); 3551 }, 3552 3553 // Update the model's index in the collection. 3554 // Do so silently, as the view is already accurate. 3555 update: function( event, ui ) { 3556 var model = collection.at( ui.item.data('sortableIndexStart') ), 3557 comparator = collection.comparator; 3558 3559 // Temporarily disable the comparator to prevent `add` 3560 // from re-sorting. 3561 delete collection.comparator; 3562 3563 // Silently shift the model to its new index. 3564 collection.remove( model, { 3565 silent: true 3566 }); 3567 collection.add( model, { 3568 silent: true, 3569 at: ui.item.index() 3570 }); 3571 3572 // Restore the comparator. 3573 collection.comparator = comparator; 3574 3575 // Fire the `reset` event to ensure other collections sync. 3576 collection.trigger( 'reset', collection ); 3577 3578 // If the collection is sorted by menu order, 3579 // update the menu order. 3580 collection.saveMenuOrder(); 3581 } 3582 }, this.options.sortable ) ); 3583 3584 // If the `orderby` property is changed on the `collection`, 3585 // check to see if we have a `comparator`. If so, disable sorting. 3586 collection.props.on( 'change:orderby', function() { 3587 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 3588 }, this ); 3589 3590 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 3591 this.refreshSortable(); 3592 }, 3593 3594 refreshSortable: function() { 3595 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 3596 return; 3597 } 3598 3599 // If the `collection` has a `comparator`, disable sorting. 3600 var collection = this.collection, 3601 orderby = collection.props.get('orderby'), 3602 enabled = 'menuOrder' === orderby || ! collection.comparator; 3603 3604 this.$el.sortable( 'option', 'disabled', ! enabled ); 3605 }, 3606 3607 /** 3608 * @param {wp.media.model.Attachment} attachment 3609 * @returns {wp.media.View} 3610 */ 3611 createAttachmentView: function( attachment ) { 3612 var view = new this.options.AttachmentView({ 3613 controller: this.controller, 3614 model: attachment, 3615 collection: this.collection, 3616 selection: this.options.selection 3617 }); 3618 3619 return this._viewsByCid[ attachment.cid ] = view; 3620 }, 3621 3622 prepare: function() { 3623 // Create all of the Attachment views, and replace 3624 // the list in a single DOM operation. 3625 if ( this.collection.length ) { 3626 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 3627 3628 // If there are no elements, clear the views and load some. 3629 } else { 3630 this.views.unset(); 3631 this.collection.more().done( this.scroll ); 3632 } 3633 }, 3634 3635 ready: function() { 3636 // Trigger the scroll event to check if we're within the 3637 // threshold to query for additional attachments. 3638 this.scroll(); 3639 }, 3640 3641 scroll: function() { 3642 var view = this, 3643 el = this.options.scrollElement, 3644 scrollTop = el.scrollTop, 3645 toolbar; 3646 3647 // The scroll event occurs on the document, but the element 3648 // that should be checked is the document body. 3649 if ( el === document ) { 3650 el = document.body; 3651 scrollTop = $(document).scrollTop(); 3652 } 3653 3654 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 3655 return; 3656 } 3657 3658 toolbar = this.views.parent.toolbar; 3659 3660 // Show the spinner only if we are close to the bottom. 3661 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 3662 toolbar.get('spinner').show(); 3663 } 3664 3665 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 3666 this.collection.more().done(function() { 3667 view.scroll(); 3668 toolbar.get('spinner').hide(); 3669 }); 3670 } 3671 } 3672 }); 3673 3674 module.exports = Attachments; 3675 3676 },{}],30:[function(require,module,exports){ 6961 3677 /*globals wp, _, jQuery */ 6962 3678 … … 7404 4120 module.exports = AttachmentsBrowser; 7405 4121 7406 7407 /***/ }), 7408 /* 79 */ 7409 /***/ (function(module, exports) { 7410 7411 /*globals wp, _, Backbone */ 7412 7413 /** 7414 * wp.media.view.Selection 7415 * 7416 * @class 7417 * @augments wp.media.View 7418 * @augments wp.Backbone.View 7419 * @augments Backbone.View 7420 */ 7421 var l10n = wp.media.view.l10n, 7422 Selection; 7423 7424 Selection = wp.media.View.extend({ 7425 tagName: 'div', 7426 className: 'media-selection', 7427 template: wp.template('media-selection'), 7428 7429 events: { 7430 'click .edit-selection': 'edit', 7431 'click .clear-selection': 'clear' 7432 }, 7433 7434 initialize: function() { 7435 _.defaults( this.options, { 7436 editable: false, 7437 clearable: true 7438 }); 7439 7440 /** 7441 * @member {wp.media.view.Attachments.Selection} 7442 */ 7443 this.attachments = new wp.media.view.Attachments.Selection({ 7444 controller: this.controller, 7445 collection: this.collection, 7446 selection: this.collection, 7447 model: new Backbone.Model() 7448 }); 7449 7450 this.views.set( '.selection-view', this.attachments ); 7451 this.collection.on( 'add remove reset', this.refresh, this ); 7452 this.controller.on( 'content:activate', this.refresh, this ); 7453 }, 7454 7455 ready: function() { 7456 this.refresh(); 7457 }, 7458 7459 refresh: function() { 7460 // If the selection hasn't been rendered, bail. 7461 if ( ! this.$el.children().length ) { 7462 return; 7463 } 7464 7465 var collection = this.collection, 7466 editing = 'edit-selection' === this.controller.content.mode(); 7467 7468 // If nothing is selected, display nothing. 7469 this.$el.toggleClass( 'empty', ! collection.length ); 7470 this.$el.toggleClass( 'one', 1 === collection.length ); 7471 this.$el.toggleClass( 'editing', editing ); 7472 7473 this.$('.count').text( l10n.selected.replace('%d', collection.length) ); 7474 }, 7475 7476 edit: function( event ) { 7477 event.preventDefault(); 7478 if ( this.options.editable ) { 7479 this.options.editable.call( this, this.collection ); 7480 } 7481 }, 7482 7483 clear: function( event ) { 7484 event.preventDefault(); 7485 this.collection.reset(); 7486 7487 // Keep focus inside media modal 7488 // after clear link is selected 7489 this.controller.modal.focusManager.focus(); 7490 } 7491 }); 7492 7493 module.exports = Selection; 7494 7495 7496 /***/ }), 7497 /* 80 */ 7498 /***/ (function(module, exports) { 7499 7500 /*globals wp */ 7501 7502 /** 7503 * wp.media.view.Attachment.Selection 7504 * 7505 * @class 7506 * @augments wp.media.view.Attachment 7507 * @augments wp.media.View 7508 * @augments wp.Backbone.View 7509 * @augments Backbone.View 7510 */ 7511 var Selection = wp.media.view.Attachment.extend({ 7512 className: 'attachment selection', 7513 7514 // On click, just select the model, instead of removing the model from 7515 // the selection. 7516 toggleSelection: function() { 7517 this.options.selection.single( this.model ); 7518 } 7519 }); 7520 7521 module.exports = Selection; 7522 7523 7524 /***/ }), 7525 /* 81 */ 7526 /***/ (function(module, exports) { 7527 4122 },{}],31:[function(require,module,exports){ 7528 4123 /*globals wp, _ */ 7529 4124 … … 7557 4152 module.exports = Selection; 7558 4153 7559 7560 /***/ }), 7561 /* 82 */ 7562 /***/ (function(module, exports) { 7563 7564 /*globals wp */ 4154 },{}],32:[function(require,module,exports){ 4155 /*globals _, Backbone */ 7565 4156 7566 4157 /** 7567 * wp.media.view. Attachments.EditSelection4158 * wp.media.view.ButtonGroup 7568 4159 * 7569 4160 * @class 7570 * @augments wp.media.view.Attachment.Selection7571 * @augments wp.media.view.Attachment7572 4161 * @augments wp.media.View 7573 4162 * @augments wp.Backbone.View 7574 4163 * @augments Backbone.View 7575 4164 */ 7576 var EditSelection = wp.media.view.Attachment.Selection.extend({ 7577 buttons: { 7578 close: true 4165 var $ = Backbone.$, 4166 ButtonGroup; 4167 4168 ButtonGroup = wp.media.View.extend({ 4169 tagName: 'div', 4170 className: 'button-group button-large media-button-group', 4171 4172 initialize: function() { 4173 /** 4174 * @member {wp.media.view.Button[]} 4175 */ 4176 this.buttons = _.map( this.options.buttons || [], function( button ) { 4177 if ( button instanceof Backbone.View ) { 4178 return button; 4179 } else { 4180 return new wp.media.view.Button( button ).render(); 4181 } 4182 }); 4183 4184 delete this.options.buttons; 4185 4186 if ( this.options.classes ) { 4187 this.$el.addClass( this.options.classes ); 4188 } 4189 }, 4190 4191 /** 4192 * @returns {wp.media.view.ButtonGroup} 4193 */ 4194 render: function() { 4195 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 4196 return this; 7579 4197 } 7580 4198 }); 7581 4199 7582 module.exports = EditSelection; 7583 7584 7585 /***/ }), 7586 /* 83 */ 7587 /***/ (function(module, exports) { 7588 4200 module.exports = ButtonGroup; 4201 4202 },{}],33:[function(require,module,exports){ 7589 4203 /*globals _, Backbone */ 7590 4204 7591 4205 /** 7592 * wp.media.view.Settings 4206 * wp.media.view.Button 4207 * 4208 * @class 4209 * @augments wp.media.View 4210 * @augments wp.Backbone.View 4211 * @augments Backbone.View 4212 */ 4213 var Button = wp.media.View.extend({ 4214 tagName: 'a', 4215 className: 'media-button', 4216 attributes: { href: '#' }, 4217 4218 events: { 4219 'click': 'click' 4220 }, 4221 4222 defaults: { 4223 text: '', 4224 style: '', 4225 size: 'large', 4226 disabled: false 4227 }, 4228 4229 initialize: function() { 4230 /** 4231 * Create a model with the provided `defaults`. 4232 * 4233 * @member {Backbone.Model} 4234 */ 4235 this.model = new Backbone.Model( this.defaults ); 4236 4237 // If any of the `options` have a key from `defaults`, apply its 4238 // value to the `model` and remove it from the `options object. 4239 _.each( this.defaults, function( def, key ) { 4240 var value = this.options[ key ]; 4241 if ( _.isUndefined( value ) ) { 4242 return; 4243 } 4244 4245 this.model.set( key, value ); 4246 delete this.options[ key ]; 4247 }, this ); 4248 4249 this.listenTo( this.model, 'change', this.render ); 4250 }, 4251 /** 4252 * @returns {wp.media.view.Button} Returns itself to allow chaining 4253 */ 4254 render: function() { 4255 var classes = [ 'button', this.className ], 4256 model = this.model.toJSON(); 4257 4258 if ( model.style ) { 4259 classes.push( 'button-' + model.style ); 4260 } 4261 4262 if ( model.size ) { 4263 classes.push( 'button-' + model.size ); 4264 } 4265 4266 classes = _.uniq( classes.concat( this.options.classes ) ); 4267 this.el.className = classes.join(' '); 4268 4269 this.$el.attr( 'disabled', model.disabled ); 4270 this.$el.text( this.model.get('text') ); 4271 4272 return this; 4273 }, 4274 /** 4275 * @param {Object} event 4276 */ 4277 click: function( event ) { 4278 if ( '#' === this.attributes.href ) { 4279 event.preventDefault(); 4280 } 4281 4282 if ( this.options.click && ! this.model.get('disabled') ) { 4283 this.options.click.apply( this, arguments ); 4284 } 4285 } 4286 }); 4287 4288 module.exports = Button; 4289 4290 },{}],34:[function(require,module,exports){ 4291 /*globals wp, _, jQuery */ 4292 4293 /** 4294 * wp.media.view.Cropper 4295 * 4296 * Uses the imgAreaSelect plugin to allow a user to crop an image. 4297 * 4298 * Takes imgAreaSelect options from 4299 * wp.customize.HeaderControl.calculateImageSelectOptions via 4300 * wp.customize.HeaderControl.openMM. 7593 4301 * 7594 4302 * @class … … 7598 4306 */ 7599 4307 var View = wp.media.View, 7600 $ = Backbone.$, 7601 Settings; 7602 7603 Settings = View.extend({ 7604 events: { 7605 'click button': 'updateHandler', 7606 'change input': 'updateHandler', 7607 'change select': 'updateHandler', 7608 'change textarea': 'updateHandler' 7609 }, 7610 4308 UploaderStatus = wp.media.view.UploaderStatus, 4309 l10n = wp.media.view.l10n, 4310 $ = jQuery, 4311 Cropper; 4312 4313 Cropper = View.extend({ 4314 className: 'crop-content', 4315 template: wp.template('crop-content'), 7611 4316 initialize: function() { 7612 this.model = this.model || new Backbone.Model(); 7613 this.listenTo( this.model, 'change', this.updateChanges ); 7614 }, 7615 4317 _.bindAll(this, 'onImageLoad'); 4318 }, 4319 ready: function() { 4320 this.controller.frame.on('content:error:crop', this.onError, this); 4321 this.$image = this.$el.find('.crop-image'); 4322 this.$image.on('load', this.onImageLoad); 4323 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250)); 4324 }, 4325 remove: function() { 4326 $(window).off('resize.cropper'); 4327 this.$el.remove(); 4328 this.$el.off(); 4329 View.prototype.remove.apply(this, arguments); 4330 }, 7616 4331 prepare: function() { 7617 return _.defaults({ 7618 model: this.model.toJSON() 7619 }, this.options ); 7620 }, 7621 /** 7622 * @returns {wp.media.view.Settings} Returns itself to allow chaining 7623 */ 7624 render: function() { 7625 View.prototype.render.apply( this, arguments ); 7626 // Select the correct values. 7627 _( this.model.attributes ).chain().keys().each( this.update, this ); 7628 return this; 7629 }, 7630 /** 7631 * @param {string} key 7632 */ 7633 update: function( key ) { 7634 var value = this.model.get( key ), 7635 $setting = this.$('[data-setting="' + key + '"]'), 7636 $buttons, $value; 7637 7638 // Bail if we didn't find a matching setting. 7639 if ( ! $setting.length ) { 7640 return; 7641 } 7642 7643 // Attempt to determine how the setting is rendered and update 7644 // the selected value. 7645 7646 // Handle dropdowns. 7647 if ( $setting.is('select') ) { 7648 $value = $setting.find('[value="' + value + '"]'); 7649 7650 if ( $value.length ) { 7651 $setting.find('option').prop( 'selected', false ); 7652 $value.prop( 'selected', true ); 7653 } else { 7654 // If we can't find the desired value, record what *is* selected. 7655 this.model.set( key, $setting.find(':selected').val() ); 7656 } 7657 7658 // Handle button groups. 7659 } else if ( $setting.hasClass('button-group') ) { 7660 $buttons = $setting.find('button').removeClass('active'); 7661 $buttons.filter( '[value="' + value + '"]' ).addClass('active'); 7662 7663 // Handle text inputs and textareas. 7664 } else if ( $setting.is('input[type="text"], textarea') ) { 7665 if ( ! $setting.is(':focus') ) { 7666 $setting.val( value ); 7667 } 7668 // Handle checkboxes. 7669 } else if ( $setting.is('input[type="checkbox"]') ) { 7670 $setting.prop( 'checked', !! value && 'false' !== value ); 7671 } 7672 }, 7673 /** 7674 * @param {Object} event 7675 */ 7676 updateHandler: function( event ) { 7677 var $setting = $( event.target ).closest('[data-setting]'), 7678 value = event.target.value, 7679 userSetting; 7680 7681 event.preventDefault(); 7682 7683 if ( ! $setting.length ) { 7684 return; 7685 } 7686 7687 // Use the correct value for checkboxes. 7688 if ( $setting.is('input[type="checkbox"]') ) { 7689 value = $setting[0].checked; 7690 } 7691 7692 // Update the corresponding setting. 7693 this.model.set( $setting.data('setting'), value ); 7694 7695 // If the setting has a corresponding user setting, 7696 // update that as well. 7697 if ( userSetting = $setting.data('userSetting') ) { 7698 window.setUserSetting( userSetting, value ); 7699 } 7700 }, 7701 7702 updateChanges: function( model ) { 7703 if ( model.hasChanged() ) { 7704 _( model.changed ).chain().keys().each( this.update, this ); 7705 } 4332 return { 4333 title: l10n.cropYourImage, 4334 url: this.options.attachment.get('url') 4335 }; 4336 }, 4337 onImageLoad: function() { 4338 var imgOptions = this.controller.get('imgSelectOptions'); 4339 if (typeof imgOptions === 'function') { 4340 imgOptions = imgOptions(this.options.attachment, this.controller); 4341 } 4342 4343 imgOptions = _.extend(imgOptions, {parent: this.$el}); 4344 this.trigger('image-loaded'); 4345 this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 4346 }, 4347 onError: function() { 4348 var filename = this.options.attachment.get('filename'); 4349 4350 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 4351 filename: UploaderStatus.prototype.filename(filename), 4352 message: window._wpMediaViewsL10n.cropError 4353 }), { at: 0 }); 7706 4354 } 7707 4355 }); 7708 4356 7709 module.exports = Settings; 7710 7711 7712 /***/ }), 7713 /* 84 */ 7714 /***/ (function(module, exports) { 7715 4357 module.exports = Cropper; 4358 4359 },{}],35:[function(require,module,exports){ 7716 4360 /*globals wp, _ */ 7717 4361 7718 4362 /** 7719 * wp.media.view.Settings.AttachmentDisplay 7720 * 7721 * @class 7722 * @augments wp.media.view.Settings 7723 * @augments wp.media.View 7724 * @augments wp.Backbone.View 7725 * @augments Backbone.View 7726 */ 7727 var Settings = wp.media.view.Settings, 7728 AttachmentDisplay; 7729 7730 AttachmentDisplay = Settings.extend({ 7731 className: 'attachment-display-settings', 7732 template: wp.template('attachment-display-settings'), 7733 7734 initialize: function() { 7735 var attachment = this.options.attachment; 7736 7737 _.defaults( this.options, { 7738 userSettings: false 7739 }); 7740 // Call 'initialize' directly on the parent class. 7741 Settings.prototype.initialize.apply( this, arguments ); 7742 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 7743 7744 if ( attachment ) { 7745 attachment.on( 'change:uploading', this.render, this ); 7746 } 7747 }, 7748 7749 dispose: function() { 7750 var attachment = this.options.attachment; 7751 if ( attachment ) { 7752 attachment.off( null, null, this ); 7753 } 7754 /** 7755 * call 'dispose' directly on the parent class 7756 */ 7757 Settings.prototype.dispose.apply( this, arguments ); 7758 }, 7759 /** 7760 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining 7761 */ 7762 render: function() { 7763 var attachment = this.options.attachment; 7764 if ( attachment ) { 7765 _.extend( this.options, { 7766 sizes: attachment.get('sizes'), 7767 type: attachment.get('type') 7768 }); 7769 } 7770 /** 7771 * call 'render' directly on the parent class 7772 */ 7773 Settings.prototype.render.call( this ); 7774 this.updateLinkTo(); 7775 return this; 7776 }, 7777 7778 updateLinkTo: function() { 7779 var linkTo = this.model.get('link'), 7780 $input = this.$('.link-to-custom'), 7781 attachment = this.options.attachment; 7782 7783 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 7784 $input.addClass( 'hidden' ); 7785 return; 7786 } 7787 7788 if ( attachment ) { 7789 if ( 'post' === linkTo ) { 7790 $input.val( attachment.get('link') ); 7791 } else if ( 'file' === linkTo ) { 7792 $input.val( attachment.get('url') ); 7793 } else if ( ! this.model.get('linkUrl') ) { 7794 $input.val('http://'); 7795 } 7796 7797 $input.prop( 'readonly', 'custom' !== linkTo ); 7798 } 7799 7800 $input.removeClass( 'hidden' ); 7801 7802 // If the input is visible, focus and select its contents. 7803 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) { 7804 $input.focus()[0].select(); 7805 } 7806 } 7807 }); 7808 7809 module.exports = AttachmentDisplay; 7810 7811 7812 /***/ }), 7813 /* 85 */ 7814 /***/ (function(module, exports) { 7815 7816 /*globals wp */ 7817 7818 /** 7819 * wp.media.view.Settings.Gallery 7820 * 7821 * @class 7822 * @augments wp.media.view.Settings 7823 * @augments wp.media.View 7824 * @augments wp.Backbone.View 7825 * @augments Backbone.View 7826 */ 7827 var Gallery = wp.media.view.Settings.extend({ 7828 className: 'collection-settings gallery-settings', 7829 template: wp.template('gallery-settings') 7830 }); 7831 7832 module.exports = Gallery; 7833 7834 7835 /***/ }), 7836 /* 86 */ 7837 /***/ (function(module, exports) { 7838 7839 /*globals wp */ 7840 7841 /** 7842 * wp.media.view.Settings.Playlist 7843 * 7844 * @class 7845 * @augments wp.media.view.Settings 7846 * @augments wp.media.View 7847 * @augments wp.Backbone.View 7848 * @augments Backbone.View 7849 */ 7850 var Playlist = wp.media.view.Settings.extend({ 7851 className: 'collection-settings playlist-settings', 7852 template: wp.template('playlist-settings') 7853 }); 7854 7855 module.exports = Playlist; 7856 7857 7858 /***/ }), 7859 /* 87 */ 7860 /***/ (function(module, exports) { 7861 7862 /*globals wp, _ */ 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 'click .refresh-attachment': 'refreshAttachment', 7899 'keydown': 'toggleSelectionHandler' 7900 }, 7901 7902 initialize: function() { 7903 this.options = _.defaults( this.options, { 7904 rerenderOnModelChange: false 7905 }); 7906 7907 this.on( 'ready', this.initialFocus ); 7908 // Call 'initialize' directly on the parent class. 7909 Attachment.prototype.initialize.apply( this, arguments ); 7910 }, 7911 7912 initialFocus: function() { 7913 if ( ! wp.media.isTouchDevice ) { 7914 this.$( ':input' ).eq( 0 ).focus(); 7915 } 7916 }, 7917 /** 7918 * @param {Object} event 7919 */ 7920 deleteAttachment: function( event ) { 7921 event.preventDefault(); 7922 7923 if ( window.confirm( l10n.warnDelete ) ) { 7924 this.model.destroy(); 7925 // Keep focus inside media modal 7926 // after image is deleted 7927 this.controller.modal.focusManager.focus(); 7928 } 7929 }, 7930 /** 7931 * @param {Object} event 7932 */ 7933 trashAttachment: function( event ) { 7934 var library = this.controller.library; 7935 event.preventDefault(); 7936 7937 if ( wp.media.view.settings.mediaTrash && 7938 'edit-metadata' === this.controller.content.mode() ) { 7939 7940 this.model.set( 'status', 'trash' ); 7941 this.model.save().done( function() { 7942 library._requery( true ); 7943 } ); 7944 } else { 7945 this.model.destroy(); 7946 } 7947 }, 7948 /** 7949 * @param {Object} event 7950 */ 7951 untrashAttachment: function( event ) { 7952 var library = this.controller.library; 7953 event.preventDefault(); 7954 7955 this.model.set( 'status', 'inherit' ); 7956 this.model.save().done( function() { 7957 library._requery( true ); 7958 } ); 7959 }, 7960 /** 7961 * @param {Object} event 7962 */ 7963 editAttachment: function( event ) { 7964 var editState = this.controller.states.get( 'edit-image' ); 7965 if ( window.imageEdit && editState ) { 7966 event.preventDefault(); 7967 7968 editState.set( 'image', this.model ); 7969 this.controller.setState( 'edit-image' ); 7970 } else { 7971 this.$el.addClass('needs-refresh'); 7972 } 7973 }, 7974 /** 7975 * @param {Object} event 7976 */ 7977 refreshAttachment: function( event ) { 7978 this.$el.removeClass('needs-refresh'); 7979 event.preventDefault(); 7980 this.model.fetch(); 7981 }, 7982 /** 7983 * When reverse tabbing(shift+tab) out of the right details panel, deliver 7984 * the focus to the item in the list that was being edited. 7985 * 7986 * @param {Object} event 7987 */ 7988 toggleSelectionHandler: function( event ) { 7989 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 7990 this.controller.trigger( 'attachment:details:shift-tab', event ); 7991 return false; 7992 } 7993 7994 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 7995 this.controller.trigger( 'attachment:keydown:arrow', event ); 7996 return; 7997 } 7998 } 7999 }); 8000 8001 module.exports = Details; 8002 8003 8004 /***/ }), 8005 /* 88 */ 8006 /***/ (function(module, exports) { 8007 8008 /*globals _ */ 8009 8010 /** 8011 * wp.media.view.AttachmentCompat 8012 * 8013 * A view to display fields added via the `attachment_fields_to_edit` filter. 4363 * wp.media.view.EditImage 8014 4364 * 8015 4365 * @class … … 8019 4369 */ 8020 4370 var View = wp.media.View, 8021 AttachmentCompat; 8022 8023 AttachmentCompat = View.extend({ 8024 tagName: 'form', 8025 className: 'compat-item', 8026 8027 events: { 8028 'submit': 'preventDefault', 8029 'change input': 'save', 8030 'change select': 'save', 8031 'change textarea': 'save' 8032 }, 8033 8034 initialize: function() { 8035 this.listenTo( this.model, 'change:compat', this.render ); 8036 }, 8037 /** 8038 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 8039 */ 8040 dispose: function() { 8041 if ( this.$(':focus').length ) { 8042 this.save(); 8043 } 8044 /** 8045 * call 'dispose' directly on the parent class 8046 */ 8047 return View.prototype.dispose.apply( this, arguments ); 8048 }, 8049 /** 8050 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 8051 */ 8052 render: function() { 8053 var compat = this.model.get('compat'); 8054 if ( ! compat || ! compat.item ) { 8055 return; 8056 } 8057 8058 this.views.detach(); 8059 this.$el.html( compat.item ); 8060 this.views.render(); 8061 return this; 8062 }, 8063 /** 8064 * @param {Object} event 8065 */ 8066 preventDefault: function( event ) { 8067 event.preventDefault(); 8068 }, 8069 /** 8070 * @param {Object} event 8071 */ 8072 save: function( event ) { 8073 var data = {}; 8074 8075 if ( event ) { 8076 event.preventDefault(); 8077 } 8078 8079 _.each( this.$el.serializeArray(), function( pair ) { 8080 data[ pair.name ] = pair.value; 8081 }); 8082 8083 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 8084 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 8085 }, 8086 8087 postSave: function() { 8088 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 4371 EditImage; 4372 4373 EditImage = View.extend({ 4374 className: 'image-editor', 4375 template: wp.template('image-editor'), 4376 4377 initialize: function( options ) { 4378 this.editor = window.imageEdit; 4379 this.controller = options.controller; 4380 View.prototype.initialize.apply( this, arguments ); 4381 }, 4382 4383 prepare: function() { 4384 return this.model.toJSON(); 4385 }, 4386 4387 loadEditor: function() { 4388 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this ); 4389 dfd.done( _.bind( this.focus, this ) ); 4390 }, 4391 4392 focus: function() { 4393 this.$( '.imgedit-submit .button' ).eq( 0 ).focus(); 4394 }, 4395 4396 back: function() { 4397 var lastState = this.controller.lastState(); 4398 this.controller.setState( lastState ); 4399 }, 4400 4401 refresh: function() { 4402 this.model.fetch(); 4403 }, 4404 4405 save: function() { 4406 var lastState = this.controller.lastState(); 4407 4408 this.model.fetch().done( _.bind( function() { 4409 this.controller.setState( lastState ); 4410 }, this ) ); 8089 4411 } 4412 8090 4413 }); 8091 4414 8092 module.exports = AttachmentCompat; 8093 8094 8095 /***/ }), 8096 /* 89 */ 8097 /***/ (function(module, exports) { 8098 8099 /** 8100 * wp.media.view.Iframe 8101 * 8102 * @class 8103 * @augments wp.media.View 8104 * @augments wp.Backbone.View 8105 * @augments Backbone.View 8106 */ 8107 var Iframe = wp.media.View.extend({ 8108 className: 'media-iframe', 8109 /** 8110 * @returns {wp.media.view.Iframe} Returns itself to allow chaining 8111 */ 8112 render: function() { 8113 this.views.detach(); 8114 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 8115 this.views.render(); 8116 return this; 8117 } 8118 }); 8119 8120 module.exports = Iframe; 8121 8122 8123 /***/ }), 8124 /* 90 */ 8125 /***/ (function(module, exports) { 8126 4415 module.exports = EditImage; 4416 4417 },{}],36:[function(require,module,exports){ 8127 4418 /** 8128 4419 * wp.media.view.Embed … … 8188 4479 module.exports = Embed; 8189 4480 8190 8191 /***/ }), 8192 /* 91 */ 8193 /***/ (function(module, exports) { 8194 8195 /** 8196 * wp.media.view.Label 8197 * 8198 * @class 8199 * @augments wp.media.View 8200 * @augments wp.Backbone.View 8201 * @augments Backbone.View 8202 */ 8203 var Label = wp.media.View.extend({ 8204 tagName: 'label', 8205 className: 'screen-reader-text', 8206 8207 initialize: function() { 8208 this.value = this.options.value; 8209 }, 8210 8211 render: function() { 8212 this.$el.html( this.value ); 8213 8214 return this; 8215 } 8216 }); 8217 8218 module.exports = Label; 8219 8220 8221 /***/ }), 8222 /* 92 */ 8223 /***/ (function(module, exports) { 8224 8225 /*globals wp, _, jQuery */ 8226 8227 /** 8228 * wp.media.view.EmbedUrl 8229 * 8230 * @class 8231 * @augments wp.media.View 8232 * @augments wp.Backbone.View 8233 * @augments Backbone.View 8234 */ 8235 var View = wp.media.View, 8236 $ = jQuery, 8237 EmbedUrl; 8238 8239 EmbedUrl = View.extend({ 8240 tagName: 'label', 8241 className: 'embed-url', 8242 8243 events: { 8244 'input': 'url', 8245 'keyup': 'url', 8246 'change': 'url' 8247 }, 8248 8249 initialize: function() { 8250 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') ); 8251 this.input = this.$input[0]; 8252 8253 this.spinner = $('<span class="spinner" />')[0]; 8254 this.$el.append([ this.input, this.spinner ]); 8255 8256 this.listenTo( this.model, 'change:url', this.render ); 8257 8258 if ( this.model.get( 'url' ) ) { 8259 _.delay( _.bind( function () { 8260 this.model.trigger( 'change:url' ); 8261 }, this ), 500 ); 8262 } 8263 }, 8264 /** 8265 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining 8266 */ 8267 render: function() { 8268 var $input = this.$input; 8269 8270 if ( $input.is(':focus') ) { 8271 return; 8272 } 8273 8274 this.input.value = this.model.get('url') || 'http://'; 8275 /** 8276 * Call `render` directly on parent class with passed arguments 8277 */ 8278 View.prototype.render.apply( this, arguments ); 8279 return this; 8280 }, 8281 8282 ready: function() { 8283 if ( ! wp.media.isTouchDevice ) { 8284 this.focus(); 8285 } 8286 }, 8287 8288 url: function( event ) { 8289 this.model.set( 'url', event.target.value ); 8290 }, 8291 8292 /** 8293 * If the input is visible, focus and select its contents. 8294 */ 8295 focus: function() { 8296 var $input = this.$input; 8297 if ( $input.is(':visible') ) { 8298 $input.focus()[0].select(); 8299 } 8300 } 8301 }); 8302 8303 module.exports = EmbedUrl; 8304 8305 8306 /***/ }), 8307 /* 93 */ 8308 /***/ (function(module, exports) { 8309 8310 /*globals wp, _, jQuery */ 8311 8312 /** 8313 * wp.media.view.EmbedLink 8314 * 8315 * @class 8316 * @augments wp.media.view.Settings 8317 * @augments wp.media.View 8318 * @augments wp.Backbone.View 8319 * @augments Backbone.View 8320 */ 8321 var $ = jQuery, 8322 EmbedLink; 8323 8324 EmbedLink = wp.media.view.Settings.extend({ 8325 className: 'embed-link-settings', 8326 template: wp.template('embed-link-settings'), 8327 8328 initialize: function() { 8329 this.spinner = $('<span class="spinner" />'); 8330 this.$el.append( this.spinner[0] ); 8331 this.listenTo( this.model, 'change:url', this.updateoEmbed ); 8332 }, 8333 8334 updateoEmbed: _.debounce( function() { 8335 var url = this.model.get( 'url' ); 8336 8337 // clear out previous results 8338 this.$('.embed-container').hide().find('.embed-preview').empty(); 8339 this.$( '.setting' ).hide(); 8340 8341 // only proceed with embed if the field contains more than 6 characters 8342 if ( url && url.length < 6 ) { 8343 return; 8344 } 8345 8346 this.fetch(); 8347 }, 600 ), 8348 8349 fetch: function() { 8350 // check if they haven't typed in 500 ms 8351 if ( $('#embed-url-field').val() !== this.model.get('url') ) { 8352 return; 8353 } 8354 8355 wp.ajax.send( 'parse-embed', { 8356 data : { 8357 post_ID: wp.media.view.settings.post.id, 8358 shortcode: '[embed]' + this.model.get('url') + '[/embed]' 8359 } 8360 } ) 8361 .done( _.bind( this.renderoEmbed, this ) ) 8362 .fail( _.bind( this.renderFail, this ) ); 8363 }, 8364 8365 renderFail: function () { 8366 this.$( '.link-text' ).show(); 8367 }, 8368 8369 renderoEmbed: function( response ) { 8370 var html = ( response && response.body ) || ''; 8371 8372 if ( html ) { 8373 this.$('.embed-container').show().find('.embed-preview').html( html ); 8374 } else { 8375 this.renderFail(); 8376 } 8377 } 8378 }); 8379 8380 module.exports = EmbedLink; 8381 8382 8383 /***/ }), 8384 /* 94 */ 8385 /***/ (function(module, exports) { 8386 4481 },{}],37:[function(require,module,exports){ 8387 4482 /*globals wp */ 8388 4483 … … 8419 4514 module.exports = EmbedImage; 8420 4515 8421 8422 /***/ }), 8423 /* 95 */ 8424 /***/ (function(module, exports) { 8425 4516 },{}],38:[function(require,module,exports){ 4517 /*globals wp, _, jQuery */ 4518 4519 /** 4520 * wp.media.view.EmbedLink 4521 * 4522 * @class 4523 * @augments wp.media.view.Settings 4524 * @augments wp.media.View 4525 * @augments wp.Backbone.View 4526 * @augments Backbone.View 4527 */ 4528 var $ = jQuery, 4529 EmbedLink; 4530 4531 EmbedLink = wp.media.view.Settings.extend({ 4532 className: 'embed-link-settings', 4533 template: wp.template('embed-link-settings'), 4534 4535 initialize: function() { 4536 this.spinner = $('<span class="spinner" />'); 4537 this.$el.append( this.spinner[0] ); 4538 this.listenTo( this.model, 'change:url', this.updateoEmbed ); 4539 }, 4540 4541 updateoEmbed: _.debounce( function() { 4542 var url = this.model.get( 'url' ); 4543 4544 // clear out previous results 4545 this.$('.embed-container').hide().find('.embed-preview').empty(); 4546 this.$( '.setting' ).hide(); 4547 4548 // only proceed with embed if the field contains more than 6 characters 4549 if ( url && url.length < 6 ) { 4550 return; 4551 } 4552 4553 this.fetch(); 4554 }, 600 ), 4555 4556 fetch: function() { 4557 // check if they haven't typed in 500 ms 4558 if ( $('#embed-url-field').val() !== this.model.get('url') ) { 4559 return; 4560 } 4561 4562 wp.ajax.send( 'parse-embed', { 4563 data : { 4564 post_ID: wp.media.view.settings.post.id, 4565 shortcode: '[embed]' + this.model.get('url') + '[/embed]' 4566 } 4567 } ) 4568 .done( _.bind( this.renderoEmbed, this ) ) 4569 .fail( _.bind( this.renderFail, this ) ); 4570 }, 4571 4572 renderFail: function () { 4573 this.$( '.link-text' ).show(); 4574 }, 4575 4576 renderoEmbed: function( response ) { 4577 var html = ( response && response.body ) || ''; 4578 4579 if ( html ) { 4580 this.$('.embed-container').show().find('.embed-preview').html( html ); 4581 } else { 4582 this.renderFail(); 4583 } 4584 } 4585 }); 4586 4587 module.exports = EmbedLink; 4588 4589 },{}],39:[function(require,module,exports){ 4590 /*globals wp, _, jQuery */ 4591 4592 /** 4593 * wp.media.view.EmbedUrl 4594 * 4595 * @class 4596 * @augments wp.media.View 4597 * @augments wp.Backbone.View 4598 * @augments Backbone.View 4599 */ 4600 var View = wp.media.View, 4601 $ = jQuery, 4602 EmbedUrl; 4603 4604 EmbedUrl = View.extend({ 4605 tagName: 'label', 4606 className: 'embed-url', 4607 4608 events: { 4609 'input': 'url', 4610 'keyup': 'url', 4611 'change': 'url' 4612 }, 4613 4614 initialize: function() { 4615 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') ); 4616 this.input = this.$input[0]; 4617 4618 this.spinner = $('<span class="spinner" />')[0]; 4619 this.$el.append([ this.input, this.spinner ]); 4620 4621 this.listenTo( this.model, 'change:url', this.render ); 4622 4623 if ( this.model.get( 'url' ) ) { 4624 _.delay( _.bind( function () { 4625 this.model.trigger( 'change:url' ); 4626 }, this ), 500 ); 4627 } 4628 }, 4629 /** 4630 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining 4631 */ 4632 render: function() { 4633 var $input = this.$input; 4634 4635 if ( $input.is(':focus') ) { 4636 return; 4637 } 4638 4639 this.input.value = this.model.get('url') || 'http://'; 4640 /** 4641 * Call `render` directly on parent class with passed arguments 4642 */ 4643 View.prototype.render.apply( this, arguments ); 4644 return this; 4645 }, 4646 4647 ready: function() { 4648 if ( ! wp.media.isTouchDevice ) { 4649 this.focus(); 4650 } 4651 }, 4652 4653 url: function( event ) { 4654 this.model.set( 'url', event.target.value ); 4655 }, 4656 4657 /** 4658 * If the input is visible, focus and select its contents. 4659 */ 4660 focus: function() { 4661 var $input = this.$input; 4662 if ( $input.is(':visible') ) { 4663 $input.focus()[0].select(); 4664 } 4665 } 4666 }); 4667 4668 module.exports = EmbedUrl; 4669 4670 },{}],40:[function(require,module,exports){ 4671 /** 4672 * wp.media.view.FocusManager 4673 * 4674 * @class 4675 * @augments wp.media.View 4676 * @augments wp.Backbone.View 4677 * @augments Backbone.View 4678 */ 4679 var FocusManager = wp.media.View.extend({ 4680 4681 events: { 4682 'keydown': 'constrainTabbing' 4683 }, 4684 4685 focus: function() { // Reset focus on first left menu item 4686 this.$('.media-menu-item').first().focus(); 4687 }, 4688 /** 4689 * @param {Object} event 4690 */ 4691 constrainTabbing: function( event ) { 4692 var tabbables; 4693 4694 // Look for the tab key. 4695 if ( 9 !== event.keyCode ) { 4696 return; 4697 } 4698 4699 // Skip the file input added by Plupload. 4700 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 4701 4702 // Keep tab focus within media modal while it's open 4703 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 4704 tabbables.first().focus(); 4705 return false; 4706 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 4707 tabbables.last().focus(); 4708 return false; 4709 } 4710 } 4711 4712 }); 4713 4714 module.exports = FocusManager; 4715 4716 },{}],41:[function(require,module,exports){ 4717 /*globals _, Backbone */ 4718 4719 /** 4720 * wp.media.view.Frame 4721 * 4722 * A frame is a composite view consisting of one or more regions and one or more 4723 * states. 4724 * 4725 * @see wp.media.controller.State 4726 * @see wp.media.controller.Region 4727 * 4728 * @class 4729 * @augments wp.media.View 4730 * @augments wp.Backbone.View 4731 * @augments Backbone.View 4732 * @mixes wp.media.controller.StateMachine 4733 */ 4734 var Frame = wp.media.View.extend({ 4735 initialize: function() { 4736 _.defaults( this.options, { 4737 mode: [ 'select' ] 4738 }); 4739 this._createRegions(); 4740 this._createStates(); 4741 this._createModes(); 4742 }, 4743 4744 _createRegions: function() { 4745 // Clone the regions array. 4746 this.regions = this.regions ? this.regions.slice() : []; 4747 4748 // Initialize regions. 4749 _.each( this.regions, function( region ) { 4750 this[ region ] = new wp.media.controller.Region({ 4751 view: this, 4752 id: region, 4753 selector: '.media-frame-' + region 4754 }); 4755 }, this ); 4756 }, 4757 /** 4758 * Create the frame's states. 4759 * 4760 * @see wp.media.controller.State 4761 * @see wp.media.controller.StateMachine 4762 * 4763 * @fires wp.media.controller.State#ready 4764 */ 4765 _createStates: function() { 4766 // Create the default `states` collection. 4767 this.states = new Backbone.Collection( null, { 4768 model: wp.media.controller.State 4769 }); 4770 4771 // Ensure states have a reference to the frame. 4772 this.states.on( 'add', function( model ) { 4773 model.frame = this; 4774 model.trigger('ready'); 4775 }, this ); 4776 4777 if ( this.options.states ) { 4778 this.states.add( this.options.states ); 4779 } 4780 }, 4781 4782 /** 4783 * A frame can be in a mode or multiple modes at one time. 4784 * 4785 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 4786 */ 4787 _createModes: function() { 4788 // Store active "modes" that the frame is in. Unrelated to region modes. 4789 this.activeModes = new Backbone.Collection(); 4790 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 4791 4792 _.each( this.options.mode, function( mode ) { 4793 this.activateMode( mode ); 4794 }, this ); 4795 }, 4796 /** 4797 * Reset all states on the frame to their defaults. 4798 * 4799 * @returns {wp.media.view.Frame} Returns itself to allow chaining 4800 */ 4801 reset: function() { 4802 this.states.invoke( 'trigger', 'reset' ); 4803 return this; 4804 }, 4805 /** 4806 * Map activeMode collection events to the frame. 4807 */ 4808 triggerModeEvents: function( model, collection, options ) { 4809 var collectionEvent, 4810 modeEventMap = { 4811 add: 'activate', 4812 remove: 'deactivate' 4813 }, 4814 eventToTrigger; 4815 // Probably a better way to do this. 4816 _.each( options, function( value, key ) { 4817 if ( value ) { 4818 collectionEvent = key; 4819 } 4820 } ); 4821 4822 if ( ! _.has( modeEventMap, collectionEvent ) ) { 4823 return; 4824 } 4825 4826 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 4827 this.trigger( eventToTrigger ); 4828 }, 4829 /** 4830 * Activate a mode on the frame. 4831 * 4832 * @param string mode Mode ID. 4833 * @returns {this} Returns itself to allow chaining. 4834 */ 4835 activateMode: function( mode ) { 4836 // Bail if the mode is already active. 4837 if ( this.isModeActive( mode ) ) { 4838 return; 4839 } 4840 this.activeModes.add( [ { id: mode } ] ); 4841 // Add a CSS class to the frame so elements can be styled for the mode. 4842 this.$el.addClass( 'mode-' + mode ); 4843 4844 return this; 4845 }, 4846 /** 4847 * Deactivate a mode on the frame. 4848 * 4849 * @param string mode Mode ID. 4850 * @returns {this} Returns itself to allow chaining. 4851 */ 4852 deactivateMode: function( mode ) { 4853 // Bail if the mode isn't active. 4854 if ( ! this.isModeActive( mode ) ) { 4855 return this; 4856 } 4857 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 4858 this.$el.removeClass( 'mode-' + mode ); 4859 /** 4860 * Frame mode deactivation event. 4861 * 4862 * @event this#{mode}:deactivate 4863 */ 4864 this.trigger( mode + ':deactivate' ); 4865 4866 return this; 4867 }, 4868 /** 4869 * Check if a mode is enabled on the frame. 4870 * 4871 * @param string mode Mode ID. 4872 * @return bool 4873 */ 4874 isModeActive: function( mode ) { 4875 return Boolean( this.activeModes.where( { id: mode } ).length ); 4876 } 4877 }); 4878 4879 // Make the `Frame` a `StateMachine`. 4880 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 4881 4882 module.exports = Frame; 4883 4884 },{}],42:[function(require,module,exports){ 4885 /*globals wp */ 4886 4887 /** 4888 * wp.media.view.MediaFrame.ImageDetails 4889 * 4890 * A media frame for manipulating an image that's already been inserted 4891 * into a post. 4892 * 4893 * @class 4894 * @augments wp.media.view.MediaFrame.Select 4895 * @augments wp.media.view.MediaFrame 4896 * @augments wp.media.view.Frame 4897 * @augments wp.media.View 4898 * @augments wp.Backbone.View 4899 * @augments Backbone.View 4900 * @mixes wp.media.controller.StateMachine 4901 */ 4902 var Select = wp.media.view.MediaFrame.Select, 4903 l10n = wp.media.view.l10n, 4904 ImageDetails; 4905 4906 ImageDetails = Select.extend({ 4907 defaults: { 4908 id: 'image', 4909 url: '', 4910 menu: 'image-details', 4911 content: 'image-details', 4912 toolbar: 'image-details', 4913 type: 'link', 4914 title: l10n.imageDetailsTitle, 4915 priority: 120 4916 }, 4917 4918 initialize: function( options ) { 4919 this.image = new wp.media.model.PostImage( options.metadata ); 4920 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 4921 Select.prototype.initialize.apply( this, arguments ); 4922 }, 4923 4924 bindHandlers: function() { 4925 Select.prototype.bindHandlers.apply( this, arguments ); 4926 this.on( 'menu:create:image-details', this.createMenu, this ); 4927 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 4928 this.on( 'content:render:edit-image', this.editImageContent, this ); 4929 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 4930 // override the select toolbar 4931 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 4932 }, 4933 4934 createStates: function() { 4935 this.states.add([ 4936 new wp.media.controller.ImageDetails({ 4937 image: this.image, 4938 editable: false 4939 }), 4940 new wp.media.controller.ReplaceImage({ 4941 id: 'replace-image', 4942 library: wp.media.query( { type: 'image' } ), 4943 image: this.image, 4944 multiple: false, 4945 title: l10n.imageReplaceTitle, 4946 toolbar: 'replace', 4947 priority: 80, 4948 displaySettings: true 4949 }), 4950 new wp.media.controller.EditImage( { 4951 image: this.image, 4952 selection: this.options.selection 4953 } ) 4954 ]); 4955 }, 4956 4957 imageDetailsContent: function( options ) { 4958 options.view = new wp.media.view.ImageDetails({ 4959 controller: this, 4960 model: this.state().image, 4961 attachment: this.state().image.attachment 4962 }); 4963 }, 4964 4965 editImageContent: function() { 4966 var state = this.state(), 4967 model = state.get('image'), 4968 view; 4969 4970 if ( ! model ) { 4971 return; 4972 } 4973 4974 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 4975 4976 this.content.set( view ); 4977 4978 // after bringing in the frame, load the actual editor via an ajax call 4979 view.loadEditor(); 4980 4981 }, 4982 4983 renderImageDetailsToolbar: function() { 4984 this.toolbar.set( new wp.media.view.Toolbar({ 4985 controller: this, 4986 items: { 4987 select: { 4988 style: 'primary', 4989 text: l10n.update, 4990 priority: 80, 4991 4992 click: function() { 4993 var controller = this.controller, 4994 state = controller.state(); 4995 4996 controller.close(); 4997 4998 // not sure if we want to use wp.media.string.image which will create a shortcode or 4999 // perhaps wp.html.string to at least to build the <img /> 5000 state.trigger( 'update', controller.image.toJSON() ); 5001 5002 // Restore and reset the default state. 5003 controller.setState( controller.options.state ); 5004 controller.reset(); 5005 } 5006 } 5007 } 5008 }) ); 5009 }, 5010 5011 renderReplaceImageToolbar: function() { 5012 var frame = this, 5013 lastState = frame.lastState(), 5014 previous = lastState && lastState.id; 5015 5016 this.toolbar.set( new wp.media.view.Toolbar({ 5017 controller: this, 5018 items: { 5019 back: { 5020 text: l10n.back, 5021 priority: 20, 5022 click: function() { 5023 if ( previous ) { 5024 frame.setState( previous ); 5025 } else { 5026 frame.close(); 5027 } 5028 } 5029 }, 5030 5031 replace: { 5032 style: 'primary', 5033 text: l10n.replace, 5034 priority: 80, 5035 5036 click: function() { 5037 var controller = this.controller, 5038 state = controller.state(), 5039 selection = state.get( 'selection' ), 5040 attachment = selection.single(); 5041 5042 controller.close(); 5043 5044 controller.image.changeAttachment( attachment, state.display( attachment ) ); 5045 5046 // not sure if we want to use wp.media.string.image which will create a shortcode or 5047 // perhaps wp.html.string to at least to build the <img /> 5048 state.trigger( 'replace', controller.image.toJSON() ); 5049 5050 // Restore and reset the default state. 5051 controller.setState( controller.options.state ); 5052 controller.reset(); 5053 } 5054 } 5055 } 5056 }) ); 5057 } 5058 5059 }); 5060 5061 module.exports = ImageDetails; 5062 5063 },{}],43:[function(require,module,exports){ 5064 /*globals wp, _ */ 5065 5066 /** 5067 * wp.media.view.MediaFrame.Post 5068 * 5069 * The frame for manipulating media on the Edit Post page. 5070 * 5071 * @class 5072 * @augments wp.media.view.MediaFrame.Select 5073 * @augments wp.media.view.MediaFrame 5074 * @augments wp.media.view.Frame 5075 * @augments wp.media.View 5076 * @augments wp.Backbone.View 5077 * @augments Backbone.View 5078 * @mixes wp.media.controller.StateMachine 5079 */ 5080 var Select = wp.media.view.MediaFrame.Select, 5081 Library = wp.media.controller.Library, 5082 l10n = wp.media.view.l10n, 5083 Post; 5084 5085 Post = Select.extend({ 5086 initialize: function() { 5087 this.counts = { 5088 audio: { 5089 count: wp.media.view.settings.attachmentCounts.audio, 5090 state: 'playlist' 5091 }, 5092 video: { 5093 count: wp.media.view.settings.attachmentCounts.video, 5094 state: 'video-playlist' 5095 } 5096 }; 5097 5098 _.defaults( this.options, { 5099 multiple: true, 5100 editing: false, 5101 state: 'insert', 5102 metadata: {} 5103 }); 5104 5105 // Call 'initialize' directly on the parent class. 5106 Select.prototype.initialize.apply( this, arguments ); 5107 this.createIframeStates(); 5108 5109 }, 5110 5111 /** 5112 * Create the default states. 5113 */ 5114 createStates: function() { 5115 var options = this.options; 5116 5117 this.states.add([ 5118 // Main states. 5119 new Library({ 5120 id: 'insert', 5121 title: l10n.insertMediaTitle, 5122 priority: 20, 5123 toolbar: 'main-insert', 5124 filterable: 'all', 5125 library: wp.media.query( options.library ), 5126 multiple: options.multiple ? 'reset' : false, 5127 editable: true, 5128 5129 // If the user isn't allowed to edit fields, 5130 // can they still edit it locally? 5131 allowLocalEdits: true, 5132 5133 // Show the attachment display settings. 5134 displaySettings: true, 5135 // Update user settings when users adjust the 5136 // attachment display settings. 5137 displayUserSettings: true 5138 }), 5139 5140 new Library({ 5141 id: 'gallery', 5142 title: l10n.createGalleryTitle, 5143 priority: 40, 5144 toolbar: 'main-gallery', 5145 filterable: 'uploaded', 5146 multiple: 'add', 5147 editable: false, 5148 5149 library: wp.media.query( _.defaults({ 5150 type: 'image' 5151 }, options.library ) ) 5152 }), 5153 5154 // Embed states. 5155 new wp.media.controller.Embed( { metadata: options.metadata } ), 5156 5157 new wp.media.controller.EditImage( { model: options.editImage } ), 5158 5159 // Gallery states. 5160 new wp.media.controller.GalleryEdit({ 5161 library: options.selection, 5162 editing: options.editing, 5163 menu: 'gallery' 5164 }), 5165 5166 new wp.media.controller.GalleryAdd(), 5167 5168 new Library({ 5169 id: 'playlist', 5170 title: l10n.createPlaylistTitle, 5171 priority: 60, 5172 toolbar: 'main-playlist', 5173 filterable: 'uploaded', 5174 multiple: 'add', 5175 editable: false, 5176 5177 library: wp.media.query( _.defaults({ 5178 type: 'audio' 5179 }, options.library ) ) 5180 }), 5181 5182 // Playlist states. 5183 new wp.media.controller.CollectionEdit({ 5184 type: 'audio', 5185 collectionType: 'playlist', 5186 title: l10n.editPlaylistTitle, 5187 SettingsView: wp.media.view.Settings.Playlist, 5188 library: options.selection, 5189 editing: options.editing, 5190 menu: 'playlist', 5191 dragInfoText: l10n.playlistDragInfo, 5192 dragInfo: false 5193 }), 5194 5195 new wp.media.controller.CollectionAdd({ 5196 type: 'audio', 5197 collectionType: 'playlist', 5198 title: l10n.addToPlaylistTitle 5199 }), 5200 5201 new Library({ 5202 id: 'video-playlist', 5203 title: l10n.createVideoPlaylistTitle, 5204 priority: 60, 5205 toolbar: 'main-video-playlist', 5206 filterable: 'uploaded', 5207 multiple: 'add', 5208 editable: false, 5209 5210 library: wp.media.query( _.defaults({ 5211 type: 'video' 5212 }, options.library ) ) 5213 }), 5214 5215 new wp.media.controller.CollectionEdit({ 5216 type: 'video', 5217 collectionType: 'playlist', 5218 title: l10n.editVideoPlaylistTitle, 5219 SettingsView: wp.media.view.Settings.Playlist, 5220 library: options.selection, 5221 editing: options.editing, 5222 menu: 'video-playlist', 5223 dragInfoText: l10n.videoPlaylistDragInfo, 5224 dragInfo: false 5225 }), 5226 5227 new wp.media.controller.CollectionAdd({ 5228 type: 'video', 5229 collectionType: 'playlist', 5230 title: l10n.addToVideoPlaylistTitle 5231 }) 5232 ]); 5233 5234 if ( wp.media.view.settings.post.featuredImageId ) { 5235 this.states.add( new wp.media.controller.FeaturedImage() ); 5236 } 5237 }, 5238 5239 bindHandlers: function() { 5240 var handlers, checkCounts; 5241 5242 Select.prototype.bindHandlers.apply( this, arguments ); 5243 5244 this.on( 'activate', this.activate, this ); 5245 5246 // Only bother checking media type counts if one of the counts is zero 5247 checkCounts = _.find( this.counts, function( type ) { 5248 return type.count === 0; 5249 } ); 5250 5251 if ( typeof checkCounts !== 'undefined' ) { 5252 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 5253 } 5254 5255 this.on( 'menu:create:gallery', this.createMenu, this ); 5256 this.on( 'menu:create:playlist', this.createMenu, this ); 5257 this.on( 'menu:create:video-playlist', this.createMenu, this ); 5258 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 5259 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 5260 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 5261 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 5262 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 5263 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 5264 5265 handlers = { 5266 menu: { 5267 'default': 'mainMenu', 5268 'gallery': 'galleryMenu', 5269 'playlist': 'playlistMenu', 5270 'video-playlist': 'videoPlaylistMenu' 5271 }, 5272 5273 content: { 5274 'embed': 'embedContent', 5275 'edit-image': 'editImageContent', 5276 'edit-selection': 'editSelectionContent' 5277 }, 5278 5279 toolbar: { 5280 'main-insert': 'mainInsertToolbar', 5281 'main-gallery': 'mainGalleryToolbar', 5282 'gallery-edit': 'galleryEditToolbar', 5283 'gallery-add': 'galleryAddToolbar', 5284 'main-playlist': 'mainPlaylistToolbar', 5285 'playlist-edit': 'playlistEditToolbar', 5286 'playlist-add': 'playlistAddToolbar', 5287 'main-video-playlist': 'mainVideoPlaylistToolbar', 5288 'video-playlist-edit': 'videoPlaylistEditToolbar', 5289 'video-playlist-add': 'videoPlaylistAddToolbar' 5290 } 5291 }; 5292 5293 _.each( handlers, function( regionHandlers, region ) { 5294 _.each( regionHandlers, function( callback, handler ) { 5295 this.on( region + ':render:' + handler, this[ callback ], this ); 5296 }, this ); 5297 }, this ); 5298 }, 5299 5300 activate: function() { 5301 // Hide menu items for states tied to particular media types if there are no items 5302 _.each( this.counts, function( type ) { 5303 if ( type.count < 1 ) { 5304 this.menuItemVisibility( type.state, 'hide' ); 5305 } 5306 }, this ); 5307 }, 5308 5309 mediaTypeCounts: function( model, attr ) { 5310 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 5311 this.counts[ attr ].count++; 5312 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 5313 } 5314 }, 5315 5316 // Menus 5317 /** 5318 * @param {wp.Backbone.View} view 5319 */ 5320 mainMenu: function( view ) { 5321 view.set({ 5322 'library-separator': new wp.media.View({ 5323 className: 'separator', 5324 priority: 100 5325 }) 5326 }); 5327 }, 5328 5329 menuItemVisibility: function( state, visibility ) { 5330 var menu = this.menu.get(); 5331 if ( visibility === 'hide' ) { 5332 menu.hide( state ); 5333 } else if ( visibility === 'show' ) { 5334 menu.show( state ); 5335 } 5336 }, 5337 /** 5338 * @param {wp.Backbone.View} view 5339 */ 5340 galleryMenu: function( view ) { 5341 var lastState = this.lastState(), 5342 previous = lastState && lastState.id, 5343 frame = this; 5344 5345 view.set({ 5346 cancel: { 5347 text: l10n.cancelGalleryTitle, 5348 priority: 20, 5349 click: function() { 5350 if ( previous ) { 5351 frame.setState( previous ); 5352 } else { 5353 frame.close(); 5354 } 5355 5356 // Keep focus inside media modal 5357 // after canceling a gallery 5358 this.controller.modal.focusManager.focus(); 5359 } 5360 }, 5361 separateCancel: new wp.media.View({ 5362 className: 'separator', 5363 priority: 40 5364 }) 5365 }); 5366 }, 5367 5368 playlistMenu: function( view ) { 5369 var lastState = this.lastState(), 5370 previous = lastState && lastState.id, 5371 frame = this; 5372 5373 view.set({ 5374 cancel: { 5375 text: l10n.cancelPlaylistTitle, 5376 priority: 20, 5377 click: function() { 5378 if ( previous ) { 5379 frame.setState( previous ); 5380 } else { 5381 frame.close(); 5382 } 5383 } 5384 }, 5385 separateCancel: new wp.media.View({ 5386 className: 'separator', 5387 priority: 40 5388 }) 5389 }); 5390 }, 5391 5392 videoPlaylistMenu: function( view ) { 5393 var lastState = this.lastState(), 5394 previous = lastState && lastState.id, 5395 frame = this; 5396 5397 view.set({ 5398 cancel: { 5399 text: l10n.cancelVideoPlaylistTitle, 5400 priority: 20, 5401 click: function() { 5402 if ( previous ) { 5403 frame.setState( previous ); 5404 } else { 5405 frame.close(); 5406 } 5407 } 5408 }, 5409 separateCancel: new wp.media.View({ 5410 className: 'separator', 5411 priority: 40 5412 }) 5413 }); 5414 }, 5415 5416 // Content 5417 embedContent: function() { 5418 var view = new wp.media.view.Embed({ 5419 controller: this, 5420 model: this.state() 5421 }).render(); 5422 5423 this.content.set( view ); 5424 5425 if ( ! wp.media.isTouchDevice ) { 5426 view.url.focus(); 5427 } 5428 }, 5429 5430 editSelectionContent: function() { 5431 var state = this.state(), 5432 selection = state.get('selection'), 5433 view; 5434 5435 view = new wp.media.view.AttachmentsBrowser({ 5436 controller: this, 5437 collection: selection, 5438 selection: selection, 5439 model: state, 5440 sortable: true, 5441 search: false, 5442 date: false, 5443 dragInfo: true, 5444 5445 AttachmentView: wp.media.view.Attachments.EditSelection 5446 }).render(); 5447 5448 view.toolbar.set( 'backToLibrary', { 5449 text: l10n.returnToLibrary, 5450 priority: -100, 5451 5452 click: function() { 5453 this.controller.content.mode('browse'); 5454 } 5455 }); 5456 5457 // Browse our library of attachments. 5458 this.content.set( view ); 5459 5460 // Trigger the controller to set focus 5461 this.trigger( 'edit:selection', this ); 5462 }, 5463 5464 editImageContent: function() { 5465 var image = this.state().get('image'), 5466 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 5467 5468 this.content.set( view ); 5469 5470 // after creating the wrapper view, load the actual editor via an ajax call 5471 view.loadEditor(); 5472 5473 }, 5474 5475 // Toolbars 5476 5477 /** 5478 * @param {wp.Backbone.View} view 5479 */ 5480 selectionStatusToolbar: function( view ) { 5481 var editable = this.state().get('editable'); 5482 5483 view.set( 'selection', new wp.media.view.Selection({ 5484 controller: this, 5485 collection: this.state().get('selection'), 5486 priority: -40, 5487 5488 // If the selection is editable, pass the callback to 5489 // switch the content mode. 5490 editable: editable && function() { 5491 this.controller.content.mode('edit-selection'); 5492 } 5493 }).render() ); 5494 }, 5495 5496 /** 5497 * @param {wp.Backbone.View} view 5498 */ 5499 mainInsertToolbar: function( view ) { 5500 var controller = this; 5501 5502 this.selectionStatusToolbar( view ); 5503 5504 view.set( 'insert', { 5505 style: 'primary', 5506 priority: 80, 5507 text: l10n.insertIntoPost, 5508 requires: { selection: true }, 5509 5510 /** 5511 * @fires wp.media.controller.State#insert 5512 */ 5513 click: function() { 5514 var state = controller.state(), 5515 selection = state.get('selection'); 5516 5517 controller.close(); 5518 state.trigger( 'insert', selection ).reset(); 5519 } 5520 }); 5521 }, 5522 5523 /** 5524 * @param {wp.Backbone.View} view 5525 */ 5526 mainGalleryToolbar: function( view ) { 5527 var controller = this; 5528 5529 this.selectionStatusToolbar( view ); 5530 5531 view.set( 'gallery', { 5532 style: 'primary', 5533 text: l10n.createNewGallery, 5534 priority: 60, 5535 requires: { selection: true }, 5536 5537 click: function() { 5538 var selection = controller.state().get('selection'), 5539 edit = controller.state('gallery-edit'), 5540 models = selection.where({ type: 'image' }); 5541 5542 edit.set( 'library', new wp.media.model.Selection( models, { 5543 props: selection.props.toJSON(), 5544 multiple: true 5545 }) ); 5546 5547 this.controller.setState('gallery-edit'); 5548 5549 // Keep focus inside media modal 5550 // after jumping to gallery view 5551 this.controller.modal.focusManager.focus(); 5552 } 5553 }); 5554 }, 5555 5556 mainPlaylistToolbar: function( view ) { 5557 var controller = this; 5558 5559 this.selectionStatusToolbar( view ); 5560 5561 view.set( 'playlist', { 5562 style: 'primary', 5563 text: l10n.createNewPlaylist, 5564 priority: 100, 5565 requires: { selection: true }, 5566 5567 click: function() { 5568 var selection = controller.state().get('selection'), 5569 edit = controller.state('playlist-edit'), 5570 models = selection.where({ type: 'audio' }); 5571 5572 edit.set( 'library', new wp.media.model.Selection( models, { 5573 props: selection.props.toJSON(), 5574 multiple: true 5575 }) ); 5576 5577 this.controller.setState('playlist-edit'); 5578 5579 // Keep focus inside media modal 5580 // after jumping to playlist view 5581 this.controller.modal.focusManager.focus(); 5582 } 5583 }); 5584 }, 5585 5586 mainVideoPlaylistToolbar: function( view ) { 5587 var controller = this; 5588 5589 this.selectionStatusToolbar( view ); 5590 5591 view.set( 'video-playlist', { 5592 style: 'primary', 5593 text: l10n.createNewVideoPlaylist, 5594 priority: 100, 5595 requires: { selection: true }, 5596 5597 click: function() { 5598 var selection = controller.state().get('selection'), 5599 edit = controller.state('video-playlist-edit'), 5600 models = selection.where({ type: 'video' }); 5601 5602 edit.set( 'library', new wp.media.model.Selection( models, { 5603 props: selection.props.toJSON(), 5604 multiple: true 5605 }) ); 5606 5607 this.controller.setState('video-playlist-edit'); 5608 5609 // Keep focus inside media modal 5610 // after jumping to video playlist view 5611 this.controller.modal.focusManager.focus(); 5612 } 5613 }); 5614 }, 5615 5616 featuredImageToolbar: function( toolbar ) { 5617 this.createSelectToolbar( toolbar, { 5618 text: l10n.setFeaturedImage, 5619 state: this.options.state 5620 }); 5621 }, 5622 5623 mainEmbedToolbar: function( toolbar ) { 5624 toolbar.view = new wp.media.view.Toolbar.Embed({ 5625 controller: this 5626 }); 5627 }, 5628 5629 galleryEditToolbar: function() { 5630 var editing = this.state().get('editing'); 5631 this.toolbar.set( new wp.media.view.Toolbar({ 5632 controller: this, 5633 items: { 5634 insert: { 5635 style: 'primary', 5636 text: editing ? l10n.updateGallery : l10n.insertGallery, 5637 priority: 80, 5638 requires: { library: true }, 5639 5640 /** 5641 * @fires wp.media.controller.State#update 5642 */ 5643 click: function() { 5644 var controller = this.controller, 5645 state = controller.state(); 5646 5647 controller.close(); 5648 state.trigger( 'update', state.get('library') ); 5649 5650 // Restore and reset the default state. 5651 controller.setState( controller.options.state ); 5652 controller.reset(); 5653 } 5654 } 5655 } 5656 }) ); 5657 }, 5658 5659 galleryAddToolbar: function() { 5660 this.toolbar.set( new wp.media.view.Toolbar({ 5661 controller: this, 5662 items: { 5663 insert: { 5664 style: 'primary', 5665 text: l10n.addToGallery, 5666 priority: 80, 5667 requires: { selection: true }, 5668 5669 /** 5670 * @fires wp.media.controller.State#reset 5671 */ 5672 click: function() { 5673 var controller = this.controller, 5674 state = controller.state(), 5675 edit = controller.state('gallery-edit'); 5676 5677 edit.get('library').add( state.get('selection').models ); 5678 state.trigger('reset'); 5679 controller.setState('gallery-edit'); 5680 } 5681 } 5682 } 5683 }) ); 5684 }, 5685 5686 playlistEditToolbar: function() { 5687 var editing = this.state().get('editing'); 5688 this.toolbar.set( new wp.media.view.Toolbar({ 5689 controller: this, 5690 items: { 5691 insert: { 5692 style: 'primary', 5693 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 5694 priority: 80, 5695 requires: { library: true }, 5696 5697 /** 5698 * @fires wp.media.controller.State#update 5699 */ 5700 click: function() { 5701 var controller = this.controller, 5702 state = controller.state(); 5703 5704 controller.close(); 5705 state.trigger( 'update', state.get('library') ); 5706 5707 // Restore and reset the default state. 5708 controller.setState( controller.options.state ); 5709 controller.reset(); 5710 } 5711 } 5712 } 5713 }) ); 5714 }, 5715 5716 playlistAddToolbar: function() { 5717 this.toolbar.set( new wp.media.view.Toolbar({ 5718 controller: this, 5719 items: { 5720 insert: { 5721 style: 'primary', 5722 text: l10n.addToPlaylist, 5723 priority: 80, 5724 requires: { selection: true }, 5725 5726 /** 5727 * @fires wp.media.controller.State#reset 5728 */ 5729 click: function() { 5730 var controller = this.controller, 5731 state = controller.state(), 5732 edit = controller.state('playlist-edit'); 5733 5734 edit.get('library').add( state.get('selection').models ); 5735 state.trigger('reset'); 5736 controller.setState('playlist-edit'); 5737 } 5738 } 5739 } 5740 }) ); 5741 }, 5742 5743 videoPlaylistEditToolbar: function() { 5744 var editing = this.state().get('editing'); 5745 this.toolbar.set( new wp.media.view.Toolbar({ 5746 controller: this, 5747 items: { 5748 insert: { 5749 style: 'primary', 5750 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 5751 priority: 140, 5752 requires: { library: true }, 5753 5754 click: function() { 5755 var controller = this.controller, 5756 state = controller.state(), 5757 library = state.get('library'); 5758 5759 library.type = 'video'; 5760 5761 controller.close(); 5762 state.trigger( 'update', library ); 5763 5764 // Restore and reset the default state. 5765 controller.setState( controller.options.state ); 5766 controller.reset(); 5767 } 5768 } 5769 } 5770 }) ); 5771 }, 5772 5773 videoPlaylistAddToolbar: function() { 5774 this.toolbar.set( new wp.media.view.Toolbar({ 5775 controller: this, 5776 items: { 5777 insert: { 5778 style: 'primary', 5779 text: l10n.addToVideoPlaylist, 5780 priority: 140, 5781 requires: { selection: true }, 5782 5783 click: function() { 5784 var controller = this.controller, 5785 state = controller.state(), 5786 edit = controller.state('video-playlist-edit'); 5787 5788 edit.get('library').add( state.get('selection').models ); 5789 state.trigger('reset'); 5790 controller.setState('video-playlist-edit'); 5791 } 5792 } 5793 } 5794 }) ); 5795 } 5796 }); 5797 5798 module.exports = Post; 5799 5800 },{}],44:[function(require,module,exports){ 5801 /*globals wp, _ */ 5802 5803 /** 5804 * wp.media.view.MediaFrame.Select 5805 * 5806 * A frame for selecting an item or items from the media library. 5807 * 5808 * @class 5809 * @augments wp.media.view.MediaFrame 5810 * @augments wp.media.view.Frame 5811 * @augments wp.media.View 5812 * @augments wp.Backbone.View 5813 * @augments Backbone.View 5814 * @mixes wp.media.controller.StateMachine 5815 */ 5816 5817 var MediaFrame = wp.media.view.MediaFrame, 5818 l10n = wp.media.view.l10n, 5819 Select; 5820 5821 Select = MediaFrame.extend({ 5822 initialize: function() { 5823 // Call 'initialize' directly on the parent class. 5824 MediaFrame.prototype.initialize.apply( this, arguments ); 5825 5826 _.defaults( this.options, { 5827 selection: [], 5828 library: {}, 5829 multiple: false, 5830 state: 'library' 5831 }); 5832 5833 this.createSelection(); 5834 this.createStates(); 5835 this.bindHandlers(); 5836 }, 5837 5838 /** 5839 * Attach a selection collection to the frame. 5840 * 5841 * A selection is a collection of attachments used for a specific purpose 5842 * by a media frame. e.g. Selecting an attachment (or many) to insert into 5843 * post content. 5844 * 5845 * @see media.model.Selection 5846 */ 5847 createSelection: function() { 5848 var selection = this.options.selection; 5849 5850 if ( ! (selection instanceof wp.media.model.Selection) ) { 5851 this.options.selection = new wp.media.model.Selection( selection, { 5852 multiple: this.options.multiple 5853 }); 5854 } 5855 5856 this._selection = { 5857 attachments: new wp.media.model.Attachments(), 5858 difference: [] 5859 }; 5860 }, 5861 5862 /** 5863 * Create the default states on the frame. 5864 */ 5865 createStates: function() { 5866 var options = this.options; 5867 5868 if ( this.options.states ) { 5869 return; 5870 } 5871 5872 // Add the default states. 5873 this.states.add([ 5874 // Main states. 5875 new wp.media.controller.Library({ 5876 library: wp.media.query( options.library ), 5877 multiple: options.multiple, 5878 title: options.title, 5879 priority: 20 5880 }) 5881 ]); 5882 }, 5883 5884 /** 5885 * Bind region mode event callbacks. 5886 * 5887 * @see media.controller.Region.render 5888 */ 5889 bindHandlers: function() { 5890 this.on( 'router:create:browse', this.createRouter, this ); 5891 this.on( 'router:render:browse', this.browseRouter, this ); 5892 this.on( 'content:create:browse', this.browseContent, this ); 5893 this.on( 'content:render:upload', this.uploadContent, this ); 5894 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 5895 }, 5896 5897 /** 5898 * Render callback for the router region in the `browse` mode. 5899 * 5900 * @param {wp.media.view.Router} routerView 5901 */ 5902 browseRouter: function( routerView ) { 5903 routerView.set({ 5904 upload: { 5905 text: l10n.uploadFilesTitle, 5906 priority: 20 5907 }, 5908 browse: { 5909 text: l10n.mediaLibraryTitle, 5910 priority: 40 5911 } 5912 }); 5913 }, 5914 5915 /** 5916 * Render callback for the content region in the `browse` mode. 5917 * 5918 * @param {wp.media.controller.Region} contentRegion 5919 */ 5920 browseContent: function( contentRegion ) { 5921 var state = this.state(); 5922 5923 this.$el.removeClass('hide-toolbar'); 5924 5925 // Browse our library of attachments. 5926 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 5927 controller: this, 5928 collection: state.get('library'), 5929 selection: state.get('selection'), 5930 model: state, 5931 sortable: state.get('sortable'), 5932 search: state.get('searchable'), 5933 filters: state.get('filterable'), 5934 date: state.get('date'), 5935 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 5936 dragInfo: state.get('dragInfo'), 5937 5938 idealColumnWidth: state.get('idealColumnWidth'), 5939 suggestedWidth: state.get('suggestedWidth'), 5940 suggestedHeight: state.get('suggestedHeight'), 5941 5942 AttachmentView: state.get('AttachmentView') 5943 }); 5944 }, 5945 5946 /** 5947 * Render callback for the content region in the `upload` mode. 5948 */ 5949 uploadContent: function() { 5950 this.$el.removeClass( 'hide-toolbar' ); 5951 this.content.set( new wp.media.view.UploaderInline({ 5952 controller: this 5953 }) ); 5954 }, 5955 5956 /** 5957 * Toolbars 5958 * 5959 * @param {Object} toolbar 5960 * @param {Object} [options={}] 5961 * @this wp.media.controller.Region 5962 */ 5963 createSelectToolbar: function( toolbar, options ) { 5964 options = options || this.options.button || {}; 5965 options.controller = this; 5966 5967 toolbar.view = new wp.media.view.Toolbar.Select( options ); 5968 } 5969 }); 5970 5971 module.exports = Select; 5972 5973 },{}],45:[function(require,module,exports){ 5974 /** 5975 * wp.media.view.Iframe 5976 * 5977 * @class 5978 * @augments wp.media.View 5979 * @augments wp.Backbone.View 5980 * @augments Backbone.View 5981 */ 5982 var Iframe = wp.media.View.extend({ 5983 className: 'media-iframe', 5984 /** 5985 * @returns {wp.media.view.Iframe} Returns itself to allow chaining 5986 */ 5987 render: function() { 5988 this.views.detach(); 5989 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 5990 this.views.render(); 5991 return this; 5992 } 5993 }); 5994 5995 module.exports = Iframe; 5996 5997 },{}],46:[function(require,module,exports){ 8426 5998 /*globals wp, _, jQuery */ 8427 5999 … … 8593 6165 module.exports = ImageDetails; 8594 6166 8595 8596 /***/ }), 8597 /* 96 */ 8598 /***/ (function(module, exports) { 8599 6167 },{}],47:[function(require,module,exports){ 6168 /** 6169 * wp.media.view.Label 6170 * 6171 * @class 6172 * @augments wp.media.View 6173 * @augments wp.Backbone.View 6174 * @augments Backbone.View 6175 */ 6176 var Label = wp.media.View.extend({ 6177 tagName: 'label', 6178 className: 'screen-reader-text', 6179 6180 initialize: function() { 6181 this.value = this.options.value; 6182 }, 6183 6184 render: function() { 6185 this.$el.html( this.value ); 6186 6187 return this; 6188 } 6189 }); 6190 6191 module.exports = Label; 6192 6193 },{}],48:[function(require,module,exports){ 8600 6194 /*globals wp, _, jQuery */ 8601 6195 8602 6196 /** 8603 * wp.media.view.Cropper 8604 * 8605 * Uses the imgAreaSelect plugin to allow a user to crop an image. 8606 * 8607 * Takes imgAreaSelect options from 8608 * wp.customize.HeaderControl.calculateImageSelectOptions via 8609 * wp.customize.HeaderControl.openMM. 6197 * wp.media.view.MediaFrame 6198 * 6199 * The frame used to create the media modal. 6200 * 6201 * @class 6202 * @augments wp.media.view.Frame 6203 * @augments wp.media.View 6204 * @augments wp.Backbone.View 6205 * @augments Backbone.View 6206 * @mixes wp.media.controller.StateMachine 6207 */ 6208 var Frame = wp.media.view.Frame, 6209 $ = jQuery, 6210 MediaFrame; 6211 6212 MediaFrame = Frame.extend({ 6213 className: 'media-frame', 6214 template: wp.template('media-frame'), 6215 regions: ['menu','title','content','toolbar','router'], 6216 6217 events: { 6218 'click div.media-frame-title h1': 'toggleMenu' 6219 }, 6220 6221 /** 6222 * @global wp.Uploader 6223 */ 6224 initialize: function() { 6225 Frame.prototype.initialize.apply( this, arguments ); 6226 6227 _.defaults( this.options, { 6228 title: '', 6229 modal: true, 6230 uploader: true 6231 }); 6232 6233 // Ensure core UI is enabled. 6234 this.$el.addClass('wp-core-ui'); 6235 6236 // Initialize modal container view. 6237 if ( this.options.modal ) { 6238 this.modal = new wp.media.view.Modal({ 6239 controller: this, 6240 title: this.options.title 6241 }); 6242 6243 this.modal.content( this ); 6244 } 6245 6246 // Force the uploader off if the upload limit has been exceeded or 6247 // if the browser isn't supported. 6248 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 6249 this.options.uploader = false; 6250 } 6251 6252 // Initialize window-wide uploader. 6253 if ( this.options.uploader ) { 6254 this.uploader = new wp.media.view.UploaderWindow({ 6255 controller: this, 6256 uploader: { 6257 dropzone: this.modal ? this.modal.$el : this.$el, 6258 container: this.$el 6259 } 6260 }); 6261 this.views.set( '.media-frame-uploader', this.uploader ); 6262 } 6263 6264 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 6265 6266 // Bind default title creation. 6267 this.on( 'title:create:default', this.createTitle, this ); 6268 this.title.mode('default'); 6269 6270 this.on( 'title:render', function( view ) { 6271 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' ); 6272 }); 6273 6274 // Bind default menu. 6275 this.on( 'menu:create:default', this.createMenu, this ); 6276 }, 6277 /** 6278 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6279 */ 6280 render: function() { 6281 // Activate the default state if no active state exists. 6282 if ( ! this.state() && this.options.state ) { 6283 this.setState( this.options.state ); 6284 } 6285 /** 6286 * call 'render' directly on the parent class 6287 */ 6288 return Frame.prototype.render.apply( this, arguments ); 6289 }, 6290 /** 6291 * @param {Object} title 6292 * @this wp.media.controller.Region 6293 */ 6294 createTitle: function( title ) { 6295 title.view = new wp.media.View({ 6296 controller: this, 6297 tagName: 'h1' 6298 }); 6299 }, 6300 /** 6301 * @param {Object} menu 6302 * @this wp.media.controller.Region 6303 */ 6304 createMenu: function( menu ) { 6305 menu.view = new wp.media.view.Menu({ 6306 controller: this 6307 }); 6308 }, 6309 6310 toggleMenu: function() { 6311 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 6312 }, 6313 6314 /** 6315 * @param {Object} toolbar 6316 * @this wp.media.controller.Region 6317 */ 6318 createToolbar: function( toolbar ) { 6319 toolbar.view = new wp.media.view.Toolbar({ 6320 controller: this 6321 }); 6322 }, 6323 /** 6324 * @param {Object} router 6325 * @this wp.media.controller.Region 6326 */ 6327 createRouter: function( router ) { 6328 router.view = new wp.media.view.Router({ 6329 controller: this 6330 }); 6331 }, 6332 /** 6333 * @param {Object} options 6334 */ 6335 createIframeStates: function( options ) { 6336 var settings = wp.media.view.settings, 6337 tabs = settings.tabs, 6338 tabUrl = settings.tabUrl, 6339 $postId; 6340 6341 if ( ! tabs || ! tabUrl ) { 6342 return; 6343 } 6344 6345 // Add the post ID to the tab URL if it exists. 6346 $postId = $('#post_ID'); 6347 if ( $postId.length ) { 6348 tabUrl += '&post_id=' + $postId.val(); 6349 } 6350 6351 // Generate the tab states. 6352 _.each( tabs, function( title, id ) { 6353 this.state( 'iframe:' + id ).set( _.defaults({ 6354 tab: id, 6355 src: tabUrl + '&tab=' + id, 6356 title: title, 6357 content: 'iframe', 6358 menu: 'default' 6359 }, options ) ); 6360 }, this ); 6361 6362 this.on( 'content:create:iframe', this.iframeContent, this ); 6363 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 6364 this.on( 'menu:render:default', this.iframeMenu, this ); 6365 this.on( 'open', this.hijackThickbox, this ); 6366 this.on( 'close', this.restoreThickbox, this ); 6367 }, 6368 6369 /** 6370 * @param {Object} content 6371 * @this wp.media.controller.Region 6372 */ 6373 iframeContent: function( content ) { 6374 this.$el.addClass('hide-toolbar'); 6375 content.view = new wp.media.view.Iframe({ 6376 controller: this 6377 }); 6378 }, 6379 6380 iframeContentCleanup: function() { 6381 this.$el.removeClass('hide-toolbar'); 6382 }, 6383 6384 iframeMenu: function( view ) { 6385 var views = {}; 6386 6387 if ( ! view ) { 6388 return; 6389 } 6390 6391 _.each( wp.media.view.settings.tabs, function( title, id ) { 6392 views[ 'iframe:' + id ] = { 6393 text: this.state( 'iframe:' + id ).get('title'), 6394 priority: 200 6395 }; 6396 }, this ); 6397 6398 view.set( views ); 6399 }, 6400 6401 hijackThickbox: function() { 6402 var frame = this; 6403 6404 if ( ! window.tb_remove || this._tb_remove ) { 6405 return; 6406 } 6407 6408 this._tb_remove = window.tb_remove; 6409 window.tb_remove = function() { 6410 frame.close(); 6411 frame.reset(); 6412 frame.setState( frame.options.state ); 6413 frame._tb_remove.call( window ); 6414 }; 6415 }, 6416 6417 restoreThickbox: function() { 6418 if ( ! this._tb_remove ) { 6419 return; 6420 } 6421 6422 window.tb_remove = this._tb_remove; 6423 delete this._tb_remove; 6424 } 6425 }); 6426 6427 // Map some of the modal's methods to the frame. 6428 _.each(['open','close','attach','detach','escape'], function( method ) { 6429 /** 6430 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6431 */ 6432 MediaFrame.prototype[ method ] = function() { 6433 if ( this.modal ) { 6434 this.modal[ method ].apply( this.modal, arguments ); 6435 } 6436 return this; 6437 }; 6438 }); 6439 6440 module.exports = MediaFrame; 6441 6442 },{}],49:[function(require,module,exports){ 6443 /*globals jQuery */ 6444 6445 /** 6446 * wp.media.view.MenuItem 6447 * 6448 * @class 6449 * @augments wp.media.View 6450 * @augments wp.Backbone.View 6451 * @augments Backbone.View 6452 */ 6453 var $ = jQuery, 6454 MenuItem; 6455 6456 MenuItem = wp.media.View.extend({ 6457 tagName: 'a', 6458 className: 'media-menu-item', 6459 6460 attributes: { 6461 href: '#' 6462 }, 6463 6464 events: { 6465 'click': '_click' 6466 }, 6467 /** 6468 * @param {Object} event 6469 */ 6470 _click: function( event ) { 6471 var clickOverride = this.options.click; 6472 6473 if ( event ) { 6474 event.preventDefault(); 6475 } 6476 6477 if ( clickOverride ) { 6478 clickOverride.call( this ); 6479 } else { 6480 this.click(); 6481 } 6482 6483 // When selecting a tab along the left side, 6484 // focus should be transferred into the main panel 6485 if ( ! wp.media.isTouchDevice ) { 6486 $('.media-frame-content input').first().focus(); 6487 } 6488 }, 6489 6490 click: function() { 6491 var state = this.options.state; 6492 6493 if ( state ) { 6494 this.controller.setState( state ); 6495 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 6496 } 6497 }, 6498 /** 6499 * @returns {wp.media.view.MenuItem} returns itself to allow chaining 6500 */ 6501 render: function() { 6502 var options = this.options; 6503 6504 if ( options.text ) { 6505 this.$el.text( options.text ); 6506 } else if ( options.html ) { 6507 this.$el.html( options.html ); 6508 } 6509 6510 return this; 6511 } 6512 }); 6513 6514 module.exports = MenuItem; 6515 6516 },{}],50:[function(require,module,exports){ 6517 /** 6518 * wp.media.view.Menu 6519 * 6520 * @class 6521 * @augments wp.media.view.PriorityList 6522 * @augments wp.media.View 6523 * @augments wp.Backbone.View 6524 * @augments Backbone.View 6525 */ 6526 var MenuItem = wp.media.view.MenuItem, 6527 PriorityList = wp.media.view.PriorityList, 6528 Menu; 6529 6530 Menu = PriorityList.extend({ 6531 tagName: 'div', 6532 className: 'media-menu', 6533 property: 'state', 6534 ItemView: MenuItem, 6535 region: 'menu', 6536 6537 /* TODO: alternatively hide on any click anywhere 6538 events: { 6539 'click': 'click' 6540 }, 6541 6542 click: function() { 6543 this.$el.removeClass( 'visible' ); 6544 }, 6545 */ 6546 6547 /** 6548 * @param {Object} options 6549 * @param {string} id 6550 * @returns {wp.media.View} 6551 */ 6552 toView: function( options, id ) { 6553 options = options || {}; 6554 options[ this.property ] = options[ this.property ] || id; 6555 return new this.ItemView( options ).render(); 6556 }, 6557 6558 ready: function() { 6559 /** 6560 * call 'ready' directly on the parent class 6561 */ 6562 PriorityList.prototype.ready.apply( this, arguments ); 6563 this.visibility(); 6564 }, 6565 6566 set: function() { 6567 /** 6568 * call 'set' directly on the parent class 6569 */ 6570 PriorityList.prototype.set.apply( this, arguments ); 6571 this.visibility(); 6572 }, 6573 6574 unset: function() { 6575 /** 6576 * call 'unset' directly on the parent class 6577 */ 6578 PriorityList.prototype.unset.apply( this, arguments ); 6579 this.visibility(); 6580 }, 6581 6582 visibility: function() { 6583 var region = this.region, 6584 view = this.controller[ region ].get(), 6585 views = this.views.get(), 6586 hide = ! views || views.length < 2; 6587 6588 if ( this === view ) { 6589 this.controller.$el.toggleClass( 'hide-' + region, hide ); 6590 } 6591 }, 6592 /** 6593 * @param {string} id 6594 */ 6595 select: function( id ) { 6596 var view = this.get( id ); 6597 6598 if ( ! view ) { 6599 return; 6600 } 6601 6602 this.deselect(); 6603 view.$el.addClass('active'); 6604 }, 6605 6606 deselect: function() { 6607 this.$el.children().removeClass('active'); 6608 }, 6609 6610 hide: function( id ) { 6611 var view = this.get( id ); 6612 6613 if ( ! view ) { 6614 return; 6615 } 6616 6617 view.$el.addClass('hidden'); 6618 }, 6619 6620 show: function( id ) { 6621 var view = this.get( id ); 6622 6623 if ( ! view ) { 6624 return; 6625 } 6626 6627 view.$el.removeClass('hidden'); 6628 } 6629 }); 6630 6631 module.exports = Menu; 6632 6633 },{}],51:[function(require,module,exports){ 6634 /*globals wp, _, jQuery */ 6635 6636 /** 6637 * wp.media.view.Modal 6638 * 6639 * A modal view, which the media modal uses as its default container. 6640 * 6641 * @class 6642 * @augments wp.media.View 6643 * @augments wp.Backbone.View 6644 * @augments Backbone.View 6645 */ 6646 var $ = jQuery, 6647 Modal; 6648 6649 Modal = wp.media.View.extend({ 6650 tagName: 'div', 6651 template: wp.template('media-modal'), 6652 6653 attributes: { 6654 tabindex: 0 6655 }, 6656 6657 events: { 6658 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 6659 'keydown': 'keydown' 6660 }, 6661 6662 initialize: function() { 6663 _.defaults( this.options, { 6664 container: document.body, 6665 title: '', 6666 propagate: true, 6667 freeze: true 6668 }); 6669 6670 this.focusManager = new wp.media.view.FocusManager({ 6671 el: this.el 6672 }); 6673 }, 6674 /** 6675 * @returns {Object} 6676 */ 6677 prepare: function() { 6678 return { 6679 title: this.options.title 6680 }; 6681 }, 6682 6683 /** 6684 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6685 */ 6686 attach: function() { 6687 if ( this.views.attached ) { 6688 return this; 6689 } 6690 6691 if ( ! this.views.rendered ) { 6692 this.render(); 6693 } 6694 6695 this.$el.appendTo( this.options.container ); 6696 6697 // Manually mark the view as attached and trigger ready. 6698 this.views.attached = true; 6699 this.views.ready(); 6700 6701 return this.propagate('attach'); 6702 }, 6703 6704 /** 6705 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6706 */ 6707 detach: function() { 6708 if ( this.$el.is(':visible') ) { 6709 this.close(); 6710 } 6711 6712 this.$el.detach(); 6713 this.views.attached = false; 6714 return this.propagate('detach'); 6715 }, 6716 6717 /** 6718 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6719 */ 6720 open: function() { 6721 var $el = this.$el, 6722 options = this.options, 6723 mceEditor; 6724 6725 if ( $el.is(':visible') ) { 6726 return this; 6727 } 6728 6729 if ( ! this.views.attached ) { 6730 this.attach(); 6731 } 6732 6733 // If the `freeze` option is set, record the window's scroll position. 6734 if ( options.freeze ) { 6735 this._freeze = { 6736 scrollTop: $( window ).scrollTop() 6737 }; 6738 } 6739 6740 // Disable page scrolling. 6741 $( 'body' ).addClass( 'modal-open' ); 6742 6743 $el.show(); 6744 6745 // Try to close the onscreen keyboard 6746 if ( 'ontouchend' in document ) { 6747 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 6748 mceEditor.iframeElement.focus(); 6749 mceEditor.iframeElement.blur(); 6750 6751 setTimeout( function() { 6752 mceEditor.iframeElement.blur(); 6753 }, 100 ); 6754 } 6755 } 6756 6757 this.$el.focus(); 6758 6759 return this.propagate('open'); 6760 }, 6761 6762 /** 6763 * @param {Object} options 6764 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6765 */ 6766 close: function( options ) { 6767 var freeze = this._freeze; 6768 6769 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 6770 return this; 6771 } 6772 6773 // Enable page scrolling. 6774 $( 'body' ).removeClass( 'modal-open' ); 6775 6776 // Hide modal and remove restricted media modal tab focus once it's closed 6777 this.$el.hide().undelegate( 'keydown' ); 6778 6779 // Put focus back in useful location once modal is closed 6780 $('#wpbody-content').focus(); 6781 6782 this.propagate('close'); 6783 6784 // If the `freeze` option is set, restore the container's scroll position. 6785 if ( freeze ) { 6786 $( window ).scrollTop( freeze.scrollTop ); 6787 } 6788 6789 if ( options && options.escape ) { 6790 this.propagate('escape'); 6791 } 6792 6793 return this; 6794 }, 6795 /** 6796 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6797 */ 6798 escape: function() { 6799 return this.close({ escape: true }); 6800 }, 6801 /** 6802 * @param {Object} event 6803 */ 6804 escapeHandler: function( event ) { 6805 event.preventDefault(); 6806 this.escape(); 6807 }, 6808 6809 /** 6810 * @param {Array|Object} content Views to register to '.media-modal-content' 6811 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6812 */ 6813 content: function( content ) { 6814 this.views.set( '.media-modal-content', content ); 6815 return this; 6816 }, 6817 6818 /** 6819 * Triggers a modal event and if the `propagate` option is set, 6820 * forwards events to the modal's controller. 6821 * 6822 * @param {string} id 6823 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6824 */ 6825 propagate: function( id ) { 6826 this.trigger( id ); 6827 6828 if ( this.options.propagate ) { 6829 this.controller.trigger( id ); 6830 } 6831 6832 return this; 6833 }, 6834 /** 6835 * @param {Object} event 6836 */ 6837 keydown: function( event ) { 6838 // Close the modal when escape is pressed. 6839 if ( 27 === event.which && this.$el.is(':visible') ) { 6840 this.escape(); 6841 event.stopImmediatePropagation(); 6842 } 6843 } 6844 }); 6845 6846 module.exports = Modal; 6847 6848 },{}],52:[function(require,module,exports){ 6849 /*globals _, Backbone */ 6850 6851 /** 6852 * wp.media.view.PriorityList 6853 * 6854 * @class 6855 * @augments wp.media.View 6856 * @augments wp.Backbone.View 6857 * @augments Backbone.View 6858 */ 6859 var PriorityList = wp.media.View.extend({ 6860 tagName: 'div', 6861 6862 initialize: function() { 6863 this._views = {}; 6864 6865 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 6866 delete this.options.views; 6867 6868 if ( ! this.options.silent ) { 6869 this.render(); 6870 } 6871 }, 6872 /** 6873 * @param {string} id 6874 * @param {wp.media.View|Object} view 6875 * @param {Object} options 6876 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining 6877 */ 6878 set: function( id, view, options ) { 6879 var priority, views, index; 6880 6881 options = options || {}; 6882 6883 // Accept an object with an `id` : `view` mapping. 6884 if ( _.isObject( id ) ) { 6885 _.each( id, function( view, id ) { 6886 this.set( id, view ); 6887 }, this ); 6888 return this; 6889 } 6890 6891 if ( ! (view instanceof Backbone.View) ) { 6892 view = this.toView( view, id, options ); 6893 } 6894 view.controller = view.controller || this.controller; 6895 6896 this.unset( id ); 6897 6898 priority = view.options.priority || 10; 6899 views = this.views.get() || []; 6900 6901 _.find( views, function( existing, i ) { 6902 if ( existing.options.priority > priority ) { 6903 index = i; 6904 return true; 6905 } 6906 }); 6907 6908 this._views[ id ] = view; 6909 this.views.add( view, { 6910 at: _.isNumber( index ) ? index : views.length || 0 6911 }); 6912 6913 return this; 6914 }, 6915 /** 6916 * @param {string} id 6917 * @returns {wp.media.View} 6918 */ 6919 get: function( id ) { 6920 return this._views[ id ]; 6921 }, 6922 /** 6923 * @param {string} id 6924 * @returns {wp.media.view.PriorityList} 6925 */ 6926 unset: function( id ) { 6927 var view = this.get( id ); 6928 6929 if ( view ) { 6930 view.remove(); 6931 } 6932 6933 delete this._views[ id ]; 6934 return this; 6935 }, 6936 /** 6937 * @param {Object} options 6938 * @returns {wp.media.View} 6939 */ 6940 toView: function( options ) { 6941 return new wp.media.View( options ); 6942 } 6943 }); 6944 6945 module.exports = PriorityList; 6946 6947 },{}],53:[function(require,module,exports){ 6948 /** 6949 * wp.media.view.RouterItem 6950 * 6951 * @class 6952 * @augments wp.media.view.MenuItem 6953 * @augments wp.media.View 6954 * @augments wp.Backbone.View 6955 * @augments Backbone.View 6956 */ 6957 var RouterItem = wp.media.view.MenuItem.extend({ 6958 /** 6959 * On click handler to activate the content region's corresponding mode. 6960 */ 6961 click: function() { 6962 var contentMode = this.options.contentMode; 6963 if ( contentMode ) { 6964 this.controller.content.mode( contentMode ); 6965 } 6966 } 6967 }); 6968 6969 module.exports = RouterItem; 6970 6971 },{}],54:[function(require,module,exports){ 6972 /*globals wp */ 6973 6974 /** 6975 * wp.media.view.Router 6976 * 6977 * @class 6978 * @augments wp.media.view.Menu 6979 * @augments wp.media.view.PriorityList 6980 * @augments wp.media.View 6981 * @augments wp.Backbone.View 6982 * @augments Backbone.View 6983 */ 6984 var Menu = wp.media.view.Menu, 6985 Router; 6986 6987 Router = Menu.extend({ 6988 tagName: 'div', 6989 className: 'media-router', 6990 property: 'contentMode', 6991 ItemView: wp.media.view.RouterItem, 6992 region: 'router', 6993 6994 initialize: function() { 6995 this.controller.on( 'content:render', this.update, this ); 6996 // Call 'initialize' directly on the parent class. 6997 Menu.prototype.initialize.apply( this, arguments ); 6998 }, 6999 7000 update: function() { 7001 var mode = this.controller.content.mode(); 7002 if ( mode ) { 7003 this.select( mode ); 7004 } 7005 } 7006 }); 7007 7008 module.exports = Router; 7009 7010 },{}],55:[function(require,module,exports){ 7011 /*globals wp */ 7012 7013 /** 7014 * wp.media.view.Search 7015 * 7016 * @class 7017 * @augments wp.media.View 7018 * @augments wp.Backbone.View 7019 * @augments Backbone.View 7020 */ 7021 var l10n = wp.media.view.l10n, 7022 Search; 7023 7024 Search = wp.media.View.extend({ 7025 tagName: 'input', 7026 className: 'search', 7027 id: 'media-search-input', 7028 7029 attributes: { 7030 type: 'search', 7031 placeholder: l10n.search 7032 }, 7033 7034 events: { 7035 'input': 'search', 7036 'keyup': 'search', 7037 'change': 'search', 7038 'search': 'search' 7039 }, 7040 7041 /** 7042 * @returns {wp.media.view.Search} Returns itself to allow chaining 7043 */ 7044 render: function() { 7045 this.el.value = this.model.escape('search'); 7046 return this; 7047 }, 7048 7049 search: function( event ) { 7050 if ( event.target.value ) { 7051 this.model.set( 'search', event.target.value ); 7052 } else { 7053 this.model.unset('search'); 7054 } 7055 } 7056 }); 7057 7058 module.exports = Search; 7059 7060 },{}],56:[function(require,module,exports){ 7061 /*globals wp, _, Backbone */ 7062 7063 /** 7064 * wp.media.view.Selection 7065 * 7066 * @class 7067 * @augments wp.media.View 7068 * @augments wp.Backbone.View 7069 * @augments Backbone.View 7070 */ 7071 var l10n = wp.media.view.l10n, 7072 Selection; 7073 7074 Selection = wp.media.View.extend({ 7075 tagName: 'div', 7076 className: 'media-selection', 7077 template: wp.template('media-selection'), 7078 7079 events: { 7080 'click .edit-selection': 'edit', 7081 'click .clear-selection': 'clear' 7082 }, 7083 7084 initialize: function() { 7085 _.defaults( this.options, { 7086 editable: false, 7087 clearable: true 7088 }); 7089 7090 /** 7091 * @member {wp.media.view.Attachments.Selection} 7092 */ 7093 this.attachments = new wp.media.view.Attachments.Selection({ 7094 controller: this.controller, 7095 collection: this.collection, 7096 selection: this.collection, 7097 model: new Backbone.Model() 7098 }); 7099 7100 this.views.set( '.selection-view', this.attachments ); 7101 this.collection.on( 'add remove reset', this.refresh, this ); 7102 this.controller.on( 'content:activate', this.refresh, this ); 7103 }, 7104 7105 ready: function() { 7106 this.refresh(); 7107 }, 7108 7109 refresh: function() { 7110 // If the selection hasn't been rendered, bail. 7111 if ( ! this.$el.children().length ) { 7112 return; 7113 } 7114 7115 var collection = this.collection, 7116 editing = 'edit-selection' === this.controller.content.mode(); 7117 7118 // If nothing is selected, display nothing. 7119 this.$el.toggleClass( 'empty', ! collection.length ); 7120 this.$el.toggleClass( 'one', 1 === collection.length ); 7121 this.$el.toggleClass( 'editing', editing ); 7122 7123 this.$('.count').text( l10n.selected.replace('%d', collection.length) ); 7124 }, 7125 7126 edit: function( event ) { 7127 event.preventDefault(); 7128 if ( this.options.editable ) { 7129 this.options.editable.call( this, this.collection ); 7130 } 7131 }, 7132 7133 clear: function( event ) { 7134 event.preventDefault(); 7135 this.collection.reset(); 7136 7137 // Keep focus inside media modal 7138 // after clear link is selected 7139 this.controller.modal.focusManager.focus(); 7140 } 7141 }); 7142 7143 module.exports = Selection; 7144 7145 },{}],57:[function(require,module,exports){ 7146 /*globals _, Backbone */ 7147 7148 /** 7149 * wp.media.view.Settings 8610 7150 * 8611 7151 * @class … … 8615 7155 */ 8616 7156 var View = wp.media.View, 8617 UploaderStatus = wp.media.view.UploaderStatus, 8618 l10n = wp.media.view.l10n, 8619 $ = jQuery, 8620 Cropper; 8621 8622 Cropper = View.extend({ 8623 className: 'crop-content', 8624 template: wp.template('crop-content'), 7157 $ = Backbone.$, 7158 Settings; 7159 7160 Settings = View.extend({ 7161 events: { 7162 'click button': 'updateHandler', 7163 'change input': 'updateHandler', 7164 'change select': 'updateHandler', 7165 'change textarea': 'updateHandler' 7166 }, 7167 8625 7168 initialize: function() { 8626 _.bindAll(this, 'onImageLoad'); 8627 }, 8628 ready: function() { 8629 this.controller.frame.on('content:error:crop', this.onError, this); 8630 this.$image = this.$el.find('.crop-image'); 8631 this.$image.on('load', this.onImageLoad); 8632 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250)); 8633 }, 8634 remove: function() { 8635 $(window).off('resize.cropper'); 8636 this.$el.remove(); 8637 this.$el.off(); 8638 View.prototype.remove.apply(this, arguments); 8639 }, 7169 this.model = this.model || new Backbone.Model(); 7170 this.listenTo( this.model, 'change', this.updateChanges ); 7171 }, 7172 8640 7173 prepare: function() { 8641 return { 8642 title: l10n.cropYourImage, 8643 url: this.options.attachment.get('url') 8644 }; 8645 }, 8646 onImageLoad: function() { 8647 var imgOptions = this.controller.get('imgSelectOptions'); 8648 if (typeof imgOptions === 'function') { 8649 imgOptions = imgOptions(this.options.attachment, this.controller); 8650 } 8651 8652 imgOptions = _.extend(imgOptions, {parent: this.$el}); 8653 this.trigger('image-loaded'); 8654 this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 8655 }, 8656 onError: function() { 8657 var filename = this.options.attachment.get('filename'); 8658 8659 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 8660 filename: UploaderStatus.prototype.filename(filename), 8661 message: window._wpMediaViewsL10n.cropError 8662 }), { at: 0 }); 7174 return _.defaults({ 7175 model: this.model.toJSON() 7176 }, this.options ); 7177 }, 7178 /** 7179 * @returns {wp.media.view.Settings} Returns itself to allow chaining 7180 */ 7181 render: function() { 7182 View.prototype.render.apply( this, arguments ); 7183 // Select the correct values. 7184 _( this.model.attributes ).chain().keys().each( this.update, this ); 7185 return this; 7186 }, 7187 /** 7188 * @param {string} key 7189 */ 7190 update: function( key ) { 7191 var value = this.model.get( key ), 7192 $setting = this.$('[data-setting="' + key + '"]'), 7193 $buttons, $value; 7194 7195 // Bail if we didn't find a matching setting. 7196 if ( ! $setting.length ) { 7197 return; 7198 } 7199 7200 // Attempt to determine how the setting is rendered and update 7201 // the selected value. 7202 7203 // Handle dropdowns. 7204 if ( $setting.is('select') ) { 7205 $value = $setting.find('[value="' + value + '"]'); 7206 7207 if ( $value.length ) { 7208 $setting.find('option').prop( 'selected', false ); 7209 $value.prop( 'selected', true ); 7210 } else { 7211 // If we can't find the desired value, record what *is* selected. 7212 this.model.set( key, $setting.find(':selected').val() ); 7213 } 7214 7215 // Handle button groups. 7216 } else if ( $setting.hasClass('button-group') ) { 7217 $buttons = $setting.find('button').removeClass('active'); 7218 $buttons.filter( '[value="' + value + '"]' ).addClass('active'); 7219 7220 // Handle text inputs and textareas. 7221 } else if ( $setting.is('input[type="text"], textarea') ) { 7222 if ( ! $setting.is(':focus') ) { 7223 $setting.val( value ); 7224 } 7225 // Handle checkboxes. 7226 } else if ( $setting.is('input[type="checkbox"]') ) { 7227 $setting.prop( 'checked', !! value && 'false' !== value ); 7228 } 7229 }, 7230 /** 7231 * @param {Object} event 7232 */ 7233 updateHandler: function( event ) { 7234 var $setting = $( event.target ).closest('[data-setting]'), 7235 value = event.target.value, 7236 userSetting; 7237 7238 event.preventDefault(); 7239 7240 if ( ! $setting.length ) { 7241 return; 7242 } 7243 7244 // Use the correct value for checkboxes. 7245 if ( $setting.is('input[type="checkbox"]') ) { 7246 value = $setting[0].checked; 7247 } 7248 7249 // Update the corresponding setting. 7250 this.model.set( $setting.data('setting'), value ); 7251 7252 // If the setting has a corresponding user setting, 7253 // update that as well. 7254 if ( userSetting = $setting.data('userSetting') ) { 7255 window.setUserSetting( userSetting, value ); 7256 } 7257 }, 7258 7259 updateChanges: function( model ) { 7260 if ( model.hasChanged() ) { 7261 _( model.changed ).chain().keys().each( this.update, this ); 7262 } 8663 7263 } 8664 7264 }); 8665 7265 8666 module.exports = Cropper; 8667 8668 8669 /***/ }), 8670 /* 97 */, 8671 /* 98 */, 8672 /* 99 */ 8673 /***/ (function(module, exports) { 8674 7266 module.exports = Settings; 7267 7268 },{}],58:[function(require,module,exports){ 8675 7269 /*globals wp, _ */ 8676 7270 8677 7271 /** 8678 * wp.media.view. EditImage7272 * wp.media.view.Settings.AttachmentDisplay 8679 7273 * 8680 7274 * @class 7275 * @augments wp.media.view.Settings 8681 7276 * @augments wp.media.View 8682 7277 * @augments wp.Backbone.View 8683 7278 * @augments Backbone.View 8684 7279 */ 8685 var View = wp.media.View, 8686 EditImage; 8687 8688 EditImage = View.extend({ 8689 className: 'image-editor', 8690 template: wp.template('image-editor'), 8691 8692 initialize: function( options ) { 8693 this.editor = window.imageEdit; 8694 this.controller = options.controller; 8695 View.prototype.initialize.apply( this, arguments ); 8696 }, 8697 8698 prepare: function() { 8699 return this.model.toJSON(); 8700 }, 8701 8702 loadEditor: function() { 8703 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this ); 8704 dfd.done( _.bind( this.focus, this ) ); 8705 }, 8706 8707 focus: function() { 8708 this.$( '.imgedit-submit .button' ).eq( 0 ).focus(); 8709 }, 8710 8711 back: function() { 8712 var lastState = this.controller.lastState(); 8713 this.controller.setState( lastState ); 8714 }, 8715 8716 refresh: function() { 8717 this.model.fetch(); 8718 }, 8719 8720 save: function() { 8721 var lastState = this.controller.lastState(); 8722 8723 this.model.fetch().done( _.bind( function() { 8724 this.controller.setState( lastState ); 8725 }, this ) ); 7280 var Settings = wp.media.view.Settings, 7281 AttachmentDisplay; 7282 7283 AttachmentDisplay = Settings.extend({ 7284 className: 'attachment-display-settings', 7285 template: wp.template('attachment-display-settings'), 7286 7287 initialize: function() { 7288 var attachment = this.options.attachment; 7289 7290 _.defaults( this.options, { 7291 userSettings: false 7292 }); 7293 // Call 'initialize' directly on the parent class. 7294 Settings.prototype.initialize.apply( this, arguments ); 7295 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 7296 7297 if ( attachment ) { 7298 attachment.on( 'change:uploading', this.render, this ); 7299 } 7300 }, 7301 7302 dispose: function() { 7303 var attachment = this.options.attachment; 7304 if ( attachment ) { 7305 attachment.off( null, null, this ); 7306 } 7307 /** 7308 * call 'dispose' directly on the parent class 7309 */ 7310 Settings.prototype.dispose.apply( this, arguments ); 7311 }, 7312 /** 7313 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining 7314 */ 7315 render: function() { 7316 var attachment = this.options.attachment; 7317 if ( attachment ) { 7318 _.extend( this.options, { 7319 sizes: attachment.get('sizes'), 7320 type: attachment.get('type') 7321 }); 7322 } 7323 /** 7324 * call 'render' directly on the parent class 7325 */ 7326 Settings.prototype.render.call( this ); 7327 this.updateLinkTo(); 7328 return this; 7329 }, 7330 7331 updateLinkTo: function() { 7332 var linkTo = this.model.get('link'), 7333 $input = this.$('.link-to-custom'), 7334 attachment = this.options.attachment; 7335 7336 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 7337 $input.addClass( 'hidden' ); 7338 return; 7339 } 7340 7341 if ( attachment ) { 7342 if ( 'post' === linkTo ) { 7343 $input.val( attachment.get('link') ); 7344 } else if ( 'file' === linkTo ) { 7345 $input.val( attachment.get('url') ); 7346 } else if ( ! this.model.get('linkUrl') ) { 7347 $input.val('http://'); 7348 } 7349 7350 $input.prop( 'readonly', 'custom' !== linkTo ); 7351 } 7352 7353 $input.removeClass( 'hidden' ); 7354 7355 // If the input is visible, focus and select its contents. 7356 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) { 7357 $input.focus()[0].select(); 7358 } 8726 7359 } 8727 8728 7360 }); 8729 7361 8730 module.exports = EditImage; 8731 8732 8733 /***/ }), 8734 /* 100 */ 8735 /***/ (function(module, exports) { 8736 7362 module.exports = AttachmentDisplay; 7363 7364 },{}],59:[function(require,module,exports){ 7365 /*globals wp */ 7366 7367 /** 7368 * wp.media.view.Settings.Gallery 7369 * 7370 * @class 7371 * @augments wp.media.view.Settings 7372 * @augments wp.media.View 7373 * @augments wp.Backbone.View 7374 * @augments Backbone.View 7375 */ 7376 var Gallery = wp.media.view.Settings.extend({ 7377 className: 'collection-settings gallery-settings', 7378 template: wp.template('gallery-settings') 7379 }); 7380 7381 module.exports = Gallery; 7382 7383 },{}],60:[function(require,module,exports){ 7384 /*globals wp */ 7385 7386 /** 7387 * wp.media.view.Settings.Playlist 7388 * 7389 * @class 7390 * @augments wp.media.view.Settings 7391 * @augments wp.media.View 7392 * @augments wp.Backbone.View 7393 * @augments Backbone.View 7394 */ 7395 var Playlist = wp.media.view.Settings.extend({ 7396 className: 'collection-settings playlist-settings', 7397 template: wp.template('playlist-settings') 7398 }); 7399 7400 module.exports = Playlist; 7401 7402 },{}],61:[function(require,module,exports){ 7403 /** 7404 * wp.media.view.Sidebar 7405 * 7406 * @class 7407 * @augments wp.media.view.PriorityList 7408 * @augments wp.media.View 7409 * @augments wp.Backbone.View 7410 * @augments Backbone.View 7411 */ 7412 var Sidebar = wp.media.view.PriorityList.extend({ 7413 className: 'media-sidebar' 7414 }); 7415 7416 module.exports = Sidebar; 7417 7418 },{}],62:[function(require,module,exports){ 8737 7419 /*globals _ */ 8738 7420 … … 8771 7453 module.exports = Spinner; 8772 7454 8773 8774 /***/ }) 8775 /******/ ])); 7455 },{}],63:[function(require,module,exports){ 7456 /*globals _, Backbone */ 7457 7458 /** 7459 * wp.media.view.Toolbar 7460 * 7461 * A toolbar which consists of a primary and a secondary section. Each sections 7462 * can be filled with views. 7463 * 7464 * @class 7465 * @augments wp.media.View 7466 * @augments wp.Backbone.View 7467 * @augments Backbone.View 7468 */ 7469 var View = wp.media.View, 7470 Toolbar; 7471 7472 Toolbar = View.extend({ 7473 tagName: 'div', 7474 className: 'media-toolbar', 7475 7476 initialize: function() { 7477 var state = this.controller.state(), 7478 selection = this.selection = state.get('selection'), 7479 library = this.library = state.get('library'); 7480 7481 this._views = {}; 7482 7483 // The toolbar is composed of two `PriorityList` views. 7484 this.primary = new wp.media.view.PriorityList(); 7485 this.secondary = new wp.media.view.PriorityList(); 7486 this.primary.$el.addClass('media-toolbar-primary search-form'); 7487 this.secondary.$el.addClass('media-toolbar-secondary'); 7488 7489 this.views.set([ this.secondary, this.primary ]); 7490 7491 if ( this.options.items ) { 7492 this.set( this.options.items, { silent: true }); 7493 } 7494 7495 if ( ! this.options.silent ) { 7496 this.render(); 7497 } 7498 7499 if ( selection ) { 7500 selection.on( 'add remove reset', this.refresh, this ); 7501 } 7502 7503 if ( library ) { 7504 library.on( 'add remove reset', this.refresh, this ); 7505 } 7506 }, 7507 /** 7508 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining 7509 */ 7510 dispose: function() { 7511 if ( this.selection ) { 7512 this.selection.off( null, null, this ); 7513 } 7514 7515 if ( this.library ) { 7516 this.library.off( null, null, this ); 7517 } 7518 /** 7519 * call 'dispose' directly on the parent class 7520 */ 7521 return View.prototype.dispose.apply( this, arguments ); 7522 }, 7523 7524 ready: function() { 7525 this.refresh(); 7526 }, 7527 7528 /** 7529 * @param {string} id 7530 * @param {Backbone.View|Object} view 7531 * @param {Object} [options={}] 7532 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 7533 */ 7534 set: function( id, view, options ) { 7535 var list; 7536 options = options || {}; 7537 7538 // Accept an object with an `id` : `view` mapping. 7539 if ( _.isObject( id ) ) { 7540 _.each( id, function( view, id ) { 7541 this.set( id, view, { silent: true }); 7542 }, this ); 7543 7544 } else { 7545 if ( ! ( view instanceof Backbone.View ) ) { 7546 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 7547 view = new wp.media.view.Button( view ).render(); 7548 } 7549 7550 view.controller = view.controller || this.controller; 7551 7552 this._views[ id ] = view; 7553 7554 list = view.options.priority < 0 ? 'secondary' : 'primary'; 7555 this[ list ].set( id, view, options ); 7556 } 7557 7558 if ( ! options.silent ) { 7559 this.refresh(); 7560 } 7561 7562 return this; 7563 }, 7564 /** 7565 * @param {string} id 7566 * @returns {wp.media.view.Button} 7567 */ 7568 get: function( id ) { 7569 return this._views[ id ]; 7570 }, 7571 /** 7572 * @param {string} id 7573 * @param {Object} options 7574 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 7575 */ 7576 unset: function( id, options ) { 7577 delete this._views[ id ]; 7578 this.primary.unset( id, options ); 7579 this.secondary.unset( id, options ); 7580 7581 if ( ! options || ! options.silent ) { 7582 this.refresh(); 7583 } 7584 return this; 7585 }, 7586 7587 refresh: function() { 7588 var state = this.controller.state(), 7589 library = state.get('library'), 7590 selection = state.get('selection'); 7591 7592 _.each( this._views, function( button ) { 7593 if ( ! button.model || ! button.options || ! button.options.requires ) { 7594 return; 7595 } 7596 7597 var requires = button.options.requires, 7598 disabled = false; 7599 7600 // Prevent insertion of attachments if any of them are still uploading 7601 disabled = _.some( selection.models, function( attachment ) { 7602 return attachment.get('uploading') === true; 7603 }); 7604 7605 if ( requires.selection && selection && ! selection.length ) { 7606 disabled = true; 7607 } else if ( requires.library && library && ! library.length ) { 7608 disabled = true; 7609 } 7610 button.model.set( 'disabled', disabled ); 7611 }); 7612 } 7613 }); 7614 7615 module.exports = Toolbar; 7616 7617 },{}],64:[function(require,module,exports){ 7618 /*globals wp, _ */ 7619 7620 /** 7621 * wp.media.view.Toolbar.Embed 7622 * 7623 * @class 7624 * @augments wp.media.view.Toolbar.Select 7625 * @augments wp.media.view.Toolbar 7626 * @augments wp.media.View 7627 * @augments wp.Backbone.View 7628 * @augments Backbone.View 7629 */ 7630 var Select = wp.media.view.Toolbar.Select, 7631 l10n = wp.media.view.l10n, 7632 Embed; 7633 7634 Embed = Select.extend({ 7635 initialize: function() { 7636 _.defaults( this.options, { 7637 text: l10n.insertIntoPost, 7638 requires: false 7639 }); 7640 // Call 'initialize' directly on the parent class. 7641 Select.prototype.initialize.apply( this, arguments ); 7642 }, 7643 7644 refresh: function() { 7645 var url = this.controller.state().props.get('url'); 7646 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 7647 /** 7648 * call 'refresh' directly on the parent class 7649 */ 7650 Select.prototype.refresh.apply( this, arguments ); 7651 } 7652 }); 7653 7654 module.exports = Embed; 7655 7656 },{}],65:[function(require,module,exports){ 7657 /*globals wp, _ */ 7658 7659 /** 7660 * wp.media.view.Toolbar.Select 7661 * 7662 * @class 7663 * @augments wp.media.view.Toolbar 7664 * @augments wp.media.View 7665 * @augments wp.Backbone.View 7666 * @augments Backbone.View 7667 */ 7668 var Toolbar = wp.media.view.Toolbar, 7669 l10n = wp.media.view.l10n, 7670 Select; 7671 7672 Select = Toolbar.extend({ 7673 initialize: function() { 7674 var options = this.options; 7675 7676 _.bindAll( this, 'clickSelect' ); 7677 7678 _.defaults( options, { 7679 event: 'select', 7680 state: false, 7681 reset: true, 7682 close: true, 7683 text: l10n.select, 7684 7685 // Does the button rely on the selection? 7686 requires: { 7687 selection: true 7688 } 7689 }); 7690 7691 options.items = _.defaults( options.items || {}, { 7692 select: { 7693 style: 'primary', 7694 text: options.text, 7695 priority: 80, 7696 click: this.clickSelect, 7697 requires: options.requires 7698 } 7699 }); 7700 // Call 'initialize' directly on the parent class. 7701 Toolbar.prototype.initialize.apply( this, arguments ); 7702 }, 7703 7704 clickSelect: function() { 7705 var options = this.options, 7706 controller = this.controller; 7707 7708 if ( options.close ) { 7709 controller.close(); 7710 } 7711 7712 if ( options.event ) { 7713 controller.state().trigger( options.event ); 7714 } 7715 7716 if ( options.state ) { 7717 controller.setState( options.state ); 7718 } 7719 7720 if ( options.reset ) { 7721 controller.reset(); 7722 } 7723 } 7724 }); 7725 7726 module.exports = Select; 7727 7728 },{}],66:[function(require,module,exports){ 7729 /*globals wp, _, jQuery */ 7730 7731 /** 7732 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap 7733 * or #wp-fullscreen-body) and relays drag'n'dropped files to a media workflow. 7734 * 7735 * wp.media.view.EditorUploader 7736 * 7737 * @class 7738 * @augments wp.media.View 7739 * @augments wp.Backbone.View 7740 * @augments Backbone.View 7741 */ 7742 var View = wp.media.View, 7743 l10n = wp.media.view.l10n, 7744 $ = jQuery, 7745 EditorUploader; 7746 7747 EditorUploader = View.extend({ 7748 tagName: 'div', 7749 className: 'uploader-editor', 7750 template: wp.template( 'uploader-editor' ), 7751 7752 localDrag: false, 7753 overContainer: false, 7754 overDropzone: false, 7755 draggingFile: null, 7756 7757 /** 7758 * Bind drag'n'drop events to callbacks. 7759 */ 7760 initialize: function() { 7761 this.initialized = false; 7762 7763 // Bail if not enabled or UA does not support drag'n'drop or File API. 7764 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 7765 return this; 7766 } 7767 7768 this.$document = $(document); 7769 this.dropzones = []; 7770 this.files = []; 7771 7772 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 7773 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 7774 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 7775 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 7776 7777 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 7778 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 7779 7780 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 7781 this.localDrag = event.type === 'dragstart'; 7782 }, this ) ); 7783 7784 this.initialized = true; 7785 return this; 7786 }, 7787 7788 /** 7789 * Check browser support for drag'n'drop. 7790 * 7791 * @return Boolean 7792 */ 7793 browserSupport: function() { 7794 var supports = false, div = document.createElement('div'); 7795 7796 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 7797 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 7798 return supports; 7799 }, 7800 7801 isDraggingFile: function( event ) { 7802 if ( this.draggingFile !== null ) { 7803 return this.draggingFile; 7804 } 7805 7806 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 7807 return false; 7808 } 7809 7810 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 7811 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 7812 7813 return this.draggingFile; 7814 }, 7815 7816 refresh: function( e ) { 7817 var dropzone_id; 7818 for ( dropzone_id in this.dropzones ) { 7819 // Hide the dropzones only if dragging has left the screen. 7820 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 7821 } 7822 7823 if ( ! _.isUndefined( e ) ) { 7824 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 7825 } 7826 7827 if ( ! this.overContainer && ! this.overDropzone ) { 7828 this.draggingFile = null; 7829 } 7830 7831 return this; 7832 }, 7833 7834 render: function() { 7835 if ( ! this.initialized ) { 7836 return this; 7837 } 7838 7839 View.prototype.render.apply( this, arguments ); 7840 $( '.wp-editor-wrap, #wp-fullscreen-body' ).each( _.bind( this.attach, this ) ); 7841 return this; 7842 }, 7843 7844 attach: function( index, editor ) { 7845 // Attach a dropzone to an editor. 7846 var dropzone = this.$el.clone(); 7847 this.dropzones.push( dropzone ); 7848 $( editor ).append( dropzone ); 7849 return this; 7850 }, 7851 7852 /** 7853 * When a file is dropped on the editor uploader, open up an editor media workflow 7854 * and upload the file immediately. 7855 * 7856 * @param {jQuery.Event} event The 'drop' event. 7857 */ 7858 drop: function( event ) { 7859 var $wrap = null, uploadView; 7860 7861 this.containerDragleave( event ); 7862 this.dropzoneDragleave( event ); 7863 7864 this.files = event.originalEvent.dataTransfer.files; 7865 if ( this.files.length < 1 ) { 7866 return; 7867 } 7868 7869 // Set the active editor to the drop target. 7870 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 7871 if ( $wrap.length > 0 && $wrap[0].id ) { 7872 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 7873 } 7874 7875 if ( ! this.workflow ) { 7876 this.workflow = wp.media.editor.open( 'content', { 7877 frame: 'post', 7878 state: 'insert', 7879 title: l10n.addMedia, 7880 multiple: true 7881 }); 7882 uploadView = this.workflow.uploader; 7883 if ( uploadView.uploader && uploadView.uploader.ready ) { 7884 this.addFiles.apply( this ); 7885 } else { 7886 this.workflow.on( 'uploader:ready', this.addFiles, this ); 7887 } 7888 } else { 7889 this.workflow.state().reset(); 7890 this.addFiles.apply( this ); 7891 this.workflow.open(); 7892 } 7893 7894 return false; 7895 }, 7896 7897 /** 7898 * Add the files to the uploader. 7899 */ 7900 addFiles: function() { 7901 if ( this.files.length ) { 7902 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 7903 this.files = []; 7904 } 7905 return this; 7906 }, 7907 7908 containerDragover: function( event ) { 7909 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 7910 return; 7911 } 7912 7913 this.overContainer = true; 7914 this.refresh(); 7915 }, 7916 7917 containerDragleave: function() { 7918 this.overContainer = false; 7919 7920 // Throttle dragleave because it's called when bouncing from some elements to others. 7921 _.delay( _.bind( this.refresh, this ), 50 ); 7922 }, 7923 7924 dropzoneDragover: function( event ) { 7925 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 7926 return; 7927 } 7928 7929 this.overDropzone = true; 7930 this.refresh( event ); 7931 return false; 7932 }, 7933 7934 dropzoneDragleave: function( e ) { 7935 this.overDropzone = false; 7936 _.delay( _.bind( this.refresh, this, e ), 50 ); 7937 }, 7938 7939 click: function( e ) { 7940 // In the rare case where the dropzone gets stuck, hide it on click. 7941 this.containerDragleave( e ); 7942 this.dropzoneDragleave( e ); 7943 this.localDrag = false; 7944 } 7945 }); 7946 7947 module.exports = EditorUploader; 7948 7949 },{}],67:[function(require,module,exports){ 7950 /*globals wp, _ */ 7951 7952 /** 7953 * wp.media.view.UploaderInline 7954 * 7955 * The inline uploader that shows up in the 'Upload Files' tab. 7956 * 7957 * @class 7958 * @augments wp.media.View 7959 * @augments wp.Backbone.View 7960 * @augments Backbone.View 7961 */ 7962 var View = wp.media.View, 7963 UploaderInline; 7964 7965 UploaderInline = View.extend({ 7966 tagName: 'div', 7967 className: 'uploader-inline', 7968 template: wp.template('uploader-inline'), 7969 7970 events: { 7971 'click .close': 'hide' 7972 }, 7973 7974 initialize: function() { 7975 _.defaults( this.options, { 7976 message: '', 7977 status: true, 7978 canClose: false 7979 }); 7980 7981 if ( ! this.options.$browser && this.controller.uploader ) { 7982 this.options.$browser = this.controller.uploader.$browser; 7983 } 7984 7985 if ( _.isUndefined( this.options.postId ) ) { 7986 this.options.postId = wp.media.view.settings.post.id; 7987 } 7988 7989 if ( this.options.status ) { 7990 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 7991 controller: this.controller 7992 }) ); 7993 } 7994 }, 7995 7996 prepare: function() { 7997 var suggestedWidth = this.controller.state().get('suggestedWidth'), 7998 suggestedHeight = this.controller.state().get('suggestedHeight'), 7999 data = {}; 8000 8001 data.message = this.options.message; 8002 data.canClose = this.options.canClose; 8003 8004 if ( suggestedWidth && suggestedHeight ) { 8005 data.suggestedWidth = suggestedWidth; 8006 data.suggestedHeight = suggestedHeight; 8007 } 8008 8009 return data; 8010 }, 8011 /** 8012 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 8013 */ 8014 dispose: function() { 8015 if ( this.disposing ) { 8016 /** 8017 * call 'dispose' directly on the parent class 8018 */ 8019 return View.prototype.dispose.apply( this, arguments ); 8020 } 8021 8022 // Run remove on `dispose`, so we can be sure to refresh the 8023 // uploader with a view-less DOM. Track whether we're disposing 8024 // so we don't trigger an infinite loop. 8025 this.disposing = true; 8026 return this.remove(); 8027 }, 8028 /** 8029 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 8030 */ 8031 remove: function() { 8032 /** 8033 * call 'remove' directly on the parent class 8034 */ 8035 var result = View.prototype.remove.apply( this, arguments ); 8036 8037 _.defer( _.bind( this.refresh, this ) ); 8038 return result; 8039 }, 8040 8041 refresh: function() { 8042 var uploader = this.controller.uploader; 8043 8044 if ( uploader ) { 8045 uploader.refresh(); 8046 } 8047 }, 8048 /** 8049 * @returns {wp.media.view.UploaderInline} 8050 */ 8051 ready: function() { 8052 var $browser = this.options.$browser, 8053 $placeholder; 8054 8055 if ( this.controller.uploader ) { 8056 $placeholder = this.$('.browser'); 8057 8058 // Check if we've already replaced the placeholder. 8059 if ( $placeholder[0] === $browser[0] ) { 8060 return; 8061 } 8062 8063 $browser.detach().text( $placeholder.text() ); 8064 $browser[0].className = $placeholder[0].className; 8065 $placeholder.replaceWith( $browser.show() ); 8066 } 8067 8068 this.refresh(); 8069 return this; 8070 }, 8071 show: function() { 8072 this.$el.removeClass( 'hidden' ); 8073 }, 8074 hide: function() { 8075 this.$el.addClass( 'hidden' ); 8076 } 8077 8078 }); 8079 8080 module.exports = UploaderInline; 8081 8082 },{}],68:[function(require,module,exports){ 8083 /*globals wp */ 8084 8085 /** 8086 * wp.media.view.UploaderStatusError 8087 * 8088 * @class 8089 * @augments wp.media.View 8090 * @augments wp.Backbone.View 8091 * @augments Backbone.View 8092 */ 8093 var UploaderStatusError = wp.media.View.extend({ 8094 className: 'upload-error', 8095 template: wp.template('uploader-status-error') 8096 }); 8097 8098 module.exports = UploaderStatusError; 8099 8100 },{}],69:[function(require,module,exports){ 8101 /*globals wp, _ */ 8102 8103 /** 8104 * wp.media.view.UploaderStatus 8105 * 8106 * An uploader status for on-going uploads. 8107 * 8108 * @class 8109 * @augments wp.media.View 8110 * @augments wp.Backbone.View 8111 * @augments Backbone.View 8112 */ 8113 var View = wp.media.View, 8114 UploaderStatus; 8115 8116 UploaderStatus = View.extend({ 8117 className: 'media-uploader-status', 8118 template: wp.template('uploader-status'), 8119 8120 events: { 8121 'click .upload-dismiss-errors': 'dismiss' 8122 }, 8123 8124 initialize: function() { 8125 this.queue = wp.Uploader.queue; 8126 this.queue.on( 'add remove reset', this.visibility, this ); 8127 this.queue.on( 'add remove reset change:percent', this.progress, this ); 8128 this.queue.on( 'add remove reset change:uploading', this.info, this ); 8129 8130 this.errors = wp.Uploader.errors; 8131 this.errors.reset(); 8132 this.errors.on( 'add remove reset', this.visibility, this ); 8133 this.errors.on( 'add', this.error, this ); 8134 }, 8135 /** 8136 * @global wp.Uploader 8137 * @returns {wp.media.view.UploaderStatus} 8138 */ 8139 dispose: function() { 8140 wp.Uploader.queue.off( null, null, this ); 8141 /** 8142 * call 'dispose' directly on the parent class 8143 */ 8144 View.prototype.dispose.apply( this, arguments ); 8145 return this; 8146 }, 8147 8148 visibility: function() { 8149 this.$el.toggleClass( 'uploading', !! this.queue.length ); 8150 this.$el.toggleClass( 'errors', !! this.errors.length ); 8151 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 8152 }, 8153 8154 ready: function() { 8155 _.each({ 8156 '$bar': '.media-progress-bar div', 8157 '$index': '.upload-index', 8158 '$total': '.upload-total', 8159 '$filename': '.upload-filename' 8160 }, function( selector, key ) { 8161 this[ key ] = this.$( selector ); 8162 }, this ); 8163 8164 this.visibility(); 8165 this.progress(); 8166 this.info(); 8167 }, 8168 8169 progress: function() { 8170 var queue = this.queue, 8171 $bar = this.$bar; 8172 8173 if ( ! $bar || ! queue.length ) { 8174 return; 8175 } 8176 8177 $bar.width( ( queue.reduce( function( memo, attachment ) { 8178 if ( ! attachment.get('uploading') ) { 8179 return memo + 100; 8180 } 8181 8182 var percent = attachment.get('percent'); 8183 return memo + ( _.isNumber( percent ) ? percent : 100 ); 8184 }, 0 ) / queue.length ) + '%' ); 8185 }, 8186 8187 info: function() { 8188 var queue = this.queue, 8189 index = 0, active; 8190 8191 if ( ! queue.length ) { 8192 return; 8193 } 8194 8195 active = this.queue.find( function( attachment, i ) { 8196 index = i; 8197 return attachment.get('uploading'); 8198 }); 8199 8200 this.$index.text( index + 1 ); 8201 this.$total.text( queue.length ); 8202 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 8203 }, 8204 /** 8205 * @param {string} filename 8206 * @returns {string} 8207 */ 8208 filename: function( filename ) { 8209 return wp.media.truncate( _.escape( filename ), 24 ); 8210 }, 8211 /** 8212 * @param {Backbone.Model} error 8213 */ 8214 error: function( error ) { 8215 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 8216 filename: this.filename( error.get('file').name ), 8217 message: error.get('message') 8218 }), { at: 0 }); 8219 }, 8220 8221 /** 8222 * @global wp.Uploader 8223 * 8224 * @param {Object} event 8225 */ 8226 dismiss: function( event ) { 8227 var errors = this.views.get('.upload-errors'); 8228 8229 event.preventDefault(); 8230 8231 if ( errors ) { 8232 _.invoke( errors, 'remove' ); 8233 } 8234 wp.Uploader.errors.reset(); 8235 } 8236 }); 8237 8238 module.exports = UploaderStatus; 8239 8240 },{}],70:[function(require,module,exports){ 8241 /*globals wp, _, jQuery */ 8242 8243 /** 8244 * wp.media.view.UploaderWindow 8245 * 8246 * An uploader window that allows for dragging and dropping media. 8247 * 8248 * @class 8249 * @augments wp.media.View 8250 * @augments wp.Backbone.View 8251 * @augments Backbone.View 8252 * 8253 * @param {object} [options] Options hash passed to the view. 8254 * @param {object} [options.uploader] Uploader properties. 8255 * @param {jQuery} [options.uploader.browser] 8256 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 8257 * @param {object} [options.uploader.params] 8258 */ 8259 var $ = jQuery, 8260 UploaderWindow; 8261 8262 UploaderWindow = wp.media.View.extend({ 8263 tagName: 'div', 8264 className: 'uploader-window', 8265 template: wp.template('uploader-window'), 8266 8267 initialize: function() { 8268 var uploader; 8269 8270 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body'); 8271 8272 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 8273 dropzone: this.$el, 8274 browser: this.$browser, 8275 params: {} 8276 }); 8277 8278 // Ensure the dropzone is a jQuery collection. 8279 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 8280 uploader.dropzone = $( uploader.dropzone ); 8281 } 8282 8283 this.controller.on( 'activate', this.refresh, this ); 8284 8285 this.controller.on( 'detach', function() { 8286 this.$browser.remove(); 8287 }, this ); 8288 }, 8289 8290 refresh: function() { 8291 if ( this.uploader ) { 8292 this.uploader.refresh(); 8293 } 8294 }, 8295 8296 ready: function() { 8297 var postId = wp.media.view.settings.post.id, 8298 dropzone; 8299 8300 // If the uploader already exists, bail. 8301 if ( this.uploader ) { 8302 return; 8303 } 8304 8305 if ( postId ) { 8306 this.options.uploader.params.post_id = postId; 8307 } 8308 this.uploader = new wp.Uploader( this.options.uploader ); 8309 8310 dropzone = this.uploader.dropzone; 8311 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 8312 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 8313 8314 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 8315 }, 8316 8317 _ready: function() { 8318 this.controller.trigger( 'uploader:ready' ); 8319 }, 8320 8321 show: function() { 8322 var $el = this.$el.show(); 8323 8324 // Ensure that the animation is triggered by waiting until 8325 // the transparent element is painted into the DOM. 8326 _.defer( function() { 8327 $el.css({ opacity: 1 }); 8328 }); 8329 }, 8330 8331 hide: function() { 8332 var $el = this.$el.css({ opacity: 0 }); 8333 8334 wp.media.transition( $el ).done( function() { 8335 // Transition end events are subject to race conditions. 8336 // Make sure that the value is set as intended. 8337 if ( '0' === $el.css('opacity') ) { 8338 $el.hide(); 8339 } 8340 }); 8341 8342 // https://core.trac.wordpress.org/ticket/27341 8343 _.delay( function() { 8344 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 8345 $el.hide(); 8346 } 8347 }, 500 ); 8348 } 8349 }); 8350 8351 module.exports = UploaderWindow; 8352 8353 },{}],71:[function(require,module,exports){ 8354 /*globals wp */ 8355 8356 /** 8357 * wp.media.View 8358 * 8359 * The base view class for media. 8360 * 8361 * Undelegating events, removing events from the model, and 8362 * removing events from the controller mirror the code for 8363 * `Backbone.View.dispose` in Backbone 0.9.8 development. 8364 * 8365 * This behavior has since been removed, and should not be used 8366 * outside of the media manager. 8367 * 8368 * @class 8369 * @augments wp.Backbone.View 8370 * @augments Backbone.View 8371 */ 8372 var View = wp.Backbone.View.extend({ 8373 constructor: function( options ) { 8374 if ( options && options.controller ) { 8375 this.controller = options.controller; 8376 } 8377 wp.Backbone.View.apply( this, arguments ); 8378 }, 8379 /** 8380 * @todo The internal comment mentions this might have been a stop-gap 8381 * before Backbone 0.9.8 came out. Figure out if Backbone core takes 8382 * care of this in Backbone.View now. 8383 * 8384 * @returns {wp.media.View} Returns itself to allow chaining 8385 */ 8386 dispose: function() { 8387 // Undelegating events, removing events from the model, and 8388 // removing events from the controller mirror the code for 8389 // `Backbone.View.dispose` in Backbone 0.9.8 development. 8390 this.undelegateEvents(); 8391 8392 if ( this.model && this.model.off ) { 8393 this.model.off( null, null, this ); 8394 } 8395 8396 if ( this.collection && this.collection.off ) { 8397 this.collection.off( null, null, this ); 8398 } 8399 8400 // Unbind controller events. 8401 if ( this.controller && this.controller.off ) { 8402 this.controller.off( null, null, this ); 8403 } 8404 8405 return this; 8406 }, 8407 /** 8408 * @returns {wp.media.View} Returns itself to allow chaining 8409 */ 8410 remove: function() { 8411 this.dispose(); 8412 /** 8413 * call 'remove' directly on the parent class 8414 */ 8415 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 8416 } 8417 }); 8418 8419 module.exports = View; 8420 8421 },{}]},{},[17]);
Note: See TracChangeset
for help on using the changeset viewer.