Changeset 46500
- Timestamp:
- 10/14/2019 07:14:31 PM (6 years ago)
- Location:
- branches/4.2
- Files:
-
- 11 edited
-
. (modified) (1 prop)
-
src/wp-includes/class-wp.php (modified) (1 diff)
-
src/wp-includes/functions.php (modified) (1 diff)
-
src/wp-includes/http.php (modified) (1 diff)
-
src/wp-includes/js/media-audiovideo.js (modified) (8 diffs)
-
src/wp-includes/js/media-grid.js (modified) (4 diffs)
-
src/wp-includes/js/media-models.js (modified) (7 diffs)
-
src/wp-includes/js/media-views.js (modified) (24 diffs)
-
src/wp-includes/pluggable.php (modified) (4 diffs)
-
src/wp-includes/query.php (modified) (2 diffs)
-
tests/phpunit/tests/auth.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
branches/4.2
- Property svn:mergeinfo changed
/trunk merged: 46474-46478,46483,46485
- Property svn:mergeinfo changed
-
branches/4.2/src/wp-includes/class-wp.php
r44065 r46500 16 16 * @var array 17 17 */ 18 public $public_query_vars = array( 'm', 'p', 'posts', 'w', 'cat', 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', 'name', 'category_name', 'tag', 'feed', 'author_name', 'static', 'pagename', 'page_id', 'error', 'comments_popup', 'attachment', 'attachment_id', 'subpost', 'subpost_id', 'preview', 'robots', 'taxonomy', 'term', 'cpage', 'post_type');18 public $public_query_vars = array( 'm', 'p', 'posts', 'w', 'cat', 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', 'name', 'category_name', 'tag', 'feed', 'author_name', 'pagename', 'page_id', 'error', 'comments_popup', 'attachment', 'attachment_id', 'subpost', 'subpost_id', 'preview', 'robots', 'taxonomy', 'term', 'cpage', 'post_type', 'embed' ); 19 19 20 20 /** -
branches/4.2/src/wp-includes/functions.php
r43998 r46500 1496 1496 if ( file_exists( $target ) ) 1497 1497 return @is_dir( $target ); 1498 1499 // Do not allow path traversals. 1500 if ( false !== strpos( $target, '../' ) || false !== strpos( $target, '..' . DIRECTORY_SEPARATOR ) ) { 1501 return false; 1502 } 1498 1503 1499 1504 // We need to find the permissions of the parent folder that exists and inherit that. -
branches/4.2/src/wp-includes/http.php
r37118 r46500 471 471 } else { 472 472 $ip = gethostbyname( $host ); 473 if ( $ip === $host ) // Error condition for gethostbyname() 474 $ip = false; 473 if ( $ip === $host ) { // Error condition for gethostbyname() 474 return false; 475 } 475 476 } 476 477 if ( $ip ) { -
branches/4.2/src/wp-includes/js/media-audiovideo.js
r33316 r46500 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){ 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 2 70 /*globals wp, _ */ 3 71 … … 208 276 }; 209 277 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){ 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 221 341 /*globals wp */ 222 342 … … 253 373 module.exports = AudioDetails; 254 374 255 },{}],3:[function(require,module,exports){ 375 376 /***/ }), 377 /* 3 */ 378 /***/ (function(module, exports) { 379 256 380 /*globals wp */ 257 381 … … 288 412 module.exports = VideoDetails; 289 413 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){ 414 415 /***/ }), 416 /* 4 */ 417 /***/ (function(module, exports) { 418 453 419 /*globals wp */ 454 420 … … 582 548 module.exports = MediaDetails; 583 549 584 },{}],8:[function(require,module,exports){ 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 585 637 /*globals wp, _ */ 586 638 … … 719 771 module.exports = VideoDetails; 720 772 721 },{}],9:[function(require,module,exports){ 773 774 /***/ }), 775 /* 7 */ 776 /***/ (function(module, exports) { 777 722 778 /*global wp, jQuery, _, MediaElementPlayer */ 723 779 … … 887 943 module.exports = MediaDetails; 888 944 889 },{}],10:[function(require,module,exports){ 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 890 994 /*globals wp */ 891 995 … … 932 1036 module.exports = VideoDetails; 933 1037 934 },{}]},{},[1]); 1038 1039 /***/ }) 1040 /******/ ]); -
branches/4.2/src/wp-includes/js/media-grid.js
r32125 r46500 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){ 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 2 99 /*globals wp */ 3 100 … … 29 126 module.exports = EditAttachmentMetadata; 30 127 31 },{}],2:[function(require,module,exports){ 128 129 /***/ }), 130 /* 12 */ 131 /***/ (function(module, exports) { 132 133 /*globals wp, _, Backbone */ 134 135 /** 136 * wp.media.view.MediaFrame.Manage 137 * 138 * A generic management frame workflow. 139 * 140 * Used in the media grid view. 141 * 142 * @class 143 * @augments wp.media.view.MediaFrame 144 * @augments wp.media.view.Frame 145 * @augments wp.media.View 146 * @augments wp.Backbone.View 147 * @augments Backbone.View 148 * @mixes wp.media.controller.StateMachine 149 */ 150 var MediaFrame = wp.media.view.MediaFrame, 151 Library = wp.media.controller.Library, 152 153 $ = Backbone.$, 154 Manage; 155 156 Manage = MediaFrame.extend({ 157 /** 158 * @global wp.Uploader 159 */ 160 initialize: function() { 161 _.defaults( this.options, { 162 title: '', 163 modal: false, 164 selection: [], 165 library: {}, // Options hash for the query to the media library. 166 multiple: 'add', 167 state: 'library', 168 uploader: true, 169 mode: [ 'grid', 'edit' ] 170 }); 171 172 this.$body = $( document.body ); 173 this.$window = $( window ); 174 this.$adminBar = $( '#wpadminbar' ); 175 this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) ); 176 $( document ).on( 'click', '.add-new-h2', _.bind( this.addNewClickHandler, this ) ); 177 178 // Ensure core and media grid view UI is enabled. 179 this.$el.addClass('wp-core-ui'); 180 181 // Force the uploader off if the upload limit has been exceeded or 182 // if the browser isn't supported. 183 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 184 this.options.uploader = false; 185 } 186 187 // Initialize a window-wide uploader. 188 if ( this.options.uploader ) { 189 this.uploader = new wp.media.view.UploaderWindow({ 190 controller: this, 191 uploader: { 192 dropzone: document.body, 193 container: document.body 194 } 195 }).render(); 196 this.uploader.ready(); 197 $('body').append( this.uploader.el ); 198 199 this.options.uploader = false; 200 } 201 202 this.gridRouter = new wp.media.view.MediaFrame.Manage.Router(); 203 204 // Call 'initialize' directly on the parent class. 205 MediaFrame.prototype.initialize.apply( this, arguments ); 206 207 // Append the frame view directly the supplied container. 208 this.$el.appendTo( this.options.container ); 209 210 this.createStates(); 211 this.bindRegionModeHandlers(); 212 this.render(); 213 this.bindSearchHandler(); 214 }, 215 216 bindSearchHandler: function() { 217 var search = this.$( '#media-search-input' ), 218 currentSearch = this.options.container.data( 'search' ), 219 searchView = this.browserView.toolbar.get( 'search' ).$el, 220 listMode = this.$( '.view-list' ), 221 222 input = _.debounce( function (e) { 223 var val = $( e.currentTarget ).val(), 224 url = ''; 225 226 if ( val ) { 227 url += '?search=' + val; 228 } 229 this.gridRouter.navigate( this.gridRouter.baseUrl( url ) ); 230 }, 1000 ); 231 232 // Update the URL when entering search string (at most once per second) 233 search.on( 'input', _.bind( input, this ) ); 234 searchView.val( currentSearch ).trigger( 'input' ); 235 236 this.gridRouter.on( 'route:search', function () { 237 var href = window.location.href; 238 if ( href.indexOf( 'mode=' ) > -1 ) { 239 href = href.replace( /mode=[^&]+/g, 'mode=list' ); 240 } else { 241 href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list'; 242 } 243 href = href.replace( 'search=', 's=' ); 244 listMode.prop( 'href', href ); 245 } ); 246 }, 247 248 /** 249 * Create the default states for the frame. 250 */ 251 createStates: function() { 252 var options = this.options; 253 254 if ( this.options.states ) { 255 return; 256 } 257 258 // Add the default states. 259 this.states.add([ 260 new Library({ 261 library: wp.media.query( options.library ), 262 multiple: options.multiple, 263 title: options.title, 264 content: 'browse', 265 toolbar: 'select', 266 contentUserSetting: false, 267 filterable: 'all', 268 autoSelect: false 269 }) 270 ]); 271 }, 272 273 /** 274 * Bind region mode activation events to proper handlers. 275 */ 276 bindRegionModeHandlers: function() { 277 this.on( 'content:create:browse', this.browseContent, this ); 278 279 // Handle a frame-level event for editing an attachment. 280 this.on( 'edit:attachment', this.openEditAttachmentModal, this ); 281 282 this.on( 'select:activate', this.bindKeydown, this ); 283 this.on( 'select:deactivate', this.unbindKeydown, this ); 284 }, 285 286 handleKeydown: function( e ) { 287 if ( 27 === e.which ) { 288 e.preventDefault(); 289 this.deactivateMode( 'select' ).activateMode( 'edit' ); 290 } 291 }, 292 293 bindKeydown: function() { 294 this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) ); 295 }, 296 297 unbindKeydown: function() { 298 this.$body.off( 'keydown.select' ); 299 }, 300 301 fixPosition: function() { 302 var $browser, $toolbar; 303 if ( ! this.isModeActive( 'select' ) ) { 304 return; 305 } 306 307 $browser = this.$('.attachments-browser'); 308 $toolbar = $browser.find('.media-toolbar'); 309 310 // Offset doesn't appear to take top margin into account, hence +16 311 if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) { 312 $browser.addClass( 'fixed' ); 313 $toolbar.css('width', $browser.width() + 'px'); 314 } else { 315 $browser.removeClass( 'fixed' ); 316 $toolbar.css('width', ''); 317 } 318 }, 319 320 /** 321 * Click handler for the `Add New` button. 322 */ 323 addNewClickHandler: function( event ) { 324 event.preventDefault(); 325 this.trigger( 'toggle:upload:attachment' ); 326 }, 327 328 /** 329 * Open the Edit Attachment modal. 330 */ 331 openEditAttachmentModal: function( model ) { 332 // Create a new EditAttachment frame, passing along the library and the attachment model. 333 wp.media( { 334 frame: 'edit-attachments', 335 controller: this, 336 library: this.state().get('library'), 337 model: model 338 } ); 339 }, 340 341 /** 342 * Create an attachments browser view within the content region. 343 * 344 * @param {Object} contentRegion Basic object with a `view` property, which 345 * should be set with the proper region view. 346 * @this wp.media.controller.Region 347 */ 348 browseContent: function( contentRegion ) { 349 var state = this.state(); 350 351 // Browse our library of attachments. 352 this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({ 353 controller: this, 354 collection: state.get('library'), 355 selection: state.get('selection'), 356 model: state, 357 sortable: state.get('sortable'), 358 search: state.get('searchable'), 359 filters: state.get('filterable'), 360 date: state.get('date'), 361 display: state.get('displaySettings'), 362 dragInfo: state.get('dragInfo'), 363 sidebar: 'errors', 364 365 suggestedWidth: state.get('suggestedWidth'), 366 suggestedHeight: state.get('suggestedHeight'), 367 368 AttachmentView: state.get('AttachmentView'), 369 370 scrollElement: document 371 }); 372 this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) ); 373 374 this.errors = wp.Uploader.errors; 375 this.errors.on( 'add remove reset', this.sidebarVisibility, this ); 376 }, 377 378 sidebarVisibility: function() { 379 this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length ); 380 }, 381 382 bindDeferred: function() { 383 if ( ! this.browserView.dfd ) { 384 return; 385 } 386 this.browserView.dfd.done( _.bind( this.startHistory, this ) ); 387 }, 388 389 startHistory: function() { 390 // Verify pushState support and activate 391 if ( window.history && window.history.pushState ) { 392 Backbone.history.start( { 393 root: window._wpMediaGridSettings.adminUrl, 394 pushState: true 395 } ); 396 } 397 } 398 }); 399 400 module.exports = Manage; 401 402 403 /***/ }), 404 /* 13 */ 405 /***/ (function(module, exports) { 406 32 407 /*globals wp */ 33 408 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){ 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 47 456 /*globals wp, Backbone */ 48 457 … … 94 503 module.exports = Router; 95 504 96 },{}],4:[function(require,module,exports){ 97 /*globals wp */ 505 506 /***/ }), 507 /* 15 */ 508 /***/ (function(module, exports) { 509 510 /*globals wp, _ */ 98 511 99 512 /** 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. 513 * wp.media.view.EditImage.Details 104 514 * 105 515 * @class 106 * @augments wp.media.view.Attachment.Details 107 * @augments wp.media.view.Attachment 516 * @augments wp.media.view.EditImage 108 517 * @augments wp.media.View 109 518 * @augments wp.Backbone.View 110 519 * @augments Backbone.View 111 520 */ 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 } ); 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 ) ); 136 541 } 137 542 }); 138 543 139 module.exports = TwoColumn; 140 141 },{}],5:[function(require,module,exports){ 142 /*globals wp */ 544 module.exports = Details; 545 546 547 /***/ }), 548 /* 16 */ 549 /***/ (function(module, exports) { 550 551 /*globals wp, _, jQuery */ 143 552 144 553 /** 145 * wp.media.view.DeleteSelectedPermanentlyButton 146 * 147 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic 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`. 148 561 * 149 562 * @class 150 * @augments wp.media.view.DeleteSelectedButton 151 * @augments wp.media.view.Button 563 * @augments wp.media.view.Frame 152 564 * @augments wp.media.View 153 565 * @augments wp.Backbone.View 154 566 * @augments Backbone.View 567 * @mixes wp.media.controller.StateMachine 155 568 */ 156 var Button = wp.media.view.Button, 157 DeleteSelected = wp.media.view.DeleteSelectedButton, 158 DeleteSelectedPermanently; 159 160 DeleteSelectedPermanently = DeleteSelected.extend({ 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 161 586 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; 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( '' ) ); 185 790 } 186 791 }); 187 792 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){ 793 module.exports = EditAttachments; 794 795 796 /***/ }), 797 /* 17 */ 798 /***/ (function(module, exports) { 799 246 800 /*globals wp */ 247 801 … … 309 863 module.exports = SelectModeToggle; 310 864 311 },{}],8:[function(require,module,exports){ 312 /*globals wp, _ */ 865 866 /***/ }), 867 /* 18 */ 868 /***/ (function(module, exports) { 869 870 /*globals wp */ 313 871 314 872 /** 315 * wp.media.view.EditImage.Details 873 * wp.media.view.DeleteSelectedButton 874 * 875 * A button that handles bulk Delete/Trash logic 316 876 * 317 877 * @class 318 * @augments wp.media.view. EditImage878 * @augments wp.media.view.Button 319 879 * @augments wp.media.View 320 880 * @augments wp.Backbone.View 321 881 * @augments Backbone.View 322 882 */ 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 ) ); 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; 343 919 } 344 920 }); 345 921 346 module.exports = Details; 347 348 },{}],9:[function(require,module,exports){ 349 /*globals wp, _, jQuery */ 922 module.exports = DeleteSelected; 923 924 925 /***/ }), 926 /* 19 */ 927 /***/ (function(module, exports) { 928 929 /*globals wp */ 350 930 351 931 /** 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`. 932 * wp.media.view.DeleteSelectedPermanentlyButton 933 * 934 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic 359 935 * 360 936 * @class 361 * @augments wp.media.view.Frame 937 * @augments wp.media.view.DeleteSelectedButton 938 * @augments wp.media.view.Button 362 939 * @augments wp.media.View 363 940 * @augments wp.Backbone.View 364 941 * @augments Backbone.View 365 * @mixes wp.media.controller.StateMachine366 942 */ 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 943 var Button = wp.media.view.Button, 944 DeleteSelected = wp.media.view.DeleteSelectedButton, 945 DeleteSelectedPermanently; 946 947 DeleteSelectedPermanently = DeleteSelected.extend({ 384 948 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( '' ) ); 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; 588 972 } 589 973 }); 590 974 591 module.exports = EditAttachments; 592 593 },{}],10:[function(require,module,exports){ 594 /*globals wp, _, Backbone */ 595 596 /** 597 * wp.media.view.MediaFrame.Manage 598 * 599 * A generic management frame workflow. 600 * 601 * Used in the media grid view. 602 * 603 * @class 604 * @augments wp.media.view.MediaFrame 605 * @augments wp.media.view.Frame 606 * @augments wp.media.View 607 * @augments wp.Backbone.View 608 * @augments Backbone.View 609 * @mixes wp.media.controller.StateMachine 610 */ 611 var MediaFrame = wp.media.view.MediaFrame, 612 Library = wp.media.controller.Library, 613 614 $ = Backbone.$, 615 Manage; 616 617 Manage = MediaFrame.extend({ 618 /** 619 * @global wp.Uploader 620 */ 621 initialize: function() { 622 _.defaults( this.options, { 623 title: '', 624 modal: false, 625 selection: [], 626 library: {}, // Options hash for the query to the media library. 627 multiple: 'add', 628 state: 'library', 629 uploader: true, 630 mode: [ 'grid', 'edit' ] 631 }); 632 633 this.$body = $( document.body ); 634 this.$window = $( window ); 635 this.$adminBar = $( '#wpadminbar' ); 636 this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) ); 637 $( document ).on( 'click', '.add-new-h2', _.bind( this.addNewClickHandler, this ) ); 638 639 // Ensure core and media grid view UI is enabled. 640 this.$el.addClass('wp-core-ui'); 641 642 // Force the uploader off if the upload limit has been exceeded or 643 // if the browser isn't supported. 644 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 645 this.options.uploader = false; 646 } 647 648 // Initialize a window-wide uploader. 649 if ( this.options.uploader ) { 650 this.uploader = new wp.media.view.UploaderWindow({ 651 controller: this, 652 uploader: { 653 dropzone: document.body, 654 container: document.body 655 } 656 }).render(); 657 this.uploader.ready(); 658 $('body').append( this.uploader.el ); 659 660 this.options.uploader = false; 661 } 662 663 this.gridRouter = new wp.media.view.MediaFrame.Manage.Router(); 664 665 // Call 'initialize' directly on the parent class. 666 MediaFrame.prototype.initialize.apply( this, arguments ); 667 668 // Append the frame view directly the supplied container. 669 this.$el.appendTo( this.options.container ); 670 671 this.createStates(); 672 this.bindRegionModeHandlers(); 673 this.render(); 674 this.bindSearchHandler(); 675 }, 676 677 bindSearchHandler: function() { 678 var search = this.$( '#media-search-input' ), 679 currentSearch = this.options.container.data( 'search' ), 680 searchView = this.browserView.toolbar.get( 'search' ).$el, 681 listMode = this.$( '.view-list' ), 682 683 input = _.debounce( function (e) { 684 var val = $( e.currentTarget ).val(), 685 url = ''; 686 687 if ( val ) { 688 url += '?search=' + val; 689 } 690 this.gridRouter.navigate( this.gridRouter.baseUrl( url ) ); 691 }, 1000 ); 692 693 // Update the URL when entering search string (at most once per second) 694 search.on( 'input', _.bind( input, this ) ); 695 searchView.val( currentSearch ).trigger( 'input' ); 696 697 this.gridRouter.on( 'route:search', function () { 698 var href = window.location.href; 699 if ( href.indexOf( 'mode=' ) > -1 ) { 700 href = href.replace( /mode=[^&]+/g, 'mode=list' ); 701 } else { 702 href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list'; 703 } 704 href = href.replace( 'search=', 's=' ); 705 listMode.prop( 'href', href ); 706 } ); 707 }, 708 709 /** 710 * Create the default states for the frame. 711 */ 712 createStates: function() { 713 var options = this.options; 714 715 if ( this.options.states ) { 716 return; 717 } 718 719 // Add the default states. 720 this.states.add([ 721 new Library({ 722 library: wp.media.query( options.library ), 723 multiple: options.multiple, 724 title: options.title, 725 content: 'browse', 726 toolbar: 'select', 727 contentUserSetting: false, 728 filterable: 'all', 729 autoSelect: false 730 }) 731 ]); 732 }, 733 734 /** 735 * Bind region mode activation events to proper handlers. 736 */ 737 bindRegionModeHandlers: function() { 738 this.on( 'content:create:browse', this.browseContent, this ); 739 740 // Handle a frame-level event for editing an attachment. 741 this.on( 'edit:attachment', this.openEditAttachmentModal, this ); 742 743 this.on( 'select:activate', this.bindKeydown, this ); 744 this.on( 'select:deactivate', this.unbindKeydown, this ); 745 }, 746 747 handleKeydown: function( e ) { 748 if ( 27 === e.which ) { 749 e.preventDefault(); 750 this.deactivateMode( 'select' ).activateMode( 'edit' ); 751 } 752 }, 753 754 bindKeydown: function() { 755 this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) ); 756 }, 757 758 unbindKeydown: function() { 759 this.$body.off( 'keydown.select' ); 760 }, 761 762 fixPosition: function() { 763 var $browser, $toolbar; 764 if ( ! this.isModeActive( 'select' ) ) { 765 return; 766 } 767 768 $browser = this.$('.attachments-browser'); 769 $toolbar = $browser.find('.media-toolbar'); 770 771 // Offset doesn't appear to take top margin into account, hence +16 772 if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) { 773 $browser.addClass( 'fixed' ); 774 $toolbar.css('width', $browser.width() + 'px'); 775 } else { 776 $browser.removeClass( 'fixed' ); 777 $toolbar.css('width', ''); 778 } 779 }, 780 781 /** 782 * Click handler for the `Add New` button. 783 */ 784 addNewClickHandler: function( event ) { 785 event.preventDefault(); 786 this.trigger( 'toggle:upload:attachment' ); 787 }, 788 789 /** 790 * Open the Edit Attachment modal. 791 */ 792 openEditAttachmentModal: function( model ) { 793 // Create a new EditAttachment frame, passing along the library and the attachment model. 794 wp.media( { 795 frame: 'edit-attachments', 796 controller: this, 797 library: this.state().get('library'), 798 model: model 799 } ); 800 }, 801 802 /** 803 * Create an attachments browser view within the content region. 804 * 805 * @param {Object} contentRegion Basic object with a `view` property, which 806 * should be set with the proper region view. 807 * @this wp.media.controller.Region 808 */ 809 browseContent: function( contentRegion ) { 810 var state = this.state(); 811 812 // Browse our library of attachments. 813 this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({ 814 controller: this, 815 collection: state.get('library'), 816 selection: state.get('selection'), 817 model: state, 818 sortable: state.get('sortable'), 819 search: state.get('searchable'), 820 filters: state.get('filterable'), 821 date: state.get('date'), 822 display: state.get('displaySettings'), 823 dragInfo: state.get('dragInfo'), 824 sidebar: 'errors', 825 826 suggestedWidth: state.get('suggestedWidth'), 827 suggestedHeight: state.get('suggestedHeight'), 828 829 AttachmentView: state.get('AttachmentView'), 830 831 scrollElement: document 832 }); 833 this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) ); 834 835 this.errors = wp.Uploader.errors; 836 this.errors.on( 'add remove reset', this.sidebarVisibility, this ); 837 }, 838 839 sidebarVisibility: function() { 840 this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length ); 841 }, 842 843 bindDeferred: function() { 844 if ( ! this.browserView.dfd ) { 845 return; 846 } 847 this.browserView.dfd.done( _.bind( this.startHistory, this ) ); 848 }, 849 850 startHistory: function() { 851 // Verify pushState support and activate 852 if ( window.history && window.history.pushState ) { 853 Backbone.history.start( { 854 root: window._wpMediaGridSettings.adminUrl, 855 pushState: true 856 } ); 857 } 858 } 859 }); 860 861 module.exports = Manage; 862 863 },{}]},{},[2]); 975 module.exports = DeleteSelectedPermanently; 976 977 978 /***/ }) 979 /******/ ]); -
branches/4.2/src/wp-includes/js/media-models.js
r32125 r46500 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){ 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 2 71 /*globals wp, _, jQuery */ 3 72 … … 59 128 delete l10n.settings; 60 129 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');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 ); 67 136 68 137 /** … … 232 301 }); 233 302 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){ 303 304 /***/ }), 305 306 /***/ 21: 307 /***/ (function(module, exports) { 308 235 309 /*globals wp, _, Backbone */ 236 310 … … 402 476 module.exports = Attachment; 403 477 404 },{}],3:[function(require,module,exports){ 478 479 /***/ }), 480 481 /***/ 22: 482 /***/ (function(module, exports) { 483 405 484 /*globals wp, _, Backbone */ 406 485 … … 936 1015 module.exports = Attachments; 937 1016 938 },{}],4:[function(require,module,exports){ 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 939 1338 /*globals Backbone */ 940 1339 … … 1092 1491 module.exports = PostImage; 1093 1492 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){ 1493 1494 /***/ }), 1495 1496 /***/ 25: 1497 /***/ (function(module, exports) { 1498 1405 1499 /*globals wp, _ */ 1406 1500 … … 1501 1595 module.exports = Selection; 1502 1596 1503 },{}]},{},[1]); 1597 1598 /***/ }) 1599 1600 /******/ }); -
branches/4.2/src/wp-includes/js/media-views.js
r32258 r46500 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){ 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, 73 $ = 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 }()); 106 107 /** 108 * A shared event bus used to provide events into 109 * the media workflows that 3rd-party devs can use to hook 110 * in. 111 */ 112 media.events = _.extend( {}, Backbone.Events ); 113 114 /** 115 * Makes it easier to bind events using transitions. 116 * 117 * @param {string} selector 118 * @param {Number} sensitivity 119 * @returns {Promise} 120 */ 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(); 140 } 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, _ */ 224 225 /** 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. 237 * 238 * @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. 244 */ 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. 255 * 256 * @since 3.5.0 257 * 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 ) { 384 return; 385 } 386 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; 398 } 399 }); 400 401 module.exports = Region; 402 403 404 /***/ }), 405 /* 28 */ 406 /***/ (function(module, exports) { 407 408 /*globals _, Backbone */ 409 410 /** 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 419 * 420 * @class 421 * @augments Backbone.Model 422 * @mixin 423 * @mixes Backbone.Events 424 * 425 * @param {Array} states 426 */ 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. 438 * 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 } 513 } 514 }); 515 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 857 /*globals wp, _, Backbone */ 858 859 /** 860 * wp.media.controller.Library 861 * 862 * A state for choosing an attachment or group of attachments from the media library. 863 * 864 * @class 865 * @augments wp.media.controller.State 866 * @augments Backbone.Model 867 * @mixes media.selectionSync 868 * 869 * @param {object} [attributes] The attributes hash passed to the state. 870 * @param {string} [attributes.id=library] Unique identifier. 871 * @param {string} [attributes.title=Media library] Title for the state. Displays in the media menu and the frame's title region. 872 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 873 * If one is not supplied, a collection of all attachments will be created. 874 * @param {wp.media.model.Selection|object} [attributes.selection] A collection to contain attachment selections within the state. 875 * If the 'selection' attribute is a plain JS object, 876 * a Selection will be created using its values as the selection instance's `props` model. 877 * Otherwise, it will copy the library's `props` model. 878 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 879 * @param {string} [attributes.content=upload] Initial mode for the content region. 880 * Overridden by persistent user setting if 'contentUserSetting' is true. 881 * @param {string} [attributes.menu=default] Initial mode for the menu region. 882 * @param {string} [attributes.router=browse] Initial mode for the router region. 883 * @param {string} [attributes.toolbar=select] Initial mode for the toolbar region. 884 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 885 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 886 * Accepts 'all', 'uploaded', or 'unattached'. 887 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 888 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 889 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 890 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 891 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 892 */ 893 var l10n = wp.media.view.l10n, 894 getUserSetting = window.getUserSetting, 895 setUserSetting = window.setUserSetting, 896 Library; 897 898 Library = wp.media.controller.State.extend({ 899 defaults: { 900 id: 'library', 901 title: l10n.mediaLibraryTitle, 902 multiple: false, 903 content: 'upload', 904 menu: 'default', 905 router: 'browse', 906 toolbar: 'select', 907 searchable: true, 908 filterable: false, 909 sortable: true, 910 autoSelect: true, 911 describe: false, 912 contentUserSetting: true, 913 syncSelection: true 914 }, 915 916 /** 917 * If a library isn't provided, query all media items. 918 * If a selection instance isn't provided, create one. 919 * 920 * @since 3.5.0 921 */ 922 initialize: function() { 923 var selection = this.get('selection'), 924 props; 925 926 if ( ! this.get('library') ) { 927 this.set( 'library', wp.media.query() ); 928 } 929 930 if ( ! ( selection instanceof wp.media.model.Selection ) ) { 931 props = selection; 932 933 if ( ! props ) { 934 props = this.get('library').props.toJSON(); 935 props = _.omit( props, 'orderby', 'query' ); 936 } 937 938 this.set( 'selection', new wp.media.model.Selection( null, { 939 multiple: this.get('multiple'), 940 props: props 941 }) ); 942 } 943 944 this.resetDisplays(); 945 }, 946 947 /** 948 * @since 3.5.0 949 */ 950 activate: function() { 951 this.syncSelection(); 952 953 wp.Uploader.queue.on( 'add', this.uploading, this ); 954 955 this.get('selection').on( 'add remove reset', this.refreshContent, this ); 956 957 if ( this.get( 'router' ) && this.get('contentUserSetting') ) { 958 this.frame.on( 'content:activate', this.saveContentMode, this ); 959 this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) ); 960 } 961 }, 962 963 /** 964 * @since 3.5.0 965 */ 966 deactivate: function() { 967 this.recordSelection(); 968 969 this.frame.off( 'content:activate', this.saveContentMode, this ); 970 971 // Unbind all event handlers that use this state as the context 972 // from the selection. 973 this.get('selection').off( null, null, this ); 974 975 wp.Uploader.queue.off( null, null, this ); 976 }, 977 978 /** 979 * Reset the library to its initial state. 980 * 981 * @since 3.5.0 982 */ 983 reset: function() { 984 this.get('selection').reset(); 985 this.resetDisplays(); 986 this.refreshContent(); 987 }, 988 989 /** 990 * Reset the attachment display settings defaults to the site options. 991 * 992 * If site options don't define them, fall back to a persistent user setting. 993 * 994 * @since 3.5.0 995 */ 996 resetDisplays: function() { 997 var defaultProps = wp.media.view.settings.defaultProps; 998 this._displays = []; 999 this._defaultDisplaySettings = { 1000 align: defaultProps.align || getUserSetting( 'align', 'none' ), 1001 size: defaultProps.size || getUserSetting( 'imgsize', 'medium' ), 1002 link: defaultProps.link || getUserSetting( 'urlbutton', 'file' ) 1003 }; 1004 }, 1005 1006 /** 1007 * Create a model to represent display settings (alignment, etc.) for an attachment. 1008 * 1009 * @since 3.5.0 1010 * 1011 * @param {wp.media.model.Attachment} attachment 1012 * @returns {Backbone.Model} 1013 */ 1014 display: function( attachment ) { 1015 var displays = this._displays; 1016 1017 if ( ! displays[ attachment.cid ] ) { 1018 displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) ); 1019 } 1020 return displays[ attachment.cid ]; 1021 }, 1022 1023 /** 1024 * Given an attachment, create attachment display settings properties. 1025 * 1026 * @since 3.6.0 1027 * 1028 * @param {wp.media.model.Attachment} attachment 1029 * @returns {Object} 1030 */ 1031 defaultDisplaySettings: function( attachment ) { 1032 var settings = this._defaultDisplaySettings; 1033 if ( settings.canEmbed = this.canEmbed( attachment ) ) { 1034 settings.link = 'embed'; 1035 } 1036 return settings; 1037 }, 1038 1039 /** 1040 * Whether an attachment can be embedded (audio or video). 1041 * 1042 * @since 3.6.0 1043 * 1044 * @param {wp.media.model.Attachment} attachment 1045 * @returns {Boolean} 1046 */ 1047 canEmbed: function( attachment ) { 1048 // If uploading, we know the filename but not the mime type. 1049 if ( ! attachment.get('uploading') ) { 1050 var type = attachment.get('type'); 1051 if ( type !== 'audio' && type !== 'video' ) { 1052 return false; 1053 } 1054 } 1055 1056 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() ); 1057 }, 1058 1059 1060 /** 1061 * If the state is active, no items are selected, and the current 1062 * content mode is not an option in the state's router (provided 1063 * the state has a router), reset the content mode to the default. 1064 * 1065 * @since 3.5.0 1066 */ 1067 refreshContent: function() { 1068 var selection = this.get('selection'), 1069 frame = this.frame, 1070 router = frame.router.get(), 1071 mode = frame.content.mode(); 1072 1073 if ( this.active && ! selection.length && router && ! router.get( mode ) ) { 1074 this.frame.content.render( this.get('content') ); 1075 } 1076 }, 1077 1078 /** 1079 * Callback handler when an attachment is uploaded. 1080 * 1081 * Switch to the Media Library if uploaded from the 'Upload Files' tab. 1082 * 1083 * Adds any uploading attachments to the selection. 1084 * 1085 * If the state only supports one attachment to be selected and multiple 1086 * attachments are uploaded, the last attachment in the upload queue will 1087 * be selected. 1088 * 1089 * @since 3.5.0 1090 * 1091 * @param {wp.media.model.Attachment} attachment 1092 */ 1093 uploading: function( attachment ) { 1094 var content = this.frame.content; 1095 1096 if ( 'upload' === content.mode() ) { 1097 this.frame.content.mode('browse'); 1098 } 1099 1100 if ( this.get( 'autoSelect' ) ) { 1101 this.get('selection').add( attachment ); 1102 this.frame.trigger( 'library:selection:add' ); 1103 } 1104 }, 1105 1106 /** 1107 * Persist the mode of the content region as a user setting. 1108 * 1109 * @since 3.5.0 1110 */ 1111 saveContentMode: function() { 1112 if ( 'browse' !== this.get('router') ) { 1113 return; 1114 } 1115 1116 var mode = this.frame.content.mode(), 1117 view = this.frame.router.get(); 1118 1119 if ( view && view.get( mode ) ) { 1120 setUserSetting( 'libraryContent', mode ); 1121 } 1122 } 1123 }); 1124 1125 // Make selectionSync available on any Media Library state. 1126 _.extend( Library.prototype, wp.media.selectionSync ); 1127 1128 module.exports = Library; 1129 1130 1131 /***/ }), 1132 /* 32 */ 1133 /***/ (function(module, exports) { 1134 2 1135 /*globals wp, _ */ 3 1136 4 1137 /** 5 * wp.media.controller.CollectionAdd 6 * 7 * A state for adding attachments to a collection (e.g. video playlist). 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. 8 1209 * 9 1210 * @class … … 12 1213 * @augments Backbone.Model 13 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 */ 1237 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 } 1274 1275 Library.prototype.initialize.apply( this, arguments ); 1276 }, 1277 1278 /** 1279 * @since 3.5.0 1280 */ 1281 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 * 14 1363 * @param {object} [attributes] The attributes hash passed to the state. 15 * @param {string} [attributes.id= library] Unique identifier.16 * @param {string} attributes.titleTitle for the state. Displays in the frame's title region.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. 17 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. 18 1367 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 19 * If one is not supplied, a collection of a ttachments of the specified typewill be created.1368 * If one is not supplied, a collection of all images will be created. 20 1369 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 21 1370 * Accepts 'all', 'uploaded', or 'unattached'. … … 32 1381 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 33 1382 * 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 1383 */ 37 1384 var Selection = wp.media.model.Selection, 38 1385 Library = wp.media.controller.Library, 39 CollectionAdd; 40 41 CollectionAdd = Library.extend({ 42 defaults: _.defaults( { 43 // Selection defaults. @see media.model.Selection 1386 l10n = wp.media.view.l10n, 1387 GalleryAdd; 1388 1389 GalleryAdd = Library.extend({ 1390 defaults: _.defaults({ 1391 id: 'gallery-library', 1392 title: l10n.addToGalleryTitle, 44 1393 multiple: 'add', 45 // Attachments browser defaults. @see media.view.AttachmentsBrowser46 1394 filterable: 'uploaded', 47 1395 menu: 'gallery', 1396 toolbar: 'gallery-add', 48 1397 priority: 100, 49 1398 syncSelection: false … … 51 1400 52 1401 /** 53 * @since 3. 9.01402 * @since 3.5.0 54 1403 */ 55 1404 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`. 1405 // If a library wasn't supplied, create a library of images. 67 1406 if ( ! this.get('library') ) { 68 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 69 } 1407 this.set( 'library', wp.media.query({ type: 'image' }) ); 1408 } 1409 70 1410 Library.prototype.initialize.apply( this, arguments ); 71 1411 }, 72 1412 73 1413 /** 74 * @since 3. 9.01414 * @since 3.5.0 75 1415 */ 76 1416 activate: function() { 77 1417 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 ); 1418 edit = this.frame.state('gallery-edit').get('library'); 1419 1420 if ( this.editLibrary && this.editLibrary !== edit ) { 1421 library.unobserve( this.editLibrary ); 83 1422 } 84 1423 … … 94 1433 library.reset( library.mirroring.models, { silent: true }); 95 1434 library.observe( edit ); 96 this. set('editLibrary', edit);1435 this.editLibrary = edit; 97 1436 98 1437 Library.prototype.activate.apply( this, arguments ); … … 100 1439 }); 101 1440 102 module.exports = CollectionAdd; 103 104 },{}],2:[function(require,module,exports){ 1441 module.exports = GalleryAdd; 1442 1443 1444 /***/ }), 1445 /* 35 */ 1446 /***/ (function(module, exports) { 1447 105 1448 /*globals wp, Backbone */ 106 1449 … … 266 1609 module.exports = CollectionEdit; 267 1610 268 },{}],3:[function(require,module,exports){ 269 /*globals wp, _, Backbone */ 1611 1612 /***/ }), 1613 /* 36 */ 1614 /***/ (function(module, exports) { 1615 1616 /*globals wp, _ */ 270 1617 271 1618 /** 272 * wp.media.controller.C ropper273 * 274 * A state for cropping an image.1619 * wp.media.controller.CollectionAdd 1620 * 1621 * A state for adding attachments to a collection (e.g. video playlist). 275 1622 * 276 1623 * @class 1624 * @augments wp.media.controller.Library 277 1625 * @augments wp.media.controller.State 278 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'). 279 1650 */ 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 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 */ 295 1690 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 } 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 ); 353 1703 }; 354 1704 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 } ); 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 ); 381 1713 } 382 1714 }); 383 1715 384 module.exports = Cropper; 385 386 },{}],4:[function(require,module,exports){ 387 /*globals wp */ 388 389 /** 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. 406 */ 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 }) ); 459 } 460 }); 461 462 module.exports = EditImage; 463 464 },{}],5:[function(require,module,exports){ 465 /*globals wp, _, Backbone */ 466 467 /** 468 * wp.media.controller.Embed 469 * 470 * A state for embedding media from a URL. 471 * 472 * @class 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. 487 */ 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. 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){ 1716 module.exports = CollectionAdd; 1717 1718 1719 /***/ }), 1720 /* 37 */ 1721 /***/ (function(module, exports) { 1722 603 1723 /*globals wp, _ */ 604 1724 … … 724 1844 module.exports = FeaturedImage; 725 1845 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 /** 925 * @since 3.5.0 926 * 927 * @param browser 928 */ 929 gallerySettings: function( browser ) { 930 if ( ! this.get('displaySettings') ) { 931 return; 932 } 933 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 }); 958 } 959 }); 960 961 module.exports = GalleryEdit; 962 963 },{}],9:[function(require,module,exports){ 964 /*globals wp, _ */ 965 966 /** 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. 971 * 972 * @class 973 * @augments wp.media.controller.State 974 * @augments Backbone.Model 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. 989 */ 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 1009 * 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'); 1022 } 1023 }); 1024 1025 module.exports = ImageDetails; 1026 1027 },{}],10:[function(require,module,exports){ 1028 /*globals wp, _, Backbone */ 1029 1030 /** 1031 * wp.media.controller.Library 1032 * 1033 * A state for choosing an attachment or group of attachments from the media library. 1034 * 1035 * @class 1036 * @augments wp.media.controller.State 1037 * @augments Backbone.Model 1038 * @mixes media.selectionSync 1039 * 1040 * @param {object} [attributes] The attributes hash passed to the state. 1041 * @param {string} [attributes.id=library] Unique identifier. 1042 * @param {string} [attributes.title=Media library] Title for the state. Displays in the media menu and the frame's title region. 1043 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1044 * If one is not supplied, a collection of all attachments will be created. 1045 * @param {wp.media.model.Selection|object} [attributes.selection] A collection to contain attachment selections within the state. 1046 * If the 'selection' attribute is a plain JS object, 1047 * a Selection will be created using its values as the selection instance's `props` model. 1048 * Otherwise, it will copy the library's `props` model. 1049 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1050 * @param {string} [attributes.content=upload] Initial mode for the content region. 1051 * Overridden by persistent user setting if 'contentUserSetting' is true. 1052 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1053 * @param {string} [attributes.router=browse] Initial mode for the router region. 1054 * @param {string} [attributes.toolbar=select] Initial mode for the toolbar region. 1055 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1056 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 1057 * Accepts 'all', 'uploaded', or 'unattached'. 1058 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1059 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1060 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1061 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1062 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1063 */ 1064 var l10n = wp.media.view.l10n, 1065 getUserSetting = window.getUserSetting, 1066 setUserSetting = window.setUserSetting, 1067 Library; 1068 1069 Library = wp.media.controller.State.extend({ 1070 defaults: { 1071 id: 'library', 1072 title: l10n.mediaLibraryTitle, 1073 multiple: false, 1074 content: 'upload', 1075 menu: 'default', 1076 router: 'browse', 1077 toolbar: 'select', 1078 searchable: true, 1079 filterable: false, 1080 sortable: true, 1081 autoSelect: true, 1082 describe: false, 1083 contentUserSetting: true, 1084 syncSelection: true 1085 }, 1086 1087 /** 1088 * If a library isn't provided, query all media items. 1089 * If a selection instance isn't provided, create one. 1090 * 1091 * @since 3.5.0 1092 */ 1093 initialize: function() { 1094 var selection = this.get('selection'), 1095 props; 1096 1097 if ( ! this.get('library') ) { 1098 this.set( 'library', wp.media.query() ); 1099 } 1100 1101 if ( ! ( selection instanceof wp.media.model.Selection ) ) { 1102 props = selection; 1103 1104 if ( ! props ) { 1105 props = this.get('library').props.toJSON(); 1106 props = _.omit( props, 'orderby', 'query' ); 1107 } 1108 1109 this.set( 'selection', new wp.media.model.Selection( null, { 1110 multiple: this.get('multiple'), 1111 props: props 1112 }) ); 1113 } 1114 1115 this.resetDisplays(); 1116 }, 1117 1118 /** 1119 * @since 3.5.0 1120 */ 1121 activate: function() { 1122 this.syncSelection(); 1123 1124 wp.Uploader.queue.on( 'add', this.uploading, this ); 1125 1126 this.get('selection').on( 'add remove reset', this.refreshContent, this ); 1127 1128 if ( this.get( 'router' ) && this.get('contentUserSetting') ) { 1129 this.frame.on( 'content:activate', this.saveContentMode, this ); 1130 this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) ); 1131 } 1132 }, 1133 1134 /** 1135 * @since 3.5.0 1136 */ 1137 deactivate: function() { 1138 this.recordSelection(); 1139 1140 this.frame.off( 'content:activate', this.saveContentMode, this ); 1141 1142 // Unbind all event handlers that use this state as the context 1143 // from the selection. 1144 this.get('selection').off( null, null, this ); 1145 1146 wp.Uploader.queue.off( null, null, this ); 1147 }, 1148 1149 /** 1150 * Reset the library to its initial state. 1151 * 1152 * @since 3.5.0 1153 */ 1154 reset: function() { 1155 this.get('selection').reset(); 1156 this.resetDisplays(); 1157 this.refreshContent(); 1158 }, 1159 1160 /** 1161 * Reset the attachment display settings defaults to the site options. 1162 * 1163 * If site options don't define them, fall back to a persistent user setting. 1164 * 1165 * @since 3.5.0 1166 */ 1167 resetDisplays: function() { 1168 var defaultProps = wp.media.view.settings.defaultProps; 1169 this._displays = []; 1170 this._defaultDisplaySettings = { 1171 align: defaultProps.align || getUserSetting( 'align', 'none' ), 1172 size: defaultProps.size || getUserSetting( 'imgsize', 'medium' ), 1173 link: defaultProps.link || getUserSetting( 'urlbutton', 'file' ) 1174 }; 1175 }, 1176 1177 /** 1178 * Create a model to represent display settings (alignment, etc.) for an attachment. 1179 * 1180 * @since 3.5.0 1181 * 1182 * @param {wp.media.model.Attachment} attachment 1183 * @returns {Backbone.Model} 1184 */ 1185 display: function( attachment ) { 1186 var displays = this._displays; 1187 1188 if ( ! displays[ attachment.cid ] ) { 1189 displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) ); 1190 } 1191 return displays[ attachment.cid ]; 1192 }, 1193 1194 /** 1195 * Given an attachment, create attachment display settings properties. 1196 * 1197 * @since 3.6.0 1198 * 1199 * @param {wp.media.model.Attachment} attachment 1200 * @returns {Object} 1201 */ 1202 defaultDisplaySettings: function( attachment ) { 1203 var settings = this._defaultDisplaySettings; 1204 if ( settings.canEmbed = this.canEmbed( attachment ) ) { 1205 settings.link = 'embed'; 1206 } 1207 return settings; 1208 }, 1209 1210 /** 1211 * Whether an attachment can be embedded (audio or video). 1212 * 1213 * @since 3.6.0 1214 * 1215 * @param {wp.media.model.Attachment} attachment 1216 * @returns {Boolean} 1217 */ 1218 canEmbed: function( attachment ) { 1219 // If uploading, we know the filename but not the mime type. 1220 if ( ! attachment.get('uploading') ) { 1221 var type = attachment.get('type'); 1222 if ( type !== 'audio' && type !== 'video' ) { 1223 return false; 1224 } 1225 } 1226 1227 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() ); 1228 }, 1229 1230 1231 /** 1232 * If the state is active, no items are selected, and the current 1233 * content mode is not an option in the state's router (provided 1234 * the state has a router), reset the content mode to the default. 1235 * 1236 * @since 3.5.0 1237 */ 1238 refreshContent: function() { 1239 var selection = this.get('selection'), 1240 frame = this.frame, 1241 router = frame.router.get(), 1242 mode = frame.content.mode(); 1243 1244 if ( this.active && ! selection.length && router && ! router.get( mode ) ) { 1245 this.frame.content.render( this.get('content') ); 1246 } 1247 }, 1248 1249 /** 1250 * Callback handler when an attachment is uploaded. 1251 * 1252 * Switch to the Media Library if uploaded from the 'Upload Files' tab. 1253 * 1254 * Adds any uploading attachments to the selection. 1255 * 1256 * If the state only supports one attachment to be selected and multiple 1257 * attachments are uploaded, the last attachment in the upload queue will 1258 * be selected. 1259 * 1260 * @since 3.5.0 1261 * 1262 * @param {wp.media.model.Attachment} attachment 1263 */ 1264 uploading: function( attachment ) { 1265 var content = this.frame.content; 1266 1267 if ( 'upload' === content.mode() ) { 1268 this.frame.content.mode('browse'); 1269 } 1270 1271 if ( this.get( 'autoSelect' ) ) { 1272 this.get('selection').add( attachment ); 1273 this.frame.trigger( 'library:selection:add' ); 1274 } 1275 }, 1276 1277 /** 1278 * Persist the mode of the content region as a user setting. 1279 * 1280 * @since 3.5.0 1281 */ 1282 saveContentMode: function() { 1283 if ( 'browse' !== this.get('router') ) { 1284 return; 1285 } 1286 1287 var mode = this.frame.content.mode(), 1288 view = this.frame.router.get(); 1289 1290 if ( view && view.get( mode ) ) { 1291 setUserSetting( 'libraryContent', mode ); 1292 } 1293 } 1294 }); 1295 1296 // Make selectionSync available on any Media Library state. 1297 _.extend( Library.prototype, wp.media.selectionSync ); 1298 1299 module.exports = Library; 1300 1301 },{}],11:[function(require,module,exports){ 1302 /*globals wp, _ */ 1303 1304 /** 1305 * wp.media.controller.MediaLibrary 1306 * 1307 * @class 1308 * @augments wp.media.controller.Library 1309 * @augments wp.media.controller.State 1310 * @augments Backbone.Model 1311 */ 1312 var Library = wp.media.controller.Library, 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 }) ); 1334 1335 Library.prototype.initialize.apply( this, arguments ); 1336 }, 1337 1338 /** 1339 * @since 3.9.0 1340 */ 1341 activate: function() { 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 } 1347 Library.prototype.activate.apply( this, arguments ); 1348 } 1349 }); 1350 1351 module.exports = MediaLibrary; 1352 1353 },{}],12:[function(require,module,exports){ 1354 /*globals Backbone, _ */ 1355 1356 /** 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. 1368 * 1369 * @class 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. 1375 */ 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. 1386 * 1387 * @since 3.5.0 1388 * 1389 * @param {string} mode 1390 * 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 ) { 1515 return; 1516 } 1517 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; 1529 } 1530 }); 1531 1532 module.exports = Region; 1533 1534 },{}],13:[function(require,module,exports){ 1846 1847 /***/ }), 1848 /* 38 */ 1849 /***/ (function(module, exports) { 1850 1535 1851 /*globals wp, _ */ 1536 1852 … … 1642 1958 module.exports = ReplaceImage; 1643 1959 1644 },{}],14:[function(require,module,exports){ 1645 /*globals _, Backbone */ 1960 1961 /***/ }), 1962 /* 39 */ 1963 /***/ (function(module, exports) { 1964 1965 /*globals wp */ 1646 1966 1647 1967 /** 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 1968 * wp.media.controller.EditImage 1969 * 1970 * A state for editing (cropping, etc.) an image. 1656 1971 * 1657 1972 * @class 1973 * @augments wp.media.controller.State 1658 1974 * @augments Backbone.Model 1659 * @mixin 1660 * @mixes Backbone.Events 1661 * 1662 * @param {Array} states 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. 1663 1984 */ 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 } 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 }) ); 1750 2037 } 1751 2038 }); 1752 2039 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 ); 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 } 1764 2855 return this; 1765 2856 }; 1766 2857 }); 1767 2858 1768 module.exports = StateMachine; 1769 1770 },{}],15:[function(require,module,exports){ 1771 /*globals _, Backbone */ 2859 module.exports = MediaFrame; 2860 2861 2862 /***/ }), 2863 /* 48 */ 2864 /***/ (function(module, exports) { 2865 2866 /*globals wp, _ */ 1772 2867 1773 2868 /** 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. 2869 * wp.media.view.MediaFrame.Select 2870 * 2871 * A frame for selecting an item or items from the media library. 1786 2872 * 1787 2873 * @class 1788 * @augments Backbone.Model 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 1789 2880 */ 1790 var State = Backbone.Model.extend({ 1791 /** 1792 * Constructor. 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. 1793 2905 * 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. 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. 1813 2909 * 1814 * @abstract 1815 * @since 3.5.0 1816 */ 1817 ready: function() {}, 1818 1819 /** 1820 * Activate event callback. 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 ) { 2934 return; 2935 } 2936 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. 1821 2951 * 1822 * @abstract 1823 * @since 3.5.0 1824 */ 1825 activate: function() {}, 1826 1827 /** 1828 * Deactivate event callback. 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. 1829 2964 * 1830 * @abstract 1831 * @since 3.5.0 1832 */ 1833 deactivate: function() {}, 1834 1835 /** 1836 * Reset event callback. 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 2976 } 2977 }); 2978 }, 2979 2980 /** 2981 * Render callback for the content region in the `browse` mode. 1837 2982 * 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 ) { 1920 return; 1921 } 1922 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. 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 1972 3023 * 1973 * @access private 1974 * @since 3.5.0 1975 * 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; 1988 } 1989 } 1990 1991 if ( ! menuItem ) { 1992 return; 1993 } 1994 1995 view.set( this.id, menuItem ); 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 ); 1996 3033 } 1997 3034 }); 1998 3035 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 }; 3036 module.exports = Select; 3037 3038 3039 /***/ }), 3040 /* 49 */ 3041 /***/ (function(module, exports) { 3042 3043 /*globals wp, _ */ 3044 3045 /** 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 3058 */ 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 }) ); 3774 } 2009 3775 }); 2010 3776 2011 module.exports = State; 2012 2013 },{}],16:[function(require,module,exports){ 2014 /*globals _ */ 3777 module.exports = Post; 3778 3779 3780 /***/ }), 3781 /* 50 */ 3782 /***/ (function(module, exports) { 3783 3784 /*globals wp */ 2015 3785 2016 3786 /** 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 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 2025 3800 */ 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 ) { 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 ) { 2035 3870 return; 2036 3871 } 2037 3872 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. 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 }) ); 3956 } 3957 3958 }); 3959 3960 module.exports = ImageDetails; 3961 3962 3963 /***/ }), 3964 /* 51 */ 3965 /***/ (function(module, exports) { 3966 3967 /*globals wp, _, jQuery */ 3968 3969 /** 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. 2057 4154 * 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 ) { 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 ) { 2065 4211 return; 2066 4212 } 2067 4213 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; 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 } 2076 4225 } 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 }()); 4226 4227 }); 4228 4229 module.exports = FocusManager; 4230 4231 4232 /***/ }), 4233 /* 53 */ 4234 /***/ (function(module, exports) { 4235 4236 /*globals wp, _, jQuery */ 2118 4237 2119 4238 /** 2120 * A shared event bus used to provide events into 2121 * the media workflows that 3rd-party devs can use to hook 2122 * in. 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] 2123 4253 */ 2124 media.events = _.extend( {}, Backbone.Events ); 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 */ 2125 4354 2126 4355 /** 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(); 2152 } 2153 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 _ */ 2232 2233 /** 2234 * wp.media.view.AttachmentCompat 2235 * 2236 * A view to display fields added via the `attachment_fields_to_edit` filter. 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 2237 4360 * 2238 4361 * @class … … 2242 4365 */ 2243 4366 var View = wp.media.View, 2244 AttachmentCompat; 2245 2246 AttachmentCompat = View.extend({ 2247 tagName: 'form', 2248 className: 'compat-item', 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 */ 4384 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'), 2249 4597 2250 4598 events: { 2251 'submit': 'preventDefault', 2252 'change input': 'save', 2253 'change select': 'save', 2254 'change textarea': 'save' 4599 'click .close': 'hide' 2255 4600 }, 2256 4601 2257 4602 initialize: function() { 2258 this.listenTo( this.model, 'change:compat', this.render ); 2259 }, 2260 /** 2261 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 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 2262 4641 */ 2263 4642 dispose: function() { 2264 if ( this.$(':focus').length ) { 2265 this.save(); 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 ); 2266 4942 } 2267 4943 /** … … 2270 4946 return View.prototype.dispose.apply( this, arguments ); 2271 4947 }, 2272 /** 2273 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 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 4987 return this; 4988 }, 4989 /** 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 ); 5036 }); 5037 } 5038 }); 5039 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 2274 5216 */ 2275 5217 render: function() { 2276 var compat = this.model.get('compat'); 2277 if ( ! compat || ! compat.item ) { 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 ) { 2278 5573 return; 2279 5574 } 2280 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 2281 5835 this.views.detach(); 2282 this.$el.html( compat.item ); 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 2283 5852 this.views.render(); 5853 2284 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 ); 2285 6054 }, 2286 6055 /** … … 2291 6060 }, 2292 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 /** 2293 6095 * @param {Object} event 2294 6096 */ 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; 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 ); 2304 6143 }); 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'] ); 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 } 2312 6222 } 2313 6223 }); 2314 6224 2315 module.exports = AttachmentCompat; 2316 2317 },{}],19:[function(require,module,exports){ 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 2318 6670 /*globals _, jQuery */ 2319 6671 … … 2394 6746 module.exports = AttachmentFilters; 2395 6747 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){ 6748 6749 /***/ }), 6750 /* 75 */ 6751 /***/ (function(module, exports) { 6752 2489 6753 /*globals wp, _ */ 2490 6754 … … 2529 6793 module.exports = DateFilter; 2530 6794 2531 },{}],22:[function(require,module,exports){ 6795 6796 /***/ }), 6797 /* 76 */ 6798 /***/ (function(module, exports) { 6799 2532 6800 /*globals wp */ 2533 6801 … … 2590 6858 module.exports = Uploaded; 2591 6859 2592 },{}],23:[function(require,module,exports){ 2593 /*globals wp, _, jQuery */ 6860 6861 /***/ }), 6862 /* 77 */ 6863 /***/ (function(module, exports) { 6864 6865 /*globals wp */ 2594 6866 2595 6867 /** 2596 * wp.media.view.Attachment 6868 * wp.media.view.AttachmentFilters.All 2597 6869 * 2598 6870 * @class 6871 * @augments wp.media.view.AttachmentFilters 2599 6872 * @augments wp.media.View 2600 6873 * @augments wp.Backbone.View 2601 6874 * @augments Backbone.View 2602 6875 */ 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' ) 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 }; 6894 }); 6895 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 2619 6906 }; 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 2798 }); 2799 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') 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 2982 6932 }; 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 } 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; 3112 6951 } 3113 6952 }); 3114 6953 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){ 6954 module.exports = All; 6955 6956 6957 /***/ }), 6958 /* 78 */ 6959 /***/ (function(module, exports) { 6960 3677 6961 /*globals wp, _, jQuery */ 3678 6962 … … 4120 7404 module.exports = AttachmentsBrowser; 4121 7405 4122 },{}],31:[function(require,module,exports){ 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 4123 7528 /*globals wp, _ */ 4124 7529 … … 4152 7557 module.exports = Selection; 4153 7558 4154 },{}],32:[function(require,module,exports){ 4155 /*globals _, Backbone */ 7559 7560 /***/ }), 7561 /* 82 */ 7562 /***/ (function(module, exports) { 7563 7564 /*globals wp */ 4156 7565 4157 7566 /** 4158 * wp.media.view. ButtonGroup7567 * wp.media.view.Attachments.EditSelection 4159 7568 * 4160 7569 * @class 7570 * @augments wp.media.view.Attachment.Selection 7571 * @augments wp.media.view.Attachment 4161 7572 * @augments wp.media.View 4162 7573 * @augments wp.Backbone.View 4163 7574 * @augments Backbone.View 4164 7575 */ 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; 7576 var EditSelection = wp.media.view.Attachment.Selection.extend({ 7577 buttons: { 7578 close: true 4197 7579 } 4198 7580 }); 4199 7581 4200 module.exports = ButtonGroup; 4201 4202 },{}],33:[function(require,module,exports){ 7582 module.exports = EditSelection; 7583 7584 7585 /***/ }), 7586 /* 83 */ 7587 /***/ (function(module, exports) { 7588 4203 7589 /*globals _, Backbone */ 4204 7590 4205 7591 /** 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. 7592 * wp.media.view.Settings 4301 7593 * 4302 7594 * @class … … 4306 7598 */ 4307 7599 var View = wp.media.View, 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'), 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 4316 7611 initialize: function() { 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 }, 7612 this.model = this.model || new Backbone.Model(); 7613 this.listenTo( this.model, 'change', this.updateChanges ); 7614 }, 7615 4331 7616 prepare: function() { 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 }); 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 } 4354 7706 } 4355 7707 }); 4356 7708 4357 module.exports = Cropper; 4358 4359 },{}],35:[function(require,module,exports){ 7709 module.exports = Settings; 7710 7711 7712 /***/ }), 7713 /* 84 */ 7714 /***/ (function(module, exports) { 7715 4360 7716 /*globals wp, _ */ 4361 7717 4362 7718 /** 4363 * wp.media.view.EditImage 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. 4364 8014 * 4365 8015 * @class … … 4369 8019 */ 4370 8020 var View = wp.media.View, 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 ) ); 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'] ); 4411 8089 } 4412 4413 8090 }); 4414 8091 4415 module.exports = EditImage; 4416 4417 },{}],36:[function(require,module,exports){ 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 4418 8127 /** 4419 8128 * wp.media.view.Embed … … 4479 8188 module.exports = Embed; 4480 8189 4481 },{}],37:[function(require,module,exports){ 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 4482 8387 /*globals wp */ 4483 8388 … … 4514 8419 module.exports = EmbedImage; 4515 8420 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){ 8421 8422 /***/ }), 8423 /* 95 */ 8424 /***/ (function(module, exports) { 8425 5998 8426 /*globals wp, _, jQuery */ 5999 8427 … … 6165 8593 module.exports = ImageDetails; 6166 8594 6167 },{}],47:[function(require,module,exports){ 8595 8596 /***/ }), 8597 /* 96 */ 8598 /***/ (function(module, exports) { 8599 8600 /*globals wp, _, jQuery */ 8601 6168 8602 /** 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){ 6194 /*globals wp, _, jQuery */ 6195 6196 /** 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 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. 7150 8610 * 7151 8611 * @class … … 7155 8615 */ 7156 8616 var View = wp.media.View, 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 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'), 7168 8625 initialize: function() { 7169 this.model = this.model || new Backbone.Model(); 7170 this.listenTo( this.model, 'change', this.updateChanges ); 7171 }, 7172 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 }, 7173 8640 prepare: function() { 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 } 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 }); 7263 8663 } 7264 8664 }); 7265 8665 7266 module.exports = Settings; 7267 7268 },{}],58:[function(require,module,exports){ 8666 module.exports = Cropper; 8667 8668 8669 /***/ }), 8670 /* 97 */, 8671 /* 98 */, 8672 /* 99 */ 8673 /***/ (function(module, exports) { 8674 7269 8675 /*globals wp, _ */ 7270 8676 7271 8677 /** 7272 * wp.media.view. Settings.AttachmentDisplay8678 * wp.media.view.EditImage 7273 8679 * 7274 8680 * @class 7275 * @augments wp.media.view.Settings7276 8681 * @augments wp.media.View 7277 8682 * @augments wp.Backbone.View 7278 8683 * @augments Backbone.View 7279 8684 */ 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 } 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 ) ); 7359 8726 } 8727 7360 8728 }); 7361 8729 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){ 8730 module.exports = EditImage; 8731 8732 8733 /***/ }), 8734 /* 100 */ 8735 /***/ (function(module, exports) { 8736 7419 8737 /*globals _ */ 7420 8738 … … 7453 8771 module.exports = Spinner; 7454 8772 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]); 8773 8774 /***/ }) 8775 /******/ ])); -
branches/4.2/src/wp-includes/pluggable.php
r45983 r46500 1075 1075 */ 1076 1076 function check_admin_referer( $action = -1, $query_arg = '_wpnonce' ) { 1077 if ( -1 == $action )1078 _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2 ' );1077 if ( -1 === $action ) 1078 _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2.0' ); 1079 1079 1080 1080 $adminurl = strtolower(admin_url()); … … 1096 1096 */ 1097 1097 do_action( 'check_admin_referer', $action, $result ); 1098 1099 if ( ! $result && ! ( -1 === $action && strpos( $referer, $adminurl ) === 0 ) ) { 1100 wp_nonce_ays( $action ); 1101 die(); 1102 } 1103 1098 1104 return $result; 1099 1105 } … … 1116 1122 */ 1117 1123 function check_ajax_referer( $action = -1, $query_arg = false, $die = true ) { 1124 if ( -1 === $action ) 1125 _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified by using the first parameter.' ), '3.2.0' ); 1126 1118 1127 $nonce = ''; 1119 1128 … … 2358 2367 } 2359 2368 endif; 2360 -
branches/4.2/src/wp-includes/query.php
r39961 r46500 1402 1402 , 'attachment_id' 1403 1403 , 'name' 1404 , 'static'1405 1404 , 'pagename' 1406 1405 , 'page_id' … … 1606 1605 // post is being queried. 1607 1606 $this->is_single = true; 1608 } elseif ( '' != $qv[' static'] || '' != $qv['pagename'] || !empty($qv['page_id']) ) {1607 } elseif ( '' != $qv['pagename'] || !empty($qv['page_id']) ) { 1609 1608 $this->is_page = true; 1610 1609 $this->is_single = false; -
branches/4.2/tests/phpunit/tests/auth.php
r30576 r46500 109 109 } 110 110 111 /** 112 * @ticket 36361 113 */ 114 public function test_check_admin_referer_with_no_action_triggers_doing_it_wrong() { 115 $this->setExpectedIncorrectUsage( 'check_admin_referer' ); 116 117 // A valid nonce needs to be set so the check doesn't die() 118 $_REQUEST['_wpnonce'] = wp_create_nonce( -1 ); 119 $result = check_admin_referer(); 120 $this->assertSame( 1, $result ); 121 122 unset( $_REQUEST['_wpnonce'] ); 123 } 124 125 /** 126 * @ticket 36361 127 */ 128 public function test_check_ajax_referer_with_no_action_triggers_doing_it_wrong() { 129 $this->setExpectedIncorrectUsage( 'check_ajax_referer' ); 130 131 // A valid nonce needs to be set so the check doesn't die() 132 $_REQUEST['_wpnonce'] = wp_create_nonce( -1 ); 133 $result = check_ajax_referer(); 134 $this->assertSame( 1, $result ); 135 136 unset( $_REQUEST['_wpnonce'] ); 137 } 138 111 139 function test_password_length_limit() { 112 140 $passwords = array(
Note: See TracChangeset
for help on using the changeset viewer.